ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.12.23: декомпозиция deep-turn пайплайна ассистента в runtime-адаптеры
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -4,8 +4,8 @@
|
||||
<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-DNcr9aV9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-qw2gR5-7.css">
|
||||
<script type="module" crossorigin src="/assets/index-DMMD5-xN.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BfGkpjEM.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -160,6 +160,7 @@ export const apiClient = {
|
||||
rawQuestions?: string;
|
||||
evalTarget?: "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0";
|
||||
compareWithReportFile?: string;
|
||||
analysisDate?: string;
|
||||
}): Promise<{ ok: boolean; report: unknown }> {
|
||||
return request("/eval/run", {
|
||||
method: "POST",
|
||||
@@ -183,7 +184,8 @@ export const apiClient = {
|
||||
caseSetFile: input.caseSetFile,
|
||||
rawQuestions: input.rawQuestions,
|
||||
eval_target: input.evalTarget,
|
||||
compare_with_report_file: input.compareWithReportFile
|
||||
compare_with_report_file: input.compareWithReportFile,
|
||||
analysis_date: input.analysisDate
|
||||
})
|
||||
});
|
||||
},
|
||||
@@ -200,6 +202,7 @@ export const apiClient = {
|
||||
evalTarget?: "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0";
|
||||
compareWithReportFile?: string;
|
||||
questions?: string[];
|
||||
analysisDate?: string;
|
||||
}): Promise<AsyncEvalRunStartResponse> {
|
||||
return request("/eval/run-async/start", {
|
||||
method: "POST",
|
||||
@@ -224,7 +227,8 @@ export const apiClient = {
|
||||
rawQuestions: input.rawQuestions,
|
||||
eval_target: input.evalTarget,
|
||||
compare_with_report_file: input.compareWithReportFile,
|
||||
questions: input.questions
|
||||
questions: input.questions,
|
||||
analysis_date: input.analysisDate
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
@@ -75,15 +75,16 @@ export function AssistantPanel({
|
||||
errorMessage
|
||||
}: AssistantPanelProps) {
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
const stickToBottomRef = useRef(true);
|
||||
const copyResetTimerRef = useRef<number | null>(null);
|
||||
const [copyState, setCopyState] = useState<"idle" | "success" | "error">("idle");
|
||||
const [copyModeLabel, setCopyModeLabel] = useState<"чат" | "тех">("чат");
|
||||
|
||||
useEffect(() => {
|
||||
if (listRef.current) {
|
||||
if (listRef.current && stickToBottomRef.current) {
|
||||
listRef.current.scrollTop = listRef.current.scrollHeight;
|
||||
}
|
||||
}, [conversation, statusText]);
|
||||
}, [conversation]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -111,85 +112,93 @@ export function AssistantPanel({
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelFrame
|
||||
title="Режим ассистента"
|
||||
subtitle="Диалоговый слой поверх normalizer, маршрутизации и factual retrieval."
|
||||
actions={
|
||||
<div className="assistant-panel-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("default");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Экспорт только user-facing чата"
|
||||
>
|
||||
Скопировать чат
|
||||
</button>
|
||||
<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>
|
||||
}
|
||||
>
|
||||
<div ref={listRef} className="assistant-chat-list">
|
||||
{conversation.length === 0 ? (
|
||||
<div className="assistant-empty muted">Диалог пуст. Отправьте первый вопрос, чтобы запустить контур ассистента.</div>
|
||||
) : null}
|
||||
{conversation.map((item) => (
|
||||
<article key={item.message_id} className={`assistant-msg ${item.role}`}>
|
||||
<header className="assistant-msg-head">
|
||||
<strong>{roleLabel(item.role)}</strong>
|
||||
<span>{shortTime(item.created_at)}</span>
|
||||
</header>
|
||||
<div className="assistant-msg-body">{item.text}</div>
|
||||
{item.role === "assistant" && item.debug ? (
|
||||
<details className="assistant-debug">
|
||||
<summary>Показать технический разбор</summary>
|
||||
<JsonView value={item.debug} />
|
||||
</details>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
function handleChatScroll(): void {
|
||||
if (!listRef.current) return;
|
||||
const node = listRef.current;
|
||||
const distanceToBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
|
||||
stickToBottomRef.current = distanceToBottom < 16;
|
||||
}
|
||||
|
||||
<div className="assistant-compose">
|
||||
<label className="full-width">
|
||||
Сообщение
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(event) => onInputChange(event.target.value)}
|
||||
rows={4}
|
||||
placeholder="Введите вопрос к данным компании..."
|
||||
/>
|
||||
</label>
|
||||
<div className="button-row">
|
||||
<label className="checkbox-row">
|
||||
<input type="checkbox" checked={useMock} onChange={(event) => onUseMockChange(event.target.checked)} />
|
||||
Mock-режим
|
||||
return (
|
||||
<PanelFrame className="assistant-panel-frame" title="Режим ассистента">
|
||||
<div className="assistant-live-shell">
|
||||
<div className="assistant-toolbar">
|
||||
<div className="assistant-toolbar-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("default");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Экспорт только user-facing чата"
|
||||
>
|
||||
Скопировать чат
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("technical");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Технический экспорт с debug payload"
|
||||
>
|
||||
Скопировать техчат
|
||||
</button>
|
||||
<button type="button" className="assistant-copy-btn" onClick={() => onClear()} disabled={busy && conversation.length === 0}>
|
||||
Сбросить сессию
|
||||
</button>
|
||||
</div>
|
||||
<div className="assistant-toolbar-meta">
|
||||
{sessionId ? <span className="status-chip">{`session: ${sessionId}`}</span> : null}
|
||||
<div className="assistant-toolbar-meta-right">
|
||||
{statusText ? <span className="assistant-live-status">{statusText}</span> : null}
|
||||
{copyState === "success" ? <span className="assistant-copy-feedback success">Скопировано ({copyModeLabel})</span> : null}
|
||||
{copyState === "error" ? <span className="assistant-copy-feedback error">Ошибка копирования</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
{errorMessage ? <p className="error-text assistant-toolbar-error">{errorMessage}</p> : null}
|
||||
</div>
|
||||
|
||||
<div ref={listRef} className="assistant-chat-list" onScroll={handleChatScroll}>
|
||||
{conversation.map((item) => (
|
||||
<article key={item.message_id} className={`assistant-msg ${item.role}`}>
|
||||
<header className="assistant-msg-head">
|
||||
<strong>{roleLabel(item.role)}</strong>
|
||||
<span>{shortTime(item.created_at)}</span>
|
||||
</header>
|
||||
<div className="assistant-msg-body">{item.text}</div>
|
||||
{item.role === "assistant" && item.debug ? (
|
||||
<details className="assistant-debug">
|
||||
<summary>Показать технический разбор</summary>
|
||||
<JsonView value={item.debug} />
|
||||
</details>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="assistant-compose">
|
||||
<label className="full-width">
|
||||
Сообщение
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(event) => onInputChange(event.target.value)}
|
||||
rows={4}
|
||||
placeholder="Введите вопрос к данным компании..."
|
||||
/>
|
||||
</label>
|
||||
<button type="button" onClick={() => onSend()} disabled={busy || !inputValue.trim()}>
|
||||
{busy ? "Выполняю..." : "Отправить"}
|
||||
</button>
|
||||
<button type="button" onClick={() => onClear()} disabled={busy && conversation.length === 0}>
|
||||
Сбросить сессию
|
||||
</button>
|
||||
<div className="button-row assistant-send-row">
|
||||
<label className="checkbox-row">
|
||||
<input type="checkbox" checked={useMock} onChange={(event) => onUseMockChange(event.target.checked)} />
|
||||
Mock-режим
|
||||
</label>
|
||||
<button type="button" className="assistant-send-btn" onClick={() => onSend()} disabled={busy || !inputValue.trim()}>
|
||||
{busy ? "Выполняю..." : "Отправить"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{statusText ? <p className="diff-summary">{statusText}</p> : null}
|
||||
{errorMessage ? <p className="error-text">{errorMessage}</p> : null}
|
||||
</div>
|
||||
</PanelFrame>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { apiClient } from "../api/client";
|
||||
import type {
|
||||
AssistantConversationItem,
|
||||
AsyncEvalRunJob,
|
||||
AutoGenHistoryRecord,
|
||||
AutoGenMode,
|
||||
@@ -18,6 +19,7 @@ import type {
|
||||
ManualCaseDecision,
|
||||
PromptState
|
||||
} from "../state/types";
|
||||
import { AssistantPanel } from "./AssistantPanel";
|
||||
import { JsonView } from "./JsonView";
|
||||
import { PanelFrame } from "./PanelFrame";
|
||||
|
||||
@@ -91,6 +93,7 @@ const LIVE_RUN_ID_PREFIX = "__live__:";
|
||||
|
||||
const AUTORUNS_UI_CONFIG_KEY = "ndc_autoruns_ui_config_v1";
|
||||
const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save";
|
||||
const ASSISTANT_STAGES = ["Анализ запроса", "Получение данных", "Подготовка ответа"];
|
||||
|
||||
const AUTOGEN_PERSONALITIES: AutoGenPersonalityDefinition[] = [
|
||||
{
|
||||
@@ -113,6 +116,7 @@ function buildDefaultPersonalityPrompts(
|
||||
|
||||
interface AutoRunsUiConfig {
|
||||
filters?: Partial<AutoRunsFilters>;
|
||||
analysisDate?: string;
|
||||
autoGenSettings?: {
|
||||
mode?: AutoGenMode;
|
||||
count?: number;
|
||||
@@ -134,6 +138,11 @@ const DEFAULT_AUTOGEN_SETTINGS: AutoGenSettingsState = {
|
||||
generatedBy: "manual_reviewer"
|
||||
};
|
||||
|
||||
function normalizeAnalysisDateInput(value: string): string {
|
||||
const normalized = String(value ?? "").trim();
|
||||
return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : "";
|
||||
}
|
||||
|
||||
function dateToInputValue(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
@@ -246,6 +255,7 @@ function buildLiveRunSummary(job: AsyncEvalRunJob): AutoRunSummary {
|
||||
llm_provider: null,
|
||||
model: null,
|
||||
use_mock: null,
|
||||
analysis_date: job.report_summary?.analysis_date ?? job.analysis_date ?? null,
|
||||
prompt_version: null,
|
||||
schema_version: null,
|
||||
suite_id: job.case_set_file,
|
||||
@@ -314,7 +324,8 @@ function buildLiveRunDetail(job: AsyncEvalRunJob, requestedCaseId: string): {
|
||||
run_id: job.report_summary.run_id,
|
||||
run_timestamp: job.report_summary.run_timestamp,
|
||||
score_index: job.report_summary.score_index,
|
||||
cases_total: job.report_summary.cases_total
|
||||
cases_total: job.report_summary.cases_total,
|
||||
analysis_date: job.report_summary.analysis_date ?? job.analysis_date ?? null
|
||||
}
|
||||
: {}
|
||||
};
|
||||
@@ -411,6 +422,7 @@ export function AutoRunsHistoryPanel({
|
||||
...DEFAULT_FILTERS,
|
||||
fromLocal: defaultFromDateValue()
|
||||
});
|
||||
const [analysisDate, setAnalysisDate] = useState("");
|
||||
const [history, setHistory] = useState<AutoRunHistoryResponse | null>(null);
|
||||
const [runDetail, setRunDetail] = useState<AutoRunDetailResponse | null>(null);
|
||||
const [dialog, setDialog] = useState<AutoRunDialogResponse | null>(null);
|
||||
@@ -439,6 +451,13 @@ export function AutoRunsHistoryPanel({
|
||||
const [annotationsBusy, setAnnotationsBusy] = useState(false);
|
||||
const [annotationResolutionBusyId, setAnnotationResolutionBusyId] = useState("");
|
||||
const [errorText, setErrorText] = useState("");
|
||||
const [assistantLiveSessionId, setAssistantLiveSessionId] = useState("");
|
||||
const [assistantLiveConversation, setAssistantLiveConversation] = useState<AssistantConversationItem[]>([]);
|
||||
const [assistantLiveInput, setAssistantLiveInput] = useState("");
|
||||
const [assistantLiveUseMock, setAssistantLiveUseMock] = useState(false);
|
||||
const [assistantLiveBusy, setAssistantLiveBusy] = useState(false);
|
||||
const [assistantLiveStatus, setAssistantLiveStatus] = useState("");
|
||||
const [assistantLiveError, setAssistantLiveError] = useState("");
|
||||
const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit));
|
||||
const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count));
|
||||
const [commentModal, setCommentModal] = useState<CommentModalState>({
|
||||
@@ -465,8 +484,6 @@ export function AutoRunsHistoryPanel({
|
||||
[autoGenHistory, selectedAutogenGenerationId]
|
||||
);
|
||||
|
||||
const activeRunSummary: AutoRunSummary | null =
|
||||
history?.items.find((item) => item.run_id === selectedRunId) ?? runDetail?.run ?? null;
|
||||
const activeCase = runDetail ? getSelectedCase(runDetail.cases, selectedCaseId) : null;
|
||||
const visibleAnnotations = useMemo(
|
||||
() => (hideResolvedAnnotations ? annotations.filter((item) => !item.resolved) : annotations),
|
||||
@@ -509,6 +526,81 @@ export function AutoRunsHistoryPanel({
|
||||
[onLog]
|
||||
);
|
||||
|
||||
function startAssistantLiveStatusTicker(): () => void {
|
||||
let index = 0;
|
||||
setAssistantLiveStatus(ASSISTANT_STAGES[0]);
|
||||
const timer = window.setInterval(() => {
|
||||
index = Math.min(index + 1, ASSISTANT_STAGES.length - 1);
|
||||
setAssistantLiveStatus(ASSISTANT_STAGES[index]);
|
||||
}, 650);
|
||||
return () => window.clearInterval(timer);
|
||||
}
|
||||
|
||||
const resetAssistantLiveSession = useCallback(() => {
|
||||
setAssistantLiveSessionId("");
|
||||
setAssistantLiveConversation([]);
|
||||
setAssistantLiveInput("");
|
||||
setAssistantLiveStatus("");
|
||||
setAssistantLiveError("");
|
||||
log("Live-чат ассистента в истории автопрогонов сброшен.");
|
||||
}, [log]);
|
||||
|
||||
const sendAssistantLiveMessage = useCallback(async () => {
|
||||
const userMessage = assistantLiveInput.trim();
|
||||
if (!userMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAssistantLiveBusy(true);
|
||||
setAssistantLiveError("");
|
||||
setAssistantLiveInput("");
|
||||
setAssistantLiveConversation((prev) => [
|
||||
...prev,
|
||||
{
|
||||
message_id: `autoruns-live-${Date.now()}`,
|
||||
session_id: assistantLiveSessionId || "pending",
|
||||
role: "user",
|
||||
text: userMessage,
|
||||
reply_type: null,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: null,
|
||||
debug: null
|
||||
}
|
||||
]);
|
||||
|
||||
const stopTicker = startAssistantLiveStatusTicker();
|
||||
try {
|
||||
const response = await apiClient.sendAssistantMessage({
|
||||
connection,
|
||||
prompts,
|
||||
userMessage,
|
||||
sessionId: assistantLiveSessionId || undefined,
|
||||
promptVersion: assistantPromptVersion,
|
||||
useMock: assistantLiveUseMock
|
||||
});
|
||||
setAssistantLiveSessionId(response.session_id);
|
||||
setAssistantLiveConversation(response.conversation);
|
||||
setAssistantLiveStatus("Ответ готов");
|
||||
log(`Live-ответ ассистента получен: trace=${response.debug.trace_id}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setAssistantLiveError(message);
|
||||
setAssistantLiveStatus("Ошибка ассистента");
|
||||
log(`Live-чат ассистента: ошибка отправки сообщения: ${message}`);
|
||||
} finally {
|
||||
stopTicker();
|
||||
setAssistantLiveBusy(false);
|
||||
}
|
||||
}, [
|
||||
assistantLiveInput,
|
||||
assistantLiveSessionId,
|
||||
assistantLiveUseMock,
|
||||
assistantPromptVersion,
|
||||
connection,
|
||||
log,
|
||||
prompts
|
||||
]);
|
||||
|
||||
const commitLimitInput = useCallback(
|
||||
(raw: string) => {
|
||||
const normalized = raw.trim();
|
||||
@@ -933,6 +1025,7 @@ export function AutoRunsHistoryPanel({
|
||||
}
|
||||
|
||||
const useMockForRun = filters.useMock === "true";
|
||||
const effectiveAnalysisDate = normalizeAnalysisDateInput(analysisDate);
|
||||
const payload = await apiClient.startEvalRunAsync({
|
||||
connection,
|
||||
prompts,
|
||||
@@ -941,7 +1034,8 @@ export function AutoRunsHistoryPanel({
|
||||
caseSetFile: generation.saved_case_set_file ?? undefined,
|
||||
useMock: useMockForRun,
|
||||
evalTarget: "assistant_stage1",
|
||||
questions: questionsForRun
|
||||
questions: questionsForRun,
|
||||
analysisDate: effectiveAnalysisDate || undefined
|
||||
});
|
||||
|
||||
const liveJob = payload.job;
|
||||
@@ -955,7 +1049,8 @@ export function AutoRunsHistoryPanel({
|
||||
|
||||
log(
|
||||
`Запущен async-прогон job=${liveJob.job_id}, run_id=${liveJob.run_id}, вопросов=${questionsForRun.length}` +
|
||||
(generation.saved_case_set_file ? `, base_case_set=${generation.saved_case_set_file}` : "")
|
||||
(generation.saved_case_set_file ? `, base_case_set=${generation.saved_case_set_file}` : "") +
|
||||
(effectiveAnalysisDate ? `, analysis_date=${effectiveAnalysisDate}` : ", analysis_date=current_state")
|
||||
);
|
||||
void pollAsyncJobStatus(liveJob.job_id);
|
||||
} catch (error) {
|
||||
@@ -965,6 +1060,7 @@ export function AutoRunsHistoryPanel({
|
||||
setAutogenRunBusy(false);
|
||||
}
|
||||
}, [
|
||||
analysisDate,
|
||||
assistantPromptVersion,
|
||||
connection,
|
||||
editableGeneratedQuestions,
|
||||
@@ -1235,6 +1331,9 @@ export function AutoRunsHistoryPanel({
|
||||
limit: typeof savedFilters.limit === "number" ? Math.max(1, Math.min(500, savedFilters.limit)) : prev.limit
|
||||
}));
|
||||
}
|
||||
if (typeof parsed.analysisDate === "string") {
|
||||
setAnalysisDate(normalizeAnalysisDateInput(parsed.analysisDate));
|
||||
}
|
||||
if (parsed.autoGenSettings) {
|
||||
setAutoGenSettings((prev) => {
|
||||
const nextPrompts: Record<string, string> = {
|
||||
@@ -1290,6 +1389,7 @@ export function AutoRunsHistoryPanel({
|
||||
const saveUiConfig = useCallback(() => {
|
||||
const payload: AutoRunsUiConfig = {
|
||||
filters,
|
||||
analysisDate,
|
||||
autoGenSettings: {
|
||||
mode: autoGenSettings.mode,
|
||||
count: autoGenSettings.count,
|
||||
@@ -1302,7 +1402,7 @@ export function AutoRunsHistoryPanel({
|
||||
hideResolvedAnnotations
|
||||
};
|
||||
localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload));
|
||||
}, [annotationDecisionFilter, autoGenSettings, filters, hideResolvedAnnotations]);
|
||||
}, [analysisDate, annotationDecisionFilter, autoGenSettings, filters, hideResolvedAnnotations]);
|
||||
|
||||
useEffect(() => {
|
||||
const onSave = () => {
|
||||
@@ -1542,6 +1642,25 @@ export function AutoRunsHistoryPanel({
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="autoruns-form-grid">
|
||||
<label>
|
||||
Дата анализа (срез)
|
||||
<input
|
||||
type="date"
|
||||
value={analysisDate}
|
||||
onChange={(event) => setAnalysisDate(normalizeAnalysisDateInput(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<div className="button-row">
|
||||
<button type="button" className="tab" disabled={!analysisDate} onClick={() => setAnalysisDate("")}>
|
||||
Сбросить дату среза
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="muted">
|
||||
Если дата среза задана, автопрогон анализирует данные на эту дату. Если поле пустое, используется текущее состояние.
|
||||
</p>
|
||||
|
||||
<div className="button-row">
|
||||
<button type="button" disabled={autoGenBusy} onClick={() => void generateAutogenBatch()}>
|
||||
{autoGenBusy ? "Генерирую..." : "Сгенерировать пачку"}
|
||||
@@ -1704,6 +1823,7 @@ export function AutoRunsHistoryPanel({
|
||||
<div className="autoruns-run-meta">
|
||||
режим={run.mode ?? "нет данных"} | mock={String(run.use_mock)}
|
||||
</div>
|
||||
<div className="autoruns-run-meta">analysis_date={run.analysis_date ?? "current_state"}</div>
|
||||
{run.llm_provider || run.model ? (
|
||||
<div className="autoruns-run-meta">
|
||||
llm={run.llm_provider ?? "нет данных"} | модель={run.model ?? "нет данных"}
|
||||
@@ -1879,39 +1999,21 @@ export function AutoRunsHistoryPanel({
|
||||
</section>
|
||||
|
||||
{showAssistantMode ? (
|
||||
<section className="autoruns-col">
|
||||
<div className="autoruns-col-header">
|
||||
<h3>Режим ассистента</h3>
|
||||
</div>
|
||||
<div className="autoruns-meta-list">
|
||||
<div>
|
||||
<span>источник:</span>
|
||||
<strong>{dialog?.source ?? "нет данных"}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>сессия:</span>
|
||||
<strong>{dialog?.session_id ?? "нет данных"}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>контур прогона:</span>
|
||||
<strong>{activeRunSummary?.eval_target ?? "нет данных"}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>оценка прогона:</span>
|
||||
<strong>{formatScore(activeRunSummary?.score_index ?? null)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>комментарии:</span>
|
||||
<strong>{runDetail?.annotations_summary?.total ?? 0}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<h4>Пакет режима ассистента</h4>
|
||||
<JsonView value={dialog?.assistant_mode ?? { note: "assistant_mode недоступен" }} />
|
||||
<h4 style={{ marginTop: 12 }}>Проверки кейса</h4>
|
||||
<JsonView value={activeCase?.checks ?? { note: "checks недоступен" }} />
|
||||
<h4 style={{ marginTop: 12 }}>Сабскор метрик</h4>
|
||||
<JsonView value={activeCase?.metric_subscores ?? { note: "metric_subscores недоступен" }} />
|
||||
</section>
|
||||
<div className="autoruns-col autoruns-assistant-live-col">
|
||||
<AssistantPanel
|
||||
sessionId={assistantLiveSessionId}
|
||||
conversation={assistantLiveConversation}
|
||||
inputValue={assistantLiveInput}
|
||||
onInputChange={setAssistantLiveInput}
|
||||
useMock={assistantLiveUseMock}
|
||||
onUseMockChange={setAssistantLiveUseMock}
|
||||
onSend={sendAssistantLiveMessage}
|
||||
onClear={resetAssistantLiveSession}
|
||||
busy={assistantLiveBusy}
|
||||
statusText={assistantLiveStatus}
|
||||
errorMessage={assistantLiveError}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showDecompositionMode ? (
|
||||
|
||||
@@ -96,6 +96,7 @@ export interface AutoRunSummary {
|
||||
llm_provider: string | null;
|
||||
model: string | null;
|
||||
use_mock: boolean | null;
|
||||
analysis_date?: string | null;
|
||||
prompt_version: string | null;
|
||||
schema_version: string | null;
|
||||
suite_id: string | null;
|
||||
@@ -367,6 +368,7 @@ export interface AsyncEvalRunJob {
|
||||
eval_target: AutoRunTarget;
|
||||
run_id: string;
|
||||
case_set_file: string | null;
|
||||
analysis_date?: string | null;
|
||||
total_cases: number;
|
||||
completed_cases: number;
|
||||
error: string | null;
|
||||
@@ -376,6 +378,7 @@ export interface AsyncEvalRunJob {
|
||||
run_timestamp: string | null;
|
||||
score_index: number | null;
|
||||
cases_total: number | null;
|
||||
analysis_date?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -249,18 +249,57 @@ body,
|
||||
background: rgb(var(--rgb-surface-focus));
|
||||
}
|
||||
|
||||
.assistant-panel-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
.assistant-toolbar {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.assistant-toolbar-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.assistant-toolbar-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.assistant-toolbar-meta-right {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.assistant-live-status {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.assistant-toolbar-error {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.assistant-copy-btn {
|
||||
width: 100%;
|
||||
justify-self: stretch;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--text-main);
|
||||
font-size: 0.6rem;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
padding: 6px 8px;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
@@ -402,16 +441,23 @@ button:disabled {
|
||||
}
|
||||
|
||||
.assistant-chat-list {
|
||||
max-height: 420px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 4px;
|
||||
overscroll-behavior: contain;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: 12px;
|
||||
background: rgb(var(--rgb-surface-horizontal));
|
||||
}
|
||||
|
||||
.assistant-chat-list .assistant-msg:first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.assistant-empty {
|
||||
padding: 18px;
|
||||
text-align: center;
|
||||
@@ -420,32 +466,45 @@ button:disabled {
|
||||
.assistant-msg {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: rgb(var(--rgb-surface-main));
|
||||
padding: 10px;
|
||||
background: rgb(var(--rgb-surface-focus));
|
||||
padding: 8px 10px;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.assistant-msg.user {
|
||||
margin-left: 12%;
|
||||
border-color: transparent;
|
||||
background: rgb(var(--rgb-surface-focus));
|
||||
background: rgb(var(--rgb-active));
|
||||
color: rgb(var(--rgb-active-text));
|
||||
}
|
||||
|
||||
.assistant-msg.assistant {
|
||||
margin-right: 12%;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.assistant-msg.user .assistant-msg-head {
|
||||
color: rgba(var(--rgb-active-text), 0.9);
|
||||
}
|
||||
|
||||
.assistant-msg.user .assistant-msg-body {
|
||||
color: rgb(var(--rgb-active-text));
|
||||
}
|
||||
|
||||
.assistant-msg-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.78rem;
|
||||
margin-bottom: 0;
|
||||
font-size: 0.74rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.assistant-msg-body {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.45;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.35;
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.assistant-trace {
|
||||
@@ -465,11 +524,63 @@ button:disabled {
|
||||
}
|
||||
|
||||
.assistant-compose {
|
||||
margin-top: 12px;
|
||||
margin-top: 0;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.assistant-send-row {
|
||||
align-items: center;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.assistant-send-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.app-root-autoruns .assistant-panel-frame .panel-header {
|
||||
position: sticky;
|
||||
top: -12px;
|
||||
z-index: 8;
|
||||
margin: -12px -12px 0;
|
||||
padding: 12px 12px 10px;
|
||||
background: rgb(var(--rgb-surface-main));
|
||||
}
|
||||
|
||||
.app-root-autoruns .assistant-panel-frame {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-root-autoruns .assistant-panel-frame .panel-body {
|
||||
flex: 1 1 auto;
|
||||
padding: 0 12px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-root-autoruns .assistant-panel-frame .assistant-live-shell {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
background: rgb(var(--rgb-background));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.app-root-autoruns .assistant-panel-frame .assistant-chat-list {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.app-root-autoruns .assistant-panel-frame .panel-header h2 {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.json-view {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
@@ -630,6 +741,18 @@ button:disabled {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.autoruns-assistant-live-col {
|
||||
background: rgb(var(--rgb-surface-main));
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
scrollbar-gutter: auto;
|
||||
}
|
||||
|
||||
.autoruns-assistant-live-col .panel-frame {
|
||||
height: 100%;
|
||||
background: rgb(var(--rgb-surface-main));
|
||||
}
|
||||
|
||||
.autoruns-col h3 {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
|
||||
Reference in New Issue
Block a user