import { useState, useCallback, useRef, useEffect, type Dispatch, type SetStateAction, type MutableRefObject, } from 'react'; import type { Topic, TopicList, TopicRenamed, TopicSummary, Command } from '../../types/protocol'; export interface UseTopicsReturn { topics: Topic[]; setTopics: Dispatch>; selectedTopic: string | null; setSelectedTopic: Dispatch>; topicRefreshTrigger: number; bumpTopicRefreshTrigger: () => void; topicsRef: MutableRefObject; selectedTopicRef: MutableRefObject; pendingNewTopicRef: MutableRefObject; /** 处理 topic_list 消息:映射格式并按 pendingNewTopic 自动聚焦,返回是否自动聚焦了新话题 */ handleTopicList: (msg: TopicList) => boolean; /** 处理 topic_renamed 消息:用刷新后的列表替换本地状态(不改 selectedTopic) */ handleTopicRenamed: (msg: TopicRenamed) => void; createTopic: (title?: string) => Command; switchTopic: (topicId: string) => Command; deleteTopic: (topicId: string) => Command; renameTopic: (topicId: string, title: string) => Command; requestTopicList: (sessionId: string | null) => Command | null; } /** 将后端 TopicSummary[] 映射为前端 Topic[] */ function mapTopicSummaries(summaries: TopicSummary[]): Topic[] { return summaries.map((t) => ({ id: t.topic_id, session_id: t.session_id, title: t.title, description: t.description || undefined, message_count: Number(t.message_count), created_at: t.created_at, updated_at: t.last_active_at, })); } export function useTopics(): UseTopicsReturn { const [topics, setTopics] = useState([]); const [selectedTopic, setSelectedTopic] = useState(null); const [topicRefreshTrigger, setTopicRefreshTrigger] = useState(0); const topicsRef = useRef([]); const selectedTopicRef = useRef(null); const pendingNewTopicRef = useRef(false); // ref 同步:确保回调中读到最新值 useEffect(() => { topicsRef.current = topics; }, [topics]); useEffect(() => { selectedTopicRef.current = selectedTopic; }, [selectedTopic]); const bumpTopicRefreshTrigger = useCallback(() => { setTopicRefreshTrigger((n) => n + 1); }, []); const handleTopicList = useCallback((msg: TopicList): boolean => { const newTopics = mapTopicSummaries(msg.topics); setTopics(newTopics); // 新建话题后自动聚焦到新话题(列表按 last_active_at DESC 排序,第一个即最新) if (pendingNewTopicRef.current) { pendingNewTopicRef.current = false; if (newTopics.length > 0) { setSelectedTopic(newTopics[0].id); return true; } } return false; }, []); const handleTopicRenamed = useCallback((msg: TopicRenamed): void => { // 后端返回刷新后的完整列表,直接替换;selectedTopic 基于 id 不变,无需调整 setTopics(mapTopicSummaries(msg.topics)); }, []); 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' })}`, }; }, []); const switchTopic = useCallback((topicId: string): Command => { return { type: 'switch_topic', topic_id: topicId }; }, []); const deleteTopic = useCallback((topicId: string): Command => { return { type: 'delete_topic', topic_id: topicId }; }, []); const renameTopic = useCallback((topicId: string, title: string): Command => { return { type: 'rename_topic', topic_id: topicId, title }; }, []); const requestTopicList = useCallback((sessionId: string | null): Command | null => { if (!sessionId) return null; return { type: 'list_topics', session_id: sessionId }; }, []); return { topics, setTopics, selectedTopic, setSelectedTopic, topicRefreshTrigger, bumpTopicRefreshTrigger, topicsRef, selectedTopicRef, pendingNewTopicRef, handleTopicList, handleTopicRenamed, createTopic, switchTopic, deleteTopic, renameTopic, requestTopicList, }; }