АДРЕСНЫЙ РЕЖИМ - авторан история - база + ДИЗАЙН
This commit is contained in:
parent
60a4bef88c
commit
cf5ba1afc2
|
|
@ -12,6 +12,10 @@ export const designConfig = {
|
||||||
scrollbarTrackRgb: "20, 20, 20",
|
scrollbarTrackRgb: "20, 20, 20",
|
||||||
scrollbarThumbRgb: "30, 30, 30",
|
scrollbarThumbRgb: "30, 30, 30",
|
||||||
scrollbarThumbHoverRgb: "30, 50, 30"
|
scrollbarThumbHoverRgb: "30, 50, 30"
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
modeColumnWidthPx: 440,
|
||||||
|
modeToggleWidthPx: 188
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
||||||
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
|
|
@ -4,8 +4,8 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>NDC AI Normalizer Playground</title>
|
<title>NDC AI Normalizer Playground</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BXQlrB3i.js"></script>
|
<script type="module" crossorigin src="/assets/index-Cbn_mHUl.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BVc11Mnb.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BT0bMOoF.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { apiClient } from "./api/client";
|
import { apiClient } from "./api/client";
|
||||||
|
import { AssistantSamPanel } from "./components/AssistantSamPanel";
|
||||||
import { AutoRunsHistoryPanel } from "./components/AutoRunsHistoryPanel";
|
import { AutoRunsHistoryPanel } from "./components/AutoRunsHistoryPanel";
|
||||||
import { AssistantPanel } from "./components/AssistantPanel";
|
import { AssistantPanel } from "./components/AssistantPanel";
|
||||||
import { ConnectionPanel } from "./components/ConnectionPanel";
|
import { ConnectionPanel } from "./components/ConnectionPanel";
|
||||||
|
|
@ -24,10 +25,13 @@ import type {
|
||||||
} from "./state/types";
|
} from "./state/types";
|
||||||
|
|
||||||
const SESSION_CONFIG_KEY = "ndc_normalizer_session_config_v1";
|
const SESSION_CONFIG_KEY = "ndc_normalizer_session_config_v1";
|
||||||
|
const AUTORUNS_LAYOUT_CONFIG_KEY = "ndc_autoruns_layout_config_v1";
|
||||||
|
const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save";
|
||||||
const ASSISTANT_STAGES = ["Анализ запроса", "Получение данных", "Подготовка ответа"];
|
const ASSISTANT_STAGES = ["Анализ запроса", "Получение данных", "Подготовка ответа"];
|
||||||
const DEFAULT_UI_MODE: UiMode = "assistant";
|
const DEFAULT_UI_MODE: UiMode = "assistant";
|
||||||
const AUTOLOAD_PROMPT_VERSION = "normalizer_v2_0_2";
|
const AUTOLOAD_PROMPT_VERSION = "normalizer_v2_0_2";
|
||||||
const ASSISTANT_PROMPT_VERSION = "address_query_runtime_v1";
|
const ASSISTANT_PROMPT_VERSION = "address_query_runtime_v1";
|
||||||
|
const TAB_KEYS: TabKey[] = ["normalized", "fragments", "scope", "flags", "route", "raw", "validation", "logs"];
|
||||||
|
|
||||||
function withTs(message: string): string {
|
function withTs(message: string): string {
|
||||||
return `[${new Date().toLocaleTimeString("ru-RU")}] ${message}`;
|
return `[${new Date().toLocaleTimeString("ru-RU")}] ${message}`;
|
||||||
|
|
@ -83,6 +87,18 @@ export default function App() {
|
||||||
const [showAutorunsAssistantMode, setShowAutorunsAssistantMode] = useState(true);
|
const [showAutorunsAssistantMode, setShowAutorunsAssistantMode] = useState(true);
|
||||||
const [showAutorunsDecompositionMode, setShowAutorunsDecompositionMode] = useState(true);
|
const [showAutorunsDecompositionMode, setShowAutorunsDecompositionMode] = useState(true);
|
||||||
const [showAutorunsProgressMode, setShowAutorunsProgressMode] = useState(true);
|
const [showAutorunsProgressMode, setShowAutorunsProgressMode] = useState(true);
|
||||||
|
const [showAutorunsCommentsMode, setShowAutorunsCommentsMode] = useState(true);
|
||||||
|
const [showAssistantConnectionMode, setShowAssistantConnectionMode] = useState(true);
|
||||||
|
const [showAssistantPromptMode, setShowAssistantPromptMode] = useState(true);
|
||||||
|
const [showAssistantChatMode, setShowAssistantChatMode] = useState(true);
|
||||||
|
const [showAssistantSamMode, setShowAssistantSamMode] = useState(true);
|
||||||
|
const [showDecompositionConnectionMode, setShowDecompositionConnectionMode] = useState(true);
|
||||||
|
const [showDecompositionPromptMode, setShowDecompositionPromptMode] = useState(true);
|
||||||
|
const [showDecompositionQueryMode, setShowDecompositionQueryMode] = useState(true);
|
||||||
|
const [showDecompositionOutputMode, setShowDecompositionOutputMode] = useState(true);
|
||||||
|
const [showDecompositionMetricsMode, setShowDecompositionMetricsMode] = useState(true);
|
||||||
|
const [showDecompositionHistoryMode, setShowDecompositionHistoryMode] = useState(true);
|
||||||
|
const [showDecompositionRuntimeMode, setShowDecompositionRuntimeMode] = useState(true);
|
||||||
const [assistantSessionId, setAssistantSessionId] = useState("");
|
const [assistantSessionId, setAssistantSessionId] = useState("");
|
||||||
const [assistantConversation, setAssistantConversation] = useState<AssistantConversationItem[]>([]);
|
const [assistantConversation, setAssistantConversation] = useState<AssistantConversationItem[]>([]);
|
||||||
const [assistantInput, setAssistantInput] = useState("");
|
const [assistantInput, setAssistantInput] = useState("");
|
||||||
|
|
@ -90,6 +106,7 @@ export default function App() {
|
||||||
const [assistantStatus, setAssistantStatus] = useState("");
|
const [assistantStatus, setAssistantStatus] = useState("");
|
||||||
const [assistantError, setAssistantError] = useState("");
|
const [assistantError, setAssistantError] = useState("");
|
||||||
const presetAutoloadDoneRef = useRef(false);
|
const presetAutoloadDoneRef = useRef(false);
|
||||||
|
const skipPresetAutoloadRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
|
|
@ -106,6 +123,8 @@ export default function App() {
|
||||||
root.style.setProperty("--rgb-scrollbar-track", colors.scrollbarTrackRgb);
|
root.style.setProperty("--rgb-scrollbar-track", colors.scrollbarTrackRgb);
|
||||||
root.style.setProperty("--rgb-scrollbar-thumb", colors.scrollbarThumbRgb);
|
root.style.setProperty("--rgb-scrollbar-thumb", colors.scrollbarThumbRgb);
|
||||||
root.style.setProperty("--rgb-scrollbar-thumb-hover", colors.scrollbarThumbHoverRgb);
|
root.style.setProperty("--rgb-scrollbar-thumb-hover", colors.scrollbarThumbHoverRgb);
|
||||||
|
root.style.setProperty("--mode-column-width", `${designConfig.layout.modeColumnWidthPx}px`);
|
||||||
|
root.style.setProperty("--mode-toggle-width", `${designConfig.layout.modeToggleWidthPx}px`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const log = (message: string) => {
|
const log = (message: string) => {
|
||||||
|
|
@ -140,6 +159,92 @@ export default function App() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cachedAutorunsLayout = localStorage.getItem(AUTORUNS_LAYOUT_CONFIG_KEY);
|
||||||
|
if (cachedAutorunsLayout) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(cachedAutorunsLayout) as {
|
||||||
|
uiMode?: UiMode;
|
||||||
|
activeTab?: TabKey;
|
||||||
|
showAutorunsAssistantMode?: boolean;
|
||||||
|
showAutorunsDecompositionMode?: boolean;
|
||||||
|
showAutorunsProgressMode?: boolean;
|
||||||
|
showAutorunsCommentsMode?: boolean;
|
||||||
|
showAssistantConnectionMode?: boolean;
|
||||||
|
showAssistantPromptMode?: boolean;
|
||||||
|
showAssistantChatMode?: boolean;
|
||||||
|
showAssistantSamMode?: boolean;
|
||||||
|
showDecompositionConnectionMode?: boolean;
|
||||||
|
showDecompositionPromptMode?: boolean;
|
||||||
|
showDecompositionQueryMode?: boolean;
|
||||||
|
showDecompositionOutputMode?: boolean;
|
||||||
|
showDecompositionMetricsMode?: boolean;
|
||||||
|
showDecompositionHistoryMode?: boolean;
|
||||||
|
showDecompositionRuntimeMode?: boolean;
|
||||||
|
prompts?: PromptState;
|
||||||
|
};
|
||||||
|
if (parsed.uiMode === "assistant" || parsed.uiMode === "decomposition" || parsed.uiMode === "autoruns") {
|
||||||
|
setUiMode(parsed.uiMode);
|
||||||
|
}
|
||||||
|
if (parsed.activeTab && TAB_KEYS.includes(parsed.activeTab)) {
|
||||||
|
setActiveTab(parsed.activeTab);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAutorunsAssistantMode === "boolean") {
|
||||||
|
setShowAutorunsAssistantMode(parsed.showAutorunsAssistantMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAutorunsDecompositionMode === "boolean") {
|
||||||
|
setShowAutorunsDecompositionMode(parsed.showAutorunsDecompositionMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAutorunsProgressMode === "boolean") {
|
||||||
|
setShowAutorunsProgressMode(parsed.showAutorunsProgressMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAutorunsCommentsMode === "boolean") {
|
||||||
|
setShowAutorunsCommentsMode(parsed.showAutorunsCommentsMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAssistantConnectionMode === "boolean") {
|
||||||
|
setShowAssistantConnectionMode(parsed.showAssistantConnectionMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAssistantPromptMode === "boolean") {
|
||||||
|
setShowAssistantPromptMode(parsed.showAssistantPromptMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAssistantChatMode === "boolean") {
|
||||||
|
setShowAssistantChatMode(parsed.showAssistantChatMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showAssistantSamMode === "boolean") {
|
||||||
|
setShowAssistantSamMode(parsed.showAssistantSamMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionConnectionMode === "boolean") {
|
||||||
|
setShowDecompositionConnectionMode(parsed.showDecompositionConnectionMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionPromptMode === "boolean") {
|
||||||
|
setShowDecompositionPromptMode(parsed.showDecompositionPromptMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionQueryMode === "boolean") {
|
||||||
|
setShowDecompositionQueryMode(parsed.showDecompositionQueryMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionOutputMode === "boolean") {
|
||||||
|
setShowDecompositionOutputMode(parsed.showDecompositionOutputMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionMetricsMode === "boolean") {
|
||||||
|
setShowDecompositionMetricsMode(parsed.showDecompositionMetricsMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionHistoryMode === "boolean") {
|
||||||
|
setShowDecompositionHistoryMode(parsed.showDecompositionHistoryMode);
|
||||||
|
}
|
||||||
|
if (typeof parsed.showDecompositionRuntimeMode === "boolean") {
|
||||||
|
setShowDecompositionRuntimeMode(parsed.showDecompositionRuntimeMode);
|
||||||
|
}
|
||||||
|
if (parsed.prompts) {
|
||||||
|
setPrompts((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...parsed.prompts
|
||||||
|
}));
|
||||||
|
skipPresetAutoloadRef.current = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore broken local cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void refreshHistory();
|
void refreshHistory();
|
||||||
void refreshPresets();
|
void refreshPresets();
|
||||||
void refreshRuns();
|
void refreshRuns();
|
||||||
|
|
@ -159,6 +264,10 @@ export default function App() {
|
||||||
const payload = await apiClient.loadPresets();
|
const payload = await apiClient.loadPresets();
|
||||||
const presets = payload.presets ?? [];
|
const presets = payload.presets ?? [];
|
||||||
setPresetList(presets);
|
setPresetList(presets);
|
||||||
|
if (skipPresetAutoloadRef.current) {
|
||||||
|
presetAutoloadDoneRef.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (presetAutoloadDoneRef.current) {
|
if (presetAutoloadDoneRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -209,6 +318,34 @@ export default function App() {
|
||||||
log("Local config saved (without API key).");
|
log("Local config saved (without API key).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveAutorunsLayout() {
|
||||||
|
localStorage.setItem(
|
||||||
|
AUTORUNS_LAYOUT_CONFIG_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
uiMode,
|
||||||
|
activeTab,
|
||||||
|
showAutorunsAssistantMode,
|
||||||
|
showAutorunsDecompositionMode,
|
||||||
|
showAutorunsProgressMode,
|
||||||
|
showAutorunsCommentsMode,
|
||||||
|
showAssistantConnectionMode,
|
||||||
|
showAssistantPromptMode,
|
||||||
|
showAssistantChatMode,
|
||||||
|
showAssistantSamMode,
|
||||||
|
showDecompositionConnectionMode,
|
||||||
|
showDecompositionPromptMode,
|
||||||
|
showDecompositionQueryMode,
|
||||||
|
showDecompositionOutputMode,
|
||||||
|
showDecompositionMetricsMode,
|
||||||
|
showDecompositionHistoryMode,
|
||||||
|
showDecompositionRuntimeMode,
|
||||||
|
prompts
|
||||||
|
})
|
||||||
|
);
|
||||||
|
window.dispatchEvent(new CustomEvent(AUTORUNS_SAVE_EVENT));
|
||||||
|
log("UI layout and prompts saved.");
|
||||||
|
}
|
||||||
|
|
||||||
async function testConnection() {
|
async function testConnection() {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
setLastError("");
|
setLastError("");
|
||||||
|
|
@ -522,10 +659,6 @@ export default function App() {
|
||||||
userMessage,
|
userMessage,
|
||||||
sessionId: assistantSessionId || undefined,
|
sessionId: assistantSessionId || undefined,
|
||||||
promptVersion: ASSISTANT_PROMPT_VERSION,
|
promptVersion: ASSISTANT_PROMPT_VERSION,
|
||||||
context: {
|
|
||||||
periodHint: query.periodHint,
|
|
||||||
businessContext: query.businessContext
|
|
||||||
},
|
|
||||||
useMock
|
useMock
|
||||||
});
|
});
|
||||||
setAssistantSessionId(response.session_id);
|
setAssistantSessionId(response.session_id);
|
||||||
|
|
@ -555,7 +688,11 @@ export default function App() {
|
||||||
}, [selectedRunId]);
|
}, [selectedRunId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={`app-root ${uiMode === "autoruns" ? "app-root-autoruns" : ""}`}>
|
<main
|
||||||
|
className={`app-root ${
|
||||||
|
uiMode === "assistant" || uiMode === "decomposition" || uiMode === "autoruns" ? "app-root-autoruns" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<header className="app-topbar">
|
<header className="app-topbar">
|
||||||
<div className="mode-switch-row">
|
<div className="mode-switch-row">
|
||||||
<button type="button" className={uiMode === "assistant" ? "tab active" : "tab"} onClick={() => setUiMode("assistant")}>
|
<button type="button" className={uiMode === "assistant" ? "tab active" : "tab"} onClick={() => setUiMode("assistant")}>
|
||||||
|
|
@ -567,9 +704,79 @@ export default function App() {
|
||||||
<button type="button" className={uiMode === "autoruns" ? "tab active" : "tab"} onClick={() => setUiMode("autoruns")}>
|
<button type="button" className={uiMode === "autoruns" ? "tab active" : "tab"} onClick={() => setUiMode("autoruns")}>
|
||||||
История автопрогонов
|
История автопрогонов
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" className="tab" onClick={saveAutorunsLayout}>
|
||||||
|
Сохранить
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{uiMode === "autoruns" ? (
|
{uiMode === "assistant" ? (
|
||||||
|
<div className="mode-switch-row mode-switch-row-right">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showAssistantConnectionMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowAssistantConnectionMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
LLM Connector
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showAssistantPromptMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowAssistantPromptMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
Prompt Manager
|
||||||
|
</button>
|
||||||
|
<button type="button" className={showAssistantChatMode ? "tab active" : "tab"} onClick={() => setShowAssistantChatMode((prev) => !prev)}>
|
||||||
|
Режим ассистента
|
||||||
|
</button>
|
||||||
|
<button type="button" className={showAssistantSamMode ? "tab active" : "tab"} onClick={() => setShowAssistantSamMode((prev) => !prev)}>
|
||||||
|
SAM
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : uiMode === "decomposition" ? (
|
||||||
|
<div className="mode-switch-row mode-switch-row-right">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showDecompositionConnectionMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowDecompositionConnectionMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
LLM
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showDecompositionPromptMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowDecompositionPromptMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
Prompt
|
||||||
|
</button>
|
||||||
|
<button type="button" className={showDecompositionQueryMode ? "tab active" : "tab"} onClick={() => setShowDecompositionQueryMode((prev) => !prev)}>
|
||||||
|
Запрос
|
||||||
|
</button>
|
||||||
|
<button type="button" className={showDecompositionOutputMode ? "tab active" : "tab"} onClick={() => setShowDecompositionOutputMode((prev) => !prev)}>
|
||||||
|
Выход
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showDecompositionMetricsMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowDecompositionMetricsMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
Метрики
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showDecompositionHistoryMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowDecompositionHistoryMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
История
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showDecompositionRuntimeMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowDecompositionRuntimeMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
NDC Run Monitor
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : uiMode === "autoruns" ? (
|
||||||
<div className="mode-switch-row mode-switch-row-right">
|
<div className="mode-switch-row mode-switch-row-right">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -592,116 +799,190 @@ export default function App() {
|
||||||
>
|
>
|
||||||
Прогресс/регресс
|
Прогресс/регресс
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={showAutorunsCommentsMode ? "tab active" : "tab"}
|
||||||
|
onClick={() => setShowAutorunsCommentsMode((prev) => !prev)}
|
||||||
|
>
|
||||||
|
Комментарии
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{uiMode === "assistant" ? (
|
{uiMode === "assistant" ? (
|
||||||
<div className="layout-grid">
|
<div className="layout-grid layout-grid-mode-columns">
|
||||||
<ConnectionPanel
|
<div className="mode-columns">
|
||||||
value={connection}
|
{showAssistantConnectionMode ? (
|
||||||
modelOptions={modelOptions}
|
<div className="mode-col">
|
||||||
modelsBusy={modelsBusy}
|
<ConnectionPanel
|
||||||
onChange={setConnection}
|
value={connection}
|
||||||
onReloadModels={reloadModels}
|
modelOptions={modelOptions}
|
||||||
onSaveLocalConfig={saveLocalConfig}
|
modelsBusy={modelsBusy}
|
||||||
onTestConnection={testConnection}
|
onChange={setConnection}
|
||||||
lastStatus={connectionStatus}
|
onReloadModels={reloadModels}
|
||||||
busy={busy || assistantBusy}
|
onSaveLocalConfig={saveLocalConfig}
|
||||||
/>
|
onTestConnection={testConnection}
|
||||||
|
lastStatus={connectionStatus}
|
||||||
|
busy={busy || assistantBusy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<PromptPanel
|
{showAssistantPromptMode ? (
|
||||||
value={prompts}
|
<div className="mode-col mode-col-wide">
|
||||||
onChange={setPrompts}
|
<PromptPanel
|
||||||
presets={presetList}
|
value={prompts}
|
||||||
selectedPresetId={selectedPresetId}
|
onChange={setPrompts}
|
||||||
onSelectPreset={setSelectedPresetId}
|
presets={presetList}
|
||||||
onLoadPreset={loadSelectedPreset}
|
selectedPresetId={selectedPresetId}
|
||||||
onSavePreset={savePreset}
|
onSelectPreset={setSelectedPresetId}
|
||||||
onResetDefaults={resetDefaults}
|
onLoadPreset={loadSelectedPreset}
|
||||||
onDiffPrevious={diffWithPrevious}
|
onSavePreset={savePreset}
|
||||||
presetName={presetName}
|
onResetDefaults={resetDefaults}
|
||||||
onPresetNameChange={setPresetName}
|
onDiffPrevious={diffWithPrevious}
|
||||||
diffSummary={diffSummary}
|
presetName={presetName}
|
||||||
/>
|
onPresetNameChange={setPresetName}
|
||||||
|
diffSummary={diffSummary}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<AssistantPanel
|
{showAssistantChatMode ? (
|
||||||
sessionId={assistantSessionId}
|
<div className="mode-col mode-col-xwide">
|
||||||
conversation={assistantConversation}
|
<AssistantPanel
|
||||||
inputValue={assistantInput}
|
sessionId={assistantSessionId}
|
||||||
onInputChange={setAssistantInput}
|
conversation={assistantConversation}
|
||||||
periodHint={query.periodHint}
|
inputValue={assistantInput}
|
||||||
onPeriodHintChange={(value) => setQuery((prev) => ({ ...prev, periodHint: value }))}
|
onInputChange={setAssistantInput}
|
||||||
businessContext={query.businessContext}
|
useMock={useMock}
|
||||||
onBusinessContextChange={(value) => setQuery((prev) => ({ ...prev, businessContext: value }))}
|
onUseMockChange={setUseMock}
|
||||||
useMock={useMock}
|
onSend={sendAssistantMessage}
|
||||||
onUseMockChange={setUseMock}
|
onClear={resetAssistantSession}
|
||||||
onSend={sendAssistantMessage}
|
busy={assistantBusy}
|
||||||
onClear={resetAssistantSession}
|
statusText={assistantStatus}
|
||||||
busy={assistantBusy}
|
errorMessage={assistantError}
|
||||||
statusText={assistantStatus}
|
/>
|
||||||
errorMessage={assistantError}
|
</div>
|
||||||
/>
|
) : null}
|
||||||
|
|
||||||
|
{showAssistantSamMode ? (
|
||||||
|
<div className="mode-col">
|
||||||
|
<AssistantSamPanel
|
||||||
|
sessionId={assistantSessionId}
|
||||||
|
conversation={assistantConversation}
|
||||||
|
statusText={assistantStatus}
|
||||||
|
errorMessage={assistantError}
|
||||||
|
useMock={useMock}
|
||||||
|
appLogs={appLogs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!showAssistantConnectionMode && !showAssistantPromptMode && !showAssistantChatMode && !showAssistantSamMode ? (
|
||||||
|
<div className="mode-columns-empty">Все панели режима ассистента скрыты. Включите нужные блоки справа в шапке.</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : uiMode === "decomposition" ? (
|
) : uiMode === "decomposition" ? (
|
||||||
<div className="layout-grid">
|
<div className="layout-grid layout-grid-mode-columns">
|
||||||
<ConnectionPanel
|
<div className="mode-columns">
|
||||||
value={connection}
|
{showDecompositionConnectionMode ? (
|
||||||
modelOptions={modelOptions}
|
<div className="mode-col">
|
||||||
modelsBusy={modelsBusy}
|
<ConnectionPanel
|
||||||
onChange={setConnection}
|
value={connection}
|
||||||
onReloadModels={reloadModels}
|
modelOptions={modelOptions}
|
||||||
onSaveLocalConfig={saveLocalConfig}
|
modelsBusy={modelsBusy}
|
||||||
onTestConnection={testConnection}
|
onChange={setConnection}
|
||||||
lastStatus={connectionStatus}
|
onReloadModels={reloadModels}
|
||||||
busy={busy}
|
onSaveLocalConfig={saveLocalConfig}
|
||||||
/>
|
onTestConnection={testConnection}
|
||||||
|
lastStatus={connectionStatus}
|
||||||
|
busy={busy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<PromptPanel
|
{showDecompositionPromptMode ? (
|
||||||
value={prompts}
|
<div className="mode-col mode-col-wide">
|
||||||
onChange={setPrompts}
|
<PromptPanel
|
||||||
presets={presetList}
|
value={prompts}
|
||||||
selectedPresetId={selectedPresetId}
|
onChange={setPrompts}
|
||||||
onSelectPreset={setSelectedPresetId}
|
presets={presetList}
|
||||||
onLoadPreset={loadSelectedPreset}
|
selectedPresetId={selectedPresetId}
|
||||||
onSavePreset={savePreset}
|
onSelectPreset={setSelectedPresetId}
|
||||||
onResetDefaults={resetDefaults}
|
onLoadPreset={loadSelectedPreset}
|
||||||
onDiffPrevious={diffWithPrevious}
|
onSavePreset={savePreset}
|
||||||
presetName={presetName}
|
onResetDefaults={resetDefaults}
|
||||||
onPresetNameChange={setPresetName}
|
onDiffPrevious={diffWithPrevious}
|
||||||
diffSummary={diffSummary}
|
presetName={presetName}
|
||||||
/>
|
onPresetNameChange={setPresetName}
|
||||||
|
diffSummary={diffSummary}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<QueryPanel
|
{showDecompositionQueryMode ? (
|
||||||
value={query}
|
<div className="mode-col">
|
||||||
onChange={setQuery}
|
<QueryPanel
|
||||||
onApplyBatchFormat={applyBatchFormat}
|
value={query}
|
||||||
onNormalize={normalize}
|
onChange={setQuery}
|
||||||
busy={busy}
|
onApplyBatchFormat={applyBatchFormat}
|
||||||
useMock={useMock}
|
onNormalize={normalize}
|
||||||
onUseMockChange={setUseMock}
|
busy={busy}
|
||||||
errorMessage={lastError}
|
useMock={useMock}
|
||||||
/>
|
onUseMockChange={setUseMock}
|
||||||
|
errorMessage={lastError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<OutputPanel tab={activeTab} onTabChange={setActiveTab} result={result} appLogs={appLogs} />
|
{showDecompositionOutputMode ? (
|
||||||
|
<div className="mode-col mode-col-xwide">
|
||||||
|
<OutputPanel tab={activeTab} onTabChange={setActiveTab} result={result} appLogs={appLogs} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<MetricsPanel result={result} />
|
{showDecompositionMetricsMode ? (
|
||||||
|
<div className="mode-col">
|
||||||
|
<MetricsPanel result={result} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<HistoryPanel items={historyItems} onRefresh={refreshHistory} onOpenTrace={openTrace} />
|
{showDecompositionHistoryMode ? (
|
||||||
|
<div className="mode-col">
|
||||||
|
<HistoryPanel items={historyItems} onRefresh={refreshHistory} onOpenTrace={openTrace} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<RuntimePanel
|
{showDecompositionRuntimeMode ? (
|
||||||
runs={runs}
|
<div className="mode-col mode-col-xwide">
|
||||||
selectedRunId={selectedRunId}
|
<RuntimePanel
|
||||||
onSelectRun={setSelectedRunId}
|
runs={runs}
|
||||||
onStartRun={startRun}
|
selectedRunId={selectedRunId}
|
||||||
onFinishRun={finishRun}
|
onSelectRun={setSelectedRunId}
|
||||||
onRefreshRuns={refreshRuns}
|
onStartRun={startRun}
|
||||||
onRunEval={runEval}
|
onFinishRun={finishRun}
|
||||||
onCopyEvalReport={copyEvalReport}
|
onRefreshRuns={refreshRuns}
|
||||||
evalBusy={evalBusy}
|
onRunEval={runEval}
|
||||||
traceItems={runTrace}
|
onCopyEvalReport={copyEvalReport}
|
||||||
evalReport={evalReport}
|
evalBusy={evalBusy}
|
||||||
/>
|
traceItems={runTrace}
|
||||||
|
evalReport={evalReport}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!showDecompositionConnectionMode &&
|
||||||
|
!showDecompositionPromptMode &&
|
||||||
|
!showDecompositionQueryMode &&
|
||||||
|
!showDecompositionOutputMode &&
|
||||||
|
!showDecompositionMetricsMode &&
|
||||||
|
!showDecompositionHistoryMode &&
|
||||||
|
!showDecompositionRuntimeMode ? (
|
||||||
|
<div className="mode-columns-empty">Все панели режима декомпозиции скрыты. Включите нужные блоки справа в шапке.</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="layout-grid layout-grid-autoruns">
|
<div className="layout-grid layout-grid-autoruns">
|
||||||
|
|
@ -713,6 +994,7 @@ export default function App() {
|
||||||
showAssistantMode={showAutorunsAssistantMode}
|
showAssistantMode={showAutorunsAssistantMode}
|
||||||
showDecompositionMode={showAutorunsDecompositionMode}
|
showDecompositionMode={showAutorunsDecompositionMode}
|
||||||
showProgressMode={showAutorunsProgressMode}
|
showProgressMode={showAutorunsProgressMode}
|
||||||
|
showCommentsMode={showAutorunsCommentsMode}
|
||||||
onLog={log}
|
onLog={log}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -720,4 +1002,3 @@ export default function App() {
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,8 @@ export const apiClient = {
|
||||||
assistant_prompt_version?: string;
|
assistant_prompt_version?: string;
|
||||||
decomposition_prompt_version?: string;
|
decomposition_prompt_version?: string;
|
||||||
prompt_fingerprint?: string;
|
prompt_fingerprint?: string;
|
||||||
|
autogen_personality_id?: string;
|
||||||
|
autogen_personality_prompt?: string;
|
||||||
};
|
};
|
||||||
}): Promise<{ ok: boolean; generation: { generation_id: string; created_at: string; mode: AutoGenMode; count: number; domain: string | null; questions: string[]; generated_by: string | null; saved_case_set_file: string | null; context: Record<string, unknown> | null } }> {
|
}): Promise<{ ok: boolean; generation: { generation_id: string; created_at: string; mode: AutoGenMode; count: number; domain: string | null; questions: string[]; generated_by: string | null; saved_case_set_file: string | null; context: Record<string, unknown> | null } }> {
|
||||||
return request("/autoruns/autogen/generate", {
|
return request("/autoruns/autogen/generate", {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,6 @@ interface AssistantPanelProps {
|
||||||
conversation: AssistantConversationItem[];
|
conversation: AssistantConversationItem[];
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
onInputChange: (value: string) => void;
|
onInputChange: (value: string) => void;
|
||||||
periodHint: string;
|
|
||||||
onPeriodHintChange: (value: string) => void;
|
|
||||||
businessContext: string;
|
|
||||||
onBusinessContextChange: (value: string) => void;
|
|
||||||
useMock: boolean;
|
useMock: boolean;
|
||||||
onUseMockChange: (value: boolean) => void;
|
onUseMockChange: (value: boolean) => void;
|
||||||
onSend: () => Promise<void> | void;
|
onSend: () => Promise<void> | void;
|
||||||
|
|
@ -70,10 +66,6 @@ export function AssistantPanel({
|
||||||
conversation,
|
conversation,
|
||||||
inputValue,
|
inputValue,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
periodHint,
|
|
||||||
onPeriodHintChange,
|
|
||||||
businessContext,
|
|
||||||
onBusinessContextChange,
|
|
||||||
useMock,
|
useMock,
|
||||||
onUseMockChange,
|
onUseMockChange,
|
||||||
onSend,
|
onSend,
|
||||||
|
|
@ -175,16 +167,6 @@ export function AssistantPanel({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="assistant-compose">
|
<div className="assistant-compose">
|
||||||
<div className="grid-two">
|
|
||||||
<label>
|
|
||||||
Подсказка по периоду
|
|
||||||
<input value={periodHint} onChange={(event) => onPeriodHintChange(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Бизнес-контекст
|
|
||||||
<input value={businessContext} onChange={(event) => onBusinessContextChange(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label className="full-width">
|
<label className="full-width">
|
||||||
Сообщение
|
Сообщение
|
||||||
<textarea
|
<textarea
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import type { AssistantConversationItem } from "../state/types";
|
||||||
|
import { JsonView } from "./JsonView";
|
||||||
|
import { PanelFrame } from "./PanelFrame";
|
||||||
|
|
||||||
|
interface AssistantSamPanelProps {
|
||||||
|
sessionId: string;
|
||||||
|
conversation: AssistantConversationItem[];
|
||||||
|
statusText: string;
|
||||||
|
errorMessage: string;
|
||||||
|
useMock: boolean;
|
||||||
|
appLogs: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(iso: string): string {
|
||||||
|
const date = new Date(iso);
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return iso;
|
||||||
|
}
|
||||||
|
return date.toLocaleString("ru-RU");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AssistantSamPanel({ sessionId, conversation, statusText, errorMessage, useMock, appLogs }: AssistantSamPanelProps) {
|
||||||
|
const assistantReplies = conversation.filter((item) => item.role === "assistant").length;
|
||||||
|
const userMessages = conversation.filter((item) => item.role === "user").length;
|
||||||
|
const lastMessage = conversation.length > 0 ? conversation[conversation.length - 1] : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelFrame title="SAM" subtitle="System Assistant Monitor: срез по текущей сессии и логам.">
|
||||||
|
<div className="metrics-grid">
|
||||||
|
<div>
|
||||||
|
<span>session_id</span>
|
||||||
|
<strong>{sessionId || "новая сессия"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>mock_mode</span>
|
||||||
|
<strong>{useMock ? "on" : "off"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>сообщений пользователя</span>
|
||||||
|
<strong>{userMessages}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>ответов ассистента</span>
|
||||||
|
<strong>{assistantReplies}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>статус</span>
|
||||||
|
<strong>{statusText || "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>ошибка</span>
|
||||||
|
<strong>{errorMessage || "нет"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>последнее сообщение</span>
|
||||||
|
<strong>{lastMessage?.created_at ? formatDateTime(lastMessage.created_at) : "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style={{ marginTop: 12 }}>Последние системные логи</h3>
|
||||||
|
<JsonView value={appLogs.slice(0, 120)} />
|
||||||
|
</PanelFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -28,11 +28,19 @@ interface AutoRunsHistoryPanelProps {
|
||||||
showAssistantMode: boolean;
|
showAssistantMode: boolean;
|
||||||
showDecompositionMode: boolean;
|
showDecompositionMode: boolean;
|
||||||
showProgressMode: boolean;
|
showProgressMode: boolean;
|
||||||
|
showCommentsMode: boolean;
|
||||||
onLog?: (message: string) => void;
|
onLog?: (message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseMockFilter = "any" | "true" | "false";
|
type UseMockFilter = "any" | "true" | "false";
|
||||||
type LeftTabMode = "settings" | "comments";
|
type AutoGenPersonalityId = "general" | "settlements_60_62" | "month_close_costs_20_44" | "vat_document_register_book";
|
||||||
|
|
||||||
|
interface AutoGenPersonalityDefinition {
|
||||||
|
id: AutoGenPersonalityId;
|
||||||
|
label: string;
|
||||||
|
domain: string;
|
||||||
|
defaultPrompt: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface AutoRunsFilters {
|
interface AutoRunsFilters {
|
||||||
fromLocal: string;
|
fromLocal: string;
|
||||||
|
|
@ -58,7 +66,8 @@ interface CommentModalState {
|
||||||
interface AutoGenSettingsState {
|
interface AutoGenSettingsState {
|
||||||
mode: AutoGenMode;
|
mode: AutoGenMode;
|
||||||
count: number;
|
count: number;
|
||||||
domain: string;
|
personalityId: AutoGenPersonalityId;
|
||||||
|
personalityPrompts: Record<AutoGenPersonalityId, string>;
|
||||||
persistToEvalCases: boolean;
|
persistToEvalCases: boolean;
|
||||||
generatedBy: string;
|
generatedBy: string;
|
||||||
}
|
}
|
||||||
|
|
@ -75,10 +84,67 @@ const DEFAULT_FILTERS: AutoRunsFilters = {
|
||||||
|
|
||||||
const DEFAULT_MANUAL_DECISION: ManualCaseDecision = "needs_dialog_policy_fix";
|
const DEFAULT_MANUAL_DECISION: ManualCaseDecision = "needs_dialog_policy_fix";
|
||||||
|
|
||||||
|
const AUTORUNS_UI_CONFIG_KEY = "ndc_autoruns_ui_config_v1";
|
||||||
|
const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save";
|
||||||
|
|
||||||
|
const AUTOGEN_PERSONALITIES: AutoGenPersonalityDefinition[] = [
|
||||||
|
{
|
||||||
|
id: "general",
|
||||||
|
label: "Общий контур",
|
||||||
|
domain: "",
|
||||||
|
defaultPrompt:
|
||||||
|
"Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "settlements_60_62",
|
||||||
|
label: "Расчеты 60/62",
|
||||||
|
domain: "settlements_60_62",
|
||||||
|
defaultPrompt:
|
||||||
|
"Генерируй вопросы по расчетам с контрагентами (счета 60/62): закрытие задолженности, авансы, сверки, переносы остатков, цепочки документов."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "month_close_costs_20_44",
|
||||||
|
label: "Закрытие месяца 20/44",
|
||||||
|
domain: "month_close_costs_20_44",
|
||||||
|
defaultPrompt:
|
||||||
|
"Генерируй вопросы по закрытию месяца и затратам на счетах 20/44: распределение, закрытие, остатки, аномалии и разницы по периодам."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vat_document_register_book",
|
||||||
|
label: "НДС и регистры",
|
||||||
|
domain: "vat_document_register_book",
|
||||||
|
defaultPrompt:
|
||||||
|
"Генерируй вопросы по НДС: начисление, вычет, книги покупок/продаж, счета-фактуры, прогноз обязательств и сверка цепочки документов."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function buildDefaultPersonalityPrompts(): Record<AutoGenPersonalityId, string> {
|
||||||
|
return AUTOGEN_PERSONALITIES.reduce((acc, item) => {
|
||||||
|
acc[item.id] = item.defaultPrompt;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<AutoGenPersonalityId, string>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AUTOGEN_PERSONALITY_IDS = new Set<AutoGenPersonalityId>(AUTOGEN_PERSONALITIES.map((item) => item.id));
|
||||||
|
|
||||||
|
interface AutoRunsUiConfig {
|
||||||
|
filters?: Partial<AutoRunsFilters>;
|
||||||
|
autoGenSettings?: {
|
||||||
|
mode?: AutoGenMode;
|
||||||
|
count?: number;
|
||||||
|
personalityId?: AutoGenPersonalityId;
|
||||||
|
personalityPrompts?: Partial<Record<AutoGenPersonalityId, string>>;
|
||||||
|
persistToEvalCases?: boolean;
|
||||||
|
generatedBy?: string;
|
||||||
|
};
|
||||||
|
annotationDecisionFilter?: ManualCaseDecision | "all";
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_AUTOGEN_SETTINGS: AutoGenSettingsState = {
|
const DEFAULT_AUTOGEN_SETTINGS: AutoGenSettingsState = {
|
||||||
mode: "codex_creative",
|
mode: "codex_creative",
|
||||||
count: 24,
|
count: 24,
|
||||||
domain: "",
|
personalityId: "general",
|
||||||
|
personalityPrompts: buildDefaultPersonalityPrompts(),
|
||||||
persistToEvalCases: true,
|
persistToEvalCases: true,
|
||||||
generatedBy: "manual_reviewer"
|
generatedBy: "manual_reviewer"
|
||||||
};
|
};
|
||||||
|
|
@ -179,13 +245,13 @@ export function AutoRunsHistoryPanel({
|
||||||
showAssistantMode,
|
showAssistantMode,
|
||||||
showDecompositionMode,
|
showDecompositionMode,
|
||||||
showProgressMode,
|
showProgressMode,
|
||||||
|
showCommentsMode,
|
||||||
onLog
|
onLog
|
||||||
}: AutoRunsHistoryPanelProps) {
|
}: AutoRunsHistoryPanelProps) {
|
||||||
const [filters, setFilters] = useState<AutoRunsFilters>({
|
const [filters, setFilters] = useState<AutoRunsFilters>({
|
||||||
...DEFAULT_FILTERS,
|
...DEFAULT_FILTERS,
|
||||||
fromLocal: defaultFromDateValue()
|
fromLocal: defaultFromDateValue()
|
||||||
});
|
});
|
||||||
const [leftTab, setLeftTab] = useState<LeftTabMode>("settings");
|
|
||||||
const [history, setHistory] = useState<AutoRunHistoryResponse | null>(null);
|
const [history, setHistory] = useState<AutoRunHistoryResponse | null>(null);
|
||||||
const [runDetail, setRunDetail] = useState<AutoRunDetailResponse | null>(null);
|
const [runDetail, setRunDetail] = useState<AutoRunDetailResponse | null>(null);
|
||||||
const [dialog, setDialog] = useState<AutoRunDialogResponse | null>(null);
|
const [dialog, setDialog] = useState<AutoRunDialogResponse | null>(null);
|
||||||
|
|
@ -219,6 +285,10 @@ export function AutoRunsHistoryPanel({
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialLoadDoneRef = useRef(false);
|
const initialLoadDoneRef = useRef(false);
|
||||||
|
const selectedPersonality = useMemo(
|
||||||
|
() => AUTOGEN_PERSONALITIES.find((item) => item.id === autoGenSettings.personalityId) ?? AUTOGEN_PERSONALITIES[0],
|
||||||
|
[autoGenSettings.personalityId]
|
||||||
|
);
|
||||||
|
|
||||||
const activeRunSummary: AutoRunSummary | null =
|
const activeRunSummary: AutoRunSummary | null =
|
||||||
history?.items.find((item) => item.run_id === selectedRunId) ?? runDetail?.run ?? null;
|
history?.items.find((item) => item.run_id === selectedRunId) ?? runDetail?.run ?? null;
|
||||||
|
|
@ -308,6 +378,7 @@ export function AutoRunsHistoryPanel({
|
||||||
setAutoGenBusy(true);
|
setAutoGenBusy(true);
|
||||||
setErrorText("");
|
setErrorText("");
|
||||||
try {
|
try {
|
||||||
|
const activePersonalityPrompt = autoGenSettings.personalityPrompts[autoGenSettings.personalityId] ?? "";
|
||||||
const promptFingerprint = [
|
const promptFingerprint = [
|
||||||
prompts.systemPrompt,
|
prompts.systemPrompt,
|
||||||
prompts.developerPrompt,
|
prompts.developerPrompt,
|
||||||
|
|
@ -320,7 +391,7 @@ export function AutoRunsHistoryPanel({
|
||||||
const payload = await apiClient.generateAutoRunQuestions({
|
const payload = await apiClient.generateAutoRunQuestions({
|
||||||
mode: autoGenSettings.mode,
|
mode: autoGenSettings.mode,
|
||||||
count: autoGenSettings.count,
|
count: autoGenSettings.count,
|
||||||
domain: autoGenSettings.domain.trim() || undefined,
|
domain: selectedPersonality.domain || undefined,
|
||||||
persist_to_eval_cases: autoGenSettings.persistToEvalCases,
|
persist_to_eval_cases: autoGenSettings.persistToEvalCases,
|
||||||
generated_by: autoGenSettings.generatedBy.trim() || undefined,
|
generated_by: autoGenSettings.generatedBy.trim() || undefined,
|
||||||
context: {
|
context: {
|
||||||
|
|
@ -328,7 +399,9 @@ export function AutoRunsHistoryPanel({
|
||||||
model: connection.model,
|
model: connection.model,
|
||||||
assistant_prompt_version: assistantPromptVersion,
|
assistant_prompt_version: assistantPromptVersion,
|
||||||
decomposition_prompt_version: decompositionPromptVersion,
|
decomposition_prompt_version: decompositionPromptVersion,
|
||||||
prompt_fingerprint: promptFingerprint
|
prompt_fingerprint: promptFingerprint,
|
||||||
|
autogen_personality_id: selectedPersonality.id,
|
||||||
|
autogen_personality_prompt: activePersonalityPrompt.trim() || undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
log(
|
log(
|
||||||
|
|
@ -346,9 +419,10 @@ export function AutoRunsHistoryPanel({
|
||||||
}, [
|
}, [
|
||||||
assistantPromptVersion,
|
assistantPromptVersion,
|
||||||
autoGenSettings.count,
|
autoGenSettings.count,
|
||||||
autoGenSettings.domain,
|
|
||||||
autoGenSettings.generatedBy,
|
autoGenSettings.generatedBy,
|
||||||
autoGenSettings.mode,
|
autoGenSettings.mode,
|
||||||
|
autoGenSettings.personalityId,
|
||||||
|
autoGenSettings.personalityPrompts,
|
||||||
autoGenSettings.persistToEvalCases,
|
autoGenSettings.persistToEvalCases,
|
||||||
connection.llmProvider,
|
connection.llmProvider,
|
||||||
connection.model,
|
connection.model,
|
||||||
|
|
@ -359,7 +433,9 @@ export function AutoRunsHistoryPanel({
|
||||||
prompts.domainPrompt,
|
prompts.domainPrompt,
|
||||||
prompts.fewShotExamples,
|
prompts.fewShotExamples,
|
||||||
prompts.schemaNotes,
|
prompts.schemaNotes,
|
||||||
prompts.systemPrompt
|
prompts.systemPrompt,
|
||||||
|
selectedPersonality.domain,
|
||||||
|
selectedPersonality.id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const loadCaseDialog = useCallback(
|
const loadCaseDialog = useCallback(
|
||||||
|
|
@ -538,7 +614,6 @@ export function AutoRunsHistoryPanel({
|
||||||
const openAnnotationContext = useCallback(
|
const openAnnotationContext = useCallback(
|
||||||
async (annotation: AutoRunAnnotationRecord) => {
|
async (annotation: AutoRunAnnotationRecord) => {
|
||||||
setSelectedAnnotationId(annotation.annotation_id);
|
setSelectedAnnotationId(annotation.annotation_id);
|
||||||
setLeftTab("settings");
|
|
||||||
await loadRunDetail(annotation.run_id, annotation.case_id);
|
await loadRunDetail(annotation.run_id, annotation.case_id);
|
||||||
if (!history?.items.some((item) => item.run_id === annotation.run_id)) {
|
if (!history?.items.some((item) => item.run_id === annotation.run_id)) {
|
||||||
setErrorText("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.");
|
setErrorText("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.");
|
||||||
|
|
@ -560,6 +635,95 @@ export function AutoRunsHistoryPanel({
|
||||||
void loadAnnotations();
|
void loadAnnotations();
|
||||||
}, [annotationDecisionFilter, loadAnnotations]);
|
}, [annotationDecisionFilter, loadAnnotations]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const raw = localStorage.getItem(AUTORUNS_UI_CONFIG_KEY);
|
||||||
|
if (!raw) return;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw) as AutoRunsUiConfig;
|
||||||
|
if (parsed.filters) {
|
||||||
|
const savedFilters = parsed.filters;
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...savedFilters,
|
||||||
|
limit: typeof savedFilters.limit === "number" ? Math.max(1, Math.min(500, savedFilters.limit)) : prev.limit
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (parsed.autoGenSettings) {
|
||||||
|
setAutoGenSettings((prev) => {
|
||||||
|
const nextPrompts = {
|
||||||
|
...prev.personalityPrompts
|
||||||
|
};
|
||||||
|
for (const item of AUTOGEN_PERSONALITIES) {
|
||||||
|
const incoming = parsed.autoGenSettings?.personalityPrompts?.[item.id];
|
||||||
|
if (typeof incoming === "string") {
|
||||||
|
nextPrompts[item.id] = incoming;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const nextPersonalityId =
|
||||||
|
parsed.autoGenSettings?.personalityId && AUTOGEN_PERSONALITY_IDS.has(parsed.autoGenSettings.personalityId)
|
||||||
|
? parsed.autoGenSettings.personalityId
|
||||||
|
: prev.personalityId;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
mode:
|
||||||
|
parsed.autoGenSettings?.mode === "codex_creative" || parsed.autoGenSettings?.mode === "qwen_seed"
|
||||||
|
? parsed.autoGenSettings.mode
|
||||||
|
: prev.mode,
|
||||||
|
count:
|
||||||
|
typeof parsed.autoGenSettings?.count === "number"
|
||||||
|
? Math.max(1, Math.min(200, parsed.autoGenSettings.count))
|
||||||
|
: prev.count,
|
||||||
|
personalityId: nextPersonalityId,
|
||||||
|
personalityPrompts: nextPrompts,
|
||||||
|
persistToEvalCases:
|
||||||
|
typeof parsed.autoGenSettings?.persistToEvalCases === "boolean"
|
||||||
|
? parsed.autoGenSettings.persistToEvalCases
|
||||||
|
: prev.persistToEvalCases,
|
||||||
|
generatedBy:
|
||||||
|
typeof parsed.autoGenSettings?.generatedBy === "string"
|
||||||
|
? parsed.autoGenSettings.generatedBy
|
||||||
|
: prev.generatedBy
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
parsed.annotationDecisionFilter === "all" ||
|
||||||
|
(typeof parsed.annotationDecisionFilter === "string" && parsed.annotationDecisionFilter.length > 0)
|
||||||
|
) {
|
||||||
|
setAnnotationDecisionFilter(parsed.annotationDecisionFilter as ManualCaseDecision | "all");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore broken local cache
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const saveUiConfig = useCallback(() => {
|
||||||
|
const payload: AutoRunsUiConfig = {
|
||||||
|
filters,
|
||||||
|
autoGenSettings: {
|
||||||
|
mode: autoGenSettings.mode,
|
||||||
|
count: autoGenSettings.count,
|
||||||
|
personalityId: autoGenSettings.personalityId,
|
||||||
|
personalityPrompts: autoGenSettings.personalityPrompts,
|
||||||
|
persistToEvalCases: autoGenSettings.persistToEvalCases,
|
||||||
|
generatedBy: autoGenSettings.generatedBy
|
||||||
|
},
|
||||||
|
annotationDecisionFilter
|
||||||
|
};
|
||||||
|
localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload));
|
||||||
|
}, [annotationDecisionFilter, autoGenSettings, filters]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onSave = () => {
|
||||||
|
saveUiConfig();
|
||||||
|
log("Сохранены настройки панели автопрогонов.");
|
||||||
|
};
|
||||||
|
window.addEventListener(AUTORUNS_SAVE_EVENT, onSave as EventListener);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(AUTORUNS_SAVE_EVENT, onSave as EventListener);
|
||||||
|
};
|
||||||
|
}, [log, saveUiConfig]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelFrame
|
<PanelFrame
|
||||||
className="autoruns-frame"
|
className="autoruns-frame"
|
||||||
|
|
@ -569,19 +733,9 @@ export function AutoRunsHistoryPanel({
|
||||||
<div className="autoruns-columns">
|
<div className="autoruns-columns">
|
||||||
<section className="autoruns-col">
|
<section className="autoruns-col">
|
||||||
<div className="autoruns-col-header">
|
<div className="autoruns-col-header">
|
||||||
<h3>Левая панель</h3>
|
<h3>Настройки</h3>
|
||||||
<div className="tab-row">
|
|
||||||
<button type="button" className={leftTab === "settings" ? "tab active" : "tab"} onClick={() => setLeftTab("settings")}>
|
|
||||||
Настройки
|
|
||||||
</button>
|
|
||||||
<button type="button" className={leftTab === "comments" ? "tab active" : "tab"} onClick={() => setLeftTab("comments")}>
|
|
||||||
Комментарии
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{leftTab === "settings" ? (
|
|
||||||
<>
|
|
||||||
<h4>Настройки выборки</h4>
|
<h4>Настройки выборки</h4>
|
||||||
<div className="autoruns-form-grid">
|
<div className="autoruns-form-grid">
|
||||||
<label>
|
<label>
|
||||||
|
|
@ -726,12 +880,22 @@ export function AutoRunsHistoryPanel({
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Домен (опц.)
|
Личность автогенерации
|
||||||
<input
|
<select
|
||||||
value={autoGenSettings.domain}
|
value={autoGenSettings.personalityId}
|
||||||
onChange={(event) => setAutoGenSettings((prev) => ({ ...prev, domain: event.target.value }))}
|
onChange={(event) =>
|
||||||
placeholder="vat / settlements / counterparties"
|
setAutoGenSettings((prev) => ({
|
||||||
/>
|
...prev,
|
||||||
|
personalityId: event.target.value as AutoGenPersonalityId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{AUTOGEN_PERSONALITIES.map((item) => (
|
||||||
|
<option key={item.id} value={item.id}>
|
||||||
|
{item.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Кто генерирует
|
Кто генерирует
|
||||||
|
|
@ -741,6 +905,22 @@ export function AutoRunsHistoryPanel({
|
||||||
placeholder="manual_reviewer"
|
placeholder="manual_reviewer"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<label className="full-width">
|
||||||
|
Промпт личности
|
||||||
|
<textarea
|
||||||
|
value={autoGenSettings.personalityPrompts[autoGenSettings.personalityId] ?? ""}
|
||||||
|
onChange={(event) =>
|
||||||
|
setAutoGenSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
personalityPrompts: {
|
||||||
|
...prev.personalityPrompts,
|
||||||
|
[prev.personalityId]: event.target.value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
placeholder="Текст промпта для выбранной личности автогенерации"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<label className="checkbox-row">
|
<label className="checkbox-row">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -805,133 +985,6 @@ export function AutoRunsHistoryPanel({
|
||||||
<textarea readOnly value={prompts.fewShotExamples} />
|
<textarea readOnly value={prompts.fewShotExamples} />
|
||||||
</label>
|
</label>
|
||||||
</details>
|
</details>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<h4>Размеченные ответы</h4>
|
|
||||||
<div className="autoruns-form-grid">
|
|
||||||
<label>
|
|
||||||
Фильтр решений
|
|
||||||
<select
|
|
||||||
value={annotationDecisionFilter}
|
|
||||||
onChange={(event) => setAnnotationDecisionFilter(event.target.value as ManualCaseDecision | "all")}
|
|
||||||
>
|
|
||||||
<option value="all">все</option>
|
|
||||||
{(availableManualDecisions.length > 0
|
|
||||||
? availableManualDecisions
|
|
||||||
: ((manualDecisionSchema?.enum as ManualCaseDecision[] | undefined) ?? [])
|
|
||||||
).map((decision) => (
|
|
||||||
<option key={decision} value={decision}>
|
|
||||||
{String(((manualDecisionSchema?.labels as Record<string, unknown> | undefined)?.[decision] ?? decision))}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="autoruns-stats-grid">
|
|
||||||
<div>
|
|
||||||
<span>Комментариев</span>
|
|
||||||
<strong>{annotations.length}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>Средний рейтинг</span>
|
|
||||||
<strong>{annotationsAverageRating === null ? "нет данных" : `${annotationsAverageRating.toFixed(2)} / 5`}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>Последний</span>
|
|
||||||
<strong>{annotations.length > 0 ? formatDateTime(annotations[0].updated_at) : "нет данных"}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>Статус</span>
|
|
||||||
<strong>{annotationsBusy ? "обновляю" : "готово"}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="button-row">
|
|
||||||
<button type="button" disabled={annotationsBusy} onClick={() => void loadAnnotations()}>
|
|
||||||
{annotationsBusy ? "Обновляю..." : "Обновить список"}
|
|
||||||
</button>
|
|
||||||
<button type="button" className="tab" disabled={postAnalysisBusy} onClick={() => void loadPostAnalysis()}>
|
|
||||||
{postAnalysisBusy ? "Идет пост-анализ..." : "Обновить пост-анализ"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="autoruns-comments-list">
|
|
||||||
{annotationsBusy ? <p className="muted">Загружаю комментарии...</p> : null}
|
|
||||||
{!annotationsBusy && annotations.length === 0 ? <p className="muted">Пока нет откомментированных ответов.</p> : null}
|
|
||||||
{annotations.map((item) => (
|
|
||||||
<button
|
|
||||||
key={item.annotation_id}
|
|
||||||
type="button"
|
|
||||||
className={selectedAnnotationId === item.annotation_id ? "autoruns-comment-item selected" : "autoruns-comment-item"}
|
|
||||||
onClick={() => void openAnnotationContext(item)}
|
|
||||||
>
|
|
||||||
<div className="autoruns-comment-head">
|
|
||||||
<strong>{renderRatingDots(item.rating)}</strong>
|
|
||||||
<span>{formatDateTime(item.updated_at)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="autoruns-run-meta">{item.run_id}</div>
|
|
||||||
<div className="autoruns-run-meta">
|
|
||||||
case={item.case_id} | msg={item.message_index}
|
|
||||||
</div>
|
|
||||||
<div className="autoruns-run-meta">
|
|
||||||
decision={item.manual_case_decision}
|
|
||||||
{item.annotation_author ? ` | author=${item.annotation_author}` : ""}
|
|
||||||
</div>
|
|
||||||
<p>{item.comment}</p>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedAnnotation ? (
|
|
||||||
<>
|
|
||||||
<h4>Тех-контекст брака</h4>
|
|
||||||
<div className="autoruns-meta-list">
|
|
||||||
<div>
|
|
||||||
<span>trace:</span>
|
|
||||||
<strong>{selectedAnnotation.technical_context.trace_id ?? "нет данных"}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>reply_type:</span>
|
|
||||||
<strong>{selectedAnnotation.technical_context.reply_type ?? "нет данных"}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>domain:</span>
|
|
||||||
<strong>{selectedAnnotation.technical_context.domain ?? "нет данных"}</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>query_class:</span>
|
|
||||||
<strong>{selectedAnnotation.technical_context.query_class ?? "нет данных"}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h4>JSON разбор</h4>
|
|
||||||
<JsonView
|
|
||||||
value={{
|
|
||||||
annotation_id: selectedAnnotation.annotation_id,
|
|
||||||
run_id: selectedAnnotation.run_id,
|
|
||||||
case_id: selectedAnnotation.case_id,
|
|
||||||
message_index: selectedAnnotation.message_index,
|
|
||||||
rating: selectedAnnotation.rating,
|
|
||||||
comment: selectedAnnotation.comment,
|
|
||||||
manual_case_decision: selectedAnnotation.manual_case_decision,
|
|
||||||
annotation_author: selectedAnnotation.annotation_author,
|
|
||||||
context: selectedAnnotation.context,
|
|
||||||
technical_context: selectedAnnotation.technical_context,
|
|
||||||
case_summary: selectedAnnotation.case_summary
|
|
||||||
? {
|
|
||||||
case_id: selectedAnnotation.case_summary.case_id,
|
|
||||||
domain: selectedAnnotation.case_summary.domain,
|
|
||||||
query_class: selectedAnnotation.case_summary.query_class,
|
|
||||||
checks: selectedAnnotation.case_summary.checks,
|
|
||||||
metric_subscores: selectedAnnotation.case_summary.metric_subscores
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{errorText ? <p className="error-text">{errorText}</p> : null}
|
{errorText ? <p className="error-text">{errorText}</p> : null}
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -1235,6 +1288,137 @@ export function AutoRunsHistoryPanel({
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{showCommentsMode ? (
|
||||||
|
<section className="autoruns-col">
|
||||||
|
<div className="autoruns-col-header">
|
||||||
|
<h3>Комментарии</h3>
|
||||||
|
</div>
|
||||||
|
<h4>Размеченные ответы</h4>
|
||||||
|
<div className="autoruns-form-grid">
|
||||||
|
<label>
|
||||||
|
Фильтр решений
|
||||||
|
<select
|
||||||
|
value={annotationDecisionFilter}
|
||||||
|
onChange={(event) => setAnnotationDecisionFilter(event.target.value as ManualCaseDecision | "all")}
|
||||||
|
>
|
||||||
|
<option value="all">все</option>
|
||||||
|
{(availableManualDecisions.length > 0
|
||||||
|
? availableManualDecisions
|
||||||
|
: ((manualDecisionSchema?.enum as ManualCaseDecision[] | undefined) ?? [])
|
||||||
|
).map((decision) => (
|
||||||
|
<option key={decision} value={decision}>
|
||||||
|
{String(((manualDecisionSchema?.labels as Record<string, unknown> | undefined)?.[decision] ?? decision))}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="autoruns-stats-grid">
|
||||||
|
<div>
|
||||||
|
<span>Комментариев</span>
|
||||||
|
<strong>{annotations.length}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Средний рейтинг</span>
|
||||||
|
<strong>{annotationsAverageRating === null ? "нет данных" : `${annotationsAverageRating.toFixed(2)} / 5`}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Последний</span>
|
||||||
|
<strong>{annotations.length > 0 ? formatDateTime(annotations[0].updated_at) : "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Статус</span>
|
||||||
|
<strong>{annotationsBusy ? "обновляю" : "готово"}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="button-row">
|
||||||
|
<button type="button" disabled={annotationsBusy} onClick={() => void loadAnnotations()}>
|
||||||
|
{annotationsBusy ? "Обновляю..." : "Обновить список"}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="tab" disabled={postAnalysisBusy} onClick={() => void loadPostAnalysis()}>
|
||||||
|
{postAnalysisBusy ? "Идет пост-анализ..." : "Обновить пост-анализ"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="autoruns-comments-list">
|
||||||
|
{annotationsBusy ? <p className="muted">Загружаю комментарии...</p> : null}
|
||||||
|
{!annotationsBusy && annotations.length === 0 ? <p className="muted">Пока нет откомментированных ответов.</p> : null}
|
||||||
|
{annotations.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.annotation_id}
|
||||||
|
type="button"
|
||||||
|
className={selectedAnnotationId === item.annotation_id ? "autoruns-comment-item selected" : "autoruns-comment-item"}
|
||||||
|
onClick={() => void openAnnotationContext(item)}
|
||||||
|
>
|
||||||
|
<div className="autoruns-comment-head">
|
||||||
|
<strong>{renderRatingDots(item.rating)}</strong>
|
||||||
|
<span>{formatDateTime(item.updated_at)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="autoruns-run-meta">{item.run_id}</div>
|
||||||
|
<div className="autoruns-run-meta">
|
||||||
|
case={item.case_id} | msg={item.message_index}
|
||||||
|
</div>
|
||||||
|
<div className="autoruns-run-meta">
|
||||||
|
decision={item.manual_case_decision}
|
||||||
|
{item.annotation_author ? ` | author=${item.annotation_author}` : ""}
|
||||||
|
</div>
|
||||||
|
<p>{item.comment}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedAnnotation ? (
|
||||||
|
<>
|
||||||
|
<h4>Тех-контекст брака</h4>
|
||||||
|
<div className="autoruns-meta-list">
|
||||||
|
<div>
|
||||||
|
<span>trace:</span>
|
||||||
|
<strong>{selectedAnnotation.technical_context.trace_id ?? "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>reply_type:</span>
|
||||||
|
<strong>{selectedAnnotation.technical_context.reply_type ?? "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>domain:</span>
|
||||||
|
<strong>{selectedAnnotation.technical_context.domain ?? "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>query_class:</span>
|
||||||
|
<strong>{selectedAnnotation.technical_context.query_class ?? "нет данных"}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4>JSON разбор</h4>
|
||||||
|
<JsonView
|
||||||
|
value={{
|
||||||
|
annotation_id: selectedAnnotation.annotation_id,
|
||||||
|
run_id: selectedAnnotation.run_id,
|
||||||
|
case_id: selectedAnnotation.case_id,
|
||||||
|
message_index: selectedAnnotation.message_index,
|
||||||
|
rating: selectedAnnotation.rating,
|
||||||
|
comment: selectedAnnotation.comment,
|
||||||
|
manual_case_decision: selectedAnnotation.manual_case_decision,
|
||||||
|
annotation_author: selectedAnnotation.annotation_author,
|
||||||
|
context: selectedAnnotation.context,
|
||||||
|
technical_context: selectedAnnotation.technical_context,
|
||||||
|
case_summary: selectedAnnotation.case_summary
|
||||||
|
? {
|
||||||
|
case_id: selectedAnnotation.case_summary.case_id,
|
||||||
|
domain: selectedAnnotation.case_summary.domain,
|
||||||
|
query_class: selectedAnnotation.case_summary.query_class,
|
||||||
|
checks: selectedAnnotation.case_summary.checks,
|
||||||
|
metric_subscores: selectedAnnotation.case_summary.metric_subscores
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{commentModal.open ? (
|
{commentModal.open ? (
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export function PromptPanel({
|
||||||
}: PromptPanelProps) {
|
}: PromptPanelProps) {
|
||||||
return (
|
return (
|
||||||
<PanelFrame title="Prompt Manager" subtitle="Системный, developer и domain уровни управляются отдельно.">
|
<PanelFrame title="Prompt Manager" subtitle="Системный, developer и domain уровни управляются отдельно.">
|
||||||
<div className="grid-two">
|
<div className="prompt-manager-grid">
|
||||||
<label>
|
<label>
|
||||||
Системный prompt
|
Системный prompt
|
||||||
<textarea
|
<textarea
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export function RuntimePanel({
|
||||||
{evalBusy ? "Идет eval v2.0.2..." : "Запустить eval v2.0.2"}
|
{evalBusy ? "Идет eval v2.0.2..." : "Запустить eval v2.0.2"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="runtime-grid">
|
<div className="runtime-stack">
|
||||||
<div className="runtime-runs">
|
<div className="runtime-runs">
|
||||||
{runs.map((run) => (
|
{runs.map((run) => (
|
||||||
<button
|
<button
|
||||||
|
|
@ -69,7 +69,7 @@ export function RuntimePanel({
|
||||||
))}
|
))}
|
||||||
{runs.length === 0 ? <p className="muted">Нет активных запусков.</p> : null}
|
{runs.length === 0 ? <p className="muted">Нет активных запусков.</p> : null}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="runtime-details">
|
||||||
<h3>Trace выбранного run</h3>
|
<h3>Trace выбранного run</h3>
|
||||||
<JsonView value={traceItems} />
|
<JsonView value={traceItems} />
|
||||||
<div className="eval-report-wrap">
|
<div className="eval-report-wrap">
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
--rgb-scrollbar-track: 31, 31, 36;
|
--rgb-scrollbar-track: 31, 31, 36;
|
||||||
--rgb-scrollbar-thumb: 74, 74, 82;
|
--rgb-scrollbar-thumb: 74, 74, 82;
|
||||||
--rgb-scrollbar-thumb-hover: 90, 90, 100;
|
--rgb-scrollbar-thumb-hover: 90, 90, 100;
|
||||||
|
--mode-column-width: 440px;
|
||||||
|
--mode-toggle-width: 188px;
|
||||||
--bg-main: rgb(var(--rgb-background));
|
--bg-main: rgb(var(--rgb-background));
|
||||||
--bg-soft: rgb(var(--rgb-surface-main));
|
--bg-soft: rgb(var(--rgb-surface-main));
|
||||||
--bg-panel: rgb(var(--rgb-surface-main));
|
--bg-panel: rgb(var(--rgb-surface-main));
|
||||||
|
|
@ -29,7 +31,7 @@
|
||||||
--radius-lg: 20px;
|
--radius-lg: 20px;
|
||||||
--radius-md: 14px;
|
--radius-md: 14px;
|
||||||
--shadow: none;
|
--shadow: none;
|
||||||
--autoruns-col-width: 360px;
|
--autoruns-col-width: var(--mode-column-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
@ -105,6 +107,12 @@ body,
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-grid.layout-grid-mode-columns {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.mode-switch-row {
|
.mode-switch-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
@ -115,6 +123,67 @@ body,
|
||||||
.mode-switch-row.mode-switch-row-right {
|
.mode-switch-row.mode-switch-row-right {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
max-width: 72%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-switch-row .tab {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-switch-row.mode-switch-row-right .tab {
|
||||||
|
width: var(--mode-toggle-width);
|
||||||
|
min-width: var(--mode-toggle-width);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-columns {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-col {
|
||||||
|
flex: 0 0 var(--mode-column-width);
|
||||||
|
width: var(--mode-column-width);
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-col.mode-col-wide {
|
||||||
|
flex-basis: var(--mode-column-width);
|
||||||
|
width: var(--mode-column-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-col.mode-col-xwide {
|
||||||
|
flex-basis: var(--mode-column-width);
|
||||||
|
width: var(--mode-column-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-col .panel-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-col .panel-body {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-columns-empty {
|
||||||
|
min-width: 360px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgb(var(--rgb-surface-main));
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-frame {
|
.panel-frame {
|
||||||
|
|
@ -303,6 +372,12 @@ button:disabled {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prompt-manager-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.full-width {
|
.full-width {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
@ -478,6 +553,17 @@ button:disabled {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.runtime-stack {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtime-details {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.runtime-runs {
|
.runtime-runs {
|
||||||
max-height: 360px;
|
max-height: 360px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
@ -532,8 +618,8 @@ button:disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoruns-col {
|
.autoruns-col {
|
||||||
flex: 0 0 var(--autoruns-col-width);
|
flex: 0 0 var(--mode-column-width);
|
||||||
width: var(--autoruns-col-width);
|
width: var(--mode-column-width);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
@ -544,11 +630,6 @@ button:disabled {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoruns-col:nth-child(3) {
|
|
||||||
flex-basis: 440px;
|
|
||||||
width: 440px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autoruns-col h3 {
|
.autoruns-col h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
|
|
@ -990,7 +1071,7 @@ button:disabled {
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
:root {
|
:root {
|
||||||
--autoruns-col-width: 340px;
|
--mode-column-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metrics-grid {
|
.metrics-grid {
|
||||||
|
|
@ -1000,11 +1081,12 @@ button:disabled {
|
||||||
|
|
||||||
@media (max-width: 920px) {
|
@media (max-width: 920px) {
|
||||||
:root {
|
:root {
|
||||||
--autoruns-col-width: 320px;
|
--mode-column-width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-two,
|
.grid-two,
|
||||||
.runtime-grid {
|
.runtime-grid,
|
||||||
|
.runtime-stack {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1021,7 +1103,7 @@ button:disabled {
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
:root {
|
:root {
|
||||||
--autoruns-col-width: 300px;
|
--mode-column-width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-root {
|
.app-root {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/components/assistantpanel.tsx","./src/components/autorunshistorypanel.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"}
|
{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/components/assistantpanel.tsx","./src/components/assistantsampanel.tsx","./src/components/autorunshistorypanel.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"}
|
||||||
Loading…
Reference in New Issue