fix(cloud-agents): 移除微信本人配对入口
This commit is contained in:
@@ -50,11 +50,11 @@ export function CloudChannelConversations({ slug, activeView = true }: { slug: s
|
||||
};
|
||||
return <section aria-label="本人微信会话" className="space-y-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3"><div><h2 className="font-medium">本人微信对话</h2>
|
||||
<p className="mt-1 text-sm leading-6 text-muted-foreground">查看你已配对的本人会话,处理确认和保存文件。新消息请从对应微信对话发送。</p></div>
|
||||
<p className="mt-1 text-sm leading-6 text-muted-foreground">查看你的本人微信会话,处理确认和保存文件。新消息请从对应微信对话发送。</p></div>
|
||||
<Button variant="ghost" size="sm" disabled={loading || busy} onClick={() => setRevision(value => value + 1)}><RefreshCw className="mr-2 h-4 w-4" />刷新会话</Button></div>
|
||||
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
|
||||
{loading && !sessions.length && <p role="status" className="text-sm text-muted-foreground">正在读取本人会话…</p>}
|
||||
{!loading && !error && !sessions.length && <p className="rounded-xl bg-muted/30 p-6 text-sm leading-6 text-muted-foreground">还没有本人微信会话。请先在发布与访问中连接个人微信,并完成本人配对。</p>}
|
||||
{!loading && !error && !sessions.length && <p className="rounded-xl bg-muted/30 p-6 text-sm leading-6 text-muted-foreground">还没有本人微信会话。请先在发布与访问中连接并启用个人微信,然后用绑定时扫码的微信发送一条消息。</p>}
|
||||
{!!sessions.length && <label className="block space-y-2 text-sm"><span>选择本人微信会话</span>
|
||||
<select className="h-10 w-full rounded-md border border-input bg-background px-3" value={selected} disabled={busy} onChange={event => setSelected(event.target.value)}>
|
||||
{!sessions.some(session => session.session_id === selected) && <option value={selected}>当前会话</option>}
|
||||
|
||||
@@ -328,7 +328,7 @@ export function CloudChannelPanel({ slug, publishedVersion, activeView = true }:
|
||||
setQrBindingKey(currentBindingKeyRef.current);
|
||||
setQr(payload);
|
||||
setQrOpen(true);
|
||||
} else if (isPairing(payload) && matchesCurrent) {
|
||||
} else if (isPairing(payload) && payload.kind === 'invite' && matchesCurrent) {
|
||||
setCreatedPairing(payload);
|
||||
}
|
||||
await refresh();
|
||||
@@ -458,9 +458,10 @@ export function CloudChannelPanel({ slug, publishedVersion, activeView = true }:
|
||||
await loadCallers();
|
||||
};
|
||||
|
||||
const issuePairing = async (kind: 'self' | 'invite') => {
|
||||
const issueInvite = async () => {
|
||||
const kind = 'invite';
|
||||
if (!binding) return;
|
||||
if (kind === 'invite' && binding.access_mode !== 'invited') {
|
||||
if (binding.access_mode !== 'invited') {
|
||||
setNotice('请先保存“本人及受邀联系人”,再生成邀请码');
|
||||
return;
|
||||
}
|
||||
@@ -471,7 +472,7 @@ export function CloudChannelPanel({ slug, publishedVersion, activeView = true }:
|
||||
operation_id: operationId(),
|
||||
};
|
||||
const result = await runMutation(key, 'createChannelPairing', input,
|
||||
kind === 'self' ? '本人配对码已生成,请在目标微信对话中发送' : '邀请码已生成,请手动发给受邀联系人');
|
||||
'邀请码已生成,请手动发给受邀联系人');
|
||||
if (requestedAccountId === currentIdRef.current && isPairing(result)) setCreatedPairing(result);
|
||||
};
|
||||
|
||||
@@ -633,7 +634,7 @@ export function CloudChannelPanel({ slug, publishedVersion, activeView = true }:
|
||||
onVerificationCode={setVerificationCode} onVerify={() => void verify()} onEnable={() => void stateAction(true)} onPause={() => void stateAction(false)}
|
||||
onRouteTarget={setRouteTargetSlug} onOpenRoute={() => void openRoutePicker()} onSwitchRoute={() => void switchRoute()} onConfirmEnable={setConfirmEnable} onUnbind={() => void unbind()} onConfirmUnbind={setConfirmUnbind}
|
||||
/>}
|
||||
{tab === 'access' && <AccessStep binding={binding} accessMode={accessMode} callers={callers} pairing={activePairing} busy={busy} busyAction={busyAction} onMode={setAccessMode} onSave={() => void updateAccessMode()} onPair={kind => void issuePairing(kind)} onClearPairing={() => setCreatedPairing(null)} onRevoke={caller => void revoke(caller)} />}
|
||||
{tab === 'access' && <AccessStep binding={binding} accessMode={accessMode} callers={callers} pairing={activePairing} busy={busy} busyAction={busyAction} onMode={setAccessMode} onSave={() => void updateAccessMode()} onInvite={() => void issueInvite()} onClearPairing={() => setCreatedPairing(null)} onRevoke={caller => void revoke(caller)} />}
|
||||
{tab === 'activity' && <ActivityStep slug={slug} channelAccountId={currentId} refreshKey={activityRevision} activity={activity} nextOffset={activityOffset} busy={busy} onRefresh={() => void loadActivity()} onMore={() => void (activityOffset == null ? undefined : loadActivity(activityOffset))} onRetry={retryPart} />}
|
||||
{tab === 'sessions' && <CloudChannelConversations slug={slug} activeView={activeView && tab === 'sessions'} />}
|
||||
</section>;
|
||||
@@ -685,20 +686,20 @@ function StepHeading({ number, title, description }: { number: string; title: st
|
||||
return <div className="flex gap-3 border-t border-border/60 pt-5"><span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-brand/10 text-sm font-medium text-brand">{number}</span><div><h3 className="font-medium">{title}</h3><p className="mt-1 text-sm leading-6 text-muted-foreground">{description}</p></div></div>;
|
||||
}
|
||||
|
||||
function AccessStep({ binding, accessMode, callers, pairing, busy, busyAction, onMode, onSave, onPair, onClearPairing, onRevoke }: { binding: CloudChannelView | null; accessMode: 'self_only' | 'invited'; callers: CloudChannelCaller[]; pairing: CloudChannelPairing | null; busy: boolean; busyAction: string; onMode: (value: 'self_only' | 'invited') => void; onSave: () => void; onPair: (kind: 'self' | 'invite') => void; onClearPairing: () => void; onRevoke: (caller: CloudChannelCaller) => void }) {
|
||||
function AccessStep({ binding, accessMode, callers, pairing, busy, busyAction, onMode, onSave, onInvite, onClearPairing, onRevoke }: { binding: CloudChannelView | null; accessMode: 'self_only' | 'invited'; callers: CloudChannelCaller[]; pairing: CloudChannelPairing | null; busy: boolean; busyAction: string; onMode: (value: 'self_only' | 'invited') => void; onSave: () => void; onInvite: () => void; onClearPairing: () => void; onRevoke: (caller: CloudChannelCaller) => void }) {
|
||||
const inviteEnabled = accessMode === 'invited' && binding?.access_mode === 'invited';
|
||||
return <div className="space-y-5"><StepHeading number="2" title="谁能使用" description="默认只允许你本人。配对码和邀请都是短期、单次使用;创建者只能查看受邀者状态和费用,不会读取其私聊内容。" />
|
||||
return <div className="space-y-5"><StepHeading number="2" title="谁能使用" description="默认只允许绑定时扫码的微信使用。受邀联系人需要单独邀请;你只能查看受邀者状态和费用,不能读取其私聊内容。" />
|
||||
{!binding ? <p className="rounded-xl bg-muted/30 p-4 text-sm text-muted-foreground">请先完成微信连接。</p> : <>
|
||||
<div className="space-y-3 rounded-xl border border-border p-4"><label className="flex cursor-pointer gap-3 text-sm"><input type="radio" name="channel-access-mode" checked={accessMode === 'self_only'} onChange={() => onMode('self_only')} /><span><strong>仅本人</strong><span className="mt-1 block text-muted-foreground">只有当前账号配对的微信会话可以调用智能体。</span></span></label><label className="flex cursor-pointer gap-3 text-sm"><input type="radio" name="channel-access-mode" checked={accessMode === 'invited'} onChange={() => onMode('invited')} /><span><strong>本人及受邀联系人</strong><span className="mt-1 block text-muted-foreground">受邀者拥有独立会话和费用记录,不能查看创建者或其他联系人内容。</span></span></label><Button variant="outline" disabled={busy || accessMode === (binding.access_mode === 'invited' ? 'invited' : 'self_only')} onClick={onSave}>{busyAction.startsWith('policy:') ? '保存中…' : '保存使用范围'}</Button></div>
|
||||
<div className="grid gap-4 md:grid-cols-2"><div className="space-y-3 rounded-xl bg-muted/30 p-4"><h3 className="text-sm font-medium">本人配对</h3><p className="text-xs leading-5 text-muted-foreground">生成一次性配对码后,在你的目标微信私聊中发送完整配对命令。未完成配对前不会调用模型。</p><Button variant="outline" disabled={busy} onClick={() => onPair('self')}>{busyAction.includes(':self') ? '生成中…' : '生成本人配对码'}</Button></div>{accessMode === 'invited' && <div className="space-y-3 rounded-xl bg-muted/30 p-4"><h3 className="text-sm font-medium">邀请联系人</h3><p className="text-xs leading-5 text-muted-foreground">邀请码需要你手动发给联系人;平台不自动向微信联系人发消息。</p><Button variant="outline" disabled={busy || !inviteEnabled} onClick={() => onPair('invite')}>{busyAction.includes(':invite') ? '生成中…' : '生成邀请码'}</Button>{!inviteEnabled && <p className="text-xs text-amber-800">请先保存“本人及受邀联系人”后生成邀请码。</p>}</div>}</div>
|
||||
{pairing && <PairingResult value={pairing} onClear={onClearPairing} />}
|
||||
<div className="space-y-3 rounded-xl border border-border p-4"><div className="flex items-center justify-between gap-3"><h3 className="flex items-center gap-2 text-sm font-medium"><Users className="h-4 w-4" />已授权调用者</h3><span className="text-xs text-muted-foreground">{callers.length} 个</span></div>{!callers.length && <p className="text-sm text-muted-foreground">还没有已配对的调用者。</p>}{callers.map(caller => <div key={caller.caller_id} className="flex items-center justify-between gap-3 border-t border-border/60 pt-3 text-sm"><div><p>{caller.access_mode === 'self_only' ? '本人微信会话' : '受邀联系人'}</p><p className="mt-1 text-xs text-muted-foreground">{statusLabel(caller.grant_state)} · 配对于 {shortDate(caller.created_at)}</p></div><Button variant="ghost" size="sm" className="text-destructive" disabled={busy || caller.grant_state === 'revoked'} onClick={() => onRevoke(caller)}>撤销</Button></div>)}</div>
|
||||
<div className="space-y-3 rounded-xl border border-border p-4"><label className="flex cursor-pointer gap-3 text-sm"><input type="radio" name="channel-access-mode" checked={accessMode === 'self_only'} onChange={() => onMode('self_only')} /><span><strong>仅本人</strong><span className="mt-1 block text-muted-foreground">使用绑定时扫码的微信,直接发送消息即可。</span></span></label><label className="flex cursor-pointer gap-3 text-sm"><input type="radio" name="channel-access-mode" checked={accessMode === 'invited'} onChange={() => onMode('invited')} /><span><strong>本人及受邀联系人</strong><span className="mt-1 block text-muted-foreground">受邀者拥有独立会话和费用记录,不能查看创建者或其他联系人内容。</span></span></label><Button variant="outline" disabled={busy || accessMode === (binding.access_mode === 'invited' ? 'invited' : 'self_only')} onClick={onSave}>{busyAction.startsWith('policy:') ? '保存中…' : '保存使用范围'}</Button></div>
|
||||
<div>{accessMode === 'invited' && <div className="space-y-3 rounded-xl bg-muted/30 p-4"><h3 className="text-sm font-medium">邀请联系人</h3><p className="text-xs leading-5 text-muted-foreground">邀请码需要你手动发给联系人;平台不自动向微信联系人发消息。</p><Button variant="outline" disabled={busy || !inviteEnabled} onClick={onInvite}>{busyAction.includes(':invite') ? '生成中…' : '生成邀请码'}</Button>{!inviteEnabled && <p className="text-xs text-amber-800">请先保存“本人及受邀联系人”后生成邀请码。</p>}</div>}</div>
|
||||
{pairing?.kind === 'invite' && <PairingResult value={pairing} onClear={onClearPairing} />}
|
||||
<div className="space-y-3 rounded-xl border border-border p-4"><div className="flex items-center justify-between gap-3"><h3 className="flex items-center gap-2 text-sm font-medium"><Users className="h-4 w-4" />已授权调用者</h3><span className="text-xs text-muted-foreground">{callers.length} 个</span></div>{!callers.length && <p className="text-sm text-muted-foreground">首次收到你的微信消息后,会在这里显示本人会话。</p>}{callers.map(caller => <div key={caller.caller_id} className="flex items-center justify-between gap-3 border-t border-border/60 pt-3 text-sm"><div><p>{caller.access_mode === 'self_only' ? '本人微信会话' : '受邀联系人'}</p><p className="mt-1 text-xs text-muted-foreground">{statusLabel(caller.grant_state)} · 授权于 {shortDate(caller.created_at)}</p></div><Button variant="ghost" size="sm" className="text-destructive" disabled={busy || caller.grant_state === 'revoked'} onClick={() => onRevoke(caller)}>撤销</Button></div>)}</div>
|
||||
</>}</div>;
|
||||
}
|
||||
|
||||
function PairingResult({ value, onClear }: { value: CloudChannelPairing; onClear: () => void }) {
|
||||
const copy = () => { if (value.code) void navigator.clipboard.writeText(`${value.kind === 'self' ? '配对' : '邀请'} ${value.code}`); };
|
||||
return <div role="status" className="space-y-3 rounded-xl bg-amber-50 p-4 text-sm"><div className="flex items-start justify-between gap-3"><div><p className="font-medium">{value.kind === 'self' ? '本人一次性配对码' : '一次性邀请码'}</p><p className="mt-1 text-xs text-amber-900/70">{value.expires_at ? shortDate(value.expires_at) : '短期有效'};关闭此面板后不会再次展示。</p></div><Button variant="ghost" size="sm" onClick={onClear}>隐藏</Button></div>{value.code ? <div className="flex items-center gap-2"><code className="min-w-0 flex-1 rounded-lg bg-white px-3 py-2 font-mono text-base tracking-widest">{value.code}</code><Button aria-label="复制配对命令" variant="outline" size="sm" onClick={copy}><Copy className="h-4 w-4" /></Button></div> : <p className="text-xs text-amber-900/70">服务已受理,请在活动记录中查看操作状态。</p>}</div>;
|
||||
const copy = () => { if (value.code) void navigator.clipboard.writeText(`邀请 ${value.code}`); };
|
||||
return <div role="status" className="space-y-3 rounded-xl bg-amber-50 p-4 text-sm"><div className="flex items-start justify-between gap-3"><div><p className="font-medium">一次性邀请码</p><p className="mt-1 text-xs text-amber-900/70">{value.expires_at ? shortDate(value.expires_at) : '短期有效'};关闭此面板后不会再次展示。</p></div><Button variant="ghost" size="sm" onClick={onClear}>隐藏</Button></div>{value.code ? <div className="flex items-center gap-2"><code className="min-w-0 flex-1 rounded-lg bg-white px-3 py-2 font-mono text-base tracking-widest">{value.code}</code><Button aria-label="复制邀请命令" variant="outline" size="sm" onClick={copy}><Copy className="h-4 w-4" /></Button></div> : <p className="text-xs text-amber-900/70">服务已受理,请在活动记录中查看操作状态。</p>}</div>;
|
||||
}
|
||||
|
||||
function ActivityStep({ slug, channelAccountId, refreshKey, activity, nextOffset, busy, onRefresh, onMore, onRetry }: {
|
||||
|
||||
@@ -56,7 +56,7 @@ export function CloudSchedules({ slug, onOpen, proposal, onProposalClosed }: { s
|
||||
{job.runs?.map((run, i) => <div key={i} className="flex items-center justify-between gap-3 border-t border-border/50 pt-3 text-sm">
|
||||
<div><p>{cloudStatus(run.status)}{run.error_message && <span className="ml-2 text-muted-foreground">{run.error_message}</span>}</p>
|
||||
{run.result_notification && <p className="mt-1 text-xs text-muted-foreground">{run.result_notification.state === 'waiting_context'
|
||||
? '微信通知待恢复:请确认连接,并在已配对的微信对话中发送一条消息。任务结果已独立保存。'
|
||||
? '微信通知待恢复:请确认连接,并在本人微信对话中发送一条消息。任务结果已独立保存。'
|
||||
: '微信通知已配置,实际送达情况可在发布渠道的活动记录中查看。'}</p>}</div>
|
||||
{run.conversation_available && <Button variant="ghost" onClick={() => onOpen({ thread_id: run.thread_id, client_thread_id: null, agent_slug: slug,
|
||||
title: job.name, status: run.status, mode: 'published', unread: false, updated_at: '', run_id: null })}>查看结果</Button>}
|
||||
@@ -160,8 +160,8 @@ function ScheduleNotification({ slug, value, onChange }: {
|
||||
{channels.find(channel => channel.id === caller.channel_account_id)?.display_name ?? '个人微信'} · 本人对话 {index + 1}
|
||||
</option>)}
|
||||
</select></label>
|
||||
<p className="text-xs leading-6 text-muted-foreground">只可选择你已配对的本人微信对话。任务仍在云端运行,结果保留在历史中,通知不会再次执行任务。</p>
|
||||
{!loading && !error && !options.length && <p className="text-xs leading-6 text-muted-foreground">还没有可用的本人微信对话。请先在“发布与访问”的个人微信渠道中完成连接、本人配对并启用,然后回来刷新。</p>}
|
||||
<p className="text-xs leading-6 text-muted-foreground">只可选择已经开始对话的本人微信。任务仍在云端运行,结果保留在历史中,通知不会再次执行任务。</p>
|
||||
{!loading && !error && !options.length && <p className="text-xs leading-6 text-muted-foreground">还没有可用的本人微信对话。请先在“发布与访问”的个人微信渠道中完成连接并启用,再用绑定时扫码的微信发送一条消息,然后回来刷新。</p>}
|
||||
{selected && !available && !loading && <p className="text-xs leading-6 text-muted-foreground">请恢复原连接,或重新选择通知目标。选择“仅保存在客户端”可关闭微信通知。</p>}
|
||||
{error && <p role="alert" className="text-xs text-destructive">{error}。原通知配置已保留,可刷新后重试。</p>}
|
||||
</section>;
|
||||
|
||||
Reference in New Issue
Block a user