Files
Cyrene_v2/frontend/web/src/admin/modules/channels/channelIdentityTest.tsx
T
Aska 5a0c1e2fd1
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(channelbridge): IM 发送过滤 action/narration + <p>拆多气泡 + 0.5-2s 间隔
把回复转发给 IM 适配器(sendReply)时剥掉 <action>/<narration> 动作/内心独白(Web 端
才渲染灰斜体,IM 只能收纯文本),并按 <p> 段落拆成多条气泡分别发送;多气泡发送间隔
随机 0.5~2s(仅 IM,Web 前端不延迟)。新增 sanitize.go stripNarration/splitBubbles/
bubbleGap + 测试。
2026-09-07 17:50:56 +08:00

81 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { adminChannelTest } from "../../../lib/api";
import type { ChannelTestResult } from "../../../lib/types";
import { useAuditStore } from "../../audit";
import { Button, Surface, Tag } from "../../../components/primitives";
/** Structural identity test (design/08: actor metadata × owner whitelist — never a
* prompt/LLM judgment). Self-contained so the list page and any adapter detail
* page can render it; `defaultPlatform` prefills when known. */
export function ChannelIdentityTest({
defaultPlatform = "",
disabled = false,
}: {
defaultPlatform?: string;
disabled?: boolean;
}): React.ReactNode {
const log = useAuditStore((s) => s.log);
const [platform, setPlatform] = useState(defaultPlatform);
const [ownerId, setOwnerId] = useState("");
const [group, setGroup] = useState(false);
const [verdict, setVerdict] = useState<ChannelTestResult | null>(null);
const [testing, setTesting] = useState(false);
const [err, setErr] = useState<string | null>(null);
const test = () => {
if (!ownerId.trim()) {
setErr("邮箱/账号 id 不能为空");
return;
}
setTesting(true);
setErr(null);
adminChannelTest({ platform: platform.trim(), accountId: ownerId.trim(), isGroup: group })
.then((v) => {
log("channel.identity-test", "ok");
setVerdict(v);
})
.catch((e: unknown) => {
log("channel.identity-test", "error");
setErr(e instanceof Error ? e.message : "身份测试失败");
})
.finally(() => setTesting(false));
};
return (
<Surface gap="sm" className="space-y-3">
<h3 className="text-sm font-semibold">身份识别测试(结构性判定 · 绝非提示词)</h3>
<p className="text-xs text-[var(--mu-text-lo)]">
输入渠道确认的发送者账号 id,后端用 channel 层 actor 元数据 × 主人白名单(configs/users/owner.yaml)判定;判定绝不依赖提示词 / LLM 自我判断。
</p>
<div className="flex flex-wrap items-center gap-2">
<input
value={platform}
onChange={(e) => setPlatform(e.target.value)}
placeholder="platform(如 kook,可留空)"
className="min-w-0 flex-1 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
/>
<input
value={ownerId}
onChange={(e) => setOwnerId(e.target.value)}
placeholder="渠道账号 id(发送者 id)"
className="min-w-0 flex-1 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
/>
<label className="flex items-center gap-1.5 text-xs text-[var(--mu-text-mid)]">
<input type="checkbox" checked={group} onChange={(e) => setGroup(e.target.checked)} className="accent-[var(--mu-accent)]" />
群聊
</label>
<Button variant="primary" disabled={disabled || testing} onClick={test}>
测试
</Button>
</div>
{err ? <Tag tone="err">{err}</Tag> : null}
{verdict ? (
<div className="space-y-1 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-xs text-[var(--mu-text-mid)]">
<div>角色:{verdict.role} | 主人:{verdict.owner ? "是" : "否"} | 群聊:{verdict.isGroup ? "是" : "否"} | 环境:{verdict.env}</div>
<div>判定:{verdict.verdict}</div>
</div>
) : null}
</Surface>
);
}