Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 174 additions & 16 deletions apps/desktop/src/components/editor/AiAssistant.vue

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,50 @@ describe("AI assistant clear/switch cancels an in-flight request (issue #5941, P
const sessionResetIdx = finallyBody.indexOf('currentSessionId.value = "";');
expect(isGeneratingIdx).toBeGreaterThan(guardIdx);
expect(sessionResetIdx).toBeGreaterThan(guardIdx);
// Issue #6743 feature 1 dual-path reset: the normal-completion path must stop
// the 1s status timer and clear the generation-status ref inside the guarded
// finally (the abandon path clears it via resetPendingRequestState()).
expect(finallyBody.indexOf("stopStatusTimer();")).toBeGreaterThan(guardIdx);
expect(finallyBody.indexOf("generationStatus.value = createGenerationStatus(Date.now());")).toBeGreaterThan(guardIdx);
});

it("the generation-status line exposes a screen-reader live region (role=status) that excludes ticking numerals", () => {
// Issue #6743 feature 1 a11y: async execution-state updates must be
// announced. The status block (data-ai-generation-status) must contain a
// role="status" live region fed by `statusLiveAnnouncement` — which, unlike
// the visible `statusText`, omits the per-second elapsed/idle numerals so a
// screen reader hears discrete state changes, not a timer ticking every 1s.
const statusBlockStart = source.indexOf("data-ai-generation-status");
expect(statusBlockStart).toBeGreaterThanOrEqual(0);
const block = source.slice(statusBlockStart);
expect(block).toContain('role="status"');
expect(block).toContain('aria-live="polite"');
expect(block).toContain('aria-atomic="true"');
expect(block).toContain("statusLiveAnnouncement");
expect(block).toContain('class="sr-only"');
});

it("the generation-status line hides once the generation is finished (agent_end) even before isGenerating clears", () => {
// Issue #6743 fix: agent_end/error arrive via the event callback before
// runAgentStream()'s promise resolves (CLI teardown / SSE close can take
// seconds), so the status line must ALSO be gated on phase !== 'finished' —
// otherwise it lingers below the completed reply showing a reset "0s".
const statusLineIdx = source.indexOf("data-ai-generation-status");
expect(statusLineIdx).toBeGreaterThanOrEqual(0);
const lineStart = source.lastIndexOf("\n", statusLineIdx) + 1;
const lineEnd = source.indexOf("\n", statusLineIdx);
const openingTag = source.slice(lineStart, lineEnd);
expect(openingTag).toContain("generationStatus.phase !== 'finished'");
expect(openingTag).toContain("data-ai-generation-status");
});

it("the >60s long-running hint is hidden once the generation is finished", () => {
// Fix keeps startedAt on the finished phase, so the hint must not reappear
// under a completed reply during the isGenerating-still-true gap.
const idx = source.indexOf("const statusLongRunningHintVisible");
expect(idx).toBeGreaterThanOrEqual(0);
const line = source.slice(idx, source.indexOf("\n", idx));
expect(line).toContain('"finished"');
});

// The three gaps below were called out on review of PR #6332: the generation guard
Expand Down Expand Up @@ -224,6 +268,12 @@ describe("AI assistant clear/switch cancels an in-flight request (issue #5941, P
// compaction summary into the NEW conversation's transcript in its finally
// block.
expect(resetBody).toContain("pendingCompaction.value = null;");
// Issue #6743 feature 1: the live generation-status line is per-request
// transient state too — the 1s status timer and the status ref must be reset
// here, otherwise switching conversations leaks a stale status line (and a
// running interval) into the next generation.
expect(resetBody).toContain("stopStatusTimer();");
expect(resetBody).toContain("generationStatus.value = createGenerationStatus(Date.now());");

const abandonBody = bodyOf("function abandonInFlightRequest(alreadyCancelledSessionId?: string)");
expect(abandonBody).toContain("resetPendingRequestState();");
Expand All @@ -239,4 +289,29 @@ describe("AI assistant clear/switch cancels an in-flight request (issue #5941, P
expect(body).toContain("if (isGenerating.value) abandonInFlightRequest();");
expect(body).not.toContain("cancelStream();");
});

it("agent step cards render a running-tool tail and a computed duration tail", () => {
// Issue #6743 (feature-1 gap): per-tool execution time in the agent step cards —
// mockup shows a spinner + "执行中…" tail on running steps and `0.8s`/`1.2s` on
// completed steps. The step-row template must special-case running tool steps
// (spinner icon + executing tail) and completed tool steps (tabular duration).
const stepsStart = source.indexOf('v-for="step in msg.agentSteps"');
expect(stepsStart, "expected to find the agent-steps v-for in AiAssistant.vue").toBeGreaterThanOrEqual(0);
const stepsBlock = source.slice(stepsStart);
// Running tool step: spinner leading icon + right-aligned "executing…" tail.
expect(stepsBlock).toContain("step.tone === 'active' && step.toolName");
expect(stepsBlock).toContain('t("ai.agentSteps.executing")');
// Completed tool step: right-aligned tabular duration tail.
expect(stepsBlock).toContain("formatToolDurationMs(step.durationMs)");
});

it("the status-line idle branch swaps Clock for Hourglass (mockup alignment)", () => {
// Mockup: idle state shows a non-spinning hourglass (spinner animation stops);
// only the >60s hint below keeps the Clock. The swap must live in the status
// line's spinner/clock slot, not touch the hint.
const statusLineIdx = source.indexOf("data-ai-generation-status");
expect(statusLineIdx).toBeGreaterThanOrEqual(0);
const statusBlock = source.slice(statusLineIdx);
expect(statusBlock).toContain("<Hourglass v-else");
});
});
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2514,6 +2514,7 @@ export default {
notRequested: "Run not requested",
skipped: "Not run",
callingTool: "Calling tool...",
executing: "Executing…",
toolDone: "Tool completed",
toolError: "Tool error",
executeSafe: "Safety check passed · Executed",
Expand All @@ -2530,6 +2531,32 @@ export default {
notRequested: "The user did not ask to run the SQL.",
skipped: "This step was not run.",
},
status: {
waitingModel: "Waiting for model response · Running {elapsed}s",
waitingModelLive: "Waiting for model response",
generating: "Generating reply · Running {elapsed}s",
generatingLive: "Generating reply",
runningTool: "Turn {turn} · Running {tool} · {elapsed}s",
runningToolAction: "· Running",
runningToolElapsed: "· {elapsed}s",
turnBadge: "Turn {turn}",
idle: "Waiting for this step · Last activity {idle}s ago",
idleLive: "Waiting for this step",
idleWithTool: "Waiting for this step · Last activity {idle}s ago · Running {tool}",
longRunningHint: "This is taking longer than expected. You can keep waiting or stop.",
cancelling: "Cancelling…",
toolLabels: {
executeQuery: "Execute query",
executeSql: "Execute SQL",
listTables: "List tables",
getColumns: "Get columns",
getCurrentTime: "Get current time",
explainQuery: "Explain query plan",
getSampleData: "Get sample data",
listCollections: "List collections",
browseCollection: "Browse collection",
},
},
proxy: "Proxy",
proxyEnable: "Send AI requests through proxy",
proxyUrl: "Proxy URL",
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2595,6 +2595,7 @@ export default withEnglishFallback({
notRequested: "No se solicitó ejecución",
skipped: "No ejecutado",
callingTool: "Llamando herramienta...",
executing: "Ejecutando…",
toolDone: "Herramienta completada",
toolError: "Error de herramienta",
executeSafe: "Verificación de seguridad superada · Ejecutado",
Expand All @@ -2611,6 +2612,32 @@ export default withEnglishFallback({
notRequested: "El usuario no solicitó ejecutar el SQL.",
skipped: "Este paso no se ejecutó.",
},
status: {
waitingModel: "Esperando respuesta del modelo · En ejecución {elapsed}s",
waitingModelLive: "Esperando respuesta del modelo",
generating: "Generando respuesta · En ejecución {elapsed}s",
generatingLive: "Generando respuesta",
runningTool: "Turno {turn} · Ejecutando {tool} · {elapsed}s",
runningToolAction: "· Ejecutando",
runningToolElapsed: "· {elapsed}s",
turnBadge: "Turno {turn}",
idle: "Esperando este paso · Última actividad hace {idle}s",
idleLive: "Esperando este paso",
idleWithTool: "Esperando este paso · Última actividad hace {idle}s · Ejecutando {tool}",
longRunningHint: "Esto está tardando más de lo esperado. Puedes seguir esperando o detenerte.",
cancelling: "Cancelando…",
toolLabels: {
executeQuery: "Ejecutar consulta",
executeSql: "Ejecutar SQL",
listTables: "Listar tablas",
getColumns: "Obtener columnas",
getCurrentTime: "Obtener hora actual",
explainQuery: "Explicar plan de ejecución",
getSampleData: "Obtener datos de muestra",
listCollections: "Listar colecciones",
browseCollection: "Explorar colecciones",
},
},
proxy: "Proxy",
proxyEnable: "Enviar solicitudes de IA mediante proxy",
proxyUrl: "URL del proxy",
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2489,6 +2489,7 @@ export default withEnglishFallback({
notRequested: "Esecuzione non richiesta",
skipped: "Non eseguito",
callingTool: "Chiamata strumento...",
executing: "In esecuzione…",
toolDone: "Strumento completato",
toolError: "Errore strumento",
executeSafe: "Controllo di sicurezza superato · Eseguito",
Expand All @@ -2505,6 +2506,32 @@ export default withEnglishFallback({
notRequested: "L'utente non ha richiesto l'esecuzione dell'SQL.",
skipped: "Questo passaggio non è stato eseguito.",
},
status: {
waitingModel: "In attesa della risposta del modello · In esecuzione da {elapsed}s",
waitingModelLive: "In attesa della risposta del modello",
generating: "Generazione della risposta · In esecuzione da {elapsed}s",
generatingLive: "Generazione della risposta",
runningTool: "Turno {turn} · Esecuzione di {tool} · {elapsed}s",
runningToolAction: "· Esecuzione",
runningToolElapsed: "· {elapsed}s",
turnBadge: "Turno {turn}",
idle: "In attesa di questo passaggio · Ultima attività {idle}s fa",
idleLive: "In attesa di questo passaggio",
idleWithTool: "In attesa di questo passaggio · Ultima attività {idle}s fa · Esecuzione di {tool}",
longRunningHint: "L'operazione sta richiedendo più tempo del previsto. Puoi continuare ad attendere o interrompere.",
cancelling: "Annullamento…",
toolLabels: {
executeQuery: "Esegui query",
executeSql: "Esegui SQL",
listTables: "Elenca tabelle",
getColumns: "Ottieni colonne",
getCurrentTime: "Ottieni ora corrente",
explainQuery: "Spiega piano di esecuzione",
getSampleData: "Ottieni dati di esempio",
listCollections: "Elenca collezioni",
browseCollection: "Sfoglia collezioni",
},
},
proxy: "Proxy",
proxyEnable: "Invia richieste AI tramite proxy",
proxyUrl: "URL Proxy",
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2629,6 +2629,7 @@ export default withEnglishFallback({
notRequested: "実行は要求されていません",
skipped: "未実行",
callingTool: "ツールを呼び出し中...",
executing: "実行中…",
toolDone: "ツール完了",
toolError: "ツールエラー",
executeSafe: "安全チェック通過 · 実行済み",
Expand All @@ -2645,6 +2646,32 @@ export default withEnglishFallback({
notRequested: "ユーザーがSQLの実行を要求しませんでした。",
skipped: "このステップは実行されませんでした。",
},
status: {
waitingModel: "モデルの応答を待機中 · 実行時間 {elapsed}s",
waitingModelLive: "モデルの応答を待機中",
generating: "返信を生成中 · 実行時間 {elapsed}s",
generatingLive: "返信を生成中",
runningTool: "{turn} ラウンド目 · {tool} を実行中 · 実行時間 {elapsed}s",
runningToolAction: "· 実行中",
runningToolElapsed: "· 実行時間 {elapsed}s",
turnBadge: "{turn} ラウンド目",
idle: "このステップの完了を待機中 · 最後のアクティビティ {idle}s 前",
idleLive: "このステップの完了を待機中",
idleWithTool: "このステップの完了を待機中 · 最後のアクティビティ {idle}s 前 · {tool} を実行中",
longRunningHint: "通常より時間がかかっています。このまま待つか停止できます。",
cancelling: "キャンセル中…",
toolLabels: {
executeQuery: "クエリを実行",
executeSql: "SQL を実行",
listTables: "テーブル一覧",
getColumns: "カラムを取得",
getCurrentTime: "現在時刻を取得",
explainQuery: "実行計画を説明",
getSampleData: "サンプルデータを取得",
listCollections: "コレクション一覧",
browseCollection: "コレクションを閲覧",
},
},
proxy: "プロキシ",
proxyEnable: "プロキシ経由でAIリクエストを送信",
proxyUrl: "プロキシURL",
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,7 @@ export default withEnglishFallback({
notRequested: "실행 요청되지 않음",
skipped: "실행되지 않음",
callingTool: "도구 호출 중...",
executing: "실행 중…",
toolDone: "도구 완료",
toolError: "도구 오류",
executeSafe: "안전성 검사 통과 · 실행됨",
Expand All @@ -2378,6 +2379,32 @@ export default withEnglishFallback({
notRequested: "사용자가 SQL 실행을 요청하지 않았습니다.",
skipped: "이 단계는 실행되지 않았습니다.",
},
status: {
waitingModel: "모델 응답 대기 중 · 실행 {elapsed}s",
waitingModelLive: "모델 응답 대기 중",
generating: "답변 생성 중 · 실행 {elapsed}s",
generatingLive: "답변 생성 중",
runningTool: "{turn}라운드 · {tool} 실행 중 · 실행 {elapsed}s",
runningToolAction: "· 실행 중",
runningToolElapsed: "· 실행 {elapsed}s",
turnBadge: "{turn}라운드",
idle: "이 단계 완료 대기 중 · 마지막 활동 {idle}s 전",
idleLive: "이 단계 완료 대기 중",
idleWithTool: "이 단계 완료 대기 중 · 마지막 활동 {idle}s 전 · {tool} 실행 중",
longRunningHint: "예상보다 오래 걸리고 있습니다. 계속 기다리거나 중지할 수 있습니다.",
cancelling: "취소 중…",
toolLabels: {
executeQuery: "쿼리 실행",
executeSql: "SQL 실행",
listTables: "테이블 나열",
getColumns: "컬럼 가져오기",
getCurrentTime: "현재 시간 가져오기",
explainQuery: "실행 계획 설명",
getSampleData: "샘플 데이터 가져오기",
listCollections: "컬렉션 나열",
browseCollection: "컬렉션 탐색",
},
},
proxy: "프록시",
proxyEnable: "프록시를 통해 AI 요청 전송",
proxyUrl: "프록시 URL",
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2597,6 +2597,7 @@ export default withEnglishFallback({
notRequested: "Execução não solicitada",
skipped: "Não executado",
callingTool: "Chamando ferramenta...",
executing: "Executando…",
toolDone: "Ferramenta concluída",
toolError: "Erro da ferramenta",
executeSafe: "Verificação de segurança aprovada · Executado",
Expand All @@ -2613,6 +2614,32 @@ export default withEnglishFallback({
notRequested: "O usuário não solicitou a execução do SQL.",
skipped: "Esta etapa não foi executada.",
},
status: {
waitingModel: "Aguardando resposta do modelo · Em execução há {elapsed}s",
waitingModelLive: "Aguardando resposta do modelo",
generating: "Gerando resposta · Em execução há {elapsed}s",
generatingLive: "Gerando resposta",
runningTool: "Turno {turn} · Executando {tool} · {elapsed}s",
runningToolAction: "· Executando",
runningToolElapsed: "· {elapsed}s",
turnBadge: "Turno {turn}",
idle: "Aguardando esta etapa · Última atividade há {idle}s",
idleLive: "Aguardando esta etapa",
idleWithTool: "Aguardando esta etapa · Última atividade há {idle}s · Executando {tool}",
longRunningHint: "Isso está demorando mais que o esperado. Você pode continuar aguardando ou parar.",
cancelling: "Cancelando…",
toolLabels: {
executeQuery: "Executar consulta",
executeSql: "Executar SQL",
listTables: "Listar tabelas",
getColumns: "Obter colunas",
getCurrentTime: "Obter hora atual",
explainQuery: "Explicar plano de execução",
getSampleData: "Obter dados de amostra",
listCollections: "Listar coleções",
browseCollection: "Navegar coleções",
},
},
proxy: "Proxy",
proxyEnable: "Enviar requisições de AI através do proxy",
proxyUrl: "URL do Proxy",
Expand Down
27 changes: 27 additions & 0 deletions apps/desktop/src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2438,6 +2438,7 @@ export default withEnglishFallback({
notRequested: "未请求执行",
skipped: "未执行",
callingTool: "正在调用工具...",
executing: "执行中…",
toolDone: "工具调用完成",
toolError: "工具调用出错",
executeSafe: "安全检查通过 · 已执行",
Expand All @@ -2454,6 +2455,32 @@ export default withEnglishFallback({
notRequested: "用户没有表达执行意图。",
skipped: "此步骤未执行。",
},
status: {
waitingModel: "等待模型响应 · 已运行 {elapsed}s",
waitingModelLive: "等待模型响应",
generating: "正在生成回复 · 已运行 {elapsed}s",
generatingLive: "正在生成回复",
runningTool: "第 {turn} 轮 · 正在执行 {tool} · 已运行 {elapsed}s",
runningToolAction: "· 正在执行",
runningToolElapsed: "· 已运行 {elapsed}s",
turnBadge: "第 {turn} 轮",
idle: "等待此步骤完成 · 最后活动 {idle}s 前",
idleLive: "等待此步骤完成",
idleWithTool: "等待此步骤完成 · 最后活动 {idle}s 前 · 正在执行 {tool}",
longRunningHint: "响应时间较长,可继续等待或停止",
cancelling: "正在取消…",
toolLabels: {
executeQuery: "执行查询",
executeSql: "执行 SQL",
listTables: "列出数据表",
getColumns: "读取字段",
getCurrentTime: "获取当前时间",
explainQuery: "解释查询计划",
getSampleData: "获取样例数据",
listCollections: "列出集合",
browseCollection: "浏览集合",
},
},
proxy: "代理",
proxyEnable: "AI 请求通过代理发送",
proxyUrl: "代理地址",
Expand Down
Loading
Loading