dsh-llm-verifier 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/client.js +147 -110
- package/lib/client.js.map +1 -1
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/client.js +72 -21
- package/lib/types/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client.test.ts +66 -0
- package/src/client.tsx +119 -63
package/src/client.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import type { ModelProviderGroup, SettingsNamespaceView } from '@deepseek-ai/dsh
|
|
|
3
3
|
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
4
4
|
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
5
5
|
import type {} from '@deepseek-ai/dsh-client-ui-slots'
|
|
6
|
-
import { Button, IconDataOutline16, IconRefreshOutline16, Input } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
6
|
+
import { Button, IconDataOutline16, IconRefreshOutline16, Input, StateDot, type StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
7
7
|
import { Fragment, useEffect, useMemo, useRef, useState } from 'react'
|
|
8
8
|
import {
|
|
9
9
|
zh, en, dictionaries, toolLabels, tFormat, useLanguage, detectLanguage,
|
|
@@ -145,7 +145,7 @@ const sectionTitle: React.CSSProperties = { display: 'flex', alignItems: 'center
|
|
|
145
145
|
const sectionHeadingStyle: React.CSSProperties = { fontSize: 14, fontWeight: 500, lineHeight: '22px' }
|
|
146
146
|
const sectionSummaryStyle: React.CSSProperties = { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0, flex: '1 1 auto' }
|
|
147
147
|
const badgeStyle: React.CSSProperties = { flex: '0 0 auto', padding: '0 6px', borderRadius: 999, fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-tertiary)', border: '1px solid var(--dsw-alias-border-l2)' }
|
|
148
|
-
const linkButton: React.CSSProperties = { border: 0, background: 'transparent', padding: 0, font: 'inherit', fontSize: 12, lineHeight: '18px', cursor: 'pointer', color: 'var(--dsw-alias-
|
|
148
|
+
const linkButton: React.CSSProperties = { border: 0, background: 'transparent', padding: 0, font: 'inherit', fontSize: 12, lineHeight: '18px', cursor: 'pointer', color: 'var(--dsw-alias-link)' }
|
|
149
149
|
const row: React.CSSProperties = { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '6px 24px', minHeight: 56, padding: '10px 0', borderBottom: '1px solid var(--dsw-alias-border-l2)' }
|
|
150
150
|
const labelCell: React.CSSProperties = { flex: '1 1 220px', minWidth: 0 }
|
|
151
151
|
const controlCell: React.CSSProperties = { flex: '0 1 268px', minWidth: 170, display: 'flex', justifyContent: 'flex-end' }
|
|
@@ -153,16 +153,33 @@ const fieldTitle: React.CSSProperties = { fontSize: 14, fontWeight: 400, lineHei
|
|
|
153
153
|
const fieldHelp: React.CSSProperties = { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)', marginTop: 2 }
|
|
154
154
|
const fullLine: React.CSSProperties = { flex: '1 1 100%', margin: '0 0 2px', fontSize: 12, lineHeight: '18px' }
|
|
155
155
|
const unitStyle: React.CSSProperties = { flex: '0 0 auto', fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' }
|
|
156
|
-
const sliderStyle: React.CSSProperties = { width: '100%', margin: 0, accentColor: 'var(--dsw-alias-
|
|
156
|
+
const sliderStyle: React.CSSProperties = { width: '100%', margin: 0, accentColor: 'var(--dsw-alias-state-business-primary)' }
|
|
157
157
|
const toolbarStyle: React.CSSProperties = { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, padding: '10px 0', borderTop: '1px solid var(--dsw-alias-border-l2)' }
|
|
158
158
|
const summaryLineStyle: React.CSSProperties = { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' }
|
|
159
|
-
const stickyBar: React.CSSProperties = { position: 'sticky', bottom: 0, zIndex: 5, display: 'flex', flexDirection: 'column', gap: 6, padding: '10px 0 12px', borderTop: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-module
|
|
159
|
+
const stickyBar: React.CSSProperties = { position: 'sticky', bottom: 0, zIndex: 5, display: 'flex', flexDirection: 'column', gap: 6, padding: '10px 0 12px', borderTop: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-module-platform)', boxShadow: '0 -10px 24px var(--dsw-alias-bg-mask-2)' }
|
|
160
160
|
const statusStyle: React.CSSProperties = { fontSize: 12, lineHeight: '18px', display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0, flexWrap: 'wrap' }
|
|
161
|
-
const selectStyle: React.CSSProperties = { boxSizing: 'border-box', width: '100%', height: 36, padding: '0 34px 0 12px', borderRadius: 8, color: 'var(--dsw-alias-label-primary)', background: 'var(--dsw-
|
|
162
|
-
const toggleStyle = (enabled: boolean): React.CSSProperties => ({ position: 'relative', flex: '0 0 auto', width: 40, height: 22, padding: 0, border: 0, borderRadius: 999, cursor: 'pointer', transition: 'background .15s ease', background: enabled ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-
|
|
163
|
-
const toggleThumbStyle = (enabled: boolean): React.CSSProperties => ({ position: 'absolute', top: 3, left: enabled ? 21 : 3, width: 16, height: 16, borderRadius: '50%', background: 'var(--dsw-static-neutral-00
|
|
164
|
-
const dashboardCard: React.CSSProperties = { border: '1px solid var(--dsw-alias-border-l2
|
|
165
|
-
const muted: React.CSSProperties = { color: 'var(--dsw-
|
|
161
|
+
const selectStyle: React.CSSProperties = { boxSizing: 'border-box', width: '100%', height: 36, padding: '0 34px 0 12px', borderRadius: 8, color: 'var(--dsw-alias-label-primary)', background: 'var(--dsw-specific-input-major)', border: '1px solid var(--dsw-alias-border-l2)', font: 'inherit', fontSize: 14, lineHeight: '22px', outline: 'none' }
|
|
162
|
+
const toggleStyle = (enabled: boolean): React.CSSProperties => ({ position: 'relative', flex: '0 0 auto', width: 40, height: 22, padding: 0, border: 0, borderRadius: 999, cursor: 'pointer', transition: 'background .15s ease', background: enabled ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-specific-input-major)' })
|
|
163
|
+
const toggleThumbStyle = (enabled: boolean): React.CSSProperties => ({ position: 'absolute', top: 3, left: enabled ? 21 : 3, width: 16, height: 16, borderRadius: '50%', background: 'var(--dsw-static-neutral-00)', boxShadow: '0 1px 3px rgba(0,0,0,.28)', transition: 'left .15s ease' })
|
|
164
|
+
const dashboardCard: React.CSSProperties = { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-module-platform)', borderRadius: 16, boxShadow: '0 12px 36px var(--dsw-alias-bg-mask-2)' }
|
|
165
|
+
const muted: React.CSSProperties = { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12 }
|
|
166
|
+
/** Chart palette rides the state aliases so bars and line stay legible in both themes. */
|
|
167
|
+
const chartBarColor = 'var(--dsw-alias-state-business-primary)'
|
|
168
|
+
const chartLineColor = 'var(--dsw-alias-state-warn-primary)'
|
|
169
|
+
/** Status chip tones; each one mixes its own state token, so a fill, a ring and its text move together. */
|
|
170
|
+
type StatusTone = 'pass' | 'warn' | 'error' | 'neutral'
|
|
171
|
+
function toneChip(tone: StatusTone): React.CSSProperties {
|
|
172
|
+
const token = tone === 'error' ? 'var(--dsw-alias-state-error-primary)'
|
|
173
|
+
: tone === 'warn' ? 'var(--dsw-alias-state-warn-primary)'
|
|
174
|
+
: tone === 'pass' ? 'var(--dsw-alias-state-success-primary)'
|
|
175
|
+
: 'var(--dsw-alias-state-business-primary)'
|
|
176
|
+
return {
|
|
177
|
+
background: `color-mix(in srgb, ${token} 14%, transparent)`,
|
|
178
|
+
border: `1px solid color-mix(in srgb, ${token} 32%, transparent)`,
|
|
179
|
+
// Amber's 500 step is a fill; its 600 step is the readable text rung.
|
|
180
|
+
color: tone === 'warn' ? 'var(--dsw-alias-state-warn-label)' : token,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
166
183
|
const toolColors: Record<string, string> = { verifier_route_classify: '#d97706', verifier_compare: '#4f8cff', verifier_select: '#8b6df6', verifier_track: '#2fc5c9', verifier_best_of_n: '#e2569b', verifier_current_session: '#f5a524' }
|
|
167
184
|
|
|
168
185
|
function record(value: unknown): Record<string, unknown> { return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {} }
|
|
@@ -478,8 +495,8 @@ export function VerifierSettings({ remote }: VerifierSettingsProps) {
|
|
|
478
495
|
const first = preset[0]
|
|
479
496
|
if (!first) return null
|
|
480
497
|
return <details style={{ margin: '2px 0 6px 4px', fontSize: 12 }}>
|
|
481
|
-
<summary style={{ cursor: 'pointer', color: 'var(--dsw-
|
|
482
|
-
<pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 240, overflow: 'auto', background: 'var(--dsw-
|
|
498
|
+
<summary style={{ cursor: 'pointer', color: 'var(--dsw-alias-label-secondary)' }}>{tFormat(t['field.criteriaPreset.previewSummary'], { count: preset.length })}</summary>
|
|
499
|
+
<pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 240, overflow: 'auto', background: 'var(--dsw-alias-markdown-code-block)', border: '1px solid var(--dsw-alias-border-l1)', borderRadius: 8, padding: 10, marginTop: 8 }}>{buildPairwisePrompt(t['field.criteriaPreset.sampleTask'], t['field.criteriaPreset.sampleA'], t['field.criteriaPreset.sampleB'], first, DEFAULT_GROUND_TRUTH_NOTE)}</pre>
|
|
483
500
|
</details>
|
|
484
501
|
})() : null
|
|
485
502
|
return <Fragment key={String(field.key)}>
|
|
@@ -549,7 +566,7 @@ export function VerifierSettings({ remote }: VerifierSettingsProps) {
|
|
|
549
566
|
</section>
|
|
550
567
|
})}
|
|
551
568
|
|
|
552
|
-
{loaded.failures.length > 0 && <div style={{ padding: '10px 12px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-
|
|
569
|
+
{loaded.failures.length > 0 && <div style={{ padding: '10px 12px', borderRadius: 8, background: 'var(--dsw-alias-state-warn-tertiary)', color: 'var(--dsw-alias-state-warn-label)', fontSize: 12, lineHeight: '18px' }}>
|
|
553
570
|
<div style={{ fontWeight: 500, marginBottom: 3 }}>{t['settings.catalogFailures']}</div>
|
|
554
571
|
{loaded.failures.map(failure => <div key={failure}>{failure}</div>)}
|
|
555
572
|
</div>}
|
|
@@ -585,11 +602,11 @@ function TrendChart({ daily, days, lang }:{daily:DailyStatistics[];days:number;l
|
|
|
585
602
|
const max=Math.max(1,...rows.flatMap(row=>[row.invocations,row.calls]));const x=(index:number)=>pad.l+(rows.length<=1?innerW/2:index*innerW/(rows.length-1));const y=(value:number)=>pad.t+innerH-value/max*innerH
|
|
586
603
|
const points=rows.map((row,index)=>`${x(index)},${y(row.calls)}`).join(' ');const step=rows.length>16?Math.ceil(rows.length/7):Math.max(1,Math.ceil(rows.length/7));const barWidth=Math.max(3,Math.min(18,innerW/Math.max(rows.length,1)*.55))
|
|
587
604
|
return <div style={{width:'100%',overflowX:'auto'}}><svg viewBox={`0 0 ${width} ${height}`} style={{display:'block',width:'100%',minWidth:620,height:'auto'}} aria-label={t['chart.ariaLabel']}>
|
|
588
|
-
{[0,.25,.5,.75,1].map(ratio=><g key={ratio}><line x1={pad.l} x2={width-pad.r} y1={pad.t+innerH*ratio} y2={pad.t+innerH*ratio} stroke="
|
|
589
|
-
{rows.map((row,index)=><rect key={row.date} x={x(index)-barWidth/2} y={y(row.invocations)} width={barWidth} height={pad.t+innerH-y(row.invocations)} rx="2" fill=
|
|
590
|
-
<polyline points={points} fill="none" stroke=
|
|
591
|
-
{rows.map((row,index)=>index%step===0||index===rows.length-1?<text key={row.date} x={x(index)} y={height-14} textAnchor="middle" fontSize="10" fill="var(--dsw-
|
|
592
|
-
</svg><div style={{display:'flex',justifyContent:'center',gap:18,...muted}}><span><i style={{display:'inline-block',width:8,height:8,borderRadius:2,background:
|
|
605
|
+
{[0,.25,.5,.75,1].map(ratio=><g key={ratio}><line x1={pad.l} x2={width-pad.r} y1={pad.t+innerH*ratio} y2={pad.t+innerH*ratio} stroke="var(--dsw-alias-border-l2)"/><text x={pad.l-8} y={pad.t+innerH*ratio+4} textAnchor="end" fontSize="10" fill="var(--dsw-alias-label-secondary)">{Math.round(max*(1-ratio))}</text></g>)}
|
|
606
|
+
{rows.map((row,index)=><rect key={row.date} x={x(index)-barWidth/2} y={y(row.invocations)} width={barWidth} height={pad.t+innerH-y(row.invocations)} rx="2" fill={chartBarColor} opacity=".82"/>)}
|
|
607
|
+
<polyline points={points} fill="none" stroke={chartLineColor} strokeWidth="2.4" strokeLinejoin="round" strokeLinecap="round"/>
|
|
608
|
+
{rows.map((row,index)=>index%step===0||index===rows.length-1?<text key={row.date} x={x(index)} y={height-14} textAnchor="middle" fontSize="10" fill="var(--dsw-alias-label-secondary)">{Number(row.date.slice(5,7))}/{Number(row.date.slice(8,10))}</text>:null)}
|
|
609
|
+
</svg><div style={{display:'flex',justifyContent:'center',gap:18,...muted}}><span><i style={{display:'inline-block',width:8,height:8,borderRadius:2,background:chartBarColor,marginRight:6}}/>{t['chart.legendToolCalls']}</span><span><i style={{display:'inline-block',width:14,height:2,background:chartLineColor,marginRight:6,verticalAlign:'middle'}}/>{t['chart.legendModelCalls']}</span></div></div>
|
|
593
610
|
}
|
|
594
611
|
|
|
595
612
|
export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }: StatisticsPageProps) {
|
|
@@ -764,12 +781,12 @@ export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }:
|
|
|
764
781
|
}, [days, sessionOnly, sessionId, refresh, rpc, t, queryKey])
|
|
765
782
|
|
|
766
783
|
const totals = data?.totals
|
|
767
|
-
return <main style={{ height: '100%', overflow: 'auto', boxSizing: 'border-box', padding: '22px clamp(16px, 3vw, 38px) 48px', color: 'var(--dsw-
|
|
784
|
+
return <main style={{ height: '100%', overflow: 'auto', boxSizing: 'border-box', padding: '22px clamp(16px, 3vw, 38px) 48px', color: 'var(--dsw-alias-label-primary)', background: 'radial-gradient(circle at 10% 0%, rgba(115,77,255,.09), transparent 32%), radial-gradient(circle at 100% 8%, rgba(47,197,201,.07), transparent 28%)' }}>
|
|
768
785
|
<div style={{ maxWidth: 1180, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
769
786
|
<header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }}>
|
|
770
787
|
<div>
|
|
771
788
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
772
|
-
<span style={{ display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 10, background: '
|
|
789
|
+
<span style={{ display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 10, background: 'color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent)', color: 'var(--dsw-alias-state-business-primary)' }}>
|
|
773
790
|
<IconDataOutline16 size={18} />
|
|
774
791
|
</span>
|
|
775
792
|
<h2 style={{ margin: 0, fontSize: 23 }}>{isGlobal ? t['global.panelTitle'] : t['stats.pageTitle']}</h2>
|
|
@@ -777,24 +794,24 @@ export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }:
|
|
|
777
794
|
<p style={{ margin: '7px 0 0 44px', ...muted }}>{isGlobal ? t['global.panelIntro'] : tFormat(t['stats.updatedAt'], { time: data ? dateTime(data.generatedAt, lang) : '--' })}</p>
|
|
778
795
|
</div>
|
|
779
796
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
780
|
-
<div style={{ display: 'flex', padding: 3, borderRadius: 10, background: 'var(--dsw-
|
|
781
|
-
{[7, 30, 90].map(value => <button key={value} aria-pressed={days === value} onClick={() => setDays(value)} style={{ border: 0, borderRadius: 7, padding: '6px 10px', cursor: 'pointer', color: days === value ? '
|
|
797
|
+
<div style={{ display: 'flex', padding: 3, borderRadius: 10, background: 'var(--dsw-alias-bg-multi-select)', border: '1px solid var(--dsw-alias-border-l2)' }}>
|
|
798
|
+
{[7, 30, 90].map(value => <button key={value} aria-pressed={days === value} onClick={() => setDays(value)} style={{ border: 0, borderRadius: 7, padding: '6px 10px', cursor: 'pointer', color: days === value ? 'var(--dsw-alias-state-business-primary)' : 'var(--dsw-alias-label-secondary)', background: days === value ? 'color-mix(in srgb, var(--dsw-alias-state-business-primary) 16%, transparent)' : 'transparent' }}>{tFormat(t['stats.daysUnit'], { days: value })}</button>)}
|
|
782
799
|
</div>
|
|
783
|
-
{Boolean(sessionId) && <button aria-pressed={sessionOnly} onClick={() => setSessionOnly(value => !value)} style={{ border: '1px solid var(--dsw-alias-border-
|
|
784
|
-
<button type="button" disabled={probe?.busy === true} onClick={() => void runProbe()} style={{ border: '1px solid var(--dsw-alias-border-
|
|
785
|
-
<button title={t['stats.refresh']} onClick={() => setRefresh(value => value + 1)} style={{ display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 9, border: '1px solid var(--dsw-alias-border-
|
|
800
|
+
{Boolean(sessionId) && <button aria-pressed={sessionOnly} onClick={() => setSessionOnly(value => !value)} style={{ border: '1px solid var(--dsw-alias-border-l3)', borderRadius: 9, padding: '7px 11px', cursor: 'pointer', color: sessionOnly ? 'var(--dsw-alias-state-business-primary)' : 'var(--dsw-alias-label-primary)', background: sessionOnly ? 'color-mix(in srgb, var(--dsw-alias-state-business-primary) 14%, transparent)' : 'var(--dsw-alias-interactive-bg-hover)' }}>{sessionOnly ? t['stats.currentSession'] : t['stats.allSessions']}</button>}
|
|
801
|
+
<button type="button" disabled={probe?.busy === true} onClick={() => void runProbe()} style={{ border: '1px solid var(--dsw-alias-border-l3)', borderRadius: 9, padding: '7px 11px', cursor: 'pointer', color: 'var(--dsw-alias-label-primary)', background: 'var(--dsw-alias-interactive-bg-hover)' }}>{probe?.busy === true ? t['probe.running'] : t['probe.button']}</button>
|
|
802
|
+
<button title={t['stats.refresh']} onClick={() => setRefresh(value => value + 1)} style={{ display: 'grid', placeItems: 'center', width: 34, height: 34, borderRadius: 9, border: '1px solid var(--dsw-alias-border-l3)', color: 'var(--dsw-alias-label-primary)', background: 'var(--dsw-alias-interactive-bg-hover)', cursor: 'pointer' }}><IconRefreshOutline16 size={16} /></button>
|
|
786
803
|
</div>
|
|
787
804
|
</header>
|
|
788
|
-
{error && <div style={{ ...dashboardCard, padding: 18, borderColor: 'var(--dsw-
|
|
805
|
+
{error && <div style={{ ...dashboardCard, padding: 18, borderColor: 'var(--dsw-alias-state-error-primary)', color: 'var(--dsw-alias-state-error-primary)' }}>{error}<div style={{ ...muted, marginTop: 6 }}>{t['stats.hostRestartHint']}</div></div>}
|
|
789
806
|
{probe !== null && <section style={{ ...dashboardCard, padding: '16px 18px' }}>
|
|
790
807
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}><strong>{t['probe.title']}</strong><span style={muted}>{t['probe.note']}</span></div>
|
|
791
|
-
{probe.error !== undefined && <div style={{ marginTop: 10, fontSize: 12, color: '
|
|
808
|
+
{probe.error !== undefined && <div style={{ marginTop: 10, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' }}>{probe.error}</div>}
|
|
792
809
|
{probe.value !== undefined && <>
|
|
793
810
|
<div style={{ ...muted, marginTop: 8 }}>{tFormat(t['probe.rubric'], { source: probe.value.rubric.source, count: probe.value.rubric.count, file: probe.value.rubric.file ?? '' })}</div>
|
|
794
|
-
{probe.value.rubric.error !== undefined && <div style={{ marginTop: 6, fontSize: 12, color: '
|
|
811
|
+
{probe.value.rubric.error !== undefined && <div style={{ marginTop: 6, fontSize: 12, color: 'var(--dsw-alias-state-warn-label)' }}>{tFormat(t['probe.rubricFallback'], { error: probe.value.rubric.error })}</div>}
|
|
795
812
|
<div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
796
|
-
{probe.value.judges.map(judge => <div key={judge.label + judge.model} style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', fontSize: 12, padding: '9px 11px', borderRadius: 9, background: 'var(--dsw-
|
|
797
|
-
<span style={{ color: judge.ok ? '
|
|
813
|
+
{probe.value.judges.map(judge => <div key={judge.label + judge.model} style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center', fontSize: 12, padding: '9px 11px', borderRadius: 9, background: 'var(--dsw-alias-interactive-bg-hover)' }}>
|
|
814
|
+
<span style={{ color: judge.ok ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontWeight: 600 }}>{judge.ok ? 'OK' : t['probe.failed']}</span>
|
|
798
815
|
<strong>{judge.label}</strong>
|
|
799
816
|
<span style={muted}>{judge.provider}/{judge.model}</span>
|
|
800
817
|
{judge.ok ? <>
|
|
@@ -802,7 +819,7 @@ export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }:
|
|
|
802
819
|
{judge.scoreA !== undefined && judge.scoreB !== undefined && <span style={muted}>{tFormat(t['probe.scores'], { a: formatPercentage(judge.scoreA), b: formatPercentage(judge.scoreB) })}</span>}
|
|
803
820
|
<span style={muted}>{tFormat(t['probe.latency'], { ms: String(judge.latencyMs) })}</span>
|
|
804
821
|
{judge.calls !== undefined && <span style={muted}>{tFormat(t['probe.calls'], { calls: String(judge.calls) })}</span>}
|
|
805
|
-
</> : <span style={{ color: '
|
|
822
|
+
</> : <span style={{ color: 'var(--dsw-alias-state-error-primary)' }}>{judge.error}</span>}
|
|
806
823
|
</div>)}
|
|
807
824
|
</div>
|
|
808
825
|
</>}
|
|
@@ -815,46 +832,46 @@ export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }:
|
|
|
815
832
|
<div style={muted}>{tFormat(t['stats.callsSummary'], { invocations: compact(totals?.invocations ?? 0, lang), calls: compact(totals?.calls ?? 0, lang) })}</div>
|
|
816
833
|
</div>
|
|
817
834
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: '9px 16px', fontSize: 13 }}>
|
|
818
|
-
<span style={muted}>{t['stats.successCalls']}</span><strong>{compact(totals?.successes ?? 0, lang)} <small style={{ color: '
|
|
835
|
+
<span style={muted}>{t['stats.successCalls']}</span><strong>{compact(totals?.successes ?? 0, lang)} <small style={{ color: 'var(--dsw-alias-state-success-primary)' }}>▲ {((totals?.successRate ?? 0) * 100).toFixed(1)}%</small></strong>
|
|
819
836
|
<span style={muted}>{t['stats.failedCalls']}</span><strong>{compact(totals?.failures ?? 0, lang)}</strong>
|
|
820
837
|
<span style={muted}>{t['stats.avgDuration']}</span><strong>{duration(totals?.averageDurationMs ?? 0)}</strong>
|
|
821
838
|
</div>
|
|
822
839
|
</section>
|
|
823
840
|
<section style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 12 }}>
|
|
824
|
-
<Metric label={t['metric.cacheHitRate']} value={((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + '%'} note={tFormat(t['metric.cacheHitNote'], { hits: compact(totals?.cacheHits ?? 0, lang), total: compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0), lang) })} accent="
|
|
841
|
+
<Metric label={t['metric.cacheHitRate']} value={((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + '%'} note={tFormat(t['metric.cacheHitNote'], { hits: compact(totals?.cacheHits ?? 0, lang), total: compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0), lang) })} accent="var(--dsw-alias-state-success-primary)" />
|
|
825
842
|
<Metric label={t['metric.prefixCacheHitRate']} value={((totals?.prefixCacheHitRate ?? 0) * 100).toFixed(1) + '%'} note={tFormat(t['metric.prefixCacheHitNote'], { cached: compact(totals?.cachedInputTokens ?? 0, lang), input: compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0), lang) })} />
|
|
826
843
|
<Metric label={t['metric.tokens']} value={compact(totals?.tokens ?? 0, lang)} note={tFormat(t['metric.tokensNote'], { input: compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0), lang), output: compact(totals?.outputTokens ?? 0, lang) })} />
|
|
827
844
|
<Metric label={t['metric.avgModelCalls']} value={(totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : '0'} note={tFormat(t['metric.avgModelCallsNote'], { attempts: compact(totals?.attempts ?? 0, lang), retries: compact(totals?.retries ?? 0, lang) })} />
|
|
828
845
|
<Metric label={t['metric.scoringMode']} value={compact(totals?.topLogprobScores ?? 0, lang)} note={tFormat(t['metric.scoringModeNote'], { explicit: compact(totals?.explicitTagScores ?? 0, lang) })} />
|
|
829
846
|
</section>
|
|
830
847
|
<section style={{ ...dashboardCard, padding: '18px 20px 16px' }}><div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}><strong>{t['chart.title']}</strong><span style={muted}>{sessionOnly ? t['stats.currentSession'] : t['stats.allSessions']}</span></div><TrendChart daily={data?.daily ?? []} days={days} lang={lang} /></section>
|
|
831
|
-
<section style={{ ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }}><div style={{ display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }}><strong>{t['table.title']}</strong><span style={muted}>{tFormat(t['table.toolCount'], { count: (data?.tools ?? []).length })}</span></div><div style={{ overflowX: 'auto' }}><table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: 760 }}><thead><tr style={{ textAlign: 'left', color: 'var(--dsw-
|
|
848
|
+
<section style={{ ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }}><div style={{ display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }}><strong>{t['table.title']}</strong><span style={muted}>{tFormat(t['table.toolCount'], { count: (data?.tools ?? []).length })}</span></div><div style={{ overflowX: 'auto' }}><table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: 760 }}><thead><tr style={{ textAlign: 'left', color: 'var(--dsw-alias-label-secondary)', background: 'var(--dsw-alias-interactive-bg-hover)' }}>{[t['table.colTool'], t['table.colInvocations'], t['table.colSuccessRate'], t['table.colAvgDuration'], t['table.colModelCalls'], t['table.colTokens'], t['table.colCacheHits'], t['table.colEstimatedCost']].map(value => <th key={value} style={{ padding: '10px 12px', fontWeight: 500 }}>{value}</th>)}</tr></thead><tbody>{(data?.tools ?? []).map(tool => <tr key={tool.toolName} style={{ borderTop: '1px solid var(--dsw-alias-border-l2)' }}><td style={{ padding: '13px 12px' }}><span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: toolColors[tool.toolName] ?? '#8691a8', marginRight: 8 }} /><strong>{labels[tool.toolName] ?? tool.toolName}</strong><div style={{ ...muted, margin: '3px 0 0 16px' }}>{tool.toolName}</div></td><td style={{ padding: '13px 12px' }}>{compact(tool.invocations, lang)}</td><td style={{ padding: '13px 12px', color: tool.successRate >= .9 ? 'var(--dsw-alias-state-success-primary)' : tool.successRate >= .7 ? 'var(--dsw-alias-state-warn-label)' : 'var(--dsw-alias-state-error-primary)' }}>{(tool.successRate * 100).toFixed(1)}%</td><td style={{ padding: '13px 12px' }}>{duration(tool.averageDurationMs)}</td><td style={{ padding: '13px 12px' }}>{compact(tool.calls, lang)}</td><td style={{ padding: '13px 12px' }}>{compact(tool.tokens, lang)}</td><td style={{ padding: '13px 12px' }}>{tool.cacheHits}/{tool.cacheHits + tool.cacheMisses}</td><td style={{ padding: '13px 12px' }}>{money(tool.estimatedCostUsd)}</td></tr>)}{(data?.tools.length ?? 0) === 0 && <tr><td colSpan={8} style={{ padding: 28, textAlign: 'center', ...muted }}>{t['table.empty']}</td></tr>}</tbody></table></div></section>
|
|
832
849
|
<section style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(290px,.45fr)', gap: 16, alignItems: 'start' }}><div style={{ ...dashboardCard, padding: '18px 18px 8px', overflow: 'hidden' }}><div style={{ display: 'flex', justifyContent: 'space-between', margin: '0 2px 12px' }}><strong>{t['recent.title']}</strong><span style={muted}>{t['recent.maxCount']}</span></div><div style={{ maxHeight: 360, overflow: 'auto' }}>{(data?.recent ?? []).map(item => {
|
|
833
850
|
const verdictInfo = item.verdict ? formatVerdictDetails(item.verdict, t) : undefined
|
|
834
851
|
const failed = !item.success || (verdictInfo ? verdictInfo.isFailed : false)
|
|
835
852
|
const detailsOpen = openDetails === item.id
|
|
836
|
-
const panelStyle: React.CSSProperties = { margin: '8px 0 0 15px', border: '1px solid var(--dsw-alias-border-l2
|
|
853
|
+
const panelStyle: React.CSSProperties = { margin: '8px 0 0 15px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: '8px 10px', background: 'var(--dsw-alias-interactive-bg-hover)', fontSize: 11 }
|
|
837
854
|
// The row is a column: identity plus timing on the first line, then the two controls on
|
|
838
855
|
// one line, then whichever panel they opened. Panels therefore span the whole row
|
|
839
856
|
// instead of being squeezed into the identity column.
|
|
840
|
-
return <div key={item.id} style={{ padding: '11px 8px', borderTop: '1px solid var(--dsw-alias-border-l2
|
|
857
|
+
return <div key={item.id} style={{ padding: '11px 8px', borderTop: '1px solid var(--dsw-alias-border-l2)' }}>
|
|
841
858
|
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(170px,1fr) auto', gap: 12 }}>
|
|
842
859
|
<div>
|
|
843
860
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
844
|
-
<span style={{ width: 7, height: 7, borderRadius: '50%', background: failed ? '
|
|
861
|
+
<span style={{ width: 7, height: 7, borderRadius: '50%', background: failed ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-success-primary)' }} />
|
|
845
862
|
<strong style={{ fontSize: 13 }}>{labels[item.toolName] ?? item.toolName}</strong>
|
|
846
863
|
<span style={muted}>{item.provider}/{item.model}</span>
|
|
847
864
|
</div>
|
|
848
|
-
{!item.success && <div title={item.errorMessage} style={{ margin: '5px 0 0 15px', fontSize: 11, color: '
|
|
865
|
+
{!item.success && <div title={item.errorMessage} style={{ margin: '5px 0 0 15px', fontSize: 11, color: 'var(--dsw-alias-state-error-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.errorMessage ?? item.errorName}</div>}
|
|
849
866
|
{item.verdict && verdictInfo && <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 6, margin: '6px 0 0 15px', fontSize: 11 }}>
|
|
850
|
-
{verdictInfo.outcomeText && <span style={{ padding: '1px 5px', borderRadius: 4, fontWeight: 500, fontSize: 11,
|
|
851
|
-
{verdictInfo.phaseText && <span style={{ padding: '1px 5px', borderRadius: 4, background: 'var(--dsw-
|
|
852
|
-
{verdictInfo.scoreText && <span style={{ color: (typeof item.verdict.threshold === 'number' && typeof item.verdict.score === 'number' && item.verdict.score < item.verdict.threshold) ? '
|
|
853
|
-
{verdictInfo.checkpointsText && <span style={{ color: 'var(--dsw-
|
|
854
|
-
{verdictInfo.criteriaText && <span style={{ color: 'var(--dsw-
|
|
855
|
-
{verdictInfo.winnerText && <span style={{ color: 'var(--dsw-
|
|
856
|
-
{item.route && <span style={{ padding: '1px 5px', borderRadius: 4, fontSize: 11,
|
|
857
|
-
{item.stats.usageIncomplete === true && <span style={{ padding: '1px 5px', borderRadius: 4, fontSize: 11,
|
|
867
|
+
{verdictInfo.outcomeText && <span style={{ padding: '1px 5px', borderRadius: 4, fontWeight: 500, fontSize: 11, ...toneChip(verdictInfo.isFailed ? 'error' : item.verdict.outcome === 'tie' ? 'warn' : 'pass') }}>{verdictInfo.outcomeText}</span>}
|
|
868
|
+
{verdictInfo.phaseText && <span style={{ padding: '1px 5px', borderRadius: 4, background: 'var(--dsw-alias-interactive-bg-hover)', color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)' }}>{verdictInfo.phaseText}</span>}
|
|
869
|
+
{verdictInfo.scoreText && <span style={{ color: (typeof item.verdict.threshold === 'number' && typeof item.verdict.score === 'number' && item.verdict.score < item.verdict.threshold) ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-label-primary)' }}>{verdictInfo.scoreText}</span>}
|
|
870
|
+
{verdictInfo.checkpointsText && <span style={{ color: 'var(--dsw-alias-label-secondary)' }}>{verdictInfo.checkpointsText}</span>}
|
|
871
|
+
{verdictInfo.criteriaText && <span style={{ color: 'var(--dsw-alias-label-secondary)' }}>{verdictInfo.criteriaText}</span>}
|
|
872
|
+
{verdictInfo.winnerText && <span style={{ color: 'var(--dsw-alias-label-secondary)' }}>{verdictInfo.winnerText}</span>}
|
|
873
|
+
{item.route && <span style={{ padding: '1px 5px', borderRadius: 4, fontSize: 11, color: 'var(--dsw-alias-label-secondary)', ...toneChip('neutral') }}>{tFormat(t['recent.route.badge'], { stage: item.route.stage, destination: item.route.destination })}</span>}
|
|
874
|
+
{item.stats.usageIncomplete === true && <span style={{ padding: '1px 5px', borderRadius: 4, fontSize: 11, ...toneChip('warn') }}>{t['recent.detail.usageIncomplete']}</span>}
|
|
858
875
|
</div>}
|
|
859
876
|
</div>
|
|
860
877
|
<div style={{ textAlign: 'right' }}><div style={{ fontSize: 12 }}>{duration(item.durationMs)}</div><div style={{ ...muted, marginTop: 3 }}>{dateTime(item.startedAt, lang)}</div></div>
|
|
@@ -863,37 +880,37 @@ export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }:
|
|
|
863
880
|
that opened it: the snapshot used to render after a details block that merely
|
|
864
881
|
happened to be expanded, which read as if it belonged to that block. */}
|
|
865
882
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '6px 0 0 15px' }}>
|
|
866
|
-
<button type="button" aria-expanded={snapshot?.id === item.id} onClick={() => void toggleSnapshot(item.id)} style={{ fontSize: 11, padding: '2px 7px', borderRadius: 5, cursor: 'pointer', color: 'var(--dsw-
|
|
867
|
-
<button type="button" aria-expanded={detailsOpen} onClick={() => setOpenDetails(current => current === item.id ? null : item.id)} style={{ fontSize: 11, padding: '2px 7px', borderRadius: 5, cursor: 'pointer', color: 'var(--dsw-
|
|
883
|
+
<button type="button" aria-expanded={snapshot?.id === item.id} onClick={() => void toggleSnapshot(item.id)} style={{ fontSize: 11, padding: '2px 7px', borderRadius: 5, cursor: 'pointer', color: 'var(--dsw-alias-label-secondary)', background: 'var(--dsw-alias-interactive-bg-hover)', border: '1px solid var(--dsw-alias-border-l2)' }}>{snapshot?.id === item.id ? t['recent.decisionHide'] : t['recent.decision']}</button>
|
|
884
|
+
<button type="button" aria-expanded={detailsOpen} onClick={() => setOpenDetails(current => current === item.id ? null : item.id)} style={{ fontSize: 11, padding: '2px 7px', borderRadius: 5, cursor: 'pointer', color: 'var(--dsw-alias-label-secondary)', background: 'var(--dsw-alias-interactive-bg-hover)', border: '1px solid var(--dsw-alias-border-l2)' }}>{detailsOpen ? t['recent.detailsHide'] : t['recent.details']}</button>
|
|
868
885
|
</div>
|
|
869
886
|
{snapshot?.id === item.id && <div style={panelStyle}>
|
|
870
|
-
{snapshot.error !== undefined && <div style={{ fontSize: 11, color: '
|
|
887
|
+
{snapshot.error !== undefined && <div style={{ fontSize: 11, color: 'var(--dsw-alias-state-error-primary)' }}>{snapshot.error}</div>}
|
|
871
888
|
{snapshot.record === undefined && snapshot.error === undefined && <div style={{ ...muted, fontSize: 11 }}>{t['recent.decisionLoading']}</div>}
|
|
872
889
|
{snapshot.record !== undefined && snapshot.record.calls.length === 0 && <div style={{ ...muted, fontSize: 11 }}>{t['recent.decisionEmpty']}</div>}
|
|
873
890
|
{(snapshot.record?.calls ?? []).map((call, index) => <div key={index} style={{ marginBottom: index === snapshot.record!.calls.length - 1 ? 0 : 10 }}>
|
|
874
|
-
<div style={{ fontSize: 11, color: 'var(--dsw-
|
|
891
|
+
<div style={{ fontSize: 11, color: 'var(--dsw-alias-label-secondary)' }}>{call.label} · {call.channel}{call.score === undefined ? '' : ' · ' + formatPercentage(call.score)}</div>
|
|
875
892
|
<div style={{ fontSize: 11, marginTop: 4 }}>{t['recent.decisionPrompt']}</div>
|
|
876
|
-
<pre style={{ margin: 0, maxHeight: 180, overflow: 'auto', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-word', background: '
|
|
893
|
+
<pre style={{ margin: 0, maxHeight: 180, overflow: 'auto', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-word', background: 'var(--dsw-alias-markdown-code-block)', padding: '6px 8px', borderRadius: 6 }}>{call.prompt}</pre>
|
|
877
894
|
<div style={{ fontSize: 11, marginTop: 4 }}>{t['recent.decisionOutput']}</div>
|
|
878
|
-
<pre style={{ margin: 0, maxHeight: 140, overflow: 'auto', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-word', background: '
|
|
895
|
+
<pre style={{ margin: 0, maxHeight: 140, overflow: 'auto', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-word', background: 'var(--dsw-alias-markdown-code-block)', padding: '6px 8px', borderRadius: 6 }}>{call.output}</pre>
|
|
879
896
|
</div>)}
|
|
880
897
|
</div>}
|
|
881
898
|
{detailsOpen && <div style={panelStyle}>
|
|
882
899
|
{(item.verdict?.criteria?.length ?? 0) > 0 && <div style={{ display: 'grid', gridTemplateColumns: 'minmax(120px,1fr) auto auto', gap: '4px 14px', marginBottom: 8 }}>
|
|
883
|
-
<strong style={{ color: 'var(--dsw-
|
|
884
|
-
<strong style={{ color: 'var(--dsw-
|
|
885
|
-
<strong style={{ color: 'var(--dsw-
|
|
900
|
+
<strong style={{ color: 'var(--dsw-alias-label-secondary)' }}>{t['recent.detail.criterion']}</strong>
|
|
901
|
+
<strong style={{ color: 'var(--dsw-alias-label-secondary)' }}>{t['recent.detail.score']}</strong>
|
|
902
|
+
<strong style={{ color: 'var(--dsw-alias-label-secondary)' }}>{t['recent.detail.threshold']}</strong>
|
|
886
903
|
{item.verdict!.criteria!.map(criterion => {
|
|
887
904
|
const threshold = typeof item.verdict!.threshold === 'number' ? item.verdict!.threshold : undefined
|
|
888
905
|
const missed = threshold !== undefined && criterion.score < threshold
|
|
889
906
|
return <Fragment key={criterion.id}>
|
|
890
907
|
<span>{criterion.id}</span>
|
|
891
|
-
<span style={{ color: missed ? '
|
|
892
|
-
<span style={{ color: 'var(--dsw-
|
|
908
|
+
<span style={{ color: missed ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-success-primary)' }}>{formatPercentage(criterion.score)}</span>
|
|
909
|
+
<span style={{ color: 'var(--dsw-alias-label-secondary)' }}>{threshold === undefined ? '—' : formatPercentage(threshold)}</span>
|
|
893
910
|
</Fragment>
|
|
894
911
|
})}
|
|
895
912
|
</div>}
|
|
896
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, color: 'var(--dsw-
|
|
913
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, color: 'var(--dsw-alias-label-secondary)' }}>
|
|
897
914
|
<span>{tFormat(t['recent.detail.calls'], { calls: compact(item.stats.calls, lang) })}</span>
|
|
898
915
|
<span>{tFormat(t['recent.detail.tokens'], { input: compact(item.stats.inputTokens, lang), cached: compact(item.stats.cachedInputTokens, lang), output: compact(item.stats.outputTokens, lang) })}</span>
|
|
899
916
|
<span>{tFormat(t['recent.detail.scoreCache'], { hits: compact(item.stats.cacheHits, lang), misses: compact(item.stats.cacheMisses, lang) })}</span>
|
|
@@ -911,14 +928,14 @@ export function StatisticsPage({ sessionId, rpc, isGlobal, blankComposerSeat }:
|
|
|
911
928
|
</div>}
|
|
912
929
|
</div>
|
|
913
930
|
})}{(data?.recent.length ?? 0) === 0 && <div style={{ padding: 24, textAlign: 'center', ...muted }}>{t['recent.empty']}</div>}</div></div>
|
|
914
|
-
<div style={{ ...dashboardCard, padding: '18px' }}><strong>{t['models.title']}</strong><div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }}>{(data?.models ?? []).map(model => <div key={model.provider + '\0' + model.model} style={{ padding: '11px 12px', borderRadius: 10, background: 'var(--dsw-
|
|
931
|
+
<div style={{ ...dashboardCard, padding: '18px' }}><strong>{t['models.title']}</strong><div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }}>{(data?.models ?? []).map(model => <div key={model.provider + '\0' + model.model} style={{ padding: '11px 12px', borderRadius: 10, background: 'var(--dsw-alias-interactive-bg-hover)' }}><div style={{ fontWeight: 650, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis' }}>{model.model}</div><div style={{ ...muted, marginTop: 3 }}>{model.provider}</div><div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 9, fontSize: 12 }}><span>{tFormat(t['models.calls'], { calls: compact(model.calls, lang) })}</span><span>{tFormat(t['models.tokens'], { tokens: compact(model.tokens, lang) })}</span><strong>{money(model.estimatedCostUsd)}</strong></div></div>)}{(data?.models.length ?? 0) === 0 && <div style={muted}>{t['models.empty']}</div>}</div></div></section>
|
|
915
932
|
</>}
|
|
916
933
|
</div>
|
|
917
934
|
</main>
|
|
918
935
|
}
|
|
919
936
|
|
|
920
937
|
export function VerifierSidebarIcon({ size, active }: { size: number; active?: boolean }) {
|
|
921
|
-
return <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, color: active ? 'var(--dsw-alias-
|
|
938
|
+
return <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, color: active ? 'var(--dsw-alias-state-business-primary)' : 'currentColor' }}><IconDataOutline16 size={Math.min(18, size)} /></span>
|
|
922
939
|
}
|
|
923
940
|
|
|
924
941
|
export function GlobalVerifierDashboard({ rpc }: { rpc: any }) {
|
|
@@ -976,6 +993,43 @@ async function readVerifierActivity(rpc: any, sessionId: string, signal: AbortSi
|
|
|
976
993
|
} catch { return null }
|
|
977
994
|
}
|
|
978
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Dock-card geometry, copied from the host's own composer-stack cards (Todo/Goal/Queue).
|
|
998
|
+
*
|
|
999
|
+
* Those cards share one width axis — the chat content column minus the composer clearances and the
|
|
1000
|
+
* four dock insets — and the fallbacks below only matter on a host that does not publish the three
|
|
1001
|
+
* custom properties. Without this clamp the status line stretches to the window edge and its text
|
|
1002
|
+
* sits far left of the composer it belongs to, which is exactly the misplacement it is here to fix.
|
|
1003
|
+
*/
|
|
1004
|
+
const activityDock: React.CSSProperties = {
|
|
1005
|
+
boxSizing: 'border-box',
|
|
1006
|
+
width: 'calc(100% - var(--dsh-composer-side-clearance, 16px) - var(--dsh-composer-side-clearance, 16px) - var(--dsh-composer-dock-inset, 8px) - var(--dsh-composer-dock-inset, 8px) - var(--dsh-composer-dock-inset, 8px) - var(--dsh-composer-dock-inset, 8px))',
|
|
1007
|
+
margin: '0 auto',
|
|
1008
|
+
}
|
|
1009
|
+
const activityBar: React.CSSProperties = {
|
|
1010
|
+
boxSizing: 'border-box',
|
|
1011
|
+
display: 'flex',
|
|
1012
|
+
alignItems: 'center',
|
|
1013
|
+
gap: 10,
|
|
1014
|
+
width: '100%',
|
|
1015
|
+
maxWidth: 'calc(var(--dsh-composer-card-max-width, 952px) - 4 * var(--dsh-composer-dock-inset, 8px))',
|
|
1016
|
+
height: 36,
|
|
1017
|
+
margin: '0 auto',
|
|
1018
|
+
padding: '0 12px',
|
|
1019
|
+
border: '0.5px solid var(--dsw-alias-border-l1)',
|
|
1020
|
+
borderRadius: 12,
|
|
1021
|
+
background: 'var(--dsw-specific-tip)',
|
|
1022
|
+
}
|
|
1023
|
+
const activityTextStyle: React.CSSProperties = {
|
|
1024
|
+
minWidth: 0,
|
|
1025
|
+
overflow: 'hidden',
|
|
1026
|
+
textOverflow: 'ellipsis',
|
|
1027
|
+
whiteSpace: 'nowrap',
|
|
1028
|
+
fontSize: 13,
|
|
1029
|
+
lineHeight: '20px',
|
|
1030
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
1031
|
+
}
|
|
1032
|
+
|
|
979
1033
|
/**
|
|
980
1034
|
* The chat-visible half of the automatic stages: what the plugin is doing for this session right now.
|
|
981
1035
|
*
|
|
@@ -1010,11 +1064,13 @@ export function VerifierActivityChip({ session, rpc }: { session?: { sessionId?:
|
|
|
1010
1064
|
return () => { cancelled = true; clearInterval(timer); controller.abort() }
|
|
1011
1065
|
}, [sessionId, running, showing, rpc])
|
|
1012
1066
|
if (rendered === null) return null
|
|
1013
|
-
const
|
|
1067
|
+
const dotState: StateDotState = rendered.tone === 'busy' ? 'ongoing' : rendered.tone === 'ok' ? 'done' : 'error'
|
|
1014
1068
|
return (
|
|
1015
|
-
<div style={
|
|
1016
|
-
<
|
|
1017
|
-
|
|
1069
|
+
<div style={activityDock}>
|
|
1070
|
+
<div style={activityBar} role="status" aria-live="polite">
|
|
1071
|
+
<StateDot state={dotState} size={8} />
|
|
1072
|
+
<span style={activityTextStyle}>{rendered.text}</span>
|
|
1073
|
+
</div>
|
|
1018
1074
|
</div>
|
|
1019
1075
|
)
|
|
1020
1076
|
}
|