@volter-ai-dev/supercode-ui 0.1.41 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/components.d.ts +1 -0
- package/components.mjs +139 -22
- package/composer.mjs +22 -1
- package/embed.mjs +138 -22
- package/index.d.ts +19 -1
- package/messenger.mjs +138 -22
- package/package.json +1 -1
- package/react/components.mjs +139 -22
- package/react/composer.mjs +22 -1
- package/react/index.d.ts +8 -1
- package/react/messenger.mjs +138 -22
- package/react/settings.d.ts +2 -2
- package/react/settings.mjs +74 -0
- package/settings.d.ts +2 -2
- package/settings.mjs +74 -0
- package/styles.css +6 -1
package/react/components.mjs
CHANGED
|
@@ -1317,7 +1317,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
1317
1317
|
] })
|
|
1318
1318
|
] });
|
|
1319
1319
|
}
|
|
1320
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1320
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, command = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
1321
1321
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
1322
1322
|
const [draft, setDraft] = useState2(remembered.draft);
|
|
1323
1323
|
const [context, setContext] = useState2(remembered.context ?? []);
|
|
@@ -1329,6 +1329,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1329
1329
|
const [dragging, setDragging] = useState2(false);
|
|
1330
1330
|
const [pickerError, setPickerError] = useState2(null);
|
|
1331
1331
|
const textarea = useRef2(null);
|
|
1332
|
+
const lastCommand = useRef2(null);
|
|
1332
1333
|
useAutosizeTextarea(textarea, draft);
|
|
1333
1334
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
1334
1335
|
useEffect2(() => {
|
|
@@ -1370,6 +1371,26 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
1370
1371
|
textarea.current?.focus({ preventScroll: true });
|
|
1371
1372
|
onDraftRestored?.(restoreDraft.id);
|
|
1372
1373
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
1374
|
+
useEffect2(() => {
|
|
1375
|
+
if (!command || command.id === lastCommand.current) return;
|
|
1376
|
+
lastCommand.current = command.id;
|
|
1377
|
+
if (command.action === "attach") {
|
|
1378
|
+
try {
|
|
1379
|
+
const attachments = partitionAttachments(command.attachments);
|
|
1380
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
1381
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
1382
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
1383
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
1384
|
+
setContext(nextContext);
|
|
1385
|
+
setImages(nextImages);
|
|
1386
|
+
setPickerError(null);
|
|
1387
|
+
remember(draft, nextContext, nextImages, queue);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
textarea.current?.focus({ preventScroll: true });
|
|
1393
|
+
}, [command?.id]);
|
|
1373
1394
|
useEffect2(() => {
|
|
1374
1395
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
1375
1396
|
return () => clearTimeout(timer);
|
|
@@ -2477,6 +2498,79 @@ function HarnessReadiness({ harnesses, adapter }) {
|
|
|
2477
2498
|
] }, item.id)) })
|
|
2478
2499
|
] });
|
|
2479
2500
|
}
|
|
2501
|
+
function HarnessPicker({ harnesses, value, disabled = false, adapter, onChange, logo: Logo = HarnessLogo }) {
|
|
2502
|
+
const [open, setOpen] = useState5(false);
|
|
2503
|
+
const menuId = useId2();
|
|
2504
|
+
const root = useRef6(null);
|
|
2505
|
+
const trigger = useRef6(null);
|
|
2506
|
+
const panel = useRef6(null);
|
|
2507
|
+
const available = harnesses.filter((item) => item.startable);
|
|
2508
|
+
const unavailable = harnesses.filter((item) => !item.startable);
|
|
2509
|
+
const selected = available.find((item) => item.id === value) ?? available[0] ?? null;
|
|
2510
|
+
const close = () => {
|
|
2511
|
+
setOpen(false);
|
|
2512
|
+
trigger.current?.focus({ preventScroll: true });
|
|
2513
|
+
};
|
|
2514
|
+
useEffect7(() => {
|
|
2515
|
+
if (!open) return;
|
|
2516
|
+
panel.current?.querySelector('[aria-selected="true"], [role="option"]')?.focus({ preventScroll: true });
|
|
2517
|
+
const dismiss = (event) => {
|
|
2518
|
+
if (event.type === "keydown" && event.key === "Escape") {
|
|
2519
|
+
event.preventDefault();
|
|
2520
|
+
close();
|
|
2521
|
+
} else if (event.type === "pointerdown" && !root.current?.contains(event.target)) {
|
|
2522
|
+
setOpen(false);
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
document.addEventListener("keydown", dismiss);
|
|
2526
|
+
document.addEventListener("pointerdown", dismiss, true);
|
|
2527
|
+
return () => {
|
|
2528
|
+
document.removeEventListener("keydown", dismiss);
|
|
2529
|
+
document.removeEventListener("pointerdown", dismiss, true);
|
|
2530
|
+
};
|
|
2531
|
+
}, [open]);
|
|
2532
|
+
const navigate = (event) => {
|
|
2533
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
2534
|
+
const items = [...panel.current.querySelectorAll('[role="option"]')];
|
|
2535
|
+
if (!items.length) return;
|
|
2536
|
+
event.preventDefault();
|
|
2537
|
+
const current = items.indexOf(document.activeElement);
|
|
2538
|
+
const index = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : event.key === "ArrowDown" ? (current + 1) % items.length : (current <= 0 ? items.length : current) - 1;
|
|
2539
|
+
items[index].focus({ preventScroll: true });
|
|
2540
|
+
};
|
|
2541
|
+
const select = (id) => {
|
|
2542
|
+
onChange(id);
|
|
2543
|
+
close();
|
|
2544
|
+
};
|
|
2545
|
+
return /* @__PURE__ */ jsxs8("div", { className: "scui-harness-menu", ref: root, onBlur: (event) => {
|
|
2546
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false);
|
|
2547
|
+
}, children: [
|
|
2548
|
+
/* @__PURE__ */ jsxs8("button", { ref: trigger, className: "scui-harness-trigger", type: "button", disabled: disabled || !selected, "aria-label": selected ? `Coding harness: ${selected.label}` : "No coding harness available", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? menuId : void 0, onClick: () => setOpen((current) => !current), children: [
|
|
2549
|
+
selected ? /* @__PURE__ */ jsx9(Logo, { id: selected.id, size: 20 }) : null,
|
|
2550
|
+
/* @__PURE__ */ jsx9("strong", { children: selected?.label ?? "No harness available" }),
|
|
2551
|
+
/* @__PURE__ */ jsx9(UiIcon, { name: "chevron", size: 13 })
|
|
2552
|
+
] }),
|
|
2553
|
+
open ? /* @__PURE__ */ jsxs8("div", { ref: panel, id: menuId, className: "scui-harness-popover", role: "dialog", "aria-label": "Choose coding harness", onKeyDown: navigate, children: [
|
|
2554
|
+
/* @__PURE__ */ jsx9("strong", { className: "scui-harness-popover-title", children: "Choose coding harness" }),
|
|
2555
|
+
/* @__PURE__ */ jsx9("div", { className: "scui-harness-options", role: "listbox", "aria-label": "Available coding harnesses", children: available.map((item) => /* @__PURE__ */ jsxs8("button", { type: "button", role: "option", "aria-selected": item.id === selected?.id, onClick: () => select(item.id), children: [
|
|
2556
|
+
/* @__PURE__ */ jsx9(Logo, { id: item.id, size: 24 }),
|
|
2557
|
+
/* @__PURE__ */ jsx9("strong", { children: item.label }),
|
|
2558
|
+
/* @__PURE__ */ jsx9("span", { children: item.id === selected?.id ? /* @__PURE__ */ jsx9(UiIcon, { name: "check", size: 15 }) : null })
|
|
2559
|
+
] }, item.id)) }),
|
|
2560
|
+
unavailable.length ? /* @__PURE__ */ jsxs8("details", { className: "scui-harness-manage", children: [
|
|
2561
|
+
/* @__PURE__ */ jsx9("summary", { children: "Manage additional harnesses" }),
|
|
2562
|
+
/* @__PURE__ */ jsx9("div", { children: unavailable.map((item) => /* @__PURE__ */ jsxs8("section", { children: [
|
|
2563
|
+
/* @__PURE__ */ jsx9(Logo, { id: item.id, size: 24 }),
|
|
2564
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2565
|
+
/* @__PURE__ */ jsx9("strong", { children: item.label }),
|
|
2566
|
+
/* @__PURE__ */ jsx9("small", { children: item.reason || (item.auth === "required" ? "Authentication required" : "Unavailable") })
|
|
2567
|
+
] }),
|
|
2568
|
+
item.repair ? /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => adapter?.copyText?.(item.repair), children: "Copy fix" }) : null
|
|
2569
|
+
] }, item.id)) })
|
|
2570
|
+
] }) : null
|
|
2571
|
+
] }) : null
|
|
2572
|
+
] });
|
|
2573
|
+
}
|
|
2480
2574
|
|
|
2481
2575
|
// src/messenger.jsx
|
|
2482
2576
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
@@ -2623,7 +2717,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2623
2717
|
onClose ? /* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx10(UiIcon, { name: "close", size: 18 }) }) : null
|
|
2624
2718
|
] });
|
|
2625
2719
|
}
|
|
2626
|
-
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
|
|
2720
|
+
function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation, composerCommand }) {
|
|
2627
2721
|
const Header = slots.header;
|
|
2628
2722
|
const HeaderActions = slots.headerActions;
|
|
2629
2723
|
const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
|
|
@@ -2750,11 +2844,11 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
|
|
|
2750
2844
|
/* @__PURE__ */ jsx10(Conversation, { state: actionState, adapter: trackedAdapter, components, slots, memoryKey, pending: pendingMessage, unreadAfterMessages }, memoryKey),
|
|
2751
2845
|
/* @__PURE__ */ jsx10(ContinuationBar, { state: actionState, adapter: trackedAdapter, labels }),
|
|
2752
2846
|
/* @__PURE__ */ jsx10(Advisory, { state: actionState, adapter: trackedAdapter, value: state.interopSettings?.advisories[0] ?? null, onReview: (recommendedChange) => setSettings({ recommendedChange }) }),
|
|
2753
|
-
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx10(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
|
|
2847
|
+
state.mode !== "mirror" || state.canSend ? /* @__PURE__ */ jsx10(Composer, { state: actionState, adapter: trackedAdapter, labels, memoryKey, pendingStatus: pending?.status ?? null, restoreDraft, command: composerCommand, contextCandidates, components, onDraftRestored: (id) => setRestoreDraft((value) => value?.id === id ? null : value), onPending: beginPending }, memoryKey) : null,
|
|
2754
2848
|
settings ? /* @__PURE__ */ jsx10(SettingsPanel, { state: actionState, adapter: trackedAdapter, value: state.interopSettings, recommendedChange: settings.recommendedChange, onClose: () => setSettings(null) }) : null
|
|
2755
2849
|
] });
|
|
2756
2850
|
}
|
|
2757
|
-
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation }) {
|
|
2851
|
+
function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation, composerCommand }) {
|
|
2758
2852
|
const startable = state.harnesses.filter((item) => item.startable);
|
|
2759
2853
|
const startableKey = startable.map((item) => `${item.id}:${item.launchModes?.join(",")}:${item.preferredLaunchMode ?? ""}`).join("\0");
|
|
2760
2854
|
const remembered = newChatMemory.get(memoryKey) ?? { harness: startable[0]?.id ?? "", draft: "", context: [], images: [], modes: {} };
|
|
@@ -2768,8 +2862,10 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2768
2862
|
const [dragging, setDragging] = useState6(false);
|
|
2769
2863
|
const [pickerError, setPickerError] = useState6(null);
|
|
2770
2864
|
const startSequence = useRef7(0);
|
|
2865
|
+
const lastComposerCommand = useRef7(null);
|
|
2771
2866
|
const textarea = useRef7(null);
|
|
2772
2867
|
const selectedHarness = startable.find((item) => item.id === harness) ?? null;
|
|
2868
|
+
const Picker = components.HarnessPicker ?? HarnessPicker;
|
|
2773
2869
|
const Readiness = components.HarnessReadiness ?? HarnessReadiness;
|
|
2774
2870
|
const launchModes = selectedHarness?.launchModes?.length ? selectedHarness.launchModes : ["headless"];
|
|
2775
2871
|
const rememberedMode = modes[harness];
|
|
@@ -2810,6 +2906,32 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2810
2906
|
remember({ harness: nextHarness, draft: nextDraft, context: nextContext, images: nextImages });
|
|
2811
2907
|
textarea.current?.focus({ preventScroll: true });
|
|
2812
2908
|
}, [navigation?.id]);
|
|
2909
|
+
useEffect8(() => {
|
|
2910
|
+
if (!composerCommand || composerCommand.id === lastComposerCommand.current) return;
|
|
2911
|
+
lastComposerCommand.current = composerCommand.id;
|
|
2912
|
+
if (composerCommand.action === "attach") {
|
|
2913
|
+
if (mode === "terminal" && !launchModes.includes("headless")) {
|
|
2914
|
+
setPickerError("Terminal starts currently accept text only. Choose Chat to attach context or images.");
|
|
2915
|
+
} else {
|
|
2916
|
+
try {
|
|
2917
|
+
const attachments = partitionAttachments(composerCommand.attachments);
|
|
2918
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
2919
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
2920
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
2921
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
2922
|
+
const nextModes = mode === "terminal" ? { ...modes, [harness]: "headless" } : modes;
|
|
2923
|
+
if (nextModes !== modes) setModes(nextModes);
|
|
2924
|
+
setContext(nextContext);
|
|
2925
|
+
setImages(nextImages);
|
|
2926
|
+
setPickerError(null);
|
|
2927
|
+
remember({ context: nextContext, images: nextImages, modes: nextModes });
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
textarea.current?.focus({ preventScroll: true });
|
|
2934
|
+
}, [composerCommand?.id]);
|
|
2813
2935
|
const pickContext = () => {
|
|
2814
2936
|
if (mode === "terminal" || !adapter.pickContext || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS) return;
|
|
2815
2937
|
setPicking(true);
|
|
@@ -2907,18 +3029,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2907
3029
|
/* @__PURE__ */ jsx10("small", { children: "Choose a coding harness and send the first message." })
|
|
2908
3030
|
] }),
|
|
2909
3031
|
/* @__PURE__ */ jsxs9("div", { className: "scui-compose", children: [
|
|
2910
|
-
/* @__PURE__ */ jsxs9("label", { className: "scui-harness-picker", children: [
|
|
2911
|
-
/* @__PURE__ */ jsx10(HarnessLogo, { id: harness, size: 24 }),
|
|
2912
|
-
/* @__PURE__ */ jsx10("span", { children: "Coding harness" }),
|
|
2913
|
-
/* @__PURE__ */ jsx10("select", { value: harness, disabled: Boolean(starting), onChange: (event) => {
|
|
2914
|
-
const value = event.currentTarget.value;
|
|
2915
|
-
setHarness(value);
|
|
2916
|
-
remember({ harness: value });
|
|
2917
|
-
}, children: state.harnesses.map((item) => /* @__PURE__ */ jsxs9("option", { value: item.id, disabled: !item.startable, children: [
|
|
2918
|
-
item.label,
|
|
2919
|
-
item.startable ? "" : " \xB7 unavailable"
|
|
2920
|
-
] }, item.id)) })
|
|
2921
|
-
] }),
|
|
2922
3032
|
/* @__PURE__ */ jsx10(Readiness, { harnesses: state.harnesses, state, adapter }),
|
|
2923
3033
|
launchModes.length > 1 ? /* @__PURE__ */ jsxs9("fieldset", { className: "scui-launch-modes", disabled: Boolean(starting), children: [
|
|
2924
3034
|
/* @__PURE__ */ jsx10("legend", { children: "Run as" }),
|
|
@@ -2947,7 +3057,7 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2947
3057
|
return next;
|
|
2948
3058
|
}) }),
|
|
2949
3059
|
terminalAttachments ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: "Terminal starts currently accept text only. Remove attachments or choose Chat." }) : pickerError ? /* @__PURE__ */ jsx10("small", { className: "scui-context-error", role: "alert", children: pickerError }) : null,
|
|
2950
|
-
/* @__PURE__ */ jsxs9("div", { className: `scui-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
3060
|
+
/* @__PURE__ */ jsxs9("div", { className: `scui-envelope scui-new-envelope${dragging ? " scui-drop-target" : ""}`, onDragEnter: (event) => {
|
|
2951
3061
|
if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) {
|
|
2952
3062
|
event.preventDefault();
|
|
2953
3063
|
setDragging(true);
|
|
@@ -2957,7 +3067,6 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2957
3067
|
}, onDragLeave: (event) => {
|
|
2958
3068
|
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
2959
3069
|
}, onDrop: dropImages, children: [
|
|
2960
|
-
adapter.pickContext ? /* @__PURE__ */ jsx10("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: mode === "terminal" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx10("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx10(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
2961
3070
|
/* @__PURE__ */ jsx10("textarea", { ref: 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 || Boolean(starting), onPaste: pasteImages, onInput: (event) => {
|
|
2962
3071
|
const value = event.currentTarget.value;
|
|
2963
3072
|
setDraft(value);
|
|
@@ -2968,12 +3077,19 @@ function NewChat({ state, adapter, onBack, onClose, onStarted, labels, memoryKey
|
|
|
2968
3077
|
send();
|
|
2969
3078
|
}
|
|
2970
3079
|
} }),
|
|
2971
|
-
/* @__PURE__ */
|
|
3080
|
+
/* @__PURE__ */ jsxs9("footer", { className: "scui-new-envelope-controls", children: [
|
|
3081
|
+
adapter.pickContext ? /* @__PURE__ */ jsx10("button", { className: "scui-attach", type: "button", "aria-label": "Attach files or images", disabled: mode === "terminal" || picking || context.length >= MAX_CONTEXT_ITEMS && images.length >= MAX_IMAGE_ITEMS || Boolean(starting), onClick: pickContext, children: picking ? /* @__PURE__ */ jsx10("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx10(UiIcon, { name: "attach", size: 17 }) }) : null,
|
|
3082
|
+
/* @__PURE__ */ jsx10(Picker, { harnesses: state.harnesses, value: harness, disabled: Boolean(starting), adapter, logo: components.HarnessLogo, onChange: (value) => {
|
|
3083
|
+
setHarness(value);
|
|
3084
|
+
remember({ harness: value });
|
|
3085
|
+
} }),
|
|
3086
|
+
/* @__PURE__ */ jsx10("span", { children: /* @__PURE__ */ jsx10("button", { type: "button", className: "scui-send", "aria-label": mode === "terminal" ? "Start terminal session" : "Start chat", disabled: !draft.trim() && !images.length || !harness || Boolean(starting) || terminalAttachments || mode === "terminal" && !draft.trim(), onClick: send, children: starting ? /* @__PURE__ */ jsx10("i", { className: "scui-control-spinner" }) : /* @__PURE__ */ jsx10(UiIcon, { name: "send", size: 17 }) }) })
|
|
3087
|
+
] })
|
|
2972
3088
|
] })
|
|
2973
3089
|
] })
|
|
2974
3090
|
] });
|
|
2975
3091
|
}
|
|
2976
|
-
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
3092
|
+
function SupercodeMessenger({ state: stateInput, adapter, class: className = "", initialView, navigation = null, composerCommand = null, onViewChange, contextCandidates: candidatesInput = [], labels, components = {}, slots = {} }) {
|
|
2977
3093
|
const state = useMemo3(() => normalizeUiState(stateInput), [stateInput]);
|
|
2978
3094
|
const contextCandidates = useMemo3(() => normalizeAttachmentCandidates(candidatesInput), [candidatesInput]);
|
|
2979
3095
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
@@ -3044,7 +3160,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
3044
3160
|
setNewNavigation(null);
|
|
3045
3161
|
setView("new");
|
|
3046
3162
|
}, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
|
|
3047
|
-
view === "new" ? /* @__PURE__ */ jsx10(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: (mode) => setView(mode === "terminal" ? "list" : "chat"), labels: copy, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation: newNavigation }) : null,
|
|
3163
|
+
view === "new" ? /* @__PURE__ */ jsx10(NewChat, { state, adapter, onBack: () => setView("list"), onClose: adapter.onClose ? close : void 0, onStarted: (mode) => setView(mode === "terminal" ? "list" : "chat"), labels: copy, memoryKey, headerActions: HeaderActions, contextCandidates, components, navigation: newNavigation, composerCommand }) : null,
|
|
3048
3164
|
view === "chat" ? /* @__PURE__ */ jsx10(Chat, { state, adapter, onBack: () => {
|
|
3049
3165
|
setListFocus(state.attached?.key ?? listFocus);
|
|
3050
3166
|
setView("list");
|
|
@@ -3053,7 +3169,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
|
|
|
3053
3169
|
setChatNavigation(null);
|
|
3054
3170
|
setNewNavigation(null);
|
|
3055
3171
|
setView("new");
|
|
3056
|
-
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
|
|
3172
|
+
}, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null, composerCommand }) : null,
|
|
3057
3173
|
opening ? /* @__PURE__ */ jsxs9("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
|
|
3058
3174
|
/* @__PURE__ */ jsx10(HarnessLogo, { id: opening.harness, size: 34 }),
|
|
3059
3175
|
/* @__PURE__ */ jsxs9("span", { children: [
|
|
@@ -3078,6 +3194,7 @@ export {
|
|
|
3078
3194
|
Conversation,
|
|
3079
3195
|
HarnessAdvisory,
|
|
3080
3196
|
HarnessLogo,
|
|
3197
|
+
HarnessPicker,
|
|
3081
3198
|
HarnessReadiness,
|
|
3082
3199
|
HarnessSettingsPanel,
|
|
3083
3200
|
ImageViewer,
|
package/react/composer.mjs
CHANGED
|
@@ -318,7 +318,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
|
|
|
318
318
|
] })
|
|
319
319
|
] });
|
|
320
320
|
}
|
|
321
|
-
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
321
|
+
function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, command = null, contextCandidates = [], components = {}, onPending, onDraftRestored }) {
|
|
322
322
|
const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], images: [], queue: [] };
|
|
323
323
|
const [draft, setDraft] = useState2(remembered.draft);
|
|
324
324
|
const [context, setContext] = useState2(remembered.context ?? []);
|
|
@@ -330,6 +330,7 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
330
330
|
const [dragging, setDragging] = useState2(false);
|
|
331
331
|
const [pickerError, setPickerError] = useState2(null);
|
|
332
332
|
const textarea = useRef2(null);
|
|
333
|
+
const lastCommand = useRef2(null);
|
|
333
334
|
useAutosizeTextarea(textarea, draft);
|
|
334
335
|
const remember = (nextDraft, nextContext, nextImages, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, images: nextImages, queue: nextQueue });
|
|
335
336
|
useEffect2(() => {
|
|
@@ -371,6 +372,26 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
|
|
|
371
372
|
textarea.current?.focus({ preventScroll: true });
|
|
372
373
|
onDraftRestored?.(restoreDraft.id);
|
|
373
374
|
}, [onDraftRestored, restoreDraft?.id]);
|
|
375
|
+
useEffect2(() => {
|
|
376
|
+
if (!command || command.id === lastCommand.current) return;
|
|
377
|
+
lastCommand.current = command.id;
|
|
378
|
+
if (command.action === "attach") {
|
|
379
|
+
try {
|
|
380
|
+
const attachments = partitionAttachments(command.attachments);
|
|
381
|
+
if (context.length + attachments.context.length > MAX_CONTEXT_ITEMS) throw new Error(`Attach at most ${MAX_CONTEXT_ITEMS} text items.`);
|
|
382
|
+
if (images.length + attachments.images.length > MAX_IMAGE_ITEMS) throw new Error(`Attach at most ${MAX_IMAGE_ITEMS} images.`);
|
|
383
|
+
const nextContext = mergeContext(context, attachments.context);
|
|
384
|
+
const nextImages = mergeImages(images, attachments.images);
|
|
385
|
+
setContext(nextContext);
|
|
386
|
+
setImages(nextImages);
|
|
387
|
+
setPickerError(null);
|
|
388
|
+
remember(draft, nextContext, nextImages, queue);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
setPickerError(error instanceof Error ? error.message : "Could not attach context.");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
textarea.current?.focus({ preventScroll: true });
|
|
394
|
+
}, [command?.id]);
|
|
374
395
|
useEffect2(() => {
|
|
375
396
|
const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
|
|
376
397
|
return () => clearTimeout(timer);
|
package/react/index.d.ts
CHANGED
|
@@ -7,9 +7,11 @@ import type {
|
|
|
7
7
|
ContextCandidateProps,
|
|
8
8
|
HarnessAdvisoryProps,
|
|
9
9
|
HarnessId,
|
|
10
|
+
HarnessPickerProps,
|
|
10
11
|
HarnessSettingsPanelProps,
|
|
11
12
|
HarnessOption,
|
|
12
13
|
MessengerLabels,
|
|
14
|
+
MessengerComposerCommand,
|
|
13
15
|
MessengerNavigation,
|
|
14
16
|
PendingMessageModel,
|
|
15
17
|
SessionActivity,
|
|
@@ -36,9 +38,11 @@ export type {
|
|
|
36
38
|
ContextCandidateProps,
|
|
37
39
|
HarnessAdvisoryProps,
|
|
38
40
|
HarnessId,
|
|
41
|
+
HarnessPickerProps,
|
|
39
42
|
HarnessSettingsPanelProps,
|
|
40
43
|
HarnessOption,
|
|
41
44
|
MessengerLabels,
|
|
45
|
+
MessengerComposerCommand,
|
|
42
46
|
MessengerNavigation,
|
|
43
47
|
PendingMessageModel,
|
|
44
48
|
SessionActivity,
|
|
@@ -66,6 +70,7 @@ export interface MessengerComponents {
|
|
|
66
70
|
HarnessLogo?: ComponentType<{ id: HarnessId; activity?: SessionActivity; size?: number }>;
|
|
67
71
|
HarnessAdvisory?: ComponentType<HarnessAdvisoryProps>;
|
|
68
72
|
HarnessSettingsPanel?: ComponentType<HarnessSettingsPanelProps>;
|
|
73
|
+
HarnessPicker?: ComponentType<HarnessPickerProps>;
|
|
69
74
|
ContextCandidate?: ComponentType<ContextCandidateProps>;
|
|
70
75
|
HarnessReadiness?: ComponentType<{ harnesses: HarnessOption[]; state: SupercodeUiState; adapter: UiAdapter }>;
|
|
71
76
|
}
|
|
@@ -87,6 +92,7 @@ export interface MessengerProps {
|
|
|
87
92
|
class?: string;
|
|
88
93
|
initialView?: 'list' | 'chat' | 'new';
|
|
89
94
|
navigation?: MessengerNavigation | null;
|
|
95
|
+
composerCommand?: MessengerComposerCommand | null;
|
|
90
96
|
onViewChange?(view: 'list' | 'chat' | 'new'): void;
|
|
91
97
|
contextCandidates?: TranscriptAttachment[];
|
|
92
98
|
labels?: Partial<MessengerLabels>;
|
|
@@ -112,11 +118,12 @@ export function ToolRow(props: ToolRowProps): ReactElement;
|
|
|
112
118
|
export function TaskPlan(props: { plan: TaskPlanModel }): ReactElement | null;
|
|
113
119
|
export function HarnessAdvisory(props: HarnessAdvisoryProps): ReactElement | null;
|
|
114
120
|
export function HarnessSettingsPanel(props: HarnessSettingsPanelProps): ReactElement;
|
|
121
|
+
export function HarnessPicker(props: HarnessPickerProps): ReactElement;
|
|
115
122
|
export function HarnessReadiness(props: { harnesses: HarnessOption[]; state: SupercodeUiState; adapter: UiAdapter }): ReactElement | null;
|
|
116
123
|
export function SessionDetails(props: { semantics: SupercodeUiState['semantics'] }): ReactElement | null;
|
|
117
124
|
export function Conversation(props: { state: SupercodeUiState; adapter: UiAdapter; components?: MessengerComponents; slots?: MessengerSlots; memoryKey?: string; pending?: string | PendingMessageModel | null; unreadAfterMessages?: number | null }): ReactElement;
|
|
118
125
|
export function SessionRow(props: { row: SessionRowModel; state: SupercodeUiState; adapter: UiAdapter; now?: number; onOpen(row: SessionRowModel): void }): ReactElement;
|
|
119
126
|
export function SessionList(props: { state: SupercodeUiState; adapter: UiAdapter; onOpen(row: SessionRowModel): void; onNew?(): void; onClose?(): void; components?: MessengerComponents; slots?: MessengerSlots; labels?: MessengerLabels; focusKey?: string | null; memoryKey?: string; headerActions?: MessengerSlots['headerActions'] }): ReactElement;
|
|
120
127
|
export function ContinuationBar(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels }): ReactElement | null;
|
|
121
|
-
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string; context?: TranscriptContext[]; images?: TranscriptImage[] } | null; contextCandidates?: TranscriptAttachment[]; components?: Pick<MessengerComponents, 'ContextCandidate'>; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): ReactElement;
|
|
128
|
+
export function Composer(props: { state: SupercodeUiState; adapter: UiAdapter; labels?: MessengerLabels; memoryKey?: string; pendingStatus?: PendingMessageModel['status'] | 'editing' | null; restoreDraft?: { id: string | number; text: string; context?: TranscriptContext[]; images?: TranscriptImage[] } | null; command?: MessengerComposerCommand | null; contextCandidates?: TranscriptAttachment[]; components?: Pick<MessengerComponents, 'ContextCandidate'>; onPending?(text: string, context?: TranscriptContext[], images?: TranscriptImage[]): void; onDraftRestored?(id: string | number): void }): ReactElement;
|
|
122
129
|
export function SupercodeMessenger(props: MessengerProps): ReactElement;
|