feat: add collapsible thinking section for intermediate results #29
@@ -10,3 +10,4 @@
|
|||||||
| #6 | Message input with send and keyboard shortcuts | COMPLETED | [issue-006.md](issue-006.md) |
|
| #6 | Message input with send and keyboard shortcuts | COMPLETED | [issue-006.md](issue-006.md) |
|
||||||
| #7 | Streaming response rendering | COMPLETED | [issue-007.md](issue-007.md) |
|
| #7 | Streaming response rendering | COMPLETED | [issue-007.md](issue-007.md) |
|
||||||
| #8 | Orchestration state progress indicator | COMPLETED | [issue-008.md](issue-008.md) |
|
| #8 | Orchestration state progress indicator | COMPLETED | [issue-008.md](issue-008.md) |
|
||||||
|
| #9 | Intermediate results display | COMPLETED | [issue-009.md](issue-009.md) |
|
||||||
|
|||||||
17
implementation-plans/issue-009.md
Normal file
17
implementation-plans/issue-009.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
# Issue #9: Intermediate results display
|
||||||
|
|
||||||
|
**Status:** COMPLETED
|
||||||
|
**Issue:** https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/9
|
||||||
|
**Branch:** `feature/issue-9-intermediate-results`
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [x] Collapsible "thinking" section component
|
||||||
|
- [x] Displays intermediate_result content when present in stream
|
||||||
|
- [x] Positioned below the orchestration progress indicator
|
||||||
|
- [x] Collapsed by default, expandable on click
|
||||||
|
- [x] Updates as new intermediate results arrive
|
||||||
|
- [x] Visually distinct from final results (muted amber styling)
|
||||||
31
src/lib/components/ThinkingSection.svelte
Normal file
31
src/lib/components/ThinkingSection.svelte
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { content }: { content: string } = $props();
|
||||||
|
|
||||||
|
let expanded = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if content}
|
||||||
|
<div class="mx-4 mb-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (expanded = !expanded)}
|
||||||
|
class="flex w-full items-center gap-2 rounded-lg bg-amber-50 px-3 py-2 text-left text-sm text-amber-700 hover:bg-amber-100 transition-colors"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="inline-block transition-transform duration-200 {expanded
|
||||||
|
? 'rotate-90'
|
||||||
|
: 'rotate-0'}"
|
||||||
|
>
|
||||||
|
▶
|
||||||
|
</span>
|
||||||
|
<span class="font-medium">Thinking...</span>
|
||||||
|
</button>
|
||||||
|
{#if expanded}
|
||||||
|
<div
|
||||||
|
class="mt-1 rounded-b-lg border border-t-0 border-amber-200 bg-amber-50/50 px-4 py-3 text-sm text-amber-800 whitespace-pre-wrap"
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import MessageList from '$lib/components/MessageList.svelte';
|
import MessageList from '$lib/components/MessageList.svelte';
|
||||||
import MessageInput from '$lib/components/MessageInput.svelte';
|
import MessageInput from '$lib/components/MessageInput.svelte';
|
||||||
import OrchestrationProgress from '$lib/components/OrchestrationProgress.svelte';
|
import OrchestrationProgress from '$lib/components/OrchestrationProgress.svelte';
|
||||||
|
import ThinkingSection from '$lib/components/ThinkingSection.svelte';
|
||||||
import { processRequest, OrchestratorError } from '$lib/services/orchestrator';
|
import { processRequest, OrchestratorError } from '$lib/services/orchestrator';
|
||||||
import { OrchestrationState } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
import { OrchestrationState } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||||
|
|
||||||
@@ -11,10 +12,12 @@
|
|||||||
let error: string | null = $state(null);
|
let error: string | null = $state(null);
|
||||||
let sessionId = $state(crypto.randomUUID());
|
let sessionId = $state(crypto.randomUUID());
|
||||||
let orchestrationState: OrchestrationState = $state(OrchestrationState.UNSPECIFIED);
|
let orchestrationState: OrchestrationState = $state(OrchestrationState.UNSPECIFIED);
|
||||||
|
let intermediateResult: string = $state('');
|
||||||
|
|
||||||
async function handleSend(content: string) {
|
async function handleSend(content: string) {
|
||||||
error = null;
|
error = null;
|
||||||
orchestrationState = OrchestrationState.UNSPECIFIED;
|
orchestrationState = OrchestrationState.UNSPECIFIED;
|
||||||
|
intermediateResult = '';
|
||||||
|
|
||||||
const userMessage: ChatMessage = {
|
const userMessage: ChatMessage = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
@@ -37,6 +40,9 @@
|
|||||||
try {
|
try {
|
||||||
for await (const response of processRequest(sessionId, content)) {
|
for await (const response of processRequest(sessionId, content)) {
|
||||||
orchestrationState = response.state;
|
orchestrationState = response.state;
|
||||||
|
if (response.intermediateResult) {
|
||||||
|
intermediateResult = response.intermediateResult;
|
||||||
|
}
|
||||||
const idx = messages.length - 1;
|
const idx = messages.length - 1;
|
||||||
messages[idx] = {
|
messages[idx] = {
|
||||||
...messages[idx],
|
...messages[idx],
|
||||||
@@ -69,6 +75,7 @@
|
|||||||
|
|
||||||
{#if isStreaming}
|
{#if isStreaming}
|
||||||
<OrchestrationProgress state={orchestrationState} />
|
<OrchestrationProgress state={orchestrationState} />
|
||||||
|
<ThinkingSection content={intermediateResult} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isStreaming && messages.length > 0 && messages[messages.length - 1].content === ''}
|
{#if isStreaming && messages.length > 0 && messages[messages.length - 1].content === ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user