ЮИ - Автопрогоны: доработать иконки и hover-состояния saved sessions

This commit is contained in:
2026-04-18 14:12:11 +03:00
parent 31cb4ccbbb
commit f58ef9ad55
32 changed files with 2214 additions and 132 deletions
@@ -204,6 +204,12 @@ interface AutoRunsUiConfig {
filters?: Partial<AutoRunsFilters>;
analysisDate?: string;
autogenPersonalityPromptHeight?: number;
groupsExpanded?: {
filters?: boolean;
generationContext?: boolean;
autogen?: boolean;
savedSessions?: boolean;
};
autoGenSettings?: {
mode?: AutoGenMode;
count?: number;
@@ -282,6 +288,22 @@ function formatDateTime(iso: string | null): string {
return new Date(parsed).toLocaleString("ru-RU");
}
function formatDialogStepTag(message: AutoRunDialogMessage): string | null {
const localIndex =
typeof message.case_message_index === "number"
? message.case_message_index
: typeof message.message_index === "number"
? message.message_index
: null;
if (localIndex === null || localIndex < 0) {
return null;
}
const turnNumber = Math.floor(localIndex / 2) + 1;
const turnLabel = String(turnNumber).padStart(3, "0");
const roleLabel = message.role === "assistant" ? "ответ" : "вопрос";
return `${turnLabel} ${roleLabel}`;
}
function formatAutoGenModeLabel(mode: AutoGenMode): string {
if (mode === "saved_user_sessions") {
return "Пользовательские сессии";
@@ -569,6 +591,35 @@ function QuestionGripIcon() {
);
}
function CardChevronIcon({ expanded }: { expanded: boolean }) {
return (
<svg
className={expanded ? "autoruns-card-chevron-svg expanded" : "autoruns-card-chevron-svg"}
viewBox="0 0 16 16"
aria-hidden="true"
focusable="false"
>
<path d="M3.5 6.2 8 10.4l4.5-4.2" />
</svg>
);
}
function CardLaunchIcon() {
return (
<svg className="autoruns-card-launch-svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<path d="M5 3.8 12 8l-7 4.2Z" />
</svg>
);
}
function GroupChevronIcon({ expanded }: { expanded: boolean }) {
return (
<svg className={expanded ? "autoruns-group-chevron-svg expanded" : "autoruns-group-chevron-svg"} viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<path d="M3.5 6.2 8 10.4l4.5-4.2" />
</svg>
);
}
export function AutoRunsHistoryPanel({
connection,
modelOptions,
@@ -620,6 +671,7 @@ export function AutoRunsHistoryPanel({
const [autoGenSettings, setAutoGenSettings] = useState<AutoGenSettingsState>(DEFAULT_AUTOGEN_SETTINGS);
const [autoGenHistory, setAutoGenHistory] = useState<AutoGenHistoryRecord[]>([]);
const [selectedAutogenGenerationId, setSelectedAutogenGenerationId] = useState("");
const [expandedSavedSessionGenerationId, setExpandedSavedSessionGenerationId] = useState("");
const [editableGeneratedQuestions, setEditableGeneratedQuestions] = useState<string[]>([]);
const [generatedQuestionsBusy, setGeneratedQuestionsBusy] = useState(false);
const [editingQuestionIndex, setEditingQuestionIndex] = useState<number | null>(null);
@@ -650,6 +702,10 @@ export function AutoRunsHistoryPanel({
const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit));
const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count));
const [autogenPersonalityPromptHeight, setAutogenPersonalityPromptHeight] = useState(160);
const [filtersGroupExpanded, setFiltersGroupExpanded] = useState(true);
const [generationContextGroupExpanded, setGenerationContextGroupExpanded] = useState(true);
const [autogenGroupExpanded, setAutogenGroupExpanded] = useState(true);
const [savedSessionsGroupExpanded, setSavedSessionsGroupExpanded] = useState(true);
const [commentModal, setCommentModal] = useState<CommentModalState>({
open: false,
caseId: "",
@@ -1485,17 +1541,20 @@ export function AutoRunsHistoryPanel({
[loadAutoGenHistory, loadHistory, log, selectedCaseId, selectedRunId, stopAsyncJobPolling]
);
const runAutogenCampaign = useCallback(async () => {
const runAutogenCampaign = useCallback(async (generationOverride?: AutoGenHistoryRecord, questionsOverride?: string[]) => {
stopAsyncJobPolling();
setAutogenRunBusy(true);
setErrorText("");
try {
const generation = selectedAutogenGeneration;
const generation = generationOverride ?? selectedAutogenGeneration;
if (!generation) {
throw new Error("История автогенерации пуста. Сначала сгенерируйте пачку вопросов.");
}
const questionsForRun = editableGeneratedQuestions
const sourceQuestions =
questionsOverride ??
(selectedAutogenGeneration?.generation_id === generation.generation_id ? editableGeneratedQuestions : generation.questions);
const questionsForRun = sourceQuestions
.map((item) => item.trim())
.filter((item) => item.length > 0);
if (questionsForRun.length === 0) {
@@ -1980,6 +2039,11 @@ export function AutoRunsHistoryPanel({
setDragOverQuestionIndex(null);
}, []);
const toggleSavedSessionQuestions = useCallback((generationId: string) => {
setSelectedAutogenGenerationId(generationId);
setExpandedSavedSessionGenerationId((prev) => (prev === generationId ? "" : generationId));
}, []);
const openAutoGenDeleteModal = useCallback((item: AutoGenHistoryRecord) => {
setAutoGenDeleteModal({
open: true,
@@ -2135,6 +2199,19 @@ export function AutoRunsHistoryPanel({
return () => window.clearTimeout(timer);
}, [editingQuestionIndex]);
useEffect(() => {
if (!isSavedUserSessionsMode) {
setExpandedSavedSessionGenerationId("");
return;
}
if (
expandedSavedSessionGenerationId &&
!visibleAutoGenHistory.some((item) => item.generation_id === expandedSavedSessionGenerationId)
) {
setExpandedSavedSessionGenerationId("");
}
}, [expandedSavedSessionGenerationId, isSavedUserSessionsMode, visibleAutoGenHistory]);
useEffect(() => {
setLimitInput(String(filters.limit));
}, [filters.limit]);
@@ -2213,6 +2290,20 @@ export function AutoRunsHistoryPanel({
if (typeof parsed.autogenPersonalityPromptHeight === "number") {
setAutogenPersonalityPromptHeight(clampAutogenPromptHeight(parsed.autogenPersonalityPromptHeight));
}
if (parsed.groupsExpanded) {
if (typeof parsed.groupsExpanded.filters === "boolean") {
setFiltersGroupExpanded(parsed.groupsExpanded.filters);
}
if (typeof parsed.groupsExpanded.generationContext === "boolean") {
setGenerationContextGroupExpanded(parsed.groupsExpanded.generationContext);
}
if (typeof parsed.groupsExpanded.autogen === "boolean") {
setAutogenGroupExpanded(parsed.groupsExpanded.autogen);
}
if (typeof parsed.groupsExpanded.savedSessions === "boolean") {
setSavedSessionsGroupExpanded(parsed.groupsExpanded.savedSessions);
}
}
if (parsed.autoGenSettings) {
setAutoGenSettings((prev) => {
const nextPrompts: Record<string, string> = {
@@ -2272,6 +2363,12 @@ export function AutoRunsHistoryPanel({
filters,
analysisDate,
autogenPersonalityPromptHeight,
groupsExpanded: {
filters: filtersGroupExpanded,
generationContext: generationContextGroupExpanded,
autogen: autogenGroupExpanded,
savedSessions: savedSessionsGroupExpanded
},
autoGenSettings: {
mode: autoGenSettings.mode,
count: autoGenSettings.count,
@@ -2284,7 +2381,18 @@ export function AutoRunsHistoryPanel({
hideResolvedAnnotations
};
localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload));
}, [analysisDate, annotationDecisionFilter, autoGenSettings, autogenPersonalityPromptHeight, filters, hideResolvedAnnotations]);
}, [
analysisDate,
annotationDecisionFilter,
autoGenSettings,
autogenGroupExpanded,
autogenPersonalityPromptHeight,
filters,
filtersGroupExpanded,
generationContextGroupExpanded,
hideResolvedAnnotations,
savedSessionsGroupExpanded
]);
useEffect(() => {
const onSave = () => {
@@ -2347,7 +2455,20 @@ export function AutoRunsHistoryPanel({
<h3>Автопрогоны</h3>
</div>
<h4>Настройки выборки</h4>
<div className="autoruns-group-heading">
<h4>Настройки выборки</h4>
<button
type="button"
className="autoruns-group-toggle"
onClick={() => setFiltersGroupExpanded((prev) => !prev)}
aria-label={filtersGroupExpanded ? "Скрыть группу настройки выборки" : "Показать группу настройки выборки"}
title={filtersGroupExpanded ? "Скрыть группу" : "Показать группу"}
>
<GroupChevronIcon expanded={filtersGroupExpanded} />
</button>
</div>
{filtersGroupExpanded ? (
<>
<div className="autoruns-form-grid">
<label>
Дата с
@@ -2453,8 +2574,22 @@ export function AutoRunsHistoryPanel({
Сбросить фильтры
</button>
</div>
</>
) : null}
<h4>Контур генерации</h4>
<div className="autoruns-group-heading">
<h4>Контур генерации</h4>
<button
type="button"
className="autoruns-group-toggle"
onClick={() => setGenerationContextGroupExpanded((prev) => !prev)}
aria-label={generationContextGroupExpanded ? "Скрыть группу контура генерации" : "Показать группу контура генерации"}
title={generationContextGroupExpanded ? "Скрыть группу" : "Показать группу"}
>
<GroupChevronIcon expanded={generationContextGroupExpanded} />
</button>
</div>
{generationContextGroupExpanded ? (
<div className="autoruns-meta-list">
<div>
<span>Провайдер:</span>
@@ -2473,8 +2608,22 @@ export function AutoRunsHistoryPanel({
<strong>{decompositionPromptVersion}</strong>
</div>
</div>
) : null}
<h4>Автопрогоны</h4>
<div className="autoruns-group-heading">
<h4>Автопрогоны</h4>
<button
type="button"
className="autoruns-group-toggle"
onClick={() => setAutogenGroupExpanded((prev) => !prev)}
aria-label={autogenGroupExpanded ? "Скрыть группу автопрогонов" : "Показать группу автопрогонов"}
title={autogenGroupExpanded ? "Скрыть группу" : "Показать группу"}
>
<GroupChevronIcon expanded={autogenGroupExpanded} />
</button>
</div>
{autogenGroupExpanded ? (
<>
<div className="autoruns-form-grid">
<label>
Режимы
@@ -2600,6 +2749,7 @@ export function AutoRunsHistoryPanel({
<button
type="button"
className="autoruns-run-launch-btn"
style={isSavedUserSessionsMode ? { display: "none" } : undefined}
disabled={autogenRunBusy || editableGeneratedQuestions.length === 0 || !selectedAutogenGeneration}
onClick={() => void runAutogenCampaign()}
>
@@ -2682,7 +2832,7 @@ export function AutoRunsHistoryPanel({
</>
) : (
<>
<div className="autoruns-generated-questions">
<div className="autoruns-generated-questions" style={isSavedUserSessionsMode ? { display: "none" } : undefined}>
<div className="autoruns-generated-questions-head">
<strong>Вопросы к запуску: {editableGeneratedQuestions.length}</strong>
</div>
@@ -2765,14 +2915,35 @@ export function AutoRunsHistoryPanel({
+
</button>
</div>
{isSavedUserSessionsMode ? (
<h4>Сохраненные пользовательские сессии</h4>
) : (
{!isSavedUserSessionsMode ? (
<p className="muted">Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету.</p>
)}
) : null}
</>
)}
</>
) : null}
<div className="autoruns-group-heading">
<h4>{isSavedUserSessionsMode ? "Сохраненные пользовательские сессии" : "История автогенераций"}</h4>
<button
type="button"
className="autoruns-group-toggle"
onClick={() => setSavedSessionsGroupExpanded((prev) => !prev)}
aria-label={
savedSessionsGroupExpanded
? isSavedUserSessionsMode
? "Скрыть группу сохраненных пользовательских сессий"
: "Скрыть группу истории автогенераций"
: isSavedUserSessionsMode
? "Показать группу сохраненных пользовательских сессий"
: "Показать группу истории автогенераций"
}
title={savedSessionsGroupExpanded ? "Скрыть группу" : "Показать группу"}
>
<GroupChevronIcon expanded={savedSessionsGroupExpanded} />
</button>
</div>
{savedSessionsGroupExpanded ? (
<div className="autoruns-autogen-list">
{autogenHistoryBusy ? (
<p className="muted">
@@ -2787,9 +2958,49 @@ export function AutoRunsHistoryPanel({
{visibleAutoGenHistory.slice(0, 30).map((item) => (
<article
key={item.generation_id}
className={selectedAutogenGenerationId === item.generation_id ? "autoruns-autogen-item selected" : "autoruns-autogen-item"}
onClick={() => setSelectedAutogenGenerationId(item.generation_id)}
className={[
"autoruns-autogen-item",
selectedAutogenGenerationId === item.generation_id ? "selected" : "",
expandedSavedSessionGenerationId === item.generation_id ? "expanded" : "",
isSavedUserSessionsMode ? "saved-session" : ""
].filter(Boolean).join(" ")}
onClick={isSavedUserSessionsMode ? undefined : () => setSelectedAutogenGenerationId(item.generation_id)}
>
{isSavedUserSessionsMode ? (
<div className="autoruns-saved-session-topbar">
<button
type="button"
className="autoruns-saved-session-icon-btn"
disabled={autogenRunBusy}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
setSelectedAutogenGenerationId(item.generation_id);
void runAutogenCampaign(
item,
selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions
);
}}
title="Запустить прогон"
aria-label={`Запустить прогон для ${formatAutoGenGenerationTitle(item)}`}
>
<CardLaunchIcon />
</button>
<button
type="button"
className="autoruns-autogen-delete-btn"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
openAutoGenDeleteModal(item);
}}
title="Удалить сохраненный набор"
aria-label={`Удалить набор ${item.generation_id}`}
>
×
</button>
</div>
) : null}
<header>
<strong>{formatAutoGenGenerationTitle(item)}</strong>
<div className="autoruns-autogen-card-actions">
@@ -2834,9 +3045,146 @@ export function AutoRunsHistoryPanel({
<div className="autoruns-run-meta">
тип={isAgentSemanticGeneration(item) ? "АГЕНТНЫЙ ПРОГОН" : "АВТОПРОГОН"}
</div>
{isSavedUserSessionsMode ? (
<>
<div className="autoruns-saved-session-footer">
<button
type="button"
className="autoruns-saved-session-icon-btn"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
toggleSavedSessionQuestions(item.generation_id);
}}
title={
expandedSavedSessionGenerationId === item.generation_id ? "Скрыть вопросы" : "Показать вопросы"
}
aria-label={
expandedSavedSessionGenerationId === item.generation_id ? "Скрыть вопросы" : "Показать вопросы"
}
>
<CardChevronIcon expanded={expandedSavedSessionGenerationId === item.generation_id} />
</button>
</div>
<div
className={
expandedSavedSessionGenerationId === item.generation_id
? "autoruns-saved-session-questions expanded"
: "autoruns-saved-session-questions"
}
>
<div className="autoruns-generated-questions autoruns-generated-questions-embedded">
<div className="autoruns-generated-questions-head">
<strong>
Вопросы к запуску:{" "}
{selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions.length : item.questions.length}
</strong>
</div>
{(selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions).length === 0 ? (
<p className="muted">Список вопросов пуст.</p>
) : (
<div className="autoruns-generated-questions-list">
{(selectedAutogenGenerationId === item.generation_id ? editableGeneratedQuestions : item.questions).map(
(question, index) => (
<div
key={`${item.generation_id}-${index}-${question.slice(0, 24)}`}
className={[
"autoruns-generated-question-item",
dragOverQuestionIndex === index && selectedAutogenGenerationId === item.generation_id ? "drag-over" : "",
draggingQuestionIndex === index && selectedAutogenGenerationId === item.generation_id ? "dragging" : "",
editingQuestionIndex === index && selectedAutogenGenerationId === item.generation_id ? "editing" : ""
].filter(Boolean).join(" ")}
onDragOver={(event) =>
selectedAutogenGenerationId === item.generation_id ? handleQuestionDragOver(event, index) : undefined
}
onDrop={(event) =>
selectedAutogenGenerationId === item.generation_id ? void handleQuestionDrop(event, index) : undefined
}
>
<button
type="button"
className="autoruns-question-grip-btn"
draggable={
selectedAutogenGenerationId === item.generation_id &&
!generatedQuestionsBusy &&
editingQuestionIndex !== index
}
disabled={
selectedAutogenGenerationId !== item.generation_id ||
generatedQuestionsBusy ||
editingQuestionIndex === index
}
onDragStart={(event) => {
setSelectedAutogenGenerationId(item.generation_id);
handleQuestionDragStart(event, index);
}}
onDragEnd={handleQuestionDragEnd}
title="Перетащить вопрос"
aria-label={`Перетащить вопрос ${index + 1}`}
>
<QuestionGripIcon />
</button>
{selectedAutogenGenerationId === item.generation_id && editingQuestionIndex === index ? (
<>
<input
ref={questionEditorRef}
className="autoruns-generated-question-input"
value={editingQuestionDraft}
onChange={(event) => setEditingQuestionDraft(event.target.value)}
onBlur={handleQuestionEditorBlur}
onKeyDown={handleQuestionEditorKeyDown}
placeholder="Текст вопроса"
disabled={generatedQuestionsBusy}
/>
<button
type="button"
className="autoruns-remove-question-btn"
onMouseDown={(event) => event.preventDefault()}
onClick={() => void handleDeleteGeneratedQuestion(index)}
title="Удалить вопрос"
aria-label={`Удалить вопрос ${index + 1}`}
disabled={generatedQuestionsBusy}
>
×
</button>
</>
) : (
<button
type="button"
className="autoruns-generated-question-text"
onDoubleClick={() => {
setSelectedAutogenGenerationId(item.generation_id);
startQuestionEdit(index);
}}
title="Двойной клик для редактирования"
>
{index + 1}. {question}
</button>
)}
</div>
)
)}
</div>
)}
<button
type="button"
className="autoruns-add-question-btn"
onClick={() => {
setSelectedAutogenGenerationId(item.generation_id);
void handleAddGeneratedQuestion();
}}
disabled={selectedAutogenGenerationId !== item.generation_id || generatedQuestionsBusy}
>
+
</button>
</div>
</div>
</>
) : null}
</article>
))}
</div>
) : null}
<details className="autoruns-prompt-details">
<summary>Копия активного промпта (только чтение)</summary>
@@ -3038,6 +3386,9 @@ export function AutoRunsHistoryPanel({
<strong>{role === "assistant" ? "Система" : "Модель/вопрос"}</strong>
<div className="autoruns-msg-head-actions">
{item.case_id ? <span className="autoruns-msg-case-tag">{item.case_id}</span> : null}
{formatDialogStepTag(item) ? (
<span className="autoruns-msg-case-tag">{formatDialogStepTag(item)}</span>
) : null}
<span>{item.created_at ? formatDateTime(item.created_at) : "нет данных"}</span>
{role === "assistant" && !isLiveRunId(selectedRunId) ? (
<>