refactor: 标题生成重构、UI样式优化、测试增强
- 将标题生成逻辑提取为独立函数,提前到Agent调用前非阻塞执行 - 修复模型/供应商不存在时的HTTP状态码 500→400 - ChatPanel: 分离模型选择useEffect、CSS类替代内联样式、按钮样式统一 - use-conversations: fetchConversations/fetchMessages改用handleResponse去重 - 聊天面板滚动优化(scroll-behavior: smooth, overflow-anchor: auto) - 测试: mock支持onFinish回调,新增首次消息标题生成测试 - 移除未使用的SendMessageRequest接口
This commit is contained in:
@@ -79,12 +79,12 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
||||
const d = wrap(db);
|
||||
const modelRow = d.select().from(models).where(eq(models.id, conversation.modelId)).get();
|
||||
if (!modelRow) {
|
||||
return jsonResponse(createApiError(`模型不存在: ${conversation.modelId}`, 500), { mode, status: 500 });
|
||||
return jsonResponse(createApiError(`模型不存在: ${conversation.modelId}`, 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
const providerRow = d.select().from(providers).where(eq(providers.id, modelRow.providerId)).get();
|
||||
if (!providerRow) {
|
||||
return jsonResponse(createApiError(`供应商不存在: ${modelRow.providerId}`, 500), { mode, status: 500 });
|
||||
return jsonResponse(createApiError(`供应商不存在: ${modelRow.providerId}`, 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
const registry = buildProviderRegistry(db);
|
||||
@@ -95,6 +95,17 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
||||
}
|
||||
|
||||
try {
|
||||
const firstUserMsg = body.messages.find((m) => m.role === "user");
|
||||
const firstUserText =
|
||||
firstUserMsg?.parts
|
||||
?.filter((p) => p.type === "text")
|
||||
.map((p) => p.text)
|
||||
.join("") ?? "";
|
||||
|
||||
if (conversation.title === "新会话" && firstUserText) {
|
||||
generateConversationTitle(firstUserText, model, db, conversation.id, logger);
|
||||
}
|
||||
|
||||
const agent = createAlfredAgent(model);
|
||||
return await createAgentUIStreamResponse({
|
||||
agent,
|
||||
@@ -110,58 +121,6 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
||||
role: "assistant",
|
||||
});
|
||||
updateConversationTimestamp(db, conversation.id);
|
||||
|
||||
try {
|
||||
if (conversation.title === "新会话") {
|
||||
const firstUserText =
|
||||
body.messages
|
||||
?.find((m) => m.role === "user")
|
||||
?.parts?.filter((p) => p.type === "text")
|
||||
?.map((p) => p.text)
|
||||
?.join("") ?? "";
|
||||
|
||||
if (firstUserText) {
|
||||
if (firstUserText.length <= 5) {
|
||||
updateConversation(db, conversation.id, { title: firstUserText });
|
||||
} else {
|
||||
void generateText({
|
||||
model,
|
||||
prompt: `请根据以下对话开头生成一个简短标题(不超过10个字):${firstUserText}`,
|
||||
system: "你是一个标题生成助手,只返回标题文本,不要解释。",
|
||||
})
|
||||
.then((result) => {
|
||||
const title = result.text.trim().slice(0, 10);
|
||||
updateConversation(db, conversation.id, { title: title || firstUserText.slice(0, 10) });
|
||||
})
|
||||
.catch((titleError: unknown) => {
|
||||
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
|
||||
logger.error({ conversationId: conversation.id, error: titleMsg }, "标题生成失败");
|
||||
try {
|
||||
updateConversation(db, conversation.id, { title: firstUserText.slice(0, 10) });
|
||||
} catch {
|
||||
logger.error({ conversationId: conversation.id }, "标题兜底更新失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (titleError: unknown) {
|
||||
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
|
||||
logger.error({ conversationId: conversation.id, error: titleMsg }, "标题生成失败");
|
||||
try {
|
||||
const fallbackTitle =
|
||||
body.messages
|
||||
?.find((m) => m.role === "user")
|
||||
?.parts?.filter((p) => p.type === "text")
|
||||
?.map((p) => p.text)
|
||||
?.join("")
|
||||
?.slice(0, 10) ?? "新会话";
|
||||
updateConversation(db, conversation.id, { title: fallbackTitle });
|
||||
} catch (fallbackError: unknown) {
|
||||
const fbMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
||||
logger.error({ conversationId: conversation.id, error: fbMsg }, "标题兜底更新失败");
|
||||
}
|
||||
}
|
||||
},
|
||||
uiMessages: body.messages,
|
||||
});
|
||||
@@ -170,3 +129,35 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
||||
return jsonResponse(createApiError(`AI 调用失败:${msg}`, 500), { mode, status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
function generateConversationTitle(
|
||||
firstUserText: string,
|
||||
model: ReturnType<ReturnType<typeof buildProviderRegistry>["languageModel"]>,
|
||||
db: Database,
|
||||
conversationId: string,
|
||||
logger: Logger,
|
||||
): void {
|
||||
if (firstUserText.length <= 5) {
|
||||
updateConversation(db, conversationId, { title: firstUserText });
|
||||
return;
|
||||
}
|
||||
|
||||
void generateText({
|
||||
model,
|
||||
prompt: `请根据以下对话开头生成一个简短标题(不超过10个字):${firstUserText}`,
|
||||
system: "你是一个标题生成助手,只返回标题文本,不要解释。",
|
||||
})
|
||||
.then((result) => {
|
||||
const title = result.text.trim().slice(0, 10);
|
||||
updateConversation(db, conversationId, { title: title || firstUserText.slice(0, 10) });
|
||||
})
|
||||
.catch((titleError: unknown) => {
|
||||
const titleMsg = titleError instanceof Error ? titleError.message : String(titleError);
|
||||
logger.error({ conversationId, error: titleMsg }, "标题生成失败");
|
||||
try {
|
||||
updateConversation(db, conversationId, { title: firstUserText.slice(0, 10) });
|
||||
} catch {
|
||||
logger.error({ conversationId }, "标题兜底更新失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user