АДРЕСНЫЙ РЕЖИМ - авторан история - база + ДИЗАЙН

This commit is contained in:
2026-04-09 17:33:32 +03:00
parent 60a4bef88c
commit cf5ba1afc2
15 changed files with 904 additions and 305 deletions
@@ -9,10 +9,6 @@ interface AssistantPanelProps {
conversation: AssistantConversationItem[];
inputValue: string;
onInputChange: (value: string) => void;
periodHint: string;
onPeriodHintChange: (value: string) => void;
businessContext: string;
onBusinessContextChange: (value: string) => void;
useMock: boolean;
onUseMockChange: (value: boolean) => void;
onSend: () => Promise<void> | void;
@@ -70,10 +66,6 @@ export function AssistantPanel({
conversation,
inputValue,
onInputChange,
periodHint,
onPeriodHintChange,
businessContext,
onBusinessContextChange,
useMock,
onUseMockChange,
onSend,
@@ -175,16 +167,6 @@ export function AssistantPanel({
</div>
<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">
Сообщение
<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;
showDecompositionMode: boolean;
showProgressMode: boolean;
showCommentsMode: boolean;
onLog?: (message: string) => void;
}
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 {
fromLocal: string;
@@ -58,7 +66,8 @@ interface CommentModalState {
interface AutoGenSettingsState {
mode: AutoGenMode;
count: number;
domain: string;
personalityId: AutoGenPersonalityId;
personalityPrompts: Record<AutoGenPersonalityId, string>;
persistToEvalCases: boolean;
generatedBy: string;
}
@@ -75,10 +84,67 @@ const DEFAULT_FILTERS: AutoRunsFilters = {
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 = {
mode: "codex_creative",
count: 24,
domain: "",
personalityId: "general",
personalityPrompts: buildDefaultPersonalityPrompts(),
persistToEvalCases: true,
generatedBy: "manual_reviewer"
};
@@ -179,13 +245,13 @@ export function AutoRunsHistoryPanel({
showAssistantMode,
showDecompositionMode,
showProgressMode,
showCommentsMode,
onLog
}: AutoRunsHistoryPanelProps) {
const [filters, setFilters] = useState<AutoRunsFilters>({
...DEFAULT_FILTERS,
fromLocal: defaultFromDateValue()
});
const [leftTab, setLeftTab] = useState<LeftTabMode>("settings");
const [history, setHistory] = useState<AutoRunHistoryResponse | null>(null);
const [runDetail, setRunDetail] = useState<AutoRunDetailResponse | null>(null);
const [dialog, setDialog] = useState<AutoRunDialogResponse | null>(null);
@@ -219,6 +285,10 @@ export function AutoRunsHistoryPanel({
});
const initialLoadDoneRef = useRef(false);
const selectedPersonality = useMemo(
() => AUTOGEN_PERSONALITIES.find((item) => item.id === autoGenSettings.personalityId) ?? AUTOGEN_PERSONALITIES[0],
[autoGenSettings.personalityId]
);
const activeRunSummary: AutoRunSummary | null =
history?.items.find((item) => item.run_id === selectedRunId) ?? runDetail?.run ?? null;
@@ -308,6 +378,7 @@ export function AutoRunsHistoryPanel({
setAutoGenBusy(true);
setErrorText("");
try {
const activePersonalityPrompt = autoGenSettings.personalityPrompts[autoGenSettings.personalityId] ?? "";
const promptFingerprint = [
prompts.systemPrompt,
prompts.developerPrompt,
@@ -320,7 +391,7 @@ export function AutoRunsHistoryPanel({
const payload = await apiClient.generateAutoRunQuestions({
mode: autoGenSettings.mode,
count: autoGenSettings.count,
domain: autoGenSettings.domain.trim() || undefined,
domain: selectedPersonality.domain || undefined,
persist_to_eval_cases: autoGenSettings.persistToEvalCases,
generated_by: autoGenSettings.generatedBy.trim() || undefined,
context: {
@@ -328,7 +399,9 @@ export function AutoRunsHistoryPanel({
model: connection.model,
assistant_prompt_version: assistantPromptVersion,
decomposition_prompt_version: decompositionPromptVersion,
prompt_fingerprint: promptFingerprint
prompt_fingerprint: promptFingerprint,
autogen_personality_id: selectedPersonality.id,
autogen_personality_prompt: activePersonalityPrompt.trim() || undefined
}
});
log(
@@ -346,9 +419,10 @@ export function AutoRunsHistoryPanel({
}, [
assistantPromptVersion,
autoGenSettings.count,
autoGenSettings.domain,
autoGenSettings.generatedBy,
autoGenSettings.mode,
autoGenSettings.personalityId,
autoGenSettings.personalityPrompts,
autoGenSettings.persistToEvalCases,
connection.llmProvider,
connection.model,
@@ -359,7 +433,9 @@ export function AutoRunsHistoryPanel({
prompts.domainPrompt,
prompts.fewShotExamples,
prompts.schemaNotes,
prompts.systemPrompt
prompts.systemPrompt,
selectedPersonality.domain,
selectedPersonality.id
]);
const loadCaseDialog = useCallback(
@@ -538,7 +614,6 @@ export function AutoRunsHistoryPanel({
const openAnnotationContext = useCallback(
async (annotation: AutoRunAnnotationRecord) => {
setSelectedAnnotationId(annotation.annotation_id);
setLeftTab("settings");
await loadRunDetail(annotation.run_id, annotation.case_id);
if (!history?.items.some((item) => item.run_id === annotation.run_id)) {
setErrorText("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.");
@@ -560,6 +635,95 @@ export function AutoRunsHistoryPanel({
void 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 (
<PanelFrame
className="autoruns-frame"
@@ -569,19 +733,9 @@ export function AutoRunsHistoryPanel({
<div className="autoruns-columns">
<section className="autoruns-col">
<div className="autoruns-col-header">
<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>
<h3>Настройки</h3>
</div>
{leftTab === "settings" ? (
<>
<h4>Настройки выборки</h4>
<div className="autoruns-form-grid">
<label>
@@ -726,12 +880,22 @@ export function AutoRunsHistoryPanel({
/>
</label>
<label>
Домен (опц.)
<input
value={autoGenSettings.domain}
onChange={(event) => setAutoGenSettings((prev) => ({ ...prev, domain: event.target.value }))}
placeholder="vat / settlements / counterparties"
/>
Личность автогенерации
<select
value={autoGenSettings.personalityId}
onChange={(event) =>
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>
Кто генерирует
@@ -741,6 +905,22 @@ export function AutoRunsHistoryPanel({
placeholder="manual_reviewer"
/>
</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">
<input
type="checkbox"
@@ -805,133 +985,6 @@ export function AutoRunsHistoryPanel({
<textarea readOnly value={prompts.fewShotExamples} />
</label>
</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}
</section>
@@ -1235,6 +1288,137 @@ export function AutoRunsHistoryPanel({
</div>
</section>
) : 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>
{commentModal.open ? (
@@ -43,7 +43,7 @@ export function PromptPanel({
}: PromptPanelProps) {
return (
<PanelFrame title="Prompt Manager" subtitle="Системный, developer и domain уровни управляются отдельно.">
<div className="grid-two">
<div className="prompt-manager-grid">
<label>
Системный prompt
<textarea
@@ -48,7 +48,7 @@ export function RuntimePanel({
{evalBusy ? "Идет eval v2.0.2..." : "Запустить eval v2.0.2"}
</button>
</div>
<div className="runtime-grid">
<div className="runtime-stack">
<div className="runtime-runs">
{runs.map((run) => (
<button
@@ -69,7 +69,7 @@ export function RuntimePanel({
))}
{runs.length === 0 ? <p className="muted">Нет активных запусков.</p> : null}
</div>
<div>
<div className="runtime-details">
<h3>Trace выбранного run</h3>
<JsonView value={traceItems} />
<div className="eval-report-wrap">