feat: organize project conversations and add coding teacher side chat
This commit is contained in:
1 parent
2f82b9f79c
commit
9242a40d87
45 files changed
+2987
-1137
No files matched your search
@@ -1,20 +1,16 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft, Check, Cpu, Files, Plus, Puzzle, Trash2, Upload, X } from 'lucide-react';
|
||||
import { ArrowLeft, Check, Cpu, Files, Puzzle, Trash2, Upload, X } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetTitle } from '@/components/ui/sheet';
|
||||
import { AgentCreationDialog, type AgentCreationInput } from '@/components/coding/AgentCreationDialog';
|
||||
import { ProjectPublishAction } from '@/components/works/ProjectPublishAction';
|
||||
import { Plugins } from '@/pages/Plugins';
|
||||
import { PROJECT_SETTINGS_PLUGINS_PATH } from '@/pages/Plugins/legacy-plugin-redirect';
|
||||
import { buildCodingModelOptions, parseCodingModelKey, type CodingModelOption } from '@/lib/coding-model-options';
|
||||
import { getCodingSkills } from '@/lib/coding-product-tools';
|
||||
import { abortCodingConversation } from '@/lib/coding-conversations';
|
||||
import { getAgentAvatarSrc } from '@/lib/agent-avatars';
|
||||
import { codingConversationStore } from '@/stores/coding-conversations';
|
||||
import { useCodingWorkspaceStore } from '@/stores/coding-workspace';
|
||||
import { useProjectConfigStore } from '@/stores/project-config';
|
||||
import { useProviderStore } from '@/stores/providers';
|
||||
@@ -39,28 +35,6 @@ const THINKING_LEVEL_LABELS = {
|
||||
max: '最高',
|
||||
} as const;
|
||||
|
||||
function createCustomAgent(index: number, modelKey: string): CodingProjectAgent {
|
||||
const now = new Date().toISOString();
|
||||
const model = parseCodingModelKey(modelKey);
|
||||
return {
|
||||
id: `custom-agent-${Date.now()}-${index}`,
|
||||
avatarId: 'avatar-01',
|
||||
roleName: '项目智能体',
|
||||
name: '',
|
||||
builtIn: false,
|
||||
enabled: true,
|
||||
model: model ? { ...model, thinkingLevel: 'off' } : null,
|
||||
modelResolution: model ? 'resolved' : 'required',
|
||||
skillIds: [],
|
||||
responsibility: { mission: '', owns: [], boundaries: [], collaborators: [], principles: [] },
|
||||
prompt: '',
|
||||
archivedAt: null,
|
||||
pinned: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
function ResourceCard({ id, icon, title, subtitle, onClick }: { id: string; icon: React.ReactNode; title: string; subtitle: string; onClick: () => void }) {
|
||||
return <button type="button" data-testid={`resource-card-${id}`} onClick={onClick} className="config-motion-resource surface-card motion-press min-w-0 rounded-2xl border border-border/70 bg-background p-3 text-left shadow-none hover:shadow-soft">
|
||||
<div className="flex min-w-0 items-center gap-2"><span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-surface-subtle text-brand [&>svg]:h-4 [&>svg]:w-4">{icon}</span><p className="truncate text-sm font-semibold">{title}</p></div><p className="mt-1.5 truncate text-[11px] font-medium text-muted-foreground">{subtitle}</p>
|
||||
@@ -89,18 +63,6 @@ function PluginWorkspaceSheet({ open, onClose }: { open: boolean; onClose: () =>
|
||||
</Sheet>;
|
||||
}
|
||||
|
||||
function AgentCard({
|
||||
agent,
|
||||
capabilityCount,
|
||||
onOpen,
|
||||
}: {
|
||||
agent: CodingProjectAgent;
|
||||
capabilityCount: number;
|
||||
onOpen: () => void;
|
||||
}) {
|
||||
return <button type="button" data-testid={`project-agent-${agent.id}`} onClick={onOpen} className="config-motion-agent surface-card motion-press h-[260px] w-[210px] flex-none rounded-2xl border border-border/70 bg-background p-4 text-left shadow-none hover:shadow-soft"><img src={getAgentAvatarSrc(agent.avatarId, agent.avatarDataUrl)} alt="" className="agent-motion-avatar mx-auto h-20 w-20 rounded-xl border border-border object-cover [image-rendering:pixelated]" /><div className="mt-4 flex items-center justify-between gap-2"><p className="truncate font-semibold">{agent.name || '未命名'}</p><Badge className="shrink-0 border border-brand/15 bg-brand-soft text-brand">可用</Badge></div><p className="mt-1 truncate text-xs font-semibold text-muted-foreground">{agent.roleName}</p><p className="mt-3 line-clamp-3 min-h-12 text-xs text-muted-foreground">{agent.responsibility.mission}</p><p className="mt-2 text-[11px] text-muted-foreground">{capabilityCount} 项插件能力</p></button>;
|
||||
}
|
||||
|
||||
function ModelCard({ model }: { model: CodingModelOption }) {
|
||||
return <div data-testid={`model-card-${model.key.replace(/[^a-zA-Z0-9_-]+/gu, '-')}`} className="rounded-xl border border-foreground/15 bg-white p-3">
|
||||
<div className="flex items-start gap-3">
|
||||
@@ -133,11 +95,11 @@ function ModelCard({ model }: { model: CodingModelOption }) {
|
||||
export function ModelList({ models }: { models: CodingModelOption[] }) {
|
||||
if (models.length === 0) {
|
||||
return <div data-testid="project-model-empty-state" className="rounded-xl border border-dashed border-foreground/15 bg-white p-4 text-sm font-medium text-muted-foreground">
|
||||
当前没有可用模型,请先在模型设置中完成配置。智能体的默认模型在智能体配置中设置。
|
||||
当前没有可用模型,请先在模型设置中完成配置。默认模型在项目编程配置中设置。
|
||||
</div>;
|
||||
}
|
||||
return <div data-testid="project-model-list" className="space-y-3">
|
||||
<p className="text-xs font-medium leading-5 text-muted-foreground">此处仅展示已经配置的模型。每个智能体可单独设置新对话使用的默认模型。</p>
|
||||
<p className="text-xs font-medium leading-5 text-muted-foreground">此处仅展示已经配置的模型。项目可设置新对话使用的默认模型。</p>
|
||||
<div className="space-y-2">
|
||||
{models.map((model) => <ModelCard key={model.key} model={model} />)}
|
||||
</div>
|
||||
@@ -146,7 +108,6 @@ export function ModelList({ models }: { models: CodingModelOption[] }) {
|
||||
|
||||
export function ProjectConfiguration() {
|
||||
const activeProject = useCodingWorkspaceStore((state) => state.activeProject);
|
||||
const conversations = useCodingWorkspaceStore((state) => state.conversations);
|
||||
const removeProject = useCodingWorkspaceStore((state) => state.removeProject);
|
||||
const reloadWorkspace = useCodingWorkspaceStore((state) => state.load);
|
||||
const stored = useProjectConfigStore((state) => activeProject ? state.configsByProjectId[activeProject.id] : undefined);
|
||||
@@ -164,8 +125,6 @@ export function ProjectConfiguration() {
|
||||
const [skills, setSkills] = useState<SkillInfo[]>([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [partnerDialogOpen, setPartnerDialogOpen] = useState(false);
|
||||
const [archiveAgentId, setArchiveAgentId] = useState<string | null>(null);
|
||||
const knowledgeInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -187,7 +146,6 @@ export function ProjectConfiguration() {
|
||||
const requestedAgentId = new URLSearchParams(location.search).get('agent');
|
||||
if (!requestedAgentId || !draft?.agents.some((agent) => agent.id === requestedAgentId)) return;
|
||||
setActiveAgentId(requestedAgentId);
|
||||
setPartnerDialogOpen(true);
|
||||
}, [draft, location.search]);
|
||||
useEffect(() => { loadSkills(); }, [loadSkills]);
|
||||
useEffect(() => { void refreshProviderSnapshot(); }, [refreshProviderSnapshot]);
|
||||
@@ -195,7 +153,7 @@ export function ProjectConfiguration() {
|
||||
() => buildCodingModelOptions(providerAccounts, providerVendors),
|
||||
[providerAccounts, providerVendors],
|
||||
);
|
||||
const activeAgent = draft?.agents.find((agent) => agent.id === activeAgentId) ?? null;
|
||||
const activeAgent = draft?.agents.find((agent) => agent.id === (activeAgentId ?? draft.defaultAgentId)) ?? null;
|
||||
const playSaveConfirmation = () => undefined;
|
||||
const handleBack = () => navigate(-1);
|
||||
const pluginWorkspaceOpen = location.pathname === PROJECT_SETTINGS_PLUGINS_PATH;
|
||||
@@ -208,65 +166,11 @@ export function ProjectConfiguration() {
|
||||
navigate('/project-config', { replace: true });
|
||||
};
|
||||
|
||||
if (!activeProject) return <><main data-testid="project-configuration-empty-state" className="min-h-full bg-background p-8 text-foreground"><div className="mx-auto w-full max-w-7xl"><h1 className="text-xl font-semibold">项目配置</h1><p className="mt-4 text-xl font-semibold tracking-[-0.02em]">一念成光,万物可创。</p><p className="mt-2 text-sm font-medium text-muted-foreground">新建或打开一个项目后即可配置智能体、知识与项目能力;插件目录仍可在这里查看。</p><div className="mt-6 max-w-sm"><ResourceCard id="plugins" icon={<Puzzle />} title="插件" subtitle="查看可选与已安装能力" onClick={openPluginWorkspace} /></div></div></main><PluginWorkspaceSheet open={pluginWorkspaceOpen} onClose={closePluginWorkspace} /></>;
|
||||
if (!activeProject) return <><main data-testid="project-configuration-empty-state" className="min-h-full bg-background p-8 text-foreground"><div className="mx-auto w-full max-w-7xl"><h1 className="text-xl font-semibold">项目配置</h1><p className="mt-4 text-xl font-semibold tracking-[-0.02em]">一念成光,万物可创。</p><p className="mt-2 text-sm font-medium text-muted-foreground">新建或打开一个项目后即可配置模型、提示词、知识与项目能力;插件目录仍可在这里查看。</p><div className="mt-6 max-w-sm"><ResourceCard id="plugins" icon={<Puzzle />} title="插件" subtitle="查看可选与已安装能力" onClick={openPluginWorkspace} /></div></div></main><PluginWorkspaceSheet open={pluginWorkspaceOpen} onClose={closePluginWorkspace} /></>;
|
||||
if (!draft) return <div className="p-8 text-center text-sm font-medium">正在读取项目配置…</div>;
|
||||
|
||||
const updateAgent = (next: CodingProjectAgent) => setDraft((current) => current ? { ...current, agents: current.agents.map((item) => item.id === next.id ? { ...next, updatedAt: new Date().toISOString() } : item) } : current);
|
||||
const handlePartnerDialogOpenChange = (open: boolean) => {
|
||||
setPartnerDialogOpen(open);
|
||||
if (!open) setActiveAgentId(null);
|
||||
};
|
||||
const handleCreatePartner = async (input: AgentCreationInput) => {
|
||||
const selectedModel = parseCodingModelKey(input.model);
|
||||
if (!selectedModel) throw new Error('默认模型无效');
|
||||
const agent = {
|
||||
...createCustomAgent(draft.agents.length + 1, input.model),
|
||||
name: input.name,
|
||||
avatarId: input.avatarId,
|
||||
avatarDataUrl: input.avatarDataUrl,
|
||||
model: { ...selectedModel, thinkingLevel: 'off' as const },
|
||||
modelResolution: 'resolved' as const,
|
||||
prompt: input.prompt,
|
||||
skillIds: input.skillIds,
|
||||
responsibility: { mission: input.responsibility, owns: [], boundaries: [], collaborators: [], principles: [] },
|
||||
};
|
||||
setDraft((current) => current ? { ...current, agents: [...current.agents, agent] } : current);
|
||||
};
|
||||
const handleUpdatePartner = async (input: AgentCreationInput) => {
|
||||
if (!activeAgentId) return;
|
||||
const selectedModel = parseCodingModelKey(input.model);
|
||||
if (!selectedModel) throw new Error('默认模型无效');
|
||||
setDraft((current) => current ? {
|
||||
...current,
|
||||
agents: current.agents.map((agent) => agent.id === activeAgentId ? {
|
||||
...agent,
|
||||
name: input.name,
|
||||
avatarId: input.avatarId,
|
||||
avatarDataUrl: input.avatarDataUrl,
|
||||
model: {
|
||||
...selectedModel,
|
||||
thinkingLevel: agent.model?.thinkingLevel ?? 'off',
|
||||
},
|
||||
modelResolution: 'resolved',
|
||||
prompt: input.prompt,
|
||||
skillIds: input.skillIds,
|
||||
responsibility: { ...agent.responsibility, mission: input.responsibility },
|
||||
updatedAt: new Date().toISOString(),
|
||||
} : agent),
|
||||
} : current);
|
||||
};
|
||||
const submit = async () => {
|
||||
const names = draft.agents.map((agent) => agent.name.trim()).filter(Boolean);
|
||||
const invalid = draft.agents.some((agent) => (
|
||||
!agent.name.trim()
|
||||
|| !agent.avatarId.trim()
|
||||
|| !agent.model
|
||||
|| !agent.responsibility.mission.trim()
|
||||
)) || new Set(names).size !== draft.agents.length;
|
||||
if (invalid) {
|
||||
toast.error('请补全智能体名称、头像、默认模型和职责说明;名称需在当前项目内唯一。');
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
await save(activeProject.id, { ...draft, initialized: true });
|
||||
@@ -287,28 +191,6 @@ export function ProjectConfiguration() {
|
||||
toast.success(`已移除项目 ${activeProject.name}`);
|
||||
navigate('/project-config');
|
||||
};
|
||||
const handleArchiveAgent = async () => {
|
||||
if (!archiveAgentId) return;
|
||||
const summaries = codingConversationStore.getState().summariesByConversationId;
|
||||
const runningConversationIds = conversations
|
||||
.filter((conversation) => (
|
||||
conversation.agentId === archiveAgentId
|
||||
&& summaries[conversation.id]?.runStatus !== 'idle'
|
||||
))
|
||||
.map((conversation) => conversation.id);
|
||||
await Promise.allSettled(runningConversationIds.map(abortCodingConversation));
|
||||
const archivedAt = new Date().toISOString();
|
||||
setDraft((current) => current ? {
|
||||
...current,
|
||||
agents: current.agents.map((agent) => agent.id === archiveAgentId
|
||||
? { ...agent, archivedAt, updatedAt: archivedAt }
|
||||
: agent),
|
||||
} : current);
|
||||
setArchiveAgentId(null);
|
||||
setPartnerDialogOpen(false);
|
||||
setActiveAgentId(null);
|
||||
};
|
||||
|
||||
return <main className="-m-5 flex min-h-[calc(100%+2.5rem)] min-h-0 flex-col overflow-auto bg-background p-4 text-foreground sm:-m-6 sm:min-h-[calc(100%+3rem)]" data-testid="project-configuration-page">
|
||||
<div className="mx-auto flex min-h-0 w-full max-w-7xl flex-1 flex-col gap-3">
|
||||
<div className="config-motion-title flex items-center gap-2">
|
||||
@@ -318,7 +200,22 @@ export function ProjectConfiguration() {
|
||||
<h1 className="text-xl font-semibold">项目配置</h1>
|
||||
</div>
|
||||
<section data-testid="project-resource-row" className="grid grid-cols-3 gap-3"><ResourceCard id="models" icon={<Cpu />} title="可用模型" subtitle={`${modelOptions.length} 个可用模型`} onClick={() => setDrawerMode('models')} /><ResourceCard id="knowledge" icon={<Files />} title="知识文件" subtitle={`${knowledge.length} 个知识文件`} onClick={() => setDrawerMode('knowledge')} /><ResourceCard id="plugins" icon={<Puzzle />} title="插件" subtitle="管理云端与本机能力" onClick={openPluginWorkspace} /></section>
|
||||
<section className="config-motion-partners surface-card rounded-2xl border border-border/70 bg-background p-5 shadow-soft"><div className="mb-4 flex items-center justify-between gap-3"><div><h2 className="text-xl font-semibold">项目智能体</h2><p className="mt-1 text-sm font-medium text-muted-foreground">智能体配置仅属于当前项目;每个智能体可以拥有多条独立对话。</p></div><Button type="button" size="icon" className="h-8 w-8 rounded-full border border-brand/20 bg-brand-soft text-brand" aria-label="创建智能体" title="创建智能体" onClick={() => { if (modelOptions.length === 0) { toast.error('请先在模型设置中配置至少一个可用模型。'); setDrawerMode('models'); return; } setActiveAgentId(null); setPartnerDialogOpen(true); }}><Plus className="h-4 w-4" /></Button></div><div data-testid="agent-cards-scroll-region" className="-m-2 flex gap-4 overflow-auto p-2">{draft.agents.filter((agent) => !agent.archivedAt).map((agent) => <AgentCard key={agent.id} agent={agent} capabilityCount={agent.skillIds.filter((id) => skills.some((skill) => skill.id === id)).length} onOpen={() => { setActiveAgentId(agent.id); setPartnerDialogOpen(true); }} />)}{draft.agents.every((agent) => agent.archivedAt) ? <div className="w-full rounded-xl border border-dashed border-foreground/15 p-6 text-center text-sm font-medium text-muted-foreground">当前项目还没有智能体,点击右上角的 + 创建。</div> : null}</div>{draft.agents.some((agent) => agent.archivedAt) ? <div className="mt-4 border-t border-border/70 pt-4"><p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">已归档</p><div className="mt-2 flex flex-wrap gap-2">{draft.agents.filter((agent) => agent.archivedAt).map((agent) => <Button key={agent.id} type="button" variant="outline" className="border-border/70 bg-surface-subtle" onClick={() => updateAgent({ ...agent, archivedAt: null })}>{agent.name || '未命名智能体'} · 恢复</Button>)}</div></div> : null}</section>
|
||||
{activeAgent && <section className="surface-card rounded-2xl border border-border/70 p-5" data-testid="project-default-settings">
|
||||
<h2 className="text-lg font-semibold">{activeAgent.id === draft.defaultAgentId ? '默认编程配置' : '历史会话编程配置'}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">新会话使用项目默认配置;历史会话保留原来的配置与记录。</p>
|
||||
<label className="mt-4 block text-sm">默认模型<select className="mt-2 block w-full rounded-lg border bg-background p-2"
|
||||
value={activeAgent.model ? JSON.stringify([activeAgent.model.accountId, activeAgent.model.modelId]) : ''}
|
||||
onChange={event => { const model = parseCodingModelKey(event.target.value); updateAgent({...activeAgent, model: model ? {...model, thinkingLevel: 'off', reasoningChoice: {mode: 'default'}} : null, modelResolution: model ? 'resolved' : 'required'}); }}>
|
||||
<option value="">发送前选择模型</option>{modelOptions.map(model => <option key={model.key} value={model.key}>{model.modelId}</option>)}
|
||||
</select></label>
|
||||
<label className="mt-4 block text-sm">项目提示词<textarea className="mt-2 min-h-36 w-full rounded-lg border bg-background p-3" value={activeAgent.prompt} onChange={event => updateAgent({...activeAgent,prompt:event.target.value})} /></label>
|
||||
<h3 className="mt-4 text-sm font-medium">项目 Skills</h3>
|
||||
<div className="mt-2 space-y-2">{skills.map(skill => <label key={skill.id} className="flex items-start gap-2 text-sm">
|
||||
<input type="checkbox" checked={activeAgent.skillIds.includes(skill.id)} disabled={!skill.available}
|
||||
onChange={event => updateAgent({...activeAgent,skillIds:event.target.checked ? [...activeAgent.skillIds,skill.id] : activeAgent.skillIds.filter(id=>id!==skill.id)})} />
|
||||
<span>{skill.name}<span className="block text-xs text-muted-foreground">{skill.description}</span></span>
|
||||
</label>)}</div>
|
||||
</section>}
|
||||
<div data-testid="project-configuration-actions" className="glass-surface config-motion-actions sticky bottom-3 z-10 mt-auto flex shrink-0 flex-col gap-2 rounded-2xl border border-border/70 bg-background/85 p-2 shadow-float sm:flex-row sm:items-start sm:justify-between">
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => setDeleteDialogOpen(true)} className="h-9 w-full border-brand/25 bg-background px-3 font-semibold text-foreground shadow-none hover:border-brand/40 hover:bg-brand-soft sm:w-auto"><Trash2 className="mr-2 h-4 w-4" />删除项目</Button>
|
||||
<div data-testid="project-configuration-primary-actions" className="flex w-full flex-col gap-2 sm:ml-auto sm:w-auto sm:flex-row sm:items-start sm:justify-end">
|
||||
@@ -335,34 +232,11 @@ export function ProjectConfiguration() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AgentCreationDialog
|
||||
open={partnerDialogOpen}
|
||||
onOpenChange={handlePartnerDialogOpenChange}
|
||||
modelOptions={modelOptions}
|
||||
skills={skills}
|
||||
agent={activeAgent}
|
||||
existingAgentNames={draft.agents.filter((agent) => agent.id !== activeAgentId).map((agent) => agent.name)}
|
||||
onCreate={handleCreatePartner}
|
||||
onUpdate={handleUpdatePartner}
|
||||
onArchive={activeAgent && !activeAgent.builtIn ? () => setArchiveAgentId(activeAgent.id) : undefined}
|
||||
title={activeAgent ? `${activeAgent.name || '未命名智能体'} · 编辑智能体` : '创建项目智能体'}
|
||||
submitLabel={activeAgent ? '保存智能体' : '创建智能体'}
|
||||
/>
|
||||
<Sheet open={drawerMode !== null} onOpenChange={(open) => { if (!open) setDrawerMode(null); }}><SheetContent className="glass-surface flex w-[min(430px,94vw)] flex-col overflow-hidden border-l border-border/70 bg-background/90 p-0 text-foreground sm:max-w-none">
|
||||
{drawerMode === 'models' ? <DrawerFrame title="可用模型" desc="查看具体模型与可选思考强度。" onClose={() => setDrawerMode(null)}><ModelList models={modelOptions} /></DrawerFrame> : null}
|
||||
{drawerMode === 'knowledge' ? <DrawerFrame title="知识文件" desc="文件保存在当前项目的 knowledge/ 目录中。" onClose={() => setDrawerMode(null)}><input ref={knowledgeInputRef} type="file" className="sr-only" onChange={(event) => { void handleKnowledge(event.target.files?.[0]); event.currentTarget.value = ''; }} /><Button className="w-full border border-foreground/15 bg-brand-soft text-foreground" onClick={() => knowledgeInputRef.current?.click()}><Upload className="mr-2 h-4 w-4" />上传知识文件</Button><div className="mt-5 space-y-3">{knowledge.length ? knowledge.map((file) => <div key={file} className="rounded-lg border border-foreground/15 bg-white p-3 font-semibold">{file}</div>) : <div className="rounded-lg border border-dashed border-foreground/15 bg-white p-4 text-sm font-medium">当前还没有知识文件。</div>}</div></DrawerFrame> : null}
|
||||
</SheetContent></Sheet>
|
||||
<PluginWorkspaceSheet open={pluginWorkspaceOpen} onClose={closePluginWorkspace} />
|
||||
<ConfirmDialog
|
||||
open={Boolean(archiveAgentId)}
|
||||
title={`归档智能体“${draft.agents.find((agent) => agent.id === archiveAgentId)?.name || '未命名智能体'}”?`}
|
||||
message="归档后将停止提供新的使用入口,但会保留智能体配置和全部对话记录;你可以之后在智能体列表底部恢复。"
|
||||
confirmLabel="确认归档"
|
||||
cancelLabel="取消"
|
||||
onCancel={() => setArchiveAgentId(null)}
|
||||
onConfirm={handleArchiveAgent}
|
||||
onError={(error) => toast.error(error instanceof Error ? error.message : String(error))}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
open={deleteDialogOpen}
|
||||
title={`删除项目“${activeProject.name}”?`}
|
||||
|
||||
Reference in new issue
Block a user