PicoBot/web/src/hooks/chat/useTopics.ts
oudecheng c724bbf864 chore(web): prettier 一次性格式化并在 CI 启用 format:check
对 47 个前端文件统一执行 npm run format,消除存量格式差异。
随后在 CI 与 Makefile check 中启用 format:check,确保后续提交强制遵守 prettier 风格。
2026-08-03 23:25:22 +08:00

134 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<SetStateAction<Topic[]>>;
selectedTopic: string | null;
setSelectedTopic: Dispatch<SetStateAction<string | null>>;
topicRefreshTrigger: number;
bumpTopicRefreshTrigger: () => void;
topicsRef: MutableRefObject<Topic[]>;
selectedTopicRef: MutableRefObject<string | null>;
pendingNewTopicRef: MutableRefObject<boolean>;
/** 处理 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<Topic[]>([]);
const [selectedTopic, setSelectedTopic] = useState<string | null>(null);
const [topicRefreshTrigger, setTopicRefreshTrigger] = useState(0);
const topicsRef = useRef<Topic[]>([]);
const selectedTopicRef = useRef<string | null>(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,
};
}