feat(ui,api,admin): sveltekit admin spa, usage by day/agent/conversation/model, rate limits, memory tree, turn snapshot
This commit is contained in:
1 parent
61562e947d
commit
98796a82c6
158 files changed
+8459
-2531
No files matched your search
@@ -0,0 +1,96 @@
|
||||
<script lang="ts">
|
||||
import MessageSquareIcon from "@lucide/svelte/icons/message-square";
|
||||
import StatusPill from "$lib/components/status-pill.svelte";
|
||||
import {
|
||||
elapsedMs,
|
||||
fmtDuration,
|
||||
fmtMoney,
|
||||
fmtTime,
|
||||
fmtTokens,
|
||||
} from "$lib/format";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Turn } from "./activity.svelte";
|
||||
import ToolNodeView from "./tool-node.svelte";
|
||||
|
||||
let { turn, now }: { turn: Turn; now: number } = $props();
|
||||
|
||||
const duration = $derived(
|
||||
turn.usage?.duration_ms ?? elapsedMs(turn.startedAt, turn.endedAt, now)
|
||||
);
|
||||
const originLabel = $derived(
|
||||
turn.itemOrigin && turn.itemOrigin !== turn.origin
|
||||
? `${turn.origin} · ${turn.itemOrigin}`
|
||||
: turn.origin
|
||||
);
|
||||
const toolCount = $derived(Object.keys(turn.nodes).length);
|
||||
</script>
|
||||
|
||||
<article
|
||||
class={cn(
|
||||
"flex animate-rise flex-col gap-2 border-b py-3",
|
||||
turn.status === "running" && "bg-signal/[0.03]"
|
||||
)}
|
||||
>
|
||||
<header class="flex flex-wrap items-center gap-x-3 gap-y-1 px-1 text-xs">
|
||||
<StatusPill status={turn.status} />
|
||||
<span
|
||||
class={cn(
|
||||
"font-medium",
|
||||
turn.origin === "inject" ? "text-note" : "text-foreground"
|
||||
)}
|
||||
>
|
||||
{originLabel}
|
||||
</span>
|
||||
<span class="tabular text-muted-foreground">{fmtTime(turn.startedAt)}</span>
|
||||
<span class="tabular text-muted-foreground">{fmtDuration(duration)}</span>
|
||||
{#if toolCount > 0}
|
||||
<span class="tabular text-muted-foreground">
|
||||
{toolCount}
|
||||
tool {toolCount === 1 ? "call" : "calls"}
|
||||
</span>
|
||||
{/if}
|
||||
{#if turn.thinking > 0 && turn.status === "running"}
|
||||
<span class="text-kind-deep">thinking…</span>
|
||||
{/if}
|
||||
{#if turn.usage}
|
||||
<span class="tabular ml-auto text-muted-foreground">
|
||||
in {fmtTokens(turn.usage.input)} · out {fmtTokens(turn.usage.output)} ·
|
||||
cache {fmtTokens(turn.usage.cache_read)}/{fmtTokens(
|
||||
turn.usage.cache_creation
|
||||
)}
|
||||
{#if typeof turn.usage.cost_usd === "number"}
|
||||
· {fmtMoney(turn.usage.cost_usd)}
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</header>
|
||||
{#if turn.userText}
|
||||
<p
|
||||
class="mx-1 rounded-md bg-muted/50 px-2.5 py-1.5 text-sm whitespace-pre-wrap"
|
||||
>
|
||||
{turn.userText}
|
||||
</p>
|
||||
{/if}
|
||||
{#if turn.roots.length > 0}
|
||||
<div class="flex flex-col">
|
||||
{#each turn.roots as rootId (rootId)}
|
||||
{#if turn.nodes[rootId]}
|
||||
<ToolNodeView node={turn.nodes[rootId]} nodes={turn.nodes} {now} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#each turn.says as text, index (index)}
|
||||
<p class="mx-1 flex gap-2 rounded-md bg-primary/8 px-2.5 py-1.5 text-sm">
|
||||
<MessageSquareIcon class="mt-0.5 size-3.5 shrink-0 text-link" />
|
||||
<span class="whitespace-pre-wrap">{text}</span>
|
||||
</p>
|
||||
{/each}
|
||||
{#if turn.text}
|
||||
<p class="mx-1 px-1 text-sm whitespace-pre-wrap">{turn.text}</p>
|
||||
{:else if turn.status === "running" && turn.roots.length === 0 && turn.thinking === 0}
|
||||
<p class="mx-1 px-1 text-muted-foreground text-sm">
|
||||
Waiting for the model…
|
||||
</p>
|
||||
{/if}
|
||||
</article>
|
||||
Reference in new issue
Block a user