feat(agents): 完善恢复费用与云资源交互

This commit is contained in:
brother7 committed 2026-09-10 22:29:24 +08:00
1 parent f8f39f39de
commit 303f26294e
24 files changed
+1170 -127

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { cloudAgentsApi } from '@/lib/cloud-agents-api';
import type { CloudPendingOperation } from '../../../shared/cloud-agents';
const labels: Record<string, string> = {
createAgent: '创建智能体', submit: '发送消息', preview: '预览草稿', resume: '继续任务', publish: '发布版本',
createApplication: '创建应用', createKey: '创建密钥', createKnowledge: '创建知识库', processKnowledge: '处理文档',
createSchedule: '创建自动任务', runSchedule: '立即运行任务',
importKnowledgeAttachment: '导入附件到知识库', createChild: '创建子智能体',
};
export function CloudRecovery({ pending, onResolved }: { pending: CloudPendingOperation[]; onResolved: () => void }) {
const [busy, setBusy] = useState('');
const [error, setError] = useState('');
const [result, setResult] = useState<{ operation: string; value: unknown } | null>(null);
const resolve = async (item: CloudPendingOperation, discard = false) => {
setBusy(item.id); setError('');
try {
const response = await cloudAgentsApi.resolvePending(item.id, discard);
if (!discard) setResult({ operation: item.operation, value: response.result });
onResolved();
} catch (e) { setError(e instanceof Error ? e.message : '暂时无法确认结果'); }
finally { setBusy(''); }
};
if (!pending.length && !result && !error) return null;
return <aside aria-label="未确认的云端操作" className="mx-auto mb-6 max-w-6xl space-y-3 rounded-xl border border-amber-200 bg-amber-50/30 p-4 text-sm">
{pending.length > 0 && <><h2 className="font-medium">有 {pending.length} 个操作的结果尚未确认</h2>
<p className="text-muted-foreground">重试会确认原操作,不会新建一份任务。移除提醒只清除本机记录,云端已接受的任务仍会继续。</p></>}
{pending.map(item => <div key={item.id} className="flex flex-wrap items-center justify-between gap-3">
<div><p>{labels[item.operation] ?? '云端操作'} · {new Date(item.created_at).toLocaleString()}</p>
{typeof item.input.query === 'string' && <p className="mt-1 line-clamp-2 text-muted-foreground">{item.input.query}</p>}</div>
<div className="flex gap-2"><Button size="sm" disabled={Boolean(busy)} onClick={() => void resolve(item)}>{busy === item.id ? '正在确认…' : '重试并确认结果'}</Button>
<Button size="sm" variant="ghost" disabled={Boolean(busy)} onClick={() => void resolve(item, true)}>移除提醒</Button></div>
</div>)}
{result && <div className="space-y-2"><p>{labels[result.operation] ?? '操作'}的结果已确认。{result.operation === 'createKey' && '请保存下面的密钥,关闭后不再显示。'}</p>
<details open={result.operation === 'createKey'}><summary className="cursor-pointer">查看结果</summary>
<pre className="max-h-48 overflow-auto whitespace-pre-wrap break-all p-2 text-xs">{JSON.stringify(result.value, null, 2)}</pre></details>
<Button variant="ghost" size="sm" onClick={() => setResult(null)}>关闭结果</Button></div>}
{error && <p role="alert" className="text-destructive">{error}</p>}
</aside>;
}