@polderlabs/bizar 4.4.12 → 4.5.0
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/bizar-dash/CHANGELOG.md +37 -276
- package/bizar-dash/dist/assets/main-CDFKHzBg.css +1 -0
- package/bizar-dash/dist/assets/main-NYFpS2wY.js +312 -0
- package/bizar-dash/dist/assets/main-NYFpS2wY.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js → mobile--0FBIKX3.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js.map → mobile--0FBIKX3.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-OgRp8VIb.js +352 -0
- package/bizar-dash/dist/assets/mobile-OgRp8VIb.js.map +1 -0
- package/bizar-dash/dist/index.html +3 -3
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/skills/agent-baseline/SKILL.md +80 -0
- package/bizar-dash/skills/bizar/SKILL.md +96 -0
- package/bizar-dash/skills/chat/SKILL.md +74 -0
- package/bizar-dash/skills/lightrag/SKILL.md +75 -0
- package/bizar-dash/skills/minimax/SKILL.md +80 -0
- package/bizar-dash/skills/obsidian/SKILL.md +55 -0
- package/bizar-dash/skills/providers/SKILL.md +75 -0
- package/bizar-dash/skills/sdk/SKILL.md +138 -0
- package/bizar-dash/skills/self-improvement/SKILL.md +53 -0
- package/bizar-dash/skills/skills-cli/SKILL.md +94 -0
- package/bizar-dash/skills/usage/SKILL.md +62 -0
- package/bizar-dash/src/server/api.mjs +12 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +5 -2
- package/bizar-dash/src/server/memory-store.mjs +38 -0
- package/bizar-dash/src/server/minimax-usage-store.mjs +372 -0
- package/bizar-dash/src/server/minimax.mjs +427 -110
- package/bizar-dash/src/server/providers-store.mjs +966 -6
- package/bizar-dash/src/server/routes/config.mjs +52 -1
- package/bizar-dash/src/server/routes/env-vars.mjs +165 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +154 -0
- package/bizar-dash/src/server/routes/memory.mjs +241 -1
- package/bizar-dash/src/server/routes/minimax.mjs +50 -57
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +14 -29
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +205 -3
- package/bizar-dash/src/server/routes/providers.mjs +266 -5
- package/bizar-dash/src/server/routes/skills.mjs +32 -43
- package/bizar-dash/src/server/routes/update.mjs +340 -0
- package/bizar-dash/src/server/routes/usage.mjs +136 -0
- package/bizar-dash/src/server/serve-info.mjs +135 -4
- package/bizar-dash/src/server/server.mjs +4 -0
- package/bizar-dash/src/server/skills-store.mjs +152 -262
- package/bizar-dash/src/web/App.tsx +118 -29
- package/bizar-dash/src/web/components/EnvVarManager.tsx +247 -0
- package/bizar-dash/src/web/components/SettingsSearch.tsx +213 -0
- package/bizar-dash/src/web/components/Topbar.tsx +0 -1
- package/bizar-dash/src/web/components/UsageChart.tsx +250 -0
- package/bizar-dash/src/web/components/UsageTable.tsx +90 -0
- package/bizar-dash/src/web/components/chat/ChatComposer.tsx +21 -25
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +199 -37
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +29 -17
- package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +7 -1
- package/bizar-dash/src/web/components/chat/InfoPanel.tsx +71 -6
- package/bizar-dash/src/web/components/chat/useChat.ts +751 -257
- package/bizar-dash/src/web/lib/api.ts +43 -0
- package/bizar-dash/src/web/main.tsx +1 -0
- package/bizar-dash/src/web/mobile/views/MobileChat.tsx +110 -35
- package/bizar-dash/src/web/styles/chat.css +135 -1
- package/bizar-dash/src/web/styles/main.css +46 -0
- package/bizar-dash/src/web/styles/minimax-usage.css +471 -0
- package/bizar-dash/src/web/styles/settings.css +418 -0
- package/bizar-dash/src/web/styles/skills.css +302 -0
- package/bizar-dash/src/web/styles/tasks.css +288 -0
- package/bizar-dash/src/web/views/Chat.tsx +276 -48
- package/bizar-dash/src/web/views/Config.tsx +3 -2065
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +620 -240
- package/bizar-dash/src/web/views/Settings.tsx +6 -0
- package/bizar-dash/src/web/views/Skills.tsx +208 -260
- package/bizar-dash/src/web/views/Tasks.tsx +348 -1119
- package/bizar-dash/tests/chat-session-create.test.mjs +391 -0
- package/bizar-dash/tests/chat-session-stream.test.mjs +308 -0
- package/bizar-dash/tests/env-vars-store.test.mjs +216 -0
- package/bizar-dash/tests/lightrag-defaults.node.test.mjs +118 -0
- package/bizar-dash/tests/minimax-chat-usage.test.mjs +178 -0
- package/bizar-dash/tests/minimax-usage-store.node.test.mjs +293 -0
- package/bizar-dash/tests/opencode-sessions-detail.test.mjs +12 -9
- package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +479 -3
- package/bizar-dash/tests/providers-store-search.node.test.mjs +166 -0
- package/bizar-dash/tests/skills-list.test.mjs +232 -0
- package/bizar-dash/tests/skills-search.test.mjs +222 -0
- package/bizar-dash/tests/tasks-create.test.mjs +187 -0
- package/bizar-dash/tests/update-check.test.mjs +127 -0
- package/bizar-dash/tests/update-run.test.mjs +266 -0
- package/cli/bin.mjs +345 -4
- package/cli/provision.mjs +118 -4
- package/config/agents/_shared/SKILLS.md +109 -0
- package/package.json +1 -1
- package/bizar-dash/dist/assets/main-BKXEqU1w.css +0 -1
- package/bizar-dash/dist/assets/main-EK_fzXn_.js +0 -352
- package/bizar-dash/dist/assets/main-EK_fzXn_.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js +0 -354
- package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js.map +0 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// src/web/components/UsageChart.tsx
|
|
2
|
+
//
|
|
3
|
+
// Hand-rolled SVG chart. No external chart library — keeps bundle small.
|
|
4
|
+
//
|
|
5
|
+
// Props:
|
|
6
|
+
// series: { labels: string[]; requests: number[]; tokens: number[] }
|
|
7
|
+
// height?: number (default 280)
|
|
8
|
+
// onHover?: (index: number | null, x: number, y: number) => void
|
|
9
|
+
|
|
10
|
+
import { useCallback, useRef, useState } from 'react';
|
|
11
|
+
import { cn } from '../lib/utils';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
series: { labels: string[]; requests: number[]; tokens: number[] };
|
|
15
|
+
height?: number;
|
|
16
|
+
onHover?: (index: number | null, x: number, y: number) => void;
|
|
17
|
+
className?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const PADDING = { top: 16, right: 20, bottom: 40, left: 52 };
|
|
21
|
+
|
|
22
|
+
export function UsageChart({ series, height = 280, onHover, className }: Props) {
|
|
23
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
24
|
+
const [tooltip, setTooltip] = useState<{ index: number; x: number; y: number } | null>(null);
|
|
25
|
+
|
|
26
|
+
const { labels, requests, tokens } = series;
|
|
27
|
+
const n = labels.length;
|
|
28
|
+
if (n === 0) {
|
|
29
|
+
return (
|
|
30
|
+
<div className={cn('usage-chart-empty', className)} style={{ height }}>
|
|
31
|
+
<span>No data for this range</span>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const innerW = 600 - PADDING.left - PADDING.right;
|
|
37
|
+
const innerH = height - PADDING.top - PADDING.bottom;
|
|
38
|
+
|
|
39
|
+
const maxReq = Math.max(...requests, 1);
|
|
40
|
+
const maxTok = Math.max(...tokens, 1);
|
|
41
|
+
// Two separate Y scales (left = requests, right = tokens)
|
|
42
|
+
const barW = Math.max(4, Math.min(24, Math.floor(innerW / n) - 4));
|
|
43
|
+
const gap = (innerW - barW * n) / (n + 1);
|
|
44
|
+
|
|
45
|
+
function barX(i: number) {
|
|
46
|
+
return PADDING.left + gap + i * (barW + gap);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function barH(v: number) {
|
|
50
|
+
return (v / maxReq) * innerH * 0.75;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function lineY(v: number) {
|
|
54
|
+
return PADDING.top + innerH - (v / maxTok) * innerH * 0.75;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Build polyline points for the token line.
|
|
58
|
+
const linePoints = tokens
|
|
59
|
+
.map((v, i) => `${barX(i) + barW / 2},${lineY(v)}`)
|
|
60
|
+
.join(' ');
|
|
61
|
+
|
|
62
|
+
// Y-axis ticks (left, requests).
|
|
63
|
+
const leftTicks = [0, 0.25, 0.5, 0.75, 1].map(t => ({
|
|
64
|
+
y: PADDING.top + innerH - t * innerH * 0.75,
|
|
65
|
+
label: t === 0 ? '0' : `${Math.round(maxReq * t).toLocaleString()}`,
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
// Y-axis ticks (right, tokens).
|
|
69
|
+
const rightTicks = [0, 0.25, 0.5, 0.75, 1].map(t => ({
|
|
70
|
+
y: PADDING.top + innerH - t * innerH * 0.75,
|
|
71
|
+
label: t === 0 ? '0' : `${Math.round(maxTok * t / 1000)}k`,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
// X-axis labels (show at most 7 to avoid crowding).
|
|
75
|
+
const labelStep = Math.max(1, Math.floor(n / 7));
|
|
76
|
+
const xLabels = labels.map((l, i) => ({ i, l, x: barX(i) + barW / 2 })).filter((_, i) => i % labelStep === 0);
|
|
77
|
+
|
|
78
|
+
const handleMouseMove = useCallback(
|
|
79
|
+
(e: React.MouseEvent<SVGSVGElement>) => {
|
|
80
|
+
if (!containerRef.current) return;
|
|
81
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
82
|
+
const svgX = (e.clientX - rect.left) * (600 / rect.width);
|
|
83
|
+
const svgY = (e.clientY - rect.top) * (height / rect.height);
|
|
84
|
+
// Find closest bar.
|
|
85
|
+
const relX = svgX - PADDING.left;
|
|
86
|
+
const i = Math.round((relX - gap / 2) / (barW + gap));
|
|
87
|
+
const clamped = Math.max(0, Math.min(n - 1, i));
|
|
88
|
+
setTooltip({ index: clamped, x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
89
|
+
onHover?.(clamped, svgX, svgY);
|
|
90
|
+
},
|
|
91
|
+
[n, barW, gap, onHover, height],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const handleMouseLeave = useCallback(() => {
|
|
95
|
+
setTooltip(null);
|
|
96
|
+
onHover?.(null, 0, 0);
|
|
97
|
+
}, [onHover]);
|
|
98
|
+
|
|
99
|
+
const tip = tooltip;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div ref={containerRef} className={cn('usage-chart', className)} style={{ position: 'relative' }}>
|
|
103
|
+
<svg
|
|
104
|
+
viewBox={`0 0 600 ${height}`}
|
|
105
|
+
width="100%"
|
|
106
|
+
height={height}
|
|
107
|
+
style={{ display: 'block', overflow: 'visible' }}
|
|
108
|
+
onMouseMove={handleMouseMove}
|
|
109
|
+
onMouseLeave={handleMouseLeave}
|
|
110
|
+
>
|
|
111
|
+
{/* Grid lines */}
|
|
112
|
+
{leftTicks.map((tick, i) => (
|
|
113
|
+
<line
|
|
114
|
+
key={`grid-${i}`}
|
|
115
|
+
x1={PADDING.left}
|
|
116
|
+
y1={tick.y}
|
|
117
|
+
x2={600 - PADDING.right}
|
|
118
|
+
y2={tick.y}
|
|
119
|
+
stroke="var(--border)"
|
|
120
|
+
strokeWidth={1}
|
|
121
|
+
strokeDasharray="4,3"
|
|
122
|
+
opacity={0.5}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
|
|
126
|
+
{/* Left Y axis — requests */}
|
|
127
|
+
{leftTicks.map((tick, i) => (
|
|
128
|
+
<text
|
|
129
|
+
key={`ly-${i}`}
|
|
130
|
+
x={PADDING.left - 6}
|
|
131
|
+
y={tick.y + 4}
|
|
132
|
+
textAnchor="end"
|
|
133
|
+
fontSize={10}
|
|
134
|
+
fill="var(--text-muted)"
|
|
135
|
+
fontFamily="var(--font-mono, ui-monospace, monospace)"
|
|
136
|
+
>
|
|
137
|
+
{tick.label}
|
|
138
|
+
</text>
|
|
139
|
+
))}
|
|
140
|
+
|
|
141
|
+
{/* Right Y axis — tokens */}
|
|
142
|
+
{rightTicks.map((tick, i) => (
|
|
143
|
+
<text
|
|
144
|
+
key={`ry-${i}`}
|
|
145
|
+
x={600 - PADDING.right + 6}
|
|
146
|
+
y={tick.y + 4}
|
|
147
|
+
textAnchor="start"
|
|
148
|
+
fontSize={10}
|
|
149
|
+
fill="var(--text-muted)"
|
|
150
|
+
fontFamily="var(--font-mono, ui-monospace, monospace)"
|
|
151
|
+
>
|
|
152
|
+
{tick.label}
|
|
153
|
+
</text>
|
|
154
|
+
))}
|
|
155
|
+
|
|
156
|
+
{/* Bars — requests */}
|
|
157
|
+
{requests.map((v, i) => {
|
|
158
|
+
const bh = barH(v);
|
|
159
|
+
const bx = barX(i);
|
|
160
|
+
const by = PADDING.top + innerH - bh;
|
|
161
|
+
return (
|
|
162
|
+
<rect
|
|
163
|
+
key={`bar-${i}`}
|
|
164
|
+
x={bx}
|
|
165
|
+
y={by}
|
|
166
|
+
width={barW}
|
|
167
|
+
height={bh}
|
|
168
|
+
rx={2}
|
|
169
|
+
fill={tip?.index === i ? 'var(--accent)' : 'var(--accent)'}
|
|
170
|
+
opacity={tip?.index === i ? 1 : 0.75}
|
|
171
|
+
style={{ transition: 'opacity 120ms ease' }}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
|
|
176
|
+
{/* Token line */}
|
|
177
|
+
<polyline
|
|
178
|
+
points={linePoints}
|
|
179
|
+
fill="none"
|
|
180
|
+
stroke="var(--warning, #d29922)"
|
|
181
|
+
strokeWidth={2}
|
|
182
|
+
strokeLinejoin="round"
|
|
183
|
+
strokeLinecap="round"
|
|
184
|
+
opacity={0.9}
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
{/* Token dots */}
|
|
188
|
+
{tokens.map((v, i) => (
|
|
189
|
+
<circle
|
|
190
|
+
key={`dot-${i}`}
|
|
191
|
+
cx={barX(i) + barW / 2}
|
|
192
|
+
cy={lineY(v)}
|
|
193
|
+
r={tip?.index === i ? 5 : 3}
|
|
194
|
+
fill="var(--warning, #d29922)"
|
|
195
|
+
opacity={tip?.index === i ? 1 : 0.7}
|
|
196
|
+
style={{ transition: 'r 120ms ease' }}
|
|
197
|
+
/>
|
|
198
|
+
))}
|
|
199
|
+
|
|
200
|
+
{/* X axis labels */}
|
|
201
|
+
{xLabels.map(({ i, l, x }) => (
|
|
202
|
+
<text
|
|
203
|
+
key={`xl-${i}`}
|
|
204
|
+
x={x}
|
|
205
|
+
y={PADDING.top + innerH + 18}
|
|
206
|
+
textAnchor="middle"
|
|
207
|
+
fontSize={10}
|
|
208
|
+
fill="var(--text-muted)"
|
|
209
|
+
fontFamily="var(--font-mono, ui-monospace, monospace)"
|
|
210
|
+
>
|
|
211
|
+
{l}
|
|
212
|
+
</text>
|
|
213
|
+
))}
|
|
214
|
+
|
|
215
|
+
{/* X axis line */}
|
|
216
|
+
<line
|
|
217
|
+
x1={PADDING.left}
|
|
218
|
+
y1={PADDING.top + innerH}
|
|
219
|
+
x2={600 - PADDING.right}
|
|
220
|
+
y2={PADDING.top + innerH}
|
|
221
|
+
stroke="var(--border)"
|
|
222
|
+
strokeWidth={1}
|
|
223
|
+
/>
|
|
224
|
+
</svg>
|
|
225
|
+
|
|
226
|
+
{/* Floating tooltip */}
|
|
227
|
+
{tip !== null && (
|
|
228
|
+
<div
|
|
229
|
+
className="usage-chart-tooltip"
|
|
230
|
+
style={{
|
|
231
|
+
left: Math.min(tip.x + 12, 600 - 140),
|
|
232
|
+
top: tip.y - 60,
|
|
233
|
+
}}
|
|
234
|
+
>
|
|
235
|
+
<div className="usage-tooltip-date">{labels[tip.index]}</div>
|
|
236
|
+
<div className="usage-tooltip-row">
|
|
237
|
+
<span className="usage-tooltip-dot" style={{ background: 'var(--accent)' }} />
|
|
238
|
+
<span>Requests:</span>
|
|
239
|
+
<strong>{requests[tip.index].toLocaleString()}</strong>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="usage-tooltip-row">
|
|
242
|
+
<span className="usage-tooltip-dot" style={{ background: 'var(--warning, #d29922)' }} />
|
|
243
|
+
<span>Tokens:</span>
|
|
244
|
+
<strong>{tokens[tip.index].toLocaleString()}</strong>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/web/components/UsageTable.tsx
|
|
2
|
+
//
|
|
3
|
+
// Sortable compact table for per-model usage breakdown.
|
|
4
|
+
//
|
|
5
|
+
// Props:
|
|
6
|
+
// rows: PerModelRow[]
|
|
7
|
+
// sortKey: string
|
|
8
|
+
// sortDir: 'asc' | 'desc'
|
|
9
|
+
// onSort: (key: string) => void
|
|
10
|
+
|
|
11
|
+
import { cn } from '../lib/utils';
|
|
12
|
+
|
|
13
|
+
export type PerModelRow = {
|
|
14
|
+
providerId: string;
|
|
15
|
+
modelId: string;
|
|
16
|
+
requests: number;
|
|
17
|
+
totalTokens: number;
|
|
18
|
+
promptTokens: number;
|
|
19
|
+
completionTokens: number;
|
|
20
|
+
errors: number;
|
|
21
|
+
avgLatencyMs: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
rows: PerModelRow[];
|
|
26
|
+
sortKey: string;
|
|
27
|
+
sortDir: 'asc' | 'desc';
|
|
28
|
+
onSort: (key: string) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function SortHeader({ label, col, sortKey, sortDir, onSort }: {
|
|
32
|
+
label: string; col: string; sortKey: string; sortDir: 'asc' | 'desc'; onSort: (k: string) => void;
|
|
33
|
+
}) {
|
|
34
|
+
const active = sortKey === col;
|
|
35
|
+
return (
|
|
36
|
+
<th
|
|
37
|
+
className={cn('usage-sort-header', active && 'is-active')}
|
|
38
|
+
onClick={() => onSort(col)}
|
|
39
|
+
aria-sort={active ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'}
|
|
40
|
+
>
|
|
41
|
+
<span>{label}</span>
|
|
42
|
+
<span className="usage-sort-icon">
|
|
43
|
+
{active ? (sortDir === 'asc' ? ' ↑' : ' ↓') : ' ↕'}
|
|
44
|
+
</span>
|
|
45
|
+
</th>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function UsageTable({ rows, sortKey, sortDir, onSort }: Props) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="usage-table-wrap">
|
|
52
|
+
<table className="usage-table">
|
|
53
|
+
<thead>
|
|
54
|
+
<tr>
|
|
55
|
+
<SortHeader label="Model" col="modelId" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
56
|
+
<SortHeader label="Requests" col="requests" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
57
|
+
<SortHeader label="Prompt Tok" col="promptTokens" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
58
|
+
<SortHeader label="Compl Tok" col="completionTokens" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
59
|
+
<SortHeader label="Total Tok" col="totalTokens" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
60
|
+
<SortHeader label="Avg Latency" col="avgLatencyMs" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
61
|
+
<SortHeader label="Errors" col="errors" sortKey={sortKey} sortDir={sortDir} onSort={onSort} />
|
|
62
|
+
</tr>
|
|
63
|
+
</thead>
|
|
64
|
+
<tbody>
|
|
65
|
+
{rows.length === 0 ? (
|
|
66
|
+
<tr>
|
|
67
|
+
<td colSpan={7} className="usage-table-empty">No data for this range</td>
|
|
68
|
+
</tr>
|
|
69
|
+
) : (
|
|
70
|
+
rows.map((row) => (
|
|
71
|
+
<tr key={`${row.providerId}::${row.modelId}`}>
|
|
72
|
+
<td className="usage-table-model">
|
|
73
|
+
<code>{row.modelId}</code>
|
|
74
|
+
</td>
|
|
75
|
+
<td className="mono">{row.requests.toLocaleString()}</td>
|
|
76
|
+
<td className="mono">{row.promptTokens.toLocaleString()}</td>
|
|
77
|
+
<td className="mono">{row.completionTokens.toLocaleString()}</td>
|
|
78
|
+
<td className="mono">{row.totalTokens.toLocaleString()}</td>
|
|
79
|
+
<td className="mono">{row.avgLatencyMs}ms</td>
|
|
80
|
+
<td className={cn('mono', row.errors > 0 && 'is-err')}>
|
|
81
|
+
{row.errors > 0 ? row.errors : '—'}
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
))
|
|
85
|
+
)}
|
|
86
|
+
</tbody>
|
|
87
|
+
</table>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
// src/components/chat/ChatComposer.tsx — bottom composer pill (
|
|
1
|
+
// src/components/chat/ChatComposer.tsx — bottom composer pill (v4.2.5).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// All wrapped in a single pill (chat-composer-pill) with rounded ends
|
|
8
|
-
// and a focus-within accent border. The legacy Composer component
|
|
9
|
-
// already provides the textarea, agent chip menu, attachment tags,
|
|
10
|
-
// and slash-command suggestion dropdown — the redesign simply nests
|
|
11
|
-
// the Composer inside the new pill chrome and adds the hint row
|
|
12
|
-
// (⏎ / ⇧⏎ / /) underneath.
|
|
13
|
-
//
|
|
14
|
-
// Props match the original FloatingComposer EXACTLY (so Chat.tsx +
|
|
15
|
-
// MobileChat.tsx don't need any changes).
|
|
3
|
+
// v4.2.5 — small refinement: the pill's border picks up the
|
|
4
|
+
// activeSource color (opencode → accent border; bizar → neutral),
|
|
5
|
+
// so the user has visual confirmation that their next message will
|
|
6
|
+
// land in the right backend.
|
|
16
7
|
|
|
17
8
|
import { useState } from 'react';
|
|
18
9
|
import { Composer } from './Composer';
|
|
@@ -32,31 +23,29 @@ interface Props {
|
|
|
32
23
|
onPickSuggestion: (cmd: string) => void;
|
|
33
24
|
agents: Array<{ name: string }>;
|
|
34
25
|
onAttach: () => void;
|
|
35
|
-
/**
|
|
36
|
-
|
|
26
|
+
/** Which stream will receive the next message. */
|
|
27
|
+
activeSource?: 'bizar' | 'opencode' | null;
|
|
28
|
+
/** Backward-compat — used to be page-level panel toggles. Ignored. */
|
|
37
29
|
sessionsOpen?: boolean;
|
|
38
30
|
/** Backward-compat — see sessionsOpen. */
|
|
39
31
|
infoOpen?: boolean;
|
|
40
32
|
}
|
|
41
33
|
|
|
42
34
|
export function ChatComposer(props: Props) {
|
|
43
|
-
const { text, sending, onSend } = props;
|
|
35
|
+
const { text, sending, onSend, activeSource } = props;
|
|
44
36
|
const [takingOff, setTakingOff] = useState(false);
|
|
45
37
|
|
|
46
|
-
// Wrap onSend with a brief takeoff animation — a small CSS hook on
|
|
47
|
-
// the wrapper pill. We don't animate a specific element here; the
|
|
48
|
-
// pill itself gets the brief `takeoff` class so the send button
|
|
49
|
-
// (the last child of Composer) gets the animation if the design
|
|
50
|
-
// wants it.
|
|
51
38
|
const handleSend = () => {
|
|
52
|
-
if (!text.trim()) return;
|
|
39
|
+
if (!text.trim() || sending) return;
|
|
53
40
|
setTakingOff(true);
|
|
54
41
|
onSend();
|
|
55
42
|
window.setTimeout(() => setTakingOff(false), 320);
|
|
56
43
|
};
|
|
57
44
|
|
|
58
45
|
return (
|
|
59
|
-
<div
|
|
46
|
+
<div
|
|
47
|
+
className={`chat-composer-wrap chat-composer-source-${activeSource ?? 'none'}`}
|
|
48
|
+
>
|
|
60
49
|
<div className={`chat-composer-pill${takingOff ? ' takeoff' : ''}`}>
|
|
61
50
|
<Composer {...props} onSend={handleSend} />
|
|
62
51
|
</div>
|
|
@@ -70,7 +59,14 @@ export function ChatComposer(props: Props) {
|
|
|
70
59
|
<span>
|
|
71
60
|
<kbd>/</kbd> commands
|
|
72
61
|
</span>
|
|
62
|
+
{activeSource === 'opencode' && (
|
|
63
|
+
<span className="chat-composer-source-hint">→ opencode</span>
|
|
64
|
+
)}
|
|
65
|
+
{activeSource === 'bizar' && (
|
|
66
|
+
<span className="chat-composer-source-hint">→ bizar chat</span>
|
|
67
|
+
)}
|
|
68
|
+
{sending && <span className="chat-composer-source-hint">sending…</span>}
|
|
73
69
|
</div>
|
|
74
70
|
</div>
|
|
75
71
|
);
|
|
76
|
-
}
|
|
72
|
+
}
|