From a4cdb31ba053e2b93206cdeb56dd8e1fa5c53a08 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Wed, 10 Jun 2026 11:26:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AF=9D=E9=A2=98?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A9=BA=E6=8F=8F=E8=BF=B0=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=EF=BC=9B=E6=9B=B4=E6=96=B0=E8=AF=9D=E9=A2=98?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=91=BD=E4=BB=A4=E4=BB=A5=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=81=9A=E7=84=A6=E6=96=B0=E8=AF=9D=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/topic_description.rs | 34 +++++++++++++++++++++++++++------- web/src/App.tsx | 2 +- web/src/hooks/useChat.ts | 12 +++++++++++- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/topic_description.rs b/src/topic_description.rs index 6d15666..ea04462 100644 --- a/src/topic_description.rs +++ b/src/topic_description.rs @@ -4,15 +4,17 @@ pub async fn generate_topic_description( provider: &dyn LLMProvider, first_user_message: &str, ) -> Result> { - let prompt = format!( - "请根据用户的第一句话,用简短的词语(不超过15字)描述这个对话的主题或意图。只输出描述内容,不要其他解释。\n\n用户消息:{}", - first_user_message - ); + let system_prompt = "你是一个话题摘要助手。请根据用户的第一句话,用简短的词语(不超过15字)描述这个对话的主题或意图。只输出描述内容,不要任何解释、标点或前缀。"; + + let user_prompt = format!("用户消息:{}", first_user_message); let request = ChatCompletionRequest { - messages: vec![Message::user(prompt)], - temperature: Some(0.3), - max_tokens: Some(50), + messages: vec![ + Message::system(system_prompt), + Message::user(user_prompt), + ], + temperature: Some(0.0), + max_tokens: Some(1024), // 给 reasoning 模型留足思考空间 tools: None, }; @@ -20,6 +22,24 @@ pub async fn generate_topic_description( let description = response.content.trim().to_string(); if description.is_empty() { + // 回退:reasoning 模型有时把所有 token 都消耗在推理上,content 为空 + // 此时尝试从 reasoning_content 中提取最后一行有意义的内容作为描述 + if let Some(ref reasoning) = response.reasoning_content { + let fallback: String = reasoning + .lines() + .rev() + .find(|line| { + let trimmed = line.trim(); + !trimmed.is_empty() && trimmed.len() <= 50 + }) + .unwrap_or("") + .trim() + .to_string(); + if !fallback.is_empty() { + let truncated: String = fallback.chars().take(50).collect(); + return Ok(truncated); + } + } return Err("LLM returned empty description".into()); } diff --git a/web/src/App.tsx b/web/src/App.tsx index e58a531..c4e0795 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -202,7 +202,7 @@ function App() { let cmd: Command switch (command) { case 'new': - cmd = { type: 'create_session', title: args.join(' ') || undefined } + cmd = createTopic(args.join(' ') || undefined) break case 'list': cmd = { type: 'list_sessions', include_archived: args[0] === 'all' } diff --git a/web/src/hooks/useChat.ts b/web/src/hooks/useChat.ts index 93b0b6e..ce3d834 100644 --- a/web/src/hooks/useChat.ts +++ b/web/src/hooks/useChat.ts @@ -158,6 +158,7 @@ export function useChat(): UseChatReturn { const schedulerViewRef = useRef(null) const topicsRef = useRef([]) const selectedTopicRef = useRef(null) + const pendingNewTopicRef = useRef(false) const isConnected = useMemo(() => connectionId !== null, [connectionId]) const selectedSession = useMemo( @@ -402,7 +403,15 @@ export function useChat(): UseChatReturn { })) setTopics(newTopics) - // 默认选中第一个 Topic(如果没有选中) + // 新建话题后自动聚焦到新话题(列表按 last_active_at DESC 排序,第一个即最新) + if (pendingNewTopicRef.current) { + pendingNewTopicRef.current = false + if (newTopics.length > 0) { + setSelectedTopic(newTopics[0].id) + setMessages([]) + } + } + setIsLoading(false) break } @@ -592,6 +601,7 @@ export function useChat(): UseChatReturn { }, []) const createTopic = useCallback((title?: string): Command => { + pendingNewTopicRef.current = true return { type: 'create_session', title: title || `话题 ${new Date().toLocaleString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}`,