52 lines
4.0 KiB
TypeScript
52 lines
4.0 KiB
TypeScript
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: '创建子智能体',
|
||
wechatBindVerification: '微信连接验证', channelAccountWechatBindVerification: '微信连接验证',
|
||
createChannelAccount: '添加微信账号', routeChannelAccount: '切换目标智能体', enableChannelAccount: '启用渠道',
|
||
pauseChannelAccount: '暂停渠道', disconnectChannelAccount: '断开微信账号', channelAccountWechatBindStart: '微信扫码连接',
|
||
channelAccountWechatUnbind: '解除微信登录',
|
||
};
|
||
|
||
export function CloudRecovery({ pending, onResolved }: { pending: CloudPendingOperation[]; onResolved: () => void }) {
|
||
const [busy, setBusy] = useState('');
|
||
const [error, setError] = useState('');
|
||
const [notice, setNotice] = useState('');
|
||
const [result, setResult] = useState<{ operation: string; value: unknown } | null>(null);
|
||
const resolve = async (item: CloudPendingOperation, discard = false) => {
|
||
setBusy(item.id); setError(''); setNotice('');
|
||
try {
|
||
const response = await cloudAgentsApi.resolvePending(item.id, discard);
|
||
if (response.requires_input) {
|
||
setResult(null);
|
||
setNotice('这次微信验证还需要验证码。请进入「渠道 → 微信」,选择对应账号后继续连接验证。');
|
||
} else 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 && !notice) 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>}
|
||
{notice && <p role="status" className="text-muted-foreground">{notice}</p>}
|
||
</aside>;
|
||
}
|