bazilion 0.14.2 → 0.16.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/dist/cli.js +1752 -825
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +16624 -9803
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +218 -3
- package/dist/web/client/assets/ChatPane-CQZzxOir.js +76 -0
- package/dist/web/client/assets/{ConfirmDialog-_bmG-wVG.js → ConfirmDialog-MicTTwdy.js} +1 -1
- package/dist/web/client/assets/{FieldRow-Do1cBkRc.js → FieldRow-DJ2Gx4Du.js} +1 -1
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-XP-QJul4.js → ProductionTeamPolicyEditor-DqUnN_ce.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-CwdFPdpk.js → ProfileCommunicationEditor-DArXub0b.js} +1 -1
- package/dist/web/client/assets/ResultCard-Cps_lXVN.js +1 -0
- package/dist/web/client/assets/ResultSourceConversation-CtzJ8QA-.js +1 -0
- package/dist/web/client/assets/TeamTabs-DbIk5vvT.js +1 -0
- package/dist/web/client/assets/{TemplatesTabs-D4WHvs6b.js → TemplatesTabs-CatJIjGi.js} +1 -1
- package/dist/web/client/assets/{UnsavedChangesGuard-B1aLqiK5.js → UnsavedChangesGuard-BCsLw-mU.js} +1 -1
- package/dist/web/client/assets/_id-B5Ov7cdO.js +1 -0
- package/dist/web/client/assets/{_id-BH3v_vI_.js → _id-BEDmIXVf.js} +1 -1
- package/dist/web/client/assets/_id-BFM_tn_s.js +1 -0
- package/dist/web/client/assets/_id-CrnEVcw8.js +1 -0
- package/dist/web/client/assets/{_id-3w4PFfma.js → _id-DxopIW-y.js} +1 -1
- package/dist/web/client/assets/{activity-A_HrG1Tv.js → activity-CrG8Bg60.js} +1 -1
- package/dist/web/client/assets/{agents-C8OVKq3N.js → agents-CMPOFcs-.js} +1 -1
- package/dist/web/client/assets/{agents-BeeFCt2c.js → agents-O4soKZND.js} +1 -1
- package/dist/web/client/assets/{approvals-BIdZ9BSn.js → approvals-CwDu_lrA.js} +1 -1
- package/dist/web/client/assets/{attention-B9bUrxiU.js → attention-BY8MMuM_.js} +1 -1
- package/dist/web/client/assets/chevron-right-Cf6vKeLq.js +1 -0
- package/dist/web/client/assets/{config-By86S7k-.js → config-Cn4iBJFz.js} +1 -1
- package/dist/web/client/assets/{inbox-DlXQCJLe.js → inbox-CWXql0K3.js} +1 -1
- package/dist/web/client/assets/{index-CLdmsmiN.js → index-Bq2zrG2_.js} +2 -2
- package/dist/web/client/assets/{learning-BqZIw2nA.js → learning-DAm4dyyc.js} +1 -1
- package/dist/web/client/assets/login-BsABI8Ux.js +1 -0
- package/dist/web/client/assets/{mcp-DKSY2yVa.js → mcp-loLB0wuh.js} +1 -1
- package/dist/web/client/assets/{members-9GuOiq6J.js → members-BJ0ikkTl.js} +1 -1
- package/dist/web/client/assets/{memory-DiGMI6FC.js → memory-Drn9Gv53.js} +1 -1
- package/dist/web/client/assets/{policy-TBGQffw5.js → policy-pQadZyk0.js} +1 -1
- package/dist/web/client/assets/results-9exU6f7m.js +1 -0
- package/dist/web/client/assets/routes-jw1PuqiR.js +1 -0
- package/dist/web/client/assets/{services-1Z398CES.js → services-BLso1BhA.js} +1 -1
- package/dist/web/client/assets/{skills-BP2gciFt.js → skills-BodqX3OI.js} +1 -1
- package/dist/web/client/assets/styles-d1yjBpud.css +2 -0
- package/dist/web/client/assets/{teams-DBIivGT7.js → teams-ByQedXZi.js} +1 -1
- package/dist/web/client/assets/{teams-tNoMDJ_M.js → teams-SYsF1bOR.js} +1 -1
- package/dist/web/client/assets/telegram-DC9PLFbB.js +1 -0
- package/dist/web/client/assets/{tokens-eDWCGQQ2.js → tokens-DGy8Ijpo.js} +1 -1
- package/dist/web/client/assets/{triggers-DZME3IcK.js → triggers-BXpVmHDG.js} +1 -1
- package/dist/web/client/assets/{welcome-B49sh_uf.js → welcome-BKsnsCxJ.js} +1 -1
- package/dist/web/client/assets/wire-constants-CBD-lTaa.js +1 -0
- package/dist/web/client/assets/{x-CHc7_-TH.js → x-BK4xNCzS.js} +1 -1
- package/dist/web/server/assets/ChatPane-CS5oI7ef.js +2666 -0
- package/dist/web/server/assets/ResultCard-B3zD9N0t.js +143 -0
- package/dist/web/server/assets/ResultSourceConversation-BpVWBRYq.js +64 -0
- package/dist/web/server/assets/{TeamTabs-Co3n2exj.js → TeamTabs-TCKjCvMA.js} +4 -0
- package/dist/web/server/assets/{_id-sNu5aXZt.js → _id-5pgKoid8.js} +2 -1
- package/dist/web/server/assets/_id-BDEpe6k0.js +6 -0
- package/dist/web/server/assets/{_id-DxCnEFpm.js → _id-BG2nkGxa.js} +3 -4
- package/dist/web/server/assets/{_id-BqWTB8Wl.js → _id-B_-CDHOi2.js} +1 -1
- package/dist/web/server/assets/{_id-BeKhPKw3.js → _id-BnqodC1c2.js} +7 -3
- package/dist/web/server/assets/{_id-DvlDkk04.js → _id-CeyHEsCZ.js} +1 -1
- package/dist/web/server/assets/{_id-BYKpT2Tu.js → _id-DFHB9JFQ.js} +1 -1
- package/dist/web/server/assets/{_id-DYU6uIBx.js → _id-DOnInzLb.js} +1 -1
- package/dist/web/server/assets/{_id-HMy7jM3P.js → _id-DrB9uR1b.js} +1 -1
- package/dist/web/server/assets/{_id-BAA9KJBU.js → _id-DtIYuJJX.js} +1 -1
- package/dist/web/server/assets/_id-DwZXhLlL.js +779 -0
- package/dist/web/server/assets/_id-wyjrf0JG.js +17 -0
- package/dist/web/server/assets/{_tanstack-start-manifest_v-Duhf0z7H.js → _tanstack-start-manifest_v-D7gKv6m6.js} +97 -70
- package/dist/web/server/assets/{activity-9VP6QDTH.js → activity-0VUkStOu.js} +1 -1
- package/dist/web/server/assets/{activity-BWv1goK6.js → activity-5fWKPDxj.js} +2 -2
- package/dist/web/server/assets/{activity-LjpblbDL.js → activity-C88qrNqv.js} +1 -1
- package/dist/web/server/assets/{agents-DjyQk94u.js → agents-C86K6HvS.js} +1 -1
- package/dist/web/server/assets/{agents-BJAv919x.js → agents-CSXLaM2W.js} +1 -1
- package/dist/web/server/assets/{approvals-BYi84gwl.js → approvals-C3MysI6l.js} +1 -1
- package/dist/web/server/assets/{approvals-7JOoBchn.js → approvals-DTdrltyk.js} +1 -1
- package/dist/web/server/assets/{approvals-Bz123liE.js → approvals-DXoN-T57.js} +15 -1
- package/dist/web/server/assets/{attention-UhqPK_Ro.js → attention-DLeLCKah.js} +1 -1
- package/dist/web/server/assets/{auth-7BHTH-KS.js → auth-CDZp9aEk.js} +1 -1
- package/dist/web/server/assets/{config-BMiajTZP.js → config-BVTHvljR.js} +1 -1
- package/dist/web/server/assets/{daemon-client-CI5NHwNC.js → daemon-client-xWyYKAcx.js} +59 -0
- package/dist/web/server/assets/{inbox-Dv55rGYq.js → inbox-PQTrjtOw.js} +1 -1
- package/dist/web/server/assets/{learning-C2upa1CE.js → learning-K0cmDsZR.js} +1 -1
- package/dist/web/server/assets/{login-BNj3U6ok.js → login-B_-9L8TU.js} +7 -2
- package/dist/web/server/assets/{login-Be8eP4Jp.js → login-rDox7PRD.js} +5 -2
- package/dist/web/server/assets/{mcp-BvnvlVFd.js → mcp-Rtraochg.js} +1 -1
- package/dist/web/server/assets/{members-DiYtr-zm.js → members-CD3GiTfF.js} +1 -1
- package/dist/web/server/assets/{members-DHYTVgwb.js → members-cc5SeEaW.js} +2 -2
- package/dist/web/server/assets/{members-BP3UTqu1.js → members-grAKFWgv.js} +1 -1
- package/dist/web/server/assets/{memory-CkXUgf5s.js → memory-C5i_GxlY.js} +2 -2
- package/dist/web/server/assets/{memory-DTFEmfF3.js → memory-CVqnyUaM.js} +1 -1
- package/dist/web/server/assets/{memory-BqV-Afy1.js → memory-egFpBD1g.js} +1 -1
- package/dist/web/server/assets/{policy-Bvvav8fh.js → policy-Dgjb0_gK.js} +2 -2
- package/dist/web/server/assets/{policy-BddK4xom.js → policy-bLKwvnja.js} +1 -1
- package/dist/web/server/assets/{policy-18fdTFO4.js → policy-ooSBgiDS.js} +1 -1
- package/dist/web/server/assets/results-CfNsFncJ.js +173 -0
- package/dist/web/server/assets/results-DXdYtHaq.js +12 -0
- package/dist/web/server/assets/results-N-kP4iFT.js +12 -0
- package/dist/web/server/assets/{router-CY_pCgP1.js → router-B6g_s7W3.js} +60 -43
- package/dist/web/server/assets/{routes-COtkyZG1.js → routes-BqSaZvlu.js} +3 -3
- package/dist/web/server/assets/{routes-BgHisAxH.js → routes-Dsh8F_7i.js} +2 -2
- package/dist/web/server/assets/{routes-DAU2g7Uz.js → routes-pcRy_Sf8.js} +1 -1
- package/dist/web/server/assets/{services-DozuJtzX.js → services-DlZA8fRL.js} +1 -1
- package/dist/web/server/assets/{skills-DfGSJTCB.js → skills-i2duvgW-.js} +1 -1
- package/dist/web/server/assets/{teams-be05ci17.js → teams-6RYIj1Xm.js} +1 -1
- package/dist/web/server/assets/{teams-DPnAX4wD.js → teams-B1AI3NfN.js} +1 -1
- package/dist/web/server/assets/{telegram-1AWL6lFw.js → telegram-C4q0YTVn.js} +1 -1
- package/dist/web/server/assets/{telegram-Dj4Wwhd6.js → telegram-CoNeOgqZ.js} +465 -1
- package/dist/web/server/assets/{telegram-DbOFpTFm.js → telegram-Zd1rIZxg.js} +1 -1
- package/dist/web/server/assets/{tokens-DjM8GsgS.js → tokens-RFquA2Bx.js} +1 -1
- package/dist/web/server/assets/{triggers-Nezfw-Bd.js → triggers-DVD-iE-N.js} +1 -1
- package/dist/web/server/server.js +31 -27
- package/dist/worker.js +1765 -710
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/dist/web/client/assets/TeamTabs-DIRiKcqx.js +0 -1
- package/dist/web/client/assets/_id-Cx7o4Zmf.js +0 -1
- package/dist/web/client/assets/_id-DIiVVxBl.js +0 -1
- package/dist/web/client/assets/chevron-right-VWrk_dTX.js +0 -1
- package/dist/web/client/assets/login-CCMPXiny.js +0 -1
- package/dist/web/client/assets/routes-g3yUOhCb.js +0 -1
- package/dist/web/client/assets/styles-B5vcgXln.css +0 -2
- package/dist/web/client/assets/telegram-CJG_DFdI.js +0 -1
- package/dist/web/client/assets/wire-constants-zZ7Wjb9C.js +0 -74
- package/dist/web/server/assets/ChatPane-CgSe7tsy.js +0 -1429
- package/dist/web/server/assets/_id-ndxFHx0t.js +0 -381
|
@@ -1,1429 +0,0 @@
|
|
|
1
|
-
import { t as Button } from "./Button-Dsnw22OF.js";
|
|
2
|
-
import { t as ConfirmDialog } from "./ConfirmDialog-C-MPYYSU.js";
|
|
3
|
-
import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import DOMPurify from "dompurify";
|
|
6
|
-
import { marked } from "marked";
|
|
7
|
-
//#region src/lib/md.ts
|
|
8
|
-
var installed = false;
|
|
9
|
-
function install() {
|
|
10
|
-
if (installed) return;
|
|
11
|
-
marked.setOptions({
|
|
12
|
-
breaks: true,
|
|
13
|
-
gfm: true
|
|
14
|
-
});
|
|
15
|
-
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
|
|
16
|
-
if (node.nodeName === "A") {
|
|
17
|
-
node.setAttribute("target", "_blank");
|
|
18
|
-
node.setAttribute("rel", "noopener noreferrer");
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
installed = true;
|
|
22
|
-
}
|
|
23
|
-
function renderMd(text, domReady) {
|
|
24
|
-
if (!text) return "";
|
|
25
|
-
if (!domReady || typeof window === "undefined") return escapeHtml(text);
|
|
26
|
-
try {
|
|
27
|
-
install();
|
|
28
|
-
const html = marked.parse(text, { async: false });
|
|
29
|
-
return DOMPurify.sanitize(html);
|
|
30
|
-
} catch {
|
|
31
|
-
return escapeHtml(text);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function escapeHtml(s) {
|
|
35
|
-
return String(s).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/components/ChatPane.tsx
|
|
39
|
-
var INBOX_WAKE_PREFIX = "[[bazilion:inbox-wake]]\n";
|
|
40
|
-
var COMPACTION_REPLAY_PREFIX = "[conversation summary]";
|
|
41
|
-
var AUTOSCROLL_THRESHOLD_PX = 40;
|
|
42
|
-
var SESSION_POLL_MS = 1e4;
|
|
43
|
-
var TOOL_GROUP_MAX_HEIGHT_PX = 320;
|
|
44
|
-
var MAX_INPUT_HEIGHT = 200;
|
|
45
|
-
var subscribeToHydration = () => () => {};
|
|
46
|
-
var browserMarkdownReady = () => true;
|
|
47
|
-
var serverMarkdownReady = () => false;
|
|
48
|
-
/** Keep one inline card per ephemeral shell approval while preserving its first position. */
|
|
49
|
-
function upsertCommandApprovalEntry(entries, approval) {
|
|
50
|
-
const index = entries.findIndex((entry) => entry.type === "command_approval" && entry.approval.id === approval.id);
|
|
51
|
-
if (index === -1) return [...entries, {
|
|
52
|
-
type: "command_approval",
|
|
53
|
-
approval
|
|
54
|
-
}];
|
|
55
|
-
const next = [...entries];
|
|
56
|
-
next[index] = {
|
|
57
|
-
type: "command_approval",
|
|
58
|
-
approval
|
|
59
|
-
};
|
|
60
|
-
return next;
|
|
61
|
-
}
|
|
62
|
-
function interactiveChatRequest(message, attachments) {
|
|
63
|
-
return {
|
|
64
|
-
message,
|
|
65
|
-
attachments,
|
|
66
|
-
bashApprovalMode: "interactive"
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function shellApprovalsUrl(agentId) {
|
|
70
|
-
return `/api/shell-approvals?agentId=${encodeURIComponent(agentId)}`;
|
|
71
|
-
}
|
|
72
|
-
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";
|
|
73
|
-
function prettyArgs(raw) {
|
|
74
|
-
if (!raw) return "";
|
|
75
|
-
try {
|
|
76
|
-
return JSON.stringify(JSON.parse(raw), null, 2);
|
|
77
|
-
} catch {
|
|
78
|
-
return raw;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function formatBytes(n) {
|
|
82
|
-
if (n < 1024) return `${n} B`;
|
|
83
|
-
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
|
84
|
-
return `${(n / (1024 * 1024)).toFixed(2)} MiB`;
|
|
85
|
-
}
|
|
86
|
-
/** Read any File into a base64 Attachment (strips the data: URL prefix). */
|
|
87
|
-
function fileToAttachment(file) {
|
|
88
|
-
return new Promise((resolve, reject) => {
|
|
89
|
-
const reader = new FileReader();
|
|
90
|
-
reader.onload = () => {
|
|
91
|
-
const result = String(reader.result);
|
|
92
|
-
const comma = result.indexOf(",");
|
|
93
|
-
resolve({
|
|
94
|
-
name: file.name,
|
|
95
|
-
data: comma >= 0 ? result.slice(comma + 1) : result,
|
|
96
|
-
mimeType: file.type || "application/octet-stream"
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
reader.onerror = () => reject(reader.error);
|
|
100
|
-
reader.readAsDataURL(file);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
var isImageMime = (m) => m.startsWith("image/");
|
|
104
|
-
function projectMessages(msgs) {
|
|
105
|
-
const entries = [];
|
|
106
|
-
let openTool = null;
|
|
107
|
-
for (const m of msgs) if (m.role === "user") {
|
|
108
|
-
openTool = null;
|
|
109
|
-
entries.push({
|
|
110
|
-
type: "user",
|
|
111
|
-
content: m.content,
|
|
112
|
-
...m.images && m.images.length > 0 ? { images: m.images } : {}
|
|
113
|
-
});
|
|
114
|
-
} else if (m.role === "assistant") {
|
|
115
|
-
if (m.content) {
|
|
116
|
-
openTool = null;
|
|
117
|
-
entries.push({
|
|
118
|
-
type: "assistant",
|
|
119
|
-
content: m.content
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
123
|
-
if (!openTool) {
|
|
124
|
-
openTool = {
|
|
125
|
-
type: "tool",
|
|
126
|
-
items: []
|
|
127
|
-
};
|
|
128
|
-
entries.push(openTool);
|
|
129
|
-
}
|
|
130
|
-
for (const tc of m.toolCalls) openTool.items.push({
|
|
131
|
-
kind: "call",
|
|
132
|
-
id: tc.id,
|
|
133
|
-
name: tc.name,
|
|
134
|
-
body: tc.arguments
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
} else if (m.role === "tool") {
|
|
138
|
-
if (!openTool) {
|
|
139
|
-
openTool = {
|
|
140
|
-
type: "tool",
|
|
141
|
-
items: []
|
|
142
|
-
};
|
|
143
|
-
entries.push(openTool);
|
|
144
|
-
}
|
|
145
|
-
const isError = m.content.startsWith("Error:") || m.content.startsWith("error:");
|
|
146
|
-
openTool.items.push({
|
|
147
|
-
kind: isError ? "error" : "result",
|
|
148
|
-
id: m.toolCallId ?? "",
|
|
149
|
-
name: m.toolName ?? "",
|
|
150
|
-
body: m.content
|
|
151
|
-
});
|
|
152
|
-
if (m.images && m.images.length > 0) {
|
|
153
|
-
entries.push({
|
|
154
|
-
type: "images",
|
|
155
|
-
images: m.images
|
|
156
|
-
});
|
|
157
|
-
openTool = null;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return entries;
|
|
161
|
-
}
|
|
162
|
-
function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
163
|
-
const markdownReady = useSyncExternalStore(subscribeToHydration, browserMarkdownReady, serverMarkdownReady);
|
|
164
|
-
const [serverMessages, setServerMessages] = useState(initialMessages);
|
|
165
|
-
const [liveEntries, setLiveEntries] = useState([]);
|
|
166
|
-
const [systemBubbles, setSystemBubbles] = useState([]);
|
|
167
|
-
const [thinking, setThinking] = useState(false);
|
|
168
|
-
const [streaming, setStreaming] = useState(false);
|
|
169
|
-
const [editIdx, setEditIdx] = useState(null);
|
|
170
|
-
const [input, setInput] = useState("");
|
|
171
|
-
const [attachments, setAttachments] = useState([]);
|
|
172
|
-
const [dragging, setDragging] = useState(false);
|
|
173
|
-
const [staleBanner, setStaleBanner] = useState(false);
|
|
174
|
-
const [recoveredTurn, setRecoveredTurn] = useState(false);
|
|
175
|
-
const [approvalBusy, setApprovalBusy] = useState({});
|
|
176
|
-
const [approvalErrors, setApprovalErrors] = useState({});
|
|
177
|
-
const [resetConfirmOpen, setResetConfirmOpen] = useState(false);
|
|
178
|
-
const messagesRef = useRef(null);
|
|
179
|
-
const fileInputRef = useRef(null);
|
|
180
|
-
const inputRef = useRef(null);
|
|
181
|
-
const currentAbortRef = useRef(null);
|
|
182
|
-
const currentAgentIdRef = useRef(agentId);
|
|
183
|
-
const commandApprovalTurnRef = useRef(false);
|
|
184
|
-
currentAgentIdRef.current = agentId;
|
|
185
|
-
const systemIdRef = useRef(0);
|
|
186
|
-
const visibleEntryCountRef = useRef(0);
|
|
187
|
-
const knownHeadRef = useRef(initialSessionHead ?? {
|
|
188
|
-
file: null,
|
|
189
|
-
size: 0
|
|
190
|
-
});
|
|
191
|
-
const streamingRef = useRef(false);
|
|
192
|
-
streamingRef.current = streaming;
|
|
193
|
-
const turnBusy = streaming || recoveredTurn;
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
currentAbortRef.current?.abort();
|
|
196
|
-
currentAbortRef.current = null;
|
|
197
|
-
commandApprovalTurnRef.current = false;
|
|
198
|
-
setServerMessages(initialMessages);
|
|
199
|
-
setLiveEntries([]);
|
|
200
|
-
setSystemBubbles([]);
|
|
201
|
-
setEditIdx(null);
|
|
202
|
-
setInput("");
|
|
203
|
-
setThinking(false);
|
|
204
|
-
setStreaming(false);
|
|
205
|
-
setStaleBanner(false);
|
|
206
|
-
setRecoveredTurn(false);
|
|
207
|
-
setApprovalBusy({});
|
|
208
|
-
setApprovalErrors({});
|
|
209
|
-
knownHeadRef.current = initialSessionHead ?? {
|
|
210
|
-
file: null,
|
|
211
|
-
size: 0
|
|
212
|
-
};
|
|
213
|
-
}, [agentId]);
|
|
214
|
-
useEffect(() => {
|
|
215
|
-
const controller = new AbortController();
|
|
216
|
-
let stopped = false;
|
|
217
|
-
async function recoverPendingCommandApprovals() {
|
|
218
|
-
try {
|
|
219
|
-
const response = await fetch(shellApprovalsUrl(agentId), { signal: controller.signal });
|
|
220
|
-
if (!response.ok) return;
|
|
221
|
-
const pending = (await response.json()).approvals.filter((approval) => approval.agentId === agentId && approval.status === "pending");
|
|
222
|
-
if (stopped || pending.length === 0) return;
|
|
223
|
-
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
224
|
-
commandApprovalTurnRef.current = true;
|
|
225
|
-
setLiveEntries((entries) => pending.reduce(upsertCommandApprovalEntry, entries));
|
|
226
|
-
setRecoveredTurn(true);
|
|
227
|
-
} catch (error) {
|
|
228
|
-
if (error.name === "AbortError") return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
recoverPendingCommandApprovals();
|
|
232
|
-
return () => {
|
|
233
|
-
stopped = true;
|
|
234
|
-
controller.abort();
|
|
235
|
-
};
|
|
236
|
-
}, [agentId]);
|
|
237
|
-
const wasNearBottomRef = useRef(true);
|
|
238
|
-
useLayoutEffect(() => {
|
|
239
|
-
const el = messagesRef.current;
|
|
240
|
-
if (!el) return;
|
|
241
|
-
if (wasNearBottomRef.current) el.scrollTop = el.scrollHeight;
|
|
242
|
-
});
|
|
243
|
-
function captureScroll() {
|
|
244
|
-
const el = messagesRef.current;
|
|
245
|
-
if (!el) {
|
|
246
|
-
wasNearBottomRef.current = true;
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
wasNearBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < AUTOSCROLL_THRESHOLD_PX;
|
|
250
|
-
}
|
|
251
|
-
useLayoutEffect(() => {
|
|
252
|
-
const el = inputRef.current;
|
|
253
|
-
if (!el) return;
|
|
254
|
-
el.style.height = "auto";
|
|
255
|
-
el.style.height = `${Math.min(el.scrollHeight, MAX_INPUT_HEIGHT)}px`;
|
|
256
|
-
}, [input]);
|
|
257
|
-
useEffect(() => {
|
|
258
|
-
const key = `bz_pending_${agentId}`;
|
|
259
|
-
function onVis() {
|
|
260
|
-
if (document.visibilityState === "visible" && sessionStorage.getItem(key)) {
|
|
261
|
-
sessionStorage.removeItem(key);
|
|
262
|
-
window.location.reload();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
document.addEventListener("visibilitychange", onVis);
|
|
266
|
-
return () => document.removeEventListener("visibilitychange", onVis);
|
|
267
|
-
}, [agentId]);
|
|
268
|
-
useEffect(() => {
|
|
269
|
-
if (!initialSessionHead) return;
|
|
270
|
-
let stopped = false;
|
|
271
|
-
let timer = null;
|
|
272
|
-
async function tick() {
|
|
273
|
-
if (stopped) return;
|
|
274
|
-
if (!document.hidden && !streamingRef.current && !staleBanner) try {
|
|
275
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
|
|
276
|
-
if (res.ok) {
|
|
277
|
-
const body = await res.json();
|
|
278
|
-
if (typeof body.size === "number") {
|
|
279
|
-
const known = knownHeadRef.current;
|
|
280
|
-
if (body.file !== known.file || body.size !== known.size) setStaleBanner(true);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
} catch {}
|
|
284
|
-
timer = setTimeout(tick, SESSION_POLL_MS);
|
|
285
|
-
}
|
|
286
|
-
timer = setTimeout(tick, SESSION_POLL_MS);
|
|
287
|
-
return () => {
|
|
288
|
-
stopped = true;
|
|
289
|
-
if (timer) clearTimeout(timer);
|
|
290
|
-
};
|
|
291
|
-
}, [
|
|
292
|
-
agentId,
|
|
293
|
-
initialSessionHead,
|
|
294
|
-
staleBanner
|
|
295
|
-
]);
|
|
296
|
-
async function refreshKnownHead() {
|
|
297
|
-
if (currentAgentIdRef.current !== agentId) return;
|
|
298
|
-
try {
|
|
299
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
|
|
300
|
-
if (!res.ok) return;
|
|
301
|
-
const body = await res.json();
|
|
302
|
-
if (currentAgentIdRef.current === agentId && typeof body.size === "number") knownHeadRef.current = {
|
|
303
|
-
file: body.file ?? null,
|
|
304
|
-
size: body.size
|
|
305
|
-
};
|
|
306
|
-
} catch {}
|
|
307
|
-
}
|
|
308
|
-
function pushSystem(text) {
|
|
309
|
-
captureScroll();
|
|
310
|
-
setSystemBubbles((prev) => [...prev, {
|
|
311
|
-
id: systemIdRef.current++,
|
|
312
|
-
content: text,
|
|
313
|
-
afterIdx: visibleEntryCountRef.current
|
|
314
|
-
}]);
|
|
315
|
-
}
|
|
316
|
-
async function runContextCommand() {
|
|
317
|
-
try {
|
|
318
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/context`);
|
|
319
|
-
if (!res.ok) {
|
|
320
|
-
pushSystem(`/context failed: ${res.statusText}`);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const ctx = await res.json();
|
|
324
|
-
const fmt = (chars, tokens) => `${chars.toLocaleString()} chars (~${tokens.toLocaleString()} tok)`;
|
|
325
|
-
const lines = [];
|
|
326
|
-
lines.push(`context for ${ctx.agentId}`);
|
|
327
|
-
lines.push(`model: ${ctx.model}`);
|
|
328
|
-
lines.push("");
|
|
329
|
-
lines.push(`system prompt: ${fmt(ctx.systemPrompt.chars, ctx.systemPrompt.tokens)}`);
|
|
330
|
-
for (const f of ctx.systemPrompt.files) lines.push(` - ${f.name}: ${f.chars.toLocaleString()} chars`);
|
|
331
|
-
lines.push("");
|
|
332
|
-
lines.push(`tools: ${ctx.tools.count} (${ctx.tools.schemaChars.toLocaleString()} chars of schema)`);
|
|
333
|
-
for (const t of ctx.tools.entries.slice(0, 5)) lines.push(` - ${t.name}: ${t.schemaChars.toLocaleString()} chars`);
|
|
334
|
-
if (ctx.skills.count > 0) {
|
|
335
|
-
lines.push("");
|
|
336
|
-
lines.push(`skills: ${ctx.skills.count}`);
|
|
337
|
-
for (const s of ctx.skills.entries.slice(0, 10)) lines.push(` - ${s.name}: ${s.blockChars.toLocaleString()} chars`);
|
|
338
|
-
}
|
|
339
|
-
lines.push("");
|
|
340
|
-
lines.push(`history: ${ctx.history.messageEntries} messages / ${ctx.history.compactionEntries} compactions`);
|
|
341
|
-
lines.push(` ${fmt(ctx.history.chars, ctx.history.tokensEstimate)}`);
|
|
342
|
-
lines.push(` bytes on disk: ${formatBytes(ctx.history.bytes)}`);
|
|
343
|
-
lines.push("");
|
|
344
|
-
lines.push(`TOTAL: ${fmt(ctx.totals.chars, ctx.totals.tokens)}`);
|
|
345
|
-
pushSystem(lines.join("\n"));
|
|
346
|
-
} catch (err) {
|
|
347
|
-
pushSystem(`/context failed: ${err.message}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
async function performReset() {
|
|
351
|
-
exitEditMode();
|
|
352
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/reset`, { method: "POST" });
|
|
353
|
-
if (!res.ok) {
|
|
354
|
-
let message = res.statusText;
|
|
355
|
-
try {
|
|
356
|
-
message = (await res.json()).error || message;
|
|
357
|
-
} catch {}
|
|
358
|
-
throw new Error(message);
|
|
359
|
-
}
|
|
360
|
-
setServerMessages([]);
|
|
361
|
-
setLiveEntries([]);
|
|
362
|
-
setSystemBubbles([]);
|
|
363
|
-
pushSystem("/reset: history wiped");
|
|
364
|
-
}
|
|
365
|
-
async function runResetCommand() {
|
|
366
|
-
if (serverMessages.length === 0) {
|
|
367
|
-
pushSystem("/reset: history already empty");
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
setResetConfirmOpen(true);
|
|
371
|
-
}
|
|
372
|
-
async function runCompactCommand(rest) {
|
|
373
|
-
if (serverMessages.length < 2) {
|
|
374
|
-
pushSystem("/compact: need ≥2 messages to compact");
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
let keepTail;
|
|
378
|
-
const argText = rest.trim();
|
|
379
|
-
if (argText) {
|
|
380
|
-
const n = Number(argText);
|
|
381
|
-
if (Number.isFinite(n) && n >= 0) keepTail = Math.floor(n);
|
|
382
|
-
}
|
|
383
|
-
exitEditMode();
|
|
384
|
-
setStreaming(true);
|
|
385
|
-
setThinking(true);
|
|
386
|
-
try {
|
|
387
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/compact`, {
|
|
388
|
-
method: "POST",
|
|
389
|
-
headers: { "content-type": "application/json" },
|
|
390
|
-
body: JSON.stringify(keepTail !== void 0 ? { keepTail } : {})
|
|
391
|
-
});
|
|
392
|
-
if (!res.ok) {
|
|
393
|
-
let msg = res.statusText;
|
|
394
|
-
try {
|
|
395
|
-
msg = (await res.json()).error || msg;
|
|
396
|
-
} catch {}
|
|
397
|
-
pushSystem(`/compact failed: ${msg}`);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const body = await res.json();
|
|
401
|
-
pushSystem(`/compact: ${body.before} → ${body.after} entries (${body.summarized} summarized, ${body.keptTail} kept verbatim; ~${body.tokensBefore.toLocaleString()} → ~${body.tokensAfter.toLocaleString()} tok). reloading…`);
|
|
402
|
-
setTimeout(() => window.location.reload(), 400);
|
|
403
|
-
} catch (err) {
|
|
404
|
-
pushSystem(`/compact failed: ${err.message}`);
|
|
405
|
-
} finally {
|
|
406
|
-
setThinking(false);
|
|
407
|
-
setStreaming(false);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
async function maybeHandleSlashCommand(text) {
|
|
411
|
-
const trimmed = text.trim();
|
|
412
|
-
if (!trimmed.startsWith("/")) return false;
|
|
413
|
-
const parts = trimmed.split(/\s+/);
|
|
414
|
-
const cmd = (parts[0] ?? "").toLowerCase();
|
|
415
|
-
const rest = parts.slice(1).join(" ");
|
|
416
|
-
switch (cmd) {
|
|
417
|
-
case "/help":
|
|
418
|
-
pushSystem(SLASH_HELP);
|
|
419
|
-
return true;
|
|
420
|
-
case "/context":
|
|
421
|
-
await runContextCommand();
|
|
422
|
-
return true;
|
|
423
|
-
case "/reset":
|
|
424
|
-
await runResetCommand();
|
|
425
|
-
return true;
|
|
426
|
-
case "/compact":
|
|
427
|
-
await runCompactCommand(rest);
|
|
428
|
-
return true;
|
|
429
|
-
default: return false;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
function findLastUserIdx() {
|
|
433
|
-
for (let i = serverMessages.length - 1; i >= 0; i--) if (serverMessages[i]?.role === "user") return i;
|
|
434
|
-
return -1;
|
|
435
|
-
}
|
|
436
|
-
function enterEditMode() {
|
|
437
|
-
if (liveEntries.length > 0 || turnBusy) return;
|
|
438
|
-
const idx = findLastUserIdx();
|
|
439
|
-
if (idx === -1) return;
|
|
440
|
-
const userMsg = serverMessages[idx];
|
|
441
|
-
if (!userMsg) return;
|
|
442
|
-
setEditIdx(idx);
|
|
443
|
-
setInput(userMsg.content);
|
|
444
|
-
inputRef.current?.focus();
|
|
445
|
-
}
|
|
446
|
-
function exitEditMode() {
|
|
447
|
-
setEditIdx(null);
|
|
448
|
-
}
|
|
449
|
-
async function addFiles(files) {
|
|
450
|
-
if (!files || turnBusy) return;
|
|
451
|
-
const arr = Array.from(files);
|
|
452
|
-
if (arr.length === 0) return;
|
|
453
|
-
const encoded = await Promise.all(arr.map(fileToAttachment));
|
|
454
|
-
setAttachments((prev) => [...prev, ...encoded].slice(0, 16));
|
|
455
|
-
}
|
|
456
|
-
function onPaste(e) {
|
|
457
|
-
const files = Array.from(e.clipboardData.files);
|
|
458
|
-
if (files.some((f) => f.type.startsWith("image/"))) {
|
|
459
|
-
e.preventDefault();
|
|
460
|
-
addFiles(files);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
function onDragOver(e) {
|
|
464
|
-
if (!Array.from(e.dataTransfer.types).includes("Files")) return;
|
|
465
|
-
e.preventDefault();
|
|
466
|
-
if (turnBusy) {
|
|
467
|
-
setDragging(false);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
setDragging(true);
|
|
471
|
-
}
|
|
472
|
-
function onDragLeave(e) {
|
|
473
|
-
if (e.currentTarget === e.target) setDragging(false);
|
|
474
|
-
}
|
|
475
|
-
function onDrop(e) {
|
|
476
|
-
if (e.dataTransfer.files.length === 0) return;
|
|
477
|
-
e.preventDefault();
|
|
478
|
-
setDragging(false);
|
|
479
|
-
if (turnBusy) return;
|
|
480
|
-
addFiles(e.dataTransfer.files);
|
|
481
|
-
}
|
|
482
|
-
const send = useCallback(async (text) => {
|
|
483
|
-
const atts = attachments;
|
|
484
|
-
if (!text.trim() && atts.length === 0 || turnBusy) return;
|
|
485
|
-
setInput("");
|
|
486
|
-
if (await maybeHandleSlashCommand(text)) return;
|
|
487
|
-
setAttachments([]);
|
|
488
|
-
let truncatedServerMessages = null;
|
|
489
|
-
if (editIdx !== null) {
|
|
490
|
-
const keep = editIdx;
|
|
491
|
-
try {
|
|
492
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/truncate`, {
|
|
493
|
-
method: "POST",
|
|
494
|
-
headers: { "content-type": "application/json" },
|
|
495
|
-
body: JSON.stringify({ keepCount: keep })
|
|
496
|
-
});
|
|
497
|
-
if (!res.ok) {
|
|
498
|
-
let err = res.statusText;
|
|
499
|
-
try {
|
|
500
|
-
err = (await res.json()).error || err;
|
|
501
|
-
} catch {}
|
|
502
|
-
setInput(text);
|
|
503
|
-
pushSystem(`Could not replace the last turn: ${err}`);
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
truncatedServerMessages = serverMessages.slice(0, keep);
|
|
507
|
-
setServerMessages(truncatedServerMessages);
|
|
508
|
-
setEditIdx(null);
|
|
509
|
-
} catch (err) {
|
|
510
|
-
setInput(text);
|
|
511
|
-
pushSystem(`Could not replace the last turn: ${err.message}`);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
captureScroll();
|
|
516
|
-
const previewImages = atts.filter((a) => isImageMime(a.mimeType));
|
|
517
|
-
const previewFiles = atts.filter((a) => !isImageMime(a.mimeType));
|
|
518
|
-
setLiveEntries([{
|
|
519
|
-
type: "user",
|
|
520
|
-
content: text,
|
|
521
|
-
...previewImages.length > 0 ? { images: previewImages } : {},
|
|
522
|
-
...previewFiles.length > 0 ? { files: previewFiles.map((f) => ({ name: f.name ?? "file" })) } : {}
|
|
523
|
-
}]);
|
|
524
|
-
setRecoveredTurn(false);
|
|
525
|
-
setApprovalBusy({});
|
|
526
|
-
setApprovalErrors({});
|
|
527
|
-
commandApprovalTurnRef.current = false;
|
|
528
|
-
sessionStorage.setItem(`bz_pending_${agentId}`, "1");
|
|
529
|
-
const abort = new AbortController();
|
|
530
|
-
currentAbortRef.current = abort;
|
|
531
|
-
setStreaming(true);
|
|
532
|
-
setThinking(true);
|
|
533
|
-
let terminalFrameReceived = false;
|
|
534
|
-
try {
|
|
535
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat`, {
|
|
536
|
-
method: "POST",
|
|
537
|
-
headers: { "content-type": "application/json" },
|
|
538
|
-
body: JSON.stringify(interactiveChatRequest(text, atts)),
|
|
539
|
-
signal: abort.signal
|
|
540
|
-
});
|
|
541
|
-
if (res.status === 202) {
|
|
542
|
-
const pending = await res.json();
|
|
543
|
-
setLiveEntries((prev) => [...prev, {
|
|
544
|
-
type: "error",
|
|
545
|
-
content: `[approval pending] ${pending.approvalId} · expires ${new Date(pending.expiresAt).toLocaleString()} · review in /approvals`
|
|
546
|
-
}]);
|
|
547
|
-
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (!res.ok || !res.body) {
|
|
551
|
-
let err = res.statusText;
|
|
552
|
-
try {
|
|
553
|
-
err = (await res.json()).error || err;
|
|
554
|
-
} catch {}
|
|
555
|
-
setLiveEntries((prev) => [...prev, {
|
|
556
|
-
type: "error",
|
|
557
|
-
content: `[error] ${err}`
|
|
558
|
-
}]);
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const reader = res.body.getReader();
|
|
562
|
-
const decoder = new TextDecoder();
|
|
563
|
-
let buffer = "";
|
|
564
|
-
while (true) {
|
|
565
|
-
const { done, value } = await reader.read();
|
|
566
|
-
if (done) break;
|
|
567
|
-
buffer += decoder.decode(value, { stream: true });
|
|
568
|
-
const lines = buffer.split("\n");
|
|
569
|
-
buffer = lines.pop() ?? "";
|
|
570
|
-
for (const line of lines) {
|
|
571
|
-
if (!line.trim()) continue;
|
|
572
|
-
let frame;
|
|
573
|
-
try {
|
|
574
|
-
frame = JSON.parse(line);
|
|
575
|
-
} catch {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
if (frame.kind === "done" || frame.kind === "fatal") terminalFrameReceived = true;
|
|
579
|
-
handleFrame(frame);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
if (buffer.trim()) try {
|
|
583
|
-
const frame = JSON.parse(buffer);
|
|
584
|
-
if (frame.kind === "done" || frame.kind === "fatal") terminalFrameReceived = true;
|
|
585
|
-
handleFrame(frame);
|
|
586
|
-
} catch {}
|
|
587
|
-
await refreshKnownHead();
|
|
588
|
-
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
589
|
-
} catch (err) {
|
|
590
|
-
if (err.name !== "AbortError") setLiveEntries((prev) => [...prev, {
|
|
591
|
-
type: "error",
|
|
592
|
-
content: `[network error] ${err.message}`
|
|
593
|
-
}]);
|
|
594
|
-
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
595
|
-
} finally {
|
|
596
|
-
if (currentAgentIdRef.current === agentId && currentAbortRef.current === abort) {
|
|
597
|
-
if (!terminalFrameReceived && commandApprovalTurnRef.current) setRecoveredTurn(true);
|
|
598
|
-
setThinking(false);
|
|
599
|
-
setStreaming(false);
|
|
600
|
-
currentAbortRef.current = null;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}, [
|
|
604
|
-
agentId,
|
|
605
|
-
editIdx,
|
|
606
|
-
serverMessages,
|
|
607
|
-
turnBusy,
|
|
608
|
-
attachments
|
|
609
|
-
]);
|
|
610
|
-
function handleFrame(frame) {
|
|
611
|
-
if (currentAgentIdRef.current !== agentId) return;
|
|
612
|
-
if (frame.kind === "fatal") {
|
|
613
|
-
commandApprovalTurnRef.current = false;
|
|
614
|
-
setLiveEntries((prev) => [...prev.map((entry) => entry.type === "command_approval" && entry.approval.status === "pending" ? {
|
|
615
|
-
type: "command_approval",
|
|
616
|
-
approval: {
|
|
617
|
-
...entry.approval,
|
|
618
|
-
status: "cancelled"
|
|
619
|
-
}
|
|
620
|
-
} : entry), {
|
|
621
|
-
type: "error",
|
|
622
|
-
content: `[fatal] ${frame.error}`
|
|
623
|
-
}]);
|
|
624
|
-
setRecoveredTurn(false);
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
if (frame.kind === "done") {
|
|
628
|
-
commandApprovalTurnRef.current = false;
|
|
629
|
-
setServerMessages(frame.messages);
|
|
630
|
-
setLiveEntries([]);
|
|
631
|
-
setRecoveredTurn(false);
|
|
632
|
-
setApprovalBusy({});
|
|
633
|
-
setApprovalErrors({});
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
if (frame.kind !== "event") return;
|
|
637
|
-
const ev = frame.event;
|
|
638
|
-
captureScroll();
|
|
639
|
-
if (ev.type === "user_message") return;
|
|
640
|
-
if (ev.type === "assistant_delta") {
|
|
641
|
-
setThinking(false);
|
|
642
|
-
setLiveEntries((prev) => {
|
|
643
|
-
const next = [...prev];
|
|
644
|
-
const last = next[next.length - 1];
|
|
645
|
-
if (last?.type === "assistant") next[next.length - 1] = {
|
|
646
|
-
type: "assistant",
|
|
647
|
-
content: last.content + ev.delta
|
|
648
|
-
};
|
|
649
|
-
else next.push({
|
|
650
|
-
type: "assistant",
|
|
651
|
-
content: ev.delta
|
|
652
|
-
});
|
|
653
|
-
return next;
|
|
654
|
-
});
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (ev.type === "assistant_message") {
|
|
658
|
-
setThinking(false);
|
|
659
|
-
setLiveEntries((prev) => {
|
|
660
|
-
const next = [...prev];
|
|
661
|
-
if (next[next.length - 1]?.type === "assistant") next[next.length - 1] = {
|
|
662
|
-
type: "assistant",
|
|
663
|
-
content: ev.text
|
|
664
|
-
};
|
|
665
|
-
else next.push({
|
|
666
|
-
type: "assistant",
|
|
667
|
-
content: ev.text
|
|
668
|
-
});
|
|
669
|
-
return next;
|
|
670
|
-
});
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
if (ev.type === "command_approval") {
|
|
674
|
-
if (ev.approval.agentId !== agentId) return;
|
|
675
|
-
commandApprovalTurnRef.current = true;
|
|
676
|
-
setThinking(ev.approval.status !== "pending" && ev.approval.status !== "cancelled");
|
|
677
|
-
setLiveEntries((entries) => upsertCommandApprovalEntry(entries, ev.approval));
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
if (ev.type === "tool_call" || ev.type === "tool_result" || ev.type === "tool_error") {
|
|
681
|
-
setThinking(ev.type !== "tool_call");
|
|
682
|
-
const item = ev.type === "tool_call" ? {
|
|
683
|
-
kind: "call",
|
|
684
|
-
id: ev.id,
|
|
685
|
-
name: ev.name,
|
|
686
|
-
body: ev.arguments
|
|
687
|
-
} : ev.type === "tool_result" ? {
|
|
688
|
-
kind: "result",
|
|
689
|
-
id: ev.id,
|
|
690
|
-
name: ev.name,
|
|
691
|
-
body: ev.result
|
|
692
|
-
} : {
|
|
693
|
-
kind: "error",
|
|
694
|
-
id: ev.id,
|
|
695
|
-
name: ev.name,
|
|
696
|
-
body: ev.error
|
|
697
|
-
};
|
|
698
|
-
const images = ev.type === "tool_result" ? ev.images : void 0;
|
|
699
|
-
setLiveEntries((prev) => {
|
|
700
|
-
const next = [...prev];
|
|
701
|
-
const last = next[next.length - 1];
|
|
702
|
-
if (last?.type === "tool") next[next.length - 1] = {
|
|
703
|
-
type: "tool",
|
|
704
|
-
items: [...last.items, item]
|
|
705
|
-
};
|
|
706
|
-
else next.push({
|
|
707
|
-
type: "tool",
|
|
708
|
-
items: [item]
|
|
709
|
-
});
|
|
710
|
-
if (images && images.length > 0) next.push({
|
|
711
|
-
type: "images",
|
|
712
|
-
images
|
|
713
|
-
});
|
|
714
|
-
return next;
|
|
715
|
-
});
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
if (ev.type === "file") {
|
|
719
|
-
setLiveEntries((prev) => [...prev, {
|
|
720
|
-
type: "file",
|
|
721
|
-
name: ev.name,
|
|
722
|
-
mimeType: ev.mimeType,
|
|
723
|
-
data: ev.data
|
|
724
|
-
}]);
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
if (ev.type === "error") setLiveEntries((prev) => [...prev, {
|
|
728
|
-
type: "error",
|
|
729
|
-
content: `[error] ${ev.error}`
|
|
730
|
-
}]);
|
|
731
|
-
}
|
|
732
|
-
async function reconcileCommandApproval(approvalId) {
|
|
733
|
-
if (currentAgentIdRef.current !== agentId) return null;
|
|
734
|
-
try {
|
|
735
|
-
const response = await fetch(shellApprovalsUrl(agentId));
|
|
736
|
-
if (!response.ok) return null;
|
|
737
|
-
const body = await response.json();
|
|
738
|
-
if (currentAgentIdRef.current !== agentId) return null;
|
|
739
|
-
const approval = body.approvals.find((item) => item.id === approvalId && item.agentId === agentId);
|
|
740
|
-
if (approval) setLiveEntries((entries) => upsertCommandApprovalEntry(entries, approval));
|
|
741
|
-
return approval ?? null;
|
|
742
|
-
} catch {
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
async function decideCommandApproval(approval, decision) {
|
|
747
|
-
if (approval.agentId !== agentId || approval.status !== "pending") return;
|
|
748
|
-
setApprovalBusy((current) => ({
|
|
749
|
-
...current,
|
|
750
|
-
[approval.id]: true
|
|
751
|
-
}));
|
|
752
|
-
setApprovalErrors((current) => {
|
|
753
|
-
const next = { ...current };
|
|
754
|
-
delete next[approval.id];
|
|
755
|
-
return next;
|
|
756
|
-
});
|
|
757
|
-
try {
|
|
758
|
-
const response = await fetch(`/api/shell-approvals/${encodeURIComponent(approval.id)}`, {
|
|
759
|
-
method: "POST",
|
|
760
|
-
headers: { "content-type": "application/json" },
|
|
761
|
-
body: JSON.stringify({ decision })
|
|
762
|
-
});
|
|
763
|
-
const body = await response.json().catch(() => null);
|
|
764
|
-
if (currentAgentIdRef.current !== agentId) return;
|
|
765
|
-
if (body?.approval?.agentId === agentId) setLiveEntries((entries) => upsertCommandApprovalEntry(entries, body.approval));
|
|
766
|
-
if (!response.ok) {
|
|
767
|
-
const message = body?.error ?? `Decision failed (${response.status})`;
|
|
768
|
-
const terminalStatus = commandApprovalStatusFromConflict(message);
|
|
769
|
-
if (terminalStatus) {
|
|
770
|
-
setLiveEntries((entries) => upsertCommandApprovalEntry(entries, {
|
|
771
|
-
...approval,
|
|
772
|
-
status: terminalStatus
|
|
773
|
-
}));
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
throw new Error(message);
|
|
777
|
-
}
|
|
778
|
-
} catch (error) {
|
|
779
|
-
const reconciled = await reconcileCommandApproval(approval.id);
|
|
780
|
-
if (currentAgentIdRef.current !== agentId) return;
|
|
781
|
-
if (!reconciled || reconciled.status === "pending") setApprovalErrors((current) => ({
|
|
782
|
-
...current,
|
|
783
|
-
[approval.id]: error instanceof Error ? error.message : String(error)
|
|
784
|
-
}));
|
|
785
|
-
} finally {
|
|
786
|
-
if (currentAgentIdRef.current === agentId) setApprovalBusy((current) => ({
|
|
787
|
-
...current,
|
|
788
|
-
[approval.id]: false
|
|
789
|
-
}));
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
async function cancel() {
|
|
793
|
-
const hasLocalStream = currentAbortRef.current !== null;
|
|
794
|
-
try {
|
|
795
|
-
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/cancel`, { method: "POST" });
|
|
796
|
-
if (currentAgentIdRef.current !== agentId) return;
|
|
797
|
-
if (res.ok || res.status === 204) {
|
|
798
|
-
if (!hasLocalStream) {
|
|
799
|
-
commandApprovalTurnRef.current = false;
|
|
800
|
-
setLiveEntries((entries) => entries.map((entry) => entry.type === "command_approval" && entry.approval.status === "pending" ? {
|
|
801
|
-
type: "command_approval",
|
|
802
|
-
approval: {
|
|
803
|
-
...entry.approval,
|
|
804
|
-
status: "cancelled"
|
|
805
|
-
}
|
|
806
|
-
} : entry));
|
|
807
|
-
setRecoveredTurn(false);
|
|
808
|
-
}
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
} catch {}
|
|
812
|
-
if (currentAgentIdRef.current !== agentId) return;
|
|
813
|
-
if (hasLocalStream) currentAbortRef.current?.abort();
|
|
814
|
-
else setLiveEntries((entries) => [...entries, {
|
|
815
|
-
type: "error",
|
|
816
|
-
content: "[cancel failed] could not reach the active turn"
|
|
817
|
-
}]);
|
|
818
|
-
}
|
|
819
|
-
function onKeyDown(e) {
|
|
820
|
-
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
821
|
-
e.preventDefault();
|
|
822
|
-
send(input);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
const baseEntries = projectMessages(serverMessages);
|
|
826
|
-
visibleEntryCountRef.current = baseEntries.length + liveEntries.length;
|
|
827
|
-
const lastUserIdx = (() => {
|
|
828
|
-
if (liveEntries.length > 0 || turnBusy) return -1;
|
|
829
|
-
for (let i = baseEntries.length - 1; i >= 0; i--) if (baseEntries[i]?.type === "user") return i;
|
|
830
|
-
return -1;
|
|
831
|
-
})();
|
|
832
|
-
const willDropFromIdx = (() => {
|
|
833
|
-
if (editIdx === null) return -1;
|
|
834
|
-
let userCount = 0;
|
|
835
|
-
for (let i = 0; i < baseEntries.length; i++) if (baseEntries[i]?.type === "user") {
|
|
836
|
-
if (userCount === serverMessages.slice(0, editIdx + 1).filter((m) => m.role === "user").length - 1) return i;
|
|
837
|
-
userCount++;
|
|
838
|
-
}
|
|
839
|
-
return lastUserIdx;
|
|
840
|
-
})();
|
|
841
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
842
|
-
className: "relative flex h-full flex-col overflow-hidden rounded-[16px] border border-frost bg-snow shadow-baziu-sm",
|
|
843
|
-
onDragOver,
|
|
844
|
-
onDragLeave,
|
|
845
|
-
onDrop,
|
|
846
|
-
children: [
|
|
847
|
-
dragging && /* @__PURE__ */ jsx("div", {
|
|
848
|
-
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",
|
|
849
|
-
children: "drop images or files to attach"
|
|
850
|
-
}),
|
|
851
|
-
/* @__PURE__ */ jsxs("div", {
|
|
852
|
-
className: "flex items-center justify-between gap-3 border-b border-frost px-4 py-2.5",
|
|
853
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
854
|
-
className: "flex min-w-0 items-center gap-2",
|
|
855
|
-
children: [/* @__PURE__ */ jsx("a", {
|
|
856
|
-
href: "/",
|
|
857
|
-
className: "inline-flex h-7 items-center rounded-md border border-frost px-2 text-xs font-semibold text-mocha lg:hidden",
|
|
858
|
-
children: "← agents"
|
|
859
|
-
}), /* @__PURE__ */ jsx("h1", {
|
|
860
|
-
className: "truncate font-display text-[1.2rem] text-charcoal",
|
|
861
|
-
children: agentName
|
|
862
|
-
})]
|
|
863
|
-
}), /* @__PURE__ */ jsx("a", {
|
|
864
|
-
href: `/agents/${agentId}?mode=settings`,
|
|
865
|
-
className: "shrink-0 text-xs text-mocha-light hover:text-sapphire",
|
|
866
|
-
children: "Manage agent →"
|
|
867
|
-
})]
|
|
868
|
-
}),
|
|
869
|
-
staleBanner && /* @__PURE__ */ jsxs("div", {
|
|
870
|
-
role: "status",
|
|
871
|
-
"aria-live": "polite",
|
|
872
|
-
"aria-atomic": "true",
|
|
873
|
-
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",
|
|
874
|
-
children: [
|
|
875
|
-
/* @__PURE__ */ jsx("span", {
|
|
876
|
-
className: "h-2 w-2 flex-none rounded-full bg-sapphire",
|
|
877
|
-
"aria-hidden": "true"
|
|
878
|
-
}),
|
|
879
|
-
/* @__PURE__ */ jsx("span", {
|
|
880
|
-
className: "flex-1",
|
|
881
|
-
children: recoveredTurn ? "the recovered turn has new activity — reload to see it" : "new activity from another source — reload to see it"
|
|
882
|
-
}),
|
|
883
|
-
/* @__PURE__ */ jsx("button", {
|
|
884
|
-
type: "button",
|
|
885
|
-
onClick: () => window.location.reload(),
|
|
886
|
-
className: "rounded-sm bg-sapphire px-3 py-1 text-[0.92em] text-snow hover:opacity-90",
|
|
887
|
-
children: "reload"
|
|
888
|
-
}),
|
|
889
|
-
/* @__PURE__ */ jsx("button", {
|
|
890
|
-
type: "button",
|
|
891
|
-
onClick: () => setStaleBanner(false),
|
|
892
|
-
className: "px-1 text-mocha-light hover:text-mocha",
|
|
893
|
-
"aria-label": "dismiss",
|
|
894
|
-
children: "×"
|
|
895
|
-
})
|
|
896
|
-
]
|
|
897
|
-
}),
|
|
898
|
-
/* @__PURE__ */ jsxs("div", {
|
|
899
|
-
ref: messagesRef,
|
|
900
|
-
onScroll: captureScroll,
|
|
901
|
-
role: "log",
|
|
902
|
-
"aria-label": `Conversation with ${agentName}`,
|
|
903
|
-
"aria-live": "polite",
|
|
904
|
-
"aria-relevant": "additions",
|
|
905
|
-
"aria-busy": turnBusy,
|
|
906
|
-
className: `min-h-[240px] flex-1 overflow-y-auto px-5 py-5 ${editIdx !== null ? "is-editing" : ""}`,
|
|
907
|
-
children: [
|
|
908
|
-
baseEntries.length === 0 && liveEntries.length === 0 && systemBubbles.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
909
|
-
className: "py-12 text-center italic text-mocha-light",
|
|
910
|
-
children: "Start a conversation…"
|
|
911
|
-
}),
|
|
912
|
-
(() => {
|
|
913
|
-
const out = [];
|
|
914
|
-
let sysIdx = 0;
|
|
915
|
-
const flushSysUpTo = (idx) => {
|
|
916
|
-
while (sysIdx < systemBubbles.length && systemBubbles[sysIdx].afterIdx <= idx) {
|
|
917
|
-
const sb = systemBubbles[sysIdx];
|
|
918
|
-
out.push(/* @__PURE__ */ jsx(Bubble, {
|
|
919
|
-
entry: {
|
|
920
|
-
type: "system",
|
|
921
|
-
content: sb.content
|
|
922
|
-
},
|
|
923
|
-
markdownReady,
|
|
924
|
-
onCommandApprovalDecision: decideCommandApproval
|
|
925
|
-
}, `y-${sb.id}`));
|
|
926
|
-
sysIdx++;
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
flushSysUpTo(0);
|
|
930
|
-
for (let i = 0; i < baseEntries.length; i++) {
|
|
931
|
-
const entry = baseEntries[i];
|
|
932
|
-
out.push(/* @__PURE__ */ jsx(Bubble, {
|
|
933
|
-
entry,
|
|
934
|
-
markdownReady,
|
|
935
|
-
isLastUser: i === lastUserIdx && editIdx === null,
|
|
936
|
-
isWillDrop: willDropFromIdx !== -1 && i >= willDropFromIdx,
|
|
937
|
-
onEdit: enterEditMode,
|
|
938
|
-
onCommandApprovalDecision: decideCommandApproval,
|
|
939
|
-
commandApprovalBusy: entry.type === "command_approval" && Boolean(approvalBusy[entry.approval.id]),
|
|
940
|
-
commandApprovalError: entry.type === "command_approval" ? approvalErrors[entry.approval.id] : void 0
|
|
941
|
-
}, `s-${i}`));
|
|
942
|
-
flushSysUpTo(i + 1);
|
|
943
|
-
}
|
|
944
|
-
for (let i = 0; i < liveEntries.length; i++) {
|
|
945
|
-
const entry = liveEntries[i];
|
|
946
|
-
out.push(/* @__PURE__ */ jsx(Bubble, {
|
|
947
|
-
entry,
|
|
948
|
-
markdownReady,
|
|
949
|
-
onCommandApprovalDecision: decideCommandApproval,
|
|
950
|
-
commandApprovalBusy: entry.type === "command_approval" && Boolean(approvalBusy[entry.approval.id]),
|
|
951
|
-
commandApprovalError: entry.type === "command_approval" ? approvalErrors[entry.approval.id] : void 0
|
|
952
|
-
}, `l-${i}`));
|
|
953
|
-
flushSysUpTo(baseEntries.length + i + 1);
|
|
954
|
-
}
|
|
955
|
-
while (sysIdx < systemBubbles.length) {
|
|
956
|
-
const sb = systemBubbles[sysIdx];
|
|
957
|
-
out.push(/* @__PURE__ */ jsx(Bubble, {
|
|
958
|
-
entry: {
|
|
959
|
-
type: "system",
|
|
960
|
-
content: sb.content
|
|
961
|
-
},
|
|
962
|
-
markdownReady,
|
|
963
|
-
onCommandApprovalDecision: decideCommandApproval
|
|
964
|
-
}, `y-${sb.id}`));
|
|
965
|
-
sysIdx++;
|
|
966
|
-
}
|
|
967
|
-
return out;
|
|
968
|
-
})(),
|
|
969
|
-
thinking && /* @__PURE__ */ jsxs("div", {
|
|
970
|
-
className: "flex items-center gap-2 px-1 py-1 text-[0.85em] text-mocha-light",
|
|
971
|
-
children: [
|
|
972
|
-
/* @__PURE__ */ jsx(Dot, {}),
|
|
973
|
-
/* @__PURE__ */ jsx(Dot, { delay: "0.15s" }),
|
|
974
|
-
/* @__PURE__ */ jsx(Dot, { delay: "0.3s" }),
|
|
975
|
-
/* @__PURE__ */ jsx("span", { children: "agent is thinking…" })
|
|
976
|
-
]
|
|
977
|
-
})
|
|
978
|
-
]
|
|
979
|
-
}),
|
|
980
|
-
/* @__PURE__ */ jsx("p", {
|
|
981
|
-
className: "sr-only",
|
|
982
|
-
role: "status",
|
|
983
|
-
"aria-live": "polite",
|
|
984
|
-
"aria-atomic": "true",
|
|
985
|
-
children: turnBusy ? `${agentName} is responding.` : "Conversation ready."
|
|
986
|
-
}),
|
|
987
|
-
editIdx !== null && /* @__PURE__ */ jsxs("div", {
|
|
988
|
-
className: "flex items-center gap-2 border-t border-frost bg-sapphire-glow px-5 py-2 text-[0.85em] text-sapphire-deep",
|
|
989
|
-
children: [/* @__PURE__ */ jsx("span", { children: "editing last message — submit to replace, or" }), /* @__PURE__ */ jsx("button", {
|
|
990
|
-
type: "button",
|
|
991
|
-
onClick: exitEditMode,
|
|
992
|
-
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",
|
|
993
|
-
children: "cancel"
|
|
994
|
-
})]
|
|
995
|
-
}),
|
|
996
|
-
attachments.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
997
|
-
className: "flex flex-wrap gap-2 border-t border-frost bg-ivory px-5 pt-3",
|
|
998
|
-
children: attachments.map((a, i) => {
|
|
999
|
-
const remove = () => setAttachments((prev) => prev.filter((_, j) => j !== i));
|
|
1000
|
-
return isImageMime(a.mimeType) ? /* @__PURE__ */ jsxs("div", {
|
|
1001
|
-
className: "relative",
|
|
1002
|
-
children: [/* @__PURE__ */ jsx("img", {
|
|
1003
|
-
src: `data:${a.mimeType};base64,${a.data}`,
|
|
1004
|
-
alt: "pending attachment",
|
|
1005
|
-
className: "h-16 w-16 rounded-md border border-frost object-cover"
|
|
1006
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
1007
|
-
type: "button",
|
|
1008
|
-
onClick: remove,
|
|
1009
|
-
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",
|
|
1010
|
-
"aria-label": "remove attachment",
|
|
1011
|
-
children: "✕"
|
|
1012
|
-
})]
|
|
1013
|
-
}, `${a.mimeType}-${i}`) : /* @__PURE__ */ jsxs("div", {
|
|
1014
|
-
className: "flex items-center gap-1.5 rounded-md border border-frost bg-snow px-2 py-1 text-[0.82em] text-mocha",
|
|
1015
|
-
children: [
|
|
1016
|
-
/* @__PURE__ */ jsx("span", { children: "📄" }),
|
|
1017
|
-
/* @__PURE__ */ jsx("span", {
|
|
1018
|
-
className: "max-w-[160px] truncate",
|
|
1019
|
-
children: a.name ?? "file"
|
|
1020
|
-
}),
|
|
1021
|
-
/* @__PURE__ */ jsx("button", {
|
|
1022
|
-
type: "button",
|
|
1023
|
-
onClick: remove,
|
|
1024
|
-
className: "text-[0.85em] text-mocha hover:text-sapphire",
|
|
1025
|
-
"aria-label": "remove attachment",
|
|
1026
|
-
children: "✕"
|
|
1027
|
-
})
|
|
1028
|
-
]
|
|
1029
|
-
}, `${a.name ?? "file"}-${i}`);
|
|
1030
|
-
})
|
|
1031
|
-
}),
|
|
1032
|
-
/* @__PURE__ */ jsxs("form", {
|
|
1033
|
-
className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-end gap-2 border-t border-frost bg-ivory px-3 py-3 sm:px-5",
|
|
1034
|
-
onSubmit: (e) => {
|
|
1035
|
-
e.preventDefault();
|
|
1036
|
-
send(input);
|
|
1037
|
-
},
|
|
1038
|
-
children: [
|
|
1039
|
-
/* @__PURE__ */ jsx("input", {
|
|
1040
|
-
ref: fileInputRef,
|
|
1041
|
-
type: "file",
|
|
1042
|
-
multiple: true,
|
|
1043
|
-
className: "hidden",
|
|
1044
|
-
onChange: (e) => {
|
|
1045
|
-
addFiles(e.target.files);
|
|
1046
|
-
e.target.value = "";
|
|
1047
|
-
}
|
|
1048
|
-
}),
|
|
1049
|
-
/* @__PURE__ */ jsx("button", {
|
|
1050
|
-
type: "button",
|
|
1051
|
-
onClick: () => fileInputRef.current?.click(),
|
|
1052
|
-
disabled: turnBusy,
|
|
1053
|
-
title: "attach images or files",
|
|
1054
|
-
"aria-label": "attach files",
|
|
1055
|
-
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",
|
|
1056
|
-
children: "📎"
|
|
1057
|
-
}),
|
|
1058
|
-
/* @__PURE__ */ jsx("textarea", {
|
|
1059
|
-
ref: inputRef,
|
|
1060
|
-
rows: 1,
|
|
1061
|
-
value: input,
|
|
1062
|
-
onChange: (e) => setInput(e.target.value),
|
|
1063
|
-
onKeyDown,
|
|
1064
|
-
onPaste,
|
|
1065
|
-
disabled: turnBusy,
|
|
1066
|
-
placeholder: "say something… (Shift+Enter for newline; paste or 📎 to attach images/files)",
|
|
1067
|
-
autoComplete: "off",
|
|
1068
|
-
"aria-label": `Message ${agentName}`,
|
|
1069
|
-
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)]"
|
|
1070
|
-
}),
|
|
1071
|
-
turnBusy ? /* @__PURE__ */ jsx("button", {
|
|
1072
|
-
type: "button",
|
|
1073
|
-
onClick: cancel,
|
|
1074
|
-
"aria-label": "Cancel current response",
|
|
1075
|
-
className: "rounded-md border-[1.5px] border-danger bg-transparent px-3 py-2 text-[0.92em] font-medium text-danger hover:bg-danger/10",
|
|
1076
|
-
children: "Cancel"
|
|
1077
|
-
}) : /* @__PURE__ */ jsx("button", {
|
|
1078
|
-
type: "submit",
|
|
1079
|
-
disabled: !input.trim() && attachments.length === 0,
|
|
1080
|
-
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",
|
|
1081
|
-
children: "Send"
|
|
1082
|
-
})
|
|
1083
|
-
]
|
|
1084
|
-
}),
|
|
1085
|
-
/* @__PURE__ */ jsx(ConfirmDialog, {
|
|
1086
|
-
open: resetConfirmOpen,
|
|
1087
|
-
onOpenChange: setResetConfirmOpen,
|
|
1088
|
-
title: `Reset chat with ${agentName}?`,
|
|
1089
|
-
description: /* @__PURE__ */ jsx("p", { children: "This permanently wipes the persisted session history for this Agent. Agent files, Team memory, and other Agent settings are not changed." }),
|
|
1090
|
-
confirmLabel: "Reset chat history",
|
|
1091
|
-
onConfirm: async () => {
|
|
1092
|
-
try {
|
|
1093
|
-
await performReset();
|
|
1094
|
-
} catch (error) {
|
|
1095
|
-
pushSystem(`/reset failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1096
|
-
throw error;
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
})
|
|
1100
|
-
]
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
function Bubble({ entry, markdownReady, isLastUser, isWillDrop, onEdit, onCommandApprovalDecision, commandApprovalBusy, commandApprovalError }) {
|
|
1104
|
-
const dropCls = isWillDrop ? "opacity-40 [&_.bubble-content]:line-through" : "";
|
|
1105
|
-
if (entry.type === "user") {
|
|
1106
|
-
if (entry.content.startsWith(INBOX_WAKE_PREFIX)) {
|
|
1107
|
-
const body = entry.content.slice(24);
|
|
1108
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1109
|
-
className: `my-4 flex flex-col items-end ${dropCls}`,
|
|
1110
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1111
|
-
className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-mocha opacity-90",
|
|
1112
|
-
children: "inbox"
|
|
1113
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1114
|
-
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",
|
|
1115
|
-
children: body
|
|
1116
|
-
})]
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1120
|
-
className: `team relative my-4 flex flex-col items-end ${dropCls}`,
|
|
1121
|
-
children: [
|
|
1122
|
-
/* @__PURE__ */ jsx("span", {
|
|
1123
|
-
className: "sr-only",
|
|
1124
|
-
children: "you"
|
|
1125
|
-
}),
|
|
1126
|
-
entry.images && entry.images.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1127
|
-
className: "mb-1 flex max-w-[85%] flex-wrap justify-end gap-1",
|
|
1128
|
-
children: entry.images.map((img, i) => /* @__PURE__ */ jsx("img", {
|
|
1129
|
-
src: `data:${img.mimeType};base64,${img.data}`,
|
|
1130
|
-
alt: "attachment",
|
|
1131
|
-
className: "max-h-48 rounded-lg border border-sapphire-light"
|
|
1132
|
-
}, i))
|
|
1133
|
-
}),
|
|
1134
|
-
entry.files && entry.files.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1135
|
-
className: "mb-1 flex max-w-[85%] flex-wrap justify-end gap-1",
|
|
1136
|
-
children: entry.files.map((f, i) => /* @__PURE__ */ jsxs("span", {
|
|
1137
|
-
className: "flex items-center gap-1 rounded-md border border-sapphire-light bg-sapphire-glow px-2 py-1 text-[0.8em] text-chocolate",
|
|
1138
|
-
children: ["📄 ", /* @__PURE__ */ jsx("span", {
|
|
1139
|
-
className: "max-w-[180px] truncate",
|
|
1140
|
-
children: f.name
|
|
1141
|
-
})]
|
|
1142
|
-
}, i))
|
|
1143
|
-
}),
|
|
1144
|
-
entry.content && /* @__PURE__ */ jsx("div", {
|
|
1145
|
-
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",
|
|
1146
|
-
children: entry.content
|
|
1147
|
-
}),
|
|
1148
|
-
isLastUser && onEdit && /* @__PURE__ */ jsx("button", {
|
|
1149
|
-
type: "button",
|
|
1150
|
-
onClick: onEdit,
|
|
1151
|
-
title: "edit and resend — replaces the last turn",
|
|
1152
|
-
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 team-hover:opacity-100 team-focus-within:opacity-100",
|
|
1153
|
-
children: "edit"
|
|
1154
|
-
})
|
|
1155
|
-
]
|
|
1156
|
-
});
|
|
1157
|
-
}
|
|
1158
|
-
if (entry.type === "assistant") {
|
|
1159
|
-
if (entry.content.startsWith(COMPACTION_REPLAY_PREFIX)) {
|
|
1160
|
-
const summary = entry.content.slice(22).trim();
|
|
1161
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1162
|
-
className: `my-3 border-y border-dashed border-frost py-2 text-[0.85em] italic text-mocha-light ${dropCls}`,
|
|
1163
|
-
children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", {
|
|
1164
|
-
className: "cursor-pointer select-none text-center [&::-webkit-details-marker]:hidden",
|
|
1165
|
-
children: "— conversation summary (click to expand) —"
|
|
1166
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1167
|
-
className: "md-content mt-2 rounded-sm bg-frost/40 p-2 not-italic",
|
|
1168
|
-
dangerouslySetInnerHTML: { __html: renderMd(summary, markdownReady) }
|
|
1169
|
-
})] })
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
if (entry.content.startsWith("[error]") || entry.content.startsWith("[fatal]") || entry.content.startsWith("[network error]")) return /* @__PURE__ */ jsxs("div", {
|
|
1173
|
-
className: `my-4 flex flex-col items-start ${dropCls}`,
|
|
1174
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1175
|
-
className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-danger",
|
|
1176
|
-
children: "error"
|
|
1177
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1178
|
-
className: "bubble-content rounded-r-sm border-l-[3px] border-l-danger bg-danger/10 py-1 pl-3 pr-2 leading-[1.55]",
|
|
1179
|
-
dangerouslySetInnerHTML: { __html: renderMd(entry.content, markdownReady) }
|
|
1180
|
-
})]
|
|
1181
|
-
});
|
|
1182
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1183
|
-
className: `my-4 flex flex-col items-start ${dropCls}`,
|
|
1184
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1185
|
-
className: "sr-only",
|
|
1186
|
-
children: "agent"
|
|
1187
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1188
|
-
className: "md-content bubble-content w-full leading-[1.55] text-chocolate",
|
|
1189
|
-
dangerouslySetInnerHTML: { __html: renderMd(entry.content, markdownReady) }
|
|
1190
|
-
})]
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
if (entry.type === "tool") return /* @__PURE__ */ jsx(ToolGroup, {
|
|
1194
|
-
items: entry.items,
|
|
1195
|
-
dropCls
|
|
1196
|
-
});
|
|
1197
|
-
if (entry.type === "images") return /* @__PURE__ */ jsx("div", {
|
|
1198
|
-
className: `my-2 space-y-2 ${dropCls}`,
|
|
1199
|
-
children: entry.images.map((img, i) => /* @__PURE__ */ jsx("img", {
|
|
1200
|
-
src: `data:${img.mimeType};base64,${img.data}`,
|
|
1201
|
-
alt: "screenshot",
|
|
1202
|
-
className: "block max-w-full rounded-lg border border-fawn"
|
|
1203
|
-
}, i))
|
|
1204
|
-
});
|
|
1205
|
-
if (entry.type === "file") {
|
|
1206
|
-
const isImage = entry.mimeType.startsWith("image/");
|
|
1207
|
-
const href = `data:${entry.mimeType};base64,${entry.data}`;
|
|
1208
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1209
|
-
className: `my-2 ${dropCls}`,
|
|
1210
|
-
children: [isImage && /* @__PURE__ */ jsx("img", {
|
|
1211
|
-
src: href,
|
|
1212
|
-
alt: entry.name,
|
|
1213
|
-
className: "mb-1 block max-w-full rounded-lg border border-fawn"
|
|
1214
|
-
}), /* @__PURE__ */ jsxs("a", {
|
|
1215
|
-
href,
|
|
1216
|
-
download: entry.name,
|
|
1217
|
-
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",
|
|
1218
|
-
children: [
|
|
1219
|
-
/* @__PURE__ */ jsx("span", { children: "📄" }),
|
|
1220
|
-
/* @__PURE__ */ jsx("span", {
|
|
1221
|
-
className: "max-w-[260px] truncate",
|
|
1222
|
-
children: entry.name
|
|
1223
|
-
}),
|
|
1224
|
-
/* @__PURE__ */ jsx("span", {
|
|
1225
|
-
className: "text-[0.85em] opacity-70",
|
|
1226
|
-
children: "download"
|
|
1227
|
-
})
|
|
1228
|
-
]
|
|
1229
|
-
})]
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
if (entry.type === "command_approval") return /* @__PURE__ */ jsx(CommandApprovalCard, {
|
|
1233
|
-
approval: entry.approval,
|
|
1234
|
-
busy: commandApprovalBusy,
|
|
1235
|
-
error: commandApprovalError,
|
|
1236
|
-
onDecision: onCommandApprovalDecision
|
|
1237
|
-
});
|
|
1238
|
-
if (entry.type === "system") return /* @__PURE__ */ jsxs("div", {
|
|
1239
|
-
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}`,
|
|
1240
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1241
|
-
className: "mr-2 font-semibold uppercase tracking-wider opacity-80",
|
|
1242
|
-
children: "system"
|
|
1243
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
1244
|
-
className: "whitespace-pre-wrap",
|
|
1245
|
-
children: entry.content
|
|
1246
|
-
})]
|
|
1247
|
-
});
|
|
1248
|
-
if (entry.type === "error") return /* @__PURE__ */ jsx("div", {
|
|
1249
|
-
className: `my-3 rounded-r-sm border-l-[3px] border-danger bg-danger/10 px-3 py-1 text-[0.92em] text-danger ${dropCls}`,
|
|
1250
|
-
children: entry.content
|
|
1251
|
-
});
|
|
1252
|
-
return null;
|
|
1253
|
-
}
|
|
1254
|
-
function CommandApprovalCard({ approval, busy = false, error, onDecision }) {
|
|
1255
|
-
const pending = approval.status === "pending";
|
|
1256
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
1257
|
-
className: "my-3 rounded-lg border border-danger/50 bg-danger/5 p-4 text-chocolate",
|
|
1258
|
-
"aria-label": "Shell command approval",
|
|
1259
|
-
children: [
|
|
1260
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1261
|
-
className: "flex flex-wrap items-start justify-between gap-2",
|
|
1262
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
1263
|
-
className: "m-0 text-[0.72em] font-semibold uppercase tracking-wider text-danger",
|
|
1264
|
-
children: "Shell command approval"
|
|
1265
|
-
}), /* @__PURE__ */ jsx("h2", {
|
|
1266
|
-
className: "m-0 mt-1 font-body text-[0.95em] font-semibold",
|
|
1267
|
-
children: pending ? "A risky command is waiting for you" : "Command decision recorded"
|
|
1268
|
-
})] }), /* @__PURE__ */ jsx("span", {
|
|
1269
|
-
className: "rounded-full border border-danger/30 px-2 py-0.5 text-[0.75em] font-semibold uppercase tracking-wide text-danger",
|
|
1270
|
-
children: approval.status.replaceAll("_", " ")
|
|
1271
|
-
})]
|
|
1272
|
-
}),
|
|
1273
|
-
/* @__PURE__ */ jsx("pre", {
|
|
1274
|
-
className: "mt-3 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded-md border border-fawn bg-snow p-3 font-mono text-[0.82em] leading-[1.45] text-charcoal",
|
|
1275
|
-
children: approval.command
|
|
1276
|
-
}),
|
|
1277
|
-
/* @__PURE__ */ jsx("ul", {
|
|
1278
|
-
className: "mt-3 space-y-1.5 pl-5 text-[0.84em] leading-[1.4] text-mocha",
|
|
1279
|
-
children: approval.risks.map((risk) => /* @__PURE__ */ jsxs("li", { children: [
|
|
1280
|
-
/* @__PURE__ */ jsx("span", {
|
|
1281
|
-
className: "font-medium text-chocolate",
|
|
1282
|
-
children: risk.message
|
|
1283
|
-
}),
|
|
1284
|
-
" ",
|
|
1285
|
-
/* @__PURE__ */ jsx("code", {
|
|
1286
|
-
className: "text-[0.9em]",
|
|
1287
|
-
children: risk.code
|
|
1288
|
-
})
|
|
1289
|
-
] }, `${risk.code}:${risk.span.start}:${risk.span.end}`))
|
|
1290
|
-
}),
|
|
1291
|
-
pending ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1292
|
-
/* @__PURE__ */ jsxs("p", {
|
|
1293
|
-
className: "mt-3 text-[0.78em] text-mocha-light",
|
|
1294
|
-
children: [
|
|
1295
|
-
"This approval expires",
|
|
1296
|
-
" ",
|
|
1297
|
-
/* @__PURE__ */ jsx("time", {
|
|
1298
|
-
dateTime: new Date(approval.expiresAt).toISOString(),
|
|
1299
|
-
children: new Date(approval.expiresAt).toLocaleString()
|
|
1300
|
-
}),
|
|
1301
|
-
". Deny blocks only this command; cancel stops the whole turn."
|
|
1302
|
-
]
|
|
1303
|
-
}),
|
|
1304
|
-
error && /* @__PURE__ */ jsx("p", {
|
|
1305
|
-
role: "alert",
|
|
1306
|
-
className: "mt-2 rounded-md bg-danger/10 px-3 py-2 text-[0.82em] text-danger",
|
|
1307
|
-
children: error
|
|
1308
|
-
}),
|
|
1309
|
-
onDecision && /* @__PURE__ */ jsxs("div", {
|
|
1310
|
-
className: "mt-3 flex flex-wrap gap-2",
|
|
1311
|
-
children: [/* @__PURE__ */ jsx(Button, {
|
|
1312
|
-
variant: "primary",
|
|
1313
|
-
disabled: busy,
|
|
1314
|
-
onClick: () => onDecision(approval, "deny"),
|
|
1315
|
-
children: "Deny"
|
|
1316
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
1317
|
-
variant: "danger",
|
|
1318
|
-
disabled: busy,
|
|
1319
|
-
onClick: () => onDecision(approval, "allow"),
|
|
1320
|
-
children: "Allow once"
|
|
1321
|
-
})]
|
|
1322
|
-
})
|
|
1323
|
-
] }) : /* @__PURE__ */ jsx("p", {
|
|
1324
|
-
role: "status",
|
|
1325
|
-
className: "mt-3 text-[0.84em] font-medium text-mocha",
|
|
1326
|
-
children: commandApprovalStatusCopy(approval.status)
|
|
1327
|
-
})
|
|
1328
|
-
]
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
function commandApprovalStatusCopy(status) {
|
|
1332
|
-
switch (status) {
|
|
1333
|
-
case "pending": return "Waiting for a decision.";
|
|
1334
|
-
case "allowed": return "Allowed once — the command may run for this tool call only.";
|
|
1335
|
-
case "denied": return "Denied — the command was not run.";
|
|
1336
|
-
case "auto_denied": return "Automatically denied — no interactive approval path was available.";
|
|
1337
|
-
case "expired": return "Expired — the command was not run.";
|
|
1338
|
-
case "cancelled": return "Cancelled — the command was not run.";
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
function commandApprovalStatusFromConflict(message) {
|
|
1342
|
-
if (!message.startsWith("shell approval already decided: ")) return null;
|
|
1343
|
-
const status = message.slice(32);
|
|
1344
|
-
switch (status) {
|
|
1345
|
-
case "allowed":
|
|
1346
|
-
case "denied":
|
|
1347
|
-
case "auto_denied":
|
|
1348
|
-
case "expired":
|
|
1349
|
-
case "cancelled": return status;
|
|
1350
|
-
default: return null;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
function ToolGroup({ items, dropCls }) {
|
|
1354
|
-
const [expanded, setExpanded] = useState(false);
|
|
1355
|
-
const contentRef = useRef(null);
|
|
1356
|
-
const [overflows, setOverflows] = useState(false);
|
|
1357
|
-
useLayoutEffect(() => {
|
|
1358
|
-
const el = contentRef.current;
|
|
1359
|
-
if (!el) return;
|
|
1360
|
-
setOverflows(el.scrollHeight > 324);
|
|
1361
|
-
}, [items.length]);
|
|
1362
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1363
|
-
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}`,
|
|
1364
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1365
|
-
ref: contentRef,
|
|
1366
|
-
className: "relative overflow-hidden",
|
|
1367
|
-
style: { maxHeight: expanded ? "none" : `${TOOL_GROUP_MAX_HEIGHT_PX}px` },
|
|
1368
|
-
children: [items.map((it, i) => /* @__PURE__ */ jsx(ToolLine, { item: it }, i)), overflows && !expanded && /* @__PURE__ */ jsx("div", {
|
|
1369
|
-
className: "pointer-events-none absolute inset-x-0 bottom-0 h-10",
|
|
1370
|
-
style: { background: "linear-gradient(to bottom, rgba(247,240,229,0), var(--color-ivory))" }
|
|
1371
|
-
})]
|
|
1372
|
-
}), overflows && /* @__PURE__ */ jsx("button", {
|
|
1373
|
-
type: "button",
|
|
1374
|
-
onClick: () => setExpanded((v) => !v),
|
|
1375
|
-
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",
|
|
1376
|
-
children: expanded ? "show less ↑" : "show more ↓"
|
|
1377
|
-
})]
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
function ToolLine({ item }) {
|
|
1381
|
-
if (item.kind === "call") {
|
|
1382
|
-
const args = prettyArgs(item.body);
|
|
1383
|
-
const multiLine = args.includes("\n");
|
|
1384
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1385
|
-
className: "whitespace-pre-wrap break-words py-0.5",
|
|
1386
|
-
children: [
|
|
1387
|
-
/* @__PURE__ */ jsx("span", {
|
|
1388
|
-
className: "mr-1 opacity-45",
|
|
1389
|
-
children: "→"
|
|
1390
|
-
}),
|
|
1391
|
-
/* @__PURE__ */ jsx("span", {
|
|
1392
|
-
className: "font-medium text-mocha",
|
|
1393
|
-
children: item.name
|
|
1394
|
-
}),
|
|
1395
|
-
multiLine ? /* @__PURE__ */ jsx("pre", {
|
|
1396
|
-
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",
|
|
1397
|
-
children: args
|
|
1398
|
-
}) : args ? /* @__PURE__ */ jsxs("span", { children: [
|
|
1399
|
-
" (",
|
|
1400
|
-
args,
|
|
1401
|
-
")"
|
|
1402
|
-
] }) : null
|
|
1403
|
-
]
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
if (item.kind === "error") return /* @__PURE__ */ jsxs("div", {
|
|
1407
|
-
className: "whitespace-pre-wrap break-words py-0.5 text-rose-baziu",
|
|
1408
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1409
|
-
className: "mr-1 opacity-45",
|
|
1410
|
-
children: "←"
|
|
1411
|
-
}), item.body]
|
|
1412
|
-
});
|
|
1413
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1414
|
-
className: "whitespace-pre-wrap break-words py-0.5",
|
|
1415
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1416
|
-
className: "mr-1 opacity-45",
|
|
1417
|
-
children: "←"
|
|
1418
|
-
}), item.body]
|
|
1419
|
-
});
|
|
1420
|
-
}
|
|
1421
|
-
function Dot({ delay = "0s" }) {
|
|
1422
|
-
return /* @__PURE__ */ jsx("span", {
|
|
1423
|
-
className: "inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-mocha-light/60",
|
|
1424
|
-
style: { animationDelay: delay },
|
|
1425
|
-
"aria-hidden": "true"
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
//#endregion
|
|
1429
|
-
export { ChatPane as t };
|