ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.12.23: декомпозиция deep-turn пайплайна ассистента в runtime-адаптеры
This commit is contained in:
@@ -75,15 +75,16 @@ export function AssistantPanel({
|
||||
errorMessage
|
||||
}: AssistantPanelProps) {
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
const stickToBottomRef = useRef(true);
|
||||
const copyResetTimerRef = useRef<number | null>(null);
|
||||
const [copyState, setCopyState] = useState<"idle" | "success" | "error">("idle");
|
||||
const [copyModeLabel, setCopyModeLabel] = useState<"чат" | "тех">("чат");
|
||||
|
||||
useEffect(() => {
|
||||
if (listRef.current) {
|
||||
if (listRef.current && stickToBottomRef.current) {
|
||||
listRef.current.scrollTop = listRef.current.scrollHeight;
|
||||
}
|
||||
}, [conversation, statusText]);
|
||||
}, [conversation]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -111,85 +112,93 @@ export function AssistantPanel({
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelFrame
|
||||
title="Режим ассистента"
|
||||
subtitle="Диалоговый слой поверх normalizer, маршрутизации и factual retrieval."
|
||||
actions={
|
||||
<div className="assistant-panel-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("default");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Экспорт только user-facing чата"
|
||||
>
|
||||
Скопировать чат
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("technical");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Технический экспорт с debug payload"
|
||||
>
|
||||
Скопировать техчат
|
||||
</button>
|
||||
{copyState === "success" ? <span className="assistant-copy-feedback success">Скопировано ({copyModeLabel})</span> : null}
|
||||
{copyState === "error" ? <span className="assistant-copy-feedback error">Ошибка копирования</span> : null}
|
||||
<span className="status-chip">{sessionId ? `session: ${sessionId}` : "новая сессия"}</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div ref={listRef} className="assistant-chat-list">
|
||||
{conversation.length === 0 ? (
|
||||
<div className="assistant-empty muted">Диалог пуст. Отправьте первый вопрос, чтобы запустить контур ассистента.</div>
|
||||
) : null}
|
||||
{conversation.map((item) => (
|
||||
<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>
|
||||
</header>
|
||||
<div className="assistant-msg-body">{item.text}</div>
|
||||
{item.role === "assistant" && item.debug ? (
|
||||
<details className="assistant-debug">
|
||||
<summary>Показать технический разбор</summary>
|
||||
<JsonView value={item.debug} />
|
||||
</details>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
function handleChatScroll(): void {
|
||||
if (!listRef.current) return;
|
||||
const node = listRef.current;
|
||||
const distanceToBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
|
||||
stickToBottomRef.current = distanceToBottom < 16;
|
||||
}
|
||||
|
||||
<div className="assistant-compose">
|
||||
<label className="full-width">
|
||||
Сообщение
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(event) => onInputChange(event.target.value)}
|
||||
rows={4}
|
||||
placeholder="Введите вопрос к данным компании..."
|
||||
/>
|
||||
</label>
|
||||
<div className="button-row">
|
||||
<label className="checkbox-row">
|
||||
<input type="checkbox" checked={useMock} onChange={(event) => onUseMockChange(event.target.checked)} />
|
||||
Mock-режим
|
||||
return (
|
||||
<PanelFrame className="assistant-panel-frame" title="Режим ассистента">
|
||||
<div className="assistant-live-shell">
|
||||
<div className="assistant-toolbar">
|
||||
<div className="assistant-toolbar-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("default");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Экспорт только user-facing чата"
|
||||
>
|
||||
Скопировать чат
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="assistant-copy-btn"
|
||||
onClick={() => {
|
||||
void handleCopyConversation("technical");
|
||||
}}
|
||||
disabled={conversation.length === 0}
|
||||
title="Технический экспорт с debug payload"
|
||||
>
|
||||
Скопировать техчат
|
||||
</button>
|
||||
<button type="button" className="assistant-copy-btn" onClick={() => onClear()} disabled={busy && conversation.length === 0}>
|
||||
Сбросить сессию
|
||||
</button>
|
||||
</div>
|
||||
<div className="assistant-toolbar-meta">
|
||||
{sessionId ? <span className="status-chip">{`session: ${sessionId}`}</span> : null}
|
||||
<div className="assistant-toolbar-meta-right">
|
||||
{statusText ? <span className="assistant-live-status">{statusText}</span> : null}
|
||||
{copyState === "success" ? <span className="assistant-copy-feedback success">Скопировано ({copyModeLabel})</span> : null}
|
||||
{copyState === "error" ? <span className="assistant-copy-feedback error">Ошибка копирования</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
{errorMessage ? <p className="error-text assistant-toolbar-error">{errorMessage}</p> : null}
|
||||
</div>
|
||||
|
||||
<div ref={listRef} className="assistant-chat-list" onScroll={handleChatScroll}>
|
||||
{conversation.map((item) => (
|
||||
<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>
|
||||
</header>
|
||||
<div className="assistant-msg-body">{item.text}</div>
|
||||
{item.role === "assistant" && item.debug ? (
|
||||
<details className="assistant-debug">
|
||||
<summary>Показать технический разбор</summary>
|
||||
<JsonView value={item.debug} />
|
||||
</details>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="assistant-compose">
|
||||
<label className="full-width">
|
||||
Сообщение
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(event) => onInputChange(event.target.value)}
|
||||
rows={4}
|
||||
placeholder="Введите вопрос к данным компании..."
|
||||
/>
|
||||
</label>
|
||||
<button type="button" onClick={() => onSend()} disabled={busy || !inputValue.trim()}>
|
||||
{busy ? "Выполняю..." : "Отправить"}
|
||||
</button>
|
||||
<button type="button" onClick={() => onClear()} disabled={busy && conversation.length === 0}>
|
||||
Сбросить сессию
|
||||
</button>
|
||||
<div className="button-row assistant-send-row">
|
||||
<label className="checkbox-row">
|
||||
<input type="checkbox" checked={useMock} onChange={(event) => onUseMockChange(event.target.checked)} />
|
||||
Mock-режим
|
||||
</label>
|
||||
<button type="button" className="assistant-send-btn" onClick={() => onSend()} disabled={busy || !inputValue.trim()}>
|
||||
{busy ? "Выполняю..." : "Отправить"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{statusText ? <p className="diff-summary">{statusText}</p> : null}
|
||||
{errorMessage ? <p className="error-text">{errorMessage}</p> : null}
|
||||
</div>
|
||||
</PanelFrame>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { apiClient } from "../api/client";
|
||||
import type {
|
||||
AssistantConversationItem,
|
||||
AsyncEvalRunJob,
|
||||
AutoGenHistoryRecord,
|
||||
AutoGenMode,
|
||||
@@ -18,6 +19,7 @@ import type {
|
||||
ManualCaseDecision,
|
||||
PromptState
|
||||
} from "../state/types";
|
||||
import { AssistantPanel } from "./AssistantPanel";
|
||||
import { JsonView } from "./JsonView";
|
||||
import { PanelFrame } from "./PanelFrame";
|
||||
|
||||
@@ -91,6 +93,7 @@ const LIVE_RUN_ID_PREFIX = "__live__:";
|
||||
|
||||
const AUTORUNS_UI_CONFIG_KEY = "ndc_autoruns_ui_config_v1";
|
||||
const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save";
|
||||
const ASSISTANT_STAGES = ["Анализ запроса", "Получение данных", "Подготовка ответа"];
|
||||
|
||||
const AUTOGEN_PERSONALITIES: AutoGenPersonalityDefinition[] = [
|
||||
{
|
||||
@@ -113,6 +116,7 @@ function buildDefaultPersonalityPrompts(
|
||||
|
||||
interface AutoRunsUiConfig {
|
||||
filters?: Partial<AutoRunsFilters>;
|
||||
analysisDate?: string;
|
||||
autoGenSettings?: {
|
||||
mode?: AutoGenMode;
|
||||
count?: number;
|
||||
@@ -134,6 +138,11 @@ const DEFAULT_AUTOGEN_SETTINGS: AutoGenSettingsState = {
|
||||
generatedBy: "manual_reviewer"
|
||||
};
|
||||
|
||||
function normalizeAnalysisDateInput(value: string): string {
|
||||
const normalized = String(value ?? "").trim();
|
||||
return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : "";
|
||||
}
|
||||
|
||||
function dateToInputValue(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
@@ -246,6 +255,7 @@ function buildLiveRunSummary(job: AsyncEvalRunJob): AutoRunSummary {
|
||||
llm_provider: null,
|
||||
model: null,
|
||||
use_mock: null,
|
||||
analysis_date: job.report_summary?.analysis_date ?? job.analysis_date ?? null,
|
||||
prompt_version: null,
|
||||
schema_version: null,
|
||||
suite_id: job.case_set_file,
|
||||
@@ -314,7 +324,8 @@ function buildLiveRunDetail(job: AsyncEvalRunJob, requestedCaseId: string): {
|
||||
run_id: job.report_summary.run_id,
|
||||
run_timestamp: job.report_summary.run_timestamp,
|
||||
score_index: job.report_summary.score_index,
|
||||
cases_total: job.report_summary.cases_total
|
||||
cases_total: job.report_summary.cases_total,
|
||||
analysis_date: job.report_summary.analysis_date ?? job.analysis_date ?? null
|
||||
}
|
||||
: {}
|
||||
};
|
||||
@@ -411,6 +422,7 @@ export function AutoRunsHistoryPanel({
|
||||
...DEFAULT_FILTERS,
|
||||
fromLocal: defaultFromDateValue()
|
||||
});
|
||||
const [analysisDate, setAnalysisDate] = useState("");
|
||||
const [history, setHistory] = useState<AutoRunHistoryResponse | null>(null);
|
||||
const [runDetail, setRunDetail] = useState<AutoRunDetailResponse | null>(null);
|
||||
const [dialog, setDialog] = useState<AutoRunDialogResponse | null>(null);
|
||||
@@ -439,6 +451,13 @@ export function AutoRunsHistoryPanel({
|
||||
const [annotationsBusy, setAnnotationsBusy] = useState(false);
|
||||
const [annotationResolutionBusyId, setAnnotationResolutionBusyId] = useState("");
|
||||
const [errorText, setErrorText] = useState("");
|
||||
const [assistantLiveSessionId, setAssistantLiveSessionId] = useState("");
|
||||
const [assistantLiveConversation, setAssistantLiveConversation] = useState<AssistantConversationItem[]>([]);
|
||||
const [assistantLiveInput, setAssistantLiveInput] = useState("");
|
||||
const [assistantLiveUseMock, setAssistantLiveUseMock] = useState(false);
|
||||
const [assistantLiveBusy, setAssistantLiveBusy] = useState(false);
|
||||
const [assistantLiveStatus, setAssistantLiveStatus] = useState("");
|
||||
const [assistantLiveError, setAssistantLiveError] = useState("");
|
||||
const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit));
|
||||
const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count));
|
||||
const [commentModal, setCommentModal] = useState<CommentModalState>({
|
||||
@@ -465,8 +484,6 @@ export function AutoRunsHistoryPanel({
|
||||
[autoGenHistory, selectedAutogenGenerationId]
|
||||
);
|
||||
|
||||
const activeRunSummary: AutoRunSummary | null =
|
||||
history?.items.find((item) => item.run_id === selectedRunId) ?? runDetail?.run ?? null;
|
||||
const activeCase = runDetail ? getSelectedCase(runDetail.cases, selectedCaseId) : null;
|
||||
const visibleAnnotations = useMemo(
|
||||
() => (hideResolvedAnnotations ? annotations.filter((item) => !item.resolved) : annotations),
|
||||
@@ -509,6 +526,81 @@ export function AutoRunsHistoryPanel({
|
||||
[onLog]
|
||||
);
|
||||
|
||||
function startAssistantLiveStatusTicker(): () => void {
|
||||
let index = 0;
|
||||
setAssistantLiveStatus(ASSISTANT_STAGES[0]);
|
||||
const timer = window.setInterval(() => {
|
||||
index = Math.min(index + 1, ASSISTANT_STAGES.length - 1);
|
||||
setAssistantLiveStatus(ASSISTANT_STAGES[index]);
|
||||
}, 650);
|
||||
return () => window.clearInterval(timer);
|
||||
}
|
||||
|
||||
const resetAssistantLiveSession = useCallback(() => {
|
||||
setAssistantLiveSessionId("");
|
||||
setAssistantLiveConversation([]);
|
||||
setAssistantLiveInput("");
|
||||
setAssistantLiveStatus("");
|
||||
setAssistantLiveError("");
|
||||
log("Live-чат ассистента в истории автопрогонов сброшен.");
|
||||
}, [log]);
|
||||
|
||||
const sendAssistantLiveMessage = useCallback(async () => {
|
||||
const userMessage = assistantLiveInput.trim();
|
||||
if (!userMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAssistantLiveBusy(true);
|
||||
setAssistantLiveError("");
|
||||
setAssistantLiveInput("");
|
||||
setAssistantLiveConversation((prev) => [
|
||||
...prev,
|
||||
{
|
||||
message_id: `autoruns-live-${Date.now()}`,
|
||||
session_id: assistantLiveSessionId || "pending",
|
||||
role: "user",
|
||||
text: userMessage,
|
||||
reply_type: null,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: null,
|
||||
debug: null
|
||||
}
|
||||
]);
|
||||
|
||||
const stopTicker = startAssistantLiveStatusTicker();
|
||||
try {
|
||||
const response = await apiClient.sendAssistantMessage({
|
||||
connection,
|
||||
prompts,
|
||||
userMessage,
|
||||
sessionId: assistantLiveSessionId || undefined,
|
||||
promptVersion: assistantPromptVersion,
|
||||
useMock: assistantLiveUseMock
|
||||
});
|
||||
setAssistantLiveSessionId(response.session_id);
|
||||
setAssistantLiveConversation(response.conversation);
|
||||
setAssistantLiveStatus("Ответ готов");
|
||||
log(`Live-ответ ассистента получен: trace=${response.debug.trace_id}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setAssistantLiveError(message);
|
||||
setAssistantLiveStatus("Ошибка ассистента");
|
||||
log(`Live-чат ассистента: ошибка отправки сообщения: ${message}`);
|
||||
} finally {
|
||||
stopTicker();
|
||||
setAssistantLiveBusy(false);
|
||||
}
|
||||
}, [
|
||||
assistantLiveInput,
|
||||
assistantLiveSessionId,
|
||||
assistantLiveUseMock,
|
||||
assistantPromptVersion,
|
||||
connection,
|
||||
log,
|
||||
prompts
|
||||
]);
|
||||
|
||||
const commitLimitInput = useCallback(
|
||||
(raw: string) => {
|
||||
const normalized = raw.trim();
|
||||
@@ -933,6 +1025,7 @@ export function AutoRunsHistoryPanel({
|
||||
}
|
||||
|
||||
const useMockForRun = filters.useMock === "true";
|
||||
const effectiveAnalysisDate = normalizeAnalysisDateInput(analysisDate);
|
||||
const payload = await apiClient.startEvalRunAsync({
|
||||
connection,
|
||||
prompts,
|
||||
@@ -941,7 +1034,8 @@ export function AutoRunsHistoryPanel({
|
||||
caseSetFile: generation.saved_case_set_file ?? undefined,
|
||||
useMock: useMockForRun,
|
||||
evalTarget: "assistant_stage1",
|
||||
questions: questionsForRun
|
||||
questions: questionsForRun,
|
||||
analysisDate: effectiveAnalysisDate || undefined
|
||||
});
|
||||
|
||||
const liveJob = payload.job;
|
||||
@@ -955,7 +1049,8 @@ export function AutoRunsHistoryPanel({
|
||||
|
||||
log(
|
||||
`Запущен async-прогон job=${liveJob.job_id}, run_id=${liveJob.run_id}, вопросов=${questionsForRun.length}` +
|
||||
(generation.saved_case_set_file ? `, base_case_set=${generation.saved_case_set_file}` : "")
|
||||
(generation.saved_case_set_file ? `, base_case_set=${generation.saved_case_set_file}` : "") +
|
||||
(effectiveAnalysisDate ? `, analysis_date=${effectiveAnalysisDate}` : ", analysis_date=current_state")
|
||||
);
|
||||
void pollAsyncJobStatus(liveJob.job_id);
|
||||
} catch (error) {
|
||||
@@ -965,6 +1060,7 @@ export function AutoRunsHistoryPanel({
|
||||
setAutogenRunBusy(false);
|
||||
}
|
||||
}, [
|
||||
analysisDate,
|
||||
assistantPromptVersion,
|
||||
connection,
|
||||
editableGeneratedQuestions,
|
||||
@@ -1235,6 +1331,9 @@ export function AutoRunsHistoryPanel({
|
||||
limit: typeof savedFilters.limit === "number" ? Math.max(1, Math.min(500, savedFilters.limit)) : prev.limit
|
||||
}));
|
||||
}
|
||||
if (typeof parsed.analysisDate === "string") {
|
||||
setAnalysisDate(normalizeAnalysisDateInput(parsed.analysisDate));
|
||||
}
|
||||
if (parsed.autoGenSettings) {
|
||||
setAutoGenSettings((prev) => {
|
||||
const nextPrompts: Record<string, string> = {
|
||||
@@ -1290,6 +1389,7 @@ export function AutoRunsHistoryPanel({
|
||||
const saveUiConfig = useCallback(() => {
|
||||
const payload: AutoRunsUiConfig = {
|
||||
filters,
|
||||
analysisDate,
|
||||
autoGenSettings: {
|
||||
mode: autoGenSettings.mode,
|
||||
count: autoGenSettings.count,
|
||||
@@ -1302,7 +1402,7 @@ export function AutoRunsHistoryPanel({
|
||||
hideResolvedAnnotations
|
||||
};
|
||||
localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload));
|
||||
}, [annotationDecisionFilter, autoGenSettings, filters, hideResolvedAnnotations]);
|
||||
}, [analysisDate, annotationDecisionFilter, autoGenSettings, filters, hideResolvedAnnotations]);
|
||||
|
||||
useEffect(() => {
|
||||
const onSave = () => {
|
||||
@@ -1542,6 +1642,25 @@ export function AutoRunsHistoryPanel({
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="autoruns-form-grid">
|
||||
<label>
|
||||
Дата анализа (срез)
|
||||
<input
|
||||
type="date"
|
||||
value={analysisDate}
|
||||
onChange={(event) => setAnalysisDate(normalizeAnalysisDateInput(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<div className="button-row">
|
||||
<button type="button" className="tab" disabled={!analysisDate} onClick={() => setAnalysisDate("")}>
|
||||
Сбросить дату среза
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="muted">
|
||||
Если дата среза задана, автопрогон анализирует данные на эту дату. Если поле пустое, используется текущее состояние.
|
||||
</p>
|
||||
|
||||
<div className="button-row">
|
||||
<button type="button" disabled={autoGenBusy} onClick={() => void generateAutogenBatch()}>
|
||||
{autoGenBusy ? "Генерирую..." : "Сгенерировать пачку"}
|
||||
@@ -1704,6 +1823,7 @@ export function AutoRunsHistoryPanel({
|
||||
<div className="autoruns-run-meta">
|
||||
режим={run.mode ?? "нет данных"} | mock={String(run.use_mock)}
|
||||
</div>
|
||||
<div className="autoruns-run-meta">analysis_date={run.analysis_date ?? "current_state"}</div>
|
||||
{run.llm_provider || run.model ? (
|
||||
<div className="autoruns-run-meta">
|
||||
llm={run.llm_provider ?? "нет данных"} | модель={run.model ?? "нет данных"}
|
||||
@@ -1879,39 +1999,21 @@ export function AutoRunsHistoryPanel({
|
||||
</section>
|
||||
|
||||
{showAssistantMode ? (
|
||||
<section className="autoruns-col">
|
||||
<div className="autoruns-col-header">
|
||||
<h3>Режим ассистента</h3>
|
||||
</div>
|
||||
<div className="autoruns-meta-list">
|
||||
<div>
|
||||
<span>источник:</span>
|
||||
<strong>{dialog?.source ?? "нет данных"}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>сессия:</span>
|
||||
<strong>{dialog?.session_id ?? "нет данных"}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>контур прогона:</span>
|
||||
<strong>{activeRunSummary?.eval_target ?? "нет данных"}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>оценка прогона:</span>
|
||||
<strong>{formatScore(activeRunSummary?.score_index ?? null)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>комментарии:</span>
|
||||
<strong>{runDetail?.annotations_summary?.total ?? 0}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<h4>Пакет режима ассистента</h4>
|
||||
<JsonView value={dialog?.assistant_mode ?? { note: "assistant_mode недоступен" }} />
|
||||
<h4 style={{ marginTop: 12 }}>Проверки кейса</h4>
|
||||
<JsonView value={activeCase?.checks ?? { note: "checks недоступен" }} />
|
||||
<h4 style={{ marginTop: 12 }}>Сабскор метрик</h4>
|
||||
<JsonView value={activeCase?.metric_subscores ?? { note: "metric_subscores недоступен" }} />
|
||||
</section>
|
||||
<div className="autoruns-col autoruns-assistant-live-col">
|
||||
<AssistantPanel
|
||||
sessionId={assistantLiveSessionId}
|
||||
conversation={assistantLiveConversation}
|
||||
inputValue={assistantLiveInput}
|
||||
onInputChange={setAssistantLiveInput}
|
||||
useMock={assistantLiveUseMock}
|
||||
onUseMockChange={setAssistantLiveUseMock}
|
||||
onSend={sendAssistantLiveMessage}
|
||||
onClear={resetAssistantLiveSession}
|
||||
busy={assistantLiveBusy}
|
||||
statusText={assistantLiveStatus}
|
||||
errorMessage={assistantLiveError}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showDecompositionMode ? (
|
||||
|
||||
Reference in New Issue
Block a user