zidane-ui 9.1.6 → 9.1.11
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/adapters/run-host.js +17 -20
- package/dist/adapters/server-files-host.js +3 -1
- package/dist/adapters/server-host.js +18 -11
- package/dist/adapters/server-machines-host.js +3 -1
- package/dist/adapters/server-session-host.js +2 -1
- package/dist/adapters/server-settings-accounts.js +2 -1
- package/dist/adapters/server-settings-host.js +3 -3
- package/dist/adapters/sse-host.js +9 -7
- package/dist/app/testing/index.js +1 -1
- package/dist/chat/SessionGroupHeader.js +2 -2
- package/dist/chat/SessionHostHeader.js +1 -1
- package/dist/chat/StatusDot.js +24 -20
- package/dist/chat/composer/Composer.d.ts +13 -8
- package/dist/chat/composer/Composer.js +14 -10
- package/dist/chat/composer/HostComposer.d.ts +1 -1
- package/dist/chat/composer/HostComposer.js +7 -10
- package/dist/chat/composer/RunStatusChip.js +13 -1
- package/dist/chat/composer/attachment-ingest.d.ts +4 -1
- package/dist/chat/composer/attachment-ingest.js +27 -10
- package/dist/chat/composer/composer-bus.d.ts +9 -1
- package/dist/chat/composer/composer-bus.js +49 -6
- package/dist/chat/composer/index.d.ts +2 -2
- package/dist/chat/composer/index.js +2 -2
- package/dist/chat/composer/use-draft-target.d.ts +4 -0
- package/dist/chat/composer/use-draft-target.js +15 -4
- package/dist/chat/composer/use-send-prompt.d.ts +5 -1
- package/dist/chat/composer/use-send-prompt.js +2 -12
- package/dist/chat/headless/chip-labels.d.ts +2 -0
- package/dist/chat/headless/chip-labels.js +3 -1
- package/dist/chat/headless/composer/labels-editor.d.ts +2 -0
- package/dist/chat/headless/composer/labels-editor.js +2 -0
- package/dist/chat/headless/file-chip.d.ts +12 -13
- package/dist/chat/headless/file-chip.js +40 -2
- package/dist/chat/headless/fixture-host.js +14 -17
- package/dist/chat/headless/host.d.ts +24 -6
- package/dist/chat/headless/index.d.ts +4 -4
- package/dist/chat/headless/index.js +3 -3
- package/dist/chat/headless/labels.d.ts +16 -0
- package/dist/chat/headless/labels.js +15 -2
- package/dist/chat/headless/send-flow.d.ts +21 -4
- package/dist/chat/headless/send-flow.js +25 -6
- package/dist/chat/headless/session-list.d.ts +11 -0
- package/dist/chat/headless/sidebar/labels.d.ts +12 -1
- package/dist/chat/headless/transcript-host.d.ts +8 -1
- package/dist/chat/headless/transcript-labels.d.ts +2 -0
- package/dist/chat/index.d.ts +3 -3
- package/dist/chat/index.js +2 -2
- package/dist/chat/interactions/InboxPanel.js +2 -2
- package/dist/chat/interactions/QuestionField.js +2 -2
- package/dist/chat/rich-composer/RichComposer.d.ts +9 -2
- package/dist/chat/rich-composer/RichComposer.js +28 -7
- package/dist/chat/rich-composer/composer-extensions.d.ts +2 -0
- package/dist/chat/rich-composer/composer-extensions.js +2 -2
- package/dist/chat/rich-composer/composer-keys.js +1 -1
- package/dist/chat/rich-composer/use-composer-bus.js +2 -1
- package/dist/chat/rich-composer/use-composer-editor.d.ts +4 -1
- package/dist/chat/rich-composer/use-composer-editor.js +2 -1
- package/dist/chat/sidebar/SessionSidebar.js +1 -2
- package/dist/chat/sidebar/SidebarSearchRow.js +1 -1
- package/dist/chat/sidebar/useSidebarKeyboardNav.d.ts +0 -2
- package/dist/chat/sidebar/useSidebarKeyboardNav.js +7 -13
- package/dist/chat/testing/contract.d.ts +1 -1
- package/dist/chat/testing/contract.js +8 -7
- package/dist/chat/transcript/BadgeMenu.d.ts +12 -3
- package/dist/chat/transcript/BadgeMenu.js +20 -3
- package/dist/chat/transcript/CodemodeScript.js +147 -44
- package/dist/chat/transcript/EditDiffBlock.js +1 -4
- package/dist/chat/transcript/FileChip.js +60 -14
- package/dist/chat/transcript/LiveOutput.js +7 -3
- package/dist/chat/transcript/badges.js +2 -2
- package/dist/chat/transcript/index.d.ts +2 -2
- package/dist/chat/transcript/markdown-context.d.ts +4 -1
- package/dist/chat/transcript/rolling.d.ts +12 -0
- package/dist/chat/transcript/rolling.js +22 -0
- package/dist/chat/transcript/script-input.d.ts +1 -1
- package/dist/chat/transcript/shell-block.js +70 -60
- package/dist/chat/transcript/tool-row.js +21 -5
- package/dist/code/editor/FileTree.d.ts +3 -1
- package/dist/code/editor/FileTree.js +29 -1
- package/dist/components/Restricted.d.ts +25 -0
- package/dist/components/Restricted.js +32 -0
- package/dist/components/StepFrame.d.ts +1 -1
- package/dist/components/ZidaneMark.d.ts +28 -0
- package/dist/components/ZidaneMark.js +114 -0
- package/dist/components/palette/PalettePanel.d.ts +7 -1
- package/dist/components/palette/PalettePanel.js +37 -11
- package/dist/components/zidane-mark.d.ts +52 -0
- package/dist/components/zidane-mark.js +94 -0
- package/dist/core.css +59 -6
- package/dist/dist/{index-DqCio7Nd.d.ts → index-Bq87OcGy.d.ts} +19 -12
- package/dist/dist/index.d.ts +1 -1
- package/dist/extensions/ExtensionSettingsSection.d.ts +18 -0
- package/dist/extensions/ExtensionSettingsSection.js +227 -0
- package/dist/extensions/headless/host.d.ts +1 -1
- package/dist/extensions/headless/labels.d.ts +41 -1
- package/dist/extensions/headless/labels.js +57 -0
- package/dist/extensions/headless/select.js +1 -2
- package/dist/extensions/index.d.ts +2 -1
- package/dist/extensions/index.js +2 -1
- package/dist/files/FilesTab.js +2 -0
- package/dist/files/TreeColumn.d.ts +5 -1
- package/dist/files/TreeColumn.js +6 -0
- package/dist/files/headless/fixture-host.js +5 -3
- package/dist/files/headless/host.d.ts +24 -6
- package/dist/files/headless/index.d.ts +2 -2
- package/dist/files/headless/labels.d.ts +1 -0
- package/dist/files/headless/labels.js +1 -0
- package/dist/files/testing/contract.js +9 -2
- package/dist/files/useWorkspace.d.ts +14 -2
- package/dist/files/useWorkspace.js +87 -9
- package/dist/git/BranchMenu.js +1 -1
- package/dist/git/MoveSessionDialog.js +1 -1
- package/dist/git/RemoteFolderPicker.js +1 -1
- package/dist/git/WherePicker.js +1 -1
- package/dist/git/headless/index.js +1 -1
- package/dist/git/useEnvironmentChoices.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/keybindings/headless/catalog.js +1 -1
- package/dist/labels.d.ts +1 -0
- package/dist/labels.js +1 -0
- package/dist/machines/DaemonMenu.js +1 -1
- package/dist/machines/HostCapabilities.d.ts +23 -1
- package/dist/machines/HostCapabilities.js +149 -65
- package/dist/machines/HostSyncPanel.js +129 -123
- package/dist/machines/HostsSection.js +5 -1
- package/dist/machines/ServerCard.d.ts +4 -2
- package/dist/machines/ServerCard.js +222 -99
- package/dist/machines/ServerMachinesPanel.js +16 -13
- package/dist/machines/ServerUpdatePanel.js +31 -21
- package/dist/machines/TokensPanel.js +75 -53
- package/dist/machines/headless/fixture-host.d.ts +5 -0
- package/dist/machines/headless/fixture-host.js +13 -4
- package/dist/machines/headless/fixtures.js +1 -1
- package/dist/machines/headless/host.d.ts +22 -2
- package/dist/machines/headless/index.d.ts +2 -2
- package/dist/machines/headless/labels.d.ts +22 -1
- package/dist/machines/headless/labels.js +22 -2
- package/dist/machines/testing/contract.d.ts +3 -1
- package/dist/machines/testing/contract.js +9 -0
- package/dist/motion/size.d.ts +22 -1
- package/dist/motion/size.js +82 -4
- package/dist/onboarding/demos/delivery.d.ts +16 -0
- package/dist/onboarding/demos/delivery.js +132 -0
- package/dist/onboarding/demos/index.d.ts +2 -1
- package/dist/onboarding/demos/index.js +2 -1
- package/dist/onboarding/headless/labels.d.ts +8 -0
- package/dist/onboarding/headless/labels.js +7 -0
- package/dist/onboarding/index.d.ts +2 -1
- package/dist/onboarding/index.js +2 -1
- package/dist/onboarding/steps/ConversationStep.js +46 -4
- package/dist/palette/CommandPalette.js +3 -0
- package/dist/palette/PaletteDialog.js +7 -3
- package/dist/palette/usePaletteController.d.ts +10 -0
- package/dist/palette/usePaletteController.js +25 -3
- package/dist/server/sse-handler.js +6 -1
- package/dist/session/ArtifactsPanel.js +1 -1
- package/dist/session/SideChat.js +1 -2
- package/dist/session/UsagePanel.js +1 -1
- package/dist/session/headless/host.d.ts +3 -1
- package/dist/session/headless/index.js +1 -1
- package/dist/settings/SettingsRestricted.d.ts +25 -0
- package/dist/settings/SettingsRestricted.js +44 -0
- package/dist/settings/SettingsRowsPage.js +31 -37
- package/dist/settings/headless/catalog.js +7 -6
- package/dist/settings/headless/labels.d.ts +4 -2
- package/dist/settings/headless/labels.js +4 -2
- package/dist/settings/headless/layout.js +1 -1
- package/dist/settings/headless/providers.d.ts +2 -0
- package/dist/settings/headless/search.d.ts +1 -1
- package/dist/settings/index.d.ts +2 -2
- package/dist/settings/index.js +2 -2
- package/dist/settings/rows.d.ts +51 -8
- package/dist/settings/rows.js +81 -26
- package/dist/settings/sections/enabled-list/EnabledListSection.js +3 -2
- package/dist/settings/sections/environments/EnvironmentsSection.js +4 -3
- package/dist/settings/sections/extensions/ExtensionConfigRows.d.ts +2 -2
- package/dist/settings/sections/extensions/ExtensionConfigRows.js +22 -25
- package/dist/settings/sections/extensions/ExtensionOptionsSection.js +3 -2
- package/dist/settings/sections/extensions/options-labels.d.ts +2 -2
- package/dist/settings/sections/extensions/options-labels.js +2 -2
- package/dist/settings/sections/host-catalog/HostCatalogSection.d.ts +5 -3
- package/dist/settings/sections/host-catalog/HostCatalogSection.js +7 -4
- package/dist/settings/sections/instructions/InstructionsSection.js +24 -17
- package/dist/settings/sections/logs/LogsSection.d.ts +2 -1
- package/dist/settings/sections/logs/LogsSection.js +64 -56
- package/dist/settings/sections/mcp/McpSection.d.ts +1 -1
- package/dist/settings/sections/mcp/McpSection.js +18 -17
- package/dist/settings/sections/models/ModelRows.d.ts +3 -3
- package/dist/settings/sections/models/ModelRows.js +8 -7
- package/dist/settings/sections/performance/PerformanceSection.js +13 -4
- package/dist/settings/sections/performance/labels.d.ts +0 -1
- package/dist/settings/sections/performance/labels.js +0 -1
- package/dist/settings/sections/providers/CredentialSources.js +18 -8
- package/dist/settings/sections/providers/ProvidersSection.d.ts +2 -2
- package/dist/settings/sections/providers/ProvidersSection.js +20 -17
- package/dist/settings/sections/providers/ServerProvidersSection.d.ts +1 -1
- package/dist/settings/sections/providers/ServerProvidersSection.js +10 -14
- package/dist/settings/sections/shared/catalog.js +3 -2
- package/dist/settings/sections/skills/SkillList.js +4 -3
- package/dist/settings/testing/accounts-demo.js +1 -0
- package/dist/settings/testing/accounts.contract.js +2 -0
- package/dist/styles.css +160 -24
- package/dist/terminal/HostTerminalSurface.js +1 -1
- package/dist/terminal/headless/links.d.ts +19 -1
- package/dist/terminal/headless/links.js +26 -2
- package/dist/theme/css-vars.js +1 -0
- package/dist/theme/derive.d.ts +4 -0
- package/dist/theme/derive.js +2 -0
- package/dist/workspace/PanelContentView.js +1 -1
- package/dist/workspace/PanelHome.d.ts +3 -1
- package/dist/workspace/PanelHome.js +4 -3
- package/dist/workspace/SidePanel.d.ts +3 -1
- package/dist/workspace/SidePanel.js +3 -2
- package/dist/workspace/headless/open-target.d.ts +1 -1
- package/dist/workspace/headless/open-target.js +2 -2
- package/package.json +2 -2
- package/src/styles/chat.css +10 -10
- package/src/styles/motion.css +37 -0
- package/src/styles/primitives.css +11 -3
- package/src/styles/sidebar.css +12 -0
- package/src/styles/tokens.css +6 -0
- package/src/styles/transcript.css +69 -5
|
@@ -125,7 +125,23 @@ function createRunHost(options) {
|
|
|
125
125
|
if (!state.transcript(sessionId).state.get().hydrated) state.hydrate(sessionId, []);
|
|
126
126
|
return state.readable(sessionId);
|
|
127
127
|
},
|
|
128
|
-
send
|
|
128
|
+
async send(input) {
|
|
129
|
+
const active = runs.get(input.sessionId);
|
|
130
|
+
const { delivery, ...prompt } = input;
|
|
131
|
+
if (delivery === "steer" && active?.steer && !input.attachments?.length) {
|
|
132
|
+
state.apply(input.sessionId, {
|
|
133
|
+
kind: "user-prompt",
|
|
134
|
+
text: input.prompt
|
|
135
|
+
});
|
|
136
|
+
active.steer(input.prompt);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (delivery && active) {
|
|
140
|
+
active.queued.push(prompt);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
return send(prompt);
|
|
144
|
+
},
|
|
129
145
|
interrupt(sessionId) {
|
|
130
146
|
const active = runs.get(sessionId);
|
|
131
147
|
if (!active) return;
|
|
@@ -134,25 +150,6 @@ function createRunHost(options) {
|
|
|
134
150
|
for (const i of state.pending(sessionId).get()) settle(i.requestId, null);
|
|
135
151
|
state.setRunning(sessionId, false, "idle");
|
|
136
152
|
},
|
|
137
|
-
async steer(sessionId, message) {
|
|
138
|
-
const active = runs.get(sessionId);
|
|
139
|
-
if (!active?.steer) return false;
|
|
140
|
-
state.apply(sessionId, {
|
|
141
|
-
kind: "user-prompt",
|
|
142
|
-
text: message
|
|
143
|
-
});
|
|
144
|
-
active.steer(message);
|
|
145
|
-
return true;
|
|
146
|
-
},
|
|
147
|
-
async followUp(sessionId, message) {
|
|
148
|
-
const active = runs.get(sessionId);
|
|
149
|
-
if (!active) return false;
|
|
150
|
-
active.queued.push({
|
|
151
|
-
sessionId,
|
|
152
|
-
prompt: message
|
|
153
|
-
});
|
|
154
|
-
return true;
|
|
155
|
-
},
|
|
156
153
|
interactions: {
|
|
157
154
|
pending: state.pending,
|
|
158
155
|
async answer(requestId, response) {
|
|
@@ -142,13 +142,15 @@ function createServerFilesHost(options) {
|
|
|
142
142
|
}), client.can("sessions.workspaceEmptyDirs") ? client.call("sessions.workspaceEmptyDirs", { sessionId }).catch(() => []) : []]);
|
|
143
143
|
const paths = /* @__PURE__ */ new Set();
|
|
144
144
|
for (const entry of entries) paths.add(relativePath(typeof entry === "string" ? entry : entry.path, cwd));
|
|
145
|
-
|
|
145
|
+
const all = [...[...paths].map((path) => ({
|
|
146
146
|
path,
|
|
147
147
|
kind: "file"
|
|
148
148
|
})), ...emptyDirs.map((path) => ({
|
|
149
149
|
path: `${path}/`,
|
|
150
150
|
kind: "dir"
|
|
151
151
|
}))].sort((a, b) => a.path.localeCompare(b.path));
|
|
152
|
+
const dir = opts?.dir?.replace(/\/+$/, "");
|
|
153
|
+
return { entries: dir ? all.filter((e) => e.path.startsWith(`${dir}/`)) : all };
|
|
152
154
|
},
|
|
153
155
|
async read(cwd, path) {
|
|
154
156
|
const sessionId = sessionOf(cwd);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writable } from "../chat/headless/store.js";
|
|
2
2
|
import { createLiveState } from "./live.js";
|
|
3
3
|
import { createServerClient } from "./server-client.js";
|
|
4
|
+
import { wireOriginal } from "../chat/headless/send-flow.js";
|
|
4
5
|
import { createServerTranscriptMembers } from "./server-transcript-members.js";
|
|
5
6
|
//#region src/adapters/server-host.ts
|
|
6
7
|
/** Transcript turns per page. */
|
|
@@ -360,17 +361,31 @@ function createServerHost(options) {
|
|
|
360
361
|
} } : {}
|
|
361
362
|
},
|
|
362
363
|
transcript: (sessionId) => state.readable(sessionId, { loadEarlier: () => void loadEarlier(sessionId) }),
|
|
363
|
-
async send({ sessionId, prompt, attachments, model, thinking, activatedSkills, echo, keepDraft }) {
|
|
364
|
-
if (echo !== false) state.echoPrompt(sessionId, prompt, attachments);
|
|
364
|
+
async send({ sessionId, prompt, attachments, model, thinking, activatedSkills, echo, keepDraft, delivery }) {
|
|
365
|
+
if (echo !== false && !delivery) state.echoPrompt(sessionId, prompt, attachments?.map(({ original: _original, ...a }) => a));
|
|
365
366
|
await applyChoice(sessionId, model, thinking);
|
|
366
367
|
const uploads = (attachments ?? []).flatMap((a) => {
|
|
367
368
|
const m = a.dataUrl?.match(/^data:([^;,]+)?;base64,(.*)$/s);
|
|
369
|
+
const original = wireOriginal(a.original);
|
|
368
370
|
return m ? [{
|
|
369
371
|
mediaType: m[1] || a.mediaType,
|
|
370
372
|
data: m[2],
|
|
371
|
-
name: a.name
|
|
373
|
+
name: a.name,
|
|
374
|
+
...original ? { original } : {}
|
|
372
375
|
}] : [];
|
|
373
376
|
});
|
|
377
|
+
if (delivery) {
|
|
378
|
+
await client.call("queue.add", {
|
|
379
|
+
sessionId,
|
|
380
|
+
text: prompt,
|
|
381
|
+
delivery,
|
|
382
|
+
requestId: crypto.randomUUID(),
|
|
383
|
+
...activatedSkills?.length ? { activatedSkills: [...activatedSkills] } : {},
|
|
384
|
+
...uploads.length ? { attachments: uploads } : {},
|
|
385
|
+
...keepDraft ? { keepDraft: true } : {}
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
374
389
|
await client.call("chat.send", {
|
|
375
390
|
sessionId,
|
|
376
391
|
prompt,
|
|
@@ -387,14 +402,6 @@ function createServerHost(options) {
|
|
|
387
402
|
interrupt(sessionId) {
|
|
388
403
|
client.call("chat.interrupt", { sessionId }).catch(onError);
|
|
389
404
|
},
|
|
390
|
-
steer: (sessionId, message) => client.call("chat.steer", {
|
|
391
|
-
sessionId,
|
|
392
|
-
message
|
|
393
|
-
}),
|
|
394
|
-
followUp: (sessionId, message) => client.call("chat.followUp", {
|
|
395
|
-
sessionId,
|
|
396
|
-
message
|
|
397
|
-
}),
|
|
398
405
|
interactions: {
|
|
399
406
|
pending: state.pending,
|
|
400
407
|
all: () => allPending,
|
|
@@ -77,7 +77,8 @@ function createServerMachinesHost(options) {
|
|
|
77
77
|
status,
|
|
78
78
|
...error ? { error } : {},
|
|
79
79
|
...hello?.appVersion ? { serverVersion: hello.appVersion } : {},
|
|
80
|
-
...hello?.scope ? { scope: hello.scope } : {},
|
|
80
|
+
...hello?.scope ? { scope: hello.scope === "admin" ? "admin" : "chat" } : {},
|
|
81
|
+
...hello?.adminCommands ? { adminCommands: hello.adminCommands } : {},
|
|
81
82
|
...hello?.startedAt ? { startedAt: hello.startedAt } : {},
|
|
82
83
|
...hello?.machineServe ? { machineServe: true } : {},
|
|
83
84
|
...lastConnectedAt ? { lastConnectedAt } : {}
|
|
@@ -192,6 +193,7 @@ function createServerMachinesHost(options) {
|
|
|
192
193
|
},
|
|
193
194
|
signIn: (_hostId, target, onDone) => serverOAuthFlow(client, target, onDone ? { onDone: () => onDone() } : {}),
|
|
194
195
|
capabilities: (_hostId, opts) => guard("read", () => client.call("host.capabilities", opts?.refresh ? { refresh: true } : {})),
|
|
196
|
+
installTool: (_hostId, name) => guard("install", () => client.call("host.installTool", { name })),
|
|
195
197
|
presence: createPresenceStore((apply) => {
|
|
196
198
|
let stopped = false;
|
|
197
199
|
let sawSignal = false;
|
|
@@ -101,7 +101,8 @@ function createServerSessionHost(options) {
|
|
|
101
101
|
text,
|
|
102
102
|
requestId: crypto.randomUUID(),
|
|
103
103
|
...add?.activatedSkills ? { activatedSkills: add.activatedSkills } : {},
|
|
104
|
-
...add?.attachments ? { attachments: add.attachments } : {}
|
|
104
|
+
...add?.attachments ? { attachments: add.attachments } : {},
|
|
105
|
+
...add?.delivery ? { delivery: add.delivery } : {}
|
|
105
106
|
})).delivered,
|
|
106
107
|
remove: queue.drop,
|
|
107
108
|
update: queue.edit,
|
|
@@ -26,7 +26,8 @@ function providerOf(auth) {
|
|
|
26
26
|
detail: m.detail,
|
|
27
27
|
active: m.active,
|
|
28
28
|
...m.key ? { key: m.key } : {},
|
|
29
|
-
...m.label ? { label: m.label } : {}
|
|
29
|
+
...m.label ? { label: m.label } : {},
|
|
30
|
+
...m.email ? { email: m.email } : {}
|
|
30
31
|
})),
|
|
31
32
|
...auth.customFields ? { customFields: auth.customFields } : {},
|
|
32
33
|
...auth.configuredFields ? { configuredFields: auth.configuredFields } : {}
|
|
@@ -50,10 +50,10 @@ function createServerSettingsHost(options) {
|
|
|
50
50
|
});
|
|
51
51
|
client.onOpen(() => void refresh());
|
|
52
52
|
if (client.state().status === "open") refresh();
|
|
53
|
-
const
|
|
54
|
-
const writableStore = writable(canWrite());
|
|
53
|
+
const writableStore = writable(client.state().status === "open" ? client.can("settings.set") : true);
|
|
55
54
|
client.onState(() => {
|
|
56
|
-
|
|
55
|
+
if (client.state().status !== "open") return;
|
|
56
|
+
const next = client.can("settings.set");
|
|
57
57
|
if (writableStore.get() !== next) writableStore.set(next);
|
|
58
58
|
});
|
|
59
59
|
const update = async (patch) => {
|
|
@@ -164,7 +164,15 @@ function createSseHost(options) {
|
|
|
164
164
|
}
|
|
165
165
|
},
|
|
166
166
|
transcript: (sessionId) => state.readable(sessionId),
|
|
167
|
-
async send({ sessionId, prompt, attachments, model, thinking }) {
|
|
167
|
+
async send({ sessionId, prompt, attachments, model, thinking, delivery }) {
|
|
168
|
+
if (delivery) {
|
|
169
|
+
const path = delivery === "steer" ? "steer" : "follow-up";
|
|
170
|
+
const body = {
|
|
171
|
+
message: prompt,
|
|
172
|
+
...attachments?.length ? { attachments } : {}
|
|
173
|
+
};
|
|
174
|
+
if ((await request("POST", `/sessions/${sid(sessionId)}/${path}`, body)).ok) return;
|
|
175
|
+
}
|
|
168
176
|
state.echoPrompt(sessionId, prompt, attachments);
|
|
169
177
|
try {
|
|
170
178
|
await request("POST", `/sessions/${sid(sessionId)}/send`, {
|
|
@@ -184,12 +192,6 @@ function createSseHost(options) {
|
|
|
184
192
|
interrupt(sessionId) {
|
|
185
193
|
request("POST", `/sessions/${sid(sessionId)}/interrupt`).catch(onError);
|
|
186
194
|
},
|
|
187
|
-
async steer(sessionId, message) {
|
|
188
|
-
return (await request("POST", `/sessions/${sid(sessionId)}/steer`, { message })).ok;
|
|
189
|
-
},
|
|
190
|
-
async followUp(sessionId, message) {
|
|
191
|
-
return (await request("POST", `/sessions/${sid(sessionId)}/follow-up`, { message })).ok;
|
|
192
|
-
},
|
|
193
195
|
interactions: {
|
|
194
196
|
pending: state.pending,
|
|
195
197
|
async answer(requestId, response) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createFixtureAppearanceHost } from "../../appearance/headless/fixture-host.js";
|
|
2
1
|
import { createFixtureApprovalsHost } from "../../approvals/headless/fixture-host.js";
|
|
2
|
+
import { createFixtureAppearanceHost } from "../../appearance/headless/fixture-host.js";
|
|
3
3
|
import { createFixtureHost } from "../../chat/headless/fixture-host.js";
|
|
4
4
|
import { createFixtureComposerHost } from "../../chat/testing/fixture-composer-host.js";
|
|
5
5
|
import { createFixtureSidebarHost } from "../../chat/testing/fixture-sidebar-host.js";
|
|
@@ -36,7 +36,7 @@ function SessionGroupHeader({ id, label, collapsed, icon, action, onToggle, onOp
|
|
|
36
36
|
onClick: () => onOpen(id),
|
|
37
37
|
"aria-current": active ? "page" : void 0,
|
|
38
38
|
title: labels.openGroup(label),
|
|
39
|
-
className: `zg-press-soft flex flex-1 items-center min-w-0 bg-transparent border-0 cursor-pointer p-0 font-medium hover:text-[var(--zg-text)] transition-colors ${active ? "text-[var(--zg-text)]" : "text-[var(--zg-text-muted)]"}`,
|
|
39
|
+
className: `zg-press-soft flex flex-1 items-center min-w-0 bg-transparent border-0 cursor-pointer p-0 font-medium hover:text-[var(--zg-text)] transition-colors ${active ? "text-[var(--zg-text)]" : "text-[var(--zg-sidebar-label-fg,var(--zg-text-muted))]"}`,
|
|
40
40
|
children: /* @__PURE__ */ jsx("span", {
|
|
41
41
|
className: "truncate",
|
|
42
42
|
children: label
|
|
@@ -65,7 +65,7 @@ function SessionGroupHeader({ id, label, collapsed, icon, action, onToggle, onOp
|
|
|
65
65
|
onClick: () => onToggle(id),
|
|
66
66
|
"aria-expanded": !collapsed,
|
|
67
67
|
title: toggleTitle,
|
|
68
|
-
className: "zg-press-soft flex flex-1 items-center gap-1 min-w-0 bg-transparent border-0 cursor-pointer p-0 font-medium text-[var(--zg-text-muted)] hover:text-[var(--zg-text)] transition-colors",
|
|
68
|
+
className: "zg-press-soft flex flex-1 items-center gap-1 min-w-0 bg-transparent border-0 cursor-pointer p-0 font-medium text-[var(--zg-sidebar-label-fg,var(--zg-text-muted))] hover:text-[var(--zg-text)] transition-colors",
|
|
69
69
|
children: [/* @__PURE__ */ jsx("span", {
|
|
70
70
|
className: "truncate",
|
|
71
71
|
children: label
|
|
@@ -12,7 +12,7 @@ function SessionHostHeader({ id, host, collapsed, onToggle, className = "", styl
|
|
|
12
12
|
const labels = useChatLabels();
|
|
13
13
|
const offline = !hostReachable(host);
|
|
14
14
|
return /* @__PURE__ */ jsxs("li", {
|
|
15
|
-
className: `group flex items-center gap-1.5 -mx-2 px-3 pt-4 pb-0.5 text-zg-meta
|
|
15
|
+
className: `group flex items-center gap-1.5 -mx-2 px-3 pt-4 pb-0.5 text-zg-meta text-[var(--zg-text-muted)] select-none ${className}`,
|
|
16
16
|
style,
|
|
17
17
|
"data-host-id": host.id,
|
|
18
18
|
children: [/* @__PURE__ */ jsx("span", {
|
package/dist/chat/StatusDot.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { ZidaneMark } from "../components/ZidaneMark.js";
|
|
1
2
|
import { Presence } from "../motion/swap.js";
|
|
2
3
|
import { useChatLabels } from "./useChatLabels.js";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { memo } from "react";
|
|
5
6
|
//#region src/chat/StatusDot.tsx
|
|
6
7
|
/**
|
|
7
|
-
* A session's state as one 8px dot: idle ring,
|
|
8
|
-
*
|
|
8
|
+
* A session's state as one 8px dot: idle ring, the zidane mark (its bands
|
|
9
|
+
* flowing) while it runs, a rippling "needs you" for a pending approval, a breathing
|
|
9
10
|
* "awaiting input", an attached ring, a popping completion, a glowing error.
|
|
10
|
-
* Three layers (core,
|
|
11
|
-
* snapping (`.zg-state-tween`, styles/motion.css). The
|
|
12
|
-
*
|
|
11
|
+
* Three layers (core, mark, ripple) so every change tweens instead of
|
|
12
|
+
* snapping (`.zg-state-tween`, styles/motion.css). The mark spins out of
|
|
13
|
+
* the dot as the core swells into it, and back (`.zg-mark-swap`); it stills itself
|
|
14
|
+
* under reduced / off motion; the other loops (ripple, breathe) keep their own fixed timing and carry `.zg-loop`, which
|
|
13
15
|
* stills them under reduced / off motion; the completion pop is the `snap`
|
|
14
16
|
* spring with its opacity on `fade`. Colours are the decorative `-dot`
|
|
15
17
|
* tones (a fill, not text: vivid in light rather than held to text floors).
|
|
@@ -35,9 +37,10 @@ const STYLES = {
|
|
|
35
37
|
label: "dotRunning",
|
|
36
38
|
style: {
|
|
37
39
|
background: "var(--zg-accent-dot)",
|
|
38
|
-
transform: "scale(
|
|
40
|
+
transform: "scale(1.5)",
|
|
41
|
+
opacity: 0
|
|
39
42
|
},
|
|
40
|
-
ring: "
|
|
43
|
+
ring: "mark"
|
|
41
44
|
},
|
|
42
45
|
pending: {
|
|
43
46
|
label: "dotPending",
|
|
@@ -75,13 +78,11 @@ const STYLES = {
|
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
};
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
animation: "zg-orbit 1.1s linear infinite"
|
|
84
|
-
};
|
|
81
|
+
const MARK_STYLE = { color: "var(--zg-accent-dot)" };
|
|
82
|
+
/** The mark grows from the dot's own size (8px of its 12px). */
|
|
83
|
+
const MARK_SWAP_STYLE = { "--zg-mark-from": 2 / 3 };
|
|
84
|
+
/** Lively but under the composer's pace: a list of running sessions should not shimmer. */
|
|
85
|
+
const MARK_SPEED = .9;
|
|
85
86
|
const RIPPLE_STYLE = {
|
|
86
87
|
background: "var(--zg-warn-dot)",
|
|
87
88
|
animation: "zg-dot 1.8s ease-in-out infinite"
|
|
@@ -104,12 +105,15 @@ function StatusDotInner({ state, label, className, style }) {
|
|
|
104
105
|
"data-core": true
|
|
105
106
|
}),
|
|
106
107
|
/* @__PURE__ */ jsx(Presence, {
|
|
107
|
-
show: cfg.ring === "
|
|
108
|
-
className: "absolute -inset-[
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
show: cfg.ring === "mark",
|
|
109
|
+
className: "zg-mark-swap absolute -inset-[2px]",
|
|
110
|
+
style: MARK_SWAP_STYLE,
|
|
111
|
+
children: /* @__PURE__ */ jsx(ZidaneMark, {
|
|
112
|
+
size: 12,
|
|
113
|
+
speed: MARK_SPEED,
|
|
114
|
+
tone: "inherit",
|
|
115
|
+
decorative: true,
|
|
116
|
+
style: MARK_STYLE
|
|
113
117
|
})
|
|
114
118
|
}),
|
|
115
119
|
/* @__PURE__ */ jsx(Presence, {
|
|
@@ -3,29 +3,34 @@ import { ComposerBadge } from "./ComposerBadges.js";
|
|
|
3
3
|
import "./attachment-ingest.js";
|
|
4
4
|
import { SuggestionSource } from "./suggestions.js";
|
|
5
5
|
import { CSSProperties, ReactNode } from "react";
|
|
6
|
+
import { MessageDelivery } from "zidane/chat";
|
|
6
7
|
//#region src/chat/composer/Composer.d.ts
|
|
7
|
-
/** What the composer hands to `onSend
|
|
8
|
+
/** What the composer hands to `onSend`. */
|
|
8
9
|
interface ComposerSubmit {
|
|
9
10
|
/** The draft as plain text. Mentions and commands are serialized to text. */
|
|
10
11
|
prompt: string;
|
|
11
12
|
/** The staged attachments. */
|
|
12
13
|
attachments: readonly ComposerAttachment[];
|
|
14
|
+
/** Set while `running`: steer the draft into the run, or queue it for when the run ends. */
|
|
15
|
+
delivery?: MessageDelivery;
|
|
13
16
|
}
|
|
14
17
|
/** `false` (or a rejection) keeps the draft: the composer puts it back. */
|
|
15
18
|
type ComposerResult = void | boolean | Promise<void | boolean>;
|
|
16
19
|
/** Props for {@link Composer}. */
|
|
17
20
|
interface ComposerProps {
|
|
18
|
-
/** Called when the user sends the draft with Enter or the Send button. Return `false` or reject to put the draft back. */
|
|
21
|
+
/** Called when the user sends the draft with Enter or the Send button, also while `running` (see `ComposerSubmit.delivery`). Return `false` or reject to put the draft back. */
|
|
19
22
|
onSend(input: ComposerSubmit): ComposerResult;
|
|
20
|
-
/** A run is in flight: Enter steers or queues, Escape and the button stop. */
|
|
23
|
+
/** A run is in flight: Enter steers or queues (`delivery`), Escape and the button stop. */
|
|
21
24
|
running?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* What Enter does with the draft while `running`: `steer` it into the run or `queue` it for when the run ends.
|
|
27
|
+
* Alt+Enter does the other. Pass the user's "Messages during a run" setting.
|
|
28
|
+
* @default 'queue'
|
|
29
|
+
*/
|
|
30
|
+
delivery?: MessageDelivery;
|
|
22
31
|
/** Called when the user stops the running agent with Escape or the Stop button. Without it the composer offers no stop. */
|
|
23
32
|
onStop?(): void;
|
|
24
|
-
/**
|
|
25
|
-
onSteer?(input: ComposerSubmit): ComposerResult;
|
|
26
|
-
/** Enter while running (without `onSteer`) holds the draft for when the run ends. */
|
|
27
|
-
onQueue?(input: ComposerSubmit): ComposerResult;
|
|
28
|
-
/** A send / steer / queue handler threw (the draft is already back). Absent: the composer shows the error itself. */
|
|
33
|
+
/** The send handler threw (the draft is already back). Absent: the composer shows the error itself. */
|
|
29
34
|
onError?(error: unknown): void;
|
|
30
35
|
/** Controlled text of the draft. Use with `onChange`. Omit for an uncontrolled draft. */
|
|
31
36
|
value?: string;
|
|
@@ -11,6 +11,7 @@ import { SendButton } from "./SendButton.js";
|
|
|
11
11
|
import { SuggestionPopup } from "./suggestions.js";
|
|
12
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
14
|
+
import { otherDelivery } from "zidane/chat/pure";
|
|
14
15
|
//#region src/chat/composer/Composer.tsx
|
|
15
16
|
const serialize = (editor) => editor.getText({ blockSeparator: "\n" });
|
|
16
17
|
/** Renders the chat prompt box: a text editor with staged attachments, a controls row, and Send or Stop. Enter sends, Shift+Enter adds a line, Escape stops a running agent, and the arrow keys recall earlier prompts. */
|
|
@@ -19,7 +20,8 @@ const Composer = forwardRef(function Composer(props, ref) {
|
|
|
19
20
|
const labels = useChatLabels();
|
|
20
21
|
const propsRef = useRef(props);
|
|
21
22
|
propsRef.current = props;
|
|
22
|
-
const
|
|
23
|
+
const runningPlaceholder = (props.delivery ?? "queue") === "steer" ? labels.composerPlaceholderSteer : labels.composerPlaceholderQueue;
|
|
24
|
+
const placeholder = props.placeholder ?? (running ? runningPlaceholder : labels.composerPlaceholder);
|
|
23
25
|
const placeholderRef = useRef(placeholder);
|
|
24
26
|
placeholderRef.current = placeholder;
|
|
25
27
|
const nameRef = useRef(props.ariaLabel ?? labels.composerLabel);
|
|
@@ -111,17 +113,18 @@ const Composer = forwardRef(function Composer(props, ref) {
|
|
|
111
113
|
editor.commands.setContent(plainTextToDocJson(props.value), { emitUpdate: false });
|
|
112
114
|
setText(props.value);
|
|
113
115
|
}, [editor, props.value]);
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
116
|
+
const delivery = props.delivery ?? "queue";
|
|
117
|
+
const route = running ? delivery : "send";
|
|
118
|
+
const canSend = !disabled && (text.trim() !== "" || ingest.attachments.length > 0);
|
|
119
|
+
const submit = useCallback(async (alternate = false) => {
|
|
117
120
|
const p = propsRef.current;
|
|
118
121
|
if (!editor) shell.current.submit = true;
|
|
119
122
|
if (!editor || p.disabled) return;
|
|
120
123
|
const prompt = serialize(editor).trim();
|
|
121
124
|
const { attachments: staged, clear, restore: putBack } = ingestRef.current;
|
|
122
125
|
if (!prompt && staged.length === 0) return;
|
|
123
|
-
const
|
|
124
|
-
|
|
126
|
+
const preferred = p.delivery ?? "queue";
|
|
127
|
+
const sending = p.running ? alternate ? otherDelivery(preferred) : preferred : void 0;
|
|
125
128
|
setSendError(null);
|
|
126
129
|
const draft = editor.getJSON();
|
|
127
130
|
editor.commands.clearContent(true);
|
|
@@ -134,9 +137,10 @@ const Composer = forwardRef(function Composer(props, ref) {
|
|
|
134
137
|
putBack(staged);
|
|
135
138
|
};
|
|
136
139
|
try {
|
|
137
|
-
if (await
|
|
140
|
+
if (await p.onSend({
|
|
138
141
|
prompt,
|
|
139
|
-
attachments: staged
|
|
142
|
+
attachments: staged,
|
|
143
|
+
...sending ? { delivery: sending } : {}
|
|
140
144
|
}) === false) restore();
|
|
141
145
|
} catch (error) {
|
|
142
146
|
restore();
|
|
@@ -171,7 +175,7 @@ const Composer = forwardRef(function Composer(props, ref) {
|
|
|
171
175
|
if (event.key === "Enter") {
|
|
172
176
|
if (event.shiftKey) return editor.commands.splitBlock();
|
|
173
177
|
event.preventDefault();
|
|
174
|
-
submit();
|
|
178
|
+
submit(event.altKey);
|
|
175
179
|
return true;
|
|
176
180
|
}
|
|
177
181
|
if (event.key === "Escape" && p.running && p.onStop) {
|
|
@@ -300,7 +304,7 @@ const Composer = forwardRef(function Composer(props, ref) {
|
|
|
300
304
|
const p = propsRef.current;
|
|
301
305
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
302
306
|
event.preventDefault();
|
|
303
|
-
submit();
|
|
307
|
+
submit(event.altKey);
|
|
304
308
|
} else if (event.key === "Escape" && p.running && p.onStop) p.onStop();
|
|
305
309
|
},
|
|
306
310
|
onPaste: (event) => ingestRef.current.paste(event),
|
|
@@ -3,7 +3,7 @@ import { ThinkingLevel } from "./ModelOptionsPicker.js";
|
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
//#region src/chat/composer/HostComposer.d.ts
|
|
5
5
|
/** Props for {@link HostComposer}: the {@link ComposerProps} the host does not drive, plus `sessionId`, `defaultModel`, `defaultThinking`, and `footer`. */
|
|
6
|
-
interface HostComposerProps extends Omit<ComposerProps, 'onSend' | 'running' | 'onStop' | '
|
|
6
|
+
interface HostComposerProps extends Omit<ComposerProps, 'onSend' | 'running' | 'onStop' | 'footer'> {
|
|
7
7
|
/** Id of the session the composer sends to. */
|
|
8
8
|
sessionId: string;
|
|
9
9
|
/** The model to start on (`provider/model` or a bare id). Default: the session's, else the first offered. */
|
|
@@ -10,7 +10,8 @@ import { useMemo, useState } from "react";
|
|
|
10
10
|
* `Composer` over a `ChatHost` for one session: Enter sends through
|
|
11
11
|
* `host.send` (with the picked model and reasoning effort), Stop calls
|
|
12
12
|
* `host.interrupt`, and while the session's transcript is running Enter
|
|
13
|
-
*
|
|
13
|
+
* sends with `delivery` (steer into the run or queue for its end; Alt+Enter
|
|
14
|
+
* the other), attachments included. The model and
|
|
14
15
|
* options (reasoning effort) pickers show when the host offers `models`
|
|
15
16
|
* (options only for a model with `thinking: true`). A capability the host leaves out hides
|
|
16
17
|
* its control; without `send` there is no composer.
|
|
@@ -22,11 +23,12 @@ import { useMemo, useState } from "react";
|
|
|
22
23
|
const NO_MODELS = constant([]);
|
|
23
24
|
const NO_OPTIONS = [];
|
|
24
25
|
const NO_ENABLED = {};
|
|
25
|
-
const toWire = ({ attachments }) => attachments.map(({ name, mediaType, size, dataUrl }) => ({
|
|
26
|
+
const toWire = ({ attachments }) => attachments.map(({ name, mediaType, size, dataUrl, original }) => ({
|
|
26
27
|
name,
|
|
27
28
|
mediaType,
|
|
28
29
|
size,
|
|
29
|
-
dataUrl
|
|
30
|
+
dataUrl,
|
|
31
|
+
...original ? { original } : {}
|
|
30
32
|
}));
|
|
31
33
|
/**
|
|
32
34
|
* Renders a {@link Composer} bound to the {@link ChatHost} in context for one session. Controls the host leaves out are hidden; without `send` nothing renders.
|
|
@@ -49,12 +51,9 @@ function HostComposer({ sessionId, defaultModel, defaultThinking = "medium", foo
|
|
|
49
51
|
prompt: input.prompt,
|
|
50
52
|
...input.attachments.length ? { attachments: toWire(input) } : {},
|
|
51
53
|
...choice ? { model: choice.key } : {},
|
|
52
|
-
...choice?.model.thinking ? { thinking } : {}
|
|
54
|
+
...choice?.model.thinking ? { thinking } : {},
|
|
55
|
+
...input.delivery ? { delivery: input.delivery } : {}
|
|
53
56
|
});
|
|
54
|
-
const relay = (fn) => async (input) => {
|
|
55
|
-
if (input.attachments.length > 0) return false;
|
|
56
|
-
if (!await fn(sessionId, input.prompt)) await send(input);
|
|
57
|
-
};
|
|
58
57
|
const pickers = host.models && providers.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
59
58
|
className: "zg-segment min-w-0 max-w-full",
|
|
60
59
|
children: [/* @__PURE__ */ jsx(ModelPicker, {
|
|
@@ -80,8 +79,6 @@ function HostComposer({ sessionId, defaultModel, defaultThinking = "medium", foo
|
|
|
80
79
|
running,
|
|
81
80
|
onSend: send,
|
|
82
81
|
...host.interrupt ? { onStop: () => host.interrupt(sessionId) } : {},
|
|
83
|
-
...host.steer ? { onSteer: relay(host.steer.bind(host)) } : {},
|
|
84
|
-
...host.followUp ? { onQueue: relay(host.followUp.bind(host)) } : {},
|
|
85
82
|
footer: pickers || footer ? /* @__PURE__ */ jsxs(Fragment, { children: [pickers, footer] }) : void 0
|
|
86
83
|
});
|
|
87
84
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ZidaneMark } from "../../components/ZidaneMark.js";
|
|
1
2
|
import { RollingText } from "../../motion/roll.js";
|
|
2
3
|
import { SmoothWidth } from "../../motion/size.js";
|
|
3
4
|
import { Presence, useCadencedValue } from "../../motion/swap.js";
|
|
@@ -11,8 +12,10 @@ import { EMPTY_RATE, fmtTokens, formatElapsed, stepStreamRate, streamRateValue }
|
|
|
11
12
|
/**
|
|
12
13
|
* The ONE live indicator for a running turn, set into the composer card:
|
|
13
14
|
*
|
|
14
|
-
* [1m 29s] [↓72.2k] [41 tok/s] [~$0.123] (one track chip per value)
|
|
15
|
+
* (mark) [1m 29s] [↓72.2k] [41 tok/s] [~$0.123] (one track chip per value)
|
|
15
16
|
*
|
|
17
|
+
* The zidane mark leads the row: its bands flow fast while text streams and
|
|
18
|
+
* ease down to a slower pace between turns (tools, waiting on the model).
|
|
16
19
|
* Visible from `run:start` to `run:end` (one fade in, one fade out), never
|
|
17
20
|
* in between: the elapsed counter and the run-wide output count are always
|
|
18
21
|
* defined, so tool calls between streaming turns do not blank the chip.
|
|
@@ -31,6 +34,9 @@ import { EMPTY_RATE, fmtTokens, formatElapsed, stepStreamRate, streamRateValue }
|
|
|
31
34
|
* elapsed time reads the host's run counters (`ComposerHost.runs`).
|
|
32
35
|
*/
|
|
33
36
|
const CHIP_CADENCE_MS = 320;
|
|
37
|
+
/** The mark's loops per second: streaming text, and the rest of the run. */
|
|
38
|
+
const MARK_STREAMING = 2.2;
|
|
39
|
+
const MARK_WORKING = 1.1;
|
|
34
40
|
/**
|
|
35
41
|
* The run's live indicator (see the header).
|
|
36
42
|
*
|
|
@@ -57,6 +63,12 @@ function RunStatusChip({ sessionId, running, progress, settledOutputTokens, cost
|
|
|
57
63
|
children: /* @__PURE__ */ jsxs("span", {
|
|
58
64
|
className: "inline-flex items-center gap-1 whitespace-nowrap",
|
|
59
65
|
children: [
|
|
66
|
+
/* @__PURE__ */ jsx(ZidaneMark, {
|
|
67
|
+
size: 14,
|
|
68
|
+
speed: progress ? MARK_STREAMING : MARK_WORKING,
|
|
69
|
+
decorative: true,
|
|
70
|
+
className: "zg-mark-in mx-0.5"
|
|
71
|
+
}),
|
|
60
72
|
/* @__PURE__ */ jsx(Segment, { children: /* @__PURE__ */ jsx(RollingText, {
|
|
61
73
|
text: formatElapsed(elapsedSec),
|
|
62
74
|
value: elapsedSec
|
|
@@ -56,7 +56,10 @@ declare function shouldFoldPasteToAttachment(text: string): boolean;
|
|
|
56
56
|
/**
|
|
57
57
|
* Reads a file into a {@link ComposerAttachment} with a `data:` URL, as the
|
|
58
58
|
* shared rule says (`planAttachment`): images, PDFs and text attach; an image
|
|
59
|
-
* in another format, or over the image cap, is re-encoded to fit
|
|
59
|
+
* in another format, or over the image cap, is re-encoded to fit, and keeps
|
|
60
|
+
* the file as picked in `original` (up to the file cap) so the host can save
|
|
61
|
+
* it for the agent and say it was converted. An animated GIF under the cap
|
|
62
|
+
* goes as it is, every frame. Rejects with
|
|
60
63
|
* an {@link AttachmentRefusedError} for a file the rule refuses (or an image
|
|
61
64
|
* that cannot be decoded), and with the read error when the file cannot be read.
|
|
62
65
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ATTACHMENT_SNIFF_BYTES, MAX_FILE_ATTACHMENT_BYTES, MAX_IMAGE_ATTACHMENT_BYTES, MAX_IMAGE_ATTACHMENT_BYTES as MAX_IMAGE_ATTACHMENT_BYTES$1, formatBytes, formatBytes as formatBytes$1, planAttachment } from "zidane/chat/pure";
|
|
1
|
+
import { ATTACHMENT_SNIFF_BYTES, MAX_FILE_ATTACHMENT_BYTES, MAX_FILE_ATTACHMENT_BYTES as MAX_FILE_ATTACHMENT_BYTES$1, MAX_IMAGE_ATTACHMENT_BYTES, MAX_IMAGE_ATTACHMENT_BYTES as MAX_IMAGE_ATTACHMENT_BYTES$1, formatBytes, formatBytes as formatBytes$1, planAttachment } from "zidane/chat/pure";
|
|
2
2
|
//#region src/chat/composer/attachment-ingest.ts
|
|
3
3
|
/**
|
|
4
4
|
* Attachment-ingest helpers, shared by the kit's `Composer` and the desktop
|
|
@@ -75,7 +75,10 @@ function shouldFoldPasteToAttachment(text) {
|
|
|
75
75
|
/**
|
|
76
76
|
* Reads a file into a {@link ComposerAttachment} with a `data:` URL, as the
|
|
77
77
|
* shared rule says (`planAttachment`): images, PDFs and text attach; an image
|
|
78
|
-
* in another format, or over the image cap, is re-encoded to fit
|
|
78
|
+
* in another format, or over the image cap, is re-encoded to fit, and keeps
|
|
79
|
+
* the file as picked in `original` (up to the file cap) so the host can save
|
|
80
|
+
* it for the agent and say it was converted. An animated GIF under the cap
|
|
81
|
+
* goes as it is, every frame. Rejects with
|
|
79
82
|
* an {@link AttachmentRefusedError} for a file the rule refuses (or an image
|
|
80
83
|
* that cannot be decoded), and with the read error when the file cannot be read.
|
|
81
84
|
*
|
|
@@ -94,19 +97,25 @@ async function readAttachment(file) {
|
|
|
94
97
|
if (plan.kind === "convert-image") {
|
|
95
98
|
const converted = await convertImage(file, plan.mediaType).catch(() => null);
|
|
96
99
|
if (!converted) throw new AttachmentRefusedError(name, { reason: "unreadable-image" });
|
|
97
|
-
|
|
100
|
+
const attachment = await toComposerAttachment(converted, renameForType(name, converted.type), converted.type);
|
|
101
|
+
if (file.size > MAX_FILE_ATTACHMENT_BYTES$1) return attachment;
|
|
102
|
+
const original = await readDataUrl(file);
|
|
103
|
+
return {
|
|
104
|
+
...attachment,
|
|
105
|
+
original: {
|
|
106
|
+
name,
|
|
107
|
+
mediaType: plan.mediaType,
|
|
108
|
+
size: file.size,
|
|
109
|
+
dataUrl: original
|
|
110
|
+
}
|
|
111
|
+
};
|
|
98
112
|
}
|
|
99
113
|
return toComposerAttachment(file, name, plan.mediaType);
|
|
100
114
|
}
|
|
101
115
|
/** The data URL's header carries `mediaType`: the send path reads the type from there. */
|
|
102
116
|
async function toComposerAttachment(blob, name, mediaType) {
|
|
103
117
|
const typed = blob.type === mediaType ? blob : new Blob([blob], { type: mediaType });
|
|
104
|
-
const dataUrl = await
|
|
105
|
-
const reader = new FileReader();
|
|
106
|
-
reader.onload = () => resolve(reader.result);
|
|
107
|
-
reader.onerror = () => reject(reader.error);
|
|
108
|
-
reader.readAsDataURL(typed);
|
|
109
|
-
});
|
|
118
|
+
const dataUrl = await readDataUrl(typed);
|
|
110
119
|
return {
|
|
111
120
|
id: crypto.randomUUID(),
|
|
112
121
|
name,
|
|
@@ -115,12 +124,20 @@ async function toComposerAttachment(blob, name, mediaType) {
|
|
|
115
124
|
size: typed.size
|
|
116
125
|
};
|
|
117
126
|
}
|
|
127
|
+
function readDataUrl(blob) {
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const reader = new FileReader();
|
|
130
|
+
reader.onload = () => resolve(reader.result);
|
|
131
|
+
reader.onerror = () => reject(reader.error);
|
|
132
|
+
reader.readAsDataURL(blob);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
118
135
|
/** Largest edge a converted image keeps (Anthropic's ceiling). */
|
|
119
136
|
const MAX_IMAGE_EDGE = 8e3;
|
|
120
137
|
/**
|
|
121
138
|
* Decode an image and encode it again, as PNG (JPEG for a JPEG: a photo grows
|
|
122
139
|
* as PNG), scaled down until it fits the image cap. An animated GIF or WebP
|
|
123
|
-
* keeps its first frame. Rejects when the image cannot be decoded (HEIC and
|
|
140
|
+
* over the cap keeps its first frame (`original` keeps them all). Rejects when the image cannot be decoded (HEIC and
|
|
124
141
|
* TIFF in Chromium) or there is no DOM to draw it in.
|
|
125
142
|
*/
|
|
126
143
|
async function convertImage(file, sourceType) {
|