Merge pull request 'feat: display inference statistics in chat UI' (#45) from feature/issue-43-inference-stats into main
Merge pull request 'feat: display inference statistics in chat UI' (#45)
This commit was merged in pull request #45.
This commit is contained in:
@@ -22,3 +22,4 @@
|
|||||||
| #18 | Dark/light theme toggle | COMPLETED | [issue-018.md](issue-018.md) |
|
| #18 | Dark/light theme toggle | COMPLETED | [issue-018.md](issue-018.md) |
|
||||||
| #19 | Responsive layout and mobile support | COMPLETED | [issue-019.md](issue-019.md) |
|
| #19 | Responsive layout and mobile support | COMPLETED | [issue-019.md](issue-019.md) |
|
||||||
| #20 | Error handling and connection status | COMPLETED | [issue-020.md](issue-020.md) |
|
| #20 | Error handling and connection status | COMPLETED | [issue-020.md](issue-020.md) |
|
||||||
|
| #43 | Display inference statistics in chat UI | COMPLETED | [issue-043.md](issue-043.md) |
|
||||||
|
|||||||
60
implementation-plans/issue-043.md
Normal file
60
implementation-plans/issue-043.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Issue #43: Display Inference Statistics in Chat UI
|
||||||
|
|
||||||
|
**Status:** COMPLETED
|
||||||
|
**Issue:** [#43](https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/43)
|
||||||
|
**Branch:** `feature/issue-43-inference-stats`
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add a collapsible UI panel that displays LLM inference statistics (token counts, context window utilization, throughput) below assistant messages after orchestration completes.
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
### Phase 1: Proto Types
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `proto/upstream/proto/llm_multiverse/v1/common.proto` — Add `InferenceStats` message
|
||||||
|
- `proto/upstream/proto/llm_multiverse/v1/orchestrator.proto` — Add optional `inference_stats` field to `ProcessRequestResponse`
|
||||||
|
|
||||||
|
**InferenceStats message fields:**
|
||||||
|
- `prompt_tokens` (uint32) — tokens in the prompt
|
||||||
|
- `completion_tokens` (uint32) — tokens generated
|
||||||
|
- `total_tokens` (uint32) — sum of prompt + completion
|
||||||
|
- `context_window_size` (uint32) — model's maximum context length
|
||||||
|
- `tokens_per_second` (float) — generation throughput
|
||||||
|
|
||||||
|
**Then regenerate types:** `npm run generate`
|
||||||
|
|
||||||
|
### Phase 2: Orchestration State
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `src/lib/composables/useOrchestration.svelte.ts` — Extract `inferenceStats` from response, expose via store getter
|
||||||
|
|
||||||
|
### Phase 3: InferenceStatsPanel Component
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `src/lib/components/InferenceStatsPanel.svelte` — New component
|
||||||
|
|
||||||
|
**Design:**
|
||||||
|
- Follow `<details>` pattern from FinalResult.svelte
|
||||||
|
- Collapsed by default
|
||||||
|
- Summary line shows key stat (e.g., total tokens + tokens/sec)
|
||||||
|
- Expanded content shows all stats in a grid layout
|
||||||
|
- Context utilization shown as a progress bar
|
||||||
|
- Blue/indigo color scheme (neutral, info-like)
|
||||||
|
- Full dark mode support
|
||||||
|
|
||||||
|
### Phase 4: Chat Page Integration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `src/routes/chat/+page.svelte` — Render `InferenceStatsPanel` after `FinalResult` when stats available
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [x] InferenceStats proto message defined and TypeScript types generated
|
||||||
|
- [x] InferenceStatsPanel displays all required metrics
|
||||||
|
- [x] Panel is collapsible, collapsed by default
|
||||||
|
- [x] Context utilization shows visual progress bar
|
||||||
|
- [x] Integrates cleanly into chat page below assistant message
|
||||||
|
- [x] Dark mode support
|
||||||
|
- [x] Build, lint, typecheck pass
|
||||||
94
src/lib/components/InferenceStatsPanel.svelte
Normal file
94
src/lib/components/InferenceStatsPanel.svelte
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { InferenceStats } from '$lib/proto/llm_multiverse/v1/common_pb';
|
||||||
|
|
||||||
|
let { stats }: { stats: InferenceStats } = $props();
|
||||||
|
|
||||||
|
const utilizationPct = $derived(
|
||||||
|
stats.contextWindowSize > 0
|
||||||
|
? Math.min(100, (stats.totalTokens / stats.contextWindowSize) * 100)
|
||||||
|
: 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const utilizationColor = $derived.by(() => {
|
||||||
|
if (utilizationPct >= 90) return { bar: 'bg-red-500', text: 'text-red-700 dark:text-red-400' };
|
||||||
|
if (utilizationPct >= 70) return { bar: 'bg-amber-500', text: 'text-amber-700 dark:text-amber-400' };
|
||||||
|
return { bar: 'bg-blue-500', text: 'text-blue-700 dark:text-blue-400' };
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatNumber(n: number): string {
|
||||||
|
return n.toLocaleString();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<details class="mx-4 mb-3 rounded-xl border border-indigo-200 dark:border-indigo-800 bg-indigo-50 dark:bg-indigo-900/20">
|
||||||
|
<summary class="flex cursor-pointer items-center gap-2 px-4 py-2.5 select-none">
|
||||||
|
<svg class="chevron h-4 w-4 shrink-0 text-gray-500 dark:text-gray-400 transition-transform" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /></svg>
|
||||||
|
<span class="rounded-full bg-indigo-100 dark:bg-indigo-900/40 px-2.5 py-0.5 text-xs font-medium text-indigo-800 dark:text-indigo-300">
|
||||||
|
Stats
|
||||||
|
</span>
|
||||||
|
<span class="truncate text-xs text-indigo-700 dark:text-indigo-400">
|
||||||
|
{formatNumber(stats.totalTokens)} tokens
|
||||||
|
{#if stats.tokensPerSecond > 0}
|
||||||
|
· {stats.tokensPerSecond.toFixed(1)} tok/s
|
||||||
|
{/if}
|
||||||
|
{#if stats.contextWindowSize > 0}
|
||||||
|
· {utilizationPct.toFixed(0)}% context
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<div class="border-t border-indigo-200 dark:border-indigo-800 px-4 pb-4 pt-3">
|
||||||
|
<div class="grid grid-cols-2 gap-x-6 gap-y-3 sm:grid-cols-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">Prompt</p>
|
||||||
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{formatNumber(stats.promptTokens)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">Completion</p>
|
||||||
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{formatNumber(stats.completionTokens)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">Total</p>
|
||||||
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{formatNumber(stats.totalTokens)}</p>
|
||||||
|
</div>
|
||||||
|
{#if stats.tokensPerSecond > 0}
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">Throughput</p>
|
||||||
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{stats.tokensPerSecond.toFixed(1)} tok/s</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if stats.contextWindowSize > 0}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<p class="text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">Context Window</p>
|
||||||
|
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{formatNumber(stats.contextWindowSize)}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if stats.contextWindowSize > 0}
|
||||||
|
<div class="mt-3 border-t border-indigo-200 dark:border-indigo-800 pt-3">
|
||||||
|
<div class="flex items-center justify-between text-xs">
|
||||||
|
<span class="font-medium text-gray-600 dark:text-gray-400">Context Utilization</span>
|
||||||
|
<span class="font-semibold {utilizationColor.text}">{utilizationPct.toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5 h-2 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
|
||||||
|
<div
|
||||||
|
class="h-full rounded-full transition-all duration-300 {utilizationColor.bar}"
|
||||||
|
style="width: {utilizationPct}%"
|
||||||
|
role="progressbar"
|
||||||
|
aria-valuenow={utilizationPct}
|
||||||
|
aria-valuemin={0}
|
||||||
|
aria-valuemax={100}
|
||||||
|
aria-label="Context window utilization"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
details[open] > summary .chevron {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ChatMessage } from '$lib/types';
|
import type { ChatMessage } from '$lib/types';
|
||||||
import type { SubagentResult } from '$lib/proto/llm_multiverse/v1/common_pb';
|
import type { SubagentResult, InferenceStats } from '$lib/proto/llm_multiverse/v1/common_pb';
|
||||||
import type { SessionConfig } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
import type { SessionConfig } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||||
import { processRequest, OrchestratorError, friendlyMessage } from '$lib/services/orchestrator';
|
import { processRequest, OrchestratorError, friendlyMessage } from '$lib/services/orchestrator';
|
||||||
import { OrchestrationState } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
import { OrchestrationState } from '$lib/proto/llm_multiverse/v1/orchestrator_pb';
|
||||||
@@ -15,6 +15,7 @@ export function createOrchestration() {
|
|||||||
let orchestrationState: OrchestrationState = $state(OrchestrationState.UNSPECIFIED);
|
let orchestrationState: OrchestrationState = $state(OrchestrationState.UNSPECIFIED);
|
||||||
let intermediateResult: string = $state('');
|
let intermediateResult: string = $state('');
|
||||||
let finalResult: SubagentResult | null = $state(null);
|
let finalResult: SubagentResult | null = $state(null);
|
||||||
|
let inferenceStats: InferenceStats | null = $state(null);
|
||||||
|
|
||||||
async function send(
|
async function send(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
@@ -27,6 +28,7 @@ export function createOrchestration() {
|
|||||||
orchestrationState = OrchestrationState.UNSPECIFIED;
|
orchestrationState = OrchestrationState.UNSPECIFIED;
|
||||||
intermediateResult = '';
|
intermediateResult = '';
|
||||||
finalResult = null;
|
finalResult = null;
|
||||||
|
inferenceStats = null;
|
||||||
|
|
||||||
let lastAuditState = OrchestrationState.UNSPECIFIED;
|
let lastAuditState = OrchestrationState.UNSPECIFIED;
|
||||||
|
|
||||||
@@ -72,6 +74,9 @@ export function createOrchestration() {
|
|||||||
if (response.intermediateResult) {
|
if (response.intermediateResult) {
|
||||||
intermediateResult = response.intermediateResult;
|
intermediateResult = response.intermediateResult;
|
||||||
}
|
}
|
||||||
|
if (response.inferenceStats) {
|
||||||
|
inferenceStats = response.inferenceStats;
|
||||||
|
}
|
||||||
if (response.finalResult) {
|
if (response.finalResult) {
|
||||||
finalResult = response.finalResult;
|
finalResult = response.finalResult;
|
||||||
if (response.finalResult.newMemoryCandidates.length > 0) {
|
if (response.finalResult.newMemoryCandidates.length > 0) {
|
||||||
@@ -145,6 +150,7 @@ export function createOrchestration() {
|
|||||||
function reset() {
|
function reset() {
|
||||||
error = null;
|
error = null;
|
||||||
finalResult = null;
|
finalResult = null;
|
||||||
|
inferenceStats = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -154,6 +160,7 @@ export function createOrchestration() {
|
|||||||
get orchestrationState() { return orchestrationState; },
|
get orchestrationState() { return orchestrationState; },
|
||||||
get intermediateResult() { return intermediateResult; },
|
get intermediateResult() { return intermediateResult; },
|
||||||
get finalResult() { return finalResult; },
|
get finalResult() { return finalResult; },
|
||||||
|
get inferenceStats() { return inferenceStats; },
|
||||||
send,
|
send,
|
||||||
retry,
|
retry,
|
||||||
reset
|
reset
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file llm_multiverse/v1/common.proto.
|
* Describes the file llm_multiverse/v1/common.proto.
|
||||||
*/
|
*/
|
||||||
export const file_llm_multiverse_v1_common: GenFile = /*@__PURE__*/
|
export const file_llm_multiverse_v1_common: GenFile = /*@__PURE__*/
|
||||||
fileDesc("Ch5sbG1fbXVsdGl2ZXJzZS92MS9jb21tb24ucHJvdG8SEWxsbV9tdWx0aXZlcnNlLnYxItABCghBcnRpZmFjdBINCgVsYWJlbBgBIAEoCRIPCgdjb250ZW50GAIgASgJEjYKDWFydGlmYWN0X3R5cGUYAyABKA4yHy5sbG1fbXVsdGl2ZXJzZS52MS5BcnRpZmFjdFR5cGUSOwoIbWV0YWRhdGEYBCADKAsyKS5sbG1fbXVsdGl2ZXJzZS52MS5BcnRpZmFjdC5NZXRhZGF0YUVudHJ5Gi8KDU1ldGFkYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJqCg9BZ2VudElkZW50aWZpZXISEAoIYWdlbnRfaWQYASABKAkSMAoKYWdlbnRfdHlwZRgCIAEoDjIcLmxsbV9tdWx0aXZlcnNlLnYxLkFnZW50VHlwZRITCgtzcGF3bl9kZXB0aBgDIAEoDSJCCgxBZ2VudExpbmVhZ2USMgoGYWdlbnRzGAEgAygLMiIubGxtX211bHRpdmVyc2UudjEuQWdlbnRJZGVudGlmaWVyItcBCg5TZXNzaW9uQ29udGV4dBISCgpzZXNzaW9uX2lkGAEgASgJEg8KB3VzZXJfaWQYAiABKAkSNgoNYWdlbnRfbGluZWFnZRgDIAEoCzIfLmxsbV9tdWx0aXZlcnNlLnYxLkFnZW50TGluZWFnZRI4Cg5vdmVycmlkZV9sZXZlbBgEIAEoDjIgLmxsbV9tdWx0aXZlcnNlLnYxLk92ZXJyaWRlTGV2ZWwSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAinQEKC0Vycm9yRGV0YWlsEgwKBGNvZGUYASABKAkSDwoHbWVzc2FnZRgCIAEoCRI+CghtZXRhZGF0YRgDIAMoCzIsLmxsbV9tdWx0aXZlcnNlLnYxLkVycm9yRGV0YWlsLk1ldGFkYXRhRW50cnkaLwoNTWV0YWRhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBImcKD01lbW9yeUNhbmRpZGF0ZRIPCgdjb250ZW50GAEgASgJEi8KBnNvdXJjZRgCIAEoDjIfLmxsbV9tdWx0aXZlcnNlLnYxLlJlc3VsdFNvdXJjZRISCgpjb25maWRlbmNlGAMgASgCIuACCg5TdWJhZ2VudFJlc3VsdBIvCgZzdGF0dXMYASABKA4yHy5sbG1fbXVsdGl2ZXJzZS52MS5SZXN1bHRTdGF0dXMSDwoHc3VtbWFyeRgCIAEoCRIuCglhcnRpZmFjdHMYAyADKAsyGy5sbG1fbXVsdGl2ZXJzZS52MS5BcnRpZmFjdBI4Cg5yZXN1bHRfcXVhbGl0eRgEIAEoDjIgLmxsbV9tdWx0aXZlcnNlLnYxLlJlc3VsdFF1YWxpdHkSLwoGc291cmNlGAUgASgOMh8ubGxtX211bHRpdmVyc2UudjEuUmVzdWx0U291cmNlEkEKFW5ld19tZW1vcnlfY2FuZGlkYXRlcxgGIAMoCzIiLmxsbV9tdWx0aXZlcnNlLnYxLk1lbW9yeUNhbmRpZGF0ZRIbCg5mYWlsdXJlX3JlYXNvbhgHIAEoCUgAiAEBQhEKD19mYWlsdXJlX3JlYXNvbiqoAQoJQWdlbnRUeXBlEhoKFkFHRU5UX1RZUEVfVU5TUEVDSUZJRUQQABIbChdBR0VOVF9UWVBFX09SQ0hFU1RSQVRPUhABEhkKFUFHRU5UX1RZUEVfUkVTRUFSQ0hFUhACEhQKEEFHRU5UX1RZUEVfQ09ERVIQAxIXChNBR0VOVF9UWVBFX1NZU0FETUlOEAQSGAoUQUdFTlRfVFlQRV9BU1NJU1RBTlQQBSr1AQoIVG9vbFR5cGUSGQoVVE9PTF9UWVBFX1VOU1BFQ0lGSUVEEAASGQoVVE9PTF9UWVBFX01FTU9SWV9SRUFEEAESGgoWVE9PTF9UWVBFX01FTU9SWV9XUklURRACEhgKFFRPT0xfVFlQRV9XRUJfU0VBUkNIEAMSFQoRVE9PTF9UWVBFX0ZTX1JFQUQQBBIWChJUT09MX1RZUEVfRlNfV1JJVEUQBRIWChJUT09MX1RZUEVfUlVOX0NPREUQBhIXChNUT09MX1RZUEVfUlVOX1NIRUxMEAcSHQoZVE9PTF9UWVBFX1BBQ0tBR0VfSU5TVEFMTBAIKnoKDU92ZXJyaWRlTGV2ZWwSHgoaT1ZFUlJJREVfTEVWRUxfVU5TUEVDSUZJRUQQABIXChNPVkVSUklERV9MRVZFTF9OT05FEAESGAoUT1ZFUlJJREVfTEVWRUxfUkVMQVgQAhIWChJPVkVSUklERV9MRVZFTF9BTEwQAyp9CgxSZXN1bHRTdGF0dXMSHQoZUkVTVUxUX1NUQVRVU19VTlNQRUNJRklFRBAAEhkKFVJFU1VMVF9TVEFUVVNfU1VDQ0VTUxABEhkKFVJFU1VMVF9TVEFUVVNfUEFSVElBTBACEhgKFFJFU1VMVF9TVEFUVVNfRkFJTEVEEAMqhwEKDVJlc3VsdFF1YWxpdHkSHgoaUkVTVUxUX1FVQUxJVFlfVU5TUEVDSUZJRUQQABIbChdSRVNVTFRfUVVBTElUWV9WRVJJRklFRBABEhsKF1JFU1VMVF9RVUFMSVRZX0lORkVSUkVEEAISHAoYUkVTVUxUX1FVQUxJVFlfVU5DRVJUQUlOEAMqhgEKDFJlc3VsdFNvdXJjZRIdChlSRVNVTFRfU09VUkNFX1VOU1BFQ0lGSUVEEAASHQoZUkVTVUxUX1NPVVJDRV9UT09MX09VVFBVVBABEiEKHVJFU1VMVF9TT1VSQ0VfTU9ERUxfS05PV0xFREdFEAISFQoRUkVTVUxUX1NPVVJDRV9XRUIQAyqgAQoMQXJ0aWZhY3RUeXBlEh0KGUFSVElGQUNUX1RZUEVfVU5TUEVDSUZJRUQQABIWChJBUlRJRkFDVF9UWVBFX0NPREUQARIWChJBUlRJRkFDVF9UWVBFX1RFWFQQAhIgChxBUlRJRkFDVF9UWVBFX0NPTU1BTkRfT1VUUFVUEAMSHwobQVJUSUZBQ1RfVFlQRV9TRUFSQ0hfUkVTVUxUEARiBnByb3RvMw", [file_google_protobuf_timestamp]);
|
fileDesc("Ch5sbG1fbXVsdGl2ZXJzZS92MS9jb21tb24ucHJvdG8SEWxsbV9tdWx0aXZlcnNlLnYxItABCghBcnRpZmFjdBINCgVsYWJlbBgBIAEoCRIPCgdjb250ZW50GAIgASgJEjYKDWFydGlmYWN0X3R5cGUYAyABKA4yHy5sbG1fbXVsdGl2ZXJzZS52MS5BcnRpZmFjdFR5cGUSOwoIbWV0YWRhdGEYBCADKAsyKS5sbG1fbXVsdGl2ZXJzZS52MS5BcnRpZmFjdC5NZXRhZGF0YUVudHJ5Gi8KDU1ldGFkYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJqCg9BZ2VudElkZW50aWZpZXISEAoIYWdlbnRfaWQYASABKAkSMAoKYWdlbnRfdHlwZRgCIAEoDjIcLmxsbV9tdWx0aXZlcnNlLnYxLkFnZW50VHlwZRITCgtzcGF3bl9kZXB0aBgDIAEoDSJCCgxBZ2VudExpbmVhZ2USMgoGYWdlbnRzGAEgAygLMiIubGxtX211bHRpdmVyc2UudjEuQWdlbnRJZGVudGlmaWVyItcBCg5TZXNzaW9uQ29udGV4dBISCgpzZXNzaW9uX2lkGAEgASgJEg8KB3VzZXJfaWQYAiABKAkSNgoNYWdlbnRfbGluZWFnZRgDIAEoCzIfLmxsbV9tdWx0aXZlcnNlLnYxLkFnZW50TGluZWFnZRI4Cg5vdmVycmlkZV9sZXZlbBgEIAEoDjIgLmxsbV9tdWx0aXZlcnNlLnYxLk92ZXJyaWRlTGV2ZWwSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAinQEKC0Vycm9yRGV0YWlsEgwKBGNvZGUYASABKAkSDwoHbWVzc2FnZRgCIAEoCRI+CghtZXRhZGF0YRgDIAMoCzIsLmxsbV9tdWx0aXZlcnNlLnYxLkVycm9yRGV0YWlsLk1ldGFkYXRhRW50cnkaLwoNTWV0YWRhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBImcKD01lbW9yeUNhbmRpZGF0ZRIPCgdjb250ZW50GAEgASgJEi8KBnNvdXJjZRgCIAEoDjIfLmxsbV9tdWx0aXZlcnNlLnYxLlJlc3VsdFNvdXJjZRISCgpjb25maWRlbmNlGAMgASgCIpABCg5JbmZlcmVuY2VTdGF0cxIVCg1wcm9tcHRfdG9rZW5zGAEgASgNEhkKEWNvbXBsZXRpb25fdG9rZW5zGAIgASgNEhQKDHRvdGFsX3Rva2VucxgDIAEoDRIbChNjb250ZXh0X3dpbmRvd19zaXplGAQgASgNEhkKEXRva2Vuc19wZXJfc2Vjb25kGAUgASgCIuACCg5TdWJhZ2VudFJlc3VsdBIvCgZzdGF0dXMYASABKA4yHy5sbG1fbXVsdGl2ZXJzZS52MS5SZXN1bHRTdGF0dXMSDwoHc3VtbWFyeRgCIAEoCRIuCglhcnRpZmFjdHMYAyADKAsyGy5sbG1fbXVsdGl2ZXJzZS52MS5BcnRpZmFjdBI4Cg5yZXN1bHRfcXVhbGl0eRgEIAEoDjIgLmxsbV9tdWx0aXZlcnNlLnYxLlJlc3VsdFF1YWxpdHkSLwoGc291cmNlGAUgASgOMh8ubGxtX211bHRpdmVyc2UudjEuUmVzdWx0U291cmNlEkEKFW5ld19tZW1vcnlfY2FuZGlkYXRlcxgGIAMoCzIiLmxsbV9tdWx0aXZlcnNlLnYxLk1lbW9yeUNhbmRpZGF0ZRIbCg5mYWlsdXJlX3JlYXNvbhgHIAEoCUgAiAEBQhEKD19mYWlsdXJlX3JlYXNvbiqoAQoJQWdlbnRUeXBlEhoKFkFHRU5UX1RZUEVfVU5TUEVDSUZJRUQQABIbChdBR0VOVF9UWVBFX09SQ0hFU1RSQVRPUhABEhkKFUFHRU5UX1RZUEVfUkVTRUFSQ0hFUhACEhQKEEFHRU5UX1RZUEVfQ09ERVIQAxIXChNBR0VOVF9UWVBFX1NZU0FETUlOEAQSGAoUQUdFTlRfVFlQRV9BU1NJU1RBTlQQBSr1AQoIVG9vbFR5cGUSGQoVVE9PTF9UWVBFX1VOU1BFQ0lGSUVEEAASGQoVVE9PTF9UWVBFX01FTU9SWV9SRUFEEAESGgoWVE9PTF9UWVBFX01FTU9SWV9XUklURRACEhgKFFRPT0xfVFlQRV9XRUJfU0VBUkNIEAMSFQoRVE9PTF9UWVBFX0ZTX1JFQUQQBBIWChJUT09MX1RZUEVfRlNfV1JJVEUQBRIWChJUT09MX1RZUEVfUlVOX0NPREUQBhIXChNUT09MX1RZUEVfUlVOX1NIRUxMEAcSHQoZVE9PTF9UWVBFX1BBQ0tBR0VfSU5TVEFMTBAIKnoKDU92ZXJyaWRlTGV2ZWwSHgoaT1ZFUlJJREVfTEVWRUxfVU5TUEVDSUZJRUQQABIXChNPVkVSUklERV9MRVZFTF9OT05FEAESGAoUT1ZFUlJJREVfTEVWRUxfUkVMQVgQAhIWChJPVkVSUklERV9MRVZFTF9BTEwQAyp9CgxSZXN1bHRTdGF0dXMSHQoZUkVTVUxUX1NUQVRVU19VTlNQRUNJRklFRBAAEhkKFVJFU1VMVF9TVEFUVVNfU1VDQ0VTUxABEhkKFVJFU1VMVF9TVEFUVVNfUEFSVElBTBACEhgKFFJFU1VMVF9TVEFUVVNfRkFJTEVEEAMqhwEKDVJlc3VsdFF1YWxpdHkSHgoaUkVTVUxUX1FVQUxJVFlfVU5TUEVDSUZJRUQQABIbChdSRVNVTFRfUVVBTElUWV9WRVJJRklFRBABEhsKF1JFU1VMVF9RVUFMSVRZX0lORkVSUkVEEAISHAoYUkVTVUxUX1FVQUxJVFlfVU5DRVJUQUlOEAMqhgEKDFJlc3VsdFNvdXJjZRIdChlSRVNVTFRfU09VUkNFX1VOU1BFQ0lGSUVEEAASHQoZUkVTVUxUX1NPVVJDRV9UT09MX09VVFBVVBABEiEKHVJFU1VMVF9TT1VSQ0VfTU9ERUxfS05PV0xFREdFEAISFQoRUkVTVUxUX1NPVVJDRV9XRUIQAyqgAQoMQXJ0aWZhY3RUeXBlEh0KGUFSVElGQUNUX1RZUEVfVU5TUEVDSUZJRUQQABIWChJBUlRJRkFDVF9UWVBFX0NPREUQARIWChJBUlRJRkFDVF9UWVBFX1RFWFQQAhIgChxBUlRJRkFDVF9UWVBFX0NPTU1BTkRfT1VUUFVUEAMSHwobQVJUSUZBQ1RfVFlQRV9TRUFSQ0hfUkVTVUxUEARiBnByb3RvMw", [file_google_protobuf_timestamp]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A concrete output produced by an agent (code, command output, etc.).
|
* A concrete output produced by an agent (code, command output, etc.).
|
||||||
@@ -200,6 +200,55 @@ export type MemoryCandidate = Message<"llm_multiverse.v1.MemoryCandidate"> & {
|
|||||||
export const MemoryCandidateSchema: GenMessage<MemoryCandidate> = /*@__PURE__*/
|
export const MemoryCandidateSchema: GenMessage<MemoryCandidate> = /*@__PURE__*/
|
||||||
messageDesc(file_llm_multiverse_v1_common, 5);
|
messageDesc(file_llm_multiverse_v1_common, 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inference statistics surfaced from model-gateway through the orchestrator.
|
||||||
|
*
|
||||||
|
* @generated from message llm_multiverse.v1.InferenceStats
|
||||||
|
*/
|
||||||
|
export type InferenceStats = Message<"llm_multiverse.v1.InferenceStats"> & {
|
||||||
|
/**
|
||||||
|
* Number of tokens in the prompt.
|
||||||
|
*
|
||||||
|
* @generated from field: uint32 prompt_tokens = 1;
|
||||||
|
*/
|
||||||
|
promptTokens: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of tokens generated.
|
||||||
|
*
|
||||||
|
* @generated from field: uint32 completion_tokens = 2;
|
||||||
|
*/
|
||||||
|
completionTokens: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum of prompt + completion tokens.
|
||||||
|
*
|
||||||
|
* @generated from field: uint32 total_tokens = 3;
|
||||||
|
*/
|
||||||
|
totalTokens: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model's maximum context length.
|
||||||
|
*
|
||||||
|
* @generated from field: uint32 context_window_size = 4;
|
||||||
|
*/
|
||||||
|
contextWindowSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generation throughput (tokens per second).
|
||||||
|
*
|
||||||
|
* @generated from field: float tokens_per_second = 5;
|
||||||
|
*/
|
||||||
|
tokensPerSecond: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message llm_multiverse.v1.InferenceStats.
|
||||||
|
* Use `create(InferenceStatsSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const InferenceStatsSchema: GenMessage<InferenceStats> = /*@__PURE__*/
|
||||||
|
messageDesc(file_llm_multiverse_v1_common, 6);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standardized return value from any subagent to its parent.
|
* Standardized return value from any subagent to its parent.
|
||||||
*
|
*
|
||||||
@@ -251,7 +300,7 @@ export type SubagentResult = Message<"llm_multiverse.v1.SubagentResult"> & {
|
|||||||
* Use `create(SubagentResultSchema)` to create a new message.
|
* Use `create(SubagentResultSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const SubagentResultSchema: GenMessage<SubagentResult> = /*@__PURE__*/
|
export const SubagentResultSchema: GenMessage<SubagentResult> = /*@__PURE__*/
|
||||||
messageDesc(file_llm_multiverse_v1_common, 6);
|
messageDesc(file_llm_multiverse_v1_common, 7);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent types with distinct tool permission manifests.
|
* Agent types with distinct tool permission manifests.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
||||||
import type { AgentType, OverrideLevel, SessionContext, SubagentResult, ToolType } from "./common_pb";
|
import type { AgentType, InferenceStats, OverrideLevel, SessionContext, SubagentResult, ToolType } from "./common_pb";
|
||||||
import { file_llm_multiverse_v1_common } from "./common_pb";
|
import { file_llm_multiverse_v1_common } from "./common_pb";
|
||||||
import type { Message } from "@bufbuild/protobuf";
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file llm_multiverse/v1/orchestrator.proto.
|
* Describes the file llm_multiverse/v1/orchestrator.proto.
|
||||||
*/
|
*/
|
||||||
export const file_llm_multiverse_v1_orchestrator: GenFile = /*@__PURE__*/
|
export const file_llm_multiverse_v1_orchestrator: GenFile = /*@__PURE__*/
|
||||||
fileDesc("CiRsbG1fbXVsdGl2ZXJzZS92MS9vcmNoZXN0cmF0b3IucHJvdG8SEWxsbV9tdWx0aXZlcnNlLnYxIn4KDVNlc3Npb25Db25maWcSOAoOb3ZlcnJpZGVfbGV2ZWwYASABKA4yIC5sbG1fbXVsdGl2ZXJzZS52MS5PdmVycmlkZUxldmVsEhYKDmRpc2FibGVkX3Rvb2xzGAIgAygJEhsKE2dyYW50ZWRfcGVybWlzc2lvbnMYAyADKAkirwEKEVN1YnRhc2tEZWZpbml0aW9uEgoKAmlkGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEjAKCmFnZW50X3R5cGUYAyABKA4yHC5sbG1fbXVsdGl2ZXJzZS52MS5BZ2VudFR5cGUSEgoKZGVwZW5kc19vbhgEIAMoCRIzCg50b29sc19yZXF1aXJlZBgFIAMoDjIbLmxsbV9tdWx0aXZlcnNlLnYxLlRvb2xUeXBlIoYCCg9TdWJhZ2VudFJlcXVlc3QSMgoHY29udGV4dBgBIAEoCzIhLmxsbV9tdWx0aXZlcnNlLnYxLlNlc3Npb25Db250ZXh0EhAKCGFnZW50X2lkGAIgASgJEjAKCmFnZW50X3R5cGUYAyABKA4yHC5sbG1fbXVsdGl2ZXJzZS52MS5BZ2VudFR5cGUSDAoEdGFzaxgEIAEoCRIfChdyZWxldmFudF9tZW1vcnlfY29udGV4dBgFIAMoCRISCgptYXhfdG9rZW5zGAYgASgNEjgKDnNlc3Npb25fY29uZmlnGAcgASgLMiAubGxtX211bHRpdmVyc2UudjEuU2Vzc2lvbkNvbmZpZyKTAQoVUHJvY2Vzc1JlcXVlc3RSZXF1ZXN0EhIKCnNlc3Npb25faWQYASABKAkSFAoMdXNlcl9tZXNzYWdlGAIgASgJEj0KDnNlc3Npb25fY29uZmlnGAMgASgLMiAubGxtX211bHRpdmVyc2UudjEuU2Vzc2lvbkNvbmZpZ0gAiAEBQhEKD19zZXNzaW9uX2NvbmZpZyLoAQoWUHJvY2Vzc1JlcXVlc3RSZXNwb25zZRI0CgVzdGF0ZRgBIAEoDjIlLmxsbV9tdWx0aXZlcnNlLnYxLk9yY2hlc3RyYXRpb25TdGF0ZRIPCgdtZXNzYWdlGAIgASgJEiAKE2ludGVybWVkaWF0ZV9yZXN1bHQYAyABKAlIAIgBARI8CgxmaW5hbF9yZXN1bHQYBCABKAsyIS5sbG1fbXVsdGl2ZXJzZS52MS5TdWJhZ2VudFJlc3VsdEgBiAEBQhYKFF9pbnRlcm1lZGlhdGVfcmVzdWx0Qg8KDV9maW5hbF9yZXN1bHQq7AEKEk9yY2hlc3RyYXRpb25TdGF0ZRIjCh9PUkNIRVNUUkFUSU9OX1NUQVRFX1VOU1BFQ0lGSUVEEAASIwofT1JDSEVTVFJBVElPTl9TVEFURV9ERUNPTVBPU0lORxABEiMKH09SQ0hFU1RSQVRJT05fU1RBVEVfRElTUEFUQ0hJTkcQAhIhCh1PUkNIRVNUUkFUSU9OX1NUQVRFX0VYRUNVVElORxADEiIKHk9SQ0hFU1RSQVRJT05fU1RBVEVfQ09NUEFDVElORxAEEiAKHE9SQ0hFU1RSQVRJT05fU1RBVEVfQ09NUExFVEUQBTJ+ChNPcmNoZXN0cmF0b3JTZXJ2aWNlEmcKDlByb2Nlc3NSZXF1ZXN0EigubGxtX211bHRpdmVyc2UudjEuUHJvY2Vzc1JlcXVlc3RSZXF1ZXN0GikubGxtX211bHRpdmVyc2UudjEuUHJvY2Vzc1JlcXVlc3RSZXNwb25zZTABYgZwcm90bzM", [file_llm_multiverse_v1_common]);
|
fileDesc("CiRsbG1fbXVsdGl2ZXJzZS92MS9vcmNoZXN0cmF0b3IucHJvdG8SEWxsbV9tdWx0aXZlcnNlLnYxIn4KDVNlc3Npb25Db25maWcSOAoOb3ZlcnJpZGVfbGV2ZWwYASABKA4yIC5sbG1fbXVsdGl2ZXJzZS52MS5PdmVycmlkZUxldmVsEhYKDmRpc2FibGVkX3Rvb2xzGAIgAygJEhsKE2dyYW50ZWRfcGVybWlzc2lvbnMYAyADKAkirwEKEVN1YnRhc2tEZWZpbml0aW9uEgoKAmlkGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEjAKCmFnZW50X3R5cGUYAyABKA4yHC5sbG1fbXVsdGl2ZXJzZS52MS5BZ2VudFR5cGUSEgoKZGVwZW5kc19vbhgEIAMoCRIzCg50b29sc19yZXF1aXJlZBgFIAMoDjIbLmxsbV9tdWx0aXZlcnNlLnYxLlRvb2xUeXBlIoYCCg9TdWJhZ2VudFJlcXVlc3QSMgoHY29udGV4dBgBIAEoCzIhLmxsbV9tdWx0aXZlcnNlLnYxLlNlc3Npb25Db250ZXh0EhAKCGFnZW50X2lkGAIgASgJEjAKCmFnZW50X3R5cGUYAyABKA4yHC5sbG1fbXVsdGl2ZXJzZS52MS5BZ2VudFR5cGUSDAoEdGFzaxgEIAEoCRIfChdyZWxldmFudF9tZW1vcnlfY29udGV4dBgFIAMoCRISCgptYXhfdG9rZW5zGAYgASgNEjgKDnNlc3Npb25fY29uZmlnGAcgASgLMiAubGxtX211bHRpdmVyc2UudjEuU2Vzc2lvbkNvbmZpZyKTAQoVUHJvY2Vzc1JlcXVlc3RSZXF1ZXN0EhIKCnNlc3Npb25faWQYASABKAkSFAoMdXNlcl9tZXNzYWdlGAIgASgJEj0KDnNlc3Npb25fY29uZmlnGAMgASgLMiAubGxtX211bHRpdmVyc2UudjEuU2Vzc2lvbkNvbmZpZ0gAiAEBQhEKD19zZXNzaW9uX2NvbmZpZyK9AgoWUHJvY2Vzc1JlcXVlc3RSZXNwb25zZRI0CgVzdGF0ZRgBIAEoDjIlLmxsbV9tdWx0aXZlcnNlLnYxLk9yY2hlc3RyYXRpb25TdGF0ZRIPCgdtZXNzYWdlGAIgASgJEiAKE2ludGVybWVkaWF0ZV9yZXN1bHQYAyABKAlIAIgBARI8CgxmaW5hbF9yZXN1bHQYBCABKAsyIS5sbG1fbXVsdGl2ZXJzZS52MS5TdWJhZ2VudFJlc3VsdEgBiAEBEj8KD2luZmVyZW5jZV9zdGF0cxgFIAEoCzIhLmxsbV9tdWx0aXZlcnNlLnYxLkluZmVyZW5jZVN0YXRzSAKIAQFCFgoUX2ludGVybWVkaWF0ZV9yZXN1bHRCDwoNX2ZpbmFsX3Jlc3VsdEISChBfaW5mZXJlbmNlX3N0YXRzKuwBChJPcmNoZXN0cmF0aW9uU3RhdGUSIwofT1JDSEVTVFJBVElPTl9TVEFURV9VTlNQRUNJRklFRBAAEiMKH09SQ0hFU1RSQVRJT05fU1RBVEVfREVDT01QT1NJTkcQARIjCh9PUkNIRVNUUkFUSU9OX1NUQVRFX0RJU1BBVENISU5HEAISIQodT1JDSEVTVFJBVElPTl9TVEFURV9FWEVDVVRJTkcQAxIiCh5PUkNIRVNUUkFUSU9OX1NUQVRFX0NPTVBBQ1RJTkcQBBIgChxPUkNIRVNUUkFUSU9OX1NUQVRFX0NPTVBMRVRFEAUyfgoTT3JjaGVzdHJhdG9yU2VydmljZRJnCg5Qcm9jZXNzUmVxdWVzdBIoLmxsbV9tdWx0aXZlcnNlLnYxLlByb2Nlc3NSZXF1ZXN0UmVxdWVzdBopLmxsbV9tdWx0aXZlcnNlLnYxLlByb2Nlc3NSZXF1ZXN0UmVzcG9uc2UwAWIGcHJvdG8z", [file_llm_multiverse_v1_common]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-session configuration for override control.
|
* Per-session configuration for override control.
|
||||||
@@ -199,6 +199,13 @@ export type ProcessRequestResponse = Message<"llm_multiverse.v1.ProcessRequestRe
|
|||||||
* @generated from field: optional llm_multiverse.v1.SubagentResult final_result = 4;
|
* @generated from field: optional llm_multiverse.v1.SubagentResult final_result = 4;
|
||||||
*/
|
*/
|
||||||
finalResult?: SubagentResult;
|
finalResult?: SubagentResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inference statistics from the model-gateway (on the final streamed message).
|
||||||
|
*
|
||||||
|
* @generated from field: optional llm_multiverse.v1.InferenceStats inference_stats = 5;
|
||||||
|
*/
|
||||||
|
inferenceStats?: InferenceStats;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import OrchestrationProgress from '$lib/components/OrchestrationProgress.svelte';
|
import OrchestrationProgress from '$lib/components/OrchestrationProgress.svelte';
|
||||||
import ThinkingSection from '$lib/components/ThinkingSection.svelte';
|
import ThinkingSection from '$lib/components/ThinkingSection.svelte';
|
||||||
import FinalResult from '$lib/components/FinalResult.svelte';
|
import FinalResult from '$lib/components/FinalResult.svelte';
|
||||||
|
import InferenceStatsPanel from '$lib/components/InferenceStatsPanel.svelte';
|
||||||
import SessionSidebar from '$lib/components/SessionSidebar.svelte';
|
import SessionSidebar from '$lib/components/SessionSidebar.svelte';
|
||||||
import ConfigSidebar from '$lib/components/ConfigSidebar.svelte';
|
import ConfigSidebar from '$lib/components/ConfigSidebar.svelte';
|
||||||
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
||||||
@@ -189,6 +190,10 @@
|
|||||||
<FinalResult result={orchestration.finalResult} />
|
<FinalResult result={orchestration.finalResult} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if orchestration.inferenceStats && !orchestration.isStreaming}
|
||||||
|
<InferenceStatsPanel stats={orchestration.inferenceStats} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if orchestration.error}
|
{#if orchestration.error}
|
||||||
<div class="mx-4 mb-2 flex items-center justify-between gap-3 rounded-lg bg-red-50 dark:bg-red-900/30 px-4 py-2 text-sm text-red-600 dark:text-red-400">
|
<div class="mx-4 mb-2 flex items-center justify-between gap-3 rounded-lg bg-red-50 dark:bg-red-900/30 px-4 py-2 text-sm text-red-600 dark:text-red-400">
|
||||||
<span>{orchestration.error}</span>
|
<span>{orchestration.error}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user