@volter-ai-dev/supercode-ui 0.1.11 → 0.1.13
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/components.mjs +116 -39
- package/composer.d.ts +1 -1
- package/composer.mjs +25 -7
- package/conversation.d.ts +1 -0
- package/conversation.mjs +11 -4
- package/embed.mjs +116 -39
- package/index.d.ts +10 -3
- package/messenger.mjs +116 -39
- package/package.json +1 -1
- package/sessions.mjs +11 -5
- package/styles.css +3 -0
package/embed.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { render } from "preact";
|
|
3
3
|
|
|
4
4
|
// src/messenger.jsx
|
|
5
|
-
import { useEffect as
|
|
5
|
+
import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "preact/hooks";
|
|
6
6
|
|
|
7
7
|
// core.mjs
|
|
8
8
|
var HARNESS_NAMES = Object.freeze({
|
|
@@ -593,10 +593,11 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
593
593
|
/* @__PURE__ */ jsx("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
|
|
594
594
|
] });
|
|
595
595
|
}
|
|
596
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, onPending }) {
|
|
596
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
|
|
597
597
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, queue: [] };
|
|
598
598
|
const [draft, setDraft] = useState(remembered.draft);
|
|
599
599
|
const [queue, setQueue] = useState(remembered.queue);
|
|
600
|
+
const [dispatching, setDispatching] = useState(false);
|
|
600
601
|
const textarea = useRef(null);
|
|
601
602
|
const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
|
|
602
603
|
const updateQueue = (update) => setQueue((items) => {
|
|
@@ -604,15 +605,31 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
604
605
|
remember(draft, next);
|
|
605
606
|
return next;
|
|
606
607
|
});
|
|
608
|
+
const queueBlocked = state.busy || pendingStatus !== null || dispatching;
|
|
609
|
+
const queuesNewMessage = state.busy || pendingStatus === "sending" || pendingStatus === "failed" || dispatching;
|
|
607
610
|
useEffect(() => {
|
|
608
|
-
if (!
|
|
611
|
+
if (!queueBlocked && state.canSend && queue.length) {
|
|
609
612
|
const [next, ...rest] = queue;
|
|
613
|
+
setDispatching(true);
|
|
610
614
|
setQueue(rest);
|
|
611
615
|
remember(draft, rest);
|
|
612
616
|
onPending?.(next);
|
|
613
617
|
adapter.onIntent({ action: "send", text: next });
|
|
614
618
|
}
|
|
615
|
-
}, [adapter, draft, memoryKey, onPending, queue,
|
|
619
|
+
}, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
|
|
620
|
+
useEffect(() => {
|
|
621
|
+
if (pendingStatus !== null || state.busy) setDispatching(false);
|
|
622
|
+
}, [pendingStatus, state.busy]);
|
|
623
|
+
useEffect(() => {
|
|
624
|
+
textarea.current?.focus({ preventScroll: true });
|
|
625
|
+
}, [memoryKey]);
|
|
626
|
+
useEffect(() => {
|
|
627
|
+
if (!restoreDraft) return;
|
|
628
|
+
setDraft(restoreDraft.text);
|
|
629
|
+
remember(restoreDraft.text, queue);
|
|
630
|
+
textarea.current?.focus({ preventScroll: true });
|
|
631
|
+
onDraftRestored?.(restoreDraft.id);
|
|
632
|
+
}, [onDraftRestored, restoreDraft?.id]);
|
|
616
633
|
useEffect(() => {
|
|
617
634
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
618
635
|
return () => clearTimeout(timer);
|
|
@@ -620,13 +637,14 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
620
637
|
const send = () => {
|
|
621
638
|
const text = draft.trim();
|
|
622
639
|
if (!text) return;
|
|
623
|
-
if (
|
|
640
|
+
if (queuesNewMessage) updateQueue((items) => [...items, text]);
|
|
624
641
|
else if (state.canSend) {
|
|
642
|
+
if (onPending) setDispatching(true);
|
|
625
643
|
onPending?.(text);
|
|
626
644
|
adapter.onIntent({ action: "send", text });
|
|
627
645
|
} else return;
|
|
628
646
|
setDraft("");
|
|
629
|
-
remember("",
|
|
647
|
+
remember("", queuesNewMessage ? [...queue, text] : queue);
|
|
630
648
|
};
|
|
631
649
|
return /* @__PURE__ */ jsxs("div", { class: "scui-compose", children: [
|
|
632
650
|
queue.length ? /* @__PURE__ */ jsxs("div", { class: "scui-queue", children: [
|
|
@@ -640,7 +658,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
640
658
|
] }, `${index}:${item}`))
|
|
641
659
|
] }) : null,
|
|
642
660
|
/* @__PURE__ */ jsxs("div", { class: "scui-envelope", children: [
|
|
643
|
-
/* @__PURE__ */ jsx("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" :
|
|
661
|
+
/* @__PURE__ */ jsx("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
|
|
644
662
|
const value = event.currentTarget.value;
|
|
645
663
|
setDraft(value);
|
|
646
664
|
remember(value, queue);
|
|
@@ -652,7 +670,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
652
670
|
} }),
|
|
653
671
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
654
672
|
state.busy ? /* @__PURE__ */ jsx("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: "\u25A0" }) : null,
|
|
655
|
-
/* @__PURE__ */ jsx("button", { class: "scui-send", type: "button", "aria-label":
|
|
673
|
+
/* @__PURE__ */ jsx("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: queuesNewMessage ? "+" : "\u2191" })
|
|
656
674
|
] })
|
|
657
675
|
] })
|
|
658
676
|
] });
|
|
@@ -1074,6 +1092,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1074
1092
|
const After = slots.afterConversation;
|
|
1075
1093
|
const Empty = slots.emptyConversation;
|
|
1076
1094
|
const remember = (value) => boundedSet(conversationMemory, memoryKey, value);
|
|
1095
|
+
const pendingMessage = typeof pending === "string" ? { text: pending, status: "sending" } : pending;
|
|
1077
1096
|
const pin = () => {
|
|
1078
1097
|
if (!scroller.current) return;
|
|
1079
1098
|
scroller.current.scrollTop = scroller.current.scrollHeight;
|
|
@@ -1095,7 +1114,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1095
1114
|
if (remembered.top !== null && !remembered.atBottom) element.scrollTop = remembered.top;
|
|
1096
1115
|
else pin();
|
|
1097
1116
|
} else if (atBottom) pin();
|
|
1098
|
-
}, [memoryKey, state.transcript.
|
|
1117
|
+
}, [memoryKey, state.transcript, state.busy, state.operation, pendingMessage?.text, pendingMessage?.status]);
|
|
1099
1118
|
return /* @__PURE__ */ jsxs2("div", { class: "scui-conversation-wrap", children: [
|
|
1100
1119
|
/* @__PURE__ */ jsx3("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
|
|
1101
1120
|
const element = event.currentTarget;
|
|
@@ -1115,9 +1134,15 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
|
|
|
1115
1134
|
!blocks.length && state.startup !== "ready" ? /* @__PURE__ */ jsx3(LoadingStatus, { state }) : null,
|
|
1116
1135
|
!blocks.length && state.startup === "ready" ? Empty ? /* @__PURE__ */ jsx3(Empty, { state, adapter, value: null }) : /* @__PURE__ */ jsx3("div", { class: "scui-empty", children: state.error ?? (state.harness ? `${harnessDisplayName(state.harness)} is listening. Say something.` : "No transcript yet.") }) : null,
|
|
1117
1136
|
blocks.map((block) => block.kind === "activity" ? /* @__PURE__ */ jsx3(Group, { value: block.entries, entries: block.entries, state, adapter }, block.id) : /* @__PURE__ */ jsx3(Entry, { value: block.entry, entry: block.entry, state, adapter }, block.id)),
|
|
1118
|
-
|
|
1119
|
-
/* @__PURE__ */ jsx3(Markdown, { value:
|
|
1120
|
-
/* @__PURE__ */
|
|
1137
|
+
pendingMessage ? /* @__PURE__ */ jsxs2("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, children: [
|
|
1138
|
+
/* @__PURE__ */ jsx3(Markdown, { value: pendingMessage.text }),
|
|
1139
|
+
/* @__PURE__ */ jsxs2("footer", { children: [
|
|
1140
|
+
/* @__PURE__ */ jsx3("small", { children: pendingMessage.status === "failed" ? "Not sent" : "Sending\u2026" }),
|
|
1141
|
+
pendingMessage.status === "failed" ? /* @__PURE__ */ jsxs2("span", { children: [
|
|
1142
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: pendingMessage.onRetry, children: "Retry" }),
|
|
1143
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: pendingMessage.onEdit, children: "Edit" })
|
|
1144
|
+
] }) : null
|
|
1145
|
+
] })
|
|
1121
1146
|
] }) : null,
|
|
1122
1147
|
state.busy ? /* @__PURE__ */ jsxs2("div", { class: "scui-working", role: "status", children: [
|
|
1123
1148
|
/* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u2726" }),
|
|
@@ -1195,12 +1220,12 @@ function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
|
|
|
1195
1220
|
}
|
|
1196
1221
|
|
|
1197
1222
|
// src/sessions.jsx
|
|
1198
|
-
import { useState as useState3 } from "preact/hooks";
|
|
1223
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState3 } from "preact/hooks";
|
|
1199
1224
|
import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
|
|
1200
1225
|
function SessionRow({ row, state, onOpen }) {
|
|
1201
1226
|
const activity = sessionActivity(state, row);
|
|
1202
1227
|
const preview = state.attention.find((item) => item.key === row.key)?.preview;
|
|
1203
|
-
return /* @__PURE__ */ jsxs4("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, type: "button", onClick: () => onOpen(row), children: [
|
|
1228
|
+
return /* @__PURE__ */ jsxs4("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", onClick: () => onOpen(row), children: [
|
|
1204
1229
|
/* @__PURE__ */ jsx5(HarnessLogo, { id: row.harness, activity, size: 34 }),
|
|
1205
1230
|
/* @__PURE__ */ jsxs4("span", { class: "scui-session-copy", children: [
|
|
1206
1231
|
/* @__PURE__ */ jsxs4("span", { children: [
|
|
@@ -1215,11 +1240,17 @@ function SessionRow({ row, state, onOpen }) {
|
|
|
1215
1240
|
] })
|
|
1216
1241
|
] });
|
|
1217
1242
|
}
|
|
1218
|
-
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS }) {
|
|
1243
|
+
function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null }) {
|
|
1219
1244
|
const [query, setQuery] = useState3("");
|
|
1245
|
+
const root = useRef3(null);
|
|
1220
1246
|
const rows = filterSessions(state.sessions, query);
|
|
1221
1247
|
const Row = components.SessionRow ?? SessionRow;
|
|
1222
|
-
|
|
1248
|
+
useEffect4(() => {
|
|
1249
|
+
if (!focusKey || !root.current) return;
|
|
1250
|
+
const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
|
|
1251
|
+
(target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
|
|
1252
|
+
}, [focusKey, rows.length]);
|
|
1253
|
+
return /* @__PURE__ */ jsxs4("section", { class: "scui-list", ref: root, children: [
|
|
1223
1254
|
/* @__PURE__ */ jsxs4("header", { class: "scui-head", children: [
|
|
1224
1255
|
/* @__PURE__ */ jsxs4("span", { class: "scui-head-copy", children: [
|
|
1225
1256
|
/* @__PURE__ */ jsx5("strong", { children: labels.chats }),
|
|
@@ -1228,7 +1259,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
|
|
|
1228
1259
|
" recent conversations"
|
|
1229
1260
|
] })
|
|
1230
1261
|
] }),
|
|
1231
|
-
/* @__PURE__ */ jsx5("button", { type: "button", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: "\uFF0B" }),
|
|
1262
|
+
/* @__PURE__ */ jsx5("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: "\uFF0B" }),
|
|
1232
1263
|
onClose ? /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
|
|
1233
1264
|
] }),
|
|
1234
1265
|
state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state, compact: rows.length > 0 }) : null,
|
|
@@ -1247,6 +1278,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
|
|
|
1247
1278
|
|
|
1248
1279
|
// src/messenger.jsx
|
|
1249
1280
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
1281
|
+
var pendingMessageMemory = /* @__PURE__ */ new Map();
|
|
1250
1282
|
function Receipt({ state, adapter }) {
|
|
1251
1283
|
const receipt = state.reductionReceipt;
|
|
1252
1284
|
if (receipt) return /* @__PURE__ */ jsx6("div", { class: "scui-receipt", children: /* @__PURE__ */ jsxs5("span", { children: [
|
|
@@ -1284,14 +1316,14 @@ function Receipt({ state, adapter }) {
|
|
|
1284
1316
|
] });
|
|
1285
1317
|
return null;
|
|
1286
1318
|
}
|
|
1287
|
-
function ChatHeader({ state, adapter, onBack, onNew, onClose }) {
|
|
1319
|
+
function ChatHeader({ state, adapter, pendingStatus, onBack, onNew, onClose }) {
|
|
1288
1320
|
const harness = state.attached?.harness ?? state.harness;
|
|
1289
1321
|
const targets = state.harnesses.filter((item) => item.startable);
|
|
1290
1322
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
1291
|
-
const status = state.needsInput ? "Needs input" : state.busy ? "Working" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
1323
|
+
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
1292
1324
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce;
|
|
1293
1325
|
return /* @__PURE__ */ jsxs5("header", { class: "scui-head scui-chat-head", children: [
|
|
1294
|
-
/* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Back to chats", onClick: onBack, children: "\u2039" }),
|
|
1326
|
+
/* @__PURE__ */ jsx6("button", { type: "button", autofocus: state.mode === "mirror" && !state.canSend, "aria-label": "Back to chats", onClick: onBack, children: "\u2039" }),
|
|
1295
1327
|
/* @__PURE__ */ jsx6(HarnessLogo, { id: harness, size: 28 }),
|
|
1296
1328
|
/* @__PURE__ */ jsxs5("span", { class: "scui-head-copy", children: [
|
|
1297
1329
|
/* @__PURE__ */ jsx6("strong", { children: title }),
|
|
@@ -1321,15 +1353,48 @@ function ChatHeader({ state, adapter, onBack, onNew, onClose }) {
|
|
|
1321
1353
|
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels }) {
|
|
1322
1354
|
const Header = slots.header;
|
|
1323
1355
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
1324
|
-
const [pending,
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1356
|
+
const [pending, setPendingState] = useState4(() => pendingMessageMemory.get(memoryKey) ?? null);
|
|
1357
|
+
const [restoreDraft, setRestoreDraft] = useState4(null);
|
|
1358
|
+
const restoreSequence = useRef4(0);
|
|
1359
|
+
const acknowledged = useRef4(/* @__PURE__ */ new Set());
|
|
1360
|
+
const setPending = (update) => setPendingState((current) => {
|
|
1361
|
+
const next = typeof update === "function" ? update(current) : update;
|
|
1362
|
+
if (next) boundedSet(pendingMessageMemory, memoryKey, next);
|
|
1363
|
+
else pendingMessageMemory.delete(memoryKey);
|
|
1364
|
+
return next;
|
|
1365
|
+
});
|
|
1366
|
+
useEffect5(() => {
|
|
1367
|
+
setPendingState(pendingMessageMemory.get(memoryKey) ?? null);
|
|
1368
|
+
setRestoreDraft(null);
|
|
1328
1369
|
}, [memoryKey]);
|
|
1329
|
-
|
|
1330
|
-
if (pending
|
|
1331
|
-
|
|
1332
|
-
|
|
1370
|
+
useEffect5(() => {
|
|
1371
|
+
if (!pending) return;
|
|
1372
|
+
if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
|
|
1373
|
+
setPending(null);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (pending.status === "sending" && state.busy && !pending.seenBusy) setPending({ ...pending, seenBusy: true });
|
|
1377
|
+
else if (pending.status === "sending" && state.error && !state.busy && !state.operation && (pending.seenBusy || state.error !== pending.initialError)) setPending({ ...pending, status: "failed" });
|
|
1378
|
+
}, [pending, state.busy, state.error, state.operation, state.transcript]);
|
|
1379
|
+
const beginPending = (text) => setPending({
|
|
1380
|
+
text,
|
|
1381
|
+
status: "sending",
|
|
1382
|
+
initialError: state.error,
|
|
1383
|
+
seenBusy: state.busy,
|
|
1384
|
+
baselineIds: state.transcript.filter((entry) => entry.role === "user" && entry.text.trim() === text.trim()).map((entry) => entry.id)
|
|
1385
|
+
});
|
|
1386
|
+
const retryPending = () => {
|
|
1387
|
+
if (!pending) return;
|
|
1388
|
+
adapter.onIntent({ action: "send", text: pending.text });
|
|
1389
|
+
setPending({ ...pending, status: "sending", initialError: state.error, seenBusy: state.busy });
|
|
1390
|
+
};
|
|
1391
|
+
const editPending = () => {
|
|
1392
|
+
if (!pending) return;
|
|
1393
|
+
restoreSequence.current += 1;
|
|
1394
|
+
setRestoreDraft({ id: restoreSequence.current, text: pending.text });
|
|
1395
|
+
setPending({ ...pending, status: "editing" });
|
|
1396
|
+
};
|
|
1397
|
+
useEffect5(() => {
|
|
1333
1398
|
const key = state.attached?.key;
|
|
1334
1399
|
if (!key || !state.attention.some((item) => item.key === key)) {
|
|
1335
1400
|
if (key) acknowledged.current.delete(key);
|
|
@@ -1340,8 +1405,9 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
1340
1405
|
adapter.onIntent({ action: "ack", key });
|
|
1341
1406
|
}
|
|
1342
1407
|
}, [adapter, state.attached?.key, state.attention]);
|
|
1408
|
+
const pendingMessage = pending && pending.status !== "editing" ? { text: pending.text, status: pending.status, onRetry: retryPending, onEdit: editPending } : null;
|
|
1343
1409
|
return /* @__PURE__ */ jsxs5("section", { class: "scui-chat", children: [
|
|
1344
|
-
Header ? /* @__PURE__ */ jsx6(Header, { state, adapter, value: null }) : /* @__PURE__ */ jsx6(ChatHeader, { state, adapter, onBack, onNew, onClose }),
|
|
1410
|
+
Header ? /* @__PURE__ */ jsx6(Header, { state, adapter, value: null }) : /* @__PURE__ */ jsx6(ChatHeader, { state, adapter, pendingStatus: pending?.status ?? null, onBack, onNew, onClose }),
|
|
1345
1411
|
operationLabel(state.operation) ? /* @__PURE__ */ jsxs5("div", { class: "scui-operation", role: "status", children: [
|
|
1346
1412
|
/* @__PURE__ */ jsx6("i", {}),
|
|
1347
1413
|
operationLabel(state.operation)
|
|
@@ -1351,9 +1417,9 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
1351
1417
|
state.recoverable ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => adapter.onIntent({ action: "refresh" }), children: "Retry" }) : null
|
|
1352
1418
|
] }) : null,
|
|
1353
1419
|
/* @__PURE__ */ jsx6(Receipt, { state, adapter }),
|
|
1354
|
-
/* @__PURE__ */ jsx6(Conversation, { state, adapter, components, slots, memoryKey, pending }, memoryKey),
|
|
1420
|
+
/* @__PURE__ */ jsx6(Conversation, { state, adapter, components, slots, memoryKey, pending: pendingMessage }, memoryKey),
|
|
1355
1421
|
/* @__PURE__ */ jsx6(ContinuationBar, { state, adapter, labels }),
|
|
1356
|
-
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx6(Composer, { state, adapter, labels, memoryKey, onPending:
|
|
1422
|
+
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx6(Composer, { state, adapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null
|
|
1357
1423
|
] });
|
|
1358
1424
|
}
|
|
1359
1425
|
function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
|
|
@@ -1362,13 +1428,13 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
|
|
|
1362
1428
|
const [harness, setHarness] = useState4(startable[0]?.id ?? "");
|
|
1363
1429
|
const [draft, setDraft] = useState4("");
|
|
1364
1430
|
const [starting, setStarting] = useState4(false);
|
|
1365
|
-
|
|
1431
|
+
useEffect5(() => {
|
|
1366
1432
|
if (!startable.some((item) => item.id === harness)) setHarness(startable[0]?.id ?? "");
|
|
1367
1433
|
}, [harness, startableKey]);
|
|
1368
|
-
|
|
1434
|
+
useEffect5(() => {
|
|
1369
1435
|
if (starting && (state.busy || state.transcript.length)) onStarted();
|
|
1370
1436
|
}, [onStarted, starting, state.busy, state.transcript.length]);
|
|
1371
|
-
|
|
1437
|
+
useEffect5(() => {
|
|
1372
1438
|
if (starting && state.error && !state.busy && !state.operation) setStarting(false);
|
|
1373
1439
|
}, [starting, state.busy, state.error, state.operation]);
|
|
1374
1440
|
const send = () => {
|
|
@@ -1401,7 +1467,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
|
|
|
1401
1467
|
] }, item.id)) })
|
|
1402
1468
|
] }),
|
|
1403
1469
|
/* @__PURE__ */ jsxs5("div", { class: "scui-envelope", children: [
|
|
1404
|
-
/* @__PURE__ */ jsx6("textarea", { rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || starting, onInput: (event) => setDraft(event.currentTarget.value), onKeyDown: (event) => {
|
|
1470
|
+
/* @__PURE__ */ jsx6("textarea", { autofocus: true, rows: 3, "aria-label": "Message coding agent", placeholder: startable.length ? "What should the agent do?" : "No coding harness is available", value: draft, disabled: !startable.length || starting, onInput: (event) => setDraft(event.currentTarget.value), onKeyDown: (event) => {
|
|
1405
1471
|
if (isSendKey(event)) {
|
|
1406
1472
|
event.preventDefault();
|
|
1407
1473
|
send();
|
|
@@ -1413,11 +1479,12 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels }) {
|
|
|
1413
1479
|
] });
|
|
1414
1480
|
}
|
|
1415
1481
|
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, labels, components = {}, slots = {} }) {
|
|
1416
|
-
const state = normalizeUiState(stateInput);
|
|
1482
|
+
const state = useMemo2(() => normalizeUiState(stateInput), [stateInput]);
|
|
1417
1483
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
1418
1484
|
const [view, setView] = useState4(initialView ?? (state.attention.length ? "list" : state.attached || state.transcript.length ? "chat" : "list"));
|
|
1419
1485
|
const [opening, setOpening] = useState4(null);
|
|
1420
|
-
|
|
1486
|
+
const [listFocus, setListFocus] = useState4(null);
|
|
1487
|
+
useEffect5(() => {
|
|
1421
1488
|
if (!opening) return;
|
|
1422
1489
|
if (state.attached?.key === opening.key) {
|
|
1423
1490
|
setOpening(null);
|
|
@@ -1427,6 +1494,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
1427
1494
|
}
|
|
1428
1495
|
}, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
|
|
1429
1496
|
const open = (row) => {
|
|
1497
|
+
setListFocus(row.key);
|
|
1430
1498
|
setOpening(row);
|
|
1431
1499
|
adapter.onIntent({ action: "ack", key: row.key });
|
|
1432
1500
|
adapter.onIntent({ action: "attach", key: row.key });
|
|
@@ -1434,9 +1502,18 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
1434
1502
|
const close = () => adapter.onClose?.();
|
|
1435
1503
|
const Footer = slots.footer;
|
|
1436
1504
|
return /* @__PURE__ */ jsxs5("main", { class: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
|
|
1437
|
-
view === "list" ? /* @__PURE__ */ jsx6(SessionList, { state, adapter, onOpen: open, onNew: () =>
|
|
1505
|
+
view === "list" ? /* @__PURE__ */ jsx6(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
|
|
1506
|
+
setListFocus("@new");
|
|
1507
|
+
setView("new");
|
|
1508
|
+
}, onClose: adapter.onClose ? close : void 0, components, labels: copy }) : null,
|
|
1438
1509
|
view === "new" ? /* @__PURE__ */ jsx6(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: () => setView("chat"), labels: copy }) : null,
|
|
1439
|
-
view === "chat" ? /* @__PURE__ */ jsx6(Chat, { state, adapter, onBack: () =>
|
|
1510
|
+
view === "chat" ? /* @__PURE__ */ jsx6(Chat, { state, adapter, onBack: () => {
|
|
1511
|
+
setListFocus(state.attached?.key ?? listFocus);
|
|
1512
|
+
setView("list");
|
|
1513
|
+
}, onNew: () => {
|
|
1514
|
+
setListFocus("@new");
|
|
1515
|
+
setView("new");
|
|
1516
|
+
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy }) : null,
|
|
1440
1517
|
opening ? /* @__PURE__ */ jsxs5("div", { class: "scui-opening", role: "status", "aria-busy": "true", children: [
|
|
1441
1518
|
/* @__PURE__ */ jsx6(HarnessLogo, { id: opening.harness, size: 34 }),
|
|
1442
1519
|
/* @__PURE__ */ jsxs5("span", { children: [
|
package/index.d.ts
CHANGED
|
@@ -237,6 +237,13 @@ export interface MessengerLabels {
|
|
|
237
237
|
forkHere: string;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
export interface PendingMessageModel {
|
|
241
|
+
text: string;
|
|
242
|
+
status: 'sending' | 'failed';
|
|
243
|
+
onRetry?(): void;
|
|
244
|
+
onEdit?(): void;
|
|
245
|
+
}
|
|
246
|
+
|
|
240
247
|
export interface ComponentOverrideProps<T = unknown> {
|
|
241
248
|
state: SupercodeUiState;
|
|
242
249
|
adapter: UiAdapter;
|
|
@@ -336,11 +343,11 @@ export function ActivityGroup(props: { entries: TranscriptEntryModel[]; state: S
|
|
|
336
343
|
export function ToolRow(props: ToolRowProps): VNode;
|
|
337
344
|
export function TaskPlan(props: { plan: TaskPlanModel }): VNode | null;
|
|
338
345
|
export function SessionDetails(props: { semantics: SessionSemanticsModel }): VNode | null;
|
|
339
|
-
export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapter; components?: MessengerComponents; slots?: MessengerSlots; memoryKey?: string; pending?: string | null }): VNode;
|
|
346
|
+
export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapter; components?: MessengerComponents; slots?: MessengerSlots; memoryKey?: string; pending?: string | PendingMessageModel | null }): VNode;
|
|
340
347
|
export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void }): VNode;
|
|
341
|
-
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; labels?: MessengerLabels }): VNode;
|
|
348
|
+
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; labels?: MessengerLabels; focusKey?: string | null }): VNode;
|
|
342
349
|
export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): VNode | null;
|
|
343
|
-
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; onPending?(text: string): void }): VNode;
|
|
350
|
+
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string } | null; onPending?(text: string): void; onDraftRestored?(id: string | number): void }): VNode;
|
|
344
351
|
export function SupercodeMessenger(props: MessengerProps): VNode;
|
|
345
352
|
|
|
346
353
|
export interface MountedMessenger {
|