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' })}`,