bazilion 0.14.2 → 0.15.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 +1126 -334
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +8180 -3651
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +171 -3
- package/dist/web/client/assets/ChatPane-55j13bBj.js +74 -0
- package/dist/web/client/assets/{ConfirmDialog-_bmG-wVG.js → ConfirmDialog-CGZzom9E.js} +1 -1
- package/dist/web/client/assets/{FieldRow-Do1cBkRc.js → FieldRow-DYNU9gWb.js} +1 -1
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-XP-QJul4.js → ProductionTeamPolicyEditor-DT5Sl8jd.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-CwdFPdpk.js → ProfileCommunicationEditor-TyCU5vXX.js} +1 -1
- package/dist/web/client/assets/ResultCard-Cps_lXVN.js +1 -0
- package/dist/web/client/assets/ResultSourceConversation-CGhKTSTD.js +1 -0
- package/dist/web/client/assets/TeamTabs-BZHyRnbd.js +1 -0
- package/dist/web/client/assets/{TemplatesTabs-D4WHvs6b.js → TemplatesTabs-BHzkPMRC.js} +1 -1
- package/dist/web/client/assets/{UnsavedChangesGuard-B1aLqiK5.js → UnsavedChangesGuard-CFQcKsrf.js} +1 -1
- package/dist/web/client/assets/_id-9nCIhbVd.js +1 -0
- package/dist/web/client/assets/{_id-3w4PFfma.js → _id-BSu3AqKA.js} +1 -1
- package/dist/web/client/assets/_id-CGd21j3k.js +1 -0
- package/dist/web/client/assets/{_id-BH3v_vI_.js → _id-D_eKokkd.js} +1 -1
- package/dist/web/client/assets/{_id-Cx7o4Zmf.js → _id-SRMFlIgP.js} +1 -1
- package/dist/web/client/assets/{activity-A_HrG1Tv.js → activity-Cn2-9PpZ.js} +1 -1
- package/dist/web/client/assets/{agents-BeeFCt2c.js → agents-CCmiZek6.js} +1 -1
- package/dist/web/client/assets/{agents-C8OVKq3N.js → agents-NY3VrZgB.js} +1 -1
- package/dist/web/client/assets/{approvals-BIdZ9BSn.js → approvals-CPDAPgrM.js} +1 -1
- package/dist/web/client/assets/{attention-B9bUrxiU.js → attention-Qra_9_OI.js} +1 -1
- package/dist/web/client/assets/chevron-right-DXE4CWf2.js +1 -0
- package/dist/web/client/assets/{config-By86S7k-.js → config-D8EO0LPB.js} +1 -1
- package/dist/web/client/assets/{inbox-DlXQCJLe.js → inbox-C5W-sm1q.js} +1 -1
- package/dist/web/client/assets/{index-CLdmsmiN.js → index-BUhhk_u9.js} +2 -2
- package/dist/web/client/assets/{learning-BqZIw2nA.js → learning-Dr-7_7J4.js} +1 -1
- package/dist/web/client/assets/login-Bu5UxbJh.js +1 -0
- package/dist/web/client/assets/{mcp-DKSY2yVa.js → mcp-BueuwQiv.js} +1 -1
- package/dist/web/client/assets/{members-9GuOiq6J.js → members-ZikuYNcy.js} +1 -1
- package/dist/web/client/assets/{memory-DiGMI6FC.js → memory-1JUJlPrn.js} +1 -1
- package/dist/web/client/assets/{policy-TBGQffw5.js → policy-DbkhsFNA.js} +1 -1
- package/dist/web/client/assets/results-mMr8Ar2u.js +1 -0
- package/dist/web/client/assets/routes-DJYB-MS9.js +1 -0
- package/dist/web/client/assets/{services-1Z398CES.js → services-kfQA9cpJ.js} +1 -1
- package/dist/web/client/assets/{skills-BP2gciFt.js → skills-C6pcgxaC.js} +1 -1
- package/dist/web/client/assets/styles-CcNf8jT_.css +2 -0
- package/dist/web/client/assets/{teams-tNoMDJ_M.js → teams-C6_Noz5X.js} +1 -1
- package/dist/web/client/assets/{teams-DBIivGT7.js → teams-LB0KBXY9.js} +1 -1
- package/dist/web/client/assets/telegram-A2jaI1QM.js +1 -0
- package/dist/web/client/assets/{tokens-eDWCGQQ2.js → tokens-BGlUpJcL.js} +1 -1
- package/dist/web/client/assets/{triggers-DZME3IcK.js → triggers-C54_feGg.js} +1 -1
- package/dist/web/client/assets/{welcome-B49sh_uf.js → welcome-DyUIswRi.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-BY2MPLr9.js} +1 -1
- package/dist/web/server/assets/{ChatPane-CgSe7tsy.js → ChatPane-DJ30FdFG.js} +1122 -107
- package/dist/web/server/assets/ResultCard-B3zD9N0t.js +143 -0
- package/dist/web/server/assets/ResultSourceConversation-zYogeOWp.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-1LwoHhJg.js} +2 -1
- package/dist/web/server/assets/{_id-BeKhPKw3.js → _id-1hTvnKLN2.js} +7 -3
- package/dist/web/server/assets/{_id-BAA9KJBU.js → _id-B2EEyStS.js} +1 -1
- package/dist/web/server/assets/_id-BDEpe6k0.js +6 -0
- package/dist/web/server/assets/{_id-BqWTB8Wl.js → _id-B_-CDHOi2.js} +1 -1
- package/dist/web/server/assets/{_id-DxCnEFpm.js → _id-BvSWsPI5.js} +3 -4
- package/dist/web/server/assets/{_id-ndxFHx0t.js → _id-CDBtO_jL.js} +2 -2
- package/dist/web/server/assets/{_id-DvlDkk04.js → _id-DDNVQx3y.js} +1 -1
- package/dist/web/server/assets/{_id-BYKpT2Tu.js → _id-DFHB9JFQ.js} +1 -1
- package/dist/web/server/assets/{_id-HMy7jM3P.js → _id-DxFLSIio.js} +1 -1
- package/dist/web/server/assets/{_id-DYU6uIBx.js → _id-T_8btpbB.js} +1 -1
- 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-DP4ZUBw3.js} +102 -75
- 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/{activity-9VP6QDTH.js → activity-D73w4mkZ.js} +1 -1
- package/dist/web/server/assets/{agents-DjyQk94u.js → agents-DB5hCSAI.js} +1 -1
- package/dist/web/server/assets/{agents-BJAv919x.js → agents-DRSo7lRX.js} +1 -1
- package/dist/web/server/assets/{approvals-BYi84gwl.js → approvals-4y9bZF4r.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-BFx8ZkmH.js} +1 -1
- package/dist/web/server/assets/{auth-7BHTH-KS.js → auth-_eaCwhyq.js} +1 -1
- package/dist/web/server/assets/{config-BMiajTZP.js → config-B7JIfv_7.js} +1 -1
- package/dist/web/server/assets/{daemon-client-CI5NHwNC.js → daemon-client-B06W1Qa2.js} +54 -0
- package/dist/web/server/assets/{inbox-Dv55rGYq.js → inbox-YRpDQcvp.js} +1 -1
- package/dist/web/server/assets/{learning-C2upa1CE.js → learning-UBZb6h6M.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-Dv6RXMaS.js} +1 -1
- package/dist/web/server/assets/{members-BP3UTqu1.js → members-BFTKSlOe.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/{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-D0PQgZAn.js} +1 -1
- package/dist/web/server/assets/{policy-18fdTFO4.js → policy-C-p8HIhA.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/results-Cfev4KNt.js +12 -0
- package/dist/web/server/assets/results-Cj-Dlw3C.js +173 -0
- package/dist/web/server/assets/results-CwEQ5EUL.js +12 -0
- package/dist/web/server/assets/{router-CY_pCgP1.js → router-d3sTMpMX.js} +60 -43
- package/dist/web/server/assets/{routes-DAU2g7Uz.js → routes-B765DVhj.js} +1 -1
- package/dist/web/server/assets/{routes-BgHisAxH.js → routes-MIZz6uMr.js} +2 -2
- package/dist/web/server/assets/{routes-COtkyZG1.js → routes-cW4MWU18.js} +3 -3
- package/dist/web/server/assets/{services-DozuJtzX.js → services-BCbdn3R9.js} +1 -1
- package/dist/web/server/assets/{skills-DfGSJTCB.js → skills-Bpoa8MIp.js} +1 -1
- package/dist/web/server/assets/{teams-be05ci17.js → teams-CXzwqzcE.js} +1 -1
- package/dist/web/server/assets/{teams-DPnAX4wD.js → teams-hWGjbpUA.js} +1 -1
- package/dist/web/server/assets/{telegram-Dj4Wwhd6.js → telegram-CoNeOgqZ.js} +465 -1
- package/dist/web/server/assets/{telegram-1AWL6lFw.js → telegram-DhpvtGUc.js} +1 -1
- package/dist/web/server/assets/{telegram-DbOFpTFm.js → telegram-Zd1rIZxg.js} +1 -1
- package/dist/web/server/assets/{tokens-DjM8GsgS.js → tokens-Bjp19Vkn.js} +1 -1
- package/dist/web/server/assets/{triggers-Nezfw-Bd.js → triggers-DJlcvXzn.js} +1 -1
- package/dist/web/server/server.js +31 -27
- package/dist/worker.js +294 -104
- 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-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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Button } from "./Button-Dsnw22OF.js";
|
|
2
|
-
import { t as
|
|
3
|
-
import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { t as ResultCard } from "./ResultCard-B3zD9N0t.js";
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import DOMPurify from "dompurify";
|
|
6
6
|
import { marked } from "marked";
|
|
@@ -35,6 +35,952 @@ function escapeHtml(s) {
|
|
|
35
35
|
return String(s).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
36
36
|
}
|
|
37
37
|
//#endregion
|
|
38
|
+
//#region src/components/ConversationLibrary.tsx
|
|
39
|
+
function ConversationLibrary({ agentId, turnBusy, onCreated, renderHistory, initialConversationId }) {
|
|
40
|
+
const [library, setLibrary] = useState(null);
|
|
41
|
+
const [offset, setOffset] = useState(0);
|
|
42
|
+
const [refresh, setRefresh] = useState(0);
|
|
43
|
+
const [selected, setSelected] = useState(null);
|
|
44
|
+
const [history, setHistory] = useState(null);
|
|
45
|
+
const [title, setTitle] = useState("");
|
|
46
|
+
const [error, setError] = useState("");
|
|
47
|
+
const [historyError, setHistoryError] = useState("");
|
|
48
|
+
const [busy, setBusy] = useState(false);
|
|
49
|
+
const pendingKey = `bazilion:conversation-create:${agentId}`;
|
|
50
|
+
const pendingCreate = useRef(null);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
try {
|
|
53
|
+
pendingCreate.current = JSON.parse(sessionStorage.getItem(pendingKey) ?? "null");
|
|
54
|
+
} catch {
|
|
55
|
+
pendingCreate.current = null;
|
|
56
|
+
}
|
|
57
|
+
}, [pendingKey]);
|
|
58
|
+
function clearPending() {
|
|
59
|
+
pendingCreate.current = null;
|
|
60
|
+
sessionStorage.removeItem(pendingKey);
|
|
61
|
+
}
|
|
62
|
+
const base = `/api/agents/${encodeURIComponent(agentId)}/conversations`;
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!initialConversationId) return;
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
fetch(`${base}/${encodeURIComponent(initialConversationId)}`, { signal: controller.signal }).then(async (response) => {
|
|
67
|
+
if (!response.ok) throw new Error("Retained conversation is unavailable");
|
|
68
|
+
const body = await response.json();
|
|
69
|
+
if (!controller.signal.aborted) {
|
|
70
|
+
setSelected(body.conversation);
|
|
71
|
+
setTitle(body.conversation.title);
|
|
72
|
+
}
|
|
73
|
+
}).catch((error) => {
|
|
74
|
+
if (!controller.signal.aborted) setError(error.message);
|
|
75
|
+
});
|
|
76
|
+
return () => controller.abort();
|
|
77
|
+
}, [base, initialConversationId]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
setLibrary(null);
|
|
81
|
+
fetch(`${base}?limit=20&offset=${offset}`, { signal: controller.signal }).then(async (response) => {
|
|
82
|
+
if (!response.ok) throw new Error("Could not load conversations.");
|
|
83
|
+
const body = await response.json();
|
|
84
|
+
if (!controller.signal.aborted) setLibrary(body);
|
|
85
|
+
}).catch((error) => {
|
|
86
|
+
if (!controller.signal.aborted) setError(error.message);
|
|
87
|
+
});
|
|
88
|
+
return () => controller.abort();
|
|
89
|
+
}, [
|
|
90
|
+
base,
|
|
91
|
+
offset,
|
|
92
|
+
refresh
|
|
93
|
+
]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!selected) return;
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
setHistory(null);
|
|
98
|
+
setHistoryError("");
|
|
99
|
+
fetch(`${base}/${encodeURIComponent(selected.id)}`, { signal: controller.signal }).then(async (response) => {
|
|
100
|
+
if (!response.ok) throw new Error("This conversation history is unavailable. You can still start a new conversation.");
|
|
101
|
+
const body = await response.json();
|
|
102
|
+
if (!controller.signal.aborted) setHistory(body.messages);
|
|
103
|
+
}).catch((error) => {
|
|
104
|
+
if (!controller.signal.aborted) setHistoryError(error.message);
|
|
105
|
+
});
|
|
106
|
+
return () => controller.abort();
|
|
107
|
+
}, [base, selected?.id]);
|
|
108
|
+
async function create() {
|
|
109
|
+
if (!library || busy || turnBusy) return;
|
|
110
|
+
setBusy(true);
|
|
111
|
+
setError("");
|
|
112
|
+
const request = pendingCreate.current ?? {
|
|
113
|
+
requestId: crypto.randomUUID(),
|
|
114
|
+
expectedSelection: library.selection
|
|
115
|
+
};
|
|
116
|
+
pendingCreate.current = request;
|
|
117
|
+
try {
|
|
118
|
+
sessionStorage.setItem(pendingKey, JSON.stringify(request));
|
|
119
|
+
const response = await fetch(base, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { "content-type": "application/json" },
|
|
122
|
+
body: JSON.stringify(request)
|
|
123
|
+
});
|
|
124
|
+
const body = await response.json();
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
if (response.status === 409 || response.status === 400) clearPending();
|
|
127
|
+
throw new Error(body.error ?? "Could not create conversation.");
|
|
128
|
+
}
|
|
129
|
+
clearPending();
|
|
130
|
+
setSelected(null);
|
|
131
|
+
setOffset(0);
|
|
132
|
+
setRefresh((value) => value + 1);
|
|
133
|
+
await onCreated();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
setError(error instanceof Error ? error.message : "Could not create conversation.");
|
|
136
|
+
} finally {
|
|
137
|
+
setBusy(false);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function rename() {
|
|
141
|
+
if (!selected || busy) return;
|
|
142
|
+
setBusy(true);
|
|
143
|
+
setError("");
|
|
144
|
+
try {
|
|
145
|
+
const response = await fetch(`${base}/${encodeURIComponent(selected.id)}`, {
|
|
146
|
+
method: "PATCH",
|
|
147
|
+
headers: { "content-type": "application/json" },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
title,
|
|
150
|
+
expectedTitleRevision: selected.titleRevision
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
const body = await response.json();
|
|
154
|
+
if (!response.ok) throw new Error(body.error ?? "Could not rename conversation.");
|
|
155
|
+
setSelected(body.conversation);
|
|
156
|
+
setRefresh((value) => value + 1);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
setError(error instanceof Error ? error.message : "Could not rename conversation.");
|
|
159
|
+
} finally {
|
|
160
|
+
setBusy(false);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
164
|
+
"aria-live": "off",
|
|
165
|
+
className: "my-3 min-w-0 space-y-4 rounded-lg border border-fawn bg-ivory p-4",
|
|
166
|
+
"aria-label": "Conversation library",
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ jsxs("div", {
|
|
169
|
+
className: "flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
170
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
171
|
+
className: "m-0 text-lg",
|
|
172
|
+
children: "Conversations"
|
|
173
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
174
|
+
variant: "primary",
|
|
175
|
+
disabled: !library || busy || turnBusy,
|
|
176
|
+
onClick: () => void create(),
|
|
177
|
+
children: "New conversation"
|
|
178
|
+
})]
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ jsx("p", {
|
|
181
|
+
className: "m-0 text-sm",
|
|
182
|
+
children: "Starting a new conversation keeps previous history and saved files. Viewing history does not change the active conversation."
|
|
183
|
+
}),
|
|
184
|
+
turnBusy && /* @__PURE__ */ jsx("p", {
|
|
185
|
+
role: "status",
|
|
186
|
+
children: "Wait for the current turn to finish before starting a new conversation."
|
|
187
|
+
}),
|
|
188
|
+
error && /* @__PURE__ */ jsxs("p", {
|
|
189
|
+
role: "alert",
|
|
190
|
+
children: [
|
|
191
|
+
error,
|
|
192
|
+
" ",
|
|
193
|
+
/* @__PURE__ */ jsx(Button, {
|
|
194
|
+
variant: "ghost",
|
|
195
|
+
disabled: busy,
|
|
196
|
+
onClick: () => {
|
|
197
|
+
setError("");
|
|
198
|
+
setRefresh((value) => value + 1);
|
|
199
|
+
},
|
|
200
|
+
children: "Refresh library"
|
|
201
|
+
})
|
|
202
|
+
]
|
|
203
|
+
}),
|
|
204
|
+
!library ? /* @__PURE__ */ jsx("p", {
|
|
205
|
+
role: "status",
|
|
206
|
+
children: "Loading conversations…"
|
|
207
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
208
|
+
!library.total && /* @__PURE__ */ jsx("p", { children: "No conversations yet." }),
|
|
209
|
+
/* @__PURE__ */ jsx("ul", {
|
|
210
|
+
className: "mt-4 grid list-none gap-2 p-0",
|
|
211
|
+
children: library.conversations.map((item) => /* @__PURE__ */ jsxs("li", {
|
|
212
|
+
className: "flex min-w-0 flex-col items-start gap-3 rounded border border-fawn p-3 sm:flex-row sm:items-center sm:justify-between",
|
|
213
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
214
|
+
className: "flex min-w-0 flex-1 flex-wrap items-center gap-2",
|
|
215
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
216
|
+
className: "min-w-0 break-words [overflow-wrap:anywhere]",
|
|
217
|
+
children: item.title
|
|
218
|
+
}), item.id === library.selection.conversationId && /* @__PURE__ */ jsx("strong", {
|
|
219
|
+
className: "shrink-0 rounded-full bg-frost px-2 py-0.5 text-xs",
|
|
220
|
+
children: "Active"
|
|
221
|
+
})]
|
|
222
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
223
|
+
variant: "ghost",
|
|
224
|
+
className: "max-w-full whitespace-normal break-words text-left sm:max-w-[50%]",
|
|
225
|
+
disabled: busy,
|
|
226
|
+
"aria-pressed": selected?.id === item.id,
|
|
227
|
+
onClick: () => {
|
|
228
|
+
setSelected(item);
|
|
229
|
+
setTitle(item.title);
|
|
230
|
+
},
|
|
231
|
+
children: ["View ", item.title]
|
|
232
|
+
})]
|
|
233
|
+
}, item.id))
|
|
234
|
+
}),
|
|
235
|
+
/* @__PURE__ */ jsxs("div", {
|
|
236
|
+
className: "mt-4 flex flex-wrap items-center gap-2",
|
|
237
|
+
children: [
|
|
238
|
+
/* @__PURE__ */ jsx(Button, {
|
|
239
|
+
variant: "ghost",
|
|
240
|
+
disabled: busy || offset === 0,
|
|
241
|
+
onClick: () => setOffset(Math.max(0, offset - 20)),
|
|
242
|
+
children: "Previous"
|
|
243
|
+
}),
|
|
244
|
+
/* @__PURE__ */ jsx(Button, {
|
|
245
|
+
variant: "ghost",
|
|
246
|
+
disabled: busy || offset + library.conversations.length >= library.total,
|
|
247
|
+
onClick: () => setOffset(offset + 20),
|
|
248
|
+
children: "Next"
|
|
249
|
+
}),
|
|
250
|
+
/* @__PURE__ */ jsxs("span", {
|
|
251
|
+
className: "text-sm",
|
|
252
|
+
children: [library.total, " conversations"]
|
|
253
|
+
})
|
|
254
|
+
]
|
|
255
|
+
})
|
|
256
|
+
] }),
|
|
257
|
+
selected && /* @__PURE__ */ jsxs("section", {
|
|
258
|
+
className: "space-y-4 border-t border-fawn pt-4",
|
|
259
|
+
"aria-label": "Retained conversation",
|
|
260
|
+
children: [
|
|
261
|
+
/* @__PURE__ */ jsxs("div", {
|
|
262
|
+
className: "grid min-w-0 grid-cols-2 items-end gap-2 sm:grid-cols-[minmax(0,1fr)_auto_auto]",
|
|
263
|
+
children: [
|
|
264
|
+
/* @__PURE__ */ jsxs("label", {
|
|
265
|
+
className: "col-span-2 m-0 grid min-w-0 gap-2 sm:col-span-1",
|
|
266
|
+
children: ["Conversation title", /* @__PURE__ */ jsx("input", {
|
|
267
|
+
className: "h-11",
|
|
268
|
+
value: title,
|
|
269
|
+
maxLength: 200,
|
|
270
|
+
onChange: (event) => setTitle(event.target.value)
|
|
271
|
+
})]
|
|
272
|
+
}),
|
|
273
|
+
/* @__PURE__ */ jsx(Button, {
|
|
274
|
+
variant: "ghost",
|
|
275
|
+
className: "h-11",
|
|
276
|
+
disabled: busy || !title.trim(),
|
|
277
|
+
onClick: () => void rename(),
|
|
278
|
+
children: "Rename"
|
|
279
|
+
}),
|
|
280
|
+
/* @__PURE__ */ jsx(Button, {
|
|
281
|
+
variant: "ghost",
|
|
282
|
+
className: "h-11",
|
|
283
|
+
disabled: busy,
|
|
284
|
+
onClick: () => setSelected(null),
|
|
285
|
+
children: "Close history"
|
|
286
|
+
})
|
|
287
|
+
]
|
|
288
|
+
}),
|
|
289
|
+
/* @__PURE__ */ jsx("p", {
|
|
290
|
+
className: "m-0 text-sm",
|
|
291
|
+
children: "Viewing retained history. New messages still go to the active conversation."
|
|
292
|
+
}),
|
|
293
|
+
historyError ? /* @__PURE__ */ jsx("p", {
|
|
294
|
+
role: "alert",
|
|
295
|
+
children: historyError
|
|
296
|
+
}) : history === null ? /* @__PURE__ */ jsx("p", {
|
|
297
|
+
role: "status",
|
|
298
|
+
children: "Loading history…"
|
|
299
|
+
}) : history.length ? renderHistory(history) : /* @__PURE__ */ jsx("p", { children: "This conversation is empty." })
|
|
300
|
+
]
|
|
301
|
+
})
|
|
302
|
+
]
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/lib/queue-journal.ts
|
|
307
|
+
/** Store bytes before submission so a reload can retry the exact same attempt. */
|
|
308
|
+
async function queueJournal(agentId, value, clearRequestId) {
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
const opening = indexedDB.open("bazilion-queue-attempts", 1);
|
|
311
|
+
opening.onupgradeneeded = () => {
|
|
312
|
+
opening.result.createObjectStore("requests", { keyPath: "input.requestId" }).createIndex("agentId", "agentId");
|
|
313
|
+
};
|
|
314
|
+
opening.onerror = () => reject(/* @__PURE__ */ new Error("Local queue recovery storage is unavailable"));
|
|
315
|
+
opening.onblocked = () => reject(/* @__PURE__ */ new Error("Close other Bazilion tabs to open queue recovery storage"));
|
|
316
|
+
opening.onsuccess = () => {
|
|
317
|
+
const db = opening.result;
|
|
318
|
+
const tx = db.transaction("requests", value === void 0 ? "readonly" : "readwrite");
|
|
319
|
+
const store = tx.objectStore("requests");
|
|
320
|
+
if (value === null && !clearRequestId) {
|
|
321
|
+
db.close();
|
|
322
|
+
reject(/* @__PURE__ */ new Error("Queue request identity is required to clear recovery state"));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const request = value === void 0 ? store.index("agentId").get(agentId) : value === null ? store.delete(clearRequestId) : store.put(value);
|
|
326
|
+
tx.oncomplete = () => {
|
|
327
|
+
db.close();
|
|
328
|
+
resolve(value === void 0 ? request.result ?? null : value);
|
|
329
|
+
};
|
|
330
|
+
tx.onerror = tx.onabort = () => {
|
|
331
|
+
db.close();
|
|
332
|
+
reject(/* @__PURE__ */ new Error("Unable to retain queue request locally; it was not acknowledged"));
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/components/UserQueuePanel.tsx
|
|
339
|
+
var UserQueuePanel = forwardRef(function UserQueuePanel({ agentId, selection, onQueueMode, onViewConversation }, ref) {
|
|
340
|
+
const base = `/api/agents/${encodeURIComponent(agentId)}/queue`;
|
|
341
|
+
const [state, setState] = useState(null);
|
|
342
|
+
const [error, setError] = useState("");
|
|
343
|
+
const [notice, setNotice] = useState("");
|
|
344
|
+
const [busy, setBusy] = useState(false);
|
|
345
|
+
const [ready, setReady] = useState(false);
|
|
346
|
+
const readyRef = useRef(false);
|
|
347
|
+
const [pending, setPending] = useState(null);
|
|
348
|
+
const pendingRef = useRef(null);
|
|
349
|
+
const locked = useRef(false);
|
|
350
|
+
const mounted = useRef(true);
|
|
351
|
+
const refreshGeneration = useRef(0);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
mounted.current = true;
|
|
354
|
+
return () => {
|
|
355
|
+
mounted.current = false;
|
|
356
|
+
};
|
|
357
|
+
}, []);
|
|
358
|
+
const [editing, setEditing] = useState(null);
|
|
359
|
+
const [text, setText] = useState("");
|
|
360
|
+
const [editFiles, setEditFiles] = useState([]);
|
|
361
|
+
const [history, setHistory] = useState(false);
|
|
362
|
+
const [offset, setOffset] = useState(0);
|
|
363
|
+
const modeRef = useRef(onQueueMode);
|
|
364
|
+
modeRef.current = onQueueMode;
|
|
365
|
+
async function refresh() {
|
|
366
|
+
const generation = ++refreshGeneration.current;
|
|
367
|
+
const response = await fetch(`${base}?all=${history ? 1 : 0}&offset=${offset}`);
|
|
368
|
+
if (!response.ok) throw new Error("Queue could not be loaded");
|
|
369
|
+
const body = await response.json();
|
|
370
|
+
const activeResponse = history ? await fetch(base) : null;
|
|
371
|
+
const active = activeResponse?.ok ? await activeResponse.json() : body;
|
|
372
|
+
if (mounted.current && generation === refreshGeneration.current) {
|
|
373
|
+
setState(body);
|
|
374
|
+
if (readyRef.current) modeRef.current(active.total > 0 || active.control.paused || !!pendingRef.current);
|
|
375
|
+
}
|
|
376
|
+
return body;
|
|
377
|
+
}
|
|
378
|
+
useEffect(() => {
|
|
379
|
+
let live = true;
|
|
380
|
+
queueJournal(agentId).then((value) => {
|
|
381
|
+
if (live) {
|
|
382
|
+
pendingRef.current = value;
|
|
383
|
+
setPending(value);
|
|
384
|
+
readyRef.current = true;
|
|
385
|
+
setReady(true);
|
|
386
|
+
refresh().catch((e) => setError(e.message));
|
|
387
|
+
}
|
|
388
|
+
}).catch((e) => {
|
|
389
|
+
if (live) setError(e.message);
|
|
390
|
+
});
|
|
391
|
+
return () => {
|
|
392
|
+
live = false;
|
|
393
|
+
};
|
|
394
|
+
}, [agentId]);
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
refresh().catch((e) => setError(e.message));
|
|
397
|
+
const timer = setInterval(() => {
|
|
398
|
+
refresh().catch((e) => setError(e.message));
|
|
399
|
+
}, 2e3);
|
|
400
|
+
return () => clearInterval(timer);
|
|
401
|
+
}, [
|
|
402
|
+
agentId,
|
|
403
|
+
history,
|
|
404
|
+
offset
|
|
405
|
+
]);
|
|
406
|
+
async function clearPending(record) {
|
|
407
|
+
await queueJournal(agentId, null, record.input.requestId);
|
|
408
|
+
const remaining = await queueJournal(agentId);
|
|
409
|
+
pendingRef.current = remaining;
|
|
410
|
+
setPending(remaining);
|
|
411
|
+
}
|
|
412
|
+
async function submit(record, retry = false) {
|
|
413
|
+
if (locked.current || !ready) return false;
|
|
414
|
+
if (pendingRef.current && !retry) {
|
|
415
|
+
setError("Resolve the unacknowledged queue request before sending another.");
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
locked.current = true;
|
|
419
|
+
setBusy(true);
|
|
420
|
+
setError("");
|
|
421
|
+
setNotice("");
|
|
422
|
+
try {
|
|
423
|
+
await queueJournal(agentId, record);
|
|
424
|
+
pendingRef.current = record;
|
|
425
|
+
setPending(record);
|
|
426
|
+
const response = await fetch(record.replacementId ? `${base}/${encodeURIComponent(record.replacementId)}` : base, {
|
|
427
|
+
method: record.replacementId ? "PATCH" : "POST",
|
|
428
|
+
headers: { "content-type": "application/json" },
|
|
429
|
+
body: JSON.stringify(record.input)
|
|
430
|
+
});
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
const detail = await response.json().catch(() => ({}));
|
|
433
|
+
if (response.status >= 400 && response.status < 500) await clearPending(record);
|
|
434
|
+
throw new Error(detail.error ?? "Queue acknowledgement unavailable; retry this same request.");
|
|
435
|
+
}
|
|
436
|
+
const item = await response.json();
|
|
437
|
+
await clearPending(record);
|
|
438
|
+
setNotice(`Follow-up ${item.status}.`);
|
|
439
|
+
setEditing(null);
|
|
440
|
+
await refresh().catch(() => setError("Input acknowledged; queue display could not refresh."));
|
|
441
|
+
return true;
|
|
442
|
+
} catch (e) {
|
|
443
|
+
setError(e instanceof Error ? e.message : "Queue request failed");
|
|
444
|
+
return false;
|
|
445
|
+
} finally {
|
|
446
|
+
locked.current = false;
|
|
447
|
+
setBusy(false);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async function action(path, body, method = "POST") {
|
|
451
|
+
if (locked.current) return false;
|
|
452
|
+
locked.current = true;
|
|
453
|
+
setBusy(true);
|
|
454
|
+
setError("");
|
|
455
|
+
try {
|
|
456
|
+
const response = await fetch(`${base}${path}`, {
|
|
457
|
+
method,
|
|
458
|
+
headers: { "content-type": "application/json" },
|
|
459
|
+
body: JSON.stringify(body)
|
|
460
|
+
});
|
|
461
|
+
if (!response.ok) {
|
|
462
|
+
const detail = await response.json();
|
|
463
|
+
throw new Error(detail.error ?? "Queue changed; refresh and try again");
|
|
464
|
+
}
|
|
465
|
+
await refresh();
|
|
466
|
+
return true;
|
|
467
|
+
} catch (e) {
|
|
468
|
+
setError(e instanceof Error ? e.message : "Queue action failed");
|
|
469
|
+
await refresh().catch(() => {});
|
|
470
|
+
return false;
|
|
471
|
+
} finally {
|
|
472
|
+
locked.current = false;
|
|
473
|
+
setBusy(false);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
useImperativeHandle(ref, () => ({
|
|
477
|
+
async enqueue(message, attachments, expectedSelection) {
|
|
478
|
+
if (!expectedSelection) {
|
|
479
|
+
setError("Refresh conversation history before queuing input.");
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
return submit({
|
|
483
|
+
agentId,
|
|
484
|
+
input: {
|
|
485
|
+
requestId: crypto.randomUUID(),
|
|
486
|
+
message,
|
|
487
|
+
attachments,
|
|
488
|
+
expectedSelection
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
async stop() {
|
|
493
|
+
if (!state) {
|
|
494
|
+
setError("Load queue controls before stopping.");
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
return action("/stop", { expectedRevision: state.control.revision });
|
|
498
|
+
}
|
|
499
|
+
}));
|
|
500
|
+
async function edit(item) {
|
|
501
|
+
try {
|
|
502
|
+
const response = await fetch(`${base}/${encodeURIComponent(item.id)}/input`);
|
|
503
|
+
if (!response.ok) throw new Error("Retained input is unavailable");
|
|
504
|
+
const input = await response.json();
|
|
505
|
+
setEditing(item);
|
|
506
|
+
setText(input.message);
|
|
507
|
+
setEditFiles(input.attachments);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
setError(e instanceof Error ? e.message : "Could not edit");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
513
|
+
"aria-label": "Follow-up queue",
|
|
514
|
+
className: "max-h-[45%] min-w-0 shrink-0 space-y-3 overflow-y-auto border-t border-frost px-5 py-4 text-sm",
|
|
515
|
+
children: [
|
|
516
|
+
/* @__PURE__ */ jsxs("div", {
|
|
517
|
+
className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
518
|
+
children: [/* @__PURE__ */ jsxs("strong", {
|
|
519
|
+
className: "shrink-0",
|
|
520
|
+
children: ["Follow-ups ", state?.control.paused ? "· Paused" : ""]
|
|
521
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
522
|
+
className: "flex flex-wrap items-center gap-2",
|
|
523
|
+
children: [
|
|
524
|
+
state && /* @__PURE__ */ jsx(Button, {
|
|
525
|
+
variant: "ghost",
|
|
526
|
+
disabled: busy,
|
|
527
|
+
onClick: () => void action("/control", {
|
|
528
|
+
paused: !state.control.paused,
|
|
529
|
+
expectedRevision: state.control.revision
|
|
530
|
+
}),
|
|
531
|
+
children: state.control.paused ? "Resume queue" : "Pause queue"
|
|
532
|
+
}),
|
|
533
|
+
state && /* @__PURE__ */ jsx(Button, {
|
|
534
|
+
variant: "danger",
|
|
535
|
+
disabled: busy,
|
|
536
|
+
onClick: () => void action("/stop", { expectedRevision: state.control.revision }),
|
|
537
|
+
children: "Stop and pause"
|
|
538
|
+
}),
|
|
539
|
+
/* @__PURE__ */ jsx(Button, {
|
|
540
|
+
variant: "ghost",
|
|
541
|
+
onClick: () => {
|
|
542
|
+
setHistory(!history);
|
|
543
|
+
setOffset(0);
|
|
544
|
+
},
|
|
545
|
+
children: history ? "Pending" : "History"
|
|
546
|
+
})
|
|
547
|
+
]
|
|
548
|
+
})]
|
|
549
|
+
}),
|
|
550
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
551
|
+
role: "alert",
|
|
552
|
+
className: "m-0 break-words text-danger",
|
|
553
|
+
children: error
|
|
554
|
+
}),
|
|
555
|
+
notice && /* @__PURE__ */ jsx("p", {
|
|
556
|
+
role: "status",
|
|
557
|
+
className: "m-0 text-mocha",
|
|
558
|
+
children: notice
|
|
559
|
+
}),
|
|
560
|
+
pending && /* @__PURE__ */ jsxs("div", {
|
|
561
|
+
role: "status",
|
|
562
|
+
className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
|
|
563
|
+
children: [
|
|
564
|
+
/* @__PURE__ */ jsx("p", { children: "A queue request has no confirmed acknowledgement. Retrying uses the same request and files." }),
|
|
565
|
+
/* @__PURE__ */ jsx("p", {
|
|
566
|
+
className: "truncate",
|
|
567
|
+
children: pending.input.message
|
|
568
|
+
}),
|
|
569
|
+
/* @__PURE__ */ jsx(Button, {
|
|
570
|
+
variant: "primary",
|
|
571
|
+
disabled: busy,
|
|
572
|
+
onClick: () => void submit(pending, true),
|
|
573
|
+
children: "Retry saved request"
|
|
574
|
+
})
|
|
575
|
+
]
|
|
576
|
+
}),
|
|
577
|
+
/* @__PURE__ */ jsxs("div", {
|
|
578
|
+
className: "min-w-0 space-y-3",
|
|
579
|
+
children: [state?.items.map((item) => /* @__PURE__ */ jsxs("article", {
|
|
580
|
+
className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
|
|
581
|
+
children: [
|
|
582
|
+
/* @__PURE__ */ jsxs("div", {
|
|
583
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
584
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
585
|
+
className: "flex flex-wrap items-center gap-2",
|
|
586
|
+
children: [/* @__PURE__ */ jsx("strong", {
|
|
587
|
+
className: "rounded-full bg-frost px-2 py-1 text-xs capitalize",
|
|
588
|
+
children: item.status
|
|
589
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
590
|
+
className: "text-xs text-mocha",
|
|
591
|
+
children: item.source === "telegram" ? "Telegram" : "Web / API"
|
|
592
|
+
})]
|
|
593
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
594
|
+
variant: "ghost",
|
|
595
|
+
onClick: () => onViewConversation(item.conversationId),
|
|
596
|
+
children: "View conversation"
|
|
597
|
+
})]
|
|
598
|
+
}),
|
|
599
|
+
/* @__PURE__ */ jsx("p", {
|
|
600
|
+
className: "m-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]",
|
|
601
|
+
children: item.text ?? "Input retention expired"
|
|
602
|
+
}),
|
|
603
|
+
item.attachments.map((file) => /* @__PURE__ */ jsxs("p", {
|
|
604
|
+
className: "m-0 break-all text-xs text-mocha",
|
|
605
|
+
children: [
|
|
606
|
+
file.name ?? "Attachment",
|
|
607
|
+
" · ",
|
|
608
|
+
file.byteLength,
|
|
609
|
+
" bytes"
|
|
610
|
+
]
|
|
611
|
+
}, file.id)),
|
|
612
|
+
item.diagnostic && /* @__PURE__ */ jsx("p", {
|
|
613
|
+
className: "m-0 break-words",
|
|
614
|
+
children: item.diagnostic
|
|
615
|
+
}),
|
|
616
|
+
item.approvalId && /* @__PURE__ */ jsx("a", {
|
|
617
|
+
href: `/approvals`,
|
|
618
|
+
children: "Review communication approval"
|
|
619
|
+
}),
|
|
620
|
+
item.status === "pending" && /* @__PURE__ */ jsxs("div", {
|
|
621
|
+
className: "flex flex-wrap items-center gap-2",
|
|
622
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
623
|
+
variant: "ghost",
|
|
624
|
+
disabled: busy || !!pending,
|
|
625
|
+
onClick: () => void edit(item),
|
|
626
|
+
children: "Edit"
|
|
627
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
628
|
+
variant: "danger",
|
|
629
|
+
disabled: busy,
|
|
630
|
+
onClick: () => void action(`/${item.id}`, { expectedRevision: item.revision }, "DELETE"),
|
|
631
|
+
children: "Remove"
|
|
632
|
+
})]
|
|
633
|
+
}),
|
|
634
|
+
item.status === "uncertain" && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { children: "This input may already have acted. Review its conversation before closing it. Closing never retries it." }), /* @__PURE__ */ jsx(Button, {
|
|
635
|
+
variant: "danger",
|
|
636
|
+
disabled: busy,
|
|
637
|
+
onClick: () => void action(`/${item.id}/reconcile`, {
|
|
638
|
+
expectedRevision: item.revision,
|
|
639
|
+
acknowledged: true
|
|
640
|
+
}),
|
|
641
|
+
children: "Acknowledge and close"
|
|
642
|
+
})] })
|
|
643
|
+
]
|
|
644
|
+
}, item.id)), editing && /* @__PURE__ */ jsxs("form", {
|
|
645
|
+
className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
|
|
646
|
+
onSubmit: (e) => {
|
|
647
|
+
e.preventDefault();
|
|
648
|
+
const expectedSelection = selection();
|
|
649
|
+
if (expectedSelection) submit({
|
|
650
|
+
agentId,
|
|
651
|
+
replacementId: editing.id,
|
|
652
|
+
input: {
|
|
653
|
+
requestId: crypto.randomUUID(),
|
|
654
|
+
expectedRevision: editing.revision,
|
|
655
|
+
expectedSelection,
|
|
656
|
+
message: text,
|
|
657
|
+
attachments: editFiles
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
},
|
|
661
|
+
children: [
|
|
662
|
+
/* @__PURE__ */ jsxs("label", {
|
|
663
|
+
className: "m-0 grid gap-2",
|
|
664
|
+
children: ["Edit queued input", /* @__PURE__ */ jsx("textarea", {
|
|
665
|
+
"aria-label": "Edit queued input",
|
|
666
|
+
rows: 3,
|
|
667
|
+
className: "w-full resize-y",
|
|
668
|
+
value: text,
|
|
669
|
+
onChange: (e) => setText(e.target.value)
|
|
670
|
+
})]
|
|
671
|
+
}),
|
|
672
|
+
editFiles.map((file, index) => /* @__PURE__ */ jsxs("div", { children: [
|
|
673
|
+
file.name ?? "Attachment",
|
|
674
|
+
" ",
|
|
675
|
+
/* @__PURE__ */ jsx(Button, {
|
|
676
|
+
variant: "ghost",
|
|
677
|
+
onClick: () => setEditFiles((files) => files.filter((_, i) => i !== index)),
|
|
678
|
+
children: "Remove attachment"
|
|
679
|
+
})
|
|
680
|
+
] }, index)),
|
|
681
|
+
/* @__PURE__ */ jsxs("div", {
|
|
682
|
+
className: "flex flex-wrap items-center gap-2",
|
|
683
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
684
|
+
variant: "primary",
|
|
685
|
+
type: "submit",
|
|
686
|
+
disabled: busy || !!pending,
|
|
687
|
+
children: "Save replacement"
|
|
688
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
689
|
+
variant: "ghost",
|
|
690
|
+
onClick: () => setEditing(null),
|
|
691
|
+
children: "Cancel edit"
|
|
692
|
+
})]
|
|
693
|
+
})
|
|
694
|
+
]
|
|
695
|
+
})]
|
|
696
|
+
}),
|
|
697
|
+
state && (offset > 0 || offset + state.items.length < state.total) && /* @__PURE__ */ jsxs("div", {
|
|
698
|
+
className: "flex flex-wrap items-center gap-2",
|
|
699
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
700
|
+
variant: "ghost",
|
|
701
|
+
disabled: offset === 0,
|
|
702
|
+
onClick: () => setOffset(Math.max(0, offset - 20)),
|
|
703
|
+
children: "Previous"
|
|
704
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
705
|
+
variant: "ghost",
|
|
706
|
+
disabled: offset + state.items.length >= state.total,
|
|
707
|
+
onClick: () => setOffset(offset + 20),
|
|
708
|
+
children: "Next"
|
|
709
|
+
})]
|
|
710
|
+
})
|
|
711
|
+
]
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/components/AgentQuestions.tsx
|
|
716
|
+
function AgentQuestions({ agentId }) {
|
|
717
|
+
const [items, setItems] = useState([]);
|
|
718
|
+
const [error, setError] = useState("");
|
|
719
|
+
useEffect(() => {
|
|
720
|
+
let live = true;
|
|
721
|
+
let running = false;
|
|
722
|
+
const refresh = async () => {
|
|
723
|
+
if (running) return;
|
|
724
|
+
running = true;
|
|
725
|
+
try {
|
|
726
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentId)}/questions`, { cache: "no-store" });
|
|
727
|
+
if (!response.ok) throw new Error("Questions could not be refreshed");
|
|
728
|
+
const body = await response.json();
|
|
729
|
+
if (live) {
|
|
730
|
+
setItems(body.questions);
|
|
731
|
+
setError("");
|
|
732
|
+
}
|
|
733
|
+
} catch (e) {
|
|
734
|
+
if (live) {
|
|
735
|
+
setItems([]);
|
|
736
|
+
setError(e instanceof Error ? e.message : "Questions unavailable");
|
|
737
|
+
}
|
|
738
|
+
} finally {
|
|
739
|
+
running = false;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
refresh();
|
|
743
|
+
const timer = setInterval(() => {
|
|
744
|
+
refresh();
|
|
745
|
+
}, 2e3);
|
|
746
|
+
return () => {
|
|
747
|
+
live = false;
|
|
748
|
+
clearInterval(timer);
|
|
749
|
+
};
|
|
750
|
+
}, [agentId]);
|
|
751
|
+
if (!items.length && !error) return null;
|
|
752
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
753
|
+
"aria-label": "Agent questions",
|
|
754
|
+
className: "max-h-[40vh] min-w-0 shrink-0 space-y-3 overflow-y-auto border-t border-frost px-5 py-3",
|
|
755
|
+
children: [
|
|
756
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
757
|
+
role: "status",
|
|
758
|
+
children: error
|
|
759
|
+
}),
|
|
760
|
+
items.filter((item) => item.status === "pending" || item.continuation === "unconfirmed").map((item) => /* @__PURE__ */ jsx(QuestionCard, { item }, item.id)),
|
|
761
|
+
items.some((item) => item.status !== "pending" && item.continuation !== "unconfirmed") && /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "Recent question outcomes" }), items.filter((item) => item.status !== "pending" && item.continuation !== "unconfirmed").map((item) => /* @__PURE__ */ jsx(QuestionCard, { item }, item.id))] })
|
|
762
|
+
]
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
function QuestionCard({ item }) {
|
|
766
|
+
const storageKey = `bazilion-question-answer:${item.agentId}:${item.id}`;
|
|
767
|
+
const [choice, setChoice] = useState("");
|
|
768
|
+
const [text, setText] = useState("");
|
|
769
|
+
const [pending, setPending] = useState(null);
|
|
770
|
+
const pendingRef = useRef(null);
|
|
771
|
+
const [ready, setReady] = useState(false);
|
|
772
|
+
const [busy, setBusy] = useState(false);
|
|
773
|
+
const locked = useRef(false);
|
|
774
|
+
const [error, setError] = useState("");
|
|
775
|
+
const [notice, setNotice] = useState("");
|
|
776
|
+
useEffect(() => {
|
|
777
|
+
try {
|
|
778
|
+
const saved = sessionStorage.getItem(storageKey);
|
|
779
|
+
if (saved) {
|
|
780
|
+
const value = JSON.parse(saved);
|
|
781
|
+
pendingRef.current = value;
|
|
782
|
+
setPending(value);
|
|
783
|
+
}
|
|
784
|
+
setReady(true);
|
|
785
|
+
} catch {
|
|
786
|
+
setError("Answer recovery storage is unavailable. Enable session storage before answering.");
|
|
787
|
+
}
|
|
788
|
+
}, [storageKey]);
|
|
789
|
+
useEffect(() => {
|
|
790
|
+
if (item.status === "pending") return;
|
|
791
|
+
try {
|
|
792
|
+
sessionStorage.removeItem(storageKey);
|
|
793
|
+
pendingRef.current = null;
|
|
794
|
+
setPending(null);
|
|
795
|
+
} catch {}
|
|
796
|
+
}, [item.status, storageKey]);
|
|
797
|
+
async function submit(answer) {
|
|
798
|
+
if (locked.current || !ready || item.status !== "pending") return;
|
|
799
|
+
locked.current = true;
|
|
800
|
+
setBusy(true);
|
|
801
|
+
setError("");
|
|
802
|
+
try {
|
|
803
|
+
if (!pendingRef.current && !answer) throw new Error("Choose an answer first");
|
|
804
|
+
const input = pendingRef.current ?? {
|
|
805
|
+
requestId: crypto.randomUUID(),
|
|
806
|
+
conversationId: item.conversationId,
|
|
807
|
+
answer
|
|
808
|
+
};
|
|
809
|
+
sessionStorage.setItem(storageKey, JSON.stringify(input));
|
|
810
|
+
pendingRef.current = input;
|
|
811
|
+
setPending(input);
|
|
812
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(item.agentId)}/questions/${encodeURIComponent(item.id)}/answer`, {
|
|
813
|
+
method: "POST",
|
|
814
|
+
headers: { "content-type": "application/json" },
|
|
815
|
+
body: JSON.stringify(input)
|
|
816
|
+
}).catch(() => {
|
|
817
|
+
throw new Error("Answer acknowledgement unavailable. Retry the same answer.");
|
|
818
|
+
});
|
|
819
|
+
if (!response.ok) {
|
|
820
|
+
if (response.status === 409 || response.status === 404) throw new Error("This question is no longer answerable. Its outcome will refresh shortly.");
|
|
821
|
+
throw new Error("Answer acknowledgement unavailable. Retry the same answer.");
|
|
822
|
+
}
|
|
823
|
+
if ((await response.json()).kind === "held") setNotice("Answer awaits communication approval. The Agent has not received it.");
|
|
824
|
+
else {
|
|
825
|
+
sessionStorage.removeItem(storageKey);
|
|
826
|
+
setNotice("Answer accepted. Waiting for saved conversation evidence that the Agent consumed it.");
|
|
827
|
+
}
|
|
828
|
+
} catch (e) {
|
|
829
|
+
setError(e instanceof Error ? e.message : "Answer failed");
|
|
830
|
+
} finally {
|
|
831
|
+
locked.current = false;
|
|
832
|
+
setBusy(false);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const disabled = !ready || busy || !!pending || item.status !== "pending" || !!item.answerApprovalId;
|
|
836
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
837
|
+
className: "flex min-w-0 flex-col gap-3 rounded-lg border border-frost bg-ivory p-3",
|
|
838
|
+
children: [
|
|
839
|
+
/* @__PURE__ */ jsx("p", {
|
|
840
|
+
className: "m-0 break-words font-medium",
|
|
841
|
+
children: item.question.prompt
|
|
842
|
+
}),
|
|
843
|
+
/* @__PURE__ */ jsxs("details", {
|
|
844
|
+
className: "text-xs text-mocha",
|
|
845
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
846
|
+
className: "cursor-pointer",
|
|
847
|
+
children: ["Question details · ", item.status]
|
|
848
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
849
|
+
className: "mt-2 break-all",
|
|
850
|
+
children: [
|
|
851
|
+
"Conversation ",
|
|
852
|
+
item.conversationId,
|
|
853
|
+
" · ",
|
|
854
|
+
item.continuation
|
|
855
|
+
]
|
|
856
|
+
})]
|
|
857
|
+
}),
|
|
858
|
+
item.status === "pending" ? /* @__PURE__ */ jsxs("form", {
|
|
859
|
+
className: "flex min-w-0 flex-col gap-3",
|
|
860
|
+
onSubmit: (event) => {
|
|
861
|
+
event.preventDefault();
|
|
862
|
+
if (choice === "other") submit({
|
|
863
|
+
kind: "text",
|
|
864
|
+
text
|
|
865
|
+
});
|
|
866
|
+
else if (choice !== "") submit({
|
|
867
|
+
kind: "choice",
|
|
868
|
+
index: Number(choice)
|
|
869
|
+
});
|
|
870
|
+
},
|
|
871
|
+
children: [
|
|
872
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
873
|
+
disabled,
|
|
874
|
+
className: "m-0 grid min-w-0 gap-2 border-0 p-0 sm:grid-cols-2",
|
|
875
|
+
children: [
|
|
876
|
+
/* @__PURE__ */ jsx("legend", {
|
|
877
|
+
className: "sr-only",
|
|
878
|
+
children: "Answer this question"
|
|
879
|
+
}),
|
|
880
|
+
item.question.choices.map((option, index) => /* @__PURE__ */ jsxs("label", {
|
|
881
|
+
className: "m-0 flex min-w-0 cursor-pointer items-center gap-2 rounded-md border border-frost px-3 py-2.5 has-[:checked]:border-sapphire has-[:checked]:bg-sapphire-glow",
|
|
882
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
883
|
+
className: "m-0 h-4 w-4 shrink-0 accent-sapphire",
|
|
884
|
+
type: "radio",
|
|
885
|
+
name: `question-${item.id}`,
|
|
886
|
+
value: index,
|
|
887
|
+
checked: choice === String(index),
|
|
888
|
+
onChange: () => setChoice(String(index))
|
|
889
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
890
|
+
className: "min-w-0 break-words [overflow-wrap:anywhere]",
|
|
891
|
+
children: [
|
|
892
|
+
option.label,
|
|
893
|
+
item.question.recommendedIndex === index ? " (Recommended)" : "",
|
|
894
|
+
option.description && /* @__PURE__ */ jsx("small", {
|
|
895
|
+
className: "block",
|
|
896
|
+
children: option.description
|
|
897
|
+
})
|
|
898
|
+
]
|
|
899
|
+
})]
|
|
900
|
+
}, index)),
|
|
901
|
+
/* @__PURE__ */ jsxs("label", {
|
|
902
|
+
className: "m-0 flex min-w-0 cursor-pointer items-center gap-2 rounded-md border border-frost px-3 py-2.5 has-[:checked]:border-sapphire has-[:checked]:bg-sapphire-glow",
|
|
903
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
904
|
+
className: "m-0 h-4 w-4 shrink-0 accent-sapphire",
|
|
905
|
+
type: "radio",
|
|
906
|
+
name: `question-${item.id}`,
|
|
907
|
+
checked: choice === "other",
|
|
908
|
+
onChange: () => setChoice("other")
|
|
909
|
+
}), /* @__PURE__ */ jsx("span", { children: "Other" })]
|
|
910
|
+
}),
|
|
911
|
+
choice === "other" && /* @__PURE__ */ jsxs("label", {
|
|
912
|
+
className: "m-0 grid gap-2 sm:col-span-2",
|
|
913
|
+
children: ["Your answer", /* @__PURE__ */ jsx("textarea", {
|
|
914
|
+
rows: 2,
|
|
915
|
+
className: "w-full resize-y",
|
|
916
|
+
value: text,
|
|
917
|
+
maxLength: 4096,
|
|
918
|
+
onChange: (event) => setText(event.target.value)
|
|
919
|
+
})]
|
|
920
|
+
})
|
|
921
|
+
]
|
|
922
|
+
}),
|
|
923
|
+
pending && /* @__PURE__ */ jsxs("p", {
|
|
924
|
+
className: "break-words",
|
|
925
|
+
children: ["Saved answer: ", pending.answer.kind === "choice" ? item.question.choices[pending.answer.index]?.label : pending.answer.kind === "text" ? pending.answer.text : "Skip"]
|
|
926
|
+
}),
|
|
927
|
+
/* @__PURE__ */ jsxs("div", {
|
|
928
|
+
className: "flex flex-wrap gap-2",
|
|
929
|
+
children: [
|
|
930
|
+
/* @__PURE__ */ jsx(Button, {
|
|
931
|
+
variant: "primary",
|
|
932
|
+
type: "submit",
|
|
933
|
+
disabled: disabled || !choice || choice === "other" && (!text.trim() || new TextEncoder().encode(text).length > 4096),
|
|
934
|
+
children: "Send answer"
|
|
935
|
+
}),
|
|
936
|
+
/* @__PURE__ */ jsx(Button, {
|
|
937
|
+
variant: "ghost",
|
|
938
|
+
disabled,
|
|
939
|
+
onClick: () => {
|
|
940
|
+
submit({ kind: "skip" });
|
|
941
|
+
},
|
|
942
|
+
children: "Skip"
|
|
943
|
+
}),
|
|
944
|
+
pending && !item.answerApprovalId && /* @__PURE__ */ jsx(Button, {
|
|
945
|
+
variant: "ghost",
|
|
946
|
+
disabled: busy,
|
|
947
|
+
onClick: () => {
|
|
948
|
+
submit();
|
|
949
|
+
},
|
|
950
|
+
children: "Retry same answer"
|
|
951
|
+
})
|
|
952
|
+
]
|
|
953
|
+
}),
|
|
954
|
+
/* @__PURE__ */ jsxs("p", {
|
|
955
|
+
className: "m-0 text-xs text-mocha",
|
|
956
|
+
children: [
|
|
957
|
+
"Clarification does not grant permission. This question expires at ",
|
|
958
|
+
new Date(item.expiresAt).toLocaleTimeString(),
|
|
959
|
+
"."
|
|
960
|
+
]
|
|
961
|
+
}),
|
|
962
|
+
item.answerApprovalId && /* @__PURE__ */ jsx("a", {
|
|
963
|
+
href: "/approvals",
|
|
964
|
+
children: "View communication approvals"
|
|
965
|
+
})
|
|
966
|
+
]
|
|
967
|
+
}) : /* @__PURE__ */ jsxs("p", { children: [
|
|
968
|
+
item.answer?.kind === "choice" ? item.question.choices[item.answer.index]?.label : item.answer?.kind === "text" ? item.answer.text : `No answer: ${item.noAnswerReason ?? item.status}`,
|
|
969
|
+
". ",
|
|
970
|
+
item.continuation === "consumed" ? "Saved in the originating conversation; task completion is separate." : item.continuation === "interrupted" ? "The waiting turn ended." : "Consumption has not been confirmed."
|
|
971
|
+
] }),
|
|
972
|
+
notice && /* @__PURE__ */ jsx("p", {
|
|
973
|
+
role: "status",
|
|
974
|
+
children: notice
|
|
975
|
+
}),
|
|
976
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
977
|
+
role: "alert",
|
|
978
|
+
children: error
|
|
979
|
+
})
|
|
980
|
+
]
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
//#endregion
|
|
38
984
|
//#region src/components/ChatPane.tsx
|
|
39
985
|
var INBOX_WAKE_PREFIX = "[[bazilion:inbox-wake]]\n";
|
|
40
986
|
var COMPACTION_REPLAY_PREFIX = "[conversation summary]";
|
|
@@ -63,13 +1009,14 @@ function interactiveChatRequest(message, attachments) {
|
|
|
63
1009
|
return {
|
|
64
1010
|
message,
|
|
65
1011
|
attachments,
|
|
66
|
-
bashApprovalMode: "interactive"
|
|
1012
|
+
bashApprovalMode: "interactive",
|
|
1013
|
+
questionMode: "web"
|
|
67
1014
|
};
|
|
68
1015
|
}
|
|
69
1016
|
function shellApprovalsUrl(agentId) {
|
|
70
1017
|
return `/api/shell-approvals?agentId=${encodeURIComponent(agentId)}`;
|
|
71
1018
|
}
|
|
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 /
|
|
1019
|
+
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 /new — open the retained conversation library\n /help — show this list";
|
|
73
1020
|
function prettyArgs(raw) {
|
|
74
1021
|
if (!raw) return "";
|
|
75
1022
|
try {
|
|
@@ -149,6 +1096,13 @@ function projectMessages(msgs) {
|
|
|
149
1096
|
name: m.toolName ?? "",
|
|
150
1097
|
body: m.content
|
|
151
1098
|
});
|
|
1099
|
+
if (m.result) {
|
|
1100
|
+
entries.push({
|
|
1101
|
+
type: "result",
|
|
1102
|
+
resultId: m.result.resultId
|
|
1103
|
+
});
|
|
1104
|
+
openTool = null;
|
|
1105
|
+
}
|
|
152
1106
|
if (m.images && m.images.length > 0) {
|
|
153
1107
|
entries.push({
|
|
154
1108
|
type: "images",
|
|
@@ -174,7 +1128,12 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
174
1128
|
const [recoveredTurn, setRecoveredTurn] = useState(false);
|
|
175
1129
|
const [approvalBusy, setApprovalBusy] = useState({});
|
|
176
1130
|
const [approvalErrors, setApprovalErrors] = useState({});
|
|
177
|
-
const [
|
|
1131
|
+
const [libraryOpen, setLibraryOpen] = useState(false);
|
|
1132
|
+
const [queueConversation, setQueueConversation] = useState();
|
|
1133
|
+
const [queueMode, setQueueMode] = useState(false);
|
|
1134
|
+
const [queueReady, setQueueReady] = useState(false);
|
|
1135
|
+
const queueRef = useRef(null);
|
|
1136
|
+
const [historyUnavailable, setHistoryUnavailable] = useState(initialSessionHead?.unavailable ?? false);
|
|
178
1137
|
const messagesRef = useRef(null);
|
|
179
1138
|
const fileInputRef = useRef(null);
|
|
180
1139
|
const inputRef = useRef(null);
|
|
@@ -200,6 +1159,9 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
200
1159
|
setSystemBubbles([]);
|
|
201
1160
|
setEditIdx(null);
|
|
202
1161
|
setInput("");
|
|
1162
|
+
setQueueReady(false);
|
|
1163
|
+
setAttachments([]);
|
|
1164
|
+
setHistoryUnavailable(initialSessionHead?.unavailable ?? false);
|
|
203
1165
|
setThinking(false);
|
|
204
1166
|
setStreaming(false);
|
|
205
1167
|
setStaleBanner(false);
|
|
@@ -259,7 +1221,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
259
1221
|
function onVis() {
|
|
260
1222
|
if (document.visibilityState === "visible" && sessionStorage.getItem(key)) {
|
|
261
1223
|
sessionStorage.removeItem(key);
|
|
262
|
-
|
|
1224
|
+
setStaleBanner(true);
|
|
263
1225
|
}
|
|
264
1226
|
}
|
|
265
1227
|
document.addEventListener("visibilitychange", onVis);
|
|
@@ -277,7 +1239,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
277
1239
|
const body = await res.json();
|
|
278
1240
|
if (typeof body.size === "number") {
|
|
279
1241
|
const known = knownHeadRef.current;
|
|
280
|
-
if (body.file !== known.file || body.size !== known.size) setStaleBanner(true);
|
|
1242
|
+
if (body.unavailable !== known.unavailable || body.file !== known.file || body.size !== known.size) setStaleBanner(true);
|
|
281
1243
|
}
|
|
282
1244
|
}
|
|
283
1245
|
} catch {}
|
|
@@ -293,16 +1255,38 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
293
1255
|
initialSessionHead,
|
|
294
1256
|
staleBanner
|
|
295
1257
|
]);
|
|
1258
|
+
async function refreshConversationHistory() {
|
|
1259
|
+
if (streamingRef.current) return;
|
|
1260
|
+
try {
|
|
1261
|
+
const response = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/messages`);
|
|
1262
|
+
if (!response.ok) throw new Error("Conversation history is unavailable. Your draft is preserved.");
|
|
1263
|
+
const body = await response.json();
|
|
1264
|
+
if (currentAgentIdRef.current !== agentId) return;
|
|
1265
|
+
setHistoryUnavailable(body.head.unavailable ?? false);
|
|
1266
|
+
setServerMessages(body.messages);
|
|
1267
|
+
setLiveEntries([]);
|
|
1268
|
+
setEditIdx(null);
|
|
1269
|
+
knownHeadRef.current = body.head;
|
|
1270
|
+
setStaleBanner(false);
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
pushSystem(error instanceof Error ? error.message : "Could not refresh conversation");
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
296
1275
|
async function refreshKnownHead() {
|
|
297
1276
|
if (currentAgentIdRef.current !== agentId) return;
|
|
298
1277
|
try {
|
|
299
1278
|
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
|
|
300
1279
|
if (!res.ok) return;
|
|
301
1280
|
const body = await res.json();
|
|
302
|
-
if (currentAgentIdRef.current === agentId && typeof body.size === "number")
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1281
|
+
if (currentAgentIdRef.current === agentId && typeof body.size === "number") {
|
|
1282
|
+
const prior = knownHeadRef.current.selection;
|
|
1283
|
+
if (prior?.conversationId && prior.conversationId !== body.selection?.conversationId) {
|
|
1284
|
+
setStaleBanner(true);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
setHistoryUnavailable(body.unavailable ?? false);
|
|
1288
|
+
knownHeadRef.current = body;
|
|
1289
|
+
}
|
|
306
1290
|
} catch {}
|
|
307
1291
|
}
|
|
308
1292
|
function pushSystem(text) {
|
|
@@ -347,28 +1331,6 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
347
1331
|
pushSystem(`/context failed: ${err.message}`);
|
|
348
1332
|
}
|
|
349
1333
|
}
|
|
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
1334
|
async function runCompactCommand(rest) {
|
|
373
1335
|
if (serverMessages.length < 2) {
|
|
374
1336
|
pushSystem("/compact: need ≥2 messages to compact");
|
|
@@ -387,7 +1349,10 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
387
1349
|
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/compact`, {
|
|
388
1350
|
method: "POST",
|
|
389
1351
|
headers: { "content-type": "application/json" },
|
|
390
|
-
body: JSON.stringify(
|
|
1352
|
+
body: JSON.stringify({
|
|
1353
|
+
keepTail,
|
|
1354
|
+
expectedSelection: knownHeadRef.current.selection
|
|
1355
|
+
})
|
|
391
1356
|
});
|
|
392
1357
|
if (!res.ok) {
|
|
393
1358
|
let msg = res.statusText;
|
|
@@ -420,8 +1385,8 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
420
1385
|
case "/context":
|
|
421
1386
|
await runContextCommand();
|
|
422
1387
|
return true;
|
|
423
|
-
case "/
|
|
424
|
-
|
|
1388
|
+
case "/new":
|
|
1389
|
+
setLibraryOpen(true);
|
|
425
1390
|
return true;
|
|
426
1391
|
case "/compact":
|
|
427
1392
|
await runCompactCommand(rest);
|
|
@@ -447,7 +1412,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
447
1412
|
setEditIdx(null);
|
|
448
1413
|
}
|
|
449
1414
|
async function addFiles(files) {
|
|
450
|
-
if (!files
|
|
1415
|
+
if (!files) return;
|
|
451
1416
|
const arr = Array.from(files);
|
|
452
1417
|
if (arr.length === 0) return;
|
|
453
1418
|
const encoded = await Promise.all(arr.map(fileToAttachment));
|
|
@@ -463,10 +1428,6 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
463
1428
|
function onDragOver(e) {
|
|
464
1429
|
if (!Array.from(e.dataTransfer.types).includes("Files")) return;
|
|
465
1430
|
e.preventDefault();
|
|
466
|
-
if (turnBusy) {
|
|
467
|
-
setDragging(false);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
1431
|
setDragging(true);
|
|
471
1432
|
}
|
|
472
1433
|
function onDragLeave(e) {
|
|
@@ -476,12 +1437,22 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
476
1437
|
if (e.dataTransfer.files.length === 0) return;
|
|
477
1438
|
e.preventDefault();
|
|
478
1439
|
setDragging(false);
|
|
479
|
-
if (turnBusy) return;
|
|
480
1440
|
addFiles(e.dataTransfer.files);
|
|
481
1441
|
}
|
|
482
1442
|
const send = useCallback(async (text) => {
|
|
483
1443
|
const atts = attachments;
|
|
484
|
-
if (!text.trim() && atts.length === 0
|
|
1444
|
+
if (!text.trim() && atts.length === 0) return;
|
|
1445
|
+
if (!queueReady) {
|
|
1446
|
+
pushSystem("Queue status is loading. Your draft is preserved.");
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
if (turnBusy || queueMode) {
|
|
1450
|
+
if (await queueRef.current?.enqueue(text, atts, knownHeadRef.current.selection) && currentAgentIdRef.current === agentId) {
|
|
1451
|
+
setInput((current) => current === text ? "" : current);
|
|
1452
|
+
setAttachments((current) => current === atts ? [] : current);
|
|
1453
|
+
}
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
485
1456
|
setInput("");
|
|
486
1457
|
if (await maybeHandleSlashCommand(text)) return;
|
|
487
1458
|
setAttachments([]);
|
|
@@ -492,7 +1463,10 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
492
1463
|
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat/truncate`, {
|
|
493
1464
|
method: "POST",
|
|
494
1465
|
headers: { "content-type": "application/json" },
|
|
495
|
-
body: JSON.stringify({
|
|
1466
|
+
body: JSON.stringify({
|
|
1467
|
+
keepCount: keep,
|
|
1468
|
+
expectedSelection: knownHeadRef.current.selection
|
|
1469
|
+
})
|
|
496
1470
|
});
|
|
497
1471
|
if (!res.ok) {
|
|
498
1472
|
let err = res.statusText;
|
|
@@ -535,7 +1509,10 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
535
1509
|
const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat`, {
|
|
536
1510
|
method: "POST",
|
|
537
1511
|
headers: { "content-type": "application/json" },
|
|
538
|
-
body: JSON.stringify(
|
|
1512
|
+
body: JSON.stringify({
|
|
1513
|
+
...interactiveChatRequest(text, atts),
|
|
1514
|
+
expectedSelection: knownHeadRef.current.selection
|
|
1515
|
+
}),
|
|
539
1516
|
signal: abort.signal
|
|
540
1517
|
});
|
|
541
1518
|
if (res.status === 202) {
|
|
@@ -547,6 +1524,15 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
547
1524
|
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
548
1525
|
return;
|
|
549
1526
|
}
|
|
1527
|
+
if (res.status === 409) {
|
|
1528
|
+
setInput(text);
|
|
1529
|
+
setAttachments(atts);
|
|
1530
|
+
setLiveEntries([]);
|
|
1531
|
+
setStaleBanner(true);
|
|
1532
|
+
sessionStorage.removeItem(`bz_pending_${agentId}`);
|
|
1533
|
+
pushSystem("Conversation changed. Refresh history, then review and send your preserved draft.");
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
550
1536
|
if (!res.ok || !res.body) {
|
|
551
1537
|
let err = res.statusText;
|
|
552
1538
|
try {
|
|
@@ -558,6 +1544,14 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
558
1544
|
}]);
|
|
559
1545
|
return;
|
|
560
1546
|
}
|
|
1547
|
+
const admittedSelection = res.headers.get("x-bazilion-conversation-selection");
|
|
1548
|
+
if (admittedSelection) try {
|
|
1549
|
+
const selection = JSON.parse(admittedSelection);
|
|
1550
|
+
if (typeof selection.conversationId === "string" && Number.isSafeInteger(selection.revision) && selection.revision >= 0) knownHeadRef.current = {
|
|
1551
|
+
...knownHeadRef.current,
|
|
1552
|
+
selection
|
|
1553
|
+
};
|
|
1554
|
+
} catch {}
|
|
561
1555
|
const reader = res.body.getReader();
|
|
562
1556
|
const decoder = new TextDecoder();
|
|
563
1557
|
let buffer = "";
|
|
@@ -605,6 +1599,8 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
605
1599
|
editIdx,
|
|
606
1600
|
serverMessages,
|
|
607
1601
|
turnBusy,
|
|
1602
|
+
queueMode,
|
|
1603
|
+
queueReady,
|
|
608
1604
|
attachments
|
|
609
1605
|
]);
|
|
610
1606
|
function handleFrame(frame) {
|
|
@@ -716,6 +1712,14 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
716
1712
|
return;
|
|
717
1713
|
}
|
|
718
1714
|
if (ev.type === "file") {
|
|
1715
|
+
if (ev.result) {
|
|
1716
|
+
const resultId = ev.result.resultId;
|
|
1717
|
+
setLiveEntries((prev) => [...prev, {
|
|
1718
|
+
type: "result",
|
|
1719
|
+
resultId
|
|
1720
|
+
}]);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
719
1723
|
setLiveEntries((prev) => [...prev, {
|
|
720
1724
|
type: "file",
|
|
721
1725
|
name: ev.name,
|
|
@@ -790,31 +1794,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
790
1794
|
}
|
|
791
1795
|
}
|
|
792
1796
|
async function cancel() {
|
|
793
|
-
|
|
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
|
-
}]);
|
|
1797
|
+
if (!await queueRef.current?.stop() && currentAgentIdRef.current === agentId) pushSystem("Stop was not confirmed. Check queue controls and try again; the active response may still be running.");
|
|
818
1798
|
}
|
|
819
1799
|
function onKeyDown(e) {
|
|
820
1800
|
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
@@ -866,6 +1846,20 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
866
1846
|
children: "Manage agent →"
|
|
867
1847
|
})]
|
|
868
1848
|
}),
|
|
1849
|
+
/* @__PURE__ */ jsx("div", {
|
|
1850
|
+
className: "mx-5 mt-2",
|
|
1851
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
1852
|
+
variant: "ghost",
|
|
1853
|
+
"aria-expanded": libraryOpen,
|
|
1854
|
+
onClick: () => setLibraryOpen((value) => !value),
|
|
1855
|
+
children: libraryOpen ? "Close conversations" : "Conversations"
|
|
1856
|
+
})
|
|
1857
|
+
}),
|
|
1858
|
+
historyUnavailable && /* @__PURE__ */ jsx("p", {
|
|
1859
|
+
role: "alert",
|
|
1860
|
+
className: "mx-5 mt-2 text-sm",
|
|
1861
|
+
children: "Selected conversation history is unavailable. Open Conversations to start a new one; retained history and files stay listed."
|
|
1862
|
+
}),
|
|
869
1863
|
staleBanner && /* @__PURE__ */ jsxs("div", {
|
|
870
1864
|
role: "status",
|
|
871
1865
|
"aria-live": "polite",
|
|
@@ -882,7 +1876,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
882
1876
|
}),
|
|
883
1877
|
/* @__PURE__ */ jsx("button", {
|
|
884
1878
|
type: "button",
|
|
885
|
-
onClick: () =>
|
|
1879
|
+
onClick: () => void refreshConversationHistory(),
|
|
886
1880
|
className: "rounded-sm bg-sapphire px-3 py-1 text-[0.92em] text-snow hover:opacity-90",
|
|
887
1881
|
children: "reload"
|
|
888
1882
|
}),
|
|
@@ -903,9 +1897,16 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
903
1897
|
"aria-live": "polite",
|
|
904
1898
|
"aria-relevant": "additions",
|
|
905
1899
|
"aria-busy": turnBusy,
|
|
906
|
-
className: `min-h-
|
|
1900
|
+
className: `min-h-0 flex-1 overflow-y-auto px-5 py-5 ${editIdx !== null ? "is-editing" : ""}`,
|
|
907
1901
|
children: [
|
|
908
|
-
|
|
1902
|
+
libraryOpen && /* @__PURE__ */ jsx(ConversationLibrary, {
|
|
1903
|
+
initialConversationId: queueConversation,
|
|
1904
|
+
agentId,
|
|
1905
|
+
turnBusy,
|
|
1906
|
+
onCreated: refreshConversationHistory,
|
|
1907
|
+
renderHistory: (messages) => /* @__PURE__ */ jsx(ResultTranscript, { messages })
|
|
1908
|
+
}, agentId),
|
|
1909
|
+
!historyUnavailable && baseEntries.length === 0 && liveEntries.length === 0 && systemBubbles.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
909
1910
|
className: "py-12 text-center italic text-mocha-light",
|
|
910
1911
|
children: "Start a conversation…"
|
|
911
1912
|
}),
|
|
@@ -994,7 +1995,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
994
1995
|
})]
|
|
995
1996
|
}),
|
|
996
1997
|
attachments.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
997
|
-
className: "flex flex-wrap gap-2 border-t border-frost bg-ivory px-5 pt-3",
|
|
1998
|
+
className: "flex max-h-24 shrink-0 flex-wrap gap-2 overflow-y-auto border-t border-frost bg-ivory px-5 pt-3",
|
|
998
1999
|
children: attachments.map((a, i) => {
|
|
999
2000
|
const remove = () => setAttachments((prev) => prev.filter((_, j) => j !== i));
|
|
1000
2001
|
return isImageMime(a.mimeType) ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -1029,8 +2030,22 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
1029
2030
|
}, `${a.name ?? "file"}-${i}`);
|
|
1030
2031
|
})
|
|
1031
2032
|
}),
|
|
2033
|
+
/* @__PURE__ */ jsx(UserQueuePanel, {
|
|
2034
|
+
onViewConversation: (id) => {
|
|
2035
|
+
setQueueConversation(id);
|
|
2036
|
+
setLibraryOpen(true);
|
|
2037
|
+
},
|
|
2038
|
+
ref: queueRef,
|
|
2039
|
+
agentId,
|
|
2040
|
+
selection: () => knownHeadRef.current.selection,
|
|
2041
|
+
onQueueMode: (mode) => {
|
|
2042
|
+
setQueueMode(mode);
|
|
2043
|
+
setQueueReady(true);
|
|
2044
|
+
}
|
|
2045
|
+
}, agentId),
|
|
2046
|
+
/* @__PURE__ */ jsx(AgentQuestions, { agentId }, `questions-${agentId}`),
|
|
1032
2047
|
/* @__PURE__ */ jsxs("form", {
|
|
1033
|
-
className: "grid grid-cols-[auto_minmax(0,1fr)
|
|
2048
|
+
className: "grid shrink-0 grid-cols-[auto_minmax(0,1fr)] items-end gap-2 border-t border-frost bg-ivory px-3 py-3 sm:grid-cols-[auto_minmax(0,1fr)_auto] sm:px-5",
|
|
1034
2049
|
onSubmit: (e) => {
|
|
1035
2050
|
e.preventDefault();
|
|
1036
2051
|
send(input);
|
|
@@ -1049,10 +2064,9 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
1049
2064
|
/* @__PURE__ */ jsx("button", {
|
|
1050
2065
|
type: "button",
|
|
1051
2066
|
onClick: () => fileInputRef.current?.click(),
|
|
1052
|
-
disabled: turnBusy,
|
|
1053
2067
|
title: "attach images or files",
|
|
1054
2068
|
"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",
|
|
2069
|
+
className: "flex h-11 items-center justify-center 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
2070
|
children: "📎"
|
|
1057
2071
|
}),
|
|
1058
2072
|
/* @__PURE__ */ jsx("textarea", {
|
|
@@ -1062,40 +2076,29 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
|
|
|
1062
2076
|
onChange: (e) => setInput(e.target.value),
|
|
1063
2077
|
onKeyDown,
|
|
1064
2078
|
onPaste,
|
|
1065
|
-
|
|
1066
|
-
|
|
2079
|
+
placeholder: "Write a message…",
|
|
2080
|
+
title: "Shift+Enter for a new line. Paste or attach files with the paperclip.",
|
|
1067
2081
|
autoComplete: "off",
|
|
1068
2082
|
"aria-label": `Message ${agentName}`,
|
|
1069
|
-
className: "max-h-[200px] min-h-
|
|
2083
|
+
className: "max-h-[200px] min-h-11 min-w-0 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
2084
|
}),
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
2085
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2086
|
+
className: "col-span-2 flex flex-wrap justify-end gap-2 sm:col-span-1",
|
|
2087
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
2088
|
+
variant: "primary",
|
|
2089
|
+
type: "submit",
|
|
2090
|
+
className: "min-h-11 whitespace-normal text-center",
|
|
2091
|
+
disabled: !input.trim() && attachments.length === 0,
|
|
2092
|
+
children: turnBusy || queueMode ? "Queue follow-up" : "Send"
|
|
2093
|
+
}), turnBusy && /* @__PURE__ */ jsx(Button, {
|
|
2094
|
+
variant: "danger",
|
|
2095
|
+
className: "min-h-11",
|
|
2096
|
+
onClick: () => void cancel(),
|
|
2097
|
+
"aria-label": "Stop response and pause queue",
|
|
2098
|
+
children: "Stop"
|
|
2099
|
+
})]
|
|
1082
2100
|
})
|
|
1083
2101
|
]
|
|
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
2102
|
})
|
|
1100
2103
|
]
|
|
1101
2104
|
});
|
|
@@ -1202,6 +2205,7 @@ function Bubble({ entry, markdownReady, isLastUser, isWillDrop, onEdit, onComman
|
|
|
1202
2205
|
className: "block max-w-full rounded-lg border border-fawn"
|
|
1203
2206
|
}, i))
|
|
1204
2207
|
});
|
|
2208
|
+
if (entry.type === "result") return /* @__PURE__ */ jsx(ResultCard, { resultId: entry.resultId });
|
|
1205
2209
|
if (entry.type === "file") {
|
|
1206
2210
|
const isImage = entry.mimeType.startsWith("image/");
|
|
1207
2211
|
const href = `data:${entry.mimeType};base64,${entry.data}`;
|
|
@@ -1425,5 +2429,16 @@ function Dot({ delay = "0s" }) {
|
|
|
1425
2429
|
"aria-hidden": "true"
|
|
1426
2430
|
});
|
|
1427
2431
|
}
|
|
2432
|
+
/** Read-only projection used when opening the exact available source of a saved result. */
|
|
2433
|
+
function ResultTranscript({ messages }) {
|
|
2434
|
+
const markdownReady = useSyncExternalStore(subscribeToHydration, browserMarkdownReady, serverMarkdownReady);
|
|
2435
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2436
|
+
"aria-label": "Source conversation",
|
|
2437
|
+
children: projectMessages(messages).map((entry, index) => /* @__PURE__ */ jsx(Bubble, {
|
|
2438
|
+
entry,
|
|
2439
|
+
markdownReady
|
|
2440
|
+
}, index))
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
1428
2443
|
//#endregion
|
|
1429
|
-
export { ChatPane as t };
|
|
2444
|
+
export { ResultTranscript as n, ChatPane as t };
|