@opengeni/react 0.29.0 → 0.33.1
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/approvals.d.ts +14 -0
- package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
- package/dist/chunk-4QOQ2DSR.js.map +1 -0
- package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
- package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
- package/dist/{chunk-CSJBF3QJ.js → chunk-A2WITZTI.js} +377 -113
- package/dist/chunk-A2WITZTI.js.map +1 -0
- package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
- package/dist/{chunk-AVLMIWYO.js → chunk-KZRUYCL4.js} +12 -6
- package/dist/chunk-KZRUYCL4.js.map +1 -0
- package/dist/{chunk-IPVOVOW6.js → chunk-MS3FBZ6A.js} +2539 -2174
- package/dist/chunk-MS3FBZ6A.js.map +1 -0
- package/dist/{chunk-OKXCECLG.js → chunk-TCCE5ZU5.js} +171 -22
- package/dist/chunk-TCCE5ZU5.js.map +1 -0
- package/dist/chunk-U255M5EZ.js +178 -0
- package/dist/chunk-U255M5EZ.js.map +1 -0
- package/dist/chunk-ZNNE2VES.js +6413 -0
- package/dist/chunk-ZNNE2VES.js.map +1 -0
- package/dist/client.d.ts +33 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/registry.d.ts +41 -0
- package/dist/commands/types.d.ts +85 -0
- package/dist/components/approval-surface.d.ts +28 -0
- package/dist/components/chat-composer.d.ts +51 -0
- package/dist/components/code-editor.d.ts +43 -0
- package/dist/components/command-palette.d.ts +25 -0
- package/dist/components/composer-transcription-control.d.ts +36 -0
- package/dist/components/composer.d.ts +277 -0
- package/dist/components/copy-button.d.ts +26 -0
- package/dist/components/desktop-viewer.d.ts +85 -0
- package/dist/components/diff-view.d.ts +38 -0
- package/dist/components/enrollment-consent.d.ts +33 -0
- package/dist/components/enrollment-device-flow.d.ts +27 -0
- package/dist/components/file-browser.d.ts +47 -0
- package/dist/components/fleet-tile.d.ts +18 -0
- package/dist/components/human-input-form.d.ts +47 -0
- package/dist/components/machine-card.d.ts +21 -0
- package/dist/components/machine-dock-bar.d.ts +31 -0
- package/dist/components/machine-health-pill.d.ts +9 -0
- package/dist/components/machine-metrics.d.ts +15 -0
- package/dist/components/machine-status-pill.d.ts +54 -0
- package/dist/components/machines/health.d.ts +25 -0
- package/dist/components/machines/machine-detail.d.ts +14 -0
- package/dist/components/machines/metric-history-chart.d.ts +24 -0
- package/dist/components/machines/metric-sparkline.d.ts +9 -0
- package/dist/components/machines/series.d.ts +24 -0
- package/dist/components/machines-dashboard.d.ts +30 -0
- package/dist/components/markdown.d.ts +31 -0
- package/dist/components/message-timeline.d.ts +98 -0
- package/dist/components/model-picker.d.ts +19 -0
- package/dist/components/pierre-diff.d.ts +34 -0
- package/dist/components/pierre-file.d.ts +27 -0
- package/dist/components/queue-draft-policy.d.ts +7 -0
- package/dist/components/queue-surface-implementation.d.ts +20 -0
- package/dist/components/queue-surface-state.d.ts +12 -0
- package/dist/components/queue-surface.d.ts +12 -0
- package/dist/components/sandbox-files.d.ts +49 -0
- package/dist/components/sandbox-terminal.d.ts +123 -0
- package/dist/components/sandbox-workspace.d.ts +128 -0
- package/dist/components/session-chrome.d.ts +70 -0
- package/dist/components/session-status.d.ts +25 -0
- package/dist/components/soften-streaming-markdown.d.ts +13 -0
- package/dist/components/stream-reveal.d.ts +37 -0
- package/dist/components/tip-follow.d.ts +139 -0
- package/dist/components/tooltip.d.ts +16 -0
- package/dist/components/workbench-changes.d.ts +34 -0
- package/dist/components/workspace-dock.d.ts +35 -0
- package/dist/composer.d.ts +11 -7
- package/dist/composer.js +9 -6
- package/dist/hooks/internal.d.ts +46 -0
- package/dist/hooks/use-available-models.d.ts +37 -0
- package/dist/hooks/use-billing-usage.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +69 -0
- package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
- package/dist/hooks/use-desktop-stream.d.ts +43 -0
- package/dist/hooks/use-environments.d.ts +35 -0
- package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
- package/dist/hooks/use-goal.d.ts +46 -0
- package/dist/hooks/use-human-input.d.ts +26 -0
- package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
- package/dist/hooks/use-machine-chip.d.ts +54 -0
- package/dist/hooks/use-machines.d.ts +81 -0
- package/dist/hooks/use-packs.d.ts +27 -0
- package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
- package/dist/hooks/use-rigs.d.ts +87 -0
- package/dist/hooks/use-sandbox-files.d.ts +108 -0
- package/dist/hooks/use-sandbox-git.d.ts +58 -0
- package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
- package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
- package/dist/hooks/use-session-capabilities.d.ts +83 -0
- package/dist/hooks/use-session-control.d.ts +22 -0
- package/dist/hooks/use-session-events.d.ts +92 -0
- package/dist/hooks/use-session-lineage.d.ts +17 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
- package/dist/hooks/use-session.d.ts +23 -0
- package/dist/hooks/use-slash-commands.d.ts +66 -0
- package/dist/hooks/use-terminal-stream.d.ts +46 -0
- package/dist/hooks/use-transcription.d.ts +73 -0
- package/dist/hooks/use-turn-queue.d.ts +44 -0
- package/dist/hooks/use-voice-input.d.ts +25 -0
- package/dist/hooks/use-windowed-sections.d.ts +42 -0
- package/dist/hooks/use-workspace-capture.d.ts +49 -0
- package/dist/hooks/use-workspace-edit.d.ts +73 -0
- package/dist/hooks/use-workspace-sessions.d.ts +30 -0
- package/dist/hooks/use-workspaces.d.ts +22 -0
- package/dist/human-input.d.ts +18 -0
- package/dist/index.d.ts +144 -2119
- package/dist/index.js +421 -324
- package/dist/index.js.map +1 -1
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/format.d.ts +48 -0
- package/dist/lib/git-patch.d.ts +8 -0
- package/dist/lib/motion-inspect.d.ts +5 -0
- package/dist/lib/motion.d.ts +14 -0
- package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
- package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
- package/dist/lib/relay-wire.d.ts +30 -0
- package/dist/lib/use-portal-token-style.d.ts +9 -0
- package/dist/lib/use-theme-type.d.ts +5 -0
- package/dist/lib/use-unicode-fonts.d.ts +12 -0
- package/dist/lib/xterm-renderer.d.ts +19 -0
- package/dist/lib/xterm-theme.d.ts +75 -0
- package/dist/machines.d.ts +30 -413
- package/dist/machines.js +3 -3
- package/dist/model-policy.d.ts +32 -0
- package/dist/model-policy.js +33 -0
- package/dist/model-policy.js.map +1 -0
- package/dist/provider.d.ts +16 -0
- package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
- package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
- package/dist/session-context.d.ts +82 -0
- package/dist/session-ui.d.ts +10 -7
- package/dist/session-ui.js +9 -5
- package/dist/session.d.ts +28 -6
- package/dist/session.js +11 -11
- package/dist/timeline/activity-rail.d.ts +19 -0
- package/dist/timeline/disclosure-context.d.ts +14 -0
- package/dist/timeline/entrance.d.ts +14 -0
- package/dist/timeline/fleet-decision-projection.d.ts +9 -0
- package/dist/timeline/fleet-decision-row.d.ts +11 -0
- package/dist/timeline/fold-memory.d.ts +15 -0
- package/dist/timeline/index.d.ts +18 -0
- package/dist/timeline/parsers.d.ts +96 -0
- package/dist/timeline/projection.d.ts +38 -0
- package/dist/timeline/registry.d.ts +40 -0
- package/dist/timeline/screenshot-lightbox.d.ts +26 -0
- package/dist/timeline/seen-activity-ids.d.ts +2 -0
- package/dist/timeline/shared.d.ts +125 -0
- package/dist/timeline/tool-diff.d.ts +8 -0
- package/dist/timeline/tool-display-name.d.ts +10 -0
- package/dist/timeline/tool-renderers.d.ts +5 -0
- package/dist/timeline/turn-summary.d.ts +116 -0
- package/dist/{use-turn-queue-D_1a8W_l.d.ts → timeline/types.d.ts} +41 -69
- package/dist/types/machines.d.ts +11 -0
- package/package.json +8 -4
- package/src/client.ts +1 -0
- package/src/components/composer-transcription-control.tsx +278 -130
- package/src/components/composer.tsx +20 -15
- package/src/components/copy-button.tsx +127 -0
- package/src/components/markdown.tsx +231 -32
- package/src/components/message-timeline.tsx +1304 -378
- package/src/components/model-picker.tsx +83 -43
- package/src/components/queue-surface-implementation.tsx +1 -1
- package/src/components/session-chrome.tsx +942 -0
- package/src/components/soften-streaming-markdown.ts +205 -0
- package/src/components/stream-reveal.ts +195 -0
- package/src/components/tip-follow.ts +459 -0
- package/src/components/tooltip.tsx +103 -0
- package/src/composer.ts +6 -0
- package/src/hooks/internal.ts +30 -4
- package/src/hooks/use-available-models.ts +51 -4
- package/src/hooks/use-composer.ts +23 -3
- package/src/hooks/use-last-started-turn-policy.ts +95 -0
- package/src/hooks/use-session-events.ts +359 -5
- package/src/hooks/use-voice-input.ts +232 -0
- package/src/index.ts +47 -1
- package/src/lib/clipboard.ts +58 -0
- package/src/lib/format.ts +18 -0
- package/src/lib/motion-inspect.ts +5 -0
- package/src/lib/motion.ts +80 -0
- package/src/model-policy.ts +210 -0
- package/src/session-ui.ts +7 -0
- package/src/timeline/activity-rail.tsx +46 -7
- package/src/timeline/entrance.tsx +10 -0
- package/src/timeline/fold-memory.ts +52 -0
- package/src/timeline/index.ts +9 -1
- package/src/timeline/projection.ts +226 -41
- package/src/timeline/seen-activity-ids.ts +19 -0
- package/src/timeline/shared.tsx +1 -1
- package/src/timeline/tool-renderers.tsx +75 -6
- package/src/timeline/turn-summary.tsx +387 -97
- package/src/timeline/types.ts +21 -0
- package/styles/index.css +99 -7
- package/styles/tokens.css +44 -3
- package/dist/chunk-AVLMIWYO.js.map +0 -1
- package/dist/chunk-CSJBF3QJ.js.map +0 -1
- package/dist/chunk-IPVOVOW6.js.map +0 -1
- package/dist/chunk-M7X4JZOD.js.map +0 -1
- package/dist/chunk-OKXCECLG.js.map +0 -1
- package/dist/chunk-ZMNPFZWL.js +0 -3918
- package/dist/chunk-ZMNPFZWL.js.map +0 -1
- package/dist/composer-BMzimGTO.d.ts +0 -528
- package/dist/session-DaONt-dY.d.ts +0 -264
- package/dist/session-context-CAjlaeZb.d.ts +0 -84
- package/dist/session-ui-B5_roA95.d.ts +0 -259
- /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
|
@@ -1,2292 +1,2503 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cn
|
|
3
|
-
} from "./chunk-TK7G6XLT.js";
|
|
4
1
|
import {
|
|
5
2
|
shouldSteerOnKey,
|
|
6
3
|
shouldSubmitOnKey
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KZRUYCL4.js";
|
|
5
|
+
import {
|
|
6
|
+
groupPickerRowsByBillingClass
|
|
7
|
+
} from "./chunk-U255M5EZ.js";
|
|
8
8
|
import {
|
|
9
|
+
cn
|
|
10
|
+
} from "./chunk-TK7G6XLT.js";
|
|
11
|
+
import {
|
|
12
|
+
composerSubmissionErrorMessage,
|
|
9
13
|
formatBytes,
|
|
10
14
|
formatRelativeTime
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-4QOQ2DSR.js";
|
|
12
16
|
|
|
13
|
-
// src/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
// src/hooks/use-transcription.ts
|
|
18
|
+
import {
|
|
19
|
+
authorizeTranscriptionAdapter,
|
|
20
|
+
createTranscriptionSessionRequest
|
|
21
|
+
} from "@opengeni/sdk";
|
|
22
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
23
|
+
var INITIAL_TRANSCRIPTION_CONTROL_STATE = {
|
|
24
|
+
status: "idle",
|
|
25
|
+
generation: 0,
|
|
26
|
+
localSessionId: null,
|
|
27
|
+
providerSessionId: null,
|
|
28
|
+
lastSequence: 0,
|
|
29
|
+
partial: "",
|
|
30
|
+
error: null,
|
|
31
|
+
acceptedFinalIds: [],
|
|
32
|
+
audioMilliseconds: 0,
|
|
33
|
+
costUsd: null
|
|
34
|
+
};
|
|
35
|
+
function transitionTranscriptionControl(state, action) {
|
|
36
|
+
if (action.type === "reset") {
|
|
37
|
+
return {
|
|
38
|
+
state: { ...INITIAL_TRANSCRIPTION_CONTROL_STATE, generation: state.generation },
|
|
39
|
+
commit: null
|
|
40
|
+
};
|
|
17
41
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
if (action.type === "start") {
|
|
43
|
+
if (action.generation <= state.generation) return { state, commit: null };
|
|
44
|
+
return {
|
|
45
|
+
state: {
|
|
46
|
+
...INITIAL_TRANSCRIPTION_CONTROL_STATE,
|
|
47
|
+
status: "requesting-permission",
|
|
48
|
+
generation: action.generation,
|
|
49
|
+
localSessionId: action.localSessionId
|
|
50
|
+
},
|
|
51
|
+
commit: null
|
|
52
|
+
};
|
|
22
53
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return true;
|
|
54
|
+
if (action.generation !== state.generation) return { state, commit: null };
|
|
55
|
+
if (action.type === "start.failed") {
|
|
56
|
+
if (state.status === "closed" || state.status === "error") return { state, commit: null };
|
|
57
|
+
return {
|
|
58
|
+
state: {
|
|
59
|
+
...state,
|
|
60
|
+
status: "error",
|
|
61
|
+
partial: "",
|
|
62
|
+
error: { code: normalizeTranscriptionErrorCode(action.code), recoverable: false }
|
|
63
|
+
},
|
|
64
|
+
commit: null
|
|
65
|
+
};
|
|
36
66
|
}
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
if (action.type === "cancel") {
|
|
68
|
+
if (!isActiveTranscriptionStatus(state.status)) return { state, commit: null };
|
|
69
|
+
return {
|
|
70
|
+
state: { ...state, status: "cancelling", partial: "", error: null },
|
|
71
|
+
commit: null
|
|
72
|
+
};
|
|
43
73
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
function filterCommands(commands, token, ctx) {
|
|
48
|
-
const needle = token.toLowerCase();
|
|
49
|
-
return commands.filter((command) => {
|
|
50
|
-
if (!hasPermission(command.permission, ctx.permissions)) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
if (command.available && !command.available(ctx)) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
if (needle.length === 0) {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
return command.name.startsWith(needle) || (command.aliases?.some((alias) => alias.startsWith(needle)) ?? false);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function argHint(args) {
|
|
63
|
-
if (!args || args.length === 0) {
|
|
64
|
-
return "";
|
|
74
|
+
if (action.type === "cancel.settled") {
|
|
75
|
+
if (state.status !== "cancelling") return { state, commit: null };
|
|
76
|
+
return { state: { ...state, status: "closed", partial: "" }, commit: null };
|
|
65
77
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
const event = action.event;
|
|
79
|
+
if (event.localSessionId !== state.localSessionId) return { state, commit: null };
|
|
80
|
+
if (!Number.isSafeInteger(event.sequence) || event.sequence <= state.lastSequence) {
|
|
81
|
+
return { state, commit: null };
|
|
82
|
+
}
|
|
83
|
+
if (state.status === "closed" || state.status === "error") return { state, commit: null };
|
|
84
|
+
if (state.status === "cancelling" && event.type !== "session.closed") {
|
|
85
|
+
return { state: { ...state, lastSequence: event.sequence }, commit: null };
|
|
86
|
+
}
|
|
87
|
+
const sequenced = { ...state, lastSequence: event.sequence };
|
|
88
|
+
switch (event.type) {
|
|
89
|
+
case "permission.requested":
|
|
90
|
+
return {
|
|
91
|
+
state: { ...sequenced, status: "requesting-permission", partial: "", error: null },
|
|
92
|
+
commit: null
|
|
93
|
+
};
|
|
94
|
+
case "session.opened":
|
|
95
|
+
return {
|
|
96
|
+
state: {
|
|
97
|
+
...sequenced,
|
|
98
|
+
status: "listening",
|
|
99
|
+
providerSessionId: event.providerSessionId,
|
|
100
|
+
error: null
|
|
101
|
+
},
|
|
102
|
+
commit: null
|
|
103
|
+
};
|
|
104
|
+
case "transcript.partial":
|
|
105
|
+
return {
|
|
106
|
+
state: { ...sequenced, status: "listening", partial: event.text, error: null },
|
|
107
|
+
commit: null
|
|
108
|
+
};
|
|
109
|
+
case "transcript.final": {
|
|
110
|
+
const text = event.text.trim();
|
|
111
|
+
const duplicate = state.acceptedFinalIds.includes(event.providerAcceptanceId);
|
|
112
|
+
const acceptedFinalIds = duplicate || text.length === 0 ? state.acceptedFinalIds : [...state.acceptedFinalIds, event.providerAcceptanceId];
|
|
113
|
+
return {
|
|
114
|
+
state: {
|
|
115
|
+
...sequenced,
|
|
116
|
+
status: "listening",
|
|
117
|
+
partial: "",
|
|
118
|
+
error: null,
|
|
119
|
+
acceptedFinalIds
|
|
120
|
+
},
|
|
121
|
+
commit: duplicate || text.length === 0 ? null : text
|
|
122
|
+
};
|
|
77
123
|
}
|
|
124
|
+
case "usage":
|
|
125
|
+
return {
|
|
126
|
+
state: {
|
|
127
|
+
...sequenced,
|
|
128
|
+
audioMilliseconds: Math.max(state.audioMilliseconds, event.audioMilliseconds),
|
|
129
|
+
costUsd: event.costUsd
|
|
130
|
+
},
|
|
131
|
+
commit: null
|
|
132
|
+
};
|
|
133
|
+
case "session.reconnecting":
|
|
134
|
+
return {
|
|
135
|
+
state: { ...sequenced, status: "reconnecting", partial: "", error: null },
|
|
136
|
+
commit: null
|
|
137
|
+
};
|
|
138
|
+
case "session.error":
|
|
139
|
+
return {
|
|
140
|
+
state: {
|
|
141
|
+
...sequenced,
|
|
142
|
+
status: event.recoverable ? "reconnecting" : "error",
|
|
143
|
+
partial: "",
|
|
144
|
+
error: {
|
|
145
|
+
code: normalizeTranscriptionErrorCode(event.code),
|
|
146
|
+
recoverable: event.recoverable
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
commit: null
|
|
150
|
+
};
|
|
151
|
+
case "session.closed":
|
|
152
|
+
return {
|
|
153
|
+
state: {
|
|
154
|
+
...sequenced,
|
|
155
|
+
status: event.reason === "error" ? "error" : "closed",
|
|
156
|
+
partial: ""
|
|
157
|
+
},
|
|
158
|
+
commit: null
|
|
159
|
+
};
|
|
78
160
|
}
|
|
79
|
-
return null;
|
|
80
161
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return
|
|
162
|
+
function appendFinalTranscript(draft, transcript) {
|
|
163
|
+
const final = transcript.trim();
|
|
164
|
+
if (!final) return draft;
|
|
165
|
+
if (!draft) return final;
|
|
166
|
+
return /\s$/u.test(draft) ? `${draft}${final}` : `${draft} ${final}`;
|
|
86
167
|
}
|
|
87
|
-
var
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
name: "goal",
|
|
114
|
-
description: "Pause or resume the session's goal loop.",
|
|
115
|
-
permission: "sessions:control",
|
|
116
|
-
available: hasSession,
|
|
117
|
-
args: [
|
|
118
|
-
{ name: "action", required: true, oneOf: ["pause", "resume"], description: "pause | resume" }
|
|
119
|
-
],
|
|
120
|
-
run: async (args, ctx) => {
|
|
121
|
-
const sessionId = requireSession(ctx);
|
|
122
|
-
const action = args[0];
|
|
123
|
-
if (action !== "pause" && action !== "resume") {
|
|
124
|
-
return { status: "error", message: "Usage: /goal pause | /goal resume" };
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
await ctx.client.updateGoal(ctx.workspaceId, sessionId, {
|
|
128
|
-
status: action === "pause" ? "paused" : "active"
|
|
129
|
-
});
|
|
130
|
-
return { status: "ok", message: action === "pause" ? "Goal paused." : "Goal resumed." };
|
|
131
|
-
} catch (cause) {
|
|
132
|
-
return { status: "error", message: goalErrorMessage(cause, action) };
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
name: "compact",
|
|
138
|
-
description: "Compact the conversation context now.",
|
|
139
|
-
permission: "sessions:control",
|
|
140
|
-
available: hasSession,
|
|
141
|
-
run: async (_args, ctx) => {
|
|
142
|
-
const sessionId = requireSession(ctx);
|
|
143
|
-
try {
|
|
144
|
-
const result = await ctx.client.compactSessionContext(ctx.workspaceId, sessionId);
|
|
145
|
-
return { status: "ok", message: result.message };
|
|
146
|
-
} catch (cause) {
|
|
147
|
-
return { status: "error", message: errorMessage(cause) ?? "Could not compact context." };
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
name: "clear",
|
|
153
|
-
description: "Clear the conversation context (destructive; audit-preserved).",
|
|
154
|
-
permission: "sessions:control",
|
|
155
|
-
danger: true,
|
|
156
|
-
available: hasSession,
|
|
157
|
-
run: async (_args, ctx) => {
|
|
158
|
-
const sessionId = requireSession(ctx);
|
|
159
|
-
const confirmed = await ctx.confirm();
|
|
160
|
-
if (!confirmed) {
|
|
161
|
-
return { status: "ok", keepDraft: true };
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
await ctx.client.clearSessionContext(ctx.workspaceId, sessionId);
|
|
165
|
-
return { status: "ok", message: "Context cleared." };
|
|
166
|
-
} catch (cause) {
|
|
167
|
-
return { status: "error", message: clearErrorMessage(cause) };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
];
|
|
172
|
-
function errorMessage(cause) {
|
|
173
|
-
if (cause && typeof cause === "object" && "message" in cause && typeof cause.message === "string") {
|
|
174
|
-
return cause.message;
|
|
175
|
-
}
|
|
176
|
-
return void 0;
|
|
177
|
-
}
|
|
178
|
-
function statusCode(cause) {
|
|
179
|
-
if (cause && typeof cause === "object" && "status" in cause && typeof cause.status === "number") {
|
|
180
|
-
return cause.status;
|
|
181
|
-
}
|
|
182
|
-
return void 0;
|
|
183
|
-
}
|
|
184
|
-
function goalErrorMessage(cause, action) {
|
|
185
|
-
const code = statusCode(cause);
|
|
186
|
-
if (code === 404) {
|
|
187
|
-
return "This session has no goal to control.";
|
|
188
|
-
}
|
|
189
|
-
if (code === 409) {
|
|
190
|
-
return action === "resume" ? "Only a paused goal can be resumed." : "Goal is already in a terminal state.";
|
|
191
|
-
}
|
|
192
|
-
return errorMessage(cause) ?? `Could not ${action} the goal.`;
|
|
193
|
-
}
|
|
194
|
-
function clearErrorMessage(cause) {
|
|
195
|
-
if (statusCode(cause) === 409) {
|
|
196
|
-
return "Pause the session and wait for the current inference to settle, then clear context.";
|
|
197
|
-
}
|
|
198
|
-
return errorMessage(cause) ?? "Could not clear context.";
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/hooks/use-slash-commands.ts
|
|
202
|
-
import { useCallback, useMemo, useRef, useState } from "react";
|
|
203
|
-
function useSlashCommands(options) {
|
|
204
|
-
const { commands, context, handlers, value, setValue } = options;
|
|
205
|
-
const [highlight, setHighlight] = useState(0);
|
|
206
|
-
const [dismissedValue, setDismissedValue] = useState(null);
|
|
207
|
-
const dismissed = dismissedValue !== null && dismissedValue === value;
|
|
208
|
-
const navigatedRef = useRef(false);
|
|
209
|
-
const navTokenRef = useRef(value);
|
|
210
|
-
if (navTokenRef.current !== value) {
|
|
211
|
-
navTokenRef.current = value;
|
|
212
|
-
navigatedRef.current = false;
|
|
213
|
-
}
|
|
214
|
-
const parsed = useMemo(() => parseCommandLine(value), [value]);
|
|
215
|
-
const filterCtx = useMemo(
|
|
216
|
-
() => ({
|
|
217
|
-
sessionId: context?.sessionId ?? null,
|
|
218
|
-
status: context?.status ?? null,
|
|
219
|
-
permissions: context?.permissions ?? []
|
|
220
|
-
}),
|
|
221
|
-
[context?.sessionId, context?.status, context?.permissions]
|
|
168
|
+
var DEFAULT_TRANSCRIPTION_LIFECYCLE_TIMEOUTS = {
|
|
169
|
+
startMs: 15e3,
|
|
170
|
+
cleanupMs: 2e3
|
|
171
|
+
};
|
|
172
|
+
function useTranscription({
|
|
173
|
+
adapter,
|
|
174
|
+
policy,
|
|
175
|
+
selection = { kind: "primary" },
|
|
176
|
+
value,
|
|
177
|
+
setValue,
|
|
178
|
+
focusInput,
|
|
179
|
+
disabled = false,
|
|
180
|
+
createLocalSessionId = defaultLocalSessionId,
|
|
181
|
+
lifecycleTimeouts,
|
|
182
|
+
onDiagnostic
|
|
183
|
+
}) {
|
|
184
|
+
const [state, setState] = useState(INITIAL_TRANSCRIPTION_CONTROL_STATE);
|
|
185
|
+
const stateRef = useRef(state);
|
|
186
|
+
const valueRef = useRef(value);
|
|
187
|
+
const sessionRef = useRef(null);
|
|
188
|
+
const pendingStartRef = useRef(null);
|
|
189
|
+
const activePolicyRef = useRef(null);
|
|
190
|
+
valueRef.current = value;
|
|
191
|
+
const startTimeoutMs = normalizeTimeout(
|
|
192
|
+
lifecycleTimeouts?.startMs,
|
|
193
|
+
DEFAULT_TRANSCRIPTION_LIFECYCLE_TIMEOUTS.startMs
|
|
222
194
|
);
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
return matchCommand(commands, value);
|
|
228
|
-
}, [commands, value, parsed]);
|
|
229
|
-
const items = useMemo(() => {
|
|
230
|
-
if (!parsed) {
|
|
231
|
-
return [];
|
|
232
|
-
}
|
|
233
|
-
if (activeCommand) {
|
|
234
|
-
return [activeCommand];
|
|
235
|
-
}
|
|
236
|
-
return filterCommands(commands, parsed.name, filterCtx);
|
|
237
|
-
}, [commands, parsed, activeCommand, filterCtx]);
|
|
238
|
-
const open = parsed !== null && items.length > 0 && !dismissed;
|
|
239
|
-
const isCommandDraft = parsed !== null && items.length > 0;
|
|
240
|
-
const clampedHighlight = items.length === 0 ? 0 : Math.min(highlight, items.length - 1);
|
|
241
|
-
const activeArgHint = activeCommand ? argHint(activeCommand.args) : "";
|
|
242
|
-
const buildContext = useCallback(
|
|
243
|
-
(command) => {
|
|
244
|
-
if (!context) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
return { ...context, ...handlers, confirm: () => handlers.confirm(command) };
|
|
248
|
-
},
|
|
249
|
-
[context, handlers]
|
|
195
|
+
const cleanupTimeoutMs = normalizeTimeout(
|
|
196
|
+
lifecycleTimeouts?.cleanupMs,
|
|
197
|
+
DEFAULT_TRANSCRIPTION_LIFECYCLE_TIMEOUTS.cleanupMs
|
|
250
198
|
);
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!ctx) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
199
|
+
const reportDiagnostic = useCallback(
|
|
200
|
+
(diagnostic) => {
|
|
201
|
+
if (!onDiagnostic) return;
|
|
257
202
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
ctx.notice({ tone: result.status === "ok" ? "ok" : "error", message: result.message });
|
|
261
|
-
}
|
|
262
|
-
if (result.status === "ok" && !result.keepDraft) {
|
|
263
|
-
setValue("");
|
|
264
|
-
}
|
|
265
|
-
} catch (cause) {
|
|
266
|
-
ctx.notice({ tone: "error", message: errorMessage2(cause) });
|
|
203
|
+
onDiagnostic(sanitizeTranscriptionDiagnostic(diagnostic));
|
|
204
|
+
} catch {
|
|
267
205
|
}
|
|
268
206
|
},
|
|
269
|
-
[
|
|
207
|
+
[onDiagnostic]
|
|
270
208
|
);
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
209
|
+
const cleanupRuntimeRef = useRef({ cleanupTimeoutMs, reportDiagnostic });
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
cleanupRuntimeRef.current = { cleanupTimeoutMs, reportDiagnostic };
|
|
212
|
+
}, [cleanupTimeoutMs, reportDiagnostic]);
|
|
213
|
+
const policyRevision = useMemo(
|
|
214
|
+
() => transcriptionPolicyRevision(policy, selection),
|
|
215
|
+
[policy, selection]
|
|
277
216
|
);
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
217
|
+
const authorization = useMemo(
|
|
218
|
+
() => adapter ? authorizeTranscriptionAdapter(policy, adapter.descriptor, selection) : null,
|
|
219
|
+
[adapter, policy, selection]
|
|
220
|
+
);
|
|
221
|
+
const unavailableReason = disabled ? "composer_disabled" : !policy.enabled ? "disabled" : !policy.acceptanceId ? "unaccepted" : !policy.primary ? "target_missing" : !adapter ? "adapter_missing" : authorization?.authorized ? null : authorization?.reason ?? "unaccepted";
|
|
222
|
+
const apply = useCallback(
|
|
223
|
+
(action) => {
|
|
224
|
+
const transition = transitionTranscriptionControl(stateRef.current, action);
|
|
225
|
+
if (transition.state !== stateRef.current) {
|
|
226
|
+
stateRef.current = transition.state;
|
|
227
|
+
setState(transition.state);
|
|
289
228
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
autocomplete(command);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const args = nameMatchesToken || parsed.hasTrailingSpace ? parsed.args : [];
|
|
299
|
-
const missing = firstMissingRequiredArg(command, args);
|
|
300
|
-
if (missing) {
|
|
301
|
-
if (!parsed.hasTrailingSpace) {
|
|
302
|
-
autocomplete(command);
|
|
303
|
-
}
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
await execute(command, args);
|
|
307
|
-
} finally {
|
|
308
|
-
runningRef.current = false;
|
|
229
|
+
if (transition.commit !== null) {
|
|
230
|
+
const next = appendFinalTranscript(valueRef.current, transition.commit);
|
|
231
|
+
valueRef.current = next;
|
|
232
|
+
setValue(next);
|
|
233
|
+
focusInput();
|
|
309
234
|
}
|
|
235
|
+
return transition.state;
|
|
310
236
|
},
|
|
311
|
-
[
|
|
237
|
+
[focusInput, setValue]
|
|
312
238
|
);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
const token = parsed.name.toLowerCase();
|
|
325
|
-
const exact = items.find((item) => item.name === token || item.aliases?.includes(token));
|
|
326
|
-
const command = activeCommand ?? exact ?? items[clampedHighlight];
|
|
327
|
-
if (!command) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
await runResolved(command);
|
|
331
|
-
}, [parsed, activeCommand, items, clampedHighlight, runResolved]);
|
|
332
|
-
const runAt = useCallback(
|
|
333
|
-
async (index) => {
|
|
334
|
-
const command = items[index];
|
|
335
|
-
if (!command) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
await runResolved(command, { explicit: true });
|
|
239
|
+
const releaseTerminalSession = useCallback(
|
|
240
|
+
(generation) => {
|
|
241
|
+
const active = sessionRef.current;
|
|
242
|
+
if (!active || active.generation !== generation) return;
|
|
243
|
+
sessionRef.current = null;
|
|
244
|
+
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
245
|
+
detachSessionCleanup(active.session, {
|
|
246
|
+
cleanupTimeoutMs,
|
|
247
|
+
reportDiagnostic
|
|
248
|
+
});
|
|
339
249
|
},
|
|
340
|
-
[
|
|
250
|
+
[cleanupTimeoutMs, reportDiagnostic]
|
|
341
251
|
);
|
|
342
|
-
const
|
|
343
|
-
(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
252
|
+
const start = useCallback(async () => {
|
|
253
|
+
if (!adapter || unavailableReason !== null || isActiveTranscriptionStatus(stateRef.current.status)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const generation = stateRef.current.generation + 1;
|
|
257
|
+
const localSessionId = createLocalSessionId();
|
|
258
|
+
apply({ type: "start", generation, localSessionId });
|
|
259
|
+
const request = createTranscriptionSessionRequest({
|
|
260
|
+
policy,
|
|
261
|
+
adapter,
|
|
262
|
+
localSessionId,
|
|
263
|
+
selection,
|
|
264
|
+
sequenceFloor: 0
|
|
265
|
+
});
|
|
266
|
+
if (!request) {
|
|
267
|
+
apply({
|
|
268
|
+
type: "start.failed",
|
|
269
|
+
generation,
|
|
270
|
+
code: "policy_blocked"
|
|
271
|
+
});
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
activePolicyRef.current = { generation, revision: policyRevision };
|
|
275
|
+
const controller = new AbortController();
|
|
276
|
+
pendingStartRef.current = { generation, controller };
|
|
277
|
+
let boundarySettled = false;
|
|
278
|
+
let accepted = false;
|
|
279
|
+
const observedStart = Promise.resolve().then(
|
|
280
|
+
() => adapter.start(
|
|
281
|
+
request,
|
|
282
|
+
(event) => {
|
|
283
|
+
apply({ type: "event", generation, event });
|
|
284
|
+
if (event.type === "session.error" && !event.recoverable) {
|
|
285
|
+
releaseTerminalSession(generation);
|
|
286
|
+
} else if (event.type === "session.closed") {
|
|
287
|
+
releaseTerminalSession(generation);
|
|
373
288
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
signal: controller.signal,
|
|
292
|
+
reportDiagnostic
|
|
377
293
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
294
|
+
)
|
|
295
|
+
).then(
|
|
296
|
+
(session) => {
|
|
297
|
+
if (boundarySettled && !accepted) {
|
|
298
|
+
detachSessionCleanup(session, {
|
|
299
|
+
cancelReason: "stale-generation",
|
|
300
|
+
cleanupTimeoutMs,
|
|
301
|
+
reportDiagnostic
|
|
302
|
+
});
|
|
382
303
|
}
|
|
383
|
-
|
|
384
|
-
|
|
304
|
+
return { kind: "session", session };
|
|
305
|
+
},
|
|
306
|
+
(error) => {
|
|
307
|
+
if (boundarySettled) {
|
|
308
|
+
reportDiagnostic({
|
|
309
|
+
operation: "start",
|
|
310
|
+
code: classifyStartError(error),
|
|
311
|
+
detail: diagnosticDetail(error)
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return { kind: "error", error };
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
let abortListener = null;
|
|
318
|
+
const aborted = new Promise((resolve) => {
|
|
319
|
+
if (controller.signal.aborted) {
|
|
320
|
+
resolve({ kind: "aborted" });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
abortListener = () => resolve({ kind: "aborted" });
|
|
324
|
+
controller.signal.addEventListener("abort", abortListener, { once: true });
|
|
325
|
+
});
|
|
326
|
+
let timeoutId = null;
|
|
327
|
+
const timedOut = new Promise((resolve) => {
|
|
328
|
+
timeoutId = setTimeout(() => resolve({ kind: "timeout" }), startTimeoutMs);
|
|
329
|
+
});
|
|
330
|
+
const outcome = await Promise.race([observedStart, aborted, timedOut]);
|
|
331
|
+
boundarySettled = true;
|
|
332
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
333
|
+
if (abortListener) controller.signal.removeEventListener("abort", abortListener);
|
|
334
|
+
if (pendingStartRef.current?.generation === generation) pendingStartRef.current = null;
|
|
335
|
+
if (outcome.kind === "aborted") return false;
|
|
336
|
+
if (outcome.kind === "timeout") {
|
|
337
|
+
controller.abort("transcription-start-timeout");
|
|
338
|
+
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
339
|
+
reportDiagnostic({
|
|
340
|
+
operation: "start",
|
|
341
|
+
code: "timeout",
|
|
342
|
+
detail: `Transcription adapter start exceeded ${startTimeoutMs}ms.`
|
|
343
|
+
});
|
|
344
|
+
apply({ type: "start.failed", generation, code: "timeout" });
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
if (outcome.kind === "error") {
|
|
348
|
+
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
349
|
+
const code = classifyStartError(outcome.error);
|
|
350
|
+
reportDiagnostic({
|
|
351
|
+
operation: "start",
|
|
352
|
+
code,
|
|
353
|
+
detail: diagnosticDetail(outcome.error)
|
|
354
|
+
});
|
|
355
|
+
if (!controller.signal.aborted) {
|
|
356
|
+
apply({ type: "start.failed", generation, code });
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const current = stateRef.current;
|
|
361
|
+
if (current.generation !== generation || current.localSessionId !== localSessionId || current.status === "cancelling" || !isActiveTranscriptionStatus(current.status)) {
|
|
362
|
+
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
363
|
+
detachSessionCleanup(outcome.session, {
|
|
364
|
+
...current.status === "error" ? {} : { cancelReason: "stale-generation" },
|
|
365
|
+
cleanupTimeoutMs,
|
|
366
|
+
reportDiagnostic
|
|
367
|
+
});
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
accepted = true;
|
|
371
|
+
sessionRef.current = { generation, session: outcome.session };
|
|
372
|
+
return true;
|
|
373
|
+
}, [
|
|
374
|
+
adapter,
|
|
375
|
+
apply,
|
|
376
|
+
createLocalSessionId,
|
|
377
|
+
cleanupTimeoutMs,
|
|
378
|
+
policy,
|
|
379
|
+
policyRevision,
|
|
380
|
+
releaseTerminalSession,
|
|
381
|
+
reportDiagnostic,
|
|
382
|
+
selection,
|
|
383
|
+
startTimeoutMs,
|
|
384
|
+
unavailableReason
|
|
385
|
+
]);
|
|
386
|
+
const cancel = useCallback(async () => {
|
|
387
|
+
const current = stateRef.current;
|
|
388
|
+
if (!isActiveTranscriptionStatus(current.status)) return false;
|
|
389
|
+
const generation = current.generation;
|
|
390
|
+
apply({ type: "cancel", generation });
|
|
391
|
+
const pending = pendingStartRef.current;
|
|
392
|
+
if (pending?.generation === generation) {
|
|
393
|
+
pendingStartRef.current = null;
|
|
394
|
+
pending.controller.abort("transcription-locally-cancelled");
|
|
395
|
+
}
|
|
396
|
+
const active = sessionRef.current;
|
|
397
|
+
sessionRef.current = null;
|
|
398
|
+
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
399
|
+
apply({ type: "cancel.settled", generation });
|
|
400
|
+
focusInput();
|
|
401
|
+
if (active?.generation === generation) {
|
|
402
|
+
detachSessionCleanup(active.session, {
|
|
403
|
+
cancelReason: "user-cancelled",
|
|
404
|
+
cleanupTimeoutMs,
|
|
405
|
+
reportDiagnostic
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
}, [apply, cleanupTimeoutMs, focusInput, reportDiagnostic]);
|
|
410
|
+
const reset = useCallback(() => apply({ type: "reset" }), [apply]);
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
if (!isActiveTranscriptionStatus(state.status)) return;
|
|
413
|
+
const onKeyDown = (event) => {
|
|
414
|
+
if (event.key !== "Escape") return;
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
void cancel();
|
|
417
|
+
};
|
|
418
|
+
document.addEventListener("keydown", onKeyDown);
|
|
419
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
420
|
+
}, [cancel, state.status]);
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
if (!isActiveTranscriptionStatus(state.status)) return;
|
|
423
|
+
const activePolicy = activePolicyRef.current;
|
|
424
|
+
if (unavailableReason === null && activePolicy?.generation === state.generation && activePolicy.revision === policyRevision) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
void cancel();
|
|
428
|
+
}, [cancel, policyRevision, state.generation, state.status, unavailableReason]);
|
|
429
|
+
useEffect(
|
|
430
|
+
() => () => {
|
|
431
|
+
const pending = pendingStartRef.current;
|
|
432
|
+
pendingStartRef.current = null;
|
|
433
|
+
pending?.controller.abort("transcription-component-unmounted");
|
|
434
|
+
const active = sessionRef.current;
|
|
435
|
+
sessionRef.current = null;
|
|
436
|
+
activePolicyRef.current = null;
|
|
437
|
+
stateRef.current = {
|
|
438
|
+
...stateRef.current,
|
|
439
|
+
generation: stateRef.current.generation + 1,
|
|
440
|
+
status: "closed",
|
|
441
|
+
partial: ""
|
|
442
|
+
};
|
|
443
|
+
if (active) {
|
|
444
|
+
const runtime = cleanupRuntimeRef.current;
|
|
445
|
+
detachSessionCleanup(active.session, {
|
|
446
|
+
cancelReason: "component-unmounted",
|
|
447
|
+
cleanupTimeoutMs: runtime.cleanupTimeoutMs,
|
|
448
|
+
reportDiagnostic: runtime.reportDiagnostic
|
|
449
|
+
});
|
|
385
450
|
}
|
|
386
451
|
},
|
|
387
|
-
[
|
|
452
|
+
[]
|
|
388
453
|
);
|
|
389
454
|
return {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
activeArgHint,
|
|
397
|
-
onKeyDown,
|
|
398
|
-
runHighlighted,
|
|
399
|
-
runAt,
|
|
400
|
-
autocompleteHighlighted
|
|
455
|
+
state,
|
|
456
|
+
available: unavailableReason === null,
|
|
457
|
+
unavailableReason,
|
|
458
|
+
start,
|
|
459
|
+
cancel,
|
|
460
|
+
reset
|
|
401
461
|
};
|
|
402
462
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
463
|
+
function isActiveTranscriptionStatus(status) {
|
|
464
|
+
return status === "requesting-permission" || status === "listening" || status === "reconnecting" || status === "cancelling";
|
|
465
|
+
}
|
|
466
|
+
function defaultLocalSessionId() {
|
|
467
|
+
return globalThis.crypto.randomUUID();
|
|
468
|
+
}
|
|
469
|
+
function classifyStartError(error) {
|
|
470
|
+
if (isRecord(error)) {
|
|
471
|
+
const normalized = normalizeTranscriptionErrorCode(error.code);
|
|
472
|
+
if (normalized !== "unknown") return normalized;
|
|
406
473
|
}
|
|
407
|
-
|
|
474
|
+
if (typeof DOMException !== "undefined" && error instanceof DOMException) {
|
|
475
|
+
if (error.name === "NotAllowedError") return "permission_denied";
|
|
476
|
+
if (error.name === "NotSupportedError") return "not_supported";
|
|
477
|
+
if (error.name === "TimeoutError") return "timeout";
|
|
478
|
+
if (error.name === "AbortError") return "cancelled";
|
|
479
|
+
}
|
|
480
|
+
return "unknown";
|
|
408
481
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
482
|
+
function normalizeTranscriptionErrorCode(code) {
|
|
483
|
+
switch (code) {
|
|
484
|
+
case "permission_denied":
|
|
485
|
+
case "not_supported":
|
|
486
|
+
case "network":
|
|
487
|
+
case "provider":
|
|
488
|
+
case "policy_blocked":
|
|
489
|
+
case "timeout":
|
|
490
|
+
case "cancelled":
|
|
491
|
+
case "unknown":
|
|
492
|
+
return code;
|
|
493
|
+
default:
|
|
494
|
+
return "unknown";
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function sanitizeTranscriptionDiagnostic(diagnostic) {
|
|
498
|
+
const source = isRecord(diagnostic) ? diagnostic : {};
|
|
499
|
+
const operation = source.operation === "start" || source.operation === "session" || source.operation === "cancel" || source.operation === "close" ? source.operation : "session";
|
|
500
|
+
const rawDetail = typeof source.detail === "string" ? source.detail : "No diagnostic detail.";
|
|
501
|
+
const detail = rawDetail.replace(/[\u0000-\u001f\u007f]+/gu, " ").replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/giu, "Bearer [REDACTED]").replace(/\bsk-[A-Za-z0-9_-]{4,}\b/gu, "[REDACTED]").replace(
|
|
502
|
+
/\b(api[-_ ]?key|authorization|access[-_ ]?token|refresh[-_ ]?token|token|secret)\b\s*[:=]\s*[^\s,;]+/giu,
|
|
503
|
+
"$1=[REDACTED]"
|
|
504
|
+
).trim().slice(0, 512);
|
|
505
|
+
return {
|
|
506
|
+
operation,
|
|
507
|
+
code: normalizeTranscriptionErrorCode(source.code),
|
|
508
|
+
detail: detail || "No diagnostic detail."
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function detachSessionCleanup(session, options) {
|
|
512
|
+
if (options.cancelReason) {
|
|
513
|
+
void runBoundedCleanup(
|
|
514
|
+
"cancel",
|
|
515
|
+
() => session.cancel(options.cancelReason),
|
|
516
|
+
options.cleanupTimeoutMs,
|
|
517
|
+
options.reportDiagnostic
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
void runBoundedCleanup(
|
|
521
|
+
"close",
|
|
522
|
+
() => session.close(),
|
|
523
|
+
options.cleanupTimeoutMs,
|
|
524
|
+
options.reportDiagnostic
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
async function runBoundedCleanup(operation, invoke, timeoutMs, reportDiagnostic) {
|
|
528
|
+
let timeoutId = null;
|
|
529
|
+
const result = Promise.resolve().then(invoke).then(
|
|
530
|
+
() => ({ kind: "settled" }),
|
|
531
|
+
(error) => ({ kind: "rejected", error })
|
|
532
|
+
);
|
|
533
|
+
const timeout = new Promise((resolve) => {
|
|
534
|
+
timeoutId = setTimeout(() => resolve({ kind: "timeout" }), timeoutMs);
|
|
535
|
+
});
|
|
536
|
+
const outcome = await Promise.race([result, timeout]);
|
|
537
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
538
|
+
if (outcome.kind === "settled") return;
|
|
539
|
+
reportDiagnostic({
|
|
540
|
+
operation,
|
|
541
|
+
code: outcome.kind === "timeout" ? "timeout" : "provider",
|
|
542
|
+
detail: outcome.kind === "timeout" ? `Transcription ${operation} exceeded ${timeoutMs}ms.` : diagnosticDetail(outcome.error)
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
function diagnosticDetail(error) {
|
|
546
|
+
if (error instanceof Error) return error.message;
|
|
547
|
+
return typeof error === "string" ? error : "Unknown transcription adapter failure.";
|
|
548
|
+
}
|
|
549
|
+
function normalizeTimeout(value, fallback) {
|
|
550
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? Math.min(Math.floor(value), 6e4) : fallback;
|
|
551
|
+
}
|
|
552
|
+
function isRecord(value) {
|
|
553
|
+
return typeof value === "object" && value !== null;
|
|
554
|
+
}
|
|
555
|
+
function transcriptionPolicyRevision(policy, selection) {
|
|
556
|
+
const targetKey = (target) => target ? [
|
|
557
|
+
target.provider,
|
|
558
|
+
target.model ?? "",
|
|
559
|
+
target.credentialMode,
|
|
560
|
+
target.credentialConnectionId ?? "",
|
|
561
|
+
target.region ?? ""
|
|
562
|
+
].join("\0") : "";
|
|
563
|
+
return [
|
|
564
|
+
policy.enabled ? "1" : "0",
|
|
565
|
+
policy.acceptanceId ?? "",
|
|
566
|
+
targetKey(policy.primary),
|
|
567
|
+
policy.language ?? "",
|
|
568
|
+
policy.autoDetectLanguage ? "1" : "0",
|
|
569
|
+
policy.diarization.enabled ? "1" : "0",
|
|
570
|
+
policy.diarization.maxSpeakers?.toString() ?? "",
|
|
571
|
+
policy.retention.mode,
|
|
572
|
+
policy.retention.maxDays?.toString() ?? "",
|
|
573
|
+
policy.privacy.allowProviderLogging ? "1" : "0",
|
|
574
|
+
policy.privacy.allowProviderTraining ? "1" : "0",
|
|
575
|
+
policy.fallback.mode,
|
|
576
|
+
policy.fallback.targets.map(targetKey).join(""),
|
|
577
|
+
policy.cost.currency,
|
|
578
|
+
policy.cost.maxPerHour?.toString() ?? "",
|
|
579
|
+
policy.cost.maxPerMonth?.toString() ?? "",
|
|
580
|
+
selection.kind,
|
|
581
|
+
selection.kind === "fallback" ? selection.index.toString() : ""
|
|
582
|
+
].join("");
|
|
494
583
|
}
|
|
495
584
|
|
|
496
|
-
// src/
|
|
497
|
-
import {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
585
|
+
// src/hooks/use-voice-input.ts
|
|
586
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
587
|
+
var MIME_PREFERENCES = ["audio/webm;codecs=opus", "audio/mp4", "audio/ogg;codecs=opus"];
|
|
588
|
+
function useVoiceInput({
|
|
589
|
+
client,
|
|
590
|
+
workspaceId,
|
|
591
|
+
capability,
|
|
592
|
+
enabled,
|
|
502
593
|
value,
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
label = "Model"
|
|
594
|
+
setValue,
|
|
595
|
+
focusInput,
|
|
596
|
+
disabled = false
|
|
507
597
|
}) {
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
598
|
+
const [status, setStatus] = useState2("idle");
|
|
599
|
+
const [error, setError] = useState2(null);
|
|
600
|
+
const [stream, setStream] = useState2(null);
|
|
601
|
+
const generationRef = useRef2(0);
|
|
602
|
+
const recorderRef = useRef2(null);
|
|
603
|
+
const streamRef = useRef2(null);
|
|
604
|
+
const timerRef = useRef2(null);
|
|
605
|
+
const controllerRef = useRef2(null);
|
|
606
|
+
const valueRef = useRef2(value);
|
|
607
|
+
valueRef.current = value;
|
|
608
|
+
const clearRuntime = useCallback2(() => {
|
|
609
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
610
|
+
timerRef.current = null;
|
|
611
|
+
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
612
|
+
streamRef.current = null;
|
|
613
|
+
recorderRef.current = null;
|
|
614
|
+
setStream(null);
|
|
615
|
+
}, []);
|
|
616
|
+
const cancel = useCallback2(() => {
|
|
617
|
+
generationRef.current += 1;
|
|
618
|
+
controllerRef.current?.abort();
|
|
619
|
+
controllerRef.current = null;
|
|
620
|
+
const recorder = recorderRef.current;
|
|
621
|
+
clearRuntime();
|
|
622
|
+
if (recorder && recorder.state !== "inactive") recorder.stop();
|
|
623
|
+
setStatus("idle");
|
|
624
|
+
setError(null);
|
|
625
|
+
focusInput();
|
|
626
|
+
}, [clearRuntime, focusInput]);
|
|
627
|
+
const stop = () => {
|
|
628
|
+
const recorder = recorderRef.current;
|
|
629
|
+
if (!recorder || recorder.state === "inactive") return;
|
|
630
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
631
|
+
timerRef.current = null;
|
|
632
|
+
setStatus("transcribing");
|
|
633
|
+
recorder.stop();
|
|
634
|
+
};
|
|
635
|
+
const start = async () => {
|
|
636
|
+
if (disabled || !client || !enabled || !capability?.available || status === "requesting-permission" || status === "recording" || status === "transcribing" || !navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
|
|
637
|
+
return false;
|
|
518
638
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
{
|
|
529
|
-
id: selectId,
|
|
530
|
-
value: value ?? "",
|
|
531
|
-
onChange: (event) => onChange(event.target.value),
|
|
532
|
-
disabled: disabled === true,
|
|
533
|
-
"aria-label": label,
|
|
534
|
-
className: cn(
|
|
535
|
-
// Sized like the other footer controls; the chevron overlay needs the
|
|
536
|
-
// right padding so the value never collides with it.
|
|
537
|
-
"h-8 max-w-[180px] cursor-pointer appearance-none truncate rounded-og-md bg-transparent",
|
|
538
|
-
"py-0 pl-2 pr-6 text-[13px] text-og-fg-muted",
|
|
539
|
-
"transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
|
|
540
|
-
"focus:outline-none focus-visible:outline-none",
|
|
541
|
-
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
542
|
-
),
|
|
543
|
-
children: groups.map((group) => /* @__PURE__ */ jsx2("optgroup", { label: group.label, children: group.models.map((model) => /* @__PURE__ */ jsx2("option", { value: model.id, children: model.label }, model.id)) }, group.label))
|
|
639
|
+
const voiceClient = client;
|
|
640
|
+
const generation = ++generationRef.current;
|
|
641
|
+
setStatus("requesting-permission");
|
|
642
|
+
setError(null);
|
|
643
|
+
try {
|
|
644
|
+
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
645
|
+
if (generation !== generationRef.current) {
|
|
646
|
+
mediaStream.getTracks().forEach((track) => track.stop());
|
|
647
|
+
return false;
|
|
544
648
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
649
|
+
const mimeType = chooseMimeType(capability.acceptedMimeTypes);
|
|
650
|
+
const recorder = new MediaRecorder(mediaStream, mimeType ? { mimeType } : void 0);
|
|
651
|
+
const chunks = [];
|
|
652
|
+
const startedAt = Date.now();
|
|
653
|
+
streamRef.current = mediaStream;
|
|
654
|
+
recorderRef.current = recorder;
|
|
655
|
+
setStream(mediaStream);
|
|
656
|
+
recorder.ondataavailable = (event) => {
|
|
657
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
658
|
+
};
|
|
659
|
+
recorder.onstop = () => {
|
|
660
|
+
clearRuntime();
|
|
661
|
+
if (generation !== generationRef.current) return;
|
|
662
|
+
const audio = new Blob(chunks, { type: recorder.mimeType || mimeType || "audio/webm" });
|
|
663
|
+
if (audio.size === 0) {
|
|
664
|
+
setStatus("error");
|
|
665
|
+
setError("invalid_audio");
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const controller = new AbortController();
|
|
669
|
+
controllerRef.current = controller;
|
|
670
|
+
void voiceClient.transcribeAudio(workspaceId, {
|
|
671
|
+
audio,
|
|
672
|
+
mimeType: audio.type,
|
|
673
|
+
durationSeconds: Math.max(0, (Date.now() - startedAt) / 1e3),
|
|
674
|
+
signal: controller.signal
|
|
675
|
+
}).then((response) => {
|
|
676
|
+
if (generation !== generationRef.current || controller.signal.aborted) return;
|
|
677
|
+
const next = appendFinalTranscript(valueRef.current, response.text);
|
|
678
|
+
if (next !== valueRef.current) {
|
|
679
|
+
valueRef.current = next;
|
|
680
|
+
setValue(next);
|
|
681
|
+
}
|
|
682
|
+
setStatus("idle");
|
|
683
|
+
focusInput();
|
|
684
|
+
}).catch((reason) => {
|
|
685
|
+
if (generation !== generationRef.current || controller.signal.aborted) return;
|
|
686
|
+
setStatus("error");
|
|
687
|
+
setError(errorCode(reason));
|
|
688
|
+
}).finally(() => {
|
|
689
|
+
if (controllerRef.current === controller) controllerRef.current = null;
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
recorder.start();
|
|
693
|
+
setStatus("recording");
|
|
694
|
+
timerRef.current = setTimeout(
|
|
695
|
+
() => stop(),
|
|
696
|
+
Math.min(capability.maxDurationSeconds, 60) * 1e3
|
|
697
|
+
);
|
|
698
|
+
return true;
|
|
699
|
+
} catch (reason) {
|
|
700
|
+
if (generation !== generationRef.current) return false;
|
|
701
|
+
clearRuntime();
|
|
702
|
+
setStatus("error");
|
|
703
|
+
setError(errorCode(reason));
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
useEffect2(() => {
|
|
708
|
+
const onKeyDown = (event) => {
|
|
709
|
+
if (event.key === "Escape" && status !== "idle") {
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
cancel();
|
|
551
712
|
}
|
|
552
|
-
|
|
553
|
-
|
|
713
|
+
};
|
|
714
|
+
document.addEventListener("keydown", onKeyDown);
|
|
715
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
716
|
+
}, [cancel, status]);
|
|
717
|
+
useEffect2(
|
|
718
|
+
() => () => {
|
|
719
|
+
generationRef.current += 1;
|
|
720
|
+
controllerRef.current?.abort();
|
|
721
|
+
const recorder = recorderRef.current;
|
|
722
|
+
clearRuntime();
|
|
723
|
+
if (recorder && recorder.state !== "inactive") recorder.stop();
|
|
724
|
+
},
|
|
725
|
+
[clearRuntime]
|
|
726
|
+
);
|
|
727
|
+
return {
|
|
728
|
+
status,
|
|
729
|
+
error,
|
|
730
|
+
available: Boolean(capability?.available && enabled && !disabled),
|
|
731
|
+
stream,
|
|
732
|
+
start,
|
|
733
|
+
stop,
|
|
734
|
+
cancel
|
|
735
|
+
};
|
|
554
736
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
messagePlaceholder: "Message the agent\u2026",
|
|
585
|
-
pausedPlaceholder: "Sending will resume this workstream\u2026",
|
|
586
|
-
inputLabel: "Message the agent",
|
|
587
|
-
keyboardHint: "Enter to queue \xB7 Cmd/Ctrl+Enter to steer \xB7 Shift+Enter for a new line",
|
|
588
|
-
slashCommandBlocked: "That's a slash command \u2014 press Enter in the command list to run it, or edit the line to send a message.",
|
|
589
|
-
controlChangedError: "This workstream was paused while you were sending. Nothing was sent, and your draft is still here.",
|
|
590
|
-
sendFailedError: "Sending failed \u2014 your draft is still here. Try again.",
|
|
591
|
-
dropFiles: "Drop files to attach",
|
|
592
|
-
attachFiles: "Attach files",
|
|
593
|
-
pauseAriaLabel: "Pause this workstream",
|
|
594
|
-
pauseTitle: "Pause this workstream; queued prompts and approvals are preserved",
|
|
595
|
-
sendMessageAriaLabel: "Send message",
|
|
596
|
-
sendAndResumeAriaLabel: "Send and resume",
|
|
597
|
-
sendTitle: "Queue message (Enter); steer with Cmd/Ctrl+Enter",
|
|
598
|
-
sendAndResumeTitle: "Send & resume (Enter); Steer & resume with Cmd/Ctrl+Enter",
|
|
599
|
-
workspacePaused: "Workspace paused",
|
|
600
|
-
pausedHere: "Paused here",
|
|
601
|
-
parentBlocker: "parent",
|
|
602
|
-
narrowerPause: "a narrower pause",
|
|
603
|
-
pausedBy: (displayName) => `Paused by ${displayName}`,
|
|
604
|
-
queuedAhead: (count) => `Send & resume queues behind ${count} waiting prompt${count === 1 ? "" : "s"}.`,
|
|
605
|
-
resumingAndSending: "Resuming and sending\u2026",
|
|
606
|
-
nextMessageResumes: "Your next message resumes this workstream automatically.",
|
|
607
|
-
resumeThisWorkstream: "Resume this workstream",
|
|
608
|
-
resumeShort: "Resume",
|
|
609
|
-
hidePauseDetails: "Hide pause details",
|
|
610
|
-
showPauseDetails: "Show pause details",
|
|
611
|
-
pauseReasonsLabel: "Reasons this workstream is paused",
|
|
612
|
-
pausedByLabel: "Paused by ",
|
|
613
|
-
alsoPausedByLabel: "Also paused by ",
|
|
614
|
-
resumeWorkspace: "Resume workspace",
|
|
615
|
-
resumeFromSession: "Resume from this session",
|
|
616
|
-
sessionCanRun: "This session will be able to run",
|
|
617
|
-
stillPausedBy: (displayName) => `Still paused by ${displayName}`,
|
|
618
|
-
restoredResourcesLabel: "Restored prompt resources",
|
|
619
|
-
restoredFile: (fileId) => `File ${fileId.slice(0, 8)}`,
|
|
620
|
-
removeRestoredResource: (index) => `Remove restored resource ${index + 1}`,
|
|
621
|
-
uploading: "Uploading",
|
|
622
|
-
uploadFailed: "Upload failed",
|
|
623
|
-
retryAttachment: (name) => `Retry ${name}`,
|
|
624
|
-
retryUpload: "Retry upload",
|
|
625
|
-
removeAttachment: (name) => `Remove ${name}`,
|
|
626
|
-
confirmCommand: (name) => `Confirm /${name}`,
|
|
627
|
-
confirmDescription: (command) => `Run /${command.name}? ${command.description}`,
|
|
628
|
-
cancel: "Cancel",
|
|
629
|
-
runCommand: (name) => `Run /${name}`,
|
|
630
|
-
commands: "Commands",
|
|
631
|
-
slashCommandsLabel: "Slash commands",
|
|
632
|
-
modelLabel: "Model",
|
|
633
|
-
close: "Close",
|
|
634
|
-
danger: "danger",
|
|
635
|
-
draftConflict: "This draft changed in another tab. Your local draft is still here.",
|
|
636
|
-
useOtherDraft: "Use other draft",
|
|
637
|
-
keepMine: "Keep mine",
|
|
638
|
-
savingDraft: "Saving draft\u2026",
|
|
639
|
-
formatBytes,
|
|
640
|
-
formatRelativeTime
|
|
641
|
-
};
|
|
642
|
-
function useChatComposerController({
|
|
643
|
-
delivery,
|
|
644
|
-
draft,
|
|
645
|
-
control,
|
|
646
|
-
effectiveControl,
|
|
647
|
-
queuedAheadCount = 0,
|
|
648
|
-
canControlWorkspace = false,
|
|
649
|
-
controlLinks,
|
|
650
|
-
disabled = false,
|
|
651
|
-
attachments,
|
|
652
|
-
commands = defaultCommands,
|
|
653
|
-
commandContext,
|
|
654
|
-
onClearView,
|
|
655
|
-
onPaste,
|
|
656
|
-
messages: messageOverrides
|
|
657
|
-
}) {
|
|
658
|
-
const messages = useMemo3(
|
|
659
|
-
() => ({ ...defaultChatComposerMessages, ...messageOverrides }),
|
|
660
|
-
[messageOverrides]
|
|
661
|
-
);
|
|
662
|
-
const id = useId2();
|
|
663
|
-
const rootRef = useRef2(null);
|
|
664
|
-
const textareaRef = useRef2(null);
|
|
665
|
-
const pauseButtonRef = useRef2(null);
|
|
666
|
-
const fileInputRef = useRef2(null);
|
|
667
|
-
const listboxId = useId2();
|
|
668
|
-
const paused = effectiveControl?.state === "paused";
|
|
669
|
-
const [controlDetailsOpen, setControlDetailsOpen] = useState2(false);
|
|
670
|
-
const [paletteMounted, setPaletteMounted] = useState2(false);
|
|
671
|
-
const [submitting, setSubmitting] = useState2(false);
|
|
672
|
-
const submittingRef = useRef2(false);
|
|
673
|
-
const controlOperationRef = useRef2(false);
|
|
674
|
-
const mountedRef = useRef2(true);
|
|
675
|
-
useEffect(() => {
|
|
676
|
-
mountedRef.current = true;
|
|
677
|
-
return () => {
|
|
678
|
-
mountedRef.current = false;
|
|
679
|
-
};
|
|
680
|
-
}, []);
|
|
681
|
-
useEffect(() => {
|
|
682
|
-
const openControl = (event) => {
|
|
683
|
-
const requestedId = event instanceof CustomEvent && isRecord(event.detail) && typeof event.detail.composerId === "string" ? event.detail.composerId : null;
|
|
684
|
-
if (requestedId && requestedId !== id) return;
|
|
685
|
-
if (!requestedId) {
|
|
686
|
-
const roots = document.querySelectorAll("[data-og-composer-id]");
|
|
687
|
-
if (roots.length > 1 && !rootRef.current?.contains(document.activeElement)) return;
|
|
688
|
-
}
|
|
689
|
-
textareaRef.current?.scrollIntoView({ block: "end", behavior: "smooth" });
|
|
690
|
-
if (paused) {
|
|
691
|
-
setControlDetailsOpen(true);
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
window.requestAnimationFrame(() => pauseButtonRef.current?.focus());
|
|
695
|
-
};
|
|
696
|
-
document.addEventListener(OPEN_WORKSTREAM_CONTROL_EVENT, openControl);
|
|
697
|
-
return () => document.removeEventListener(OPEN_WORKSTREAM_CONTROL_EVENT, openControl);
|
|
698
|
-
}, [id, paused]);
|
|
699
|
-
const blockedByAttachment = attachments?.hasUnresolved === true;
|
|
700
|
-
const hasReadyAttachment = (attachments?.readyResources.length ?? 0) > 0;
|
|
701
|
-
const [dragging, setDragging] = useState2(false);
|
|
702
|
-
const dragCarriesFiles = (event) => event.dataTransfer !== null && [...event.dataTransfer.types].includes("Files");
|
|
703
|
-
const handleDragOver = useCallback2(
|
|
704
|
-
(event) => {
|
|
705
|
-
if (!attachments || !dragCarriesFiles(event)) return;
|
|
706
|
-
event.preventDefault();
|
|
707
|
-
setDragging(true);
|
|
708
|
-
},
|
|
709
|
-
[attachments]
|
|
710
|
-
);
|
|
711
|
-
const handleDragLeave = useCallback2(
|
|
712
|
-
(event) => {
|
|
713
|
-
if (!attachments) return;
|
|
714
|
-
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
715
|
-
setDragging(false);
|
|
716
|
-
},
|
|
717
|
-
[attachments]
|
|
718
|
-
);
|
|
719
|
-
const handleDrop = useCallback2(
|
|
720
|
-
(event) => {
|
|
721
|
-
if (!attachments || !dragCarriesFiles(event)) return;
|
|
722
|
-
event.preventDefault();
|
|
723
|
-
setDragging(false);
|
|
724
|
-
if (event.dataTransfer.files.length > 0) attachments.addFiles(event.dataTransfer.files);
|
|
725
|
-
},
|
|
726
|
-
[attachments]
|
|
727
|
-
);
|
|
728
|
-
const [notice, setNotice] = useState2(null);
|
|
729
|
-
const [helpOpen, setHelpOpen] = useState2(false);
|
|
730
|
-
const [confirmState, setConfirmState] = useState2(null);
|
|
731
|
-
const pendingConfirm = useRef2(null);
|
|
732
|
-
const settleConfirmation = useCallback2((confirmed) => {
|
|
733
|
-
const resolve = pendingConfirm.current;
|
|
734
|
-
pendingConfirm.current = null;
|
|
735
|
-
setConfirmState(null);
|
|
736
|
-
resolve?.(confirmed);
|
|
737
|
-
}, []);
|
|
738
|
-
useEffect(
|
|
739
|
-
() => () => {
|
|
740
|
-
pendingConfirm.current?.(false);
|
|
741
|
-
pendingConfirm.current = null;
|
|
742
|
-
},
|
|
743
|
-
[]
|
|
744
|
-
);
|
|
745
|
-
const resizeInput = useCallback2(() => {
|
|
746
|
-
const textarea = textareaRef.current;
|
|
747
|
-
if (!textarea) return;
|
|
748
|
-
textarea.style.height = "0px";
|
|
749
|
-
textarea.style.height = `${Math.min(textarea.scrollHeight, 220)}px`;
|
|
750
|
-
}, []);
|
|
751
|
-
useEffect(() => resizeInput(), [delivery.value, resizeInput]);
|
|
752
|
-
const handlers = useMemo3(
|
|
753
|
-
() => ({
|
|
754
|
-
notice: (next) => {
|
|
755
|
-
setNotice(next);
|
|
756
|
-
delivery.clearError();
|
|
757
|
-
},
|
|
758
|
-
openHelp: () => setHelpOpen(true),
|
|
759
|
-
clearView: () => {
|
|
760
|
-
if (!onClearView) return false;
|
|
761
|
-
onClearView();
|
|
762
|
-
return true;
|
|
763
|
-
},
|
|
764
|
-
confirm: (command) => new Promise((resolve) => {
|
|
765
|
-
pendingConfirm.current?.(false);
|
|
766
|
-
pendingConfirm.current = resolve;
|
|
767
|
-
setConfirmState({ command, resolve: settleConfirmation });
|
|
768
|
-
})
|
|
769
|
-
}),
|
|
770
|
-
[delivery, onClearView, settleConfirmation]
|
|
771
|
-
);
|
|
772
|
-
const palette = useSlashCommands({
|
|
773
|
-
commands,
|
|
774
|
-
context: commandContext,
|
|
775
|
-
handlers,
|
|
776
|
-
value: delivery.value,
|
|
777
|
-
setValue: delivery.setValue
|
|
778
|
-
});
|
|
779
|
-
const paletteEnabled = commandContext !== void 0;
|
|
780
|
-
const commandDraftBlocked = paletteEnabled && palette.isCommandDraft;
|
|
781
|
-
const submitBlocker = disabled ? "disabled" : blockedByAttachment ? "attachment" : delivery.sending || submitting ? "sending" : commandDraftBlocked ? "command" : delivery.canSend || hasReadyAttachment ? null : "empty";
|
|
782
|
-
const canSubmit = submitBlocker === null;
|
|
783
|
-
const submit = useCallback2(
|
|
784
|
-
async (mode) => {
|
|
785
|
-
if (commandDraftBlocked) {
|
|
786
|
-
setNotice({ tone: "error", message: messages.slashCommandBlocked });
|
|
787
|
-
delivery.clearError();
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
if (disabled || blockedByAttachment || delivery.sending || submittingRef.current)
|
|
791
|
-
return false;
|
|
792
|
-
if (!delivery.canSend && !hasReadyAttachment) return false;
|
|
793
|
-
submittingRef.current = true;
|
|
794
|
-
setSubmitting(true);
|
|
795
|
-
try {
|
|
796
|
-
return mode === "steer" ? await delivery.steer() : await delivery.send();
|
|
797
|
-
} finally {
|
|
798
|
-
submittingRef.current = false;
|
|
799
|
-
if (mountedRef.current) setSubmitting(false);
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
[
|
|
803
|
-
blockedByAttachment,
|
|
804
|
-
commandDraftBlocked,
|
|
805
|
-
delivery,
|
|
806
|
-
disabled,
|
|
807
|
-
hasReadyAttachment,
|
|
808
|
-
messages.slashCommandBlocked
|
|
809
|
-
]
|
|
810
|
-
);
|
|
811
|
-
const handleKeyDown = useCallback2(
|
|
812
|
-
(event) => {
|
|
813
|
-
if (paletteEnabled && paletteMounted && palette.onKeyDown(event)) return;
|
|
814
|
-
if (!shouldSubmitOnKey(event)) return;
|
|
815
|
-
event.preventDefault();
|
|
816
|
-
void submit(shouldSteerOnKey(event) ? "steer" : "queue");
|
|
817
|
-
},
|
|
818
|
-
[palette, paletteEnabled, paletteMounted, submit]
|
|
819
|
-
);
|
|
820
|
-
const handlePaste = useCallback2(
|
|
821
|
-
(event) => {
|
|
822
|
-
onPaste?.(event);
|
|
823
|
-
attachments?.addFromPaste(event);
|
|
824
|
-
},
|
|
825
|
-
[attachments, onPaste]
|
|
826
|
-
);
|
|
827
|
-
const handleFileChange = useCallback2(
|
|
828
|
-
(event) => {
|
|
829
|
-
if (event.target.files) attachments?.addFiles(event.target.files);
|
|
830
|
-
event.target.value = "";
|
|
831
|
-
},
|
|
832
|
-
[attachments]
|
|
833
|
-
);
|
|
834
|
-
const helpCommands = useMemo3(
|
|
835
|
-
() => commands.filter((command) => {
|
|
836
|
-
if (command.permission && commandContext) {
|
|
837
|
-
const permissions = commandContext.permissions;
|
|
838
|
-
return permissions.includes(command.permission) || permissions.includes("workspace:admin");
|
|
839
|
-
}
|
|
840
|
-
return true;
|
|
841
|
-
}),
|
|
842
|
-
[commandContext, commands]
|
|
843
|
-
);
|
|
844
|
-
const activeNotice = notice ?? (delivery.error ? {
|
|
845
|
-
tone: "error",
|
|
846
|
-
message: /control changed|paused while/i.test(delivery.error.message) ? messages.controlChangedError : delivery.error.message || messages.sendFailedError
|
|
847
|
-
} : null);
|
|
848
|
-
useEffect(() => {
|
|
849
|
-
if (notice?.tone !== "ok") return;
|
|
850
|
-
const timer = window.setTimeout(
|
|
851
|
-
() => setNotice((current) => current === notice ? null : current),
|
|
852
|
-
2400
|
|
853
|
-
);
|
|
854
|
-
return () => window.clearTimeout(timer);
|
|
855
|
-
}, [notice]);
|
|
856
|
-
const runControlOperation = useCallback2(async (operation) => {
|
|
857
|
-
if (controlOperationRef.current) return false;
|
|
858
|
-
controlOperationRef.current = true;
|
|
859
|
-
try {
|
|
860
|
-
await operation();
|
|
861
|
-
return true;
|
|
862
|
-
} finally {
|
|
863
|
-
controlOperationRef.current = false;
|
|
864
|
-
}
|
|
865
|
-
}, []);
|
|
866
|
-
const pause = useCallback2(
|
|
867
|
-
async (reason) => {
|
|
868
|
-
if (!control || control.pausing || control.resuming) return false;
|
|
869
|
-
return await runControlOperation(() => control.pause(reason));
|
|
870
|
-
},
|
|
871
|
-
[control, runControlOperation]
|
|
872
|
-
);
|
|
873
|
-
const resume = useCallback2(
|
|
874
|
-
async (reason) => {
|
|
875
|
-
if (!control || control.pausing || control.resuming) return false;
|
|
876
|
-
return await runControlOperation(() => control.resume(reason));
|
|
877
|
-
},
|
|
878
|
-
[control, runControlOperation]
|
|
879
|
-
);
|
|
880
|
-
const resumeScope = useCallback2(
|
|
881
|
-
async (option) => {
|
|
882
|
-
if (!control || control.pausing || control.resuming) return false;
|
|
883
|
-
return await runControlOperation(() => control.resumeScope(option));
|
|
884
|
-
},
|
|
885
|
-
[control, runControlOperation]
|
|
886
|
-
);
|
|
737
|
+
function chooseMimeType(accepted) {
|
|
738
|
+
const normalized = accepted.map((value) => value.trim().toLowerCase());
|
|
739
|
+
return MIME_PREFERENCES.find((mimeType) => {
|
|
740
|
+
const bare = mimeType.split(";")[0] ?? mimeType;
|
|
741
|
+
const allowed = normalized.includes(mimeType) || normalized.includes(bare) || normalized.some((candidate) => candidate.split(";")[0] === bare);
|
|
742
|
+
return allowed && (typeof MediaRecorder.isTypeSupported !== "function" || MediaRecorder.isTypeSupported(mimeType));
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function errorCode(error) {
|
|
746
|
+
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
|
|
747
|
+
return error.code;
|
|
748
|
+
}
|
|
749
|
+
if (error instanceof DOMException && error.name === "NotAllowedError") return "permission_denied";
|
|
750
|
+
if (error instanceof DOMException && error.name === "NotSupportedError") return "not_supported";
|
|
751
|
+
return "unknown";
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/commands/registry.ts
|
|
755
|
+
function parseCommandLine(value) {
|
|
756
|
+
if (value[0] !== "/") {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
const body = value.slice(1);
|
|
760
|
+
const firstSpace = body.indexOf(" ");
|
|
761
|
+
if (firstSpace === -1) {
|
|
762
|
+
return { name: body, rest: "", hasTrailingSpace: false, args: [] };
|
|
763
|
+
}
|
|
764
|
+
const name = body.slice(0, firstSpace);
|
|
765
|
+
const rest = body.slice(firstSpace + 1);
|
|
887
766
|
return {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
fileInputRef,
|
|
893
|
-
listboxId,
|
|
894
|
-
effectiveControl,
|
|
895
|
-
queuedAheadCount,
|
|
896
|
-
canControlWorkspace,
|
|
897
|
-
controlLinks,
|
|
898
|
-
disabled,
|
|
899
|
-
attachments,
|
|
900
|
-
messages,
|
|
901
|
-
sending: delivery.sending || submitting,
|
|
902
|
-
error: delivery.error,
|
|
903
|
-
clearError: delivery.clearError,
|
|
904
|
-
hasDraftState: draft !== void 0,
|
|
905
|
-
draftConflict: draft?.draftConflict ?? null,
|
|
906
|
-
draftSaving: draft?.draftSaving ?? false,
|
|
907
|
-
restoredResources: draft?.restoredResources ?? [],
|
|
908
|
-
removeRestoredResource: draft?.removeRestoredResource,
|
|
909
|
-
resolveDraftConflict: draft?.resolveDraftConflict,
|
|
910
|
-
hasControl: control !== void 0,
|
|
911
|
-
pausing: control?.pausing ?? false,
|
|
912
|
-
resuming: control?.resuming ?? false,
|
|
913
|
-
pause,
|
|
914
|
-
resume,
|
|
915
|
-
resumeScope,
|
|
916
|
-
paused,
|
|
917
|
-
controlDetailsOpen,
|
|
918
|
-
setControlDetailsOpen,
|
|
919
|
-
paletteMounted,
|
|
920
|
-
setPaletteMounted,
|
|
921
|
-
dragging,
|
|
922
|
-
handleDragOver,
|
|
923
|
-
handleDragLeave,
|
|
924
|
-
handleDrop,
|
|
925
|
-
palette,
|
|
926
|
-
paletteEnabled,
|
|
927
|
-
commandDraftBlocked,
|
|
928
|
-
confirmState,
|
|
929
|
-
settleConfirmation,
|
|
930
|
-
helpOpen,
|
|
931
|
-
setHelpOpen,
|
|
932
|
-
helpCommands,
|
|
933
|
-
activeNotice,
|
|
934
|
-
canSubmit,
|
|
935
|
-
submitBlocker,
|
|
936
|
-
submit,
|
|
937
|
-
handleKeyDown,
|
|
938
|
-
handlePaste,
|
|
939
|
-
handleFileChange,
|
|
940
|
-
focusInput: () => textareaRef.current?.focus(),
|
|
941
|
-
setValue: delivery.setValue,
|
|
942
|
-
value: delivery.value
|
|
767
|
+
name,
|
|
768
|
+
rest,
|
|
769
|
+
hasTrailingSpace: true,
|
|
770
|
+
args: rest.split(/\s+/).filter((token) => token.length > 0)
|
|
943
771
|
};
|
|
944
772
|
}
|
|
945
|
-
var
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
773
|
+
var PERMISSION_SUPERUSER = "workspace:admin";
|
|
774
|
+
function hasPermission(required, permissions) {
|
|
775
|
+
if (!required) {
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
return permissions.includes(required) || permissions.includes(PERMISSION_SUPERUSER);
|
|
950
779
|
}
|
|
951
|
-
function
|
|
952
|
-
|
|
780
|
+
function matchCommand(commands, value) {
|
|
781
|
+
const parsed = parseCommandLine(value);
|
|
782
|
+
if (!parsed) {
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
const token = parsed.name.toLowerCase();
|
|
786
|
+
return commands.find((command) => command.name === token || command.aliases?.includes(token)) ?? null;
|
|
953
787
|
}
|
|
954
|
-
function
|
|
955
|
-
|
|
788
|
+
function filterCommands(commands, token, ctx) {
|
|
789
|
+
const needle = token.toLowerCase();
|
|
790
|
+
return commands.filter((command) => {
|
|
791
|
+
if (!hasPermission(command.permission, ctx.permissions)) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
if (command.available && !command.available(ctx)) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
if (needle.length === 0) {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
return command.name.startsWith(needle) || (command.aliases?.some((alias) => alias.startsWith(needle)) ?? false);
|
|
801
|
+
});
|
|
956
802
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
"
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
803
|
+
function argHint(args) {
|
|
804
|
+
if (!args || args.length === 0) {
|
|
805
|
+
return "";
|
|
806
|
+
}
|
|
807
|
+
return args.map((arg) => {
|
|
808
|
+
const label = arg.oneOf ? arg.oneOf.join("|") : arg.name;
|
|
809
|
+
return arg.required ? `<${label}>` : `[${label}]`;
|
|
810
|
+
}).join(" ");
|
|
811
|
+
}
|
|
812
|
+
function firstMissingRequiredArg(command, args) {
|
|
813
|
+
const required = (command.args ?? []).filter((arg) => arg.required);
|
|
814
|
+
for (let i = 0; i < required.length; i += 1) {
|
|
815
|
+
const value = args[i];
|
|
816
|
+
if (value === void 0 || value.length === 0) {
|
|
817
|
+
return required[i] ?? null;
|
|
970
818
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
{
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
819
|
+
}
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
function requireSession(ctx) {
|
|
823
|
+
if (!ctx.sessionId) {
|
|
824
|
+
throw new Error("No active session yet \u2014 start a session first.");
|
|
825
|
+
}
|
|
826
|
+
return ctx.sessionId;
|
|
827
|
+
}
|
|
828
|
+
var hasSession = (ctx) => ctx.sessionId !== null;
|
|
829
|
+
var defaultCommands = [
|
|
830
|
+
{
|
|
831
|
+
name: "help",
|
|
832
|
+
aliases: ["?"],
|
|
833
|
+
description: "Show available commands.",
|
|
834
|
+
run: (_args, ctx) => {
|
|
835
|
+
ctx.openHelp();
|
|
836
|
+
return { status: "ok" };
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
name: "clear-view",
|
|
841
|
+
description: "Clear the local timeline view (this device only; no server change).",
|
|
842
|
+
run: (_args, ctx) => {
|
|
843
|
+
const cleared = ctx.clearView();
|
|
844
|
+
if (!cleared) {
|
|
845
|
+
return {
|
|
846
|
+
status: "error",
|
|
847
|
+
message: "This view can't be cleared here (no local timeline to reset)."
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
return { status: "ok", message: "Local view cleared." };
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
name: "goal",
|
|
855
|
+
description: "Pause or resume the session's goal loop.",
|
|
856
|
+
permission: "sessions:control",
|
|
857
|
+
available: hasSession,
|
|
858
|
+
args: [
|
|
859
|
+
{ name: "action", required: true, oneOf: ["pause", "resume"], description: "pause | resume" }
|
|
860
|
+
],
|
|
861
|
+
run: async (args, ctx) => {
|
|
862
|
+
const sessionId = requireSession(ctx);
|
|
863
|
+
const action = args[0];
|
|
864
|
+
if (action !== "pause" && action !== "resume") {
|
|
865
|
+
return { status: "error", message: "Usage: /goal pause | /goal resume" };
|
|
866
|
+
}
|
|
867
|
+
try {
|
|
868
|
+
await ctx.client.updateGoal(ctx.workspaceId, sessionId, {
|
|
869
|
+
status: action === "pause" ? "paused" : "active"
|
|
870
|
+
});
|
|
871
|
+
return { status: "ok", message: action === "pause" ? "Goal paused." : "Goal resumed." };
|
|
872
|
+
} catch (cause) {
|
|
873
|
+
return { status: "error", message: goalErrorMessage(cause, action) };
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
name: "compact",
|
|
879
|
+
description: "Compact the conversation context now.",
|
|
880
|
+
permission: "sessions:control",
|
|
881
|
+
available: hasSession,
|
|
882
|
+
run: async (_args, ctx) => {
|
|
883
|
+
const sessionId = requireSession(ctx);
|
|
884
|
+
try {
|
|
885
|
+
const result = await ctx.client.compactSessionContext(ctx.workspaceId, sessionId);
|
|
886
|
+
return { status: "ok", message: result.message };
|
|
887
|
+
} catch (cause) {
|
|
888
|
+
return { status: "error", message: errorMessage(cause) ?? "Could not compact context." };
|
|
889
|
+
}
|
|
999
890
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
"
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
children: [
|
|
1020
|
-
controller.dragging ? /* @__PURE__ */ jsx3(
|
|
1021
|
-
"div",
|
|
1022
|
-
{
|
|
1023
|
-
"aria-hidden": true,
|
|
1024
|
-
className: cn(
|
|
1025
|
-
"pointer-events-none absolute inset-0 z-10 flex items-center justify-center",
|
|
1026
|
-
"rounded-og-lg bg-og-surface-1/85 text-sm font-medium text-og-accent backdrop-blur-[1px]"
|
|
1027
|
-
),
|
|
1028
|
-
children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-2", children: [
|
|
1029
|
-
/* @__PURE__ */ jsx3(PaperclipIcon, { className: "size-4" }),
|
|
1030
|
-
controller.messages.dropFiles
|
|
1031
|
-
] })
|
|
1032
|
-
}
|
|
1033
|
-
) : null,
|
|
1034
|
-
children
|
|
1035
|
-
]
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
name: "clear",
|
|
894
|
+
description: "Clear the conversation context (destructive; audit-preserved).",
|
|
895
|
+
permission: "sessions:control",
|
|
896
|
+
danger: true,
|
|
897
|
+
available: hasSession,
|
|
898
|
+
run: async (_args, ctx) => {
|
|
899
|
+
const sessionId = requireSession(ctx);
|
|
900
|
+
const confirmed = await ctx.confirm();
|
|
901
|
+
if (!confirmed) {
|
|
902
|
+
return { status: "ok", keepDraft: true };
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
await ctx.client.clearSessionContext(ctx.workspaceId, sessionId);
|
|
906
|
+
return { status: "ok", message: "Context cleared." };
|
|
907
|
+
} catch (cause) {
|
|
908
|
+
return { status: "error", message: clearErrorMessage(cause) };
|
|
909
|
+
}
|
|
1036
910
|
}
|
|
911
|
+
}
|
|
912
|
+
];
|
|
913
|
+
function errorMessage(cause) {
|
|
914
|
+
if (cause && typeof cause === "object" && "message" in cause && typeof cause.message === "string") {
|
|
915
|
+
return cause.message;
|
|
916
|
+
}
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
function statusCode(cause) {
|
|
920
|
+
if (cause && typeof cause === "object" && "status" in cause && typeof cause.status === "number") {
|
|
921
|
+
return cause.status;
|
|
922
|
+
}
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
function goalErrorMessage(cause, action) {
|
|
926
|
+
const code = statusCode(cause);
|
|
927
|
+
if (code === 404) {
|
|
928
|
+
return "This session has no goal to control.";
|
|
929
|
+
}
|
|
930
|
+
if (code === 409) {
|
|
931
|
+
return action === "resume" ? "Only a paused goal can be resumed." : "Goal is already in a terminal state.";
|
|
932
|
+
}
|
|
933
|
+
return errorMessage(cause) ?? `Could not ${action} the goal.`;
|
|
934
|
+
}
|
|
935
|
+
function clearErrorMessage(cause) {
|
|
936
|
+
if (statusCode(cause) === 409) {
|
|
937
|
+
return "Pause the session and wait for the current inference to settle, then clear context.";
|
|
938
|
+
}
|
|
939
|
+
return errorMessage(cause) ?? "Could not clear context.";
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/hooks/use-slash-commands.ts
|
|
943
|
+
import { useCallback as useCallback3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
|
|
944
|
+
function useSlashCommands(options) {
|
|
945
|
+
const { commands, context, handlers, value, setValue } = options;
|
|
946
|
+
const [highlight, setHighlight] = useState3(0);
|
|
947
|
+
const [dismissedValue, setDismissedValue] = useState3(null);
|
|
948
|
+
const dismissed = dismissedValue !== null && dismissedValue === value;
|
|
949
|
+
const navigatedRef = useRef3(false);
|
|
950
|
+
const navTokenRef = useRef3(value);
|
|
951
|
+
if (navTokenRef.current !== value) {
|
|
952
|
+
navTokenRef.current = value;
|
|
953
|
+
navigatedRef.current = false;
|
|
954
|
+
}
|
|
955
|
+
const parsed = useMemo2(() => parseCommandLine(value), [value]);
|
|
956
|
+
const filterCtx = useMemo2(
|
|
957
|
+
() => ({
|
|
958
|
+
sessionId: context?.sessionId ?? null,
|
|
959
|
+
status: context?.status ?? null,
|
|
960
|
+
permissions: context?.permissions ?? []
|
|
961
|
+
}),
|
|
962
|
+
[context?.sessionId, context?.status, context?.permissions]
|
|
1037
963
|
);
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (!controller.paused || !controller.effectiveControl || !controller.hasControl) return null;
|
|
1042
|
-
return /* @__PURE__ */ jsx3(
|
|
1043
|
-
WorkstreamPausedStrip,
|
|
1044
|
-
{
|
|
1045
|
-
control: controller.effectiveControl,
|
|
1046
|
-
queuedAheadCount: controller.queuedAheadCount,
|
|
1047
|
-
open: controller.controlDetailsOpen,
|
|
1048
|
-
busy: controller.resuming,
|
|
1049
|
-
sending: controller.sending,
|
|
1050
|
-
canControlWorkspace: controller.canControlWorkspace,
|
|
1051
|
-
controlLinks: controller.controlLinks,
|
|
1052
|
-
messages: controller.messages,
|
|
1053
|
-
onOpenChange: controller.setControlDetailsOpen,
|
|
1054
|
-
onResume: () => void controller.resume(),
|
|
1055
|
-
onResumeOption: (option) => void controller.resumeScope(option)
|
|
964
|
+
const activeCommand = useMemo2(() => {
|
|
965
|
+
if (!parsed || !parsed.hasTrailingSpace) {
|
|
966
|
+
return null;
|
|
1056
967
|
}
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
return /* @__PURE__ */ jsx3(
|
|
1063
|
-
RestoredResourceChips,
|
|
1064
|
-
{
|
|
1065
|
-
resources: controller.restoredResources,
|
|
1066
|
-
messages: controller.messages,
|
|
1067
|
-
onRemove: controller.removeRestoredResource
|
|
968
|
+
return matchCommand(commands, value);
|
|
969
|
+
}, [commands, value, parsed]);
|
|
970
|
+
const items = useMemo2(() => {
|
|
971
|
+
if (!parsed) {
|
|
972
|
+
return [];
|
|
1068
973
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
function Attachments() {
|
|
1072
|
-
const controller = useComposerController();
|
|
1073
|
-
if (!controller.attachments || controller.attachments.attachments.length === 0) return null;
|
|
1074
|
-
return /* @__PURE__ */ jsx3(
|
|
1075
|
-
AttachmentChips,
|
|
1076
|
-
{
|
|
1077
|
-
attachments: controller.attachments.attachments,
|
|
1078
|
-
messages: controller.messages,
|
|
1079
|
-
onRemove: controller.attachments.remove,
|
|
1080
|
-
onRetry: controller.attachments.retry
|
|
974
|
+
if (activeCommand) {
|
|
975
|
+
return [activeCommand];
|
|
1081
976
|
}
|
|
977
|
+
return filterCommands(commands, parsed.name, filterCtx);
|
|
978
|
+
}, [commands, parsed, activeCommand, filterCtx]);
|
|
979
|
+
const open = parsed !== null && items.length > 0 && !dismissed;
|
|
980
|
+
const isCommandDraft = parsed !== null && items.length > 0;
|
|
981
|
+
const clampedHighlight = items.length === 0 ? 0 : Math.min(highlight, items.length - 1);
|
|
982
|
+
const activeArgHint = activeCommand ? argHint(activeCommand.args) : "";
|
|
983
|
+
const buildContext = useCallback3(
|
|
984
|
+
(command) => {
|
|
985
|
+
if (!context) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
return { ...context, ...handlers, confirm: () => handlers.confirm(command) };
|
|
989
|
+
},
|
|
990
|
+
[context, handlers]
|
|
1082
991
|
);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
"aria-controls": paletteOpen ? controller.listboxId : void 0,
|
|
1103
|
-
"aria-activedescendant": paletteOpen ? `${controller.listboxId}-option-${controller.palette.highlight}` : void 0,
|
|
1104
|
-
className: cn(
|
|
1105
|
-
"block w-full resize-none bg-transparent px-4 pt-3.5 pb-1 text-base leading-6 md:text-og-md",
|
|
1106
|
-
"text-og-fg placeholder:text-og-fg-subtle focus:outline-none focus-visible:outline-none",
|
|
1107
|
-
"disabled:cursor-not-allowed disabled:opacity-60",
|
|
1108
|
-
className
|
|
1109
|
-
)
|
|
1110
|
-
}
|
|
992
|
+
const execute = useCallback3(
|
|
993
|
+
async (command, args) => {
|
|
994
|
+
const ctx = buildContext(command);
|
|
995
|
+
if (!ctx) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
const result = await command.run(args, ctx);
|
|
1000
|
+
if (result.message) {
|
|
1001
|
+
ctx.notice({ tone: result.status === "ok" ? "ok" : "error", message: result.message });
|
|
1002
|
+
}
|
|
1003
|
+
if (result.status === "ok" && !result.keepDraft) {
|
|
1004
|
+
setValue("");
|
|
1005
|
+
}
|
|
1006
|
+
} catch (cause) {
|
|
1007
|
+
ctx.notice({ tone: "error", message: errorMessage2(cause) });
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
[buildContext, setValue]
|
|
1111
1011
|
);
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
onConfirm: () => controller.settleConfirmation(true),
|
|
1124
|
-
returnFocusRef: controller.textareaRef
|
|
1012
|
+
const autocomplete = useCallback3(
|
|
1013
|
+
(command) => {
|
|
1014
|
+
setValue(`/${command.name} `);
|
|
1015
|
+
setHighlight(0);
|
|
1016
|
+
},
|
|
1017
|
+
[setValue]
|
|
1018
|
+
);
|
|
1019
|
+
const autocompleteHighlighted = useCallback3(() => {
|
|
1020
|
+
const command = items[clampedHighlight];
|
|
1021
|
+
if (command) {
|
|
1022
|
+
autocomplete(command);
|
|
1125
1023
|
}
|
|
1024
|
+
}, [items, clampedHighlight, autocomplete]);
|
|
1025
|
+
const runningRef = useRef3(false);
|
|
1026
|
+
const runResolved = useCallback3(
|
|
1027
|
+
async (command, executionOptions) => {
|
|
1028
|
+
if (!parsed || runningRef.current) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
runningRef.current = true;
|
|
1032
|
+
try {
|
|
1033
|
+
const explicit = executionOptions?.explicit ?? false;
|
|
1034
|
+
const nameMatchesToken = command.name === parsed.name.toLowerCase() || (command.aliases?.includes(parsed.name.toLowerCase()) ?? false);
|
|
1035
|
+
if (!explicit && !activeCommand && !nameMatchesToken && !parsed.hasTrailingSpace) {
|
|
1036
|
+
autocomplete(command);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
const args = nameMatchesToken || parsed.hasTrailingSpace ? parsed.args : [];
|
|
1040
|
+
const missing = firstMissingRequiredArg(command, args);
|
|
1041
|
+
if (missing) {
|
|
1042
|
+
if (!parsed.hasTrailingSpace) {
|
|
1043
|
+
autocomplete(command);
|
|
1044
|
+
}
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
await execute(command, args);
|
|
1048
|
+
} finally {
|
|
1049
|
+
runningRef.current = false;
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
[parsed, activeCommand, autocomplete, execute]
|
|
1126
1053
|
);
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
"div",
|
|
1131
|
-
{
|
|
1132
|
-
...props,
|
|
1133
|
-
ref,
|
|
1134
|
-
className: cn("flex items-end gap-2 px-2.5 pb-2.5 pt-1", className)
|
|
1054
|
+
const runHighlighted = useCallback3(async () => {
|
|
1055
|
+
if (!parsed) {
|
|
1056
|
+
return;
|
|
1135
1057
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
return /* @__PURE__ */ jsx3(
|
|
1141
|
-
"span",
|
|
1142
|
-
{
|
|
1143
|
-
...props,
|
|
1144
|
-
ref,
|
|
1145
|
-
className: cn("flex min-w-0 flex-1 flex-wrap items-center gap-1.5", className)
|
|
1058
|
+
if (navigatedRef.current && !activeCommand) {
|
|
1059
|
+
const highlighted = items[clampedHighlight];
|
|
1060
|
+
if (highlighted) {
|
|
1061
|
+
await runResolved(highlighted, { explicit: true });
|
|
1146
1062
|
}
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
);
|
|
1150
|
-
var Hint = forwardRef(function ComposerHint({ className, children, ...props }, ref) {
|
|
1151
|
-
const controller = useComposerController();
|
|
1152
|
-
return /* @__PURE__ */ jsx3(
|
|
1153
|
-
"span",
|
|
1154
|
-
{
|
|
1155
|
-
...props,
|
|
1156
|
-
ref,
|
|
1157
|
-
className: cn("min-w-0 flex-1 px-1.5 text-og-xs text-og-fg-subtle max-sm:hidden", className),
|
|
1158
|
-
children: children ?? controller.messages.keyboardHint
|
|
1063
|
+
return;
|
|
1159
1064
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
{
|
|
1166
|
-
...props,
|
|
1167
|
-
ref,
|
|
1168
|
-
className: cn("ml-auto flex shrink-0 items-center gap-1.5", className)
|
|
1065
|
+
const token = parsed.name.toLowerCase();
|
|
1066
|
+
const exact = items.find((item) => item.name === token || item.aliases?.includes(token));
|
|
1067
|
+
const command = activeCommand ?? exact ?? items[clampedHighlight];
|
|
1068
|
+
if (!command) {
|
|
1069
|
+
return;
|
|
1169
1070
|
}
|
|
1071
|
+
await runResolved(command);
|
|
1072
|
+
}, [parsed, activeCommand, items, clampedHighlight, runResolved]);
|
|
1073
|
+
const runAt = useCallback3(
|
|
1074
|
+
async (index) => {
|
|
1075
|
+
const command = items[index];
|
|
1076
|
+
if (!command) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
await runResolved(command, { explicit: true });
|
|
1080
|
+
},
|
|
1081
|
+
[items, runResolved]
|
|
1170
1082
|
);
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
className: "hidden",
|
|
1185
|
-
onChange: controller.handleFileChange
|
|
1083
|
+
const onKeyDown = useCallback3(
|
|
1084
|
+
(event) => {
|
|
1085
|
+
if (!open) {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
switch (event.key) {
|
|
1089
|
+
case "ArrowDown": {
|
|
1090
|
+
event.preventDefault();
|
|
1091
|
+
navigatedRef.current = true;
|
|
1092
|
+
setHighlight(
|
|
1093
|
+
(current) => items.length === 0 ? 0 : (Math.min(current, items.length - 1) + 1) % items.length
|
|
1094
|
+
);
|
|
1095
|
+
return true;
|
|
1186
1096
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
onClick: () => controller.fileInputRef.current?.click(),
|
|
1196
|
-
"aria-label": ariaLabel ?? controller.messages.attachFiles,
|
|
1197
|
-
title: title ?? controller.messages.attachFiles,
|
|
1198
|
-
className: cn(
|
|
1199
|
-
"inline-flex size-8 items-center justify-center rounded-og-md",
|
|
1200
|
-
"text-og-fg-muted transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
|
|
1201
|
-
"disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
|
|
1202
|
-
className
|
|
1203
|
-
),
|
|
1204
|
-
children: children ?? /* @__PURE__ */ jsx3(PaperclipIcon, { className: "size-4" })
|
|
1097
|
+
case "ArrowUp": {
|
|
1098
|
+
event.preventDefault();
|
|
1099
|
+
navigatedRef.current = true;
|
|
1100
|
+
setHighlight((current) => {
|
|
1101
|
+
const base = Math.min(current, items.length - 1);
|
|
1102
|
+
return items.length === 0 ? 0 : (base - 1 + items.length) % items.length;
|
|
1103
|
+
});
|
|
1104
|
+
return true;
|
|
1205
1105
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1106
|
+
case "Tab": {
|
|
1107
|
+
event.preventDefault();
|
|
1108
|
+
autocompleteHighlighted();
|
|
1109
|
+
return true;
|
|
1110
|
+
}
|
|
1111
|
+
case "Enter": {
|
|
1112
|
+
if (event.shiftKey || event.nativeEvent?.isComposing) {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
event.preventDefault();
|
|
1116
|
+
void runHighlighted();
|
|
1117
|
+
return true;
|
|
1118
|
+
}
|
|
1119
|
+
case "Escape": {
|
|
1120
|
+
event.preventDefault();
|
|
1121
|
+
setDismissedValue(value);
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
default:
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
[open, items, autocompleteHighlighted, runHighlighted, value]
|
|
1228
1129
|
);
|
|
1130
|
+
return {
|
|
1131
|
+
open,
|
|
1132
|
+
isCommandDraft,
|
|
1133
|
+
items,
|
|
1134
|
+
highlight: clampedHighlight,
|
|
1135
|
+
setHighlight,
|
|
1136
|
+
activeCommand,
|
|
1137
|
+
activeArgHint,
|
|
1138
|
+
onKeyDown,
|
|
1139
|
+
runHighlighted,
|
|
1140
|
+
runAt,
|
|
1141
|
+
autocompleteHighlighted
|
|
1142
|
+
};
|
|
1229
1143
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (!controller.effectiveControl || controller.paused || !controller.hasControl) return null;
|
|
1234
|
-
const busy = controller.pausing || controller.resuming;
|
|
1235
|
-
return /* @__PURE__ */ jsx3(
|
|
1236
|
-
"button",
|
|
1237
|
-
{
|
|
1238
|
-
...props,
|
|
1239
|
-
ref: mergeRefs(controller.pauseButtonRef, ref),
|
|
1240
|
-
type: "button",
|
|
1241
|
-
onClick: () => void controller.pause(),
|
|
1242
|
-
disabled: busy,
|
|
1243
|
-
"aria-label": ariaLabel ?? controller.messages.pauseAriaLabel,
|
|
1244
|
-
title: title ?? controller.messages.pauseTitle,
|
|
1245
|
-
className: cn(
|
|
1246
|
-
"inline-flex size-8 items-center justify-center rounded-og-md border border-og-border pointer-coarse:size-11",
|
|
1247
|
-
"bg-og-surface-2 text-og-fg-muted transition-colors duration-150",
|
|
1248
|
-
"hover:border-og-status-waiting/50 hover:text-og-status-waiting",
|
|
1249
|
-
"disabled:opacity-50",
|
|
1250
|
-
className
|
|
1251
|
-
),
|
|
1252
|
-
children: children ?? (busy ? /* @__PURE__ */ jsx3(LoaderCircleIcon, { className: "size-3.5 animate-og-spin" }) : /* @__PURE__ */ jsx3(PauseIcon, { className: "size-3.5 fill-current" }))
|
|
1253
|
-
}
|
|
1254
|
-
);
|
|
1144
|
+
function errorMessage2(cause) {
|
|
1145
|
+
if (cause instanceof Error) {
|
|
1146
|
+
return cause.message;
|
|
1255
1147
|
}
|
|
1256
|
-
);
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1148
|
+
return String(cause);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// src/components/command-palette.tsx
|
|
1152
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
1153
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1154
|
+
function CommandPalette({
|
|
1155
|
+
open,
|
|
1156
|
+
items,
|
|
1157
|
+
highlight,
|
|
1158
|
+
onHighlight,
|
|
1159
|
+
onRun,
|
|
1160
|
+
argHintText,
|
|
1161
|
+
listboxId,
|
|
1162
|
+
label = "Slash commands",
|
|
1163
|
+
dangerLabel = "danger"
|
|
1164
|
+
}) {
|
|
1165
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsxs(
|
|
1166
|
+
motion.div,
|
|
1167
|
+
{
|
|
1168
|
+
initial: { opacity: 0, y: 6, scale: 0.99 },
|
|
1169
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
1170
|
+
exit: { opacity: 0, y: 6, scale: 0.99 },
|
|
1171
|
+
transition: { duration: 0.13, ease: "easeOut" },
|
|
1172
|
+
className: cn(
|
|
1173
|
+
"absolute bottom-full left-0 right-0 z-20 mb-2 overflow-hidden",
|
|
1174
|
+
"rounded-og-lg border border-og-border bg-og-surface-2 shadow-og-sm"
|
|
1175
|
+
),
|
|
1176
|
+
children: [
|
|
1177
|
+
/* @__PURE__ */ jsx(
|
|
1178
|
+
"ul",
|
|
1179
|
+
{
|
|
1180
|
+
id: listboxId,
|
|
1181
|
+
role: "listbox",
|
|
1182
|
+
"aria-label": label,
|
|
1183
|
+
className: "max-h-72 overflow-y-auto py-1",
|
|
1184
|
+
children: items.map((command, index) => {
|
|
1185
|
+
const selected = index === highlight;
|
|
1186
|
+
const hint = argHint(command.args);
|
|
1187
|
+
return /* @__PURE__ */ jsxs(
|
|
1188
|
+
"li",
|
|
1189
|
+
{
|
|
1190
|
+
id: `${listboxId}-option-${index}`,
|
|
1191
|
+
role: "option",
|
|
1192
|
+
"aria-selected": selected,
|
|
1193
|
+
onMouseEnter: () => onHighlight(index),
|
|
1194
|
+
onMouseDown: (event) => {
|
|
1195
|
+
event.preventDefault();
|
|
1196
|
+
onRun(index);
|
|
1197
|
+
},
|
|
1198
|
+
className: cn(
|
|
1199
|
+
"mx-1 flex cursor-pointer items-center gap-2 rounded-og-md px-2.5 py-1.5",
|
|
1200
|
+
"transition-colors duration-100",
|
|
1201
|
+
selected ? "bg-og-accent/15 text-og-fg" : "text-og-fg-muted hover:bg-og-surface-3"
|
|
1202
|
+
),
|
|
1203
|
+
children: [
|
|
1204
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [
|
|
1205
|
+
/* @__PURE__ */ jsxs(
|
|
1206
|
+
"span",
|
|
1207
|
+
{
|
|
1208
|
+
className: cn(
|
|
1209
|
+
"font-mono text-[13px]",
|
|
1210
|
+
selected ? "text-og-accent" : "text-og-fg"
|
|
1211
|
+
),
|
|
1212
|
+
children: [
|
|
1213
|
+
"/",
|
|
1214
|
+
command.name
|
|
1215
|
+
]
|
|
1216
|
+
}
|
|
1217
|
+
),
|
|
1218
|
+
hint ? /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-[11px] text-og-fg-subtle", children: hint }) : null
|
|
1219
|
+
] }),
|
|
1220
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
1221
|
+
command.danger ? /* @__PURE__ */ jsx("span", { className: "rounded-og-xs bg-og-status-failed/15 px-1 text-[10px] uppercase tracking-wide text-og-status-failed", children: dangerLabel }) : null,
|
|
1222
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[12px] text-og-fg-subtle max-sm:hidden", children: command.description })
|
|
1223
|
+
] })
|
|
1224
|
+
]
|
|
1225
|
+
},
|
|
1226
|
+
command.name
|
|
1227
|
+
);
|
|
1228
|
+
})
|
|
1229
|
+
}
|
|
1277
1230
|
),
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
);
|
|
1283
|
-
function Help() {
|
|
1284
|
-
const controller = useComposerController();
|
|
1285
|
-
return /* @__PURE__ */ jsx3(AnimatePresence2, { children: controller.helpOpen ? /* @__PURE__ */ jsx3(
|
|
1286
|
-
HelpPanel,
|
|
1287
|
-
{
|
|
1288
|
-
commands: controller.helpCommands,
|
|
1289
|
-
messages: controller.messages,
|
|
1290
|
-
onClose: () => controller.setHelpOpen(false)
|
|
1231
|
+
argHintText ? /* @__PURE__ */ jsx("div", { className: "border-t border-og-border px-3 py-1.5 font-mono text-[11px] text-og-fg-subtle", children: argHintText }) : null
|
|
1232
|
+
]
|
|
1291
1233
|
}
|
|
1292
1234
|
) : null });
|
|
1293
1235
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
exit: { opacity: 0, height: 0 },
|
|
1303
|
-
className: cn(
|
|
1304
|
-
"overflow-hidden px-1 pt-1.5 text-xs",
|
|
1305
|
-
controller.activeNotice.tone === "ok" ? "text-og-fg-muted" : "text-og-status-failed"
|
|
1306
|
-
),
|
|
1307
|
-
children: controller.activeNotice.message
|
|
1308
|
-
}
|
|
1309
|
-
) : null }),
|
|
1310
|
-
controller.draftConflict ? /* @__PURE__ */ jsxs3("div", { className: "mt-1.5 flex flex-wrap items-center gap-2 px-1 text-og-xs text-og-status-failed", children: [
|
|
1311
|
-
/* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1", children: controller.messages.draftConflict }),
|
|
1312
|
-
/* @__PURE__ */ jsx3(
|
|
1313
|
-
"button",
|
|
1314
|
-
{
|
|
1315
|
-
type: "button",
|
|
1316
|
-
className: "underline underline-offset-2",
|
|
1317
|
-
onClick: () => void controller.resolveDraftConflict?.("use_remote"),
|
|
1318
|
-
children: controller.messages.useOtherDraft
|
|
1319
|
-
}
|
|
1320
|
-
),
|
|
1321
|
-
/* @__PURE__ */ jsx3(
|
|
1322
|
-
"button",
|
|
1323
|
-
{
|
|
1324
|
-
type: "button",
|
|
1325
|
-
className: "font-medium underline underline-offset-2",
|
|
1326
|
-
onClick: () => void controller.resolveDraftConflict?.("keep_mine"),
|
|
1327
|
-
children: controller.messages.keepMine
|
|
1328
|
-
}
|
|
1329
|
-
)
|
|
1330
|
-
] }) : controller.draftSaving ? /* @__PURE__ */ jsx3("p", { className: "px-1 pt-1 text-og-xs text-og-fg-subtle", children: controller.messages.savingDraft }) : null
|
|
1331
|
-
] });
|
|
1236
|
+
|
|
1237
|
+
// src/components/model-picker.tsx
|
|
1238
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
1239
|
+
import { useId, useMemo as useMemo3 } from "react";
|
|
1240
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1241
|
+
var EMPTY_CLIENT_MODELS = [];
|
|
1242
|
+
function isCodexClientModel(model) {
|
|
1243
|
+
return model.id.startsWith("codex/") || model.provider === "codex-subscription";
|
|
1332
1244
|
}
|
|
1333
|
-
function
|
|
1334
|
-
|
|
1335
|
-
return /* @__PURE__ */ jsxs3("div", { className: "sr-only", children: [
|
|
1336
|
-
controller.activeNotice ? /* @__PURE__ */ jsx3("p", { role: controller.activeNotice.tone === "error" ? "alert" : "status", children: controller.activeNotice.message }) : null,
|
|
1337
|
-
controller.draftConflict ? /* @__PURE__ */ jsx3("p", { role: "alert", children: controller.messages.draftConflict }) : controller.draftSaving ? /* @__PURE__ */ jsx3("p", { role: "status", children: controller.messages.savingDraft }) : null
|
|
1338
|
-
] });
|
|
1245
|
+
function isCodexPickerRow(row) {
|
|
1246
|
+
return row.id.startsWith("codex/") || row.provider === "codex-subscription";
|
|
1339
1247
|
}
|
|
1340
|
-
function
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1248
|
+
function applyCodexOnlyRows(rows, codexOnly) {
|
|
1249
|
+
if (!codexOnly) return rows;
|
|
1250
|
+
return rows.map((row) => {
|
|
1251
|
+
if (isCodexPickerRow(row)) return row;
|
|
1252
|
+
return {
|
|
1253
|
+
...row,
|
|
1254
|
+
selectable: false,
|
|
1255
|
+
unavailableReason: row.unavailableReason ?? "Codex-only session"
|
|
1256
|
+
};
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
function providerGroups(models, codexOnly) {
|
|
1260
|
+
const byProvider = /* @__PURE__ */ new Map();
|
|
1261
|
+
for (const model of models) {
|
|
1262
|
+
let group = byProvider.get(model.provider);
|
|
1263
|
+
if (!group) {
|
|
1264
|
+
group = { label: model.providerLabel, models: [] };
|
|
1265
|
+
byProvider.set(model.provider, group);
|
|
1345
1266
|
}
|
|
1346
|
-
|
|
1267
|
+
group.models.push({
|
|
1268
|
+
...model,
|
|
1269
|
+
optionDisabled: codexOnly && !isCodexClientModel(model)
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
return [...byProvider.values()];
|
|
1347
1273
|
}
|
|
1348
|
-
function
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
onOpenChange,
|
|
1358
|
-
onResume,
|
|
1359
|
-
onResumeOption
|
|
1274
|
+
function ModelPicker({
|
|
1275
|
+
models = EMPTY_CLIENT_MODELS,
|
|
1276
|
+
rows,
|
|
1277
|
+
value,
|
|
1278
|
+
onChange,
|
|
1279
|
+
disabled,
|
|
1280
|
+
className,
|
|
1281
|
+
label = "Model",
|
|
1282
|
+
codexOnly = false
|
|
1360
1283
|
}) {
|
|
1361
|
-
const
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
(option) => option !== primary && option.scope !== "selected" && (option.scope !== "workspace" || canControlWorkspace)
|
|
1284
|
+
const selectId = useId();
|
|
1285
|
+
const effectiveRows = useMemo3(
|
|
1286
|
+
() => rows && rows.length > 0 ? applyCodexOnlyRows(rows, codexOnly) : [],
|
|
1287
|
+
[rows, codexOnly]
|
|
1366
1288
|
);
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
"
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
"
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
onClick: () => onOpenChange(!open),
|
|
1404
|
-
"aria-label": open ? messages.hidePauseDetails : messages.showPauseDetails,
|
|
1405
|
-
children: /* @__PURE__ */ jsx3(ChevronDownIcon2, { className: cn("size-3.5 transition-transform", open && "rotate-180") })
|
|
1406
|
-
}
|
|
1407
|
-
)
|
|
1408
|
-
] }),
|
|
1409
|
-
open ? /* @__PURE__ */ jsxs3("div", { className: "border-t border-og-status-waiting/20 px-3 pb-3 pt-2", children: [
|
|
1410
|
-
/* @__PURE__ */ jsx3("ul", { className: "grid gap-2", "aria-label": messages.pauseReasonsLabel, children: control.blockers.map((entry, index) => /* @__PURE__ */ jsxs3(
|
|
1411
|
-
"li",
|
|
1412
|
-
{
|
|
1413
|
-
className: "text-og-xs",
|
|
1414
|
-
children: [
|
|
1415
|
-
/* @__PURE__ */ jsx3("span", { className: "font-medium text-og-fg", children: index === 0 ? messages.pausedByLabel : messages.alsoPausedByLabel }),
|
|
1416
|
-
blockerHref(entry, controlLinks) ? /* @__PURE__ */ jsx3(
|
|
1417
|
-
"a",
|
|
1418
|
-
{
|
|
1419
|
-
href: blockerHref(entry, controlLinks),
|
|
1420
|
-
className: "font-medium text-og-fg underline decoration-og-border-strong underline-offset-2 hover:text-og-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-ring/40",
|
|
1421
|
-
children: entry.displayName
|
|
1422
|
-
}
|
|
1423
|
-
) : /* @__PURE__ */ jsx3("span", { className: "font-medium text-og-fg", children: entry.displayName }),
|
|
1424
|
-
entry.reason ? /* @__PURE__ */ jsxs3("span", { className: "text-og-fg-muted", children: [
|
|
1425
|
-
" \xB7 ",
|
|
1426
|
-
entry.reason
|
|
1427
|
-
] }) : null,
|
|
1428
|
-
entry.actor ? /* @__PURE__ */ jsxs3("span", { className: "text-og-fg-subtle", children: [
|
|
1429
|
-
" \xB7 ",
|
|
1430
|
-
entry.actor
|
|
1431
|
-
] }) : null,
|
|
1432
|
-
entry.changedAt ? /* @__PURE__ */ jsxs3("span", { className: "text-og-fg-subtle", children: [
|
|
1433
|
-
" ",
|
|
1434
|
-
"\xB7 ",
|
|
1435
|
-
messages.formatRelativeTime(entry.changedAt)
|
|
1436
|
-
] }) : null
|
|
1437
|
-
]
|
|
1438
|
-
},
|
|
1439
|
-
`${entry.kind}-${entry.sessionId ?? "workspace"}-${entry.revision}`
|
|
1440
|
-
)) }),
|
|
1441
|
-
broaderOptions.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "mt-2 flex flex-wrap gap-1.5", children: broaderOptions.map((option) => /* @__PURE__ */ jsxs3(
|
|
1442
|
-
"button",
|
|
1443
|
-
{
|
|
1444
|
-
type: "button",
|
|
1445
|
-
disabled: busy,
|
|
1446
|
-
title: option.impactCopy,
|
|
1447
|
-
onClick: () => onResumeOption(option),
|
|
1448
|
-
className: "rounded-og-md border border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg pointer-coarse:min-h-10",
|
|
1449
|
-
children: [
|
|
1450
|
-
/* @__PURE__ */ jsx3("span", { className: "block font-medium", children: option.scope === "workspace" ? messages.resumeWorkspace : messages.resumeFromSession }),
|
|
1451
|
-
/* @__PURE__ */ jsx3("span", { className: "block text-[10px] text-og-fg-subtle", children: option.selectedStateAfter === "active" ? messages.sessionCanRun : messages.stillPausedBy(
|
|
1452
|
-
option.remainingPrimaryBlocker?.displayName ?? messages.narrowerPause
|
|
1453
|
-
) })
|
|
1454
|
-
]
|
|
1455
|
-
},
|
|
1456
|
-
`${option.scope}-${option.targetId ?? "selected"}`
|
|
1457
|
-
)) }) : null
|
|
1458
|
-
] }) : null
|
|
1289
|
+
const billingGroups = useMemo3(
|
|
1290
|
+
() => effectiveRows.length > 0 ? groupPickerRowsByBillingClass(effectiveRows) : [],
|
|
1291
|
+
[effectiveRows]
|
|
1292
|
+
);
|
|
1293
|
+
const providerGroupList = useMemo3(() => providerGroups(models, codexOnly), [models, codexOnly]);
|
|
1294
|
+
const useBillingGroups = Boolean(effectiveRows.length);
|
|
1295
|
+
if (models.length === 0 && effectiveRows.length === 0) {
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
return /* @__PURE__ */ jsxs2("span", { className: cn("relative inline-flex items-center", className), children: [
|
|
1299
|
+
/* @__PURE__ */ jsx2("label", { htmlFor: selectId, className: "sr-only", children: label }),
|
|
1300
|
+
/* @__PURE__ */ jsx2(
|
|
1301
|
+
"select",
|
|
1302
|
+
{
|
|
1303
|
+
id: selectId,
|
|
1304
|
+
value: value ?? "",
|
|
1305
|
+
onChange: (event) => onChange(event.target.value),
|
|
1306
|
+
disabled: disabled === true,
|
|
1307
|
+
"aria-label": label,
|
|
1308
|
+
className: cn(
|
|
1309
|
+
"h-8 max-w-[180px] cursor-pointer appearance-none truncate rounded-og-md bg-transparent",
|
|
1310
|
+
"py-0 pl-2 pr-6 text-[13px] text-og-fg-muted",
|
|
1311
|
+
"transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
|
|
1312
|
+
"focus:outline-none focus-visible:outline-none",
|
|
1313
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
1314
|
+
),
|
|
1315
|
+
children: useBillingGroups ? billingGroups.map((group) => /* @__PURE__ */ jsx2("optgroup", { label: group.label, children: group.rows.map((row) => /* @__PURE__ */ jsx2("option", { value: row.id, disabled: !row.selectable, children: row.unavailableReason ? `${row.label} (${row.unavailableReason})` : row.label }, row.id)) }, group.billingClass)) : providerGroupList.map((group) => /* @__PURE__ */ jsx2("optgroup", { label: group.label, children: group.models.map((model) => /* @__PURE__ */ jsx2("option", { value: model.id, disabled: model.optionDisabled, children: model.label }, model.id)) }, group.label))
|
|
1316
|
+
}
|
|
1317
|
+
),
|
|
1318
|
+
/* @__PURE__ */ jsx2(
|
|
1319
|
+
ChevronDownIcon,
|
|
1320
|
+
{
|
|
1321
|
+
"aria-hidden": true,
|
|
1322
|
+
className: "pointer-events-none absolute right-1.5 size-3.5 text-og-fg-subtle"
|
|
1323
|
+
}
|
|
1324
|
+
)
|
|
1459
1325
|
] });
|
|
1460
1326
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1327
|
+
|
|
1328
|
+
// src/components/composer.tsx
|
|
1329
|
+
import {
|
|
1330
|
+
ArrowUpIcon,
|
|
1331
|
+
ChevronDownIcon as ChevronDownIcon2,
|
|
1332
|
+
FileIcon,
|
|
1333
|
+
ImageIcon,
|
|
1334
|
+
LoaderCircleIcon,
|
|
1335
|
+
PaperclipIcon,
|
|
1336
|
+
PauseIcon,
|
|
1337
|
+
PlayIcon,
|
|
1338
|
+
RotateCwIcon,
|
|
1339
|
+
XIcon
|
|
1340
|
+
} from "lucide-react";
|
|
1341
|
+
import { AnimatePresence as AnimatePresence2, motion as motion2 } from "motion/react";
|
|
1342
|
+
import {
|
|
1343
|
+
createContext,
|
|
1344
|
+
forwardRef,
|
|
1345
|
+
useCallback as useCallback4,
|
|
1346
|
+
useContext,
|
|
1347
|
+
useEffect as useEffect3,
|
|
1348
|
+
useId as useId2,
|
|
1349
|
+
useMemo as useMemo4,
|
|
1350
|
+
useRef as useRef4,
|
|
1351
|
+
useState as useState4
|
|
1352
|
+
} from "react";
|
|
1353
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1354
|
+
var OPEN_WORKSTREAM_CONTROL_EVENT = "opengeni:open-workstream-control";
|
|
1355
|
+
var defaultChatComposerMessages = {
|
|
1356
|
+
messagePlaceholder: "Message the agent\u2026",
|
|
1357
|
+
pausedPlaceholder: "Sending will resume this workstream\u2026",
|
|
1358
|
+
inputLabel: "Message the agent",
|
|
1359
|
+
keyboardHint: "Enter to queue \xB7 Cmd/Ctrl+Enter to steer \xB7 Shift+Enter for a new line",
|
|
1360
|
+
slashCommandBlocked: "That's a slash command \u2014 press Enter in the command list to run it, or edit the line to send a message.",
|
|
1361
|
+
controlChangedError: "This workstream was paused while you were sending. Nothing was sent, and your draft is still here.",
|
|
1362
|
+
sendFailedError: "Sending failed \u2014 your draft is still here. Try again.",
|
|
1363
|
+
dropFiles: "Drop files to attach",
|
|
1364
|
+
attachFiles: "Attach files",
|
|
1365
|
+
pauseAriaLabel: "Pause this workstream",
|
|
1366
|
+
pauseTitle: "Pause this workstream; queued prompts and approvals are preserved",
|
|
1367
|
+
sendMessageAriaLabel: "Send message",
|
|
1368
|
+
sendAndResumeAriaLabel: "Send and resume",
|
|
1369
|
+
sendTitle: "Queue message (Enter); steer with Cmd/Ctrl+Enter",
|
|
1370
|
+
sendAndResumeTitle: "Send & resume (Enter); Steer & resume with Cmd/Ctrl+Enter",
|
|
1371
|
+
workspacePaused: "Workspace paused",
|
|
1372
|
+
pausedHere: "Paused here",
|
|
1373
|
+
parentBlocker: "parent",
|
|
1374
|
+
narrowerPause: "a narrower pause",
|
|
1375
|
+
pausedBy: (displayName) => `Paused by ${displayName}`,
|
|
1376
|
+
queuedAhead: (count) => `Send & resume queues behind ${count} waiting prompt${count === 1 ? "" : "s"}.`,
|
|
1377
|
+
resumingAndSending: "Resuming and sending\u2026",
|
|
1378
|
+
nextMessageResumes: "Your next message resumes this workstream automatically.",
|
|
1379
|
+
resumeThisWorkstream: "Resume this workstream",
|
|
1380
|
+
resumeShort: "Resume",
|
|
1381
|
+
hidePauseDetails: "Hide pause details",
|
|
1382
|
+
showPauseDetails: "Show pause details",
|
|
1383
|
+
pauseReasonsLabel: "Reasons this workstream is paused",
|
|
1384
|
+
pausedByLabel: "Paused by ",
|
|
1385
|
+
alsoPausedByLabel: "Also paused by ",
|
|
1386
|
+
resumeWorkspace: "Resume workspace",
|
|
1387
|
+
resumeFromSession: "Resume from this session",
|
|
1388
|
+
sessionCanRun: "This session will be able to run",
|
|
1389
|
+
stillPausedBy: (displayName) => `Still paused by ${displayName}`,
|
|
1390
|
+
restoredResourcesLabel: "Restored prompt resources",
|
|
1391
|
+
restoredFile: (fileId) => `File ${fileId.slice(0, 8)}`,
|
|
1392
|
+
removeRestoredResource: (index) => `Remove restored resource ${index + 1}`,
|
|
1393
|
+
uploading: "Uploading",
|
|
1394
|
+
uploadFailed: "Upload failed",
|
|
1395
|
+
retryAttachment: (name) => `Retry ${name}`,
|
|
1396
|
+
retryUpload: "Retry upload",
|
|
1397
|
+
removeAttachment: (name) => `Remove ${name}`,
|
|
1398
|
+
confirmCommand: (name) => `Confirm /${name}`,
|
|
1399
|
+
confirmDescription: (command) => `Run /${command.name}? ${command.description}`,
|
|
1400
|
+
cancel: "Cancel",
|
|
1401
|
+
runCommand: (name) => `Run /${name}`,
|
|
1402
|
+
commands: "Commands",
|
|
1403
|
+
slashCommandsLabel: "Slash commands",
|
|
1404
|
+
modelLabel: "Model",
|
|
1405
|
+
close: "Close",
|
|
1406
|
+
danger: "danger",
|
|
1407
|
+
draftConflict: "This draft changed in another tab. Your local draft is still here.",
|
|
1408
|
+
useOtherDraft: "Use other draft",
|
|
1409
|
+
keepMine: "Keep mine",
|
|
1410
|
+
// Kept for embedder message overrides / back-compat. Routine autosave is
|
|
1411
|
+
// silent — only draft conflicts surface under the composer.
|
|
1412
|
+
savingDraft: "Saving draft\u2026",
|
|
1413
|
+
formatBytes,
|
|
1414
|
+
formatRelativeTime
|
|
1415
|
+
};
|
|
1416
|
+
function useChatComposerController({
|
|
1417
|
+
delivery,
|
|
1418
|
+
draft,
|
|
1419
|
+
control,
|
|
1420
|
+
effectiveControl,
|
|
1421
|
+
queuedAheadCount = 0,
|
|
1422
|
+
canControlWorkspace = false,
|
|
1423
|
+
controlLinks,
|
|
1424
|
+
disabled = false,
|
|
1425
|
+
attachments,
|
|
1426
|
+
commands = defaultCommands,
|
|
1427
|
+
commandContext,
|
|
1428
|
+
onClearView,
|
|
1429
|
+
onPaste,
|
|
1430
|
+
messages: messageOverrides
|
|
1469
1431
|
}) {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
)
|
|
1496
|
-
|
|
1432
|
+
const messages = useMemo4(
|
|
1433
|
+
() => ({ ...defaultChatComposerMessages, ...messageOverrides }),
|
|
1434
|
+
[messageOverrides]
|
|
1435
|
+
);
|
|
1436
|
+
const id = useId2();
|
|
1437
|
+
const rootRef = useRef4(null);
|
|
1438
|
+
const textareaRef = useRef4(null);
|
|
1439
|
+
const pauseButtonRef = useRef4(null);
|
|
1440
|
+
const fileInputRef = useRef4(null);
|
|
1441
|
+
const listboxId = useId2();
|
|
1442
|
+
const paused = effectiveControl?.state === "paused";
|
|
1443
|
+
const [controlDetailsOpen, setControlDetailsOpen] = useState4(false);
|
|
1444
|
+
const [paletteMounted, setPaletteMounted] = useState4(false);
|
|
1445
|
+
const [submitting, setSubmitting] = useState4(false);
|
|
1446
|
+
const submittingRef = useRef4(false);
|
|
1447
|
+
const controlOperationRef = useRef4(false);
|
|
1448
|
+
const mountedRef = useRef4(true);
|
|
1449
|
+
useEffect3(() => {
|
|
1450
|
+
mountedRef.current = true;
|
|
1451
|
+
return () => {
|
|
1452
|
+
mountedRef.current = false;
|
|
1453
|
+
};
|
|
1454
|
+
}, []);
|
|
1455
|
+
useEffect3(() => {
|
|
1456
|
+
const openControl = (event) => {
|
|
1457
|
+
const requestedId = event instanceof CustomEvent && isRecord2(event.detail) && typeof event.detail.composerId === "string" ? event.detail.composerId : null;
|
|
1458
|
+
if (requestedId && requestedId !== id) return;
|
|
1459
|
+
if (!requestedId) {
|
|
1460
|
+
const roots = document.querySelectorAll("[data-og-composer-id]");
|
|
1461
|
+
if (roots.length > 1 && !rootRef.current?.contains(document.activeElement)) return;
|
|
1462
|
+
}
|
|
1463
|
+
textareaRef.current?.scrollIntoView({ block: "end", behavior: "smooth" });
|
|
1464
|
+
if (paused) {
|
|
1465
|
+
setControlDetailsOpen(true);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
window.requestAnimationFrame(() => pauseButtonRef.current?.focus());
|
|
1469
|
+
};
|
|
1470
|
+
document.addEventListener(OPEN_WORKSTREAM_CONTROL_EVENT, openControl);
|
|
1471
|
+
return () => document.removeEventListener(OPEN_WORKSTREAM_CONTROL_EVENT, openControl);
|
|
1472
|
+
}, [id, paused]);
|
|
1473
|
+
const blockedByAttachment = attachments?.hasUnresolved === true;
|
|
1474
|
+
const hasReadyAttachment = (attachments?.readyResources.length ?? 0) > 0;
|
|
1475
|
+
const [dragging, setDragging] = useState4(false);
|
|
1476
|
+
const dragCarriesFiles = (event) => event.dataTransfer !== null && [...event.dataTransfer.types].includes("Files");
|
|
1477
|
+
const handleDragOver = useCallback4(
|
|
1478
|
+
(event) => {
|
|
1479
|
+
if (!attachments || !dragCarriesFiles(event)) return;
|
|
1480
|
+
event.preventDefault();
|
|
1481
|
+
setDragging(true);
|
|
1482
|
+
},
|
|
1483
|
+
[attachments]
|
|
1484
|
+
);
|
|
1485
|
+
const handleDragLeave = useCallback4(
|
|
1486
|
+
(event) => {
|
|
1487
|
+
if (!attachments) return;
|
|
1488
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
1489
|
+
setDragging(false);
|
|
1490
|
+
},
|
|
1491
|
+
[attachments]
|
|
1492
|
+
);
|
|
1493
|
+
const handleDrop = useCallback4(
|
|
1494
|
+
(event) => {
|
|
1495
|
+
if (!attachments || !dragCarriesFiles(event)) return;
|
|
1496
|
+
event.preventDefault();
|
|
1497
|
+
setDragging(false);
|
|
1498
|
+
if (event.dataTransfer.files.length > 0) attachments.addFiles(event.dataTransfer.files);
|
|
1499
|
+
},
|
|
1500
|
+
[attachments]
|
|
1501
|
+
);
|
|
1502
|
+
const [notice, setNotice] = useState4(null);
|
|
1503
|
+
const [helpOpen, setHelpOpen] = useState4(false);
|
|
1504
|
+
const [confirmState, setConfirmState] = useState4(null);
|
|
1505
|
+
const pendingConfirm = useRef4(null);
|
|
1506
|
+
const settleConfirmation = useCallback4((confirmed) => {
|
|
1507
|
+
const resolve = pendingConfirm.current;
|
|
1508
|
+
pendingConfirm.current = null;
|
|
1509
|
+
setConfirmState(null);
|
|
1510
|
+
resolve?.(confirmed);
|
|
1511
|
+
}, []);
|
|
1512
|
+
useEffect3(
|
|
1513
|
+
() => () => {
|
|
1514
|
+
pendingConfirm.current?.(false);
|
|
1515
|
+
pendingConfirm.current = null;
|
|
1516
|
+
},
|
|
1517
|
+
[]
|
|
1518
|
+
);
|
|
1519
|
+
const resizeInput = useCallback4(() => {
|
|
1520
|
+
const textarea = textareaRef.current;
|
|
1521
|
+
if (!textarea) return;
|
|
1522
|
+
textarea.style.height = "0px";
|
|
1523
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 220)}px`;
|
|
1524
|
+
}, []);
|
|
1525
|
+
useEffect3(() => resizeInput(), [delivery.value, resizeInput]);
|
|
1526
|
+
const handlers = useMemo4(
|
|
1527
|
+
() => ({
|
|
1528
|
+
notice: (next) => {
|
|
1529
|
+
setNotice(next);
|
|
1530
|
+
delivery.clearError();
|
|
1531
|
+
},
|
|
1532
|
+
openHelp: () => setHelpOpen(true),
|
|
1533
|
+
clearView: () => {
|
|
1534
|
+
if (!onClearView) return false;
|
|
1535
|
+
onClearView();
|
|
1536
|
+
return true;
|
|
1537
|
+
},
|
|
1538
|
+
confirm: (command) => new Promise((resolve) => {
|
|
1539
|
+
pendingConfirm.current?.(false);
|
|
1540
|
+
pendingConfirm.current = resolve;
|
|
1541
|
+
setConfirmState({ command, resolve: settleConfirmation });
|
|
1542
|
+
})
|
|
1543
|
+
}),
|
|
1544
|
+
[delivery, onClearView, settleConfirmation]
|
|
1545
|
+
);
|
|
1546
|
+
const palette = useSlashCommands({
|
|
1547
|
+
commands,
|
|
1548
|
+
context: commandContext,
|
|
1549
|
+
handlers,
|
|
1550
|
+
value: delivery.value,
|
|
1551
|
+
setValue: delivery.setValue
|
|
1552
|
+
});
|
|
1553
|
+
const paletteEnabled = commandContext !== void 0;
|
|
1554
|
+
const commandDraftBlocked = paletteEnabled && palette.isCommandDraft;
|
|
1555
|
+
const submitBlocker = disabled ? "disabled" : blockedByAttachment ? "attachment" : delivery.sending || submitting ? "sending" : commandDraftBlocked ? "command" : delivery.canSend || hasReadyAttachment ? null : "empty";
|
|
1556
|
+
const canSubmit = submitBlocker === null;
|
|
1557
|
+
const submit = useCallback4(
|
|
1558
|
+
async (mode) => {
|
|
1559
|
+
if (commandDraftBlocked) {
|
|
1560
|
+
setNotice({ tone: "error", message: messages.slashCommandBlocked });
|
|
1561
|
+
delivery.clearError();
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
if (disabled || blockedByAttachment || delivery.sending || submittingRef.current)
|
|
1565
|
+
return false;
|
|
1566
|
+
if (!delivery.canSend && !hasReadyAttachment) return false;
|
|
1567
|
+
submittingRef.current = true;
|
|
1568
|
+
setSubmitting(true);
|
|
1569
|
+
try {
|
|
1570
|
+
return mode === "steer" ? await delivery.steer() : await delivery.send();
|
|
1571
|
+
} finally {
|
|
1572
|
+
submittingRef.current = false;
|
|
1573
|
+
if (mountedRef.current) setSubmitting(false);
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
[
|
|
1577
|
+
blockedByAttachment,
|
|
1578
|
+
commandDraftBlocked,
|
|
1579
|
+
delivery,
|
|
1580
|
+
disabled,
|
|
1581
|
+
hasReadyAttachment,
|
|
1582
|
+
messages.slashCommandBlocked
|
|
1583
|
+
]
|
|
1497
1584
|
);
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
{
|
|
1516
|
-
|
|
1517
|
-
"
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
]
|
|
1585
|
+
const handleKeyDown = useCallback4(
|
|
1586
|
+
(event) => {
|
|
1587
|
+
if (paletteEnabled && paletteMounted && palette.onKeyDown(event)) return;
|
|
1588
|
+
if (!shouldSubmitOnKey(event)) return;
|
|
1589
|
+
event.preventDefault();
|
|
1590
|
+
void submit(shouldSteerOnKey(event) ? "steer" : "queue");
|
|
1591
|
+
},
|
|
1592
|
+
[palette, paletteEnabled, paletteMounted, submit]
|
|
1593
|
+
);
|
|
1594
|
+
const handlePaste = useCallback4(
|
|
1595
|
+
(event) => {
|
|
1596
|
+
onPaste?.(event);
|
|
1597
|
+
attachments?.addFromPaste(event);
|
|
1598
|
+
},
|
|
1599
|
+
[attachments, onPaste]
|
|
1600
|
+
);
|
|
1601
|
+
const handleFileChange = useCallback4(
|
|
1602
|
+
(event) => {
|
|
1603
|
+
if (event.target.files) attachments?.addFiles(event.target.files);
|
|
1604
|
+
event.target.value = "";
|
|
1605
|
+
},
|
|
1606
|
+
[attachments]
|
|
1607
|
+
);
|
|
1608
|
+
const helpCommands = useMemo4(
|
|
1609
|
+
() => commands.filter((command) => {
|
|
1610
|
+
if (command.permission && commandContext) {
|
|
1611
|
+
const permissions = commandContext.permissions;
|
|
1612
|
+
return permissions.includes(command.permission) || permissions.includes("workspace:admin");
|
|
1613
|
+
}
|
|
1614
|
+
return true;
|
|
1615
|
+
}),
|
|
1616
|
+
[commandContext, commands]
|
|
1617
|
+
);
|
|
1618
|
+
const activeNotice = notice ?? (delivery.error ? {
|
|
1619
|
+
tone: "error",
|
|
1620
|
+
message: /control changed|paused while/i.test(delivery.error.message) ? messages.controlChangedError : composerSubmissionErrorMessage(delivery.error) || messages.sendFailedError
|
|
1621
|
+
} : null);
|
|
1622
|
+
useEffect3(() => {
|
|
1623
|
+
if (notice?.tone !== "ok") return;
|
|
1624
|
+
const timer = window.setTimeout(
|
|
1625
|
+
() => setNotice((current) => current === notice ? null : current),
|
|
1626
|
+
2400
|
|
1627
|
+
);
|
|
1628
|
+
return () => window.clearTimeout(timer);
|
|
1629
|
+
}, [notice]);
|
|
1630
|
+
const runControlOperation = useCallback4(async (operation) => {
|
|
1631
|
+
if (controlOperationRef.current) return false;
|
|
1632
|
+
controlOperationRef.current = true;
|
|
1633
|
+
try {
|
|
1634
|
+
await operation();
|
|
1635
|
+
return true;
|
|
1636
|
+
} finally {
|
|
1637
|
+
controlOperationRef.current = false;
|
|
1552
1638
|
}
|
|
1639
|
+
}, []);
|
|
1640
|
+
const pause = useCallback4(
|
|
1641
|
+
async (reason) => {
|
|
1642
|
+
if (!control || control.pausing || control.resuming) return false;
|
|
1643
|
+
return await runControlOperation(() => control.pause(reason));
|
|
1644
|
+
},
|
|
1645
|
+
[control, runControlOperation]
|
|
1646
|
+
);
|
|
1647
|
+
const resume = useCallback4(
|
|
1648
|
+
async (reason) => {
|
|
1649
|
+
if (!control || control.pausing || control.resuming) return false;
|
|
1650
|
+
return await runControlOperation(() => control.resume(reason));
|
|
1651
|
+
},
|
|
1652
|
+
[control, runControlOperation]
|
|
1653
|
+
);
|
|
1654
|
+
const resumeScope = useCallback4(
|
|
1655
|
+
async (option) => {
|
|
1656
|
+
if (!control || control.pausing || control.resuming) return false;
|
|
1657
|
+
return await runControlOperation(() => control.resumeScope(option));
|
|
1658
|
+
},
|
|
1659
|
+
[control, runControlOperation]
|
|
1553
1660
|
);
|
|
1661
|
+
return {
|
|
1662
|
+
id,
|
|
1663
|
+
rootRef,
|
|
1664
|
+
textareaRef,
|
|
1665
|
+
pauseButtonRef,
|
|
1666
|
+
fileInputRef,
|
|
1667
|
+
listboxId,
|
|
1668
|
+
effectiveControl,
|
|
1669
|
+
queuedAheadCount,
|
|
1670
|
+
canControlWorkspace,
|
|
1671
|
+
controlLinks,
|
|
1672
|
+
disabled,
|
|
1673
|
+
attachments,
|
|
1674
|
+
messages,
|
|
1675
|
+
sending: delivery.sending || submitting,
|
|
1676
|
+
error: delivery.error,
|
|
1677
|
+
clearError: delivery.clearError,
|
|
1678
|
+
hasDraftState: draft !== void 0,
|
|
1679
|
+
draftConflict: draft?.draftConflict ?? null,
|
|
1680
|
+
draftSaving: draft?.draftSaving ?? false,
|
|
1681
|
+
restoredResources: draft?.restoredResources ?? [],
|
|
1682
|
+
removeRestoredResource: draft?.removeRestoredResource,
|
|
1683
|
+
resolveDraftConflict: draft?.resolveDraftConflict,
|
|
1684
|
+
hasControl: control !== void 0,
|
|
1685
|
+
pausing: control?.pausing ?? false,
|
|
1686
|
+
resuming: control?.resuming ?? false,
|
|
1687
|
+
pause,
|
|
1688
|
+
resume,
|
|
1689
|
+
resumeScope,
|
|
1690
|
+
paused,
|
|
1691
|
+
controlDetailsOpen,
|
|
1692
|
+
setControlDetailsOpen,
|
|
1693
|
+
paletteMounted,
|
|
1694
|
+
setPaletteMounted,
|
|
1695
|
+
dragging,
|
|
1696
|
+
handleDragOver,
|
|
1697
|
+
handleDragLeave,
|
|
1698
|
+
handleDrop,
|
|
1699
|
+
palette,
|
|
1700
|
+
paletteEnabled,
|
|
1701
|
+
commandDraftBlocked,
|
|
1702
|
+
confirmState,
|
|
1703
|
+
settleConfirmation,
|
|
1704
|
+
helpOpen,
|
|
1705
|
+
setHelpOpen,
|
|
1706
|
+
helpCommands,
|
|
1707
|
+
activeNotice,
|
|
1708
|
+
canSubmit,
|
|
1709
|
+
submitBlocker,
|
|
1710
|
+
submit,
|
|
1711
|
+
handleKeyDown,
|
|
1712
|
+
handlePaste,
|
|
1713
|
+
handleFileChange,
|
|
1714
|
+
focusInput: () => textareaRef.current?.focus(),
|
|
1715
|
+
setValue: delivery.setValue,
|
|
1716
|
+
value: delivery.value
|
|
1717
|
+
};
|
|
1554
1718
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
}) {
|
|
1561
|
-
return /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap gap-2 border-b border-og-border px-3 py-2", children: attachments.map((attachment) => {
|
|
1562
|
-
const failed = attachment.status === "failed";
|
|
1563
|
-
const statusText = attachment.status === "uploading" ? messages.uploading : failed ? attachment.error || messages.uploadFailed : messages.formatBytes(attachment.sizeBytes);
|
|
1564
|
-
return /* @__PURE__ */ jsxs3(
|
|
1565
|
-
"div",
|
|
1566
|
-
{
|
|
1567
|
-
className: cn(
|
|
1568
|
-
"flex min-w-0 max-w-[240px] items-center gap-2 rounded-og-md border px-2 py-1.5 text-og-sm",
|
|
1569
|
-
failed ? "border-og-status-failed/40 bg-og-status-failed/10" : "border-og-border bg-og-surface-2"
|
|
1570
|
-
),
|
|
1571
|
-
children: [
|
|
1572
|
-
attachment.previewUrl ? /* @__PURE__ */ jsx3(
|
|
1573
|
-
"img",
|
|
1574
|
-
{
|
|
1575
|
-
src: attachment.previewUrl,
|
|
1576
|
-
alt: "",
|
|
1577
|
-
className: "size-8 shrink-0 rounded object-cover"
|
|
1578
|
-
}
|
|
1579
|
-
) : attachment.contentType.startsWith("image/") ? /* @__PURE__ */ jsx3(ImageIcon, { className: "size-4 shrink-0 text-og-fg-muted" }) : /* @__PURE__ */ jsx3(FileIcon, { className: "size-4 shrink-0 text-og-fg-muted" }),
|
|
1580
|
-
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
1581
|
-
/* @__PURE__ */ jsx3("div", { className: "truncate font-medium text-og-fg", children: attachment.name }),
|
|
1582
|
-
/* @__PURE__ */ jsx3(
|
|
1583
|
-
"div",
|
|
1584
|
-
{
|
|
1585
|
-
className: cn(
|
|
1586
|
-
"truncate text-og-xs",
|
|
1587
|
-
failed ? "text-og-status-failed" : "text-og-fg-subtle"
|
|
1588
|
-
),
|
|
1589
|
-
title: failed ? statusText : void 0,
|
|
1590
|
-
children: statusText
|
|
1591
|
-
}
|
|
1592
|
-
)
|
|
1593
|
-
] }),
|
|
1594
|
-
attachment.status === "uploading" ? /* @__PURE__ */ jsx3(LoaderCircleIcon, { className: "size-3.5 shrink-0 animate-og-spin" }) : null,
|
|
1595
|
-
failed && onRetry ? /* @__PURE__ */ jsx3(
|
|
1596
|
-
"button",
|
|
1597
|
-
{
|
|
1598
|
-
type: "button",
|
|
1599
|
-
onClick: () => onRetry(attachment.id),
|
|
1600
|
-
className: "shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-10",
|
|
1601
|
-
"aria-label": messages.retryAttachment(attachment.name),
|
|
1602
|
-
title: messages.retryUpload,
|
|
1603
|
-
children: /* @__PURE__ */ jsx3(RotateCwIcon, { className: "size-3.5" })
|
|
1604
|
-
}
|
|
1605
|
-
) : null,
|
|
1606
|
-
/* @__PURE__ */ jsx3(
|
|
1607
|
-
"button",
|
|
1608
|
-
{
|
|
1609
|
-
type: "button",
|
|
1610
|
-
onClick: () => onRemove(attachment.id),
|
|
1611
|
-
className: "shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-10",
|
|
1612
|
-
"aria-label": messages.removeAttachment(attachment.name),
|
|
1613
|
-
children: /* @__PURE__ */ jsx3(XIcon, { className: "size-3.5" })
|
|
1614
|
-
}
|
|
1615
|
-
)
|
|
1616
|
-
]
|
|
1617
|
-
},
|
|
1618
|
-
attachment.id
|
|
1619
|
-
);
|
|
1620
|
-
}) });
|
|
1719
|
+
var ComposerContext = createContext(null);
|
|
1720
|
+
function useComposerController() {
|
|
1721
|
+
const controller = useContext(ComposerContext);
|
|
1722
|
+
if (!controller) throw new Error("useChatComposer must be used inside <Composer.Root>");
|
|
1723
|
+
return controller;
|
|
1621
1724
|
}
|
|
1622
|
-
function
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1725
|
+
function useChatComposer() {
|
|
1726
|
+
return useComposerController();
|
|
1727
|
+
}
|
|
1728
|
+
function isRecord2(value) {
|
|
1729
|
+
return typeof value === "object" && value !== null;
|
|
1730
|
+
}
|
|
1731
|
+
var Root = forwardRef(function ComposerRoot({ controller, children, className, style, ...props }, forwardedRef) {
|
|
1732
|
+
return /* @__PURE__ */ jsx3(ComposerContext.Provider, { value: controller, children: /* @__PURE__ */ jsxs3(
|
|
1733
|
+
"div",
|
|
1629
1734
|
{
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
className: "
|
|
1735
|
+
...props,
|
|
1736
|
+
ref: mergeRefs(controller.rootRef, forwardedRef),
|
|
1737
|
+
"data-og-composer-id": controller.id,
|
|
1738
|
+
className: cn("og-root", className),
|
|
1739
|
+
style: { paddingBottom: "env(safe-area-inset-bottom)", ...style },
|
|
1634
1740
|
children: [
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
/* @__PURE__ */ jsx3(
|
|
1638
|
-
"button",
|
|
1639
|
-
{
|
|
1640
|
-
type: "button",
|
|
1641
|
-
onClick: onClose,
|
|
1642
|
-
className: "text-og-xs text-og-fg-subtle hover:text-og-fg",
|
|
1643
|
-
children: messages.close
|
|
1644
|
-
}
|
|
1645
|
-
)
|
|
1646
|
-
] }),
|
|
1647
|
-
/* @__PURE__ */ jsx3("ul", { className: "py-1", children: commands.map((command) => {
|
|
1648
|
-
const hint = argHint(command.args);
|
|
1649
|
-
return /* @__PURE__ */ jsxs3("li", { className: "flex items-baseline gap-2 px-3 py-1", children: [
|
|
1650
|
-
/* @__PURE__ */ jsxs3("span", { className: "font-mono text-og-sm text-og-accent", children: [
|
|
1651
|
-
"/",
|
|
1652
|
-
command.name,
|
|
1653
|
-
hint ? /* @__PURE__ */ jsx3("span", { className: "ml-1 text-og-fg-subtle", children: hint }) : null
|
|
1654
|
-
] }),
|
|
1655
|
-
/* @__PURE__ */ jsx3("span", { className: "text-og-sm text-og-fg-muted", children: command.description }),
|
|
1656
|
-
command.danger ? /* @__PURE__ */ jsx3("span", { className: "ml-auto rounded-og-xs bg-og-status-failed/15 px-1 text-og-xs uppercase tracking-wide text-og-status-failed", children: messages.danger }) : null
|
|
1657
|
-
] }, command.name);
|
|
1658
|
-
}) })
|
|
1741
|
+
children,
|
|
1742
|
+
/* @__PURE__ */ jsx3(ComposerAnnouncements, {})
|
|
1659
1743
|
]
|
|
1660
1744
|
}
|
|
1661
|
-
);
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
return {
|
|
1685
|
-
state: { ...INITIAL_TRANSCRIPTION_CONTROL_STATE, generation: state.generation },
|
|
1686
|
-
commit: null
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
if (action.type === "start") {
|
|
1690
|
-
if (action.generation <= state.generation) return { state, commit: null };
|
|
1691
|
-
return {
|
|
1692
|
-
state: {
|
|
1693
|
-
...INITIAL_TRANSCRIPTION_CONTROL_STATE,
|
|
1694
|
-
status: "requesting-permission",
|
|
1695
|
-
generation: action.generation,
|
|
1696
|
-
localSessionId: action.localSessionId
|
|
1697
|
-
},
|
|
1698
|
-
commit: null
|
|
1699
|
-
};
|
|
1700
|
-
}
|
|
1701
|
-
if (action.generation !== state.generation) return { state, commit: null };
|
|
1702
|
-
if (action.type === "start.failed") {
|
|
1703
|
-
if (state.status === "closed" || state.status === "error") return { state, commit: null };
|
|
1704
|
-
return {
|
|
1705
|
-
state: {
|
|
1706
|
-
...state,
|
|
1707
|
-
status: "error",
|
|
1708
|
-
partial: "",
|
|
1709
|
-
error: { code: normalizeTranscriptionErrorCode(action.code), recoverable: false }
|
|
1745
|
+
) });
|
|
1746
|
+
});
|
|
1747
|
+
var Frame = forwardRef(function ComposerFrame({ className, ...props }, ref) {
|
|
1748
|
+
return /* @__PURE__ */ jsx3("div", { ...props, ref, className: cn("relative", className) });
|
|
1749
|
+
});
|
|
1750
|
+
function CommandPalette2({ className }) {
|
|
1751
|
+
const controller = useComposerController();
|
|
1752
|
+
const setPaletteMounted = controller.setPaletteMounted;
|
|
1753
|
+
useEffect3(() => {
|
|
1754
|
+
setPaletteMounted(true);
|
|
1755
|
+
return () => setPaletteMounted(false);
|
|
1756
|
+
}, [setPaletteMounted]);
|
|
1757
|
+
if (!controller.paletteEnabled) return null;
|
|
1758
|
+
return /* @__PURE__ */ jsx3("div", { className, children: /* @__PURE__ */ jsx3(
|
|
1759
|
+
CommandPalette,
|
|
1760
|
+
{
|
|
1761
|
+
open: controller.palette.open && controller.confirmState === null,
|
|
1762
|
+
items: controller.palette.items,
|
|
1763
|
+
highlight: controller.palette.highlight,
|
|
1764
|
+
onHighlight: controller.palette.setHighlight,
|
|
1765
|
+
onRun: (index) => {
|
|
1766
|
+
controller.palette.setHighlight(index);
|
|
1767
|
+
void controller.palette.runAt(index);
|
|
1710
1768
|
},
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
if (!isActiveTranscriptionStatus(state.status)) return { state, commit: null };
|
|
1716
|
-
return {
|
|
1717
|
-
state: { ...state, status: "cancelling", partial: "", error: null },
|
|
1718
|
-
commit: null
|
|
1719
|
-
};
|
|
1720
|
-
}
|
|
1721
|
-
if (action.type === "cancel.settled") {
|
|
1722
|
-
if (state.status !== "cancelling") return { state, commit: null };
|
|
1723
|
-
return { state: { ...state, status: "closed", partial: "" }, commit: null };
|
|
1724
|
-
}
|
|
1725
|
-
const event = action.event;
|
|
1726
|
-
if (event.localSessionId !== state.localSessionId) return { state, commit: null };
|
|
1727
|
-
if (!Number.isSafeInteger(event.sequence) || event.sequence <= state.lastSequence) {
|
|
1728
|
-
return { state, commit: null };
|
|
1729
|
-
}
|
|
1730
|
-
if (state.status === "closed" || state.status === "error") return { state, commit: null };
|
|
1731
|
-
if (state.status === "cancelling" && event.type !== "session.closed") {
|
|
1732
|
-
return { state: { ...state, lastSequence: event.sequence }, commit: null };
|
|
1733
|
-
}
|
|
1734
|
-
const sequenced = { ...state, lastSequence: event.sequence };
|
|
1735
|
-
switch (event.type) {
|
|
1736
|
-
case "permission.requested":
|
|
1737
|
-
return {
|
|
1738
|
-
state: { ...sequenced, status: "requesting-permission", partial: "", error: null },
|
|
1739
|
-
commit: null
|
|
1740
|
-
};
|
|
1741
|
-
case "session.opened":
|
|
1742
|
-
return {
|
|
1743
|
-
state: {
|
|
1744
|
-
...sequenced,
|
|
1745
|
-
status: "listening",
|
|
1746
|
-
providerSessionId: event.providerSessionId,
|
|
1747
|
-
error: null
|
|
1748
|
-
},
|
|
1749
|
-
commit: null
|
|
1750
|
-
};
|
|
1751
|
-
case "transcript.partial":
|
|
1752
|
-
return {
|
|
1753
|
-
state: { ...sequenced, status: "listening", partial: event.text, error: null },
|
|
1754
|
-
commit: null
|
|
1755
|
-
};
|
|
1756
|
-
case "transcript.final": {
|
|
1757
|
-
const text = event.text.trim();
|
|
1758
|
-
const duplicate = state.acceptedFinalIds.includes(event.providerAcceptanceId);
|
|
1759
|
-
const acceptedFinalIds = duplicate || text.length === 0 ? state.acceptedFinalIds : [...state.acceptedFinalIds, event.providerAcceptanceId];
|
|
1760
|
-
return {
|
|
1761
|
-
state: {
|
|
1762
|
-
...sequenced,
|
|
1763
|
-
status: "listening",
|
|
1764
|
-
partial: "",
|
|
1765
|
-
error: null,
|
|
1766
|
-
acceptedFinalIds
|
|
1767
|
-
},
|
|
1768
|
-
commit: duplicate || text.length === 0 ? null : text
|
|
1769
|
-
};
|
|
1769
|
+
argHintText: controller.palette.activeArgHint,
|
|
1770
|
+
listboxId: controller.listboxId,
|
|
1771
|
+
label: controller.messages.slashCommandsLabel,
|
|
1772
|
+
dangerLabel: controller.messages.danger
|
|
1770
1773
|
}
|
|
1771
|
-
|
|
1772
|
-
return {
|
|
1773
|
-
state: {
|
|
1774
|
-
...sequenced,
|
|
1775
|
-
audioMilliseconds: Math.max(state.audioMilliseconds, event.audioMilliseconds),
|
|
1776
|
-
costUsd: event.costUsd
|
|
1777
|
-
},
|
|
1778
|
-
commit: null
|
|
1779
|
-
};
|
|
1780
|
-
case "session.reconnecting":
|
|
1781
|
-
return {
|
|
1782
|
-
state: { ...sequenced, status: "reconnecting", partial: "", error: null },
|
|
1783
|
-
commit: null
|
|
1784
|
-
};
|
|
1785
|
-
case "session.error":
|
|
1786
|
-
return {
|
|
1787
|
-
state: {
|
|
1788
|
-
...sequenced,
|
|
1789
|
-
status: event.recoverable ? "reconnecting" : "error",
|
|
1790
|
-
partial: "",
|
|
1791
|
-
error: {
|
|
1792
|
-
code: normalizeTranscriptionErrorCode(event.code),
|
|
1793
|
-
recoverable: event.recoverable
|
|
1794
|
-
}
|
|
1795
|
-
},
|
|
1796
|
-
commit: null
|
|
1797
|
-
};
|
|
1798
|
-
case "session.closed":
|
|
1799
|
-
return {
|
|
1800
|
-
state: {
|
|
1801
|
-
...sequenced,
|
|
1802
|
-
status: event.reason === "error" ? "error" : "closed",
|
|
1803
|
-
partial: ""
|
|
1804
|
-
},
|
|
1805
|
-
commit: null
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
function appendFinalTranscript(draft, transcript) {
|
|
1810
|
-
const final = transcript.trim();
|
|
1811
|
-
if (!final) return draft;
|
|
1812
|
-
if (!draft) return final;
|
|
1813
|
-
return /\s$/u.test(draft) ? `${draft}${final}` : `${draft} ${final}`;
|
|
1774
|
+
) });
|
|
1814
1775
|
}
|
|
1815
|
-
var
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1776
|
+
var Surface = forwardRef(function ComposerSurface({ className, children, ...props }, ref) {
|
|
1777
|
+
const controller = useComposerController();
|
|
1778
|
+
return /* @__PURE__ */ jsxs3(
|
|
1779
|
+
"div",
|
|
1780
|
+
{
|
|
1781
|
+
...props,
|
|
1782
|
+
ref,
|
|
1783
|
+
onDragOver: controller.attachments ? controller.handleDragOver : void 0,
|
|
1784
|
+
onDragLeave: controller.attachments ? controller.handleDragLeave : void 0,
|
|
1785
|
+
onDrop: controller.attachments ? controller.handleDrop : void 0,
|
|
1786
|
+
className: cn(
|
|
1787
|
+
"relative rounded-og-lg border border-og-border/90 bg-og-surface-1 shadow-og-sm",
|
|
1788
|
+
"transition-[border-color,box-shadow] duration-200 ease-og-out",
|
|
1789
|
+
"focus-within:border-og-accent/50 focus-within:shadow-og-glow",
|
|
1790
|
+
controller.dragging && "border-dashed border-og-accent",
|
|
1791
|
+
className
|
|
1792
|
+
),
|
|
1793
|
+
children: [
|
|
1794
|
+
controller.dragging ? /* @__PURE__ */ jsx3(
|
|
1795
|
+
"div",
|
|
1796
|
+
{
|
|
1797
|
+
"aria-hidden": true,
|
|
1798
|
+
className: cn(
|
|
1799
|
+
"pointer-events-none absolute inset-0 z-10 flex items-center justify-center",
|
|
1800
|
+
"rounded-og-lg bg-og-surface-1/85 text-sm font-medium text-og-accent backdrop-blur-[1px]"
|
|
1801
|
+
),
|
|
1802
|
+
children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-2", children: [
|
|
1803
|
+
/* @__PURE__ */ jsx3(PaperclipIcon, { className: "size-4" }),
|
|
1804
|
+
controller.messages.dropFiles
|
|
1805
|
+
] })
|
|
1806
|
+
}
|
|
1807
|
+
) : null,
|
|
1808
|
+
children
|
|
1809
|
+
]
|
|
1810
|
+
}
|
|
1841
1811
|
);
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1812
|
+
});
|
|
1813
|
+
function PausedState() {
|
|
1814
|
+
const controller = useComposerController();
|
|
1815
|
+
if (!controller.paused || !controller.effectiveControl || !controller.hasControl) return null;
|
|
1816
|
+
return /* @__PURE__ */ jsx3(
|
|
1817
|
+
WorkstreamPausedStrip,
|
|
1818
|
+
{
|
|
1819
|
+
control: controller.effectiveControl,
|
|
1820
|
+
queuedAheadCount: controller.queuedAheadCount,
|
|
1821
|
+
open: controller.controlDetailsOpen,
|
|
1822
|
+
busy: controller.resuming,
|
|
1823
|
+
sending: controller.sending,
|
|
1824
|
+
canControlWorkspace: controller.canControlWorkspace,
|
|
1825
|
+
controlLinks: controller.controlLinks,
|
|
1826
|
+
messages: controller.messages,
|
|
1827
|
+
onOpenChange: controller.setControlDetailsOpen,
|
|
1828
|
+
onResume: () => void controller.resume(),
|
|
1829
|
+
onResumeOption: (option) => void controller.resumeScope(option)
|
|
1830
|
+
}
|
|
1845
1831
|
);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1832
|
+
}
|
|
1833
|
+
function RestoredResources() {
|
|
1834
|
+
const controller = useComposerController();
|
|
1835
|
+
if (controller.restoredResources.length === 0 || !controller.removeRestoredResource) return null;
|
|
1836
|
+
return /* @__PURE__ */ jsx3(
|
|
1837
|
+
RestoredResourceChips,
|
|
1838
|
+
{
|
|
1839
|
+
resources: controller.restoredResources,
|
|
1840
|
+
messages: controller.messages,
|
|
1841
|
+
onRemove: controller.removeRestoredResource
|
|
1842
|
+
}
|
|
1855
1843
|
);
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1844
|
+
}
|
|
1845
|
+
function Attachments() {
|
|
1846
|
+
const controller = useComposerController();
|
|
1847
|
+
if (!controller.attachments || controller.attachments.attachments.length === 0) return null;
|
|
1848
|
+
return /* @__PURE__ */ jsx3(
|
|
1849
|
+
AttachmentChips,
|
|
1850
|
+
{
|
|
1851
|
+
attachments: controller.attachments.attachments,
|
|
1852
|
+
messages: controller.messages,
|
|
1853
|
+
onRemove: controller.attachments.remove,
|
|
1854
|
+
onRetry: controller.attachments.retry
|
|
1855
|
+
}
|
|
1863
1856
|
);
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1857
|
+
}
|
|
1858
|
+
var Input = forwardRef(function ComposerInput({ rows = 1, placeholder, className, "aria-label": ariaLabel, ...props }, forwardedRef) {
|
|
1859
|
+
const controller = useComposerController();
|
|
1860
|
+
const paletteOpen = controller.paletteEnabled && controller.paletteMounted && controller.palette.open;
|
|
1861
|
+
return /* @__PURE__ */ jsx3(
|
|
1862
|
+
"textarea",
|
|
1863
|
+
{
|
|
1864
|
+
...props,
|
|
1865
|
+
ref: mergeRefs(controller.textareaRef, forwardedRef),
|
|
1866
|
+
rows,
|
|
1867
|
+
value: controller.value,
|
|
1868
|
+
onChange: (event) => controller.setValue(event.target.value),
|
|
1869
|
+
onKeyDown: controller.handleKeyDown,
|
|
1870
|
+
onPaste: controller.handlePaste,
|
|
1871
|
+
placeholder: controller.paused && !controller.disabled ? controller.messages.pausedPlaceholder : placeholder ?? controller.messages.messagePlaceholder,
|
|
1872
|
+
disabled: controller.disabled,
|
|
1873
|
+
"aria-label": ariaLabel ?? controller.messages.inputLabel,
|
|
1874
|
+
"aria-keyshortcuts": "Enter Meta+Enter Control+Enter Shift+Enter",
|
|
1875
|
+
"aria-autocomplete": controller.paletteEnabled && controller.paletteMounted ? "list" : void 0,
|
|
1876
|
+
"aria-controls": paletteOpen ? controller.listboxId : void 0,
|
|
1877
|
+
"aria-activedescendant": paletteOpen ? `${controller.listboxId}-option-${controller.palette.highlight}` : void 0,
|
|
1878
|
+
className: cn(
|
|
1879
|
+
"block w-full resize-none bg-transparent px-3.5 pt-3 pb-1 text-base leading-6 md:px-4 md:text-og-md",
|
|
1880
|
+
"text-og-fg placeholder:text-og-fg-subtle focus:outline-none focus-visible:outline-none",
|
|
1881
|
+
"disabled:cursor-not-allowed disabled:opacity-60",
|
|
1882
|
+
className
|
|
1883
|
+
)
|
|
1884
|
+
}
|
|
1867
1885
|
);
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
return transition.state;
|
|
1883
|
-
},
|
|
1884
|
-
[focusInput, setValue]
|
|
1886
|
+
});
|
|
1887
|
+
function Confirmation() {
|
|
1888
|
+
const controller = useComposerController();
|
|
1889
|
+
const command = controller.confirmState?.command;
|
|
1890
|
+
if (!command || !controller.confirmState) return null;
|
|
1891
|
+
return /* @__PURE__ */ jsx3(
|
|
1892
|
+
ConfirmBar,
|
|
1893
|
+
{
|
|
1894
|
+
command,
|
|
1895
|
+
messages: controller.messages,
|
|
1896
|
+
onCancel: () => controller.settleConfirmation(false),
|
|
1897
|
+
onConfirm: () => controller.settleConfirmation(true),
|
|
1898
|
+
returnFocusRef: controller.textareaRef
|
|
1899
|
+
}
|
|
1885
1900
|
);
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
});
|
|
1896
|
-
},
|
|
1897
|
-
[cleanupTimeoutMs, reportDiagnostic]
|
|
1901
|
+
}
|
|
1902
|
+
var Footer = forwardRef(function ComposerFooter({ className, ...props }, ref) {
|
|
1903
|
+
return /* @__PURE__ */ jsx3(
|
|
1904
|
+
"div",
|
|
1905
|
+
{
|
|
1906
|
+
...props,
|
|
1907
|
+
ref,
|
|
1908
|
+
className: cn("flex items-end gap-1.5 px-2 pb-2 pt-0.5 sm:px-2.5 sm:pb-2.5", className)
|
|
1909
|
+
}
|
|
1898
1910
|
);
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1911
|
+
});
|
|
1912
|
+
var Controls = forwardRef(
|
|
1913
|
+
function ComposerControls({ className, ...props }, ref) {
|
|
1914
|
+
return /* @__PURE__ */ jsx3(
|
|
1915
|
+
"span",
|
|
1916
|
+
{
|
|
1917
|
+
...props,
|
|
1918
|
+
ref,
|
|
1919
|
+
className: cn("flex min-w-0 flex-1 flex-wrap items-center gap-1.5", className)
|
|
1920
|
+
}
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
);
|
|
1924
|
+
var Hint = forwardRef(function ComposerHint({ className, children, ...props }, ref) {
|
|
1925
|
+
const controller = useComposerController();
|
|
1926
|
+
return /* @__PURE__ */ jsx3(
|
|
1927
|
+
"span",
|
|
1928
|
+
{
|
|
1929
|
+
...props,
|
|
1930
|
+
ref,
|
|
1931
|
+
className: cn("min-w-0 flex-1 px-1.5 text-og-xs text-og-fg-subtle max-sm:hidden", className),
|
|
1932
|
+
children: children ?? controller.messages.keyboardHint
|
|
1902
1933
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
});
|
|
1913
|
-
if (!request) {
|
|
1914
|
-
apply({
|
|
1915
|
-
type: "start.failed",
|
|
1916
|
-
generation,
|
|
1917
|
-
code: "policy_blocked"
|
|
1918
|
-
});
|
|
1919
|
-
return false;
|
|
1934
|
+
);
|
|
1935
|
+
});
|
|
1936
|
+
var Actions = forwardRef(function ComposerActions({ className, ...props }, ref) {
|
|
1937
|
+
return /* @__PURE__ */ jsx3(
|
|
1938
|
+
"span",
|
|
1939
|
+
{
|
|
1940
|
+
...props,
|
|
1941
|
+
ref,
|
|
1942
|
+
className: cn("ml-auto flex shrink-0 items-center gap-1.5", className)
|
|
1920
1943
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
apply({ type: "event", generation, event });
|
|
1931
|
-
if (event.type === "session.error" && !event.recoverable) {
|
|
1932
|
-
releaseTerminalSession(generation);
|
|
1933
|
-
} else if (event.type === "session.closed") {
|
|
1934
|
-
releaseTerminalSession(generation);
|
|
1935
|
-
}
|
|
1936
|
-
},
|
|
1944
|
+
);
|
|
1945
|
+
});
|
|
1946
|
+
var AttachButton = forwardRef(
|
|
1947
|
+
function ComposerAttachButton({ accept, multiple = true, className, "aria-label": ariaLabel, title, children, ...props }, ref) {
|
|
1948
|
+
const controller = useComposerController();
|
|
1949
|
+
if (!controller.attachments) return null;
|
|
1950
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1951
|
+
/* @__PURE__ */ jsx3(
|
|
1952
|
+
"input",
|
|
1937
1953
|
{
|
|
1938
|
-
|
|
1939
|
-
|
|
1954
|
+
ref: controller.fileInputRef,
|
|
1955
|
+
type: "file",
|
|
1956
|
+
accept,
|
|
1957
|
+
multiple,
|
|
1958
|
+
className: "hidden",
|
|
1959
|
+
onChange: controller.handleFileChange
|
|
1960
|
+
}
|
|
1961
|
+
),
|
|
1962
|
+
/* @__PURE__ */ jsx3(
|
|
1963
|
+
"button",
|
|
1964
|
+
{
|
|
1965
|
+
...props,
|
|
1966
|
+
ref,
|
|
1967
|
+
type: "button",
|
|
1968
|
+
disabled: controller.disabled,
|
|
1969
|
+
onClick: () => controller.fileInputRef.current?.click(),
|
|
1970
|
+
"aria-label": ariaLabel ?? controller.messages.attachFiles,
|
|
1971
|
+
title: title ?? controller.messages.attachFiles,
|
|
1972
|
+
className: cn(
|
|
1973
|
+
"inline-flex size-8 items-center justify-center rounded-og-md",
|
|
1974
|
+
"text-og-fg-muted transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
|
|
1975
|
+
"disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
|
|
1976
|
+
className
|
|
1977
|
+
),
|
|
1978
|
+
children: children ?? /* @__PURE__ */ jsx3(PaperclipIcon, { className: "size-4" })
|
|
1940
1979
|
}
|
|
1941
1980
|
)
|
|
1942
|
-
)
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1981
|
+
] });
|
|
1982
|
+
}
|
|
1983
|
+
);
|
|
1984
|
+
function ModelPicker2({
|
|
1985
|
+
models,
|
|
1986
|
+
rows,
|
|
1987
|
+
value,
|
|
1988
|
+
onChange,
|
|
1989
|
+
label,
|
|
1990
|
+
className,
|
|
1991
|
+
codexOnly
|
|
1992
|
+
}) {
|
|
1993
|
+
const controller = useComposerController();
|
|
1994
|
+
return /* @__PURE__ */ jsx3(
|
|
1995
|
+
ModelPicker,
|
|
1996
|
+
{
|
|
1997
|
+
models,
|
|
1998
|
+
rows,
|
|
1999
|
+
value,
|
|
2000
|
+
onChange: (modelId) => onChange?.(modelId),
|
|
2001
|
+
disabled: controller.disabled,
|
|
2002
|
+
label: label ?? controller.messages.modelLabel,
|
|
2003
|
+
className,
|
|
2004
|
+
codexOnly
|
|
2005
|
+
}
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
var PauseButton = forwardRef(
|
|
2009
|
+
function ComposerPauseButton({ className, "aria-label": ariaLabel, title, children, ...props }, ref) {
|
|
2010
|
+
const controller = useComposerController();
|
|
2011
|
+
if (!controller.effectiveControl || controller.paused || !controller.hasControl) return null;
|
|
2012
|
+
const busy = controller.pausing || controller.resuming;
|
|
2013
|
+
return /* @__PURE__ */ jsx3(
|
|
2014
|
+
"button",
|
|
2015
|
+
{
|
|
2016
|
+
...props,
|
|
2017
|
+
ref: mergeRefs(controller.pauseButtonRef, ref),
|
|
2018
|
+
type: "button",
|
|
2019
|
+
onClick: () => void controller.pause(),
|
|
2020
|
+
disabled: busy,
|
|
2021
|
+
"aria-label": ariaLabel ?? controller.messages.pauseAriaLabel,
|
|
2022
|
+
title: title ?? controller.messages.pauseTitle,
|
|
2023
|
+
className: cn(
|
|
2024
|
+
"inline-flex size-8 items-center justify-center rounded-og-md border border-og-border pointer-coarse:size-11",
|
|
2025
|
+
"bg-og-surface-2 text-og-fg-muted transition-colors duration-150",
|
|
2026
|
+
"hover:border-og-status-waiting/50 hover:text-og-status-waiting",
|
|
2027
|
+
"disabled:opacity-50",
|
|
2028
|
+
className
|
|
2029
|
+
),
|
|
2030
|
+
children: children ?? (busy ? /* @__PURE__ */ jsx3(LoaderCircleIcon, { className: "size-3.5 animate-og-spin" }) : /* @__PURE__ */ jsx3(PauseIcon, { className: "size-3.5 fill-current" }))
|
|
1962
2031
|
}
|
|
1963
2032
|
);
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2033
|
+
}
|
|
2034
|
+
);
|
|
2035
|
+
var SendButton = forwardRef(
|
|
2036
|
+
function ComposerSendButton({ className, "aria-label": ariaLabel, title, children, ...props }, ref) {
|
|
2037
|
+
const controller = useComposerController();
|
|
2038
|
+
return /* @__PURE__ */ jsx3(
|
|
2039
|
+
"button",
|
|
2040
|
+
{
|
|
2041
|
+
...props,
|
|
2042
|
+
ref,
|
|
2043
|
+
type: "button",
|
|
2044
|
+
onClick: () => void controller.submit("queue"),
|
|
2045
|
+
disabled: !controller.canSubmit,
|
|
2046
|
+
"aria-label": ariaLabel ?? (controller.paused ? controller.messages.sendAndResumeAriaLabel : controller.messages.sendMessageAriaLabel),
|
|
2047
|
+
title: title ?? (controller.paused ? controller.messages.sendAndResumeTitle : controller.messages.sendTitle),
|
|
2048
|
+
className: cn(
|
|
2049
|
+
"inline-flex size-8 items-center justify-center rounded-og-md pointer-coarse:size-11",
|
|
2050
|
+
"bg-og-accent text-og-accent-fg shadow-og-sm",
|
|
2051
|
+
"transition-[background-color,transform,opacity] duration-150 ease-og-spring",
|
|
2052
|
+
"hover:bg-og-accent-strong active:scale-95",
|
|
2053
|
+
"disabled:cursor-not-allowed disabled:bg-og-surface-3 disabled:text-og-fg-subtle disabled:shadow-none",
|
|
2054
|
+
className
|
|
2055
|
+
),
|
|
2056
|
+
children: children ?? (controller.sending ? /* @__PURE__ */ jsx3(LoaderCircleIcon, { className: "size-4 animate-og-spin" }) : /* @__PURE__ */ jsx3(ArrowUpIcon, { className: "size-4" }))
|
|
1969
2057
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
if (pendingStartRef.current?.generation === generation) pendingStartRef.current = null;
|
|
1982
|
-
if (outcome.kind === "aborted") return false;
|
|
1983
|
-
if (outcome.kind === "timeout") {
|
|
1984
|
-
controller.abort("transcription-start-timeout");
|
|
1985
|
-
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
1986
|
-
reportDiagnostic({
|
|
1987
|
-
operation: "start",
|
|
1988
|
-
code: "timeout",
|
|
1989
|
-
detail: `Transcription adapter start exceeded ${startTimeoutMs}ms.`
|
|
1990
|
-
});
|
|
1991
|
-
apply({ type: "start.failed", generation, code: "timeout" });
|
|
1992
|
-
return false;
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
);
|
|
2061
|
+
function Help() {
|
|
2062
|
+
const controller = useComposerController();
|
|
2063
|
+
return /* @__PURE__ */ jsx3(AnimatePresence2, { children: controller.helpOpen ? /* @__PURE__ */ jsx3(
|
|
2064
|
+
HelpPanel,
|
|
2065
|
+
{
|
|
2066
|
+
commands: controller.helpCommands,
|
|
2067
|
+
messages: controller.messages,
|
|
2068
|
+
onClose: () => controller.setHelpOpen(false)
|
|
1993
2069
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2070
|
+
) : null });
|
|
2071
|
+
}
|
|
2072
|
+
function Status() {
|
|
2073
|
+
const controller = useComposerController();
|
|
2074
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
2075
|
+
/* @__PURE__ */ jsx3(AnimatePresence2, { children: controller.activeNotice ? /* @__PURE__ */ jsx3(
|
|
2076
|
+
motion2.p,
|
|
2077
|
+
{
|
|
2078
|
+
initial: { opacity: 0, height: 0 },
|
|
2079
|
+
animate: { opacity: 1, height: "auto" },
|
|
2080
|
+
exit: { opacity: 0, height: 0 },
|
|
2081
|
+
className: cn(
|
|
2082
|
+
"overflow-hidden px-1 pt-1.5 text-xs",
|
|
2083
|
+
controller.activeNotice.tone === "ok" ? "text-og-fg-muted" : "text-og-status-failed"
|
|
2084
|
+
),
|
|
2085
|
+
children: controller.activeNotice.message
|
|
2004
2086
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
pending.controller.abort("transcription-locally-cancelled");
|
|
2042
|
-
}
|
|
2043
|
-
const active = sessionRef.current;
|
|
2044
|
-
sessionRef.current = null;
|
|
2045
|
-
if (activePolicyRef.current?.generation === generation) activePolicyRef.current = null;
|
|
2046
|
-
apply({ type: "cancel.settled", generation });
|
|
2047
|
-
focusInput();
|
|
2048
|
-
if (active?.generation === generation) {
|
|
2049
|
-
detachSessionCleanup(active.session, {
|
|
2050
|
-
cancelReason: "user-cancelled",
|
|
2051
|
-
cleanupTimeoutMs,
|
|
2052
|
-
reportDiagnostic
|
|
2053
|
-
});
|
|
2054
|
-
}
|
|
2055
|
-
return true;
|
|
2056
|
-
}, [apply, cleanupTimeoutMs, focusInput, reportDiagnostic]);
|
|
2057
|
-
const reset = useCallback3(() => apply({ type: "reset" }), [apply]);
|
|
2058
|
-
useEffect2(() => {
|
|
2059
|
-
if (!isActiveTranscriptionStatus(state.status)) return;
|
|
2060
|
-
const onKeyDown = (event) => {
|
|
2061
|
-
if (event.key !== "Escape") return;
|
|
2062
|
-
event.preventDefault();
|
|
2063
|
-
void cancel();
|
|
2064
|
-
};
|
|
2065
|
-
document.addEventListener("keydown", onKeyDown);
|
|
2066
|
-
return () => document.removeEventListener("keydown", onKeyDown);
|
|
2067
|
-
}, [cancel, state.status]);
|
|
2068
|
-
useEffect2(() => {
|
|
2069
|
-
if (!isActiveTranscriptionStatus(state.status)) return;
|
|
2070
|
-
const activePolicy = activePolicyRef.current;
|
|
2071
|
-
if (unavailableReason === null && activePolicy?.generation === state.generation && activePolicy.revision === policyRevision) {
|
|
2072
|
-
return;
|
|
2087
|
+
) : null }),
|
|
2088
|
+
controller.draftConflict ? /* @__PURE__ */ jsxs3("div", { className: "mt-1.5 flex flex-wrap items-center gap-2 px-1 text-og-xs text-og-status-failed", children: [
|
|
2089
|
+
/* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1", children: controller.messages.draftConflict }),
|
|
2090
|
+
/* @__PURE__ */ jsx3(
|
|
2091
|
+
"button",
|
|
2092
|
+
{
|
|
2093
|
+
type: "button",
|
|
2094
|
+
className: "underline underline-offset-2",
|
|
2095
|
+
onClick: () => void controller.resolveDraftConflict?.("use_remote"),
|
|
2096
|
+
children: controller.messages.useOtherDraft
|
|
2097
|
+
}
|
|
2098
|
+
),
|
|
2099
|
+
/* @__PURE__ */ jsx3(
|
|
2100
|
+
"button",
|
|
2101
|
+
{
|
|
2102
|
+
type: "button",
|
|
2103
|
+
className: "font-medium underline underline-offset-2",
|
|
2104
|
+
onClick: () => void controller.resolveDraftConflict?.("keep_mine"),
|
|
2105
|
+
children: controller.messages.keepMine
|
|
2106
|
+
}
|
|
2107
|
+
)
|
|
2108
|
+
] }) : null
|
|
2109
|
+
] });
|
|
2110
|
+
}
|
|
2111
|
+
function ComposerAnnouncements() {
|
|
2112
|
+
const controller = useComposerController();
|
|
2113
|
+
return /* @__PURE__ */ jsxs3("div", { className: "sr-only", children: [
|
|
2114
|
+
controller.activeNotice ? /* @__PURE__ */ jsx3("p", { role: controller.activeNotice.tone === "error" ? "alert" : "status", children: controller.activeNotice.message }) : null,
|
|
2115
|
+
controller.draftConflict ? /* @__PURE__ */ jsx3("p", { role: "alert", children: controller.messages.draftConflict }) : null
|
|
2116
|
+
] });
|
|
2117
|
+
}
|
|
2118
|
+
function mergeRefs(...refs) {
|
|
2119
|
+
return (node) => {
|
|
2120
|
+
for (const ref of refs) {
|
|
2121
|
+
if (typeof ref === "function") ref(node);
|
|
2122
|
+
else if (ref) ref.current = node;
|
|
2073
2123
|
}
|
|
2074
|
-
void cancel();
|
|
2075
|
-
}, [cancel, policyRevision, state.generation, state.status, unavailableReason]);
|
|
2076
|
-
useEffect2(
|
|
2077
|
-
() => () => {
|
|
2078
|
-
const pending = pendingStartRef.current;
|
|
2079
|
-
pendingStartRef.current = null;
|
|
2080
|
-
pending?.controller.abort("transcription-component-unmounted");
|
|
2081
|
-
const active = sessionRef.current;
|
|
2082
|
-
sessionRef.current = null;
|
|
2083
|
-
activePolicyRef.current = null;
|
|
2084
|
-
stateRef.current = {
|
|
2085
|
-
...stateRef.current,
|
|
2086
|
-
generation: stateRef.current.generation + 1,
|
|
2087
|
-
status: "closed",
|
|
2088
|
-
partial: ""
|
|
2089
|
-
};
|
|
2090
|
-
if (active) {
|
|
2091
|
-
const runtime = cleanupRuntimeRef.current;
|
|
2092
|
-
detachSessionCleanup(active.session, {
|
|
2093
|
-
cancelReason: "component-unmounted",
|
|
2094
|
-
cleanupTimeoutMs: runtime.cleanupTimeoutMs,
|
|
2095
|
-
reportDiagnostic: runtime.reportDiagnostic
|
|
2096
|
-
});
|
|
2097
|
-
}
|
|
2098
|
-
},
|
|
2099
|
-
[]
|
|
2100
|
-
);
|
|
2101
|
-
return {
|
|
2102
|
-
state,
|
|
2103
|
-
available: unavailableReason === null,
|
|
2104
|
-
unavailableReason,
|
|
2105
|
-
start,
|
|
2106
|
-
cancel,
|
|
2107
|
-
reset
|
|
2108
2124
|
};
|
|
2109
2125
|
}
|
|
2110
|
-
function
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2126
|
+
function WorkstreamPausedStrip({
|
|
2127
|
+
control,
|
|
2128
|
+
queuedAheadCount,
|
|
2129
|
+
open,
|
|
2130
|
+
busy,
|
|
2131
|
+
sending,
|
|
2132
|
+
canControlWorkspace,
|
|
2133
|
+
controlLinks,
|
|
2134
|
+
messages,
|
|
2135
|
+
onOpenChange,
|
|
2136
|
+
onResume,
|
|
2137
|
+
onResumeOption
|
|
2138
|
+
}) {
|
|
2139
|
+
const blocker = control.primaryBlocker;
|
|
2140
|
+
const cause = blocker?.kind === "workspace" ? messages.workspacePaused : control.directState === "paused" ? messages.pausedHere : messages.pausedBy(blocker?.displayName ?? messages.parentBlocker);
|
|
2141
|
+
const primary = control.resumeOptions.find((option) => option.scope === "selected");
|
|
2142
|
+
const broaderOptions = control.resumeOptions.filter(
|
|
2143
|
+
(option) => option !== primary && option.scope !== "selected" && (option.scope !== "workspace" || canControlWorkspace)
|
|
2144
|
+
);
|
|
2145
|
+
return /* @__PURE__ */ jsxs3("div", { className: "border-b border-og-status-waiting/25 bg-og-status-waiting/[0.07]", children: [
|
|
2146
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2 px-3 py-2", children: [
|
|
2147
|
+
/* @__PURE__ */ jsx3(PauseIcon, { className: "size-3.5 shrink-0 text-og-status-waiting" }),
|
|
2148
|
+
/* @__PURE__ */ jsxs3(
|
|
2149
|
+
"button",
|
|
2150
|
+
{
|
|
2151
|
+
type: "button",
|
|
2152
|
+
className: "min-w-0 flex-1 text-left",
|
|
2153
|
+
onClick: () => onOpenChange(!open),
|
|
2154
|
+
"aria-expanded": open,
|
|
2155
|
+
children: [
|
|
2156
|
+
/* @__PURE__ */ jsx3("span", { className: "block truncate text-og-xs font-medium text-og-fg", children: cause }),
|
|
2157
|
+
/* @__PURE__ */ jsx3("span", { className: "block truncate text-[11px] text-og-fg-muted", children: queuedAheadCount > 0 ? messages.queuedAhead(queuedAheadCount) : sending ? messages.resumingAndSending : messages.nextMessageResumes })
|
|
2158
|
+
]
|
|
2159
|
+
}
|
|
2160
|
+
),
|
|
2161
|
+
/* @__PURE__ */ jsxs3(
|
|
2162
|
+
"button",
|
|
2163
|
+
{
|
|
2164
|
+
type: "button",
|
|
2165
|
+
"aria-label": messages.resumeThisWorkstream,
|
|
2166
|
+
className: "inline-flex min-h-8 shrink-0 items-center gap-1.5 rounded-og-md border border-og-status-waiting/35 bg-og-surface-1 px-2.5 text-og-xs font-medium text-og-fg hover:bg-og-surface-2 pointer-coarse:min-h-11",
|
|
2167
|
+
disabled: busy,
|
|
2168
|
+
onClick: onResume,
|
|
2169
|
+
children: [
|
|
2170
|
+
busy ? /* @__PURE__ */ jsx3(LoaderCircleIcon, { className: "size-3.5 animate-og-spin" }) : /* @__PURE__ */ jsx3(PlayIcon, { className: "size-3.5 fill-current" }),
|
|
2171
|
+
/* @__PURE__ */ jsx3("span", { className: "hidden min-[400px]:inline", children: messages.resumeThisWorkstream }),
|
|
2172
|
+
/* @__PURE__ */ jsx3("span", { className: "min-[400px]:hidden", children: messages.resumeShort })
|
|
2173
|
+
]
|
|
2174
|
+
}
|
|
2175
|
+
),
|
|
2176
|
+
/* @__PURE__ */ jsx3(
|
|
2177
|
+
"button",
|
|
2178
|
+
{
|
|
2179
|
+
type: "button",
|
|
2180
|
+
className: "inline-flex size-8 shrink-0 items-center justify-center rounded-og-md text-og-fg-muted hover:bg-og-surface-2 pointer-coarse:size-11",
|
|
2181
|
+
onClick: () => onOpenChange(!open),
|
|
2182
|
+
"aria-label": open ? messages.hidePauseDetails : messages.showPauseDetails,
|
|
2183
|
+
children: /* @__PURE__ */ jsx3(ChevronDownIcon2, { className: cn("size-3.5 transition-transform", open && "rotate-180") })
|
|
2184
|
+
}
|
|
2185
|
+
)
|
|
2186
|
+
] }),
|
|
2187
|
+
open ? /* @__PURE__ */ jsxs3("div", { className: "border-t border-og-status-waiting/20 px-3 pb-3 pt-2", children: [
|
|
2188
|
+
/* @__PURE__ */ jsx3("ul", { className: "grid gap-2", "aria-label": messages.pauseReasonsLabel, children: control.blockers.map((entry, index) => /* @__PURE__ */ jsxs3(
|
|
2189
|
+
"li",
|
|
2190
|
+
{
|
|
2191
|
+
className: "text-og-xs",
|
|
2192
|
+
children: [
|
|
2193
|
+
/* @__PURE__ */ jsx3("span", { className: "font-medium text-og-fg", children: index === 0 ? messages.pausedByLabel : messages.alsoPausedByLabel }),
|
|
2194
|
+
blockerHref(entry, controlLinks) ? /* @__PURE__ */ jsx3(
|
|
2195
|
+
"a",
|
|
2196
|
+
{
|
|
2197
|
+
href: blockerHref(entry, controlLinks),
|
|
2198
|
+
className: "font-medium text-og-fg underline decoration-og-border-strong underline-offset-2 hover:text-og-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-ring/40",
|
|
2199
|
+
children: entry.displayName
|
|
2200
|
+
}
|
|
2201
|
+
) : /* @__PURE__ */ jsx3("span", { className: "font-medium text-og-fg", children: entry.displayName }),
|
|
2202
|
+
entry.reason ? /* @__PURE__ */ jsxs3("span", { className: "text-og-fg-muted", children: [
|
|
2203
|
+
" \xB7 ",
|
|
2204
|
+
entry.reason
|
|
2205
|
+
] }) : null,
|
|
2206
|
+
entry.actor ? /* @__PURE__ */ jsxs3("span", { className: "text-og-fg-subtle", children: [
|
|
2207
|
+
" \xB7 ",
|
|
2208
|
+
entry.actor
|
|
2209
|
+
] }) : null,
|
|
2210
|
+
entry.changedAt ? /* @__PURE__ */ jsxs3("span", { className: "text-og-fg-subtle", children: [
|
|
2211
|
+
" ",
|
|
2212
|
+
"\xB7 ",
|
|
2213
|
+
messages.formatRelativeTime(entry.changedAt)
|
|
2214
|
+
] }) : null
|
|
2215
|
+
]
|
|
2216
|
+
},
|
|
2217
|
+
`${entry.kind}-${entry.sessionId ?? "workspace"}-${entry.revision}`
|
|
2218
|
+
)) }),
|
|
2219
|
+
broaderOptions.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "mt-2 flex flex-wrap gap-1.5", children: broaderOptions.map((option) => /* @__PURE__ */ jsxs3(
|
|
2220
|
+
"button",
|
|
2221
|
+
{
|
|
2222
|
+
type: "button",
|
|
2223
|
+
disabled: busy,
|
|
2224
|
+
title: option.impactCopy,
|
|
2225
|
+
onClick: () => onResumeOption(option),
|
|
2226
|
+
className: "rounded-og-md border border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg pointer-coarse:min-h-10",
|
|
2227
|
+
children: [
|
|
2228
|
+
/* @__PURE__ */ jsx3("span", { className: "block font-medium", children: option.scope === "workspace" ? messages.resumeWorkspace : messages.resumeFromSession }),
|
|
2229
|
+
/* @__PURE__ */ jsx3("span", { className: "block text-[10px] text-og-fg-subtle", children: option.selectedStateAfter === "active" ? messages.sessionCanRun : messages.stillPausedBy(
|
|
2230
|
+
option.remainingPrimaryBlocker?.displayName ?? messages.narrowerPause
|
|
2231
|
+
) })
|
|
2232
|
+
]
|
|
2233
|
+
},
|
|
2234
|
+
`${option.scope}-${option.targetId ?? "selected"}`
|
|
2235
|
+
)) }) : null
|
|
2236
|
+
] }) : null
|
|
2237
|
+
] });
|
|
2115
2238
|
}
|
|
2116
|
-
function
|
|
2117
|
-
if (
|
|
2118
|
-
|
|
2119
|
-
if (normalized !== "unknown") return normalized;
|
|
2120
|
-
}
|
|
2121
|
-
if (typeof DOMException !== "undefined" && error instanceof DOMException) {
|
|
2122
|
-
if (error.name === "NotAllowedError") return "permission_denied";
|
|
2123
|
-
if (error.name === "NotSupportedError") return "not_supported";
|
|
2124
|
-
if (error.name === "TimeoutError") return "timeout";
|
|
2125
|
-
if (error.name === "AbortError") return "cancelled";
|
|
2126
|
-
}
|
|
2127
|
-
return "unknown";
|
|
2239
|
+
function blockerHref(blocker, links) {
|
|
2240
|
+
if (blocker.kind === "workspace") return links?.workspaceHref;
|
|
2241
|
+
return blocker.sessionId ? links?.sessionHref?.(blocker.sessionId) : void 0;
|
|
2128
2242
|
}
|
|
2129
|
-
function
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2243
|
+
function RestoredResourceChips({
|
|
2244
|
+
resources,
|
|
2245
|
+
messages,
|
|
2246
|
+
onRemove
|
|
2247
|
+
}) {
|
|
2248
|
+
return /* @__PURE__ */ jsx3(
|
|
2249
|
+
"div",
|
|
2250
|
+
{
|
|
2251
|
+
className: "flex flex-wrap gap-1.5 border-b border-og-border px-3 py-2",
|
|
2252
|
+
"aria-label": messages.restoredResourcesLabel,
|
|
2253
|
+
children: resources.map((resource, index) => /* @__PURE__ */ jsxs3(
|
|
2254
|
+
"span",
|
|
2255
|
+
{
|
|
2256
|
+
className: "inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-og-md border border-og-border bg-og-surface-2 px-2 py-1 text-og-xs text-og-fg-muted",
|
|
2257
|
+
children: [
|
|
2258
|
+
/* @__PURE__ */ jsx3(FileIcon, { className: "size-3 shrink-0" }),
|
|
2259
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: resource.kind === "file" ? messages.restoredFile(resource.fileId) : resource.uri }),
|
|
2260
|
+
/* @__PURE__ */ jsx3(
|
|
2261
|
+
"button",
|
|
2262
|
+
{
|
|
2263
|
+
type: "button",
|
|
2264
|
+
className: "shrink-0 hover:text-og-fg",
|
|
2265
|
+
onClick: () => onRemove(index),
|
|
2266
|
+
"aria-label": messages.removeRestoredResource(index),
|
|
2267
|
+
children: /* @__PURE__ */ jsx3(XIcon, { className: "size-3" })
|
|
2268
|
+
}
|
|
2269
|
+
)
|
|
2270
|
+
]
|
|
2271
|
+
},
|
|
2272
|
+
`${resource.kind}-${resource.kind === "file" ? resource.fileId : resource.uri}`
|
|
2273
|
+
))
|
|
2274
|
+
}
|
|
2275
|
+
);
|
|
2143
2276
|
}
|
|
2144
|
-
function
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2277
|
+
function ConfirmBar({
|
|
2278
|
+
command,
|
|
2279
|
+
messages,
|
|
2280
|
+
onCancel,
|
|
2281
|
+
onConfirm,
|
|
2282
|
+
returnFocusRef
|
|
2283
|
+
}) {
|
|
2284
|
+
const confirmRef = useRef4(null);
|
|
2285
|
+
const descriptionId = useId2();
|
|
2286
|
+
useEffect3(() => {
|
|
2287
|
+
const returnTo = returnFocusRef?.current ?? null;
|
|
2288
|
+
confirmRef.current?.focus();
|
|
2289
|
+
return () => returnTo?.focus();
|
|
2290
|
+
}, [returnFocusRef]);
|
|
2291
|
+
return /* @__PURE__ */ jsxs3(
|
|
2292
|
+
"div",
|
|
2293
|
+
{
|
|
2294
|
+
role: "alertdialog",
|
|
2295
|
+
"aria-label": messages.confirmCommand(command.name),
|
|
2296
|
+
"aria-describedby": descriptionId,
|
|
2297
|
+
"data-testid": "danger-confirm",
|
|
2298
|
+
onKeyDown: (event) => {
|
|
2299
|
+
if (event.key === "Escape") {
|
|
2300
|
+
event.preventDefault();
|
|
2301
|
+
event.stopPropagation();
|
|
2302
|
+
onCancel();
|
|
2303
|
+
}
|
|
2304
|
+
},
|
|
2305
|
+
className: "flex items-end justify-between gap-2 px-2.5 pb-2.5 pt-1",
|
|
2306
|
+
children: [
|
|
2307
|
+
/* @__PURE__ */ jsx3("span", { id: descriptionId, className: "min-w-0 flex-1 px-1.5 text-og-sm text-og-status-failed", children: messages.confirmDescription(command) }),
|
|
2308
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex shrink-0 items-center gap-1.5", children: [
|
|
2309
|
+
/* @__PURE__ */ jsx3(
|
|
2310
|
+
"button",
|
|
2311
|
+
{
|
|
2312
|
+
type: "button",
|
|
2313
|
+
onClick: onCancel,
|
|
2314
|
+
className: "rounded-og-md border border-og-border bg-og-surface-2 px-2.5 py-1 text-og-sm text-og-fg-muted hover:bg-og-surface-3 pointer-coarse:min-h-11",
|
|
2315
|
+
children: messages.cancel
|
|
2316
|
+
}
|
|
2317
|
+
),
|
|
2318
|
+
/* @__PURE__ */ jsx3(
|
|
2319
|
+
"button",
|
|
2320
|
+
{
|
|
2321
|
+
ref: confirmRef,
|
|
2322
|
+
type: "button",
|
|
2323
|
+
onClick: onConfirm,
|
|
2324
|
+
className: "rounded-og-md border border-og-status-failed/50 bg-og-status-failed/15 px-2.5 py-1 text-og-sm text-og-status-failed hover:bg-og-status-failed/25 pointer-coarse:min-h-11",
|
|
2325
|
+
children: messages.runCommand(command.name)
|
|
2326
|
+
}
|
|
2327
|
+
)
|
|
2328
|
+
] })
|
|
2329
|
+
]
|
|
2330
|
+
}
|
|
2331
|
+
);
|
|
2157
2332
|
}
|
|
2158
|
-
function
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2333
|
+
function AttachmentChips({
|
|
2334
|
+
attachments,
|
|
2335
|
+
messages,
|
|
2336
|
+
onRemove,
|
|
2337
|
+
onRetry
|
|
2338
|
+
}) {
|
|
2339
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap gap-2 border-b border-og-border px-3 py-2", children: attachments.map((attachment) => {
|
|
2340
|
+
const failed = attachment.status === "failed";
|
|
2341
|
+
const statusText = attachment.status === "uploading" ? messages.uploading : failed ? attachment.error || messages.uploadFailed : messages.formatBytes(attachment.sizeBytes);
|
|
2342
|
+
return /* @__PURE__ */ jsxs3(
|
|
2343
|
+
"div",
|
|
2344
|
+
{
|
|
2345
|
+
className: cn(
|
|
2346
|
+
"flex min-w-0 max-w-[240px] items-center gap-2 rounded-og-md border px-2 py-1.5 text-og-sm",
|
|
2347
|
+
failed ? "border-og-status-failed/40 bg-og-status-failed/10" : "border-og-border bg-og-surface-2"
|
|
2348
|
+
),
|
|
2349
|
+
children: [
|
|
2350
|
+
attachment.previewUrl ? /* @__PURE__ */ jsx3(
|
|
2351
|
+
"img",
|
|
2352
|
+
{
|
|
2353
|
+
src: attachment.previewUrl,
|
|
2354
|
+
alt: "",
|
|
2355
|
+
className: "size-8 shrink-0 rounded object-cover"
|
|
2356
|
+
}
|
|
2357
|
+
) : attachment.contentType.startsWith("image/") ? /* @__PURE__ */ jsx3(ImageIcon, { className: "size-4 shrink-0 text-og-fg-muted" }) : /* @__PURE__ */ jsx3(FileIcon, { className: "size-4 shrink-0 text-og-fg-muted" }),
|
|
2358
|
+
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
|
|
2359
|
+
/* @__PURE__ */ jsx3("div", { className: "truncate font-medium text-og-fg", children: attachment.name }),
|
|
2360
|
+
/* @__PURE__ */ jsx3(
|
|
2361
|
+
"div",
|
|
2362
|
+
{
|
|
2363
|
+
className: cn(
|
|
2364
|
+
"truncate text-og-xs",
|
|
2365
|
+
failed ? "text-og-status-failed" : "text-og-fg-subtle"
|
|
2366
|
+
),
|
|
2367
|
+
title: failed ? statusText : void 0,
|
|
2368
|
+
children: statusText
|
|
2369
|
+
}
|
|
2370
|
+
)
|
|
2371
|
+
] }),
|
|
2372
|
+
attachment.status === "uploading" ? /* @__PURE__ */ jsx3(LoaderCircleIcon, { className: "size-3.5 shrink-0 animate-og-spin" }) : null,
|
|
2373
|
+
failed && onRetry ? /* @__PURE__ */ jsx3(
|
|
2374
|
+
"button",
|
|
2375
|
+
{
|
|
2376
|
+
type: "button",
|
|
2377
|
+
onClick: () => onRetry(attachment.id),
|
|
2378
|
+
className: "shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-10",
|
|
2379
|
+
"aria-label": messages.retryAttachment(attachment.name),
|
|
2380
|
+
title: messages.retryUpload,
|
|
2381
|
+
children: /* @__PURE__ */ jsx3(RotateCwIcon, { className: "size-3.5" })
|
|
2382
|
+
}
|
|
2383
|
+
) : null,
|
|
2384
|
+
/* @__PURE__ */ jsx3(
|
|
2385
|
+
"button",
|
|
2386
|
+
{
|
|
2387
|
+
type: "button",
|
|
2388
|
+
onClick: () => onRemove(attachment.id),
|
|
2389
|
+
className: "shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-10",
|
|
2390
|
+
"aria-label": messages.removeAttachment(attachment.name),
|
|
2391
|
+
children: /* @__PURE__ */ jsx3(XIcon, { className: "size-3.5" })
|
|
2392
|
+
}
|
|
2393
|
+
)
|
|
2394
|
+
]
|
|
2395
|
+
},
|
|
2396
|
+
attachment.id
|
|
2165
2397
|
);
|
|
2166
|
-
}
|
|
2167
|
-
void runBoundedCleanup(
|
|
2168
|
-
"close",
|
|
2169
|
-
() => session.close(),
|
|
2170
|
-
options.cleanupTimeoutMs,
|
|
2171
|
-
options.reportDiagnostic
|
|
2172
|
-
);
|
|
2398
|
+
}) });
|
|
2173
2399
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2400
|
+
function HelpPanel({
|
|
2401
|
+
commands,
|
|
2402
|
+
messages,
|
|
2403
|
+
onClose
|
|
2404
|
+
}) {
|
|
2405
|
+
return /* @__PURE__ */ jsxs3(
|
|
2406
|
+
motion2.div,
|
|
2407
|
+
{
|
|
2408
|
+
initial: { opacity: 0, height: 0 },
|
|
2409
|
+
animate: { opacity: 1, height: "auto" },
|
|
2410
|
+
exit: { opacity: 0, height: 0 },
|
|
2411
|
+
className: "mt-2 overflow-hidden rounded-og-lg border border-og-border bg-og-surface-2",
|
|
2412
|
+
children: [
|
|
2413
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between border-b border-og-border px-3 py-1.5", children: [
|
|
2414
|
+
/* @__PURE__ */ jsx3("span", { className: "text-og-sm font-medium text-og-fg", children: messages.commands }),
|
|
2415
|
+
/* @__PURE__ */ jsx3(
|
|
2416
|
+
"button",
|
|
2417
|
+
{
|
|
2418
|
+
type: "button",
|
|
2419
|
+
onClick: onClose,
|
|
2420
|
+
className: "text-og-xs text-og-fg-subtle hover:text-og-fg",
|
|
2421
|
+
children: messages.close
|
|
2422
|
+
}
|
|
2423
|
+
)
|
|
2424
|
+
] }),
|
|
2425
|
+
/* @__PURE__ */ jsx3("ul", { className: "py-1", children: commands.map((command) => {
|
|
2426
|
+
const hint = argHint(command.args);
|
|
2427
|
+
return /* @__PURE__ */ jsxs3("li", { className: "flex items-baseline gap-2 px-3 py-1", children: [
|
|
2428
|
+
/* @__PURE__ */ jsxs3("span", { className: "font-mono text-og-sm text-og-accent", children: [
|
|
2429
|
+
"/",
|
|
2430
|
+
command.name,
|
|
2431
|
+
hint ? /* @__PURE__ */ jsx3("span", { className: "ml-1 text-og-fg-subtle", children: hint }) : null
|
|
2432
|
+
] }),
|
|
2433
|
+
/* @__PURE__ */ jsx3("span", { className: "text-og-sm text-og-fg-muted", children: command.description }),
|
|
2434
|
+
command.danger ? /* @__PURE__ */ jsx3("span", { className: "ml-auto rounded-og-xs bg-og-status-failed/15 px-1 text-og-xs uppercase tracking-wide text-og-status-failed", children: messages.danger }) : null
|
|
2435
|
+
] }, command.name);
|
|
2436
|
+
}) })
|
|
2437
|
+
]
|
|
2438
|
+
}
|
|
2179
2439
|
);
|
|
2180
|
-
const timeout = new Promise((resolve) => {
|
|
2181
|
-
timeoutId = setTimeout(() => resolve({ kind: "timeout" }), timeoutMs);
|
|
2182
|
-
});
|
|
2183
|
-
const outcome = await Promise.race([result, timeout]);
|
|
2184
|
-
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
2185
|
-
if (outcome.kind === "settled") return;
|
|
2186
|
-
reportDiagnostic({
|
|
2187
|
-
operation,
|
|
2188
|
-
code: outcome.kind === "timeout" ? "timeout" : "provider",
|
|
2189
|
-
detail: outcome.kind === "timeout" ? `Transcription ${operation} exceeded ${timeoutMs}ms.` : diagnosticDetail(outcome.error)
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
function diagnosticDetail(error) {
|
|
2193
|
-
if (error instanceof Error) return error.message;
|
|
2194
|
-
return typeof error === "string" ? error : "Unknown transcription adapter failure.";
|
|
2195
|
-
}
|
|
2196
|
-
function normalizeTimeout(value, fallback) {
|
|
2197
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? Math.min(Math.floor(value), 6e4) : fallback;
|
|
2198
|
-
}
|
|
2199
|
-
function isRecord2(value) {
|
|
2200
|
-
return typeof value === "object" && value !== null;
|
|
2201
|
-
}
|
|
2202
|
-
function transcriptionPolicyRevision(policy, selection) {
|
|
2203
|
-
const targetKey = (target) => target ? [
|
|
2204
|
-
target.provider,
|
|
2205
|
-
target.model ?? "",
|
|
2206
|
-
target.credentialMode,
|
|
2207
|
-
target.credentialConnectionId ?? "",
|
|
2208
|
-
target.region ?? ""
|
|
2209
|
-
].join("\0") : "";
|
|
2210
|
-
return [
|
|
2211
|
-
policy.enabled ? "1" : "0",
|
|
2212
|
-
policy.acceptanceId ?? "",
|
|
2213
|
-
targetKey(policy.primary),
|
|
2214
|
-
policy.language ?? "",
|
|
2215
|
-
policy.autoDetectLanguage ? "1" : "0",
|
|
2216
|
-
policy.diarization.enabled ? "1" : "0",
|
|
2217
|
-
policy.diarization.maxSpeakers?.toString() ?? "",
|
|
2218
|
-
policy.retention.mode,
|
|
2219
|
-
policy.retention.maxDays?.toString() ?? "",
|
|
2220
|
-
policy.privacy.allowProviderLogging ? "1" : "0",
|
|
2221
|
-
policy.privacy.allowProviderTraining ? "1" : "0",
|
|
2222
|
-
policy.fallback.mode,
|
|
2223
|
-
policy.fallback.targets.map(targetKey).join(""),
|
|
2224
|
-
policy.cost.currency,
|
|
2225
|
-
policy.cost.maxPerHour?.toString() ?? "",
|
|
2226
|
-
policy.cost.maxPerMonth?.toString() ?? "",
|
|
2227
|
-
selection.kind,
|
|
2228
|
-
selection.kind === "fallback" ? selection.index.toString() : ""
|
|
2229
|
-
].join("");
|
|
2230
2440
|
}
|
|
2231
2441
|
|
|
2232
2442
|
// src/components/composer-transcription-control.tsx
|
|
2233
|
-
import { LoaderCircleIcon as LoaderCircleIcon2, MicIcon,
|
|
2234
|
-
import {
|
|
2443
|
+
import { LoaderCircleIcon as LoaderCircleIcon2, MicIcon, SquareIcon, XIcon as XIcon2 } from "lucide-react";
|
|
2444
|
+
import { AnimatePresence as AnimatePresence3, motion as motion3 } from "motion/react";
|
|
2445
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
2446
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2235
2447
|
var defaultMessages = {
|
|
2236
2448
|
start: "Start voice input",
|
|
2237
|
-
stop: "Stop
|
|
2449
|
+
stop: "Stop and transcribe",
|
|
2450
|
+
cancel: "Cancel recording",
|
|
2238
2451
|
retry: "Retry voice input",
|
|
2239
|
-
requestingPermission: "Requesting microphone
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
cancelling: "Cancelling voice input\u2026",
|
|
2452
|
+
requestingPermission: "Requesting microphone\u2026",
|
|
2453
|
+
recording: "Recording. Press Escape to cancel.",
|
|
2454
|
+
transcribing: "Transcribing\u2026",
|
|
2243
2455
|
unavailableDisabled: "Voice input is unavailable while the composer is disabled.",
|
|
2244
|
-
|
|
2245
|
-
unavailablePolicy: "Enable and accept transcription in Workspace settings.",
|
|
2456
|
+
unavailable: "Voice input is unavailable for this workspace.",
|
|
2246
2457
|
errorPermissionDenied: "Microphone permission was denied. Your draft was not changed.",
|
|
2247
2458
|
errorNotSupported: "Voice input is not supported on this device.",
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
errorTimeout: "Voice input took too long to start. Try again.",
|
|
2252
|
-
errorCancelled: "Voice input was cancelled.",
|
|
2459
|
+
errorUnavailable: "Voice input is not configured.",
|
|
2460
|
+
errorTooLarge: "Recording is too large. Try a shorter message.",
|
|
2461
|
+
errorInvalidAudio: "The recording could not be read. Try again.",
|
|
2253
2462
|
errorUnknown: "Voice input could not start. Try again."
|
|
2254
2463
|
};
|
|
2464
|
+
var WAVEFORM_BARS = 18;
|
|
2465
|
+
var WAVEFORM_BAR_KEYS = Array.from(
|
|
2466
|
+
{ length: WAVEFORM_BARS },
|
|
2467
|
+
(_, index) => `voice-waveform-bar-${index + 1}`
|
|
2468
|
+
);
|
|
2255
2469
|
function ComposerTranscriptionControl({
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2470
|
+
client = null,
|
|
2471
|
+
workspaceId = "",
|
|
2472
|
+
capability = null,
|
|
2473
|
+
workspaceEnabled = false,
|
|
2259
2474
|
messages: overrides,
|
|
2260
|
-
className
|
|
2261
|
-
lifecycleTimeouts,
|
|
2262
|
-
onDiagnostic
|
|
2475
|
+
className
|
|
2263
2476
|
}) {
|
|
2264
2477
|
const composer = useChatComposer();
|
|
2265
2478
|
const messages = { ...defaultMessages, ...overrides };
|
|
2266
|
-
const transcription =
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2479
|
+
const transcription = useVoiceInput({
|
|
2480
|
+
client,
|
|
2481
|
+
workspaceId,
|
|
2482
|
+
capability,
|
|
2483
|
+
enabled: workspaceEnabled,
|
|
2270
2484
|
value: composer.value,
|
|
2271
2485
|
setValue: composer.setValue,
|
|
2272
2486
|
focusInput: composer.focusInput,
|
|
2273
|
-
disabled: composer.disabled
|
|
2274
|
-
lifecycleTimeouts,
|
|
2275
|
-
onDiagnostic
|
|
2487
|
+
disabled: composer.disabled
|
|
2276
2488
|
});
|
|
2277
|
-
const status = transcription
|
|
2278
|
-
const active = status === "requesting-permission" || status === "
|
|
2279
|
-
const unavailableMessage =
|
|
2280
|
-
const
|
|
2281
|
-
const errorMessage3 = transcription.
|
|
2282
|
-
const announcement =
|
|
2283
|
-
function
|
|
2284
|
-
if (unavailableMessage
|
|
2489
|
+
const { status } = transcription;
|
|
2490
|
+
const active = status === "requesting-permission" || status === "recording" || status === "transcribing";
|
|
2491
|
+
const unavailableMessage = composer.disabled ? messages.unavailableDisabled : !capability?.available || !workspaceEnabled ? messages.unavailable : null;
|
|
2492
|
+
const idleLabel = unavailableMessage ?? (status === "error" ? messages.retry : messages.start);
|
|
2493
|
+
const errorMessage3 = transcription.error ? transcriptionErrorMessage(transcription.error, messages) : null;
|
|
2494
|
+
const announcement = status === "requesting-permission" ? messages.requestingPermission : status === "recording" ? messages.recording : status === "transcribing" ? messages.transcribing : status === "error" ? errorMessage3 ?? messages.errorUnknown : unavailableMessage;
|
|
2495
|
+
function start(event) {
|
|
2496
|
+
if (unavailableMessage) {
|
|
2285
2497
|
event.preventDefault();
|
|
2286
2498
|
return;
|
|
2287
2499
|
}
|
|
2288
|
-
|
|
2289
|
-
else void transcription.start();
|
|
2500
|
+
void transcription.start();
|
|
2290
2501
|
}
|
|
2291
2502
|
return /* @__PURE__ */ jsxs4(
|
|
2292
2503
|
"span",
|
|
@@ -2294,35 +2505,105 @@ function ComposerTranscriptionControl({
|
|
|
2294
2505
|
className: cn("inline-flex min-w-0 items-center gap-1.5", className),
|
|
2295
2506
|
"data-transcription-status": status,
|
|
2296
2507
|
children: [
|
|
2297
|
-
/* @__PURE__ */ jsx4(
|
|
2298
|
-
|
|
2508
|
+
/* @__PURE__ */ jsx4(AnimatePresence3, { mode: "popLayout", initial: false, children: active ? /* @__PURE__ */ jsxs4(
|
|
2509
|
+
motion3.span,
|
|
2510
|
+
{
|
|
2511
|
+
initial: { opacity: 0, scale: 0.96 },
|
|
2512
|
+
animate: { opacity: 1, scale: 1 },
|
|
2513
|
+
exit: { opacity: 0, scale: 0.96 },
|
|
2514
|
+
transition: { duration: 0.16, ease: [0.22, 1, 0.36, 1] },
|
|
2515
|
+
className: cn(
|
|
2516
|
+
"inline-flex h-8 items-center gap-1 rounded-og-md border border-og-border/80",
|
|
2517
|
+
"bg-og-surface-2/70 pl-2 pr-1 pointer-coarse:h-11"
|
|
2518
|
+
),
|
|
2519
|
+
children: [
|
|
2520
|
+
status === "requesting-permission" ? /* @__PURE__ */ jsx4(
|
|
2521
|
+
LoaderCircleIcon2,
|
|
2522
|
+
{
|
|
2523
|
+
className: "size-3.5 shrink-0 text-og-fg-muted animate-og-spin motion-reduce:animate-none",
|
|
2524
|
+
"aria-hidden": true
|
|
2525
|
+
}
|
|
2526
|
+
) : /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1.5", children: [
|
|
2527
|
+
status === "recording" ? /* @__PURE__ */ jsx4(
|
|
2528
|
+
"span",
|
|
2529
|
+
{
|
|
2530
|
+
"aria-hidden": true,
|
|
2531
|
+
className: "size-1.5 shrink-0 rounded-full bg-og-status-failed animate-og-pulse motion-reduce:animate-none"
|
|
2532
|
+
}
|
|
2533
|
+
) : null,
|
|
2534
|
+
/* @__PURE__ */ jsx4(
|
|
2535
|
+
VoiceWaveform,
|
|
2536
|
+
{
|
|
2537
|
+
stream: status === "recording" ? transcription.stream : null,
|
|
2538
|
+
mode: status === "transcribing" ? "transcribing" : "recording"
|
|
2539
|
+
}
|
|
2540
|
+
)
|
|
2541
|
+
] }),
|
|
2542
|
+
status === "recording" ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2543
|
+
/* @__PURE__ */ jsx4(
|
|
2544
|
+
"button",
|
|
2545
|
+
{
|
|
2546
|
+
type: "button",
|
|
2547
|
+
onClick: () => transcription.cancel(),
|
|
2548
|
+
"aria-label": messages.cancel,
|
|
2549
|
+
"aria-keyshortcuts": "Escape",
|
|
2550
|
+
title: messages.cancel,
|
|
2551
|
+
className: cn(
|
|
2552
|
+
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
2553
|
+
"text-og-fg-muted transition-colors duration-150 motion-reduce:transition-none",
|
|
2554
|
+
"hover:bg-og-surface-3 hover:text-og-fg pointer-coarse:size-9"
|
|
2555
|
+
),
|
|
2556
|
+
children: /* @__PURE__ */ jsx4(XIcon2, { className: "size-3.5" })
|
|
2557
|
+
}
|
|
2558
|
+
),
|
|
2559
|
+
/* @__PURE__ */ jsx4(
|
|
2560
|
+
"button",
|
|
2561
|
+
{
|
|
2562
|
+
type: "button",
|
|
2563
|
+
onClick: () => transcription.stop(),
|
|
2564
|
+
"aria-label": messages.stop,
|
|
2565
|
+
title: messages.stop,
|
|
2566
|
+
className: cn(
|
|
2567
|
+
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
2568
|
+
"bg-og-fg text-og-bg transition-colors duration-150 motion-reduce:transition-none",
|
|
2569
|
+
"hover:bg-og-fg-muted pointer-coarse:size-9"
|
|
2570
|
+
),
|
|
2571
|
+
children: /* @__PURE__ */ jsx4(SquareIcon, { className: "size-2.5 fill-current" })
|
|
2572
|
+
}
|
|
2573
|
+
)
|
|
2574
|
+
] }) : status === "transcribing" ? /* @__PURE__ */ jsx4("span", { className: "og-shimmer-text px-1.5 text-og-xs font-medium whitespace-nowrap", children: messages.transcribing }) : /* @__PURE__ */ jsx4("span", { className: "px-1.5 text-og-xs text-og-fg-muted whitespace-nowrap", children: messages.requestingPermission })
|
|
2575
|
+
]
|
|
2576
|
+
},
|
|
2577
|
+
status === "transcribing" ? "transcribing" : "capture"
|
|
2578
|
+
) : /* @__PURE__ */ jsx4(
|
|
2579
|
+
motion3.button,
|
|
2299
2580
|
{
|
|
2300
2581
|
type: "button",
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2582
|
+
initial: { opacity: 0, scale: 0.96 },
|
|
2583
|
+
animate: { opacity: 1, scale: 1 },
|
|
2584
|
+
exit: { opacity: 0, scale: 0.96 },
|
|
2585
|
+
transition: { duration: 0.14, ease: [0.22, 1, 0.36, 1] },
|
|
2586
|
+
onClick: start,
|
|
2587
|
+
"aria-label": idleLabel,
|
|
2588
|
+
"aria-pressed": false,
|
|
2589
|
+
"aria-disabled": unavailableMessage !== null,
|
|
2590
|
+
title: idleLabel,
|
|
2307
2591
|
className: cn(
|
|
2308
2592
|
"inline-flex size-8 shrink-0 items-center justify-center rounded-og-md pointer-coarse:size-11",
|
|
2309
2593
|
"text-og-fg-muted transition-colors duration-150 motion-reduce:transition-none",
|
|
2310
|
-
unavailableMessage ? "cursor-not-allowed opacity-45" :
|
|
2311
|
-
status === "listening" && "bg-og-status-failed/10 text-og-status-failed"
|
|
2594
|
+
unavailableMessage ? "cursor-not-allowed opacity-45" : "hover:bg-og-surface-2 hover:text-og-fg"
|
|
2312
2595
|
),
|
|
2313
|
-
children:
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
|
|
2596
|
+
children: /* @__PURE__ */ jsx4(MicIcon, { className: "size-4" })
|
|
2597
|
+
},
|
|
2598
|
+
"idle"
|
|
2599
|
+
) }),
|
|
2600
|
+
status === "error" && errorMessage3 ? /* @__PURE__ */ jsx4(
|
|
2317
2601
|
"span",
|
|
2318
2602
|
{
|
|
2319
2603
|
"aria-hidden": "true",
|
|
2320
|
-
title:
|
|
2321
|
-
className:
|
|
2322
|
-
|
|
2323
|
-
status === "error" && "text-og-status-failed"
|
|
2324
|
-
),
|
|
2325
|
-
children: announcement
|
|
2604
|
+
title: errorMessage3,
|
|
2605
|
+
className: "max-w-40 truncate text-og-xs text-og-status-failed max-sm:max-w-24",
|
|
2606
|
+
children: errorMessage3
|
|
2326
2607
|
}
|
|
2327
2608
|
) : null,
|
|
2328
2609
|
/* @__PURE__ */ jsx4("span", { className: "sr-only", role: status === "error" ? "alert" : "status", "aria-live": "polite", children: announcement })
|
|
@@ -2330,28 +2611,116 @@ function ComposerTranscriptionControl({
|
|
|
2330
2611
|
}
|
|
2331
2612
|
);
|
|
2332
2613
|
}
|
|
2614
|
+
function VoiceWaveform({
|
|
2615
|
+
stream,
|
|
2616
|
+
mode
|
|
2617
|
+
}) {
|
|
2618
|
+
const [levels, setLevels] = useState5(() => restingLevels(WAVEFORM_BARS));
|
|
2619
|
+
const [live, setLive] = useState5(false);
|
|
2620
|
+
useEffect4(() => {
|
|
2621
|
+
if (!stream || mode !== "recording") {
|
|
2622
|
+
setLive(false);
|
|
2623
|
+
setLevels(restingLevels(WAVEFORM_BARS));
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
const AudioCtx = window.AudioContext ?? window.webkitAudioContext;
|
|
2627
|
+
if (!AudioCtx) return;
|
|
2628
|
+
let cancelled = false;
|
|
2629
|
+
let frame = 0;
|
|
2630
|
+
let context = null;
|
|
2631
|
+
try {
|
|
2632
|
+
context = new AudioCtx();
|
|
2633
|
+
const source = context.createMediaStreamSource(stream);
|
|
2634
|
+
const analyser = context.createAnalyser();
|
|
2635
|
+
analyser.fftSize = 64;
|
|
2636
|
+
analyser.smoothingTimeConstant = 0.8;
|
|
2637
|
+
source.connect(analyser);
|
|
2638
|
+
const bins = new Uint8Array(analyser.frequencyBinCount);
|
|
2639
|
+
const tick = () => {
|
|
2640
|
+
if (cancelled) return;
|
|
2641
|
+
analyser.getByteFrequencyData(bins);
|
|
2642
|
+
const next = new Array(WAVEFORM_BARS);
|
|
2643
|
+
const usable = Math.max(8, Math.floor(bins.length * 0.65));
|
|
2644
|
+
for (let i = 0; i < WAVEFORM_BARS; i++) {
|
|
2645
|
+
const start = Math.floor(i / WAVEFORM_BARS * usable);
|
|
2646
|
+
const end = Math.max(start + 1, Math.floor((i + 1) / WAVEFORM_BARS * usable));
|
|
2647
|
+
let sum = 0;
|
|
2648
|
+
for (let j = start; j < end; j++) sum += bins[j] ?? 0;
|
|
2649
|
+
const avg = sum / (end - start) / 255;
|
|
2650
|
+
next[i] = Math.min(1, 0.14 + avg * 1.65);
|
|
2651
|
+
}
|
|
2652
|
+
setLevels(next);
|
|
2653
|
+
setLive(true);
|
|
2654
|
+
frame = requestAnimationFrame(tick);
|
|
2655
|
+
};
|
|
2656
|
+
void context.resume().then(() => {
|
|
2657
|
+
if (!cancelled) frame = requestAnimationFrame(tick);
|
|
2658
|
+
});
|
|
2659
|
+
} catch {
|
|
2660
|
+
setLive(false);
|
|
2661
|
+
}
|
|
2662
|
+
return () => {
|
|
2663
|
+
cancelled = true;
|
|
2664
|
+
cancelAnimationFrame(frame);
|
|
2665
|
+
void context?.close();
|
|
2666
|
+
};
|
|
2667
|
+
}, [stream, mode]);
|
|
2668
|
+
return /* @__PURE__ */ jsx4(
|
|
2669
|
+
"span",
|
|
2670
|
+
{
|
|
2671
|
+
"aria-hidden": true,
|
|
2672
|
+
"data-voice-waveform": live ? "live" : "fallback",
|
|
2673
|
+
className: "flex h-4 w-[3.75rem] items-center justify-center gap-px",
|
|
2674
|
+
children: WAVEFORM_BAR_KEYS.map((key, index) => {
|
|
2675
|
+
const level = levels[index] ?? 0;
|
|
2676
|
+
return /* @__PURE__ */ jsx4(
|
|
2677
|
+
"span",
|
|
2678
|
+
{
|
|
2679
|
+
className: cn(
|
|
2680
|
+
"w-0.5 rounded-full bg-og-fg-muted/90 origin-center",
|
|
2681
|
+
!live && "animate-og-waveform motion-reduce:animate-none",
|
|
2682
|
+
mode === "transcribing" && "opacity-45"
|
|
2683
|
+
),
|
|
2684
|
+
style: {
|
|
2685
|
+
height: live ? `${Math.max(3, Math.round(level * 14))}px` : "14px",
|
|
2686
|
+
animationDelay: live ? void 0 : `${index % 9 * 0.07}s`,
|
|
2687
|
+
opacity: live ? 0.55 + level * 0.45 : void 0
|
|
2688
|
+
}
|
|
2689
|
+
},
|
|
2690
|
+
key
|
|
2691
|
+
);
|
|
2692
|
+
})
|
|
2693
|
+
}
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
function restingLevels(count) {
|
|
2697
|
+
return Array.from({ length: count }, (_, index) => 0.22 + index * 17 % 5 * 0.06);
|
|
2698
|
+
}
|
|
2333
2699
|
function transcriptionErrorMessage(code, messages) {
|
|
2334
2700
|
switch (code) {
|
|
2335
2701
|
case "permission_denied":
|
|
2336
2702
|
return messages.errorPermissionDenied;
|
|
2337
2703
|
case "not_supported":
|
|
2338
2704
|
return messages.errorNotSupported;
|
|
2339
|
-
case "
|
|
2340
|
-
return messages.
|
|
2341
|
-
case "
|
|
2342
|
-
return messages.
|
|
2343
|
-
case "
|
|
2344
|
-
return messages.
|
|
2345
|
-
case "timeout":
|
|
2346
|
-
return messages.errorTimeout;
|
|
2347
|
-
case "cancelled":
|
|
2348
|
-
return messages.errorCancelled;
|
|
2705
|
+
case "unavailable":
|
|
2706
|
+
return messages.errorUnavailable;
|
|
2707
|
+
case "too_large":
|
|
2708
|
+
return messages.errorTooLarge;
|
|
2709
|
+
case "invalid_audio":
|
|
2710
|
+
return messages.errorInvalidAudio;
|
|
2349
2711
|
case "unknown":
|
|
2350
2712
|
return messages.errorUnknown;
|
|
2713
|
+
default:
|
|
2714
|
+
return messages.errorUnknown;
|
|
2351
2715
|
}
|
|
2352
2716
|
}
|
|
2353
2717
|
|
|
2354
2718
|
export {
|
|
2719
|
+
INITIAL_TRANSCRIPTION_CONTROL_STATE,
|
|
2720
|
+
transitionTranscriptionControl,
|
|
2721
|
+
appendFinalTranscript,
|
|
2722
|
+
useTranscription,
|
|
2723
|
+
useVoiceInput,
|
|
2355
2724
|
parseCommandLine,
|
|
2356
2725
|
hasPermission,
|
|
2357
2726
|
matchCommand,
|
|
@@ -2385,10 +2754,6 @@ export {
|
|
|
2385
2754
|
SendButton,
|
|
2386
2755
|
Help,
|
|
2387
2756
|
Status,
|
|
2388
|
-
INITIAL_TRANSCRIPTION_CONTROL_STATE,
|
|
2389
|
-
transitionTranscriptionControl,
|
|
2390
|
-
appendFinalTranscript,
|
|
2391
|
-
useTranscription,
|
|
2392
2757
|
ComposerTranscriptionControl
|
|
2393
2758
|
};
|
|
2394
|
-
//# sourceMappingURL=chunk-
|
|
2759
|
+
//# sourceMappingURL=chunk-MS3FBZ6A.js.map
|