feat: better tool parsing ui
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { ThreadMessageLike } from '@assistant-ui/react'
|
||||
|
||||
import { mediaDisplayLabel, mediaMarkdownHref } from '@/lib/media'
|
||||
import type { SessionMessage } from '@/types/hermes'
|
||||
import type { SessionMessage, UsageStats } from '@/types/hermes'
|
||||
|
||||
export type ChatMessagePart = Exclude<ThreadMessageLike['content'], string>[number]
|
||||
|
||||
@@ -38,6 +38,7 @@ export type GatewayEventPayload = {
|
||||
cwd?: string
|
||||
branch?: string
|
||||
personality?: string
|
||||
usage?: Partial<UsageStats>
|
||||
// clarify.request
|
||||
request_id?: string
|
||||
question?: string
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import type { UsageStats } from '@/types/hermes'
|
||||
|
||||
export function formatK(value: number): string {
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
if (value >= 1_000_000) {
|
||||
return `${(value / 1_000_000).toFixed(1)}M`
|
||||
}
|
||||
|
||||
if (value >= 1_000) {
|
||||
return `${(value / 1_000).toFixed(1)}k`
|
||||
}
|
||||
|
||||
return `${Math.round(value)}`
|
||||
}
|
||||
|
||||
export function formatDuration(elapsedMs: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000))
|
||||
const seconds = totalSeconds % 60
|
||||
const minutes = Math.floor(totalSeconds / 60) % 60
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const ss = String(seconds).padStart(2, '0')
|
||||
const mm = String(minutes).padStart(2, '0')
|
||||
|
||||
return hours > 0 ? `${hours}:${mm}:${ss}` : `${minutes}:${ss}`
|
||||
}
|
||||
|
||||
export function compactPath(path: string, max = 44): string {
|
||||
const trimmed = path.trim()
|
||||
|
||||
if (trimmed.length <= max) {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
const segments = trimmed.split('/').filter(Boolean)
|
||||
|
||||
if (segments.length < 2) {
|
||||
return `…${trimmed.slice(-(max - 1))}`
|
||||
}
|
||||
|
||||
const tail = segments.slice(-2).join('/')
|
||||
|
||||
return tail.length + 2 >= max ? `…${tail.slice(-(max - 1))}` : `…/${tail}`
|
||||
}
|
||||
|
||||
export function contextBar(percent: number | undefined, width = 10): string {
|
||||
const bounded = Math.max(0, Math.min(100, percent ?? 0))
|
||||
const filled = Math.round((bounded / 100) * width)
|
||||
|
||||
return `${'█'.repeat(filled)}${'░'.repeat(width - filled)}`
|
||||
}
|
||||
|
||||
export function usageContextLabel(usage: UsageStats): string {
|
||||
if (usage.context_max) {
|
||||
return `${formatK(usage.context_used ?? 0)}/${formatK(usage.context_max)}`
|
||||
}
|
||||
|
||||
return usage.total > 0 ? `${formatK(usage.total)} tok` : ''
|
||||
}
|
||||
|
||||
export function contextBarLabel(usage: UsageStats): string {
|
||||
if (!usage.context_max) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const pct = Math.max(0, Math.min(100, Math.round(usage.context_percent ?? 0)))
|
||||
|
||||
return `[${contextBar(usage.context_percent)}] ${pct}%`
|
||||
}
|
||||
|
||||
export function LiveDuration({ since }: { since: number | null | undefined }) {
|
||||
const [now, setNow] = useState(() => Date.now())
|
||||
|
||||
useEffect(() => {
|
||||
if (!since) {
|
||||
return
|
||||
}
|
||||
|
||||
const tick = () => setNow(Date.now())
|
||||
tick()
|
||||
const timer = window.setInterval(tick, 1000)
|
||||
|
||||
return () => window.clearInterval(timer)
|
||||
}, [since])
|
||||
|
||||
return since ? formatDuration(now - since) : null
|
||||
}
|
||||
Reference in New Issue
Block a user