diff --git a/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte b/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte index 3e57542dbf..a7070f9347 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte +++ b/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte @@ -5,6 +5,7 @@ ArrowDown, CheckIcon, HistoryIcon, + Hourglass, MousePointer2, Plus, TextSelect, @@ -14,13 +15,14 @@ import Button from '$lib/components/common/button/Button.svelte' import { fade } from 'svelte/transition' import Popover from '$lib/components/meltComponents/Popover.svelte' - import { type DisplayMessage } from './shared' + import { type DisplayMessage, type ToolDisplayMessage } from './shared' import type { ContextElement } from './context' import ChatQuickActions from './ChatQuickActions.svelte' import ProviderModelSelector from './ProviderModelSelector.svelte' import ChatMode from './ChatMode.svelte' import DatatableCreationPolicy from './DatatableCreationPolicy.svelte' import Markdown from 'svelte-exmarkdown' + import { twMerge } from 'tailwind-merge' import { AIMode } from './AIChatManager.svelte' import { getAiChatManager } from './aiChatManagerContext' import ChatTypingIndicator from './ChatTypingIndicator.svelte' @@ -165,6 +167,29 @@ const showTypingIndicator = $derived(aiChatManager.loading) + // "Waiting for user" detection — when the latest tool message is staged + // for confirmation or has an unanswered askUserQuestion, the AI loop is + // paused on the user, not on its own work. The typing-dots indicator + // implies the AI is busy, which is misleading; surface a text pill + // instead so users know to act on the tool above. + const waitingForUserAction = $derived.by(() => { + if (!aiChatManager.loading) return false + const last = messages[messages.length - 1] + if (!last || last.role !== 'tool') return false + const tm = last as ToolDisplayMessage + if (tm.needsConfirmation && tm.isLoading) return true + if ( + tm.userQuestion && + tm.isLoading && + !tm.error && + !tm.userQuestion.selectedChoice && + !tm.userQuestion.canceled + ) { + return true + } + return false + }) + // Get app context for display when in APP mode const appContext = $derived.by((): SelectedContext | undefined => { if (aiChatManager.mode !== AIMode.APP || !aiChatManager.appAiChatHelpers) { @@ -283,7 +308,17 @@ {/each} {#if showTypingIndicator}
- + {#if waitingForUserAction} + + + Waiting for your input + + {:else} + + {/if}
{/if} @@ -291,11 +326,14 @@ {#if showScrollToLatest}
+ +