refactor: Refine Chat Message Processing State Display

This commit is contained in:
Aleksander Grygier 2026-01-28 18:28:03 +01:00
parent 6047da3f72
commit aa4fb786a2
6 changed files with 70 additions and 17 deletions

View File

@ -17,10 +17,16 @@
interface Props {
class?: string;
message: DatabaseMessage;
isLastAssistantMessage?: boolean;
siblingInfo?: ChatMessageSiblingInfo | null;
}
let { class: className = '', message, siblingInfo = null }: Props = $props();
let {
class: className = '',
message,
isLastAssistantMessage = false,
siblingInfo = null
}: Props = $props();
const chatActions = getChatActionsContext();
@ -278,6 +284,7 @@
bind:textareaElement
class={className}
{deletionInfo}
{isLastAssistantMessage}
{message}
messageContent={message.content}
onConfirmDelete={handleConfirmDelete}

View File

@ -67,7 +67,7 @@
{:else if section.type === AgenticSectionType.TOOL_CALL_STREAMING}
{@const streamingIcon = isStreaming ? Loader2 : AlertTriangle}
{@const streamingIconClass = isStreaming ? 'h-4 w-4 animate-spin' : 'h-4 w-4 text-yellow-500'}
{@const streamingSubtitle = isStreaming ? 'streaming...' : 'incomplete'}
{@const streamingSubtitle = isStreaming ? '' : 'incomplete'}
<CollapsibleContentBlock
open={isExpanded(index, section)}
@ -171,7 +171,7 @@
</CollapsibleContentBlock>
{:else if section.type === AgenticSectionType.REASONING_PENDING}
{@const reasoningTitle = isStreaming ? 'Reasoning...' : 'Reasoning'}
{@const reasoningSubtitle = isStreaming ? 'streaming...' : 'incomplete'}
{@const reasoningSubtitle = isStreaming ? '' : 'incomplete'}
<CollapsibleContentBlock
open={isExpanded(index, section)}

View File

@ -33,6 +33,7 @@
assistantMessages: number;
messageTypes: string[];
} | null;
isLastAssistantMessage?: boolean;
message: DatabaseMessage;
messageContent: string | undefined;
onCopy: () => void;
@ -51,6 +52,7 @@
let {
class: className = '',
deletionInfo,
isLastAssistantMessage = false,
message,
messageContent,
onConfirmDelete,
@ -100,6 +102,25 @@
let displayedModel = $derived(message.model ?? null);
let isCurrentlyLoading = $derived(isLoading());
let isStreaming = $derived(isChatStreaming());
let hasNoContent = $derived(!message?.content?.trim());
let isActivelyProcessing = $derived(isCurrentlyLoading || isStreaming);
let showProcessingInfoTop = $derived(
message?.role === MessageRole.ASSISTANT &&
isActivelyProcessing &&
hasNoContent &&
isLastAssistantMessage
);
let showProcessingInfoBottom = $derived(
message?.role === MessageRole.ASSISTANT &&
isActivelyProcessing &&
!hasNoContent &&
isLastAssistantMessage
);
function handleCopyModel() {
void copyToClipboard(displayedModel ?? '');
}
@ -111,7 +132,7 @@
});
$effect(() => {
if (isLoading() && !message?.content?.trim()) {
if (showProcessingInfoTop || showProcessingInfoBottom) {
processingState.startMonitoring();
}
});
@ -122,11 +143,13 @@
role="group"
aria-label="Assistant message with actions"
>
{#if message?.role === MessageRole.ASSISTANT && isLoading() && !message?.content?.trim()}
{#if showProcessingInfoTop}
<div class="mt-6 w-full max-w-[48rem]" in:fade>
<div class="processing-container">
<span class="processing-text">
{processingState.getPromptProgressText() ?? processingState.getProcessingMessage()}
{processingState.getPromptProgressText() ??
processingState.getProcessingMessage() ??
'Processing...'}
</span>
</div>
</div>
@ -193,7 +216,19 @@
</div>
{/if}
<div class="info my-6 grid gap-4">
{#if showProcessingInfoBottom}
<div class="mt-4 w-full max-w-[48rem]" in:fade>
<div class="processing-container">
<span class="processing-text">
{processingState.getPromptProgressText() ??
processingState.getProcessingMessage() ??
'Processing...'}
</span>
</div>
</div>
{/if}
<div class="info my-6 grid gap-4 tabular-nums">
{#if displayedModel}
<div class="inline-flex flex-wrap items-start gap-2 text-xs text-muted-foreground">
{#if isRouter}

View File

@ -100,16 +100,26 @@
return [];
}
// Filter out system messages if showSystemMessage is false
const filteredMessages = currentConfig.showSystemMessage
? messages
: messages.filter((msg) => msg.type !== MessageRole.SYSTEM);
return filteredMessages.map((message) => {
let lastAssistantIndex = -1;
for (let i = filteredMessages.length - 1; i >= 0; i--) {
if (filteredMessages[i].role === MessageRole.ASSISTANT) {
lastAssistantIndex = i;
break;
}
}
return filteredMessages.map((message, index) => {
const siblingInfo = getMessageSiblings(allConversationMessages, message.id);
const isLastAssistantMessage =
message.role === MessageRole.ASSISTANT && index === lastAssistantIndex;
return {
message,
isLastAssistantMessage,
siblingInfo: siblingInfo || {
message,
siblingIds: [message.id],
@ -122,7 +132,12 @@
</script>
<div class="flex h-full flex-col space-y-10 pt-16 md:pt-24 {className}" style="height: auto; ">
{#each displayMessages as { message, siblingInfo } (message.id)}
<ChatMessage class="mx-auto w-full max-w-[48rem]" {message} {siblingInfo} />
{#each displayMessages as { message, isLastAssistantMessage, siblingInfo } (message.id)}
<ChatMessage
class="mx-auto w-full max-w-[48rem]"
{message}
{isLastAssistantMessage}
{siblingInfo}
/>
{/each}
</div>

View File

@ -11,7 +11,7 @@
let isCurrentConversationLoading = $derived(isLoading());
let isStreaming = $derived(isChatStreaming());
let hasProcessingData = $derived(processingState.processingState !== null);
let processingDetails = $derived(processingState.getProcessingDetails());
let processingDetails = $derived(processingState.getTechnicalDetails());
let showProcessingInfo = $derived(
isCurrentConversationLoading || isStreaming || config().keepStatsVisible || hasProcessingData

View File

@ -197,11 +197,7 @@ export function useProcessingState(): UseProcessingStateReturn {
const details: string[] = [];
// Always show context info when we have valid data
if (
typeof stateToUse.contextTotal === 'number' &&
stateToUse.contextUsed >= 0 &&
stateToUse.contextTotal > 0
) {
if (stateToUse.contextUsed >= 0 && stateToUse.contextTotal > 0) {
const contextPercent = Math.round((stateToUse.contextUsed / stateToUse.contextTotal) * 100);
details.push(