Этап 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
@@ -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>