bazilion 0.5.0 → 0.6.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/README.md +14 -9
- package/dist/cli.js +365 -140
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +4452 -16989
- package/dist/daemon.js.map +1 -1
- package/dist/web/client/assets/AgentAvatar-Cq7PVBE_.js +1 -0
- package/dist/web/client/assets/AgentTabs-M5K8emJ8.js +1 -0
- package/dist/web/client/assets/Button-BefUtjMZ.js +1 -0
- package/dist/web/client/assets/ChatPane-C8zQfpzH.js +74 -0
- package/dist/web/client/assets/ConfigTabs-DRqAymcN.js +1 -0
- package/dist/web/client/assets/FieldRow-BqWnxZiX.js +1 -0
- package/dist/web/client/assets/MembersEditor-B-47GLUb.js +1 -0
- package/dist/web/client/assets/SpawnTeamModal-DiiC7Nc0.js +1 -0
- package/dist/web/client/assets/TemplatesTabs-DB4KERTQ.js +1 -0
- package/dist/web/client/assets/_id-B7gv1aPQ.js +1 -0
- package/dist/web/client/assets/_id-C7d1AqS0.js +1 -0
- package/dist/web/client/assets/_id-DhiwjC8u.js +1 -0
- package/dist/web/client/assets/_id-p8lUuNFm.js +1 -0
- package/dist/web/client/assets/agents-v8G-wt0M.js +1 -0
- package/dist/web/client/assets/config-BdmYpS_y.js +3 -0
- package/dist/web/client/assets/groups-DiY_ExGM.js +1 -0
- package/dist/web/client/assets/inbox-DtfVx246.js +1 -0
- package/dist/web/client/assets/index-BQUL6oyH.js +12 -0
- package/dist/web/client/assets/login-CVS70W9g.js +1 -0
- package/dist/web/client/assets/mcp-BDLQq8OZ.js +1 -0
- package/dist/web/client/assets/memory-6GGuAXwx.js +1 -0
- package/dist/web/client/assets/profile-groups-BA8rwZMA.js +1 -0
- package/dist/web/client/assets/profiles-C0aAcPZg.js +1 -0
- package/dist/web/client/assets/routes-DgAhnKFw.js +1 -0
- package/dist/web/client/assets/services-BZ86QEah.js +1 -0
- package/dist/web/client/assets/skills-mv_NNNwo.js +1 -0
- package/dist/web/client/assets/styles-CmqUskDu.css +2 -0
- package/dist/web/client/assets/telegram-5f9WXs3x.js +1 -0
- package/dist/web/client/assets/tokens-DHiYsFUh.js +1 -0
- package/dist/web/client/assets/triggers-CjC8Ljrz.js +1 -0
- package/dist/web/client/assets/welcome-C-Cu-ziL.js +1 -0
- package/dist/web/client/assets/wire-constants-BkT5-597.js +1 -0
- package/dist/web/client/baziu.svg +8 -0
- package/dist/web/server/assets/AgentAvatar-8gTAy_tP.js +37 -0
- package/dist/web/server/assets/AgentTabs-BOjTN7QF.js +34 -0
- package/dist/web/server/assets/Button-Dsnw22OF.js +17 -0
- package/dist/web/server/assets/ChatPane-BvpswYvF.js +1084 -0
- package/dist/web/server/assets/ConfigTabs-CyuxzesF.js +41 -0
- package/dist/web/server/assets/FieldRow-DwyqycWI.js +94 -0
- package/dist/web/server/assets/MembersEditor-BWQztvrX.js +131 -0
- package/dist/web/server/assets/Sidebar-DmEiewRN.js +529 -0
- package/dist/web/server/assets/SpawnTeamModal-dSN421k_.js +107 -0
- package/dist/web/server/assets/TemplatesTabs---7h5GYa.js +30 -0
- package/dist/web/server/assets/_id-BZ-YCWSw.js +16 -0
- package/dist/web/server/assets/_id-Bm4-_AP-.js +367 -0
- package/dist/web/server/assets/_id-C78M9_j9.js +12 -0
- package/dist/web/server/assets/_id-CGZbudCl.js +28 -0
- package/dist/web/server/assets/_id-CHNm7EAd.js +161 -0
- package/dist/web/server/assets/_id-CrQbC5L8.js +16 -0
- package/dist/web/server/assets/_id-Crcw2HhT.js +38 -0
- package/dist/web/server/assets/_id-CvIGhq4U.js +16 -0
- package/dist/web/server/assets/_id-DF3pfi9M.js +27 -0
- package/dist/web/server/assets/_id-Dc8P9sy2.js +24 -0
- package/dist/web/server/assets/_id-GJk3EYTT.js +662 -0
- package/dist/web/server/assets/_id-pd4S7LTi.js +467 -0
- package/dist/web/server/assets/_tanstack-start-manifest_v-CPyKYMJg.js +174 -0
- package/dist/web/server/assets/agents-70EwQJR6.js +210 -0
- package/dist/web/server/assets/agents-Ds4lcREv.js +26 -0
- package/dist/web/server/assets/agents-K4Smhnq3.js +14 -0
- package/dist/web/server/assets/auth-DpAGLHpA.js +26 -0
- package/dist/web/server/assets/config-CMXUx_IF.js +19 -0
- package/dist/web/server/assets/config-DQbCsY_l.js +382 -0
- package/dist/web/server/assets/config-DxCGYgR9.js +12 -0
- package/dist/web/server/assets/createServerRpc-CBTntoKx.js +12 -0
- package/dist/web/server/assets/createSsrRpc-DHBVUN8O.js +16 -0
- package/dist/web/server/assets/daemon-client-2NRMzlTr.js +96 -0
- package/dist/web/server/assets/empty-plugin-adapters-Bos_V2s-.js +5 -0
- package/dist/web/server/assets/groups-CXVew7BP.js +150 -0
- package/dist/web/server/assets/groups-Cms8cvH5.js +12 -0
- package/dist/web/server/assets/groups-Dmcvw5ya.js +21 -0
- package/dist/web/server/assets/inbox-0QfwsrGo.js +27 -0
- package/dist/web/server/assets/inbox-8HybA2Ws.js +133 -0
- package/dist/web/server/assets/inbox-B7UWoICA.js +42 -0
- package/dist/web/server/assets/login-CNznncgA.js +58 -0
- package/dist/web/server/assets/login-CQybKk7Y.js +74 -0
- package/dist/web/server/assets/mcp-42BrkI1I.js +12 -0
- package/dist/web/server/assets/mcp-DIa0kIOf.js +268 -0
- package/dist/web/server/assets/mcp-DsTjY3uz.js +12 -0
- package/dist/web/server/assets/memory-BfrMp8iY.js +284 -0
- package/dist/web/server/assets/memory-Dr0lOM2y.js +28 -0
- package/dist/web/server/assets/memory-EEgLDfCj.js +16 -0
- package/dist/web/server/assets/profile-groups-BW5gU-fG.js +12 -0
- package/dist/web/server/assets/profile-groups-B_kIT6BU.js +208 -0
- package/dist/web/server/assets/profile-groups-C5isDI2t.js +28 -0
- package/dist/web/server/assets/profiles-BP1v-jna.js +453 -0
- package/dist/web/server/assets/profiles-BbPTpmyq.js +26 -0
- package/dist/web/server/assets/profiles-Cy5auEj8.js +12 -0
- package/dist/web/server/assets/router-CL100-Ly.js +460 -0
- package/dist/web/server/assets/routes-CzJLQxcS.js +14 -0
- package/dist/web/server/assets/routes-D2H1Hnoh.js +57 -0
- package/dist/web/server/assets/routes-Dzk8jvIo.js +33 -0
- package/dist/web/server/assets/services-Bgj6M2gC.js +12 -0
- package/dist/web/server/assets/services-DEgYsWJ4.js +17 -0
- package/dist/web/server/assets/services-DFenjiw0.js +94 -0
- package/dist/web/server/assets/skills-BASIP_rT.js +12 -0
- package/dist/web/server/assets/skills-C-JPRCHh.js +12 -0
- package/dist/web/server/assets/skills-DZXN680t.js +261 -0
- package/dist/web/server/assets/start-cOXH7eTI.js +4 -0
- package/dist/web/server/assets/telegram-BbvqVWxa.js +583 -0
- package/dist/web/server/assets/telegram-Bq49AtnP.js +12 -0
- package/dist/web/server/assets/telegram-sUckI94O.js +12 -0
- package/dist/web/server/assets/tokens-BERRE0wB.js +14 -0
- package/dist/web/server/assets/tokens-CImgxhDI.js +193 -0
- package/dist/web/server/assets/tokens-Dwn00iV7.js +12 -0
- package/dist/web/server/assets/triggers-DtexqbNH.js +16 -0
- package/dist/web/server/assets/triggers-TyIjwSYn.js +26 -0
- package/dist/web/server/assets/triggers-_iSeahif.js +191 -0
- package/dist/web/server/assets/welcome-BkQ9NC5K.js +81 -0
- package/dist/web/server/assets/wire-constants-Deb4NxBb.js +16 -0
- package/dist/web/server/server.js +5899 -0
- package/dist/web-server.js +42 -0
- package/dist/web-server.js.map +1 -0
- package/package.json +10 -4
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import DOMPurify from "dompurify";
|
|
4
|
+
import { marked } from "marked";
|
|
5
|
+
//#region src/lib/md.ts
|
|
6
|
+
var installed = false;
|
|
7
|
+
function install() {
|
|
8
|
+
if (installed) return;
|
|
9
|
+
marked.setOptions({
|
|
10
|
+
breaks: true,
|
|
11
|
+
gfm: true
|
|
12
|
+
});
|
|
13
|
+
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
|
|
14
|
+
if (node.nodeName === "A") {
|
|
15
|
+
node.setAttribute("target", "_blank");
|
|
16
|
+
node.setAttribute("rel", "noopener noreferrer");
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
installed = true;
|
|
20
|
+
}
|
|
21
|
+
function renderMd(text) {
|
|
22
|
+
if (!text) return "";
|
|
23
|
+
if (typeof window === "undefined") return escapeHtml(text);
|
|
24
|
+
install();
|
|
25
|
+
try {
|
|
26
|
+
const html = marked.parse(text, { async: false });
|
|
27
|
+
return DOMPurify.sanitize(html);
|
|
28
|
+
} catch {
|
|
29
|
+
return escapeHtml(text);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function escapeHtml(s) {
|
|
33
|
+
return String(s).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/components/ChatPane.tsx
|
|
37
|
+
var INBOX_WAKE_PREFIX = "[[bazilion:inbox-wake]]\n";
|
|
38
|
+
var COMPACTION_REPLAY_PREFIX = "[conversation summary]";
|
|
39
|
+
var AUTOSCROLL_THRESHOLD_PX = 40;
|
|
40
|
+
var SESSION_POLL_MS = 1e4;
|
|
41
|
+
var TOOL_GROUP_MAX_HEIGHT_PX = 320;
|
|
42
|
+
var MAX_INPUT_HEIGHT = 200;
|
|
43
|
+
var SLASH_HELP = "slash commands:\n /context — context breakdown (system prompt, tools, skills, history)\n /compact [N] — summarize the head; keep the last N messages verbatim (default 10)\n /reset — reset chat history for this agent\n /help — show this list";
|
|
44
|
+
function prettyArgs(raw) {
|
|
45
|
+
if (!raw) return "";
|
|
46
|
+
try {
|
|
47
|
+
return JSON.stringify(JSON.parse(raw), null, 2);
|
|
48
|
+
} catch {
|
|
49
|
+
return raw;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function formatBytes(n) {
|
|
53
|
+
if (n < 1024) return `${n} B`;
|
|
54
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
|
55
|
+
return `${(n / (1024 * 1024)).toFixed(2)} MiB`;
|
|
56
|
+
}
|
|
57
|
+
/** Read any File into a base64 Attachment (strips the data: URL prefix). */
|
|
58
|
+
function fileToAttachment(file) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const reader = new FileReader();
|
|
61
|
+
reader.onload = () => {
|
|
62
|
+
const result = String(reader.result);
|
|
63
|
+
const comma = result.indexOf(",");
|
|
64
|
+
resolve({
|
|
65
|
+
name: file.name,
|
|
66
|
+
data: comma >= 0 ? result.slice(comma + 1) : result,
|
|
67
|
+
mimeType: file.type || "application/octet-stream"
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
reader.onerror = () => reject(reader.error);
|
|
71
|
+
reader.readAsDataURL(file);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
var isImageMime = (m) => m.startsWith("image/");
|
|
75
|
+
function projectMessages(msgs) {
|
|
76
|
+
const entries = [];
|
|
77
|
+
let openTool = null;
|
|
78
|
+
for (const m of msgs) if (m.role === "user") {
|
|
79
|
+
openTool = null;
|
|
80
|
+
entries.push({
|
|
81
|
+
type: "user",
|
|
82
|
+
content: m.content,
|
|
83
|
+
...m.images && m.images.length > 0 ? { images: m.images } : {}
|
|
84
|
+
});
|
|
85
|
+
} else if (m.role === "assistant") {
|
|
86
|
+
if (m.content) {
|
|
87
|
+
openTool = null;
|
|
88
|
+
entries.push({
|
|
89
|
+
type: "assistant",
|
|
90
|
+
content: m.content
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
94
|
+
if (!openTool) {
|
|
95
|
+
openTool = {
|
|
96
|
+
type: "tool",
|
|
97
|
+
items: []
|
|
98
|
+
};
|
|
99
|
+
entries.push(openTool);
|
|
100
|
+
}
|
|
101
|
+
for (const tc of m.toolCalls) openTool.items.push({
|
|
102
|
+
kind: "call",
|
|
103
|
+
id: tc.id,
|
|
104
|
+
name: tc.name,
|
|
105
|
+
body: tc.arguments
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} else if (m.role === "tool") {
|
|
109
|
+
if (!openTool) {
|
|
110
|
+
openTool = {
|
|
111
|
+
type: "tool",
|
|
112
|
+
items: []
|
|
113
|
+
};
|
|
114
|
+
entries.push(openTool);
|
|
115
|
+
}
|
|
116
|
+
const isError = m.content.startsWith("Error:") || m.content.startsWith("error:");
|
|
117
|
+
openTool.items.push({
|
|
118
|
+
kind: isError ? "error" : "result",
|
|
119
|
+
id: m.toolCallId ?? "",
|
|
120
|
+
name: m.toolName ?? "",
|
|
121
|
+
body: m.content
|
|
122
|
+
});
|
|
123
|
+
if (m.images && m.images.length > 0) {
|
|
124
|
+
entries.push({
|
|
125
|
+
type: "images",
|
|
126
|
+
images: m.images
|
|
127
|
+
});
|
|
128
|
+
openTool = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return entries;
|
|
132
|
+
}
|
|
133
|
+
function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
134
|
+
const [serverMessages, setServerMessages] = useState(initialMessages);
|
|
135
|
+
const [liveEntries, setLiveEntries] = useState([]);
|
|
136
|
+
const [systemBubbles, setSystemBubbles] = useState([]);
|
|
137
|
+
const [thinking, setThinking] = useState(false);
|
|
138
|
+
const [streaming, setStreaming] = useState(false);
|
|
139
|
+
const [editIdx, setEditIdx] = useState(null);
|
|
140
|
+
const [input, setInput] = useState("");
|
|
141
|
+
const [attachments, setAttachments] = useState([]);
|
|
142
|
+
const [dragging, setDragging] = useState(false);
|
|
143
|
+
const [staleBanner, setStaleBanner] = useState(false);
|
|
144
|
+
const messagesRef = useRef(null);
|
|
145
|
+
const fileInputRef = useRef(null);
|
|
146
|
+
const inputRef = useRef(null);
|
|
147
|
+
const currentAbortRef = useRef(null);
|
|
148
|
+
const systemIdRef = useRef(0);
|
|
149
|
+
const visibleEntryCountRef = useRef(0);
|
|
150
|
+
const knownHeadRef = useRef(initialSessionHead ?? {
|
|
151
|
+
file: null,
|
|
152
|
+
size: 0
|
|
153
|
+
});
|
|
154
|
+
const streamingRef = useRef(false);
|
|
155
|
+
streamingRef.current = streaming;
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
setServerMessages(initialMessages);
|
|
158
|
+
setLiveEntries([]);
|
|
159
|
+
setSystemBubbles([]);
|
|
160
|
+
setEditIdx(null);
|
|
161
|
+
setInput("");
|
|
162
|
+
setThinking(false);
|
|
163
|
+
setStreaming(false);
|
|
164
|
+
setStaleBanner(false);
|
|
165
|
+
knownHeadRef.current = initialSessionHead ?? {
|
|
166
|
+
file: null,
|
|
167
|
+
size: 0
|
|
168
|
+
};
|
|
169
|
+
}, [agentId]);
|
|
170
|
+
const wasNearBottomRef = useRef(true);
|
|
171
|
+
useLayoutEffect(() => {
|
|
172
|
+
const el = messagesRef.current;
|
|
173
|
+
if (!el) return;
|
|
174
|
+
if (wasNearBottomRef.current) el.scrollTop = el.scrollHeight;
|
|
175
|
+
});
|
|
176
|
+
function captureScroll() {
|
|
177
|
+
const el = messagesRef.current;
|
|
178
|
+
if (!el) {
|
|
179
|
+
wasNearBottomRef.current = true;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
wasNearBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < AUTOSCROLL_THRESHOLD_PX;
|
|
183
|
+
}
|
|
184
|
+
useLayoutEffect(() => {
|
|
185
|
+
const el = inputRef.current;
|
|
186
|
+
if (!el) return;
|
|
187
|
+
el.style.height = "auto";
|
|
188
|
+
el.style.height = `${Math.min(el.scrollHeight, MAX_INPUT_HEIGHT)}px`;
|
|
189
|
+
}, [input]);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const key = `bz_pending_${agentId}`;
|
|
192
|
+
function onVis() {
|
|
193
|
+
if (document.visibilityState === "visible" && sessionStorage.getItem(key)) {
|
|
194
|
+
sessionStorage.removeItem(key);
|
|
195
|
+
window.location.reload();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
document.addEventListener("visibilitychange", onVis);
|
|
199
|
+
return () => document.removeEventListener("visibilitychange", onVis);
|
|
200
|
+
}, [agentId]);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (!initialSessionHead) return;
|
|
203
|
+
let stopped = false;
|
|
204
|
+
let timer = null;
|
|
205
|
+
async function tick() {
|
|
206
|
+
if (stopped) return;
|
|
207
|
+
if (!document.hidden && !streamingRef.current && !staleBanner) try {
|
|
208
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
|
|
209
|
+
if (res.ok) {
|
|
210
|
+
const body = await res.json();
|
|
211
|
+
if (typeof body.size === "number") {
|
|
212
|
+
const known = knownHeadRef.current;
|
|
213
|
+
if (body.file !== known.file || body.size !== known.size) setStaleBanner(true);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
timer = setTimeout(tick, SESSION_POLL_MS);
|
|
218
|
+
}
|
|
219
|
+
timer = setTimeout(tick, SESSION_POLL_MS);
|
|
220
|
+
return () => {
|
|
221
|
+
stopped = true;
|
|
222
|
+
if (timer) clearTimeout(timer);
|
|
223
|
+
};
|
|
224
|
+
}, [
|
|
225
|
+
agentId,
|
|
226
|
+
initialSessionHead,
|
|
227
|
+
staleBanner
|
|
228
|
+
]);
|
|
229
|
+
async function refreshKnownHead() {
|
|
230
|
+
try {
|
|
231
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
|
|
232
|
+
if (!res.ok) return;
|
|
233
|
+
const body = await res.json();
|
|
234
|
+
if (typeof body.size === "number") knownHeadRef.current = {
|
|
235
|
+
file: body.file ?? null,
|
|
236
|
+
size: body.size
|
|
237
|
+
};
|
|
238
|
+
} catch {}
|
|
239
|
+
}
|
|
240
|
+
function pushSystem(text) {
|
|
241
|
+
captureScroll();
|
|
242
|
+
setSystemBubbles((prev) => [...prev, {
|
|
243
|
+
id: systemIdRef.current++,
|
|
244
|
+
content: text,
|
|
245
|
+
afterIdx: visibleEntryCountRef.current
|
|
246
|
+
}]);
|
|
247
|
+
}
|
|
248
|
+
async function runContextCommand() {
|
|
249
|
+
try {
|
|
250
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/context`);
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
pushSystem(`/context failed: ${res.statusText}`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const ctx = await res.json();
|
|
256
|
+
const fmt = (chars, tokens) => `${chars.toLocaleString()} chars (~${tokens.toLocaleString()} tok)`;
|
|
257
|
+
const lines = [];
|
|
258
|
+
lines.push(`context for ${ctx.agentId}`);
|
|
259
|
+
lines.push(`model: ${ctx.model}`);
|
|
260
|
+
lines.push("");
|
|
261
|
+
lines.push(`system prompt: ${fmt(ctx.systemPrompt.chars, ctx.systemPrompt.tokens)}`);
|
|
262
|
+
for (const f of ctx.systemPrompt.files) lines.push(` - ${f.name}: ${f.chars.toLocaleString()} chars`);
|
|
263
|
+
lines.push("");
|
|
264
|
+
lines.push(`tools: ${ctx.tools.count} (${ctx.tools.schemaChars.toLocaleString()} chars of schema)`);
|
|
265
|
+
for (const t of ctx.tools.entries.slice(0, 5)) lines.push(` - ${t.name}: ${t.schemaChars.toLocaleString()} chars`);
|
|
266
|
+
if (ctx.skills.count > 0) {
|
|
267
|
+
lines.push("");
|
|
268
|
+
lines.push(`skills: ${ctx.skills.count}`);
|
|
269
|
+
for (const s of ctx.skills.entries.slice(0, 10)) lines.push(` - ${s.name}: ${s.blockChars.toLocaleString()} chars`);
|
|
270
|
+
}
|
|
271
|
+
lines.push("");
|
|
272
|
+
lines.push(`history: ${ctx.history.messageEntries} messages / ${ctx.history.compactionEntries} compactions`);
|
|
273
|
+
lines.push(` ${fmt(ctx.history.chars, ctx.history.tokensEstimate)}`);
|
|
274
|
+
lines.push(` bytes on disk: ${formatBytes(ctx.history.bytes)}`);
|
|
275
|
+
lines.push("");
|
|
276
|
+
lines.push(`TOTAL: ${fmt(ctx.totals.chars, ctx.totals.tokens)}`);
|
|
277
|
+
pushSystem(lines.join("\n"));
|
|
278
|
+
} catch (err) {
|
|
279
|
+
pushSystem(`/context failed: ${err.message}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function runResetCommand() {
|
|
283
|
+
if (serverMessages.length === 0) {
|
|
284
|
+
pushSystem("/reset: history already empty");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!confirm("reset chat history for this agent?")) return;
|
|
288
|
+
exitEditMode();
|
|
289
|
+
try {
|
|
290
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/reset`, { method: "POST" });
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
let msg = res.statusText;
|
|
293
|
+
try {
|
|
294
|
+
msg = (await res.json()).error || msg;
|
|
295
|
+
} catch {}
|
|
296
|
+
pushSystem(`/reset failed: ${msg}`);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
setServerMessages([]);
|
|
300
|
+
setLiveEntries([]);
|
|
301
|
+
setSystemBubbles([]);
|
|
302
|
+
pushSystem("/reset: history wiped");
|
|
303
|
+
} catch (err) {
|
|
304
|
+
pushSystem(`/reset failed: ${err.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function runCompactCommand(rest) {
|
|
308
|
+
if (serverMessages.length < 2) {
|
|
309
|
+
pushSystem("/compact: need ≥2 messages to compact");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
let keepTail;
|
|
313
|
+
const argText = rest.trim();
|
|
314
|
+
if (argText) {
|
|
315
|
+
const n = Number(argText);
|
|
316
|
+
if (Number.isFinite(n) && n >= 0) keepTail = Math.floor(n);
|
|
317
|
+
}
|
|
318
|
+
exitEditMode();
|
|
319
|
+
setStreaming(true);
|
|
320
|
+
setThinking(true);
|
|
321
|
+
try {
|
|
322
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/compact`, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers: { "content-type": "application/json" },
|
|
325
|
+
body: JSON.stringify(keepTail !== void 0 ? { keepTail } : {})
|
|
326
|
+
});
|
|
327
|
+
if (!res.ok) {
|
|
328
|
+
let msg = res.statusText;
|
|
329
|
+
try {
|
|
330
|
+
msg = (await res.json()).error || msg;
|
|
331
|
+
} catch {}
|
|
332
|
+
pushSystem(`/compact failed: ${msg}`);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const body = await res.json();
|
|
336
|
+
pushSystem(`/compact: ${body.before} → ${body.after} entries (${body.summarized} summarized, ${body.keptTail} kept verbatim; ~${body.tokensBefore.toLocaleString()} → ~${body.tokensAfter.toLocaleString()} tok). reloading…`);
|
|
337
|
+
setTimeout(() => window.location.reload(), 400);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
pushSystem(`/compact failed: ${err.message}`);
|
|
340
|
+
} finally {
|
|
341
|
+
setThinking(false);
|
|
342
|
+
setStreaming(false);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function maybeHandleSlashCommand(text) {
|
|
346
|
+
const trimmed = text.trim();
|
|
347
|
+
if (!trimmed.startsWith("/")) return false;
|
|
348
|
+
const parts = trimmed.split(/\s+/);
|
|
349
|
+
const cmd = (parts[0] ?? "").toLowerCase();
|
|
350
|
+
const rest = parts.slice(1).join(" ");
|
|
351
|
+
switch (cmd) {
|
|
352
|
+
case "/help":
|
|
353
|
+
pushSystem(SLASH_HELP);
|
|
354
|
+
return true;
|
|
355
|
+
case "/context":
|
|
356
|
+
await runContextCommand();
|
|
357
|
+
return true;
|
|
358
|
+
case "/reset":
|
|
359
|
+
await runResetCommand();
|
|
360
|
+
return true;
|
|
361
|
+
case "/compact":
|
|
362
|
+
await runCompactCommand(rest);
|
|
363
|
+
return true;
|
|
364
|
+
default: return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function findLastUserIdx() {
|
|
368
|
+
for (let i = serverMessages.length - 1; i >= 0; i--) if (serverMessages[i]?.role === "user") return i;
|
|
369
|
+
return -1;
|
|
370
|
+
}
|
|
371
|
+
function enterEditMode() {
|
|
372
|
+
if (liveEntries.length > 0 || streaming) return;
|
|
373
|
+
const idx = findLastUserIdx();
|
|
374
|
+
if (idx === -1) return;
|
|
375
|
+
const userMsg = serverMessages[idx];
|
|
376
|
+
if (!userMsg) return;
|
|
377
|
+
setEditIdx(idx);
|
|
378
|
+
setInput(userMsg.content);
|
|
379
|
+
inputRef.current?.focus();
|
|
380
|
+
}
|
|
381
|
+
function exitEditMode() {
|
|
382
|
+
setEditIdx(null);
|
|
383
|
+
}
|
|
384
|
+
async function addFiles(files) {
|
|
385
|
+
if (!files) return;
|
|
386
|
+
const arr = Array.from(files);
|
|
387
|
+
if (arr.length === 0) return;
|
|
388
|
+
const encoded = await Promise.all(arr.map(fileToAttachment));
|
|
389
|
+
setAttachments((prev) => [...prev, ...encoded].slice(0, 16));
|
|
390
|
+
}
|
|
391
|
+
function onPaste(e) {
|
|
392
|
+
const files = Array.from(e.clipboardData.files);
|
|
393
|
+
if (files.some((f) => f.type.startsWith("image/"))) {
|
|
394
|
+
e.preventDefault();
|
|
395
|
+
addFiles(files);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function onDragOver(e) {
|
|
399
|
+
if (streaming) return;
|
|
400
|
+
if (Array.from(e.dataTransfer.types).includes("Files")) {
|
|
401
|
+
e.preventDefault();
|
|
402
|
+
setDragging(true);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function onDragLeave(e) {
|
|
406
|
+
if (e.currentTarget === e.target) setDragging(false);
|
|
407
|
+
}
|
|
408
|
+
function onDrop(e) {
|
|
409
|
+
if (e.dataTransfer.files.length > 0) {
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
setDragging(false);
|
|
412
|
+
addFiles(e.dataTransfer.files);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const send = useCallback(async (text) => {
|
|
416
|
+
const atts = attachments;
|
|
417
|
+
if (!text.trim() && atts.length === 0 || streaming) return;
|
|
418
|
+
setInput("");
|
|
419
|
+
if (await maybeHandleSlashCommand(text)) return;
|
|
420
|
+
setAttachments([]);
|
|
421
|
+
let truncatedServerMessages = null;
|
|
422
|
+
if (editIdx !== null) {
|
|
423
|
+
const keep = editIdx;
|
|
424
|
+
try {
|
|
425
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/truncate`, {
|
|
426
|
+
method: "POST",
|
|
427
|
+
headers: { "content-type": "application/json" },
|
|
428
|
+
body: JSON.stringify({ keepCount: keep })
|
|
429
|
+
});
|
|
430
|
+
if (!res.ok) {
|
|
431
|
+
let err = res.statusText;
|
|
432
|
+
try {
|
|
433
|
+
err = (await res.json()).error || err;
|
|
434
|
+
} catch {}
|
|
435
|
+
setInput(text);
|
|
436
|
+
alert(`failed to replace last turn: ${err}`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
truncatedServerMessages = serverMessages.slice(0, keep);
|
|
440
|
+
setServerMessages(truncatedServerMessages);
|
|
441
|
+
setEditIdx(null);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
setInput(text);
|
|
444
|
+
alert(`failed to replace last turn: ${err.message}`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
captureScroll();
|
|
449
|
+
const previewImages = atts.filter((a) => isImageMime(a.mimeType));
|
|
450
|
+
const previewFiles = atts.filter((a) => !isImageMime(a.mimeType));
|
|
451
|
+
setLiveEntries([{
|
|
452
|
+
type: "user",
|
|
453
|
+
content: text,
|
|
454
|
+
...previewImages.length > 0 ? { images: previewImages } : {},
|
|
455
|
+
...previewFiles.length > 0 ? { files: previewFiles.map((f) => ({ name: f.name ?? "file" })) } : {}
|
|
456
|
+
}]);
|
|
457
|
+
sessionStorage.setItem(`bz_pending_${agentId}`, "1");
|
|
458
|
+
const abort = new AbortController();
|
|
459
|
+
currentAbortRef.current = abort;
|
|
460
|
+
setStreaming(true);
|
|
461
|
+
setThinking(true);
|
|
462
|
+
try {
|
|
463
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat`, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: { "content-type": "application/json" },
|
|
466
|
+
body: JSON.stringify({
|
|
467
|
+
message: text,
|
|
468
|
+
attachments: atts
|
|
469
|
+
}),
|
|
470
|
+
signal: abort.signal
|
|
471
|
+
});
|
|
472
|
+
if (!res.ok || !res.body) {
|
|
473
|
+
let err = res.statusText;
|
|
474
|
+
try {
|
|
475
|
+
err = (await res.json()).error || err;
|
|
476
|
+
} catch {}
|
|
477
|
+
setLiveEntries((prev) => [...prev, {
|
|
478
|
+
type: "error",
|
|
479
|
+
content: `[error] ${err}`
|
|
480
|
+
}]);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const reader = res.body.getReader();
|
|
484
|
+
const decoder = new TextDecoder();
|
|
485
|
+
let buffer = "";
|
|
486
|
+
while (true) {
|
|
487
|
+
const { done, value } = await reader.read();
|
|
488
|
+
if (done) break;
|
|
489
|
+
buffer += decoder.decode(value, { stream: true });
|
|
490
|
+
const lines = buffer.split("\n");
|
|
491
|
+
buffer = lines.pop() ?? "";
|
|
492
|
+
for (const line of lines) {
|
|
493
|
+
if (!line.trim()) continue;
|
|
494
|
+
let frame;
|
|
495
|
+
try {
|
|
496
|
+
frame = JSON.parse(line);
|
|
497
|
+
} catch {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
handleFrame(frame);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (buffer.trim()) try {
|
|
504
|
+
handleFrame(JSON.parse(buffer));
|
|
505
|
+
} catch {}
|
|
506
|
+
await refreshKnownHead();
|
|
507
|
+
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
if (err.name !== "AbortError") setLiveEntries((prev) => [...prev, {
|
|
510
|
+
type: "error",
|
|
511
|
+
content: `[network error] ${err.message}`
|
|
512
|
+
}]);
|
|
513
|
+
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
514
|
+
} finally {
|
|
515
|
+
setThinking(false);
|
|
516
|
+
setStreaming(false);
|
|
517
|
+
currentAbortRef.current = null;
|
|
518
|
+
}
|
|
519
|
+
}, [
|
|
520
|
+
agentId,
|
|
521
|
+
editIdx,
|
|
522
|
+
serverMessages,
|
|
523
|
+
streaming,
|
|
524
|
+
attachments
|
|
525
|
+
]);
|
|
526
|
+
function handleFrame(frame) {
|
|
527
|
+
if (frame.kind === "fatal") {
|
|
528
|
+
setLiveEntries((prev) => [...prev, {
|
|
529
|
+
type: "error",
|
|
530
|
+
content: `[fatal] ${frame.error}`
|
|
531
|
+
}]);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (frame.kind === "done") {
|
|
535
|
+
setServerMessages(frame.messages);
|
|
536
|
+
setLiveEntries([]);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (frame.kind !== "event") return;
|
|
540
|
+
const ev = frame.event;
|
|
541
|
+
captureScroll();
|
|
542
|
+
if (ev.type === "user_message") return;
|
|
543
|
+
if (ev.type === "assistant_delta") {
|
|
544
|
+
setThinking(false);
|
|
545
|
+
setLiveEntries((prev) => {
|
|
546
|
+
const next = [...prev];
|
|
547
|
+
const last = next[next.length - 1];
|
|
548
|
+
if (last?.type === "assistant") next[next.length - 1] = {
|
|
549
|
+
type: "assistant",
|
|
550
|
+
content: last.content + ev.delta
|
|
551
|
+
};
|
|
552
|
+
else next.push({
|
|
553
|
+
type: "assistant",
|
|
554
|
+
content: ev.delta
|
|
555
|
+
});
|
|
556
|
+
return next;
|
|
557
|
+
});
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (ev.type === "assistant_message") {
|
|
561
|
+
setThinking(false);
|
|
562
|
+
setLiveEntries((prev) => {
|
|
563
|
+
const next = [...prev];
|
|
564
|
+
if (next[next.length - 1]?.type === "assistant") next[next.length - 1] = {
|
|
565
|
+
type: "assistant",
|
|
566
|
+
content: ev.text
|
|
567
|
+
};
|
|
568
|
+
else next.push({
|
|
569
|
+
type: "assistant",
|
|
570
|
+
content: ev.text
|
|
571
|
+
});
|
|
572
|
+
return next;
|
|
573
|
+
});
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (ev.type === "tool_call" || ev.type === "tool_result" || ev.type === "tool_error") {
|
|
577
|
+
setThinking(ev.type !== "tool_call");
|
|
578
|
+
const item = ev.type === "tool_call" ? {
|
|
579
|
+
kind: "call",
|
|
580
|
+
id: ev.id,
|
|
581
|
+
name: ev.name,
|
|
582
|
+
body: ev.arguments
|
|
583
|
+
} : ev.type === "tool_result" ? {
|
|
584
|
+
kind: "result",
|
|
585
|
+
id: ev.id,
|
|
586
|
+
name: ev.name,
|
|
587
|
+
body: ev.result
|
|
588
|
+
} : {
|
|
589
|
+
kind: "error",
|
|
590
|
+
id: ev.id,
|
|
591
|
+
name: ev.name,
|
|
592
|
+
body: ev.error
|
|
593
|
+
};
|
|
594
|
+
const images = ev.type === "tool_result" ? ev.images : void 0;
|
|
595
|
+
setLiveEntries((prev) => {
|
|
596
|
+
const next = [...prev];
|
|
597
|
+
const last = next[next.length - 1];
|
|
598
|
+
if (last?.type === "tool") next[next.length - 1] = {
|
|
599
|
+
type: "tool",
|
|
600
|
+
items: [...last.items, item]
|
|
601
|
+
};
|
|
602
|
+
else next.push({
|
|
603
|
+
type: "tool",
|
|
604
|
+
items: [item]
|
|
605
|
+
});
|
|
606
|
+
if (images && images.length > 0) next.push({
|
|
607
|
+
type: "images",
|
|
608
|
+
images
|
|
609
|
+
});
|
|
610
|
+
return next;
|
|
611
|
+
});
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (ev.type === "file") {
|
|
615
|
+
setLiveEntries((prev) => [...prev, {
|
|
616
|
+
type: "file",
|
|
617
|
+
name: ev.name,
|
|
618
|
+
mimeType: ev.mimeType,
|
|
619
|
+
data: ev.data
|
|
620
|
+
}]);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (ev.type === "error") setLiveEntries((prev) => [...prev, {
|
|
624
|
+
type: "error",
|
|
625
|
+
content: `[error] ${ev.error}`
|
|
626
|
+
}]);
|
|
627
|
+
}
|
|
628
|
+
async function cancel() {
|
|
629
|
+
try {
|
|
630
|
+
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/cancel`, { method: "POST" });
|
|
631
|
+
if (res.ok || res.status === 204) return;
|
|
632
|
+
} catch {}
|
|
633
|
+
currentAbortRef.current?.abort();
|
|
634
|
+
}
|
|
635
|
+
function onKeyDown(e) {
|
|
636
|
+
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
637
|
+
e.preventDefault();
|
|
638
|
+
send(input);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const baseEntries = projectMessages(serverMessages);
|
|
642
|
+
visibleEntryCountRef.current = baseEntries.length + liveEntries.length;
|
|
643
|
+
const lastUserIdx = (() => {
|
|
644
|
+
if (liveEntries.length > 0 || streaming) return -1;
|
|
645
|
+
for (let i = baseEntries.length - 1; i >= 0; i--) if (baseEntries[i]?.type === "user") return i;
|
|
646
|
+
return -1;
|
|
647
|
+
})();
|
|
648
|
+
const willDropFromIdx = (() => {
|
|
649
|
+
if (editIdx === null) return -1;
|
|
650
|
+
let userCount = 0;
|
|
651
|
+
for (let i = 0; i < baseEntries.length; i++) if (baseEntries[i]?.type === "user") {
|
|
652
|
+
if (userCount === serverMessages.slice(0, editIdx + 1).filter((m) => m.role === "user").length - 1) return i;
|
|
653
|
+
userCount++;
|
|
654
|
+
}
|
|
655
|
+
return lastUserIdx;
|
|
656
|
+
})();
|
|
657
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
658
|
+
className: "relative flex h-full flex-col overflow-hidden rounded-[16px] border border-frost bg-snow shadow-baziu-sm",
|
|
659
|
+
onDragOver,
|
|
660
|
+
onDragLeave,
|
|
661
|
+
onDrop,
|
|
662
|
+
children: [
|
|
663
|
+
dragging && /* @__PURE__ */ jsx("div", {
|
|
664
|
+
className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-[16px] border-2 border-dashed border-sapphire bg-sapphire-glow/80 text-[0.95em] font-medium text-sapphire-deep",
|
|
665
|
+
children: "drop images or files to attach"
|
|
666
|
+
}),
|
|
667
|
+
/* @__PURE__ */ jsxs("div", {
|
|
668
|
+
className: "flex items-baseline justify-between border-b border-frost px-4 py-2.5",
|
|
669
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
670
|
+
className: "font-display text-[1.2rem] text-charcoal",
|
|
671
|
+
children: agentName
|
|
672
|
+
}), /* @__PURE__ */ jsx("a", {
|
|
673
|
+
href: `/agents/${agentId}`,
|
|
674
|
+
className: "text-xs text-mocha-light hover:text-sapphire",
|
|
675
|
+
children: "settings →"
|
|
676
|
+
})]
|
|
677
|
+
}),
|
|
678
|
+
staleBanner && /* @__PURE__ */ jsxs("div", {
|
|
679
|
+
className: "mx-5 mt-2 flex items-center gap-2 rounded-md border border-sapphire bg-frost px-4 py-2 text-[0.86em] text-mocha",
|
|
680
|
+
children: [
|
|
681
|
+
/* @__PURE__ */ jsx("span", {
|
|
682
|
+
className: "h-2 w-2 flex-none rounded-full bg-sapphire",
|
|
683
|
+
"aria-hidden": "true"
|
|
684
|
+
}),
|
|
685
|
+
/* @__PURE__ */ jsx("span", {
|
|
686
|
+
className: "flex-1",
|
|
687
|
+
children: "new activity from another source — reload to see it"
|
|
688
|
+
}),
|
|
689
|
+
/* @__PURE__ */ jsx("button", {
|
|
690
|
+
type: "button",
|
|
691
|
+
onClick: () => window.location.reload(),
|
|
692
|
+
className: "rounded-sm bg-sapphire px-3 py-1 text-[0.92em] text-snow hover:opacity-90",
|
|
693
|
+
children: "reload"
|
|
694
|
+
}),
|
|
695
|
+
/* @__PURE__ */ jsx("button", {
|
|
696
|
+
type: "button",
|
|
697
|
+
onClick: () => setStaleBanner(false),
|
|
698
|
+
className: "px-1 text-mocha-light hover:text-mocha",
|
|
699
|
+
"aria-label": "dismiss",
|
|
700
|
+
children: "×"
|
|
701
|
+
})
|
|
702
|
+
]
|
|
703
|
+
}),
|
|
704
|
+
/* @__PURE__ */ jsxs("div", {
|
|
705
|
+
ref: messagesRef,
|
|
706
|
+
onScroll: captureScroll,
|
|
707
|
+
className: `min-h-[240px] flex-1 overflow-y-auto px-5 py-5 ${editIdx !== null ? "is-editing" : ""}`,
|
|
708
|
+
children: [
|
|
709
|
+
baseEntries.length === 0 && liveEntries.length === 0 && systemBubbles.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
710
|
+
className: "py-12 text-center italic text-fawn",
|
|
711
|
+
children: "start a conversation…"
|
|
712
|
+
}),
|
|
713
|
+
(() => {
|
|
714
|
+
const out = [];
|
|
715
|
+
let sysIdx = 0;
|
|
716
|
+
const flushSysUpTo = (idx) => {
|
|
717
|
+
while (sysIdx < systemBubbles.length && systemBubbles[sysIdx].afterIdx <= idx) {
|
|
718
|
+
const sb = systemBubbles[sysIdx];
|
|
719
|
+
out.push(/* @__PURE__ */ jsx(Bubble, { entry: {
|
|
720
|
+
type: "system",
|
|
721
|
+
content: sb.content
|
|
722
|
+
} }, `y-${sb.id}`));
|
|
723
|
+
sysIdx++;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
flushSysUpTo(0);
|
|
727
|
+
for (let i = 0; i < baseEntries.length; i++) {
|
|
728
|
+
const entry = baseEntries[i];
|
|
729
|
+
out.push(/* @__PURE__ */ jsx(Bubble, {
|
|
730
|
+
entry,
|
|
731
|
+
isLastUser: i === lastUserIdx && editIdx === null,
|
|
732
|
+
isWillDrop: willDropFromIdx !== -1 && i >= willDropFromIdx,
|
|
733
|
+
onEdit: enterEditMode
|
|
734
|
+
}, `s-${i}`));
|
|
735
|
+
flushSysUpTo(i + 1);
|
|
736
|
+
}
|
|
737
|
+
for (let i = 0; i < liveEntries.length; i++) {
|
|
738
|
+
const entry = liveEntries[i];
|
|
739
|
+
out.push(/* @__PURE__ */ jsx(Bubble, { entry }, `l-${i}`));
|
|
740
|
+
flushSysUpTo(baseEntries.length + i + 1);
|
|
741
|
+
}
|
|
742
|
+
while (sysIdx < systemBubbles.length) {
|
|
743
|
+
const sb = systemBubbles[sysIdx];
|
|
744
|
+
out.push(/* @__PURE__ */ jsx(Bubble, { entry: {
|
|
745
|
+
type: "system",
|
|
746
|
+
content: sb.content
|
|
747
|
+
} }, `y-${sb.id}`));
|
|
748
|
+
sysIdx++;
|
|
749
|
+
}
|
|
750
|
+
return out;
|
|
751
|
+
})(),
|
|
752
|
+
thinking && /* @__PURE__ */ jsxs("div", {
|
|
753
|
+
className: "flex items-center gap-2 px-1 py-1 text-[0.85em] text-mocha-light",
|
|
754
|
+
children: [
|
|
755
|
+
/* @__PURE__ */ jsx(Dot, {}),
|
|
756
|
+
/* @__PURE__ */ jsx(Dot, { delay: "0.15s" }),
|
|
757
|
+
/* @__PURE__ */ jsx(Dot, { delay: "0.3s" }),
|
|
758
|
+
/* @__PURE__ */ jsx("span", { children: "agent is thinking…" })
|
|
759
|
+
]
|
|
760
|
+
})
|
|
761
|
+
]
|
|
762
|
+
}),
|
|
763
|
+
editIdx !== null && /* @__PURE__ */ jsxs("div", {
|
|
764
|
+
className: "flex items-center gap-2 border-t border-frost bg-sapphire-glow px-5 py-2 text-[0.85em] text-sapphire-deep",
|
|
765
|
+
children: [/* @__PURE__ */ jsx("span", { children: "editing last message — submit to replace, or" }), /* @__PURE__ */ jsx("button", {
|
|
766
|
+
type: "button",
|
|
767
|
+
onClick: exitEditMode,
|
|
768
|
+
className: "ml-auto rounded-sm border border-sapphire-light bg-transparent px-2 py-0.5 text-[0.92em] text-sapphire-deep hover:bg-snow",
|
|
769
|
+
children: "cancel"
|
|
770
|
+
})]
|
|
771
|
+
}),
|
|
772
|
+
attachments.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
773
|
+
className: "flex flex-wrap gap-2 border-t border-frost bg-ivory px-5 pt-3",
|
|
774
|
+
children: attachments.map((a, i) => {
|
|
775
|
+
const remove = () => setAttachments((prev) => prev.filter((_, j) => j !== i));
|
|
776
|
+
return isImageMime(a.mimeType) ? /* @__PURE__ */ jsxs("div", {
|
|
777
|
+
className: "relative",
|
|
778
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
779
|
+
src: `data:${a.mimeType};base64,${a.data}`,
|
|
780
|
+
alt: "pending attachment",
|
|
781
|
+
className: "h-16 w-16 rounded-md border border-frost object-cover"
|
|
782
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
783
|
+
type: "button",
|
|
784
|
+
onClick: remove,
|
|
785
|
+
className: "absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full border border-frost bg-snow text-[0.7em] text-mocha hover:border-sapphire hover:text-sapphire",
|
|
786
|
+
"aria-label": "remove attachment",
|
|
787
|
+
children: "✕"
|
|
788
|
+
})]
|
|
789
|
+
}, `${a.mimeType}-${i}`) : /* @__PURE__ */ jsxs("div", {
|
|
790
|
+
className: "flex items-center gap-1.5 rounded-md border border-frost bg-snow px-2 py-1 text-[0.82em] text-mocha",
|
|
791
|
+
children: [
|
|
792
|
+
/* @__PURE__ */ jsx("span", { children: "📄" }),
|
|
793
|
+
/* @__PURE__ */ jsx("span", {
|
|
794
|
+
className: "max-w-[160px] truncate",
|
|
795
|
+
children: a.name ?? "file"
|
|
796
|
+
}),
|
|
797
|
+
/* @__PURE__ */ jsx("button", {
|
|
798
|
+
type: "button",
|
|
799
|
+
onClick: remove,
|
|
800
|
+
className: "text-[0.85em] text-mocha hover:text-sapphire",
|
|
801
|
+
"aria-label": "remove attachment",
|
|
802
|
+
children: "✕"
|
|
803
|
+
})
|
|
804
|
+
]
|
|
805
|
+
}, `${a.name ?? "file"}-${i}`);
|
|
806
|
+
})
|
|
807
|
+
}),
|
|
808
|
+
/* @__PURE__ */ jsxs("form", {
|
|
809
|
+
className: "flex items-end gap-2 border-t border-frost bg-ivory px-5 py-3",
|
|
810
|
+
onSubmit: (e) => {
|
|
811
|
+
e.preventDefault();
|
|
812
|
+
send(input);
|
|
813
|
+
},
|
|
814
|
+
children: [
|
|
815
|
+
/* @__PURE__ */ jsx("input", {
|
|
816
|
+
ref: fileInputRef,
|
|
817
|
+
type: "file",
|
|
818
|
+
multiple: true,
|
|
819
|
+
className: "hidden",
|
|
820
|
+
onChange: (e) => {
|
|
821
|
+
addFiles(e.target.files);
|
|
822
|
+
e.target.value = "";
|
|
823
|
+
}
|
|
824
|
+
}),
|
|
825
|
+
/* @__PURE__ */ jsx("button", {
|
|
826
|
+
type: "button",
|
|
827
|
+
onClick: () => fileInputRef.current?.click(),
|
|
828
|
+
disabled: streaming,
|
|
829
|
+
title: "attach images or files",
|
|
830
|
+
"aria-label": "attach files",
|
|
831
|
+
className: "rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[1em] text-mocha transition-colors hover:border-sapphire hover:text-sapphire disabled:cursor-not-allowed disabled:opacity-50",
|
|
832
|
+
children: "📎"
|
|
833
|
+
}),
|
|
834
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
835
|
+
ref: inputRef,
|
|
836
|
+
rows: 1,
|
|
837
|
+
value: input,
|
|
838
|
+
onChange: (e) => setInput(e.target.value),
|
|
839
|
+
onKeyDown,
|
|
840
|
+
onPaste,
|
|
841
|
+
disabled: streaming,
|
|
842
|
+
placeholder: "say something… (Shift+Enter for newline; paste or 📎 to attach images/files)",
|
|
843
|
+
autoComplete: "off",
|
|
844
|
+
className: "max-h-[200px] min-h-[2.4rem] flex-1 resize-none overflow-y-auto rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[0.93em] leading-[1.45] text-chocolate outline-none transition-colors focus:border-sapphire focus:shadow-[0_0_0_3px_var(--color-sapphire-glow)]"
|
|
845
|
+
}),
|
|
846
|
+
/* @__PURE__ */ jsx("button", {
|
|
847
|
+
type: "submit",
|
|
848
|
+
disabled: streaming || !input.trim() && attachments.length === 0,
|
|
849
|
+
className: "rounded-md bg-sapphire px-4 py-2 text-[0.92em] font-semibold text-snow transition-colors hover:bg-sapphire-deep disabled:cursor-not-allowed disabled:opacity-50",
|
|
850
|
+
children: "send"
|
|
851
|
+
}),
|
|
852
|
+
streaming && /* @__PURE__ */ jsx("button", {
|
|
853
|
+
type: "button",
|
|
854
|
+
onClick: cancel,
|
|
855
|
+
className: "rounded-md border-[1.5px] border-frost bg-transparent px-3 py-1.5 text-[0.92em] font-medium text-mocha hover:border-sapphire-light hover:bg-sapphire-glow hover:text-sapphire",
|
|
856
|
+
children: "cancel"
|
|
857
|
+
})
|
|
858
|
+
]
|
|
859
|
+
})
|
|
860
|
+
]
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
function Bubble({ entry, isLastUser, isWillDrop, onEdit }) {
|
|
864
|
+
const dropCls = isWillDrop ? "opacity-40 [&_.bubble-content]:line-through" : "";
|
|
865
|
+
if (entry.type === "user") {
|
|
866
|
+
if (entry.content.startsWith(INBOX_WAKE_PREFIX)) {
|
|
867
|
+
const body = entry.content.slice(24);
|
|
868
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
869
|
+
className: `my-4 flex flex-col items-end ${dropCls}`,
|
|
870
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
871
|
+
className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-mocha opacity-90",
|
|
872
|
+
children: "inbox"
|
|
873
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
874
|
+
className: "bubble-content max-w-[85%] whitespace-pre-wrap break-words rounded-[14px_14px_4px_14px] border border-fawn border-l-[3px] border-l-mocha-light bg-ivory px-3 py-2 font-mono text-[0.88em] leading-[1.5] text-mocha",
|
|
875
|
+
children: body
|
|
876
|
+
})]
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
880
|
+
className: `group relative my-4 flex flex-col items-end ${dropCls}`,
|
|
881
|
+
children: [
|
|
882
|
+
/* @__PURE__ */ jsx("span", {
|
|
883
|
+
className: "sr-only",
|
|
884
|
+
children: "you"
|
|
885
|
+
}),
|
|
886
|
+
entry.images && entry.images.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
887
|
+
className: "mb-1 flex max-w-[85%] flex-wrap justify-end gap-1",
|
|
888
|
+
children: entry.images.map((img, i) => /* @__PURE__ */ jsx("img", {
|
|
889
|
+
src: `data:${img.mimeType};base64,${img.data}`,
|
|
890
|
+
alt: "attachment",
|
|
891
|
+
className: "max-h-48 rounded-lg border border-sapphire-light"
|
|
892
|
+
}, i))
|
|
893
|
+
}),
|
|
894
|
+
entry.files && entry.files.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
895
|
+
className: "mb-1 flex max-w-[85%] flex-wrap justify-end gap-1",
|
|
896
|
+
children: entry.files.map((f, i) => /* @__PURE__ */ jsxs("span", {
|
|
897
|
+
className: "flex items-center gap-1 rounded-md border border-sapphire-light bg-sapphire-glow px-2 py-1 text-[0.8em] text-chocolate",
|
|
898
|
+
children: ["📄 ", /* @__PURE__ */ jsx("span", {
|
|
899
|
+
className: "max-w-[180px] truncate",
|
|
900
|
+
children: f.name
|
|
901
|
+
})]
|
|
902
|
+
}, i))
|
|
903
|
+
}),
|
|
904
|
+
entry.content && /* @__PURE__ */ jsx("div", {
|
|
905
|
+
className: "bubble-content max-w-[85%] whitespace-pre-wrap break-words rounded-[14px_14px_4px_14px] border border-sapphire-light bg-sapphire-glow px-3 py-2 text-chocolate",
|
|
906
|
+
children: entry.content
|
|
907
|
+
}),
|
|
908
|
+
isLastUser && onEdit && /* @__PURE__ */ jsx("button", {
|
|
909
|
+
type: "button",
|
|
910
|
+
onClick: onEdit,
|
|
911
|
+
title: "edit and resend — replaces the last turn",
|
|
912
|
+
className: "absolute left-1 top-1 rounded-sm border border-fawn bg-ivory px-2 py-0.5 text-[0.78em] font-medium text-mocha opacity-65 transition hover:border-sapphire hover:bg-sapphire-glow hover:text-sapphire hover:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100",
|
|
913
|
+
children: "edit"
|
|
914
|
+
})
|
|
915
|
+
]
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
if (entry.type === "assistant") {
|
|
919
|
+
if (entry.content.startsWith(COMPACTION_REPLAY_PREFIX)) {
|
|
920
|
+
const summary = entry.content.slice(22).trim();
|
|
921
|
+
return /* @__PURE__ */ jsx("div", {
|
|
922
|
+
className: `my-3 border-y border-dashed border-frost py-2 text-[0.85em] italic text-mocha-light ${dropCls}`,
|
|
923
|
+
children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", {
|
|
924
|
+
className: "cursor-pointer select-none text-center [&::-webkit-details-marker]:hidden",
|
|
925
|
+
children: "— conversation summary (click to expand) —"
|
|
926
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
927
|
+
className: "md-content mt-2 rounded-sm bg-frost/40 p-2 not-italic",
|
|
928
|
+
dangerouslySetInnerHTML: { __html: renderMd(summary) }
|
|
929
|
+
})] })
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
if (entry.content.startsWith("[error]") || entry.content.startsWith("[fatal]") || entry.content.startsWith("[network error]")) return /* @__PURE__ */ jsxs("div", {
|
|
933
|
+
className: `my-4 flex flex-col items-start ${dropCls}`,
|
|
934
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
935
|
+
className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-[#9B3D3D]",
|
|
936
|
+
children: "error"
|
|
937
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
938
|
+
className: "bubble-content rounded-r-sm border-l-[3px] border-l-[#9B3D3D] bg-[rgba(196,135,138,0.06)] py-1 pl-3 pr-2 leading-[1.55]",
|
|
939
|
+
dangerouslySetInnerHTML: { __html: renderMd(entry.content) }
|
|
940
|
+
})]
|
|
941
|
+
});
|
|
942
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
943
|
+
className: `my-4 flex flex-col items-start ${dropCls}`,
|
|
944
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
945
|
+
className: "sr-only",
|
|
946
|
+
children: "agent"
|
|
947
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
948
|
+
className: "md-content bubble-content w-full leading-[1.55] text-chocolate",
|
|
949
|
+
dangerouslySetInnerHTML: { __html: renderMd(entry.content) }
|
|
950
|
+
})]
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
if (entry.type === "tool") return /* @__PURE__ */ jsx(ToolGroup, {
|
|
954
|
+
items: entry.items,
|
|
955
|
+
dropCls
|
|
956
|
+
});
|
|
957
|
+
if (entry.type === "images") return /* @__PURE__ */ jsx("div", {
|
|
958
|
+
className: `my-2 space-y-2 ${dropCls}`,
|
|
959
|
+
children: entry.images.map((img, i) => /* @__PURE__ */ jsx("img", {
|
|
960
|
+
src: `data:${img.mimeType};base64,${img.data}`,
|
|
961
|
+
alt: "screenshot",
|
|
962
|
+
className: "block max-w-full rounded-lg border border-fawn"
|
|
963
|
+
}, i))
|
|
964
|
+
});
|
|
965
|
+
if (entry.type === "file") {
|
|
966
|
+
const isImage = entry.mimeType.startsWith("image/");
|
|
967
|
+
const href = `data:${entry.mimeType};base64,${entry.data}`;
|
|
968
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
969
|
+
className: `my-2 ${dropCls}`,
|
|
970
|
+
children: [isImage && /* @__PURE__ */ jsx("img", {
|
|
971
|
+
src: href,
|
|
972
|
+
alt: entry.name,
|
|
973
|
+
className: "mb-1 block max-w-full rounded-lg border border-fawn"
|
|
974
|
+
}), /* @__PURE__ */ jsxs("a", {
|
|
975
|
+
href,
|
|
976
|
+
download: entry.name,
|
|
977
|
+
className: "inline-flex items-center gap-2 rounded-md border border-fawn bg-ivory px-3 py-2 text-[0.88em] text-mocha hover:border-sapphire hover:text-sapphire",
|
|
978
|
+
children: [
|
|
979
|
+
/* @__PURE__ */ jsx("span", { children: "📄" }),
|
|
980
|
+
/* @__PURE__ */ jsx("span", {
|
|
981
|
+
className: "max-w-[260px] truncate",
|
|
982
|
+
children: entry.name
|
|
983
|
+
}),
|
|
984
|
+
/* @__PURE__ */ jsx("span", {
|
|
985
|
+
className: "text-[0.85em] opacity-70",
|
|
986
|
+
children: "download"
|
|
987
|
+
})
|
|
988
|
+
]
|
|
989
|
+
})]
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
if (entry.type === "system") return /* @__PURE__ */ jsxs("div", {
|
|
993
|
+
className: `my-3 rounded-r-sm border-l-[3px] border-sapphire bg-sapphire-glow px-3 py-1 font-mono text-[0.88em] text-sapphire-deep ${dropCls}`,
|
|
994
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
995
|
+
className: "mr-2 font-semibold uppercase tracking-wider opacity-80",
|
|
996
|
+
children: "system"
|
|
997
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
998
|
+
className: "whitespace-pre-wrap",
|
|
999
|
+
children: entry.content
|
|
1000
|
+
})]
|
|
1001
|
+
});
|
|
1002
|
+
if (entry.type === "error") return /* @__PURE__ */ jsx("div", {
|
|
1003
|
+
className: `my-3 rounded-r-sm border-l-[3px] border-[#9B3D3D] bg-[rgba(196,135,138,0.08)] px-3 py-1 text-[0.92em] text-[#9B3D3D] ${dropCls}`,
|
|
1004
|
+
children: entry.content
|
|
1005
|
+
});
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
function ToolGroup({ items, dropCls }) {
|
|
1009
|
+
const [expanded, setExpanded] = useState(false);
|
|
1010
|
+
const contentRef = useRef(null);
|
|
1011
|
+
const [overflows, setOverflows] = useState(false);
|
|
1012
|
+
useLayoutEffect(() => {
|
|
1013
|
+
const el = contentRef.current;
|
|
1014
|
+
if (!el) return;
|
|
1015
|
+
setOverflows(el.scrollHeight > 324);
|
|
1016
|
+
}, [items.length]);
|
|
1017
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1018
|
+
className: `my-1.5 rounded-r-sm border-l-[3px] border-fawn bg-ivory px-3 py-2 font-mono text-[0.82em] leading-[1.5] text-mocha-light ${dropCls}`,
|
|
1019
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1020
|
+
ref: contentRef,
|
|
1021
|
+
className: "relative overflow-hidden",
|
|
1022
|
+
style: { maxHeight: expanded ? "none" : `${TOOL_GROUP_MAX_HEIGHT_PX}px` },
|
|
1023
|
+
children: [items.map((it, i) => /* @__PURE__ */ jsx(ToolLine, { item: it }, i)), overflows && !expanded && /* @__PURE__ */ jsx("div", {
|
|
1024
|
+
className: "pointer-events-none absolute inset-x-0 bottom-0 h-10",
|
|
1025
|
+
style: { background: "linear-gradient(to bottom, rgba(247,240,229,0), var(--color-ivory))" }
|
|
1026
|
+
})]
|
|
1027
|
+
}), overflows && /* @__PURE__ */ jsx("button", {
|
|
1028
|
+
type: "button",
|
|
1029
|
+
onClick: () => setExpanded((v) => !v),
|
|
1030
|
+
className: "mt-2 rounded-sm border border-fawn bg-transparent px-2 py-0.5 text-[0.9em] text-mocha hover:border-sapphire hover:text-sapphire-deep",
|
|
1031
|
+
children: expanded ? "show less ↑" : "show more ↓"
|
|
1032
|
+
})]
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
function ToolLine({ item }) {
|
|
1036
|
+
if (item.kind === "call") {
|
|
1037
|
+
const args = prettyArgs(item.body);
|
|
1038
|
+
const multiLine = args.includes("\n");
|
|
1039
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1040
|
+
className: "whitespace-pre-wrap break-words py-0.5",
|
|
1041
|
+
children: [
|
|
1042
|
+
/* @__PURE__ */ jsx("span", {
|
|
1043
|
+
className: "mr-1 opacity-45",
|
|
1044
|
+
children: "→"
|
|
1045
|
+
}),
|
|
1046
|
+
/* @__PURE__ */ jsx("span", {
|
|
1047
|
+
className: "font-medium text-mocha",
|
|
1048
|
+
children: item.name
|
|
1049
|
+
}),
|
|
1050
|
+
multiLine ? /* @__PURE__ */ jsx("pre", {
|
|
1051
|
+
className: "ml-4 mt-0.5 rounded-sm bg-[rgba(42,31,22,0.04)] px-2 py-1 font-mono text-[0.95em] leading-[1.4] text-mocha",
|
|
1052
|
+
children: args
|
|
1053
|
+
}) : args ? /* @__PURE__ */ jsxs("span", { children: [
|
|
1054
|
+
" (",
|
|
1055
|
+
args,
|
|
1056
|
+
")"
|
|
1057
|
+
] }) : null
|
|
1058
|
+
]
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
if (item.kind === "error") return /* @__PURE__ */ jsxs("div", {
|
|
1062
|
+
className: "whitespace-pre-wrap break-words py-0.5 text-rose-baziu",
|
|
1063
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1064
|
+
className: "mr-1 opacity-45",
|
|
1065
|
+
children: "←"
|
|
1066
|
+
}), item.body]
|
|
1067
|
+
});
|
|
1068
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1069
|
+
className: "whitespace-pre-wrap break-words py-0.5",
|
|
1070
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1071
|
+
className: "mr-1 opacity-45",
|
|
1072
|
+
children: "←"
|
|
1073
|
+
}), item.body]
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
function Dot({ delay = "0s" }) {
|
|
1077
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1078
|
+
className: "inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-mocha-light/60",
|
|
1079
|
+
style: { animationDelay: delay },
|
|
1080
|
+
"aria-hidden": "true"
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
//#endregion
|
|
1084
|
+
export { ChatPane as t };
|