import { useCallback, useEffect, useRef, useState } from 'react'; import { Copy, Link as LinkIcon, Plus } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { usePendingCloudInput } from './CloudPending'; import { cloudAgentsApi } from '@/lib/cloud-agents-api'; import type { CloudAccess as Access, CloudApplication, CloudKey } from '../../../shared/cloud-agents'; import { CloudCosts } from './CloudCosts'; import { CloudLifecycle } from './CloudLifecycle'; import type { CloudAgentDraft } from '../../../shared/cloud-agents'; import type { CloudAgentOperations } from '../../../shared/cloud-agents'; import { cloudStatus } from './CloudChat'; const errorText = (e: unknown) => e instanceof Error ? e.message : '操作失败,请重试'; export function CloudAccessPanel({ slug, revision, onPublished, onChanged, activeView = true }: { slug: string; revision: number; onPublished: () => void; onChanged?: (draft: CloudAgentDraft) => void; activeView?: boolean }) { const [access, setAccess] = useState(null); const [query, setQuery] = useState(''); const [users, setUsers] = useState<{ account_id: string; display_name: string; username: string }[]>([]); const [name, setName] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [notice, setNotice] = useState(''); usePendingCloudInput(busy || Boolean(name.trim())); const publishOperation = useRef<{ operation_id: string; expected_revision: number } | null>(null); const applicationOperation = useRef<{ operation_id: string; name: string } | null>(null); const alive = useRef(true); const refresh = useCallback(async () => { const result = await cloudAgentsApi.call('access', { slug }); if (!alive.current) return; setAccess(result); }, [slug]); useEffect(() => { alive.current = true; refresh().catch(e => { if (alive.current) setError(errorText(e)); }); return () => { alive.current = false; }; }, [refresh]); const act = async (task: () => Promise, success?: string) => { if (busy) return; setBusy(true); setError(''); setNotice(''); try { await task(); await refresh(); if (alive.current && success) setNotice(success); } catch(e) { if (alive.current) setError(errorText(e)); } finally { if (alive.current) setBusy(false); } }; const publish = () => act(async () => { const input = publishOperation.current ?? { operation_id: crypto.randomUUID(), expected_revision: revision }; publishOperation.current = input; await cloudAgentsApi.call('publish', { slug, ...input }); publishOperation.current = null; onPublished(); }, '已发布,新的调用将使用这个版本'); const createApplication = () => act(async () => { const input = applicationOperation.current ?? { operation_id: crypto.randomUUID(), name: name.trim() }; applicationOperation.current = input; await cloudAgentsApi.call('createApplication', { slug, ...input }); applicationOperation.current = null; setName(''); }, '应用已创建,可为它生成 API 凭据'); const copy = async (text: string) => { try { await navigator.clipboard.writeText(text); setNotice('已复制'); } catch { setError('复制失败,请手动选择文本复制'); } }; return

{access?.published_version ? '当前发布版本 ' + access.published_version : '尚未发布'}

发布保存的修订 {revision}。发布后仅自己可用,分享与应用需要分别开启。

自己使用、分享使用、API 调用、自动任务和个人微信渠道产生的费用,均从你的个人词元点数扣除。你可以随时停用智能体或撤销访问。

{access?.published_version &&
{access.enabled ? '智能体已启用' : '智能体已停用'}
}
{access?.published_version && } {error &&

{error}

} {notice &&

{notice}

}

分享给指定用户

选择获准使用的账号,再发送分享链接。每个人的对话和文件分别保存。

setQuery(e.target.value)} placeholder="用户名、显示名或账号 ID" maxLength={100} />
{users.map(user =>

{user.display_name} @{user.username}

{user.account_id}

)} {access?.grants.filter(g => g.enabled).map(grant =>
{grant.account_id}
)} {access?.published_version &&
{access.share_url}
}

应用与 API

为网站或脚本创建独立应用。应用通过凭据调用此智能体,并拥有自己的会话空间。

setName(e.target.value)} maxLength={100} placeholder="例如:我的个人网站" />
{access?.applications.map(app => )} {access?.api_url &&
API 调用示例

POST {access.api_url}

{'Authorization: Bearer <应用凭据>\nContent-Type: application/json\n\n' + JSON.stringify({ request_id: '唯一请求标识', thread_id: '会话标识', query: '帮我整理今天的创作计划' }, null, 2)}

相同请求标识与输入可重试。通过返回的 request_id 查询排队状态,通过 run_id 读取运行结果或订阅事件;这些请求继续使用同一应用凭据。

}
v.version) ?? []} onChanged={draft => { onChanged?.(draft); void refresh().catch(e => setError(errorText(e))); }} /> {access &&
发布历史({access.versions.length}) {access.versions.map(v =>

版本 {v.version} · 草稿修订 {v.draft_revision} · {new Date(v.created_at).toLocaleString()}

)}
}
; } function LinkedChannels({ slug, activeView }: { slug: string; activeView: boolean }) { const [items, setItems] = useState([]); const [error, setError] = useState(''); useEffect(() => { if (!activeView) return; let live = true; void cloudAgentsApi.call('channelBindings', { slug }).then(result => { if (live) { setItems(result.items); setError(''); } }).catch(error => { if (live) setError(errorText(error)); }); return () => { live = false; }; }, [activeView, slug]); return

已关联渠道

微信账号统一在“渠道”中连接、切换目标和查看对话活动。

{error &&

{error}

} {!error && items.length === 0 &&

暂时没有关联的微信账号。打开渠道后会预选当前智能体。

} {items.map(item => )}
; } function ApplicationKeys({ application, onUpdated }: { application: CloudApplication; onUpdated: () => Promise }) { const [keys, setKeys] = useState([]); const [secret, setSecret] = useState(''); usePendingCloudInput(Boolean(secret)); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const operation = useRef(null); const refresh = useCallback(async () => { const result = await cloudAgentsApi.call('keys', { application_id: application.id }); setKeys(result.keys); }, [application.id]); useEffect(() => { let live = true; cloudAgentsApi.call('keys', { application_id: application.id }).then(v => { if (live) setKeys(v.keys); }).catch(e => { if (live) setError(errorText(e)); }); return () => { live = false; }; }, [application.id]); const act = async (task: () => Promise) => { setBusy(true); setError(''); try { await task(); await refresh(); await onUpdated(); } catch(e) { setError(errorText(e)); } finally { setBusy(false); } }; return

{application.name} · {application.enabled ? '已启用' : '已停用'}

{keys.map(key =>
{key.prefix}… {key.revoked_at ? 已撤销 : }
)} {secret &&

请保存这份凭据,关闭此页后不再展示。

} {error &&

{error}

}
; } function ApplicationCalls({ applicationId }: { applicationId: string }) { const [page, setPage] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const read = async (offset = 0) => { setBusy(true); setError(''); try { const result = await cloudAgentsApi.call('applicationCalls', { application_id: applicationId, offset }); setPage(previous => ({ ...result, items: offset && previous ? [...previous.items, ...result.items] : result.items })); } catch (e) { setError(errorText(e)); } finally { setBusy(false); } }; return
{ if (e.currentTarget.open && !page) void read(); }}> 应用调用状态

包括尚未产生模型费用的请求。只显示状态与版本,不展示应用的会话内容。

{error &&

{error}

} {page && !page.items.length &&

此应用还没有调用记录。

} {page?.items.map(item =>

{new Date(item.created_at).toLocaleString()} · v{item.version} · {cloudStatus(item.run_status ?? item.request_status)}

请求 {item.request_id}{item.run_id && ' · 运行 ' + item.run_id}

)}
{page?.next_offset != null && }
; }