Merge pull request 'feat: add orchestration state progress indicator' (#28) from feature/issue-8-progress-indicator into main
This commit was merged in pull request #28.
This commit is contained in:
@@ -9,3 +9,4 @@
|
||||
| #5 | Chat page layout and message list component | COMPLETED | [issue-005.md](issue-005.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) |
|
||||
| #8 | Orchestration state progress indicator | COMPLETED | [issue-008.md](issue-008.md) |
|
||||
|
||||
16
implementation-plans/issue-008.md
Normal file
16
implementation-plans/issue-008.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
---
|
||||
|
||||
# Issue #8: Orchestration state progress indicator
|
||||
|
||||
**Status:** COMPLETED
|
||||
**Issue:** https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/8
|
||||
**Branch:** `feature/issue-8-progress-indicator`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Stepper/progress bar component showing orchestration phases
|
||||
- [x] Phases: Decomposing → Dispatching → Executing → Compacting → Complete
|
||||
- [x] Updates in real-time as OrchestrationState changes in the stream
|
||||
- [x] Current phase visually highlighted, completed phases marked
|
||||
- [x] Smooth transitions between states
|
||||
56
src/lib/components/OrchestrationProgress.svelte
Normal file
56
src/lib/components/OrchestrationProgress.svelte
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { OrchestrationState } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||
|
||||
let { state }: { state: OrchestrationState } = $props();
|
||||
|
||||
const phases = [
|
||||
{ state: OrchestrationState.DECOMPOSING, label: 'Decomposing' },
|
||||
{ state: OrchestrationState.DISPATCHING, label: 'Dispatching' },
|
||||
{ state: OrchestrationState.EXECUTING, label: 'Executing' },
|
||||
{ state: OrchestrationState.COMPACTING, label: 'Compacting' },
|
||||
{ state: OrchestrationState.COMPLETE, label: 'Complete' }
|
||||
];
|
||||
|
||||
function getStatus(phaseState: OrchestrationState): 'completed' | 'active' | 'pending' {
|
||||
if (state === OrchestrationState.UNSPECIFIED) return 'pending';
|
||||
if (phaseState < state) return 'completed';
|
||||
if (phaseState === state) return 'active';
|
||||
return 'pending';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-4 mb-2 rounded-xl bg-gray-50 px-4 py-3">
|
||||
<div class="flex items-center justify-between">
|
||||
{#each phases as phase, i (phase.state)}
|
||||
{@const status = getStatus(phase.state)}
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium transition-all duration-300
|
||||
{status === 'completed'
|
||||
? 'bg-green-500 text-white'
|
||||
: status === 'active'
|
||||
? 'bg-blue-500 text-white ring-2 ring-blue-300 ring-offset-1'
|
||||
: 'bg-gray-200 text-gray-500'}"
|
||||
>
|
||||
{#if status === 'completed'}
|
||||
✓
|
||||
{:else}
|
||||
{i + 1}
|
||||
{/if}
|
||||
</div>
|
||||
<span
|
||||
class="text-xs transition-colors duration-300
|
||||
{status === 'active' ? 'font-medium text-blue-600' : 'text-gray-500'}"
|
||||
>
|
||||
{phase.label}
|
||||
</span>
|
||||
</div>
|
||||
{#if i < phases.length - 1}
|
||||
<div
|
||||
class="mb-5 h-0.5 flex-1 transition-colors duration-300
|
||||
{getStatus(phases[i + 1].state) !== 'pending' ? 'bg-green-500' : 'bg-gray-200'}"
|
||||
></div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,15 +2,19 @@
|
||||
import type { ChatMessage } from '$lib/types';
|
||||
import MessageList from '$lib/components/MessageList.svelte';
|
||||
import MessageInput from '$lib/components/MessageInput.svelte';
|
||||
import OrchestrationProgress from '$lib/components/OrchestrationProgress.svelte';
|
||||
import { processRequest, OrchestratorError } from '$lib/services/orchestrator';
|
||||
import { OrchestrationState } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||
|
||||
let messages: ChatMessage[] = $state([]);
|
||||
let isStreaming = $state(false);
|
||||
let error: string | null = $state(null);
|
||||
let sessionId = $state(crypto.randomUUID());
|
||||
let orchestrationState: OrchestrationState = $state(OrchestrationState.UNSPECIFIED);
|
||||
|
||||
async function handleSend(content: string) {
|
||||
error = null;
|
||||
orchestrationState = OrchestrationState.UNSPECIFIED;
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
@@ -32,6 +36,7 @@
|
||||
|
||||
try {
|
||||
for await (const response of processRequest(sessionId, content)) {
|
||||
orchestrationState = response.state;
|
||||
const idx = messages.length - 1;
|
||||
messages[idx] = {
|
||||
...messages[idx],
|
||||
@@ -44,7 +49,6 @@
|
||||
? `Error (${err.code}): ${err.message}`
|
||||
: 'An unexpected error occurred';
|
||||
error = msg;
|
||||
// Update the assistant message with the error
|
||||
const idx = messages.length - 1;
|
||||
messages[idx] = {
|
||||
...messages[idx],
|
||||
@@ -63,6 +67,10 @@
|
||||
|
||||
<MessageList {messages} />
|
||||
|
||||
{#if isStreaming}
|
||||
<OrchestrationProgress state={orchestrationState} />
|
||||
{/if}
|
||||
|
||||
{#if isStreaming && messages.length > 0 && messages[messages.length - 1].content === ''}
|
||||
<div class="flex justify-start px-4 pb-2">
|
||||
<div class="flex items-center gap-1.5 rounded-2xl bg-gray-200 px-4 py-2.5">
|
||||
|
||||
Reference in New Issue
Block a user