feat: improve protocol debug panel
This commit is contained in:
parent
9ecd91376d
commit
6532cf6ebe
@ -8,6 +8,113 @@ type ProtocolDebugEvent = {
|
||||
payload: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type TaskBoundaryDecision = {
|
||||
taskType?: string;
|
||||
taskMode?: string;
|
||||
reason?: string;
|
||||
confidence?: number;
|
||||
shouldUseCanvasContext?: boolean;
|
||||
shouldUseHistoryImages?: boolean;
|
||||
shouldUseWorkflowMemory?: boolean;
|
||||
shouldAllowExecution?: boolean;
|
||||
};
|
||||
|
||||
type AgentModelSlot = {
|
||||
slot?: string;
|
||||
modelId?: string;
|
||||
displayName?: string;
|
||||
providerType?: string;
|
||||
providerKey?: string;
|
||||
source?: string;
|
||||
reason?: string;
|
||||
locked?: boolean;
|
||||
};
|
||||
|
||||
type ProviderHealthSnapshot = {
|
||||
providerKey?: string;
|
||||
providerType?: string;
|
||||
lastEventType?: string;
|
||||
lastError?: string;
|
||||
consecutiveFailures?: number;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function toTaskBoundaryDecision(value: unknown): TaskBoundaryDecision | null {
|
||||
const record = asRecord(value);
|
||||
if (!record) return null;
|
||||
return {
|
||||
taskType: typeof record.taskType === "string" ? record.taskType : undefined,
|
||||
taskMode: typeof record.taskMode === "string" ? record.taskMode : undefined,
|
||||
reason: typeof record.reason === "string" ? record.reason : undefined,
|
||||
confidence:
|
||||
typeof record.confidence === "number" && Number.isFinite(record.confidence)
|
||||
? record.confidence
|
||||
: undefined,
|
||||
shouldUseCanvasContext:
|
||||
typeof record.shouldUseCanvasContext === "boolean"
|
||||
? record.shouldUseCanvasContext
|
||||
: undefined,
|
||||
shouldUseHistoryImages:
|
||||
typeof record.shouldUseHistoryImages === "boolean"
|
||||
? record.shouldUseHistoryImages
|
||||
: undefined,
|
||||
shouldUseWorkflowMemory:
|
||||
typeof record.shouldUseWorkflowMemory === "boolean"
|
||||
? record.shouldUseWorkflowMemory
|
||||
: undefined,
|
||||
shouldAllowExecution:
|
||||
typeof record.shouldAllowExecution === "boolean"
|
||||
? record.shouldAllowExecution
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function toAgentModelSlots(value: unknown) {
|
||||
const record = asRecord(value);
|
||||
if (!record) return [] as AgentModelSlot[];
|
||||
|
||||
const slots: AgentModelSlot[] = [];
|
||||
for (const [slotName, slotValue] of Object.entries(record)) {
|
||||
const slotRecord = asRecord(slotValue);
|
||||
if (!slotRecord) continue;
|
||||
slots.push({
|
||||
slot: slotName,
|
||||
modelId: typeof slotRecord.modelId === "string" ? slotRecord.modelId : undefined,
|
||||
displayName:
|
||||
typeof slotRecord.displayName === "string" ? slotRecord.displayName : undefined,
|
||||
providerType:
|
||||
typeof slotRecord.providerType === "string" ? slotRecord.providerType : undefined,
|
||||
providerKey:
|
||||
typeof slotRecord.providerKey === "string" ? slotRecord.providerKey : undefined,
|
||||
source: typeof slotRecord.source === "string" ? slotRecord.source : undefined,
|
||||
reason: typeof slotRecord.reason === "string" ? slotRecord.reason : undefined,
|
||||
locked: typeof slotRecord.locked === "boolean" ? slotRecord.locked : undefined,
|
||||
});
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
function toProviderHealthSnapshot(value: unknown): ProviderHealthSnapshot | null {
|
||||
const record = asRecord(value);
|
||||
if (!record) return null;
|
||||
return {
|
||||
providerKey: typeof record.providerKey === "string" ? record.providerKey : undefined,
|
||||
providerType: typeof record.providerType === "string" ? record.providerType : undefined,
|
||||
lastEventType:
|
||||
typeof record.lastEventType === "string" ? record.lastEventType : undefined,
|
||||
lastError: typeof record.lastError === "string" ? record.lastError : undefined,
|
||||
consecutiveFailures:
|
||||
typeof record.consecutiveFailures === "number" ? record.consecutiveFailures : undefined,
|
||||
updatedAt: typeof record.updatedAt === "number" ? record.updatedAt : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function ProtocolDebugPanel({
|
||||
enabled,
|
||||
events,
|
||||
@ -33,6 +140,29 @@ export function ProtocolDebugPanel({
|
||||
}));
|
||||
}, [events]);
|
||||
|
||||
const latestTaskBoundary = useMemo(() => {
|
||||
for (let index = events.length - 1; index >= 0; index -= 1) {
|
||||
const payload = events[index]?.payload;
|
||||
const boundary = toTaskBoundaryDecision(payload?.taskBoundary);
|
||||
if (boundary?.taskType) return boundary;
|
||||
}
|
||||
return null;
|
||||
}, [events]);
|
||||
|
||||
const latestModelSlotSummary = useMemo(() => {
|
||||
for (let index = events.length - 1; index >= 0; index -= 1) {
|
||||
const payload = events[index]?.payload;
|
||||
const slots = toAgentModelSlots(payload?.modelSlots);
|
||||
if (slots.length > 0) {
|
||||
return {
|
||||
slots,
|
||||
providerHealth: toProviderHealthSnapshot(payload?.providerHealth),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [events]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-2 w-full max-w-4xl px-2 md:px-4">
|
||||
<details className="rounded-xl border border-border/60 bg-muted/30 p-3 text-xs">
|
||||
@ -60,6 +190,94 @@ export function ProtocolDebugPanel({
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
{(latestTaskBoundary || latestModelSlotSummary) && (
|
||||
<div className="grid gap-2 md:grid-cols-2">
|
||||
{latestTaskBoundary && (
|
||||
<div className="rounded-lg border border-border/60 bg-background px-3 py-2">
|
||||
<div className="mb-2 text-[11px] font-medium text-foreground">当前任务边界</div>
|
||||
<div className="flex flex-wrap gap-2 text-[11px] text-muted-foreground">
|
||||
<span className="rounded-md bg-muted px-2 py-1">
|
||||
type: {latestTaskBoundary.taskType || "unknown"}
|
||||
</span>
|
||||
<span className="rounded-md bg-muted px-2 py-1">
|
||||
mode: {latestTaskBoundary.taskMode || "unknown"}
|
||||
</span>
|
||||
{typeof latestTaskBoundary.confidence === "number" && (
|
||||
<span className="rounded-md bg-muted px-2 py-1">
|
||||
confidence: {latestTaskBoundary.confidence}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 text-[11px] leading-relaxed text-muted-foreground">
|
||||
{latestTaskBoundary.reason || "无原因说明"}
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-2 text-[11px] text-muted-foreground">
|
||||
<span className="rounded-md border border-border px-2 py-1">
|
||||
canvas {latestTaskBoundary.shouldUseCanvasContext ? "on" : "off"}
|
||||
</span>
|
||||
<span className="rounded-md border border-border px-2 py-1">
|
||||
history {latestTaskBoundary.shouldUseHistoryImages ? "on" : "off"}
|
||||
</span>
|
||||
<span className="rounded-md border border-border px-2 py-1">
|
||||
workflow {latestTaskBoundary.shouldUseWorkflowMemory ? "on" : "off"}
|
||||
</span>
|
||||
<span className="rounded-md border border-border px-2 py-1">
|
||||
execution {latestTaskBoundary.shouldAllowExecution ? "yes" : "no"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{latestModelSlotSummary && (
|
||||
<div className="rounded-lg border border-border/60 bg-background px-3 py-2">
|
||||
<div className="mb-2 text-[11px] font-medium text-foreground">当前模型槽位</div>
|
||||
<div className="space-y-2">
|
||||
{latestModelSlotSummary.slots.map((slot) => (
|
||||
<div
|
||||
key={`${slot.slot}-${slot.modelId}`}
|
||||
className="rounded-md border border-border/60 px-2 py-2"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded-md bg-muted px-2 py-1 text-[11px] font-medium text-foreground">
|
||||
{slot.slot}
|
||||
</span>
|
||||
<span className="text-[11px] text-foreground">
|
||||
{slot.displayName || slot.modelId || "unknown"}
|
||||
</span>
|
||||
{slot.locked && (
|
||||
<span className="rounded-md border border-border px-2 py-1 text-[11px] text-muted-foreground">
|
||||
locked
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] text-muted-foreground">
|
||||
provider: {slot.providerType || "unknown"} · source: {slot.source || "unknown"}
|
||||
</div>
|
||||
{slot.reason && (
|
||||
<div className="mt-1 text-[11px] text-muted-foreground">
|
||||
reason: {slot.reason}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{latestModelSlotSummary.providerHealth && (
|
||||
<div className="rounded-md border border-border/60 px-2 py-2 text-[11px] text-muted-foreground">
|
||||
provider health: {latestModelSlotSummary.providerHealth.lastEventType || "unknown"}
|
||||
{typeof latestModelSlotSummary.providerHealth.consecutiveFailures === "number" &&
|
||||
` · failures ${latestModelSlotSummary.providerHealth.consecutiveFailures}`}
|
||||
{latestModelSlotSummary.providerHealth.providerType &&
|
||||
` · ${latestModelSlotSummary.providerHealth.providerType}`}
|
||||
{latestModelSlotSummary.providerHealth.lastError && (
|
||||
<div className="mt-1 break-words">
|
||||
{latestModelSlotSummary.providerHealth.lastError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{events.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border/60 px-3 py-2 text-muted-foreground">
|
||||
暂无协议调试事件
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user