feat(roundtable): 会商前端调用报错收集 + 诊断页用户名/范围切换
- 前端调用报错收集(用户实际遇到的报错,服务端诊断看不到的也收):新增 POST /api/roundtable-diagnostics/report(非 admin,scope="client")+ 前端上报器 roundtable-planning/api/diagnostics-report.ts。streamMultiAgent(第二步席位 + 第三步报告/结构化/绘制/大屏)覆盖 HTTP 错误(502)、SSE error 帧、worker error 帧 (模型没调用成功)、fetch 网络不可达、流式中途中断,reported 去重;initMultiAgent / intent / recommend / 后台作业接口同步上报。 - 诊断页:每条报错显示上报用户名(👤,user_id→邮箱@前缀,后端 best-effort 解析); 第一个筛选改为「后台挂起 / 前台调用」切换(前台调用=foreground,client,后端 scope 支持逗号多值),阶段下拉随范围联动(后台→二三步;前台→一二三步)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
4a403c95c5
commit
762d3620c4
12 files changed
+433
-18
No files matched your search
@@ -55,7 +55,37 @@ const STAGE_LABELS: Record<string, string> = {
|
||||
|
||||
const SCOPE_LABELS: Record<string, string> = {
|
||||
background: "后台挂起",
|
||||
foreground: "前台实时",
|
||||
foreground: "前台实时(服务端)",
|
||||
client: "前端上报",
|
||||
};
|
||||
|
||||
// 范围切换:后台挂起(background) / 前台调用(foreground 服务端观测 + client 前端上报)。
|
||||
type ScopeCategory = "background" | "frontend";
|
||||
|
||||
// 每个范围下可选的阶段(按用户要求:后台挂起只到第二/三步;前台调用含第一步)。
|
||||
const STAGE_OPTIONS: Record<ScopeCategory, string[]> = {
|
||||
background: [
|
||||
"step2_init",
|
||||
"step2_leader",
|
||||
"step2_seat",
|
||||
"step2_orchestration",
|
||||
"step3_summary",
|
||||
"step3_report",
|
||||
"step3_dashboard",
|
||||
"step3_structure",
|
||||
"job",
|
||||
],
|
||||
frontend: [
|
||||
"step1_intent",
|
||||
"step2_init",
|
||||
"step2_leader",
|
||||
"step2_seat",
|
||||
"step2_recommend",
|
||||
"step3_summary",
|
||||
"step3_report",
|
||||
"step3_dashboard",
|
||||
"step3_structure",
|
||||
],
|
||||
};
|
||||
|
||||
const LEVEL_STYLES: Record<string, string> = {
|
||||
@@ -101,6 +131,11 @@ function DiagnosticRow({ item }: { item: DiagnosticItem }) {
|
||||
{item.agentName ? (
|
||||
<span className="text-xs text-slate-500 dark:text-muted-foreground">· {item.agentName}</span>
|
||||
) : null}
|
||||
{item.userName ? (
|
||||
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-xs text-slate-600 dark:bg-slate-800 dark:text-slate-300">
|
||||
👤 {item.userName}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="ml-auto text-xs text-slate-400 dark:text-muted-foreground">{fmtTime(item.createdAt)}</span>
|
||||
</div>
|
||||
<p className="mt-1 whitespace-pre-wrap break-words text-slate-700 dark:text-foreground">
|
||||
@@ -133,7 +168,7 @@ export default function RoundtableDiagnosticsPage() {
|
||||
const isAdmin = getStoredAuth()?.system_role === "admin";
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [scope, setScope] = useState<string>(ALL);
|
||||
const [scopeCategory, setScopeCategory] = useState<ScopeCategory>("background");
|
||||
const [stage, setStage] = useState<string>(ALL);
|
||||
const [level, setLevel] = useState<string>(ALL);
|
||||
const [event, setEvent] = useState<string>(ALL);
|
||||
@@ -141,9 +176,12 @@ export default function RoundtableDiagnosticsPage() {
|
||||
const [q, setQ] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
// 「前台调用」= 服务端观测(foreground) + 前端上报(client) 两种 scope 合并查(后端按逗号分隔多值)。
|
||||
const scopeParam = scopeCategory === "background" ? "background" : "foreground,client";
|
||||
|
||||
const filters: DiagnosticFilters = useMemo(
|
||||
() => ({
|
||||
scope: scope === ALL ? undefined : scope,
|
||||
scope: scopeParam,
|
||||
stage: stage === ALL ? undefined : stage,
|
||||
level: level === ALL ? undefined : level,
|
||||
event: event === ALL ? undefined : event,
|
||||
@@ -151,7 +189,7 @@ export default function RoundtableDiagnosticsPage() {
|
||||
limit: PAGE_SIZE,
|
||||
offset: (page - 1) * PAGE_SIZE,
|
||||
}),
|
||||
[scope, stage, level, event, q, page],
|
||||
[scopeParam, stage, level, event, q, page],
|
||||
);
|
||||
|
||||
const { data, isLoading, isFetching, refetch } = useRoundtableDiagnostics(filters, isAdmin);
|
||||
@@ -169,6 +207,13 @@ export default function RoundtableDiagnosticsPage() {
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
// 切换范围时清空阶段筛选(两种范围的阶段选项不同)+ 回到第一页。
|
||||
const switchScope = (next: ScopeCategory) => {
|
||||
setScopeCategory(next);
|
||||
setStage(ALL);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const applySearch = () => resetPageAnd(() => setQ(qInput.trim()));
|
||||
|
||||
return (
|
||||
@@ -199,10 +244,26 @@ export default function RoundtableDiagnosticsPage() {
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-wrap items-center gap-2 border-b border-slate-200 dark:border-border bg-white dark:bg-card px-4 py-2">
|
||||
<FilterSelect label="范围" value={scope} onChange={(v) => resetPageAnd(() => setScope(v))}
|
||||
options={facets?.scope ?? []} labels={SCOPE_LABELS} />
|
||||
{/* 范围切换:后台挂起 / 前台调用 */}
|
||||
<div className="inline-flex rounded-lg border border-slate-200 dark:border-border p-0.5">
|
||||
{(["background", "frontend"] as ScopeCategory[]).map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() => switchScope(c)}
|
||||
className={cn(
|
||||
"rounded-md px-3 py-1 text-xs font-medium transition-colors",
|
||||
scopeCategory === c
|
||||
? "bg-slate-800 text-white dark:bg-primary dark:text-primary-foreground"
|
||||
: "text-slate-500 hover:text-slate-700 dark:text-muted-foreground dark:hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{c === "background" ? "后台挂起" : "前台调用"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<FilterSelect label="阶段" value={stage} onChange={(v) => resetPageAnd(() => setStage(v))}
|
||||
options={facets?.stage ?? []} labels={STAGE_LABELS} />
|
||||
options={STAGE_OPTIONS[scopeCategory]} labels={STAGE_LABELS} />
|
||||
<FilterSelect label="级别" value={level} onChange={(v) => resetPageAnd(() => setLevel(v))}
|
||||
options={facets?.level ?? []} />
|
||||
<FilterSelect label="事件" value={event} onChange={(v) => resetPageAnd(() => setEvent(v))}
|
||||
|
||||
Reference in new issue
Block a user