feat: 添加会话标题自动生成功能
- 后端:首次 AI 回复后自动从用户消息提取标题 - 后端:通过 WebSocket 推送 session_updated 事件 - 前端:useChat hook 处理标题更新事件 - 前端:Sidebar 组件实时更新会话标题显示
This commit is contained in:
@@ -196,6 +196,20 @@ export async function processMessage(sessionId: string, content: string): Promis
|
||||
payload: assistantMessage,
|
||||
});
|
||||
|
||||
// 检查是否需要生成会话标题(首次对话完成后)
|
||||
const session = sessionManager.get(sessionId);
|
||||
const messages = sessionManager.getMessages(sessionId);
|
||||
if (session && !session.name && messages.length === 2) {
|
||||
// 首条用户消息 + 首条 AI 回复 = 2 条消息
|
||||
const userMessage = messages.find(m => m.role === 'user');
|
||||
if (userMessage) {
|
||||
// 异步生成标题,不阻塞响应
|
||||
generateSessionTitle(sessionId, userMessage.content, response).catch(err => {
|
||||
console.error('[Agent] Failed to generate session title:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
emitStatusEvent(sessionId, 'idle');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
@@ -247,3 +261,62 @@ export function getAgentStats(sessionId: string): {
|
||||
contextUsage: agent.getContextUsageFormatted(),
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 会话标题生成
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 从用户消息中提取简短标题
|
||||
* 使用简单的启发式方法,不依赖 LLM
|
||||
*/
|
||||
function extractTitleFromMessage(userMessage: string): string {
|
||||
// 移除多余空白
|
||||
const cleaned = userMessage.trim().replace(/\s+/g, ' ');
|
||||
|
||||
// 如果消息很短,直接使用
|
||||
if (cleaned.length <= 30) {
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
// 尝试提取第一句话
|
||||
const firstSentence = cleaned.match(/^[^。!?.!?]+[。!?.!?]?/)?.[0] || cleaned;
|
||||
|
||||
// 如果第一句话也很长,截断
|
||||
if (firstSentence.length > 40) {
|
||||
return firstSentence.slice(0, 37) + '...';
|
||||
}
|
||||
|
||||
return firstSentence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成会话标题并更新
|
||||
*/
|
||||
async function generateSessionTitle(
|
||||
sessionId: string,
|
||||
userMessage: string,
|
||||
_assistantResponse: string
|
||||
): Promise<void> {
|
||||
const sessionManager = getSessionManager();
|
||||
|
||||
// 使用简单提取方式生成标题
|
||||
const title = extractTitleFromMessage(userMessage);
|
||||
|
||||
// 更新会话标题
|
||||
const updatedSession = await sessionManager.updateSessionName(sessionId, title);
|
||||
|
||||
if (updatedSession) {
|
||||
// 广播标题更新事件
|
||||
broadcastToSession(sessionId, {
|
||||
type: 'session_updated',
|
||||
sessionId,
|
||||
payload: {
|
||||
id: updatedSession.id,
|
||||
name: updatedSession.name,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[Agent] Session title generated: "${title}"`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +253,22 @@ export class SessionManager {
|
||||
return fullMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会话名称/标题
|
||||
*/
|
||||
async updateSessionName(sessionId: string, name: string): Promise<Session | undefined> {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return undefined;
|
||||
|
||||
session.name = name;
|
||||
session.updatedAt = new Date().toISOString();
|
||||
|
||||
// 持久化
|
||||
await this.persist(sessionId);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话数量
|
||||
*/
|
||||
|
||||
@@ -108,7 +108,8 @@ export interface ServerMessage {
|
||||
| 'tool_result'
|
||||
| 'done'
|
||||
| 'cancelled'
|
||||
| 'error';
|
||||
| 'error'
|
||||
| 'session_updated';
|
||||
sessionId: string;
|
||||
payload?: unknown;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user