- Новая таблица dialog_messages: последние DIALOG_LIMIT пар «вопрос — ответ», подкладываются в каждый запрос - /clear сбрасывает только контекст диалога, архив (включая импорт) не трогает; без подтверждения - /context показывает контекст и архив по отдельности Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
3.8 KiB
TypeScript
72 lines
3.8 KiB
TypeScript
import type { ChatMember, ChatMessage, ImageAttachment, ImageSource, Persona } from "../../types.js";
|
||
import { formatChatMembers } from "./format-chat-members.js";
|
||
import { formatChatMessages } from "./format-chat-messages.js";
|
||
|
||
export interface Question {
|
||
chatId: number;
|
||
chatTitle: string;
|
||
/** Как зовут спрашивающего: имя, фамилия и ник. */
|
||
askerName: string;
|
||
/** Может быть пустым, если прислали только картинку. */
|
||
text: string;
|
||
/** Сообщение, на которое ответил спрашивающий, — сразу передаём его как контекст. */
|
||
repliedTo?: ChatMessage;
|
||
/** Участники, упомянутые в вопросе через @ник или по имени-ссылке. */
|
||
mentionedMembers: ChatMember[];
|
||
/** Упомянутые ники, которых бот в чате не встречал. */
|
||
unknownUsernames: string[];
|
||
/** Где в вопросе есть картинки — даже если модели их не отправляем. */
|
||
imageSources: ImageSource[];
|
||
/** Скачанные картинки для модели; пусто, если модель их не видит или скачать не вышло. */
|
||
images: ImageAttachment[];
|
||
}
|
||
|
||
const IMAGE_SOURCES: Record<ImageSource, string> = {
|
||
question: "из вопроса",
|
||
replied: "из сообщения, на которое ответили",
|
||
};
|
||
|
||
/** Подсказка модели, откуда картинки — или что посмотреть их она не может. */
|
||
const imagesNote = ({ imageSources, images }: Question): string | undefined => {
|
||
if (imageSources.length === 0) return undefined;
|
||
|
||
if (images.length === 0) return "К сообщению приложено изображение, но ты не можешь его просмотреть — честно скажи об этом.";
|
||
|
||
return `Приложены изображения (по порядку): ${images.map((image) => IMAGE_SOURCES[image.source]).join(", ")}.`;
|
||
};
|
||
|
||
const mentionsSection = (question: Question): string | undefined => {
|
||
const known = question.mentionedMembers.length > 0 ? `Упомянуты участники:\n${formatChatMembers(question.mentionedMembers)}` : "";
|
||
|
||
const unknown =
|
||
question.unknownUsernames.length > 0
|
||
? `Эти ники в чате не встречались: ${question.unknownUsernames.map((u) => `@${u}`).join(", ")}.`
|
||
: "";
|
||
|
||
return [known, unknown].filter(Boolean).join("\n") || undefined;
|
||
};
|
||
|
||
/** Системные правила бота из `prompts/system.md` + личность + контекст конкретного вопроса. */
|
||
export const buildInstructions = (systemRules: string, question: Question, persona: Persona): string =>
|
||
[
|
||
`Твоя личность: ${persona.name}.\n${persona.prompt}`,
|
||
systemRules,
|
||
`Чат: «${question.chatTitle}». Вопрос задаёт ${question.askerName}.`,
|
||
mentionsSection(question),
|
||
]
|
||
.filter(Boolean)
|
||
.join("\n\n");
|
||
|
||
export const buildPrompt = (question: Question): string => {
|
||
const replied = question.repliedTo
|
||
? `Вопрос задан в ответ на сообщение:\n${formatChatMessages([question.repliedTo])}`
|
||
: undefined;
|
||
|
||
const asked = question.text || "(текста нет — прислали только изображение, опиши и прокомментируй его)";
|
||
|
||
return [replied, imagesNote(question), replied ? `Вопрос: ${asked}` : asked].filter(Boolean).join("\n\n");
|
||
};
|
||
|
||
/** Текст реплики пользователя — с именем, потому что в чате боту пишут разные админы. */
|
||
export const buildUserText = (question: Question): string => `${question.askerName}:\n${buildPrompt(question)}`;
|