АДРЕСНЫЙ РЕЖИМ - авторан история - база + ДИЗАЙН
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user