- 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 缓存
101 lines
3.3 KiB
TypeScript
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>
|
|
);
|
|
}
|