@polderlabs/bizar 4.4.13 → 4.5.1
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/__vite-browser-external-BIHI7g3E.js +2 -0
- package/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js.map +1 -0
- package/bizar-dash/dist/assets/main-eWZ4NlCL.css +1 -0
- package/bizar-dash/dist/assets/main-usWhlPWa.js +362 -0
- package/bizar-dash/dist/assets/main-usWhlPWa.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js → mobile-DQLFCjwJ.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-DSb-t42Y.js.map → mobile-DQLFCjwJ.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-O6ANdD4W.js +352 -0
- package/bizar-dash/dist/assets/mobile-O6ANdD4W.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 +116 -0
- package/bizar-dash/skills/chat/SKILL.md +74 -0
- package/bizar-dash/skills/headroom/SKILL.md +94 -0
- package/bizar-dash/skills/lightrag/SKILL.md +86 -0
- package/bizar-dash/skills/minimax/SKILL.md +80 -0
- package/bizar-dash/skills/obsidian/SKILL.md +68 -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 +18 -0
- package/bizar-dash/src/server/headroom.mjs +645 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +272 -2
- package/bizar-dash/src/server/memory-obsidian.mjs +230 -0
- package/bizar-dash/src/server/memory-store.mjs +189 -0
- package/bizar-dash/src/server/minimax-usage-store.mjs +372 -0
- package/bizar-dash/src/server/minimax.mjs +196 -5
- package/bizar-dash/src/server/providers-store.mjs +956 -0
- package/bizar-dash/src/server/routes/_shared.mjs +17 -0
- 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/headroom.mjs +126 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +154 -0
- package/bizar-dash/src/server/routes/memory.mjs +668 -1
- 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 +20 -0
- package/bizar-dash/src/server/skills-store.mjs +152 -262
- package/bizar-dash/src/web/App.tsx +120 -29
- package/bizar-dash/src/web/components/EnvVarManager.tsx +247 -0
- package/bizar-dash/src/web/components/HeadroomSettings.tsx +418 -0
- package/bizar-dash/src/web/components/HeadroomStatus.tsx +158 -0
- package/bizar-dash/src/web/components/SettingsSearch.tsx +213 -0
- package/bizar-dash/src/web/components/Topbar.tsx +2 -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/lib/types.ts +16 -0
- package/bizar-dash/src/web/main.tsx +2 -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/memory.css +955 -0
- package/bizar-dash/src/web/styles/minimax-usage.css +335 -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/Memory.tsx +140 -0
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +476 -461
- package/bizar-dash/src/web/views/Overview.tsx +3 -0
- package/bizar-dash/src/web/views/Settings.tsx +36 -0
- package/bizar-dash/src/web/views/Skills.tsx +208 -260
- package/bizar-dash/src/web/views/Tasks.tsx +348 -1119
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +289 -0
- package/bizar-dash/src/web/views/memory/GitSyncPanel.tsx +220 -0
- package/bizar-dash/src/web/views/memory/LightragPanel.tsx +307 -0
- package/bizar-dash/src/web/views/memory/MemoryOverview.tsx +354 -0
- package/bizar-dash/src/web/views/memory/MemoryStatusCard.tsx +160 -0
- package/bizar-dash/src/web/views/memory/ObsidianPanel.tsx +642 -0
- package/bizar-dash/src/web/views/memory/SemanticSearchPanel.tsx +194 -0
- 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/headroom-install.test.mjs +173 -0
- package/bizar-dash/tests/headroom-settings.test.mjs +126 -0
- package/bizar-dash/tests/headroom-status.test.mjs +117 -0
- package/bizar-dash/tests/lightrag-defaults.node.test.mjs +118 -0
- package/bizar-dash/tests/memory-lightrag-extended.test.mjs +162 -0
- package/bizar-dash/tests/memory-obsidian.test.mjs +269 -0
- package/bizar-dash/tests/memory-tab.test.mjs +322 -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/mod-upgrade.node.test.mjs +1 -1
- 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/submit-feedback.test.mjs +6 -6
- 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 +247 -1
- 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-BB5mJurD.js +0 -352
- package/bizar-dash/dist/assets/main-BB5mJurD.js.map +0 -1
- package/bizar-dash/dist/assets/main-BsnQLXdh.css +0 -1
- package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js +0 -354
- package/bizar-dash/dist/assets/mobile-Dl1q7Cyq.js.map +0 -1
|
@@ -1,29 +1,77 @@
|
|
|
1
|
-
// src/components/chat/ChatInfoPanel.tsx — right info panel (
|
|
1
|
+
// src/components/chat/ChatInfoPanel.tsx — right info panel (v4.2.5).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// any changes).
|
|
3
|
+
// v4.2.5 — overhauled: shows session metadata (id, agent, model,
|
|
4
|
+
// provider, message count), the current `activeSource`, the model
|
|
5
|
+
// in use, a section for usage/cost placeholders (filled in by the
|
|
6
|
+
// MiniMax usage module when present), and rename / delete / export
|
|
7
|
+
// actions wired to the parent.
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import { useEffect, useState } from 'react';
|
|
10
|
+
import {
|
|
11
|
+
MessageSquare,
|
|
12
|
+
Bot,
|
|
13
|
+
Server,
|
|
14
|
+
Terminal,
|
|
15
|
+
Pencil,
|
|
16
|
+
Trash2,
|
|
17
|
+
Download,
|
|
18
|
+
CircleDollarSign,
|
|
19
|
+
} from 'lucide-react';
|
|
20
|
+
import type { ChatMessage } from '../../lib/types';
|
|
11
21
|
|
|
12
22
|
interface Props {
|
|
13
23
|
sessionId: string;
|
|
14
|
-
messages:
|
|
24
|
+
messages: ChatMessage[];
|
|
15
25
|
pinned: Set<number>;
|
|
16
26
|
agent: string;
|
|
17
27
|
model: string;
|
|
18
28
|
agents: Array<{ name: string; model?: string; mode?: string }>;
|
|
19
29
|
mcps: Array<{ id: string; command?: string; enabled?: boolean }>;
|
|
20
30
|
allCommands: Array<{ cmd: string; desc: string; mod?: string }>;
|
|
31
|
+
/** Which stream is currently displayed. */
|
|
32
|
+
activeSource?: 'bizar' | 'opencode' | null;
|
|
33
|
+
/** Optional rename/delete/export handlers wired by Chat.tsx. */
|
|
34
|
+
onRename?: () => void;
|
|
35
|
+
onDelete?: () => void;
|
|
36
|
+
onExport?: () => void;
|
|
37
|
+
/** Per-action busy flags — disable buttons while pending. */
|
|
38
|
+
busy?: { rename?: boolean; delete?: boolean };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface UsageRow {
|
|
42
|
+
promptTokens: number;
|
|
43
|
+
completionTokens: number;
|
|
44
|
+
totalTokens: number;
|
|
45
|
+
costUsd: number;
|
|
21
46
|
}
|
|
22
47
|
|
|
23
48
|
function formatNumber(n: number): string {
|
|
24
49
|
return n.toLocaleString();
|
|
25
50
|
}
|
|
26
51
|
|
|
52
|
+
/** Map a model id like "opencode/deepseek-v4-flash-free" → "opencode". */
|
|
53
|
+
function providerFromModel(model: string): string {
|
|
54
|
+
if (!model) return '';
|
|
55
|
+
if (!model.includes('/')) return '';
|
|
56
|
+
return model.split('/')[0];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Map a model id to a cost (USD/M-token) — placeholder rates; the
|
|
60
|
+
* MiniMax usage module supplies real numbers where available. */
|
|
61
|
+
function approxCostPer1M(model: string): number {
|
|
62
|
+
if (!model) return 0;
|
|
63
|
+
const m = model.toLowerCase();
|
|
64
|
+
if (m.includes('opus')) return 15;
|
|
65
|
+
if (m.includes('sonnet')) return 3;
|
|
66
|
+
if (m.includes('haiku')) return 0.8;
|
|
67
|
+
if (m.includes('gpt-4o')) return 5;
|
|
68
|
+
if (m.includes('gpt-4')) return 10;
|
|
69
|
+
if (m.includes('deepseek')) return 0.27;
|
|
70
|
+
if (m.includes('minimax')) return 0.3;
|
|
71
|
+
if (m.includes('llama-3.3-70b')) return 0.59;
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
27
75
|
export function ChatInfoPanel({
|
|
28
76
|
sessionId,
|
|
29
77
|
messages,
|
|
@@ -33,22 +81,74 @@ export function ChatInfoPanel({
|
|
|
33
81
|
agents,
|
|
34
82
|
mcps,
|
|
35
83
|
allCommands,
|
|
84
|
+
activeSource = 'bizar',
|
|
85
|
+
onRename,
|
|
86
|
+
onDelete,
|
|
87
|
+
onExport,
|
|
88
|
+
busy,
|
|
36
89
|
}: Props) {
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
90
|
+
// Best-effort: pull usage summary from the MiniMax usage endpoint
|
|
91
|
+
// when the session is opencode. Falls back silently if the module
|
|
92
|
+
// isn't installed yet.
|
|
93
|
+
const [usage, setUsage] = useState<UsageRow | null>(null);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
let cancelled = false;
|
|
96
|
+
(async () => {
|
|
97
|
+
try {
|
|
98
|
+
const res = await fetch('/api/usage?range=24h', {
|
|
99
|
+
headers: { Accept: 'application/json' },
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) return;
|
|
102
|
+
const body = (await res.json()) as UsageRow;
|
|
103
|
+
if (!cancelled) setUsage(body);
|
|
104
|
+
} catch {
|
|
105
|
+
/* ignore — usage is non-essential */
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
return () => {
|
|
109
|
+
cancelled = true;
|
|
110
|
+
};
|
|
111
|
+
}, [sessionId]);
|
|
112
|
+
|
|
41
113
|
const msgCount = messages?.length ?? 0;
|
|
42
114
|
const pinnedCount = pinned?.size ?? 0;
|
|
43
|
-
|
|
115
|
+
|
|
116
|
+
// Estimate tokens / cost from message length when no real usage
|
|
117
|
+
// data is available. Real numbers come from the MiniMax module.
|
|
118
|
+
const totalChars = messages.reduce(
|
|
119
|
+
(n, m) => n + ((m.content || m.message || '') as string).length,
|
|
120
|
+
0,
|
|
121
|
+
);
|
|
122
|
+
const approxTokens = Math.round(totalChars / 4);
|
|
123
|
+
const rate = approxCostPer1M(model);
|
|
124
|
+
const approxCostUsd = (approxTokens / 1_000_000) * rate;
|
|
125
|
+
const tokensUsed = usage?.totalTokens ?? approxTokens;
|
|
126
|
+
const costUsd = usage?.costUsd ?? approxCostUsd;
|
|
44
127
|
const tokensTotal = 128_000;
|
|
45
|
-
const
|
|
128
|
+
const provider = providerFromModel(model);
|
|
46
129
|
|
|
47
130
|
return (
|
|
48
131
|
<aside className="chat-info">
|
|
49
132
|
<div className="chat-info-section">
|
|
50
|
-
<
|
|
51
|
-
|
|
133
|
+
<div className="chat-info-section-head">
|
|
134
|
+
<h4>
|
|
135
|
+
<MessageSquare
|
|
136
|
+
size={11}
|
|
137
|
+
style={{ marginRight: 4, verticalAlign: -1 }}
|
|
138
|
+
aria-hidden
|
|
139
|
+
/>{' '}
|
|
140
|
+
Session
|
|
141
|
+
</h4>
|
|
142
|
+
<span
|
|
143
|
+
className={`chat-source-badge chat-source-${activeSource ?? 'none'}`}
|
|
144
|
+
style={{ marginLeft: 'auto' }}
|
|
145
|
+
>
|
|
146
|
+
{activeSource === 'opencode' ? 'opencode' : 'bizar chat'}
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="chat-info-mono chat-ellipsis" title={sessionId}>
|
|
150
|
+
{sessionId || 'Live'}
|
|
151
|
+
</div>
|
|
52
152
|
<div className="chat-info-mono">
|
|
53
153
|
{formatNumber(msgCount)} message{msgCount === 1 ? '' : 's'}
|
|
54
154
|
{pinnedCount > 0 && ` · ${pinnedCount} pinned`}
|
|
@@ -56,15 +156,24 @@ export function ChatInfoPanel({
|
|
|
56
156
|
</div>
|
|
57
157
|
|
|
58
158
|
<div className="chat-info-section">
|
|
59
|
-
<h4>
|
|
60
|
-
|
|
159
|
+
<h4>
|
|
160
|
+
<Bot size={11} style={{ marginRight: 4, verticalAlign: -1 }} aria-hidden />{' '}
|
|
161
|
+
Agent
|
|
162
|
+
</h4>
|
|
163
|
+
<div className="chat-info-value">{agent || '—'}</div>
|
|
61
164
|
</div>
|
|
62
165
|
|
|
63
166
|
<div className="chat-info-section">
|
|
64
|
-
<h4>
|
|
167
|
+
<h4>
|
|
168
|
+
<Server size={11} style={{ marginRight: 4, verticalAlign: -1 }} aria-hidden />{' '}
|
|
169
|
+
Model
|
|
170
|
+
</h4>
|
|
65
171
|
<div className="chat-mono chat-ellipsis" title={model}>
|
|
66
172
|
{model || '—'}
|
|
67
173
|
</div>
|
|
174
|
+
{provider && (
|
|
175
|
+
<div className="chat-info-mono">provider · {provider}</div>
|
|
176
|
+
)}
|
|
68
177
|
</div>
|
|
69
178
|
|
|
70
179
|
<div className="chat-info-section">
|
|
@@ -75,20 +184,36 @@ export function ChatInfoPanel({
|
|
|
75
184
|
<div className="chat-info-bar" aria-hidden>
|
|
76
185
|
<div
|
|
77
186
|
className="chat-info-bar-fill"
|
|
78
|
-
style={{
|
|
187
|
+
style={{
|
|
188
|
+
width: `${Math.min(100, (tokensUsed / Math.max(1, tokensTotal)) * 100)}%`,
|
|
189
|
+
}}
|
|
79
190
|
/>
|
|
80
191
|
</div>
|
|
81
192
|
</div>
|
|
82
193
|
|
|
83
194
|
<div className="chat-info-section">
|
|
84
|
-
<h4>
|
|
195
|
+
<h4>
|
|
196
|
+
<CircleDollarSign
|
|
197
|
+
size={11}
|
|
198
|
+
style={{ marginRight: 4, verticalAlign: -1 }}
|
|
199
|
+
aria-hidden
|
|
200
|
+
/>{' '}
|
|
201
|
+
Cost
|
|
202
|
+
</h4>
|
|
85
203
|
<div className="chat-mono">${costUsd.toFixed(4)}</div>
|
|
204
|
+
{!usage && (
|
|
205
|
+
<div className="chat-info-mono">approx · live from MiniMax</div>
|
|
206
|
+
)}
|
|
86
207
|
</div>
|
|
87
208
|
|
|
88
209
|
<div className="chat-info-section">
|
|
89
210
|
<h4>
|
|
90
|
-
<Bot
|
|
91
|
-
|
|
211
|
+
<Bot
|
|
212
|
+
size={11}
|
|
213
|
+
style={{ marginRight: 4, verticalAlign: -1 }}
|
|
214
|
+
aria-hidden
|
|
215
|
+
/>{' '}
|
|
216
|
+
Attached agents
|
|
92
217
|
</h4>
|
|
93
218
|
{agents?.length ? (
|
|
94
219
|
<div className="chat-info-agents">
|
|
@@ -105,7 +230,12 @@ export function ChatInfoPanel({
|
|
|
105
230
|
|
|
106
231
|
<div className="chat-info-section">
|
|
107
232
|
<h4>
|
|
108
|
-
<Server
|
|
233
|
+
<Server
|
|
234
|
+
size={11}
|
|
235
|
+
style={{ marginRight: 4, verticalAlign: -1 }}
|
|
236
|
+
aria-hidden
|
|
237
|
+
/>{' '}
|
|
238
|
+
MCPs
|
|
109
239
|
</h4>
|
|
110
240
|
{mcps?.length ? (
|
|
111
241
|
<div className="chat-info-agents">
|
|
@@ -122,23 +252,55 @@ export function ChatInfoPanel({
|
|
|
122
252
|
|
|
123
253
|
<div className="chat-info-section">
|
|
124
254
|
<h4>
|
|
125
|
-
<Terminal
|
|
126
|
-
commands
|
|
127
|
-
</h4>
|
|
128
|
-
<div className="chat-info-mono">{allCommands?.length ?? 0} available</div>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
<div className="chat-info-section">
|
|
132
|
-
<h4>
|
|
133
|
-
<MessageSquare
|
|
255
|
+
<Terminal
|
|
134
256
|
size={11}
|
|
135
257
|
style={{ marginRight: 4, verticalAlign: -1 }}
|
|
136
258
|
aria-hidden
|
|
137
259
|
/>{' '}
|
|
138
|
-
|
|
260
|
+
Slash commands
|
|
139
261
|
</h4>
|
|
140
|
-
<div className="chat-info-mono">
|
|
262
|
+
<div className="chat-info-mono">{allCommands?.length ?? 0} available</div>
|
|
141
263
|
</div>
|
|
264
|
+
|
|
265
|
+
{(onRename || onDelete || onExport) && (
|
|
266
|
+
<div className="chat-info-section">
|
|
267
|
+
<h4>Actions</h4>
|
|
268
|
+
<div className="chat-info-actions">
|
|
269
|
+
{onRename && (
|
|
270
|
+
<button
|
|
271
|
+
type="button"
|
|
272
|
+
className="btn btn-ghost btn-sm"
|
|
273
|
+
onClick={onRename}
|
|
274
|
+
disabled={busy?.rename}
|
|
275
|
+
title="Rename session"
|
|
276
|
+
>
|
|
277
|
+
<Pencil size={12} aria-hidden /> Rename
|
|
278
|
+
</button>
|
|
279
|
+
)}
|
|
280
|
+
{onExport && (
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
className="btn btn-ghost btn-sm"
|
|
284
|
+
onClick={onExport}
|
|
285
|
+
title="Export transcript"
|
|
286
|
+
>
|
|
287
|
+
<Download size={12} aria-hidden /> Export
|
|
288
|
+
</button>
|
|
289
|
+
)}
|
|
290
|
+
{onDelete && (
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
className="btn btn-ghost btn-sm btn-danger"
|
|
294
|
+
onClick={onDelete}
|
|
295
|
+
disabled={busy?.delete}
|
|
296
|
+
title="Delete session"
|
|
297
|
+
>
|
|
298
|
+
<Trash2 size={12} aria-hidden /> Delete
|
|
299
|
+
</button>
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
142
304
|
</aside>
|
|
143
305
|
);
|
|
144
|
-
}
|
|
306
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// src/components/chat/ChatThread.tsx — message list, welcome screen, loading skeleton.
|
|
2
|
+
//
|
|
3
|
+
// v4.2.5 — accepts `activeSource` so the loading skeleton can hint
|
|
4
|
+
// which stream is loading and the welcome variant for "opencode"
|
|
5
|
+
// can differ from the local chat welcome.
|
|
2
6
|
|
|
3
7
|
import { useEffect, useRef } from 'react';
|
|
4
8
|
import { LoadingSkeleton } from './LoadingSkeleton';
|
|
@@ -19,6 +23,12 @@ interface Props {
|
|
|
19
23
|
activeProject: { name: string } | null;
|
|
20
24
|
sessionId: string;
|
|
21
25
|
pinned: Set<number>;
|
|
26
|
+
/** Which stream currently owns the thread — purely cosmetic, lets
|
|
27
|
+
* the empty/loading states be slightly more specific. The type
|
|
28
|
+
* uses `unknown` so we don't pull in a tight coupling; the value
|
|
29
|
+
* is reflected back into the loading skeleton and welcome screen
|
|
30
|
+
* via data attributes so CSS can style accordingly. */
|
|
31
|
+
activeSource?: 'bizar' | 'opencode' | null;
|
|
22
32
|
onPickSuggestion: (text: string) => void;
|
|
23
33
|
onCopy: (m: Message) => void;
|
|
24
34
|
onDelete: (idx: number) => void;
|
|
@@ -32,6 +42,7 @@ export function ChatThread({
|
|
|
32
42
|
activeProject,
|
|
33
43
|
sessionId,
|
|
34
44
|
pinned,
|
|
45
|
+
activeSource,
|
|
35
46
|
onPickSuggestion,
|
|
36
47
|
onCopy,
|
|
37
48
|
onDelete,
|
|
@@ -40,19 +51,12 @@ export function ChatThread({
|
|
|
40
51
|
}: Props) {
|
|
41
52
|
const innerRef = useRef<HTMLDivElement>(null);
|
|
42
53
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
54
|
+
// Only auto-scroll when we own the scroll container (i.e. when
|
|
55
|
+
// rendered directly as the message scroller). When embedded
|
|
45
56
|
// inside Chat.tsx's chat-thread-scroll, the parent handles scroll.
|
|
46
|
-
// We detect this by checking if our closest scroll ancestor is
|
|
47
|
-
// ourselves; if so, auto-scroll. Otherwise, no-op.
|
|
48
57
|
useEffect(() => {
|
|
49
58
|
const el = innerRef.current;
|
|
50
59
|
if (!el) return;
|
|
51
|
-
// If we ARE the scroll container (legacy / mobile path), scroll.
|
|
52
|
-
// If a parent .chat-thread-scroll wraps us, the parent scrolls.
|
|
53
|
-
const isScroller = el.scrollHeight > el.clientHeight && el.matches(':scope');
|
|
54
|
-
// Simpler heuristic: if no `.chat-thread-scroll` ancestor exists,
|
|
55
|
-
// auto-scroll. Otherwise let the parent handle it.
|
|
56
60
|
let p: HTMLElement | null = el.parentElement;
|
|
57
61
|
let wrapped = false;
|
|
58
62
|
while (p) {
|
|
@@ -63,13 +67,13 @@ export function ChatThread({
|
|
|
63
67
|
p = p.parentElement;
|
|
64
68
|
}
|
|
65
69
|
if (!wrapped) el.scrollTop = el.scrollHeight;
|
|
66
|
-
// isScroller is referenced for clarity; suppress unused warning.
|
|
67
|
-
void isScroller;
|
|
68
70
|
}, [messages]);
|
|
69
71
|
|
|
72
|
+
const stream: 'bizar' | 'opencode' = activeSource === 'opencode' ? 'opencode' : 'bizar';
|
|
73
|
+
|
|
70
74
|
if (loading) {
|
|
71
75
|
return (
|
|
72
|
-
<div className="chat-thread legacy" ref={innerRef}>
|
|
76
|
+
<div className="chat-thread legacy" ref={innerRef} data-stream={stream}>
|
|
73
77
|
<LoadingSkeleton count={3} />
|
|
74
78
|
</div>
|
|
75
79
|
);
|
|
@@ -77,16 +81,24 @@ export function ChatThread({
|
|
|
77
81
|
|
|
78
82
|
if (!activeProject) {
|
|
79
83
|
return (
|
|
80
|
-
<div className="chat-thread legacy" ref={innerRef}>
|
|
81
|
-
<WelcomeScreen
|
|
84
|
+
<div className="chat-thread legacy" ref={innerRef} data-stream={stream}>
|
|
85
|
+
<WelcomeScreen
|
|
86
|
+
variant="no-project"
|
|
87
|
+
projectName=""
|
|
88
|
+
onPickSuggestion={onPickSuggestion}
|
|
89
|
+
/>
|
|
82
90
|
</div>
|
|
83
91
|
);
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
if (messages.length === 0) {
|
|
87
95
|
return (
|
|
88
|
-
<div className="chat-thread legacy" ref={innerRef}>
|
|
89
|
-
<WelcomeScreen
|
|
96
|
+
<div className="chat-thread legacy" ref={innerRef} data-stream={stream}>
|
|
97
|
+
<WelcomeScreen
|
|
98
|
+
variant="empty"
|
|
99
|
+
projectName={activeProject.name}
|
|
100
|
+
onPickSuggestion={onPickSuggestion}
|
|
101
|
+
/>
|
|
90
102
|
</div>
|
|
91
103
|
);
|
|
92
104
|
}
|
|
@@ -106,4 +118,4 @@ export function ChatThread({
|
|
|
106
118
|
))}
|
|
107
119
|
</div>
|
|
108
120
|
);
|
|
109
|
-
}
|
|
121
|
+
}
|
|
@@ -19,11 +19,17 @@ interface Props {
|
|
|
19
19
|
onAttach: () => void;
|
|
20
20
|
sessionsOpen: boolean;
|
|
21
21
|
infoOpen: boolean;
|
|
22
|
+
/** Which stream will receive the next message — propagates the
|
|
23
|
+
* pill border color and a small "→ opencode" hint. */
|
|
24
|
+
activeSource?: 'bizar' | 'opencode' | null;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export function FloatingComposer(props: Props) {
|
|
28
|
+
const { activeSource } = props;
|
|
25
29
|
return (
|
|
26
|
-
<div
|
|
30
|
+
<div
|
|
31
|
+
className={`chat-composer-wrap chat-composer-source-${activeSource ?? 'none'} legacy`}
|
|
32
|
+
>
|
|
27
33
|
<div className="chat-composer-pill">
|
|
28
34
|
<Composer {...props} />
|
|
29
35
|
</div>
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
// src/components/chat/InfoPanel.tsx — compact
|
|
1
|
+
// src/components/chat/InfoPanel.tsx — mobile-compact info sidebar.
|
|
2
|
+
//
|
|
3
|
+
// v4.2.5 — overhauled: shows activeSource badge, message count,
|
|
4
|
+
// session metadata, model/provider, and rename/delete actions for
|
|
5
|
+
// the active session.
|
|
2
6
|
|
|
3
7
|
import { useState } from 'react';
|
|
4
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
MessageSquare,
|
|
10
|
+
Bot,
|
|
11
|
+
Server,
|
|
12
|
+
Terminal,
|
|
13
|
+
ChevronDown,
|
|
14
|
+
Trash2,
|
|
15
|
+
Pencil,
|
|
16
|
+
} from 'lucide-react';
|
|
5
17
|
|
|
6
18
|
interface Props {
|
|
7
19
|
sessionId: string;
|
|
@@ -12,6 +24,11 @@ interface Props {
|
|
|
12
24
|
agents: Array<{ name: string; model?: string; mode?: string }>;
|
|
13
25
|
mcps: Array<{ id: string; command?: string; enabled?: boolean }>;
|
|
14
26
|
allCommands: Array<{ cmd: string; desc: string; mod?: string }>;
|
|
27
|
+
activeSource?: 'bizar' | 'opencode' | null;
|
|
28
|
+
/** Optional rename handler — when omitted the rename button is hidden. */
|
|
29
|
+
onRename?: () => void;
|
|
30
|
+
/** Optional delete handler — when omitted the delete button is hidden. */
|
|
31
|
+
onDelete?: () => void;
|
|
15
32
|
}
|
|
16
33
|
|
|
17
34
|
export function InfoPanel({
|
|
@@ -23,6 +40,9 @@ export function InfoPanel({
|
|
|
23
40
|
agents,
|
|
24
41
|
mcps,
|
|
25
42
|
allCommands,
|
|
43
|
+
activeSource = 'bizar',
|
|
44
|
+
onRename,
|
|
45
|
+
onDelete,
|
|
26
46
|
}: Props) {
|
|
27
47
|
const [showAllAgents, setShowAllAgents] = useState(false);
|
|
28
48
|
const [showCommands, setShowCommands] = useState(false);
|
|
@@ -30,11 +50,21 @@ export function InfoPanel({
|
|
|
30
50
|
const visibleAgents = showAllAgents ? agents : agents.slice(0, 4);
|
|
31
51
|
const visibleCommands = showCommands ? allCommands : allCommands.slice(0, 4);
|
|
32
52
|
|
|
53
|
+
// Derive a friendly provider from the model id (e.g.
|
|
54
|
+
// "opencode/deepseek-v4-flash-free" → provider "opencode").
|
|
55
|
+
const provider = model.includes('/') ? model.split('/')[0] : '';
|
|
56
|
+
|
|
33
57
|
return (
|
|
34
58
|
<div className="chat-info">
|
|
35
59
|
<div className="chat-info-card">
|
|
36
60
|
<div className="chat-info-card-title">
|
|
37
61
|
<MessageSquare size={14} /> Session
|
|
62
|
+
<span
|
|
63
|
+
className={`chat-source-badge chat-source-${activeSource ?? 'none'}`}
|
|
64
|
+
style={{ marginLeft: 'auto' }}
|
|
65
|
+
>
|
|
66
|
+
{activeSource === 'opencode' ? 'opencode' : 'bizar chat'}
|
|
67
|
+
</span>
|
|
38
68
|
</div>
|
|
39
69
|
<dl className="env-table">
|
|
40
70
|
<dt>id</dt>
|
|
@@ -46,8 +76,38 @@ export function InfoPanel({
|
|
|
46
76
|
<dt>Agent</dt>
|
|
47
77
|
<dd className="mono">{agent || '—'}</dd>
|
|
48
78
|
<dt>Model</dt>
|
|
49
|
-
<dd className="mono ellipsis" title={model}>
|
|
79
|
+
<dd className="mono ellipsis" title={model}>
|
|
80
|
+
{model || '—'}
|
|
81
|
+
</dd>
|
|
82
|
+
{provider && (
|
|
83
|
+
<>
|
|
84
|
+
<dt>Provider</dt>
|
|
85
|
+
<dd className="mono">{provider}</dd>
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
50
88
|
</dl>
|
|
89
|
+
{(onRename || onDelete) && (
|
|
90
|
+
<div className="chat-info-actions">
|
|
91
|
+
{onRename && (
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
className="btn btn-ghost btn-sm"
|
|
95
|
+
onClick={onRename}
|
|
96
|
+
>
|
|
97
|
+
<Pencil size={12} aria-hidden /> Rename
|
|
98
|
+
</button>
|
|
99
|
+
)}
|
|
100
|
+
{onDelete && (
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
className="btn btn-ghost btn-danger btn-sm"
|
|
104
|
+
onClick={onDelete}
|
|
105
|
+
>
|
|
106
|
+
<Trash2 size={12} aria-hidden /> Delete
|
|
107
|
+
</button>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
51
111
|
</div>
|
|
52
112
|
|
|
53
113
|
<div className="chat-info-card">
|
|
@@ -80,7 +140,9 @@ export function InfoPanel({
|
|
|
80
140
|
<span className="chat-info-card-count">{mcps.length}</span>
|
|
81
141
|
</div>
|
|
82
142
|
<ul className="mod-mini-list">
|
|
83
|
-
{mcps.length === 0 ?
|
|
143
|
+
{mcps.length === 0 ? (
|
|
144
|
+
<li className="muted">No MCPs configured.</li>
|
|
145
|
+
) : null}
|
|
84
146
|
{mcps.map((m) => (
|
|
85
147
|
<li key={m.id} className="mod-mini">
|
|
86
148
|
<span className="mod-mini-name">{m.id}</span>
|
|
@@ -101,7 +163,10 @@ export function InfoPanel({
|
|
|
101
163
|
>
|
|
102
164
|
<Terminal size={14} /> Slash commands
|
|
103
165
|
<span className="chat-info-card-count">{allCommands.length}</span>
|
|
104
|
-
<ChevronDown
|
|
166
|
+
<ChevronDown
|
|
167
|
+
size={12}
|
|
168
|
+
className={`chat-info-card-chevron ${showCommands ? 'open' : ''}`}
|
|
169
|
+
/>
|
|
105
170
|
</button>
|
|
106
171
|
{showCommands && (
|
|
107
172
|
<ul className="mod-mini-list">
|
|
@@ -116,4 +181,4 @@ export function InfoPanel({
|
|
|
116
181
|
</div>
|
|
117
182
|
</div>
|
|
118
183
|
);
|
|
119
|
-
}
|
|
184
|
+
}
|