Files
Alfred/src/web/features/chat/ChatPage.tsx
lanyuanxiaoyao 5b09a16bc3 refactor(web): React 最佳实践优化 — memo/callback + 目录边界 + 路由增强
- useLogger: useMemo + JSON.stringify 替代 useState 派生
- useIsDark: effectiveTheme 替代 token 色值比较
- useCurrentProject: layouts/ 提升到 shared/hooks/
- ConsoleShell: locale useMemo 缓存
- ConsoleOutlet: 添加 Suspense 边界
- routes: 添加 layout 级 errorElement
- Table 组件: operationColumn useMemo + useCallback
- ChatPanel: footer 合并为 useCallback, props 传入模型数据
- ChatPage: textModels/conversations useMemo 缓存
2026-06-03 11:32:28 +08:00

101 lines
3.3 KiB
TypeScript

import { DeleteOutlined, MoreOutlined } from "@ant-design/icons";
import { Conversations } from "@ant-design/x";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { App, Spin } from "antd";
import { useMemo, useState } from "react";
import type { Conversation } from "../../../shared/api";
import { createConversation, deleteConversation, fetchConversations } from "../../shared/hooks/use-conversations";
import { useCurrentProject } from "../../shared/hooks/use-current-project";
import { useModelList } from "../../shared/hooks/use-models";
import { ChatPanel } from "./ChatPanel";
export function ChatPage() {
const project = useCurrentProject();
const [activeConversationId, setActiveConversationId] = useState<null | string>(null);
const queryClient = useQueryClient();
const { message } = App.useApp();
const CONVERSATIONS_KEY = ["conversations", project.id] as const;
const { data, isLoading } = useQuery({
queryFn: () => fetchConversations(project.id),
queryKey: CONVERSATIONS_KEY,
});
const { data: modelsData } = useModelList({ pageSize: 200 });
const textModels = useMemo(
() => (modelsData?.items ?? []).filter((m) => m.capabilities.includes("text")),
[modelsData],
);
const defaultModelId = textModels[0]?.id ?? null;
const deleteMutation = useMutation({
mutationFn: (id: string) => deleteConversation(project.id, id),
onError: (err: Error) => {
void message.error(`删除会话失败:${err.message}`);
},
onSuccess: (_data: void, id: string) => {
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
if (activeConversationId === id) setActiveConversationId(null);
},
});
const conversations = useMemo(
() => (data?.items ?? []).map((c: Conversation) => ({ key: c.id, label: c.title })),
[data],
);
return (
<div className="app-chat-page">
<div className="app-chat-conversations">
{isLoading ? (
<Spin />
) : (
<Conversations
activeKey={activeConversationId ?? ""}
creation={{
onClick: () => {
void createConversation(project.id, defaultModelId ?? undefined)
.then((conv) => {
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
setActiveConversationId(conv.id);
})
.catch((err: Error) => {
void message.error(`创建会话失败:${err.message}`);
});
},
}}
items={conversations}
menu={(conv) => ({
items: [
{
danger: true,
icon: <DeleteOutlined />,
key: "delete",
label: "删除",
onClick: () => {
deleteMutation.mutate(conv.key);
},
},
],
trigger: <MoreOutlined />,
})}
onActiveChange={(key) => setActiveConversationId(key)}
/>
)}
</div>
<ChatPanel
conversationId={activeConversationId}
defaultModelId={defaultModelId}
onConversationCreated={setActiveConversationId}
projectId={project.id}
textModels={textModels}
/>
</div>
);
}