ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов Stage 3.8 - юи
This commit is contained in:
@@ -16,6 +16,10 @@ interface AssistantPanelProps {
|
||||
busy: boolean;
|
||||
statusText: string;
|
||||
errorMessage: string;
|
||||
showCommentAction?: boolean;
|
||||
onCommentAssistantMessage?: (item: AssistantConversationItem, index: number) => void;
|
||||
isAssistantMessageCommented?: (item: AssistantConversationItem, index: number) => boolean;
|
||||
canCommentAssistantMessage?: (item: AssistantConversationItem, index: number) => boolean;
|
||||
}
|
||||
|
||||
function roleLabel(role: AssistantConversationItem["role"]): string {
|
||||
@@ -61,6 +65,15 @@ async function copyTextToClipboard(text: string): Promise<boolean> {
|
||||
return copied;
|
||||
}
|
||||
|
||||
function CommentBubbleIcon({ commented }: { commented: boolean }) {
|
||||
const className = commented ? "comment-icon-svg commented" : "comment-icon-svg";
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M5 6.5h14v9H11.5l-4.5 3v-3H5z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function AssistantPanel({
|
||||
sessionId,
|
||||
conversation,
|
||||
@@ -72,7 +85,11 @@ export function AssistantPanel({
|
||||
onClear,
|
||||
busy,
|
||||
statusText,
|
||||
errorMessage
|
||||
errorMessage,
|
||||
showCommentAction = false,
|
||||
onCommentAssistantMessage,
|
||||
isAssistantMessageCommented,
|
||||
canCommentAssistantMessage
|
||||
}: AssistantPanelProps) {
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
const stickToBottomRef = useRef(true);
|
||||
@@ -162,11 +179,45 @@ export function AssistantPanel({
|
||||
</div>
|
||||
|
||||
<div ref={listRef} className="assistant-chat-list" onScroll={handleChatScroll}>
|
||||
{conversation.map((item) => (
|
||||
{conversation.map((item, index) => {
|
||||
const commentEnabled =
|
||||
item.role === "assistant" &&
|
||||
showCommentAction &&
|
||||
typeof onCommentAssistantMessage === "function" &&
|
||||
(typeof canCommentAssistantMessage === "function" ? canCommentAssistantMessage(item, index) : true);
|
||||
const commented =
|
||||
item.role === "assistant" && typeof isAssistantMessageCommented === "function"
|
||||
? isAssistantMessageCommented(item, index)
|
||||
: false;
|
||||
return (
|
||||
<article key={item.message_id} className={`assistant-msg ${item.role}`}>
|
||||
<header className="assistant-msg-head">
|
||||
<strong>{roleLabel(item.role)}</strong>
|
||||
<span>{shortTime(item.created_at)}</span>
|
||||
<div className="assistant-msg-head-main">
|
||||
<strong>{roleLabel(item.role)}</strong>
|
||||
<span>{shortTime(item.created_at)}</span>
|
||||
</div>
|
||||
{item.role === "assistant" && showCommentAction ? (
|
||||
<div className="assistant-msg-head-actions">
|
||||
<button
|
||||
type="button"
|
||||
className={commented ? "autoruns-comment-icon assistant-comment-btn commented" : "autoruns-comment-icon assistant-comment-btn"}
|
||||
onClick={() => onCommentAssistantMessage?.(item, index)}
|
||||
disabled={!commentEnabled}
|
||||
title={
|
||||
commentEnabled
|
||||
? "Комментировать ответ ассистента"
|
||||
: "Комментарий недоступен для этого сообщения"
|
||||
}
|
||||
aria-label={
|
||||
commentEnabled
|
||||
? "Комментировать ответ ассистента"
|
||||
: "Комментарий недоступен для этого сообщения"
|
||||
}
|
||||
>
|
||||
<CommentBubbleIcon commented={commented} />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</header>
|
||||
<div className="assistant-msg-body">{item.text}</div>
|
||||
{item.role === "assistant" && item.debug ? (
|
||||
@@ -176,13 +227,15 @@ export function AssistantPanel({
|
||||
</details>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="assistant-compose">
|
||||
<label className="full-width">
|
||||
Сообщение
|
||||
<textarea
|
||||
className="assistant-input-textarea"
|
||||
value={inputValue}
|
||||
onChange={(event) => onInputChange(event.target.value)}
|
||||
rows={4}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type SyntheticEvent } from "react";
|
||||
import { apiClient } from "../api/client";
|
||||
import type {
|
||||
AssistantConversationItem,
|
||||
AssistantAnnotationRecord,
|
||||
AsyncEvalRunJob,
|
||||
AutoGenHistoryRecord,
|
||||
AutoGenMode,
|
||||
@@ -68,6 +69,16 @@ interface CommentModalState {
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface AssistantLiveCommentModalState {
|
||||
open: boolean;
|
||||
messageIndex: number;
|
||||
rating: number;
|
||||
comment: string;
|
||||
annotationAuthor: string;
|
||||
saving: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface AutoGenSettingsState {
|
||||
mode: AutoGenMode;
|
||||
count: number;
|
||||
@@ -117,6 +128,7 @@ function buildDefaultPersonalityPrompts(
|
||||
interface AutoRunsUiConfig {
|
||||
filters?: Partial<AutoRunsFilters>;
|
||||
analysisDate?: string;
|
||||
autogenPersonalityPromptHeight?: number;
|
||||
autoGenSettings?: {
|
||||
mode?: AutoGenMode;
|
||||
count?: number;
|
||||
@@ -143,6 +155,11 @@ function normalizeAnalysisDateInput(value: string): string {
|
||||
return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : "";
|
||||
}
|
||||
|
||||
function clampAutogenPromptHeight(value: number | null | undefined): number {
|
||||
const numeric = typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : 160;
|
||||
return Math.max(110, Math.min(520, numeric));
|
||||
}
|
||||
|
||||
function dateToInputValue(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
@@ -391,9 +408,6 @@ function CommentBubbleIcon({ commented }: { commented: boolean }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M5 6.5h14v9H11.5l-4.5 3v-3H5z" />
|
||||
<circle className="comment-icon-dot" cx="9" cy="11" r="1.05" />
|
||||
<circle className="comment-icon-dot" cx="12" cy="11" r="1.05" />
|
||||
<circle className="comment-icon-dot" cx="15" cy="11" r="1.05" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -462,6 +476,7 @@ export function AutoRunsHistoryPanel({
|
||||
const [errorText, setErrorText] = useState("");
|
||||
const [assistantLiveSessionId, setAssistantLiveSessionId] = useState("");
|
||||
const [assistantLiveConversation, setAssistantLiveConversation] = useState<AssistantConversationItem[]>([]);
|
||||
const [assistantLiveAnnotations, setAssistantLiveAnnotations] = useState<AssistantAnnotationRecord[]>([]);
|
||||
const [assistantLiveInput, setAssistantLiveInput] = useState("");
|
||||
const [assistantLiveUseMock, setAssistantLiveUseMock] = useState(false);
|
||||
const [assistantLiveBusy, setAssistantLiveBusy] = useState(false);
|
||||
@@ -469,6 +484,7 @@ export function AutoRunsHistoryPanel({
|
||||
const [assistantLiveError, setAssistantLiveError] = useState("");
|
||||
const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit));
|
||||
const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count));
|
||||
const [autogenPersonalityPromptHeight, setAutogenPersonalityPromptHeight] = useState(160);
|
||||
const [commentModal, setCommentModal] = useState<CommentModalState>({
|
||||
open: false,
|
||||
caseId: "",
|
||||
@@ -481,6 +497,15 @@ export function AutoRunsHistoryPanel({
|
||||
saving: false,
|
||||
error: ""
|
||||
});
|
||||
const [assistantLiveCommentModal, setAssistantLiveCommentModal] = useState<AssistantLiveCommentModalState>({
|
||||
open: false,
|
||||
messageIndex: -1,
|
||||
rating: 3,
|
||||
comment: "",
|
||||
annotationAuthor: "manual_reviewer",
|
||||
saving: false,
|
||||
error: ""
|
||||
});
|
||||
|
||||
const initialLoadDoneRef = useRef(false);
|
||||
const asyncJobPollTimerRef = useRef<number | null>(null);
|
||||
@@ -510,6 +535,27 @@ export function AutoRunsHistoryPanel({
|
||||
}
|
||||
return null;
|
||||
}, [commentModal.messageIndex, dialog]);
|
||||
const assistantLiveAnnotationsByMessageId = useMemo(() => {
|
||||
const map = new Map<string, AssistantAnnotationRecord>();
|
||||
for (const item of assistantLiveAnnotations) {
|
||||
if (item.message_id) {
|
||||
map.set(item.message_id, item);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [assistantLiveAnnotations]);
|
||||
const assistantLiveCommentModalMessage =
|
||||
assistantLiveCommentModal.messageIndex >= 0 ? assistantLiveConversation[assistantLiveCommentModal.messageIndex] ?? null : null;
|
||||
const assistantLiveCommentModalQuestion = useMemo(() => {
|
||||
if (assistantLiveCommentModal.messageIndex < 0) return null;
|
||||
for (let index = assistantLiveCommentModal.messageIndex - 1; index >= 0; index -= 1) {
|
||||
const candidate = assistantLiveConversation[index];
|
||||
if (candidate?.role === "user") {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [assistantLiveCommentModal.messageIndex, assistantLiveConversation]);
|
||||
|
||||
const annotationsAverageRating = useMemo(() => {
|
||||
if (visibleAnnotations.length === 0) return null;
|
||||
@@ -535,6 +581,44 @@ export function AutoRunsHistoryPanel({
|
||||
[onLog]
|
||||
);
|
||||
|
||||
const loadAssistantLiveAnnotationsForSession = useCallback(
|
||||
async (sessionIdRaw: string) => {
|
||||
const sessionId = String(sessionIdRaw ?? "").trim();
|
||||
if (!sessionId) {
|
||||
setAssistantLiveAnnotations([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = await apiClient.loadAssistantAnnotations({
|
||||
session_id: sessionId,
|
||||
limit: 400
|
||||
});
|
||||
setAssistantLiveAnnotations(payload.items ?? []);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log(`Assistant live annotations load error: ${message}`);
|
||||
}
|
||||
},
|
||||
[log]
|
||||
);
|
||||
|
||||
const closeAssistantLiveCommentModal = useCallback((options?: { force?: boolean }) => {
|
||||
setAssistantLiveCommentModal((prev) => {
|
||||
if (prev.saving && !options?.force) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
open: false,
|
||||
messageIndex: -1,
|
||||
rating: 3,
|
||||
comment: "",
|
||||
annotationAuthor: "manual_reviewer",
|
||||
saving: false,
|
||||
error: ""
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const copyRunIdToClipboard = useCallback(
|
||||
async (event: React.SyntheticEvent, runId: string) => {
|
||||
event.stopPropagation();
|
||||
@@ -580,11 +664,13 @@ export function AutoRunsHistoryPanel({
|
||||
const resetAssistantLiveSession = useCallback(() => {
|
||||
setAssistantLiveSessionId("");
|
||||
setAssistantLiveConversation([]);
|
||||
setAssistantLiveAnnotations([]);
|
||||
setAssistantLiveInput("");
|
||||
setAssistantLiveStatus("");
|
||||
setAssistantLiveError("");
|
||||
closeAssistantLiveCommentModal({ force: true });
|
||||
log("Live-чат ассистента в истории автопрогонов сброшен.");
|
||||
}, [log]);
|
||||
}, [closeAssistantLiveCommentModal, log]);
|
||||
|
||||
const sendAssistantLiveMessage = useCallback(async () => {
|
||||
const userMessage = assistantLiveInput.trim();
|
||||
@@ -621,6 +707,7 @@ export function AutoRunsHistoryPanel({
|
||||
});
|
||||
setAssistantLiveSessionId(response.session_id);
|
||||
setAssistantLiveConversation(response.conversation);
|
||||
await loadAssistantLiveAnnotationsForSession(response.session_id);
|
||||
setAssistantLiveStatus("Ответ готов");
|
||||
log(`Live-ответ ассистента получен: trace=${response.debug.trace_id}`);
|
||||
} catch (error) {
|
||||
@@ -638,6 +725,7 @@ export function AutoRunsHistoryPanel({
|
||||
assistantLiveUseMock,
|
||||
assistantPromptVersion,
|
||||
connection,
|
||||
loadAssistantLiveAnnotationsForSession,
|
||||
log,
|
||||
prompts
|
||||
]);
|
||||
@@ -692,6 +780,20 @@ export function AutoRunsHistoryPanel({
|
||||
[autoGenSettings.count]
|
||||
);
|
||||
|
||||
const commitAutogenPromptHeight = useCallback((height: number) => {
|
||||
setAutogenPersonalityPromptHeight(clampAutogenPromptHeight(height));
|
||||
}, []);
|
||||
|
||||
const captureAutogenPromptHeight = useCallback(
|
||||
(event: SyntheticEvent<HTMLTextAreaElement>) => {
|
||||
const nextHeight = event.currentTarget.offsetHeight;
|
||||
if (Number.isFinite(nextHeight) && nextHeight > 0) {
|
||||
commitAutogenPromptHeight(nextHeight);
|
||||
}
|
||||
},
|
||||
[commitAutogenPromptHeight]
|
||||
);
|
||||
|
||||
const loadAnnotations = useCallback(async () => {
|
||||
setAnnotationsBusy(true);
|
||||
try {
|
||||
@@ -1202,6 +1304,94 @@ export function AutoRunsHistoryPanel({
|
||||
selectedRunId
|
||||
]);
|
||||
|
||||
const canCommentAssistantLiveMessage = useCallback((item: AssistantConversationItem): boolean => item.role === "assistant", []);
|
||||
|
||||
const isAssistantLiveMessageCommented = useCallback(
|
||||
(item: AssistantConversationItem): boolean => item.role === "assistant" && assistantLiveAnnotationsByMessageId.has(item.message_id),
|
||||
[assistantLiveAnnotationsByMessageId]
|
||||
);
|
||||
|
||||
const openAssistantLiveCommentModal = useCallback(
|
||||
(item: AssistantConversationItem, index: number) => {
|
||||
if (item.role !== "assistant") {
|
||||
return;
|
||||
}
|
||||
const sessionIdFromState = assistantLiveSessionId.trim();
|
||||
const sessionIdFromItem = String(item.session_id ?? "").trim();
|
||||
const resolvedSessionId = sessionIdFromState || sessionIdFromItem;
|
||||
if (!resolvedSessionId) {
|
||||
setAssistantLiveError("Сначала получите ответ ассистента в активной сессии.");
|
||||
return;
|
||||
}
|
||||
if (!sessionIdFromState && sessionIdFromItem) {
|
||||
setAssistantLiveSessionId(sessionIdFromItem);
|
||||
}
|
||||
const existing = assistantLiveAnnotationsByMessageId.get(item.message_id) ?? null;
|
||||
setAssistantLiveError("");
|
||||
setAssistantLiveCommentModal({
|
||||
open: true,
|
||||
messageIndex: index,
|
||||
rating: existing?.rating ?? 3,
|
||||
comment: existing?.comment ?? "",
|
||||
annotationAuthor: existing?.annotation_author ?? "manual_reviewer",
|
||||
saving: false,
|
||||
error: ""
|
||||
});
|
||||
},
|
||||
[assistantLiveAnnotationsByMessageId, assistantLiveSessionId]
|
||||
);
|
||||
|
||||
const submitAssistantLiveCommentModal = useCallback(async () => {
|
||||
if (assistantLiveCommentModal.messageIndex < 0) {
|
||||
return;
|
||||
}
|
||||
if (!assistantLiveCommentModal.comment.trim()) {
|
||||
setAssistantLiveCommentModal((prev) => ({ ...prev, error: "Добавьте комментарий." }));
|
||||
return;
|
||||
}
|
||||
|
||||
const modalMessage = assistantLiveConversation[assistantLiveCommentModal.messageIndex] ?? null;
|
||||
const sessionId =
|
||||
assistantLiveSessionId.trim() || (modalMessage?.role === "assistant" ? String(modalMessage.session_id ?? "").trim() : "");
|
||||
if (!sessionId) {
|
||||
setAssistantLiveCommentModal((prev) => ({ ...prev, error: "Сессия ассистента не найдена." }));
|
||||
return;
|
||||
}
|
||||
|
||||
setAssistantLiveCommentModal((prev) => ({ ...prev, saving: true, error: "" }));
|
||||
try {
|
||||
const payload = await apiClient.saveAssistantAnnotation({
|
||||
session_id: sessionId,
|
||||
message_index: assistantLiveCommentModal.messageIndex,
|
||||
rating: assistantLiveCommentModal.rating,
|
||||
comment: assistantLiveCommentModal.comment.trim(),
|
||||
annotation_author: assistantLiveCommentModal.annotationAuthor.trim() || undefined
|
||||
});
|
||||
setAssistantLiveAnnotations((prev) => {
|
||||
const next = [...prev];
|
||||
const index = next.findIndex((item) => item.annotation_id === payload.annotation.annotation_id);
|
||||
if (index >= 0) {
|
||||
next[index] = payload.annotation;
|
||||
} else {
|
||||
next.unshift(payload.annotation);
|
||||
}
|
||||
return next.sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
|
||||
});
|
||||
closeAssistantLiveCommentModal({ force: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setAssistantLiveCommentModal((prev) => ({ ...prev, saving: false, error: message }));
|
||||
}
|
||||
}, [
|
||||
assistantLiveCommentModal.annotationAuthor,
|
||||
assistantLiveCommentModal.comment,
|
||||
assistantLiveCommentModal.messageIndex,
|
||||
assistantLiveCommentModal.rating,
|
||||
assistantLiveConversation,
|
||||
assistantLiveSessionId,
|
||||
closeAssistantLiveCommentModal
|
||||
]);
|
||||
|
||||
const applyLocalAnnotationPatch = useCallback((annotation: AutoRunAnnotationRecord) => {
|
||||
setAnnotations((prev) =>
|
||||
prev.map((item) =>
|
||||
@@ -1316,6 +1506,14 @@ export function AutoRunsHistoryPanel({
|
||||
setAutogenCountInput(String(autoGenSettings.count));
|
||||
}, [autoGenSettings.count]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!assistantLiveSessionId.trim()) {
|
||||
setAssistantLiveAnnotations([]);
|
||||
return;
|
||||
}
|
||||
void loadAssistantLiveAnnotationsForSession(assistantLiveSessionId);
|
||||
}, [assistantLiveSessionId, loadAssistantLiveAnnotationsForSession]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeAsyncJob) return;
|
||||
const liveRunId = toLiveRunId(activeAsyncJob.job_id);
|
||||
@@ -1375,6 +1573,9 @@ export function AutoRunsHistoryPanel({
|
||||
if (typeof parsed.analysisDate === "string") {
|
||||
setAnalysisDate(normalizeAnalysisDateInput(parsed.analysisDate));
|
||||
}
|
||||
if (typeof parsed.autogenPersonalityPromptHeight === "number") {
|
||||
setAutogenPersonalityPromptHeight(clampAutogenPromptHeight(parsed.autogenPersonalityPromptHeight));
|
||||
}
|
||||
if (parsed.autoGenSettings) {
|
||||
setAutoGenSettings((prev) => {
|
||||
const nextPrompts: Record<string, string> = {
|
||||
@@ -1431,6 +1632,7 @@ export function AutoRunsHistoryPanel({
|
||||
const payload: AutoRunsUiConfig = {
|
||||
filters,
|
||||
analysisDate,
|
||||
autogenPersonalityPromptHeight,
|
||||
autoGenSettings: {
|
||||
mode: autoGenSettings.mode,
|
||||
count: autoGenSettings.count,
|
||||
@@ -1443,7 +1645,7 @@ export function AutoRunsHistoryPanel({
|
||||
hideResolvedAnnotations
|
||||
};
|
||||
localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload));
|
||||
}, [analysisDate, annotationDecisionFilter, autoGenSettings, filters, hideResolvedAnnotations]);
|
||||
}, [analysisDate, annotationDecisionFilter, autoGenSettings, autogenPersonalityPromptHeight, filters, hideResolvedAnnotations]);
|
||||
|
||||
useEffect(() => {
|
||||
const onSave = () => {
|
||||
@@ -1596,9 +1798,6 @@ export function AutoRunsHistoryPanel({
|
||||
</div>
|
||||
|
||||
<h4>Автогенерация вопросов</h4>
|
||||
<p className="muted">
|
||||
`qwen_seed` использует текущую LLM-модель из активного контура подключения (та же модель, что и для ответов ассистента).
|
||||
</p>
|
||||
<div className="autoruns-form-grid">
|
||||
<label>
|
||||
Режим генерации
|
||||
@@ -1660,6 +1859,7 @@ export function AutoRunsHistoryPanel({
|
||||
<label className="full-width">
|
||||
Промпт личности
|
||||
<textarea
|
||||
className="autoruns-personality-prompt"
|
||||
value={autoGenSettings.personalityPrompts[autoGenSettings.personalityId] ?? ""}
|
||||
onChange={(event) =>
|
||||
setAutoGenSettings((prev) => ({
|
||||
@@ -1671,6 +1871,9 @@ export function AutoRunsHistoryPanel({
|
||||
}))
|
||||
}
|
||||
placeholder="Текст промпта для выбранной личности автогенерации"
|
||||
style={{ height: `${autogenPersonalityPromptHeight}px` }}
|
||||
onMouseUp={captureAutogenPromptHeight}
|
||||
onTouchEnd={captureAutogenPromptHeight}
|
||||
/>
|
||||
</label>
|
||||
<label className="checkbox-row">
|
||||
@@ -1698,9 +1901,6 @@ export function AutoRunsHistoryPanel({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="muted">
|
||||
Если дата среза задана, автопрогон анализирует данные на эту дату. Если поле пустое, используется текущее состояние.
|
||||
</p>
|
||||
|
||||
<div className="button-row">
|
||||
<button type="button" disabled={autoGenBusy} onClick={() => void generateAutogenBatch()}>
|
||||
@@ -1711,7 +1911,7 @@ export function AutoRunsHistoryPanel({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="tab"
|
||||
className="autoruns-run-launch-btn"
|
||||
disabled={autogenRunBusy || editableGeneratedQuestions.length === 0}
|
||||
onClick={() => void runAutogenCampaign()}
|
||||
>
|
||||
@@ -1764,7 +1964,7 @@ export function AutoRunsHistoryPanel({
|
||||
title="Удалить вопрос из запуска"
|
||||
aria-label="Удалить вопрос из запуска"
|
||||
>
|
||||
X
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -2071,6 +2271,10 @@ export function AutoRunsHistoryPanel({
|
||||
busy={assistantLiveBusy}
|
||||
statusText={assistantLiveStatus}
|
||||
errorMessage={assistantLiveError}
|
||||
showCommentAction
|
||||
onCommentAssistantMessage={openAssistantLiveCommentModal}
|
||||
isAssistantMessageCommented={isAssistantLiveMessageCommented}
|
||||
canCommentAssistantMessage={canCommentAssistantLiveMessage}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -2359,6 +2563,89 @@ export function AutoRunsHistoryPanel({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{assistantLiveCommentModal.open ? (
|
||||
<div
|
||||
className="autoruns-comment-modal-backdrop"
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
closeAssistantLiveCommentModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="autoruns-comment-modal">
|
||||
<h3>Комментарий к ответу ассистента</h3>
|
||||
<p className="muted">Комментарий сохраняется отдельно от комментариев автопрогонов.</p>
|
||||
|
||||
{assistantLiveCommentModalQuestion ? (
|
||||
<details className="autoruns-prompt-details" open>
|
||||
<summary>Вопрос пользователя</summary>
|
||||
<p className="autoruns-comment-quote">{assistantLiveCommentModalQuestion.text}</p>
|
||||
</details>
|
||||
) : null}
|
||||
{assistantLiveCommentModalMessage ? (
|
||||
<details className="autoruns-prompt-details" open>
|
||||
<summary>Ответ ассистента</summary>
|
||||
<p className="autoruns-comment-quote">{assistantLiveCommentModalMessage.text}</p>
|
||||
</details>
|
||||
) : null}
|
||||
|
||||
<div className="autoruns-rating-row" role="group" aria-label="Рейтинг ответа ассистента">
|
||||
{[1, 2, 3, 4, 5].map((value) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
className={assistantLiveCommentModal.rating >= value ? "autoruns-rating-dot active" : "autoruns-rating-dot"}
|
||||
onClick={() => setAssistantLiveCommentModal((prev) => ({ ...prev, rating: value }))}
|
||||
disabled={assistantLiveCommentModal.saving}
|
||||
aria-label={`Оценка ${value}`}
|
||||
>
|
||||
{assistantLiveCommentModal.rating >= value ? "●" : "○"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="autoruns-form-grid">
|
||||
<label>
|
||||
Автор комментария
|
||||
<input
|
||||
value={assistantLiveCommentModal.annotationAuthor}
|
||||
onChange={(event) => setAssistantLiveCommentModal((prev) => ({ ...prev, annotationAuthor: event.target.value }))}
|
||||
placeholder="manual_reviewer"
|
||||
disabled={assistantLiveCommentModal.saving}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
Комментарий
|
||||
<textarea
|
||||
value={assistantLiveCommentModal.comment}
|
||||
onChange={(event) => setAssistantLiveCommentModal((prev) => ({ ...prev, comment: event.target.value }))}
|
||||
placeholder="Что именно не так в ответе и что нужно исправить."
|
||||
rows={4}
|
||||
disabled={assistantLiveCommentModal.saving}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{assistantLiveCommentModal.error ? <p className="error-text">{assistantLiveCommentModal.error}</p> : null}
|
||||
|
||||
<div className="button-row">
|
||||
<button type="button" onClick={() => void submitAssistantLiveCommentModal()} disabled={assistantLiveCommentModal.saving}>
|
||||
{assistantLiveCommentModal.saving ? "Сохраняю..." : "Готово"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="tab"
|
||||
onClick={() => closeAssistantLiveCommentModal()}
|
||||
disabled={assistantLiveCommentModal.saving}
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{commentModal.open ? (
|
||||
<div
|
||||
className="autoruns-comment-modal-backdrop"
|
||||
|
||||
Reference in New Issue
Block a user