Этап 4 / Волна 18 закрытие блокеров по времени, доменной полярности и допуску доказательной базы

This commit is contained in:
2026-03-29 00:40:06 +03:00
parent 7eb1410501
commit d7e145010b
140 changed files with 417053 additions and 42196 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NDC AI Normalizer Playground</title>
<script type="module" crossorigin src="/assets/index-PA_66ng-.js"></script>
<script type="module" crossorigin src="/assets/index-B5_Zqbf2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Ch7jCAii.css">
</head>
<body>
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from "react";
import type { AssistantConversationItem } from "../state/types";
import { buildConversationExportForCopy } from "../utils/conversationExport";
import { JsonView } from "./JsonView";
import { PanelFrame } from "./PanelFrame";
@@ -33,50 +34,6 @@ function shortTime(iso: string): string {
return date.toLocaleTimeString("ru-RU");
}
function stringifyDebug(value: unknown): string {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
function buildConversationExport(
sessionId: string,
conversation: AssistantConversationItem[],
includeDebugPayload = false
): string {
const lines: string[] = [];
lines.push("# Assistant conversation export");
lines.push(`session_id: ${sessionId || "n/a"}`);
lines.push(`exported_at: ${new Date().toISOString()}`);
lines.push("");
for (let index = 0; index < conversation.length; index += 1) {
const item = conversation[index];
lines.push(`## ${index + 1}. ${item.role}`);
lines.push(`message_id: ${item.message_id}`);
lines.push(`created_at: ${item.created_at}`);
lines.push(`reply_type: ${item.reply_type ?? "n/a"}`);
if (item.trace_id) {
lines.push(`trace_id: ${item.trace_id}`);
}
lines.push("");
lines.push(item.text || "(empty)");
lines.push("");
if (includeDebugPayload && item.role === "assistant" && item.debug) {
lines.push("### debug_payload_json");
lines.push("```json");
lines.push(stringifyDebug(item.debug));
lines.push("```");
lines.push("");
}
}
return lines.join("\n");
}
async function copyTextToClipboard(text: string): Promise<boolean> {
if (navigator.clipboard && window.isSecureContext) {
try {
@@ -128,6 +85,7 @@ export function AssistantPanel({
const listRef = useRef<HTMLDivElement | null>(null);
const copyResetTimerRef = useRef<number | null>(null);
const [copyState, setCopyState] = useState<"idle" | "success" | "error">("idle");
const [copyModeLabel, setCopyModeLabel] = useState<"чат" | "тех">("чат");
useEffect(() => {
if (listRef.current) {
@@ -143,13 +101,14 @@ export function AssistantPanel({
};
}, []);
async function handleCopyConversation(): Promise<void> {
async function handleCopyConversation(mode: "default" | "technical"): Promise<void> {
if (conversation.length === 0) {
return;
}
// Copy full run context for diagnostics (including debug payload blocks).
const exportText = buildConversationExport(sessionId, conversation, true);
const exportText = buildConversationExportForCopy(sessionId, conversation, mode);
const copied = await copyTextToClipboard(exportText);
setCopyModeLabel(mode === "technical" ? "тех" : "чат");
setCopyState(copied ? "success" : "error");
if (copyResetTimerRef.current !== null) {
@@ -170,13 +129,25 @@ export function AssistantPanel({
type="button"
className="assistant-copy-btn"
onClick={() => {
void handleCopyConversation();
void handleCopyConversation("default");
}}
disabled={conversation.length === 0}
title="Экспорт только user-facing чата"
>
Скопировать чат
</button>
{copyState === "success" ? <span className="assistant-copy-feedback success">Скопировано</span> : null}
<button
type="button"
className="assistant-copy-btn"
onClick={() => {
void handleCopyConversation("technical");
}}
disabled={conversation.length === 0}
title="Технический экспорт с debug payload"
>
Скопировать техчат
</button>
{copyState === "success" ? <span className="assistant-copy-feedback success">Скопировано ({copyModeLabel})</span> : null}
{copyState === "error" ? <span className="assistant-copy-feedback error">Ошибка копирования</span> : null}
<span className="status-chip">{sessionId ? `session: ${sessionId}` : "новая сессия"}</span>
</div>
@@ -0,0 +1,85 @@
export type ConversationExportMode = "default" | "technical";
export interface ConversationExportItem {
message_id: string;
role: "user" | "assistant";
text: string;
reply_type: string | null;
created_at: string;
trace_id: string | null;
debug?: unknown | null;
}
const DEBUG_SECTION_PATTERN =
/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json|debug_payload|technical_breakdown)\b/i;
const INLINE_TECH_LINE_PATTERNS: RegExp[] = [
/\b(?:debug_payload_json|technical_breakdown_json)\b/i,
/\b(?:route_summary|semantic_profile|domain_scope|relation_patterns|account_scope)\b/i,
/\b(?:coverage_report|retrieval_status|problem_unit_state|candidate_evidence)\b/i,
/\b(?:graph_domain_scope|graph_runtime|selection_reason|why_included)\b/i
];
function stringifyDebug(value: unknown): string {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
export function sanitizeConversationExportText(value: string): string {
const raw = String(value ?? "");
const cutMatch = raw.match(DEBUG_SECTION_PATTERN);
const preCut = cutMatch ? raw.slice(0, cutMatch.index) : raw;
const withoutDebugHeadings = preCut
.replace(/###\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)\b[\s\S]*$/gi, "");
const lines = withoutDebugHeadings
.split(/\r?\n/g)
.map((line) => line.trimEnd())
.filter((line) => line.trim().length > 0)
.filter((line) => !INLINE_TECH_LINE_PATTERNS.some((pattern) => pattern.test(line)));
return lines.join("\n").trim();
}
export function buildConversationExportForCopy(
sessionId: string,
conversation: ConversationExportItem[],
mode: ConversationExportMode = "default"
): string {
const includeDebug = mode === "technical";
const lines: string[] = [];
lines.push("# Assistant conversation export");
lines.push(`session_id: ${sessionId || "n/a"}`);
lines.push(`export_mode: ${mode}`);
lines.push(`exported_at: ${new Date().toISOString()}`);
lines.push("");
for (let index = 0; index < conversation.length; index += 1) {
const item = conversation[index];
const safeText = sanitizeConversationExportText(item.text || "");
lines.push(`## ${index + 1}. ${item.role}`);
lines.push(`message_id: ${item.message_id}`);
lines.push(`created_at: ${item.created_at}`);
lines.push(`reply_type: ${item.reply_type ?? "n/a"}`);
if (item.trace_id) {
lines.push(`trace_id: ${item.trace_id}`);
}
lines.push("");
lines.push(safeText || "(empty)");
lines.push("");
if (includeDebug && item.role === "assistant" && item.debug) {
lines.push("### technical_debug_payload_json");
lines.push("```json");
lines.push(stringifyDebug(item.debug));
lines.push("```");
lines.push("");
}
}
return lines.join("\n");
}
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/components/assistantpanel.tsx","./src/components/connectionpanel.tsx","./src/components/historypanel.tsx","./src/components/jsonview.tsx","./src/components/metricspanel.tsx","./src/components/outputpanel.tsx","./src/components/panelframe.tsx","./src/components/promptpanel.tsx","./src/components/querypanel.tsx","./src/components/runtimepanel.tsx","./src/state/defaults.ts","./src/state/types.ts"],"version":"5.9.3"}
{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/components/assistantpanel.tsx","./src/components/connectionpanel.tsx","./src/components/historypanel.tsx","./src/components/jsonview.tsx","./src/components/metricspanel.tsx","./src/components/outputpanel.tsx","./src/components/panelframe.tsx","./src/components/promptpanel.tsx","./src/components/querypanel.tsx","./src/components/runtimepanel.tsx","./src/state/defaults.ts","./src/state/types.ts","./src/utils/conversationexport.ts"],"version":"5.9.3"}