diff --git a/src/routes/chatSession.ts b/src/routes/chatSession.ts index 03e90bf..7cb21dc 100644 --- a/src/routes/chatSession.ts +++ b/src/routes/chatSession.ts @@ -4,7 +4,10 @@ import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js"; import { collectTextContent } from "./chatStream.js"; -const TITLE_PROMPT_TIMEOUT_MS = 2500; +const TITLE_PROMPT_TIMEOUT_MS = 5000; +const TITLE_CONTEXT_MESSAGE_LIMIT = 40; +const TITLE_CONTEXT_CHAR_LIMIT = 2400; +const TITLE_CONTEXT_MESSAGE_CHAR_LIMIT = 240; const buildSessionTitle = (message: string) => { const normalized = message.replace(/\s+/g, " ").trim(); @@ -18,7 +21,7 @@ const buildTitleConversationContext = async ( runtime: OpencodeRuntimeAdapter, sessionId: string, ) => { - const messages = await runtime.messages(sessionId, 12); + const messages = await runtime.messages(sessionId, TITLE_CONTEXT_MESSAGE_LIMIT); const recentMessages = messages .filter( (message) => @@ -26,19 +29,35 @@ const buildTitleConversationContext = async ( ) .map((message) => ({ role: message.info.role, - content: collectTextContent(message.parts).replace(/\s+/g, " ").trim(), + content: collectTextContent(message.parts) + .replace(/\s+/g, " ") + .trim() + .slice(0, TITLE_CONTEXT_MESSAGE_CHAR_LIMIT), })) - .filter((message) => message.content.length > 0) - .slice(-6); + .filter((message) => message.content.length > 0); if (recentMessages.length === 0) { return ""; } - return recentMessages - .map((message) => `${message.role === "user" ? "用户" : "助手"}:${message.content}`) - .join("\n") - .slice(0, 2400); + const formattedMessages = recentMessages.map( + (message) => `${message.role === "user" ? "用户" : "助手"}:${message.content}`, + ); + const fullConversation = formattedMessages.join("\n"); + if (fullConversation.length <= TITLE_CONTEXT_CHAR_LIMIT) { + return fullConversation; + } + + const headCount = Math.min(4, formattedMessages.length); + const tailCount = Math.min(8, Math.max(0, formattedMessages.length - headCount)); + const middleOmitted = formattedMessages.length > headCount + tailCount; + const summary = [ + ...formattedMessages.slice(0, headCount), + ...(middleOmitted ? ["……(中间省略若干轮对话)"] : []), + ...formattedMessages.slice(-tailCount), + ].join("\n"); + + return summary.slice(0, TITLE_CONTEXT_CHAR_LIMIT); }; const normalizeGeneratedTitle = (rawTitle: string, fallback: string) => { @@ -75,15 +94,17 @@ export const generateSessionTitle = async ( titleSession.id, [ "你是会话标题生成器。", - "请根据用户问题生成一个 8-16 字中文标题。", + "请根据下面整段多轮对话生成一个 8-16 字中文标题。", "要求:简洁、可读、避免标点、不要引号、不要解释。", - "请优先概括最近这轮对话的核心任务或结论。", + "先理解完整对话,再概括核心任务或结论。", + "不要直接照抄用户任一条消息原文。", "只输出标题本身。", "", conversation, ].join("\n"), ) .then(async () => { + await runtime.waitForSessionIdle(titleSession.id, TITLE_PROMPT_TIMEOUT_MS); const messages = await runtime.messages(titleSession.id, 20); const assistantMessage = [...messages] .reverse()