@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,136 +1,125 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ClientVoiceInputConfig,
|
|
3
|
+
OpenGeniClient,
|
|
2
4
|
TranscriptionAdapter,
|
|
3
|
-
TranscriptionDiagnostic,
|
|
4
|
-
TranscriptionErrorCode,
|
|
5
|
-
TranscriptionTargetSelection,
|
|
6
5
|
WorkspaceTranscriptionPolicy,
|
|
7
6
|
} from "@opengeni/sdk";
|
|
8
|
-
import { LoaderCircleIcon, MicIcon,
|
|
9
|
-
import {
|
|
7
|
+
import { LoaderCircleIcon, MicIcon, SquareIcon, XIcon } from "lucide-react";
|
|
8
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
9
|
+
import { useEffect, useState, type MouseEvent } from "react";
|
|
10
10
|
import { cn } from "../lib/cn";
|
|
11
|
-
import {
|
|
11
|
+
import { useVoiceInput } from "../hooks/use-voice-input";
|
|
12
12
|
import { useChatComposer } from "./composer";
|
|
13
13
|
|
|
14
14
|
export type ComposerTranscriptionMessages = {
|
|
15
15
|
start: string;
|
|
16
16
|
stop: string;
|
|
17
|
+
cancel: string;
|
|
17
18
|
retry: string;
|
|
18
19
|
requestingPermission: string;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
cancelling: string;
|
|
20
|
+
recording: string;
|
|
21
|
+
transcribing: string;
|
|
22
22
|
unavailableDisabled: string;
|
|
23
|
-
|
|
24
|
-
unavailablePolicy: string;
|
|
23
|
+
unavailable: string;
|
|
25
24
|
errorPermissionDenied: string;
|
|
26
25
|
errorNotSupported: string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
errorTimeout: string;
|
|
31
|
-
errorCancelled: string;
|
|
26
|
+
errorUnavailable: string;
|
|
27
|
+
errorTooLarge: string;
|
|
28
|
+
errorInvalidAudio: string;
|
|
32
29
|
errorUnknown: string;
|
|
33
30
|
};
|
|
34
31
|
|
|
35
32
|
const defaultMessages: ComposerTranscriptionMessages = {
|
|
36
33
|
start: "Start voice input",
|
|
37
|
-
stop: "Stop
|
|
34
|
+
stop: "Stop and transcribe",
|
|
35
|
+
cancel: "Cancel recording",
|
|
38
36
|
retry: "Retry voice input",
|
|
39
|
-
requestingPermission: "Requesting microphone
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cancelling: "Cancelling voice input…",
|
|
37
|
+
requestingPermission: "Requesting microphone…",
|
|
38
|
+
recording: "Recording. Press Escape to cancel.",
|
|
39
|
+
transcribing: "Transcribing…",
|
|
43
40
|
unavailableDisabled: "Voice input is unavailable while the composer is disabled.",
|
|
44
|
-
|
|
45
|
-
unavailablePolicy: "Enable and accept transcription in Workspace settings.",
|
|
41
|
+
unavailable: "Voice input is unavailable for this workspace.",
|
|
46
42
|
errorPermissionDenied: "Microphone permission was denied. Your draft was not changed.",
|
|
47
43
|
errorNotSupported: "Voice input is not supported on this device.",
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
errorTimeout: "Voice input took too long to start. Try again.",
|
|
52
|
-
errorCancelled: "Voice input was cancelled.",
|
|
44
|
+
errorUnavailable: "Voice input is not configured.",
|
|
45
|
+
errorTooLarge: "Recording is too large. Try a shorter message.",
|
|
46
|
+
errorInvalidAudio: "The recording could not be read. Try again.",
|
|
53
47
|
errorUnknown: "Voice input could not start. Try again.",
|
|
54
48
|
};
|
|
55
49
|
|
|
56
50
|
export type ComposerTranscriptionControlProps = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
client?: Pick<OpenGeniClient, "transcribeAudio"> | null | undefined;
|
|
52
|
+
workspaceId?: string | undefined;
|
|
53
|
+
capability?: ClientVoiceInputConfig | null | undefined;
|
|
54
|
+
workspaceEnabled?: boolean | undefined;
|
|
55
|
+
/** @deprecated Native MediaRecorder input no longer uses host adapters. */
|
|
56
|
+
adapter?: TranscriptionAdapter | null | undefined;
|
|
57
|
+
/** @deprecated Native MediaRecorder input no longer uses host policies. */
|
|
58
|
+
policy?: WorkspaceTranscriptionPolicy | undefined;
|
|
59
|
+
/** @deprecated Native input uses the server capability duration limit. */
|
|
60
|
+
lifecycleTimeouts?: unknown;
|
|
61
|
+
/** @deprecated Native input does not expose provider diagnostics. */
|
|
62
|
+
onDiagnostic?: unknown;
|
|
60
63
|
messages?: Partial<ComposerTranscriptionMessages> | undefined;
|
|
61
64
|
className?: string | undefined;
|
|
62
|
-
lifecycleTimeouts?: Partial<TranscriptionLifecycleTimeouts> | undefined;
|
|
63
|
-
onDiagnostic?: ((diagnostic: TranscriptionDiagnostic) => void) | undefined;
|
|
64
65
|
};
|
|
65
66
|
|
|
67
|
+
const WAVEFORM_BARS = 18;
|
|
68
|
+
const WAVEFORM_BAR_KEYS = Array.from(
|
|
69
|
+
{ length: WAVEFORM_BARS },
|
|
70
|
+
(_, index) => `voice-waveform-bar-${index + 1}`,
|
|
71
|
+
);
|
|
72
|
+
|
|
66
73
|
/** One provider-neutral microphone control for the nearest editable composer. */
|
|
67
74
|
export function ComposerTranscriptionControl({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
client = null,
|
|
76
|
+
workspaceId = "",
|
|
77
|
+
capability = null,
|
|
78
|
+
workspaceEnabled = false,
|
|
71
79
|
messages: overrides,
|
|
72
80
|
className,
|
|
73
|
-
lifecycleTimeouts,
|
|
74
|
-
onDiagnostic,
|
|
75
81
|
}: ComposerTranscriptionControlProps) {
|
|
76
82
|
const composer = useChatComposer();
|
|
77
83
|
const messages = { ...defaultMessages, ...overrides };
|
|
78
|
-
const transcription =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
const transcription = useVoiceInput({
|
|
85
|
+
client,
|
|
86
|
+
workspaceId,
|
|
87
|
+
capability,
|
|
88
|
+
enabled: workspaceEnabled,
|
|
82
89
|
value: composer.value,
|
|
83
90
|
setValue: composer.setValue,
|
|
84
91
|
focusInput: composer.focusInput,
|
|
85
92
|
disabled: composer.disabled,
|
|
86
|
-
lifecycleTimeouts,
|
|
87
|
-
onDiagnostic,
|
|
88
93
|
});
|
|
89
|
-
const status = transcription
|
|
94
|
+
const { status } = transcription;
|
|
90
95
|
const active =
|
|
91
|
-
status === "requesting-permission" ||
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
: transcription.unavailableReason
|
|
101
|
-
? messages.unavailablePolicy
|
|
102
|
-
: null;
|
|
103
|
-
const label = unavailableMessage
|
|
104
|
-
? unavailableMessage
|
|
105
|
-
: status === "error"
|
|
106
|
-
? messages.retry
|
|
107
|
-
: active
|
|
108
|
-
? messages.stop
|
|
109
|
-
: messages.start;
|
|
110
|
-
const errorMessage = transcription.state.error
|
|
111
|
-
? transcriptionErrorMessage(transcription.state.error.code, messages)
|
|
96
|
+
status === "requesting-permission" || status === "recording" || status === "transcribing";
|
|
97
|
+
const unavailableMessage = composer.disabled
|
|
98
|
+
? messages.unavailableDisabled
|
|
99
|
+
: !capability?.available || !workspaceEnabled
|
|
100
|
+
? messages.unavailable
|
|
101
|
+
: null;
|
|
102
|
+
const idleLabel = unavailableMessage ?? (status === "error" ? messages.retry : messages.start);
|
|
103
|
+
const errorMessage = transcription.error
|
|
104
|
+
? transcriptionErrorMessage(transcription.error, messages)
|
|
112
105
|
: null;
|
|
113
106
|
const announcement =
|
|
114
|
-
|
|
115
|
-
(status === "requesting-permission"
|
|
107
|
+
status === "requesting-permission"
|
|
116
108
|
? messages.requestingPermission
|
|
117
|
-
: status === "
|
|
118
|
-
? messages.
|
|
119
|
-
: status === "
|
|
120
|
-
?
|
|
121
|
-
: status === "
|
|
122
|
-
? messages.
|
|
123
|
-
:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
function activate(event: MouseEvent<HTMLButtonElement>) {
|
|
128
|
-
if (unavailableMessage || status === "cancelling") {
|
|
109
|
+
: status === "recording"
|
|
110
|
+
? messages.recording
|
|
111
|
+
: status === "transcribing"
|
|
112
|
+
? messages.transcribing
|
|
113
|
+
: status === "error"
|
|
114
|
+
? (errorMessage ?? messages.errorUnknown)
|
|
115
|
+
: unavailableMessage;
|
|
116
|
+
|
|
117
|
+
function start(event: MouseEvent<HTMLButtonElement>) {
|
|
118
|
+
if (unavailableMessage) {
|
|
129
119
|
event.preventDefault();
|
|
130
120
|
return;
|
|
131
121
|
}
|
|
132
|
-
|
|
133
|
-
else void transcription.start();
|
|
122
|
+
void transcription.start();
|
|
134
123
|
}
|
|
135
124
|
|
|
136
125
|
return (
|
|
@@ -138,45 +127,110 @@ export function ComposerTranscriptionControl({
|
|
|
138
127
|
className={cn("inline-flex min-w-0 items-center gap-1.5", className)}
|
|
139
128
|
data-transcription-status={status}
|
|
140
129
|
>
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
130
|
+
<AnimatePresence mode="popLayout" initial={false}>
|
|
131
|
+
{active ? (
|
|
132
|
+
<motion.span
|
|
133
|
+
key={status === "transcribing" ? "transcribing" : "capture"}
|
|
134
|
+
initial={{ opacity: 0, scale: 0.96 }}
|
|
135
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
136
|
+
exit={{ opacity: 0, scale: 0.96 }}
|
|
137
|
+
transition={{ duration: 0.16, ease: [0.22, 1, 0.36, 1] }}
|
|
138
|
+
className={cn(
|
|
139
|
+
"inline-flex h-8 items-center gap-1 rounded-og-md border border-og-border/80",
|
|
140
|
+
"bg-og-surface-2/70 pl-2 pr-1 pointer-coarse:h-11",
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{status === "requesting-permission" ? (
|
|
144
|
+
<LoaderCircleIcon
|
|
145
|
+
className="size-3.5 shrink-0 text-og-fg-muted animate-og-spin motion-reduce:animate-none"
|
|
146
|
+
aria-hidden
|
|
147
|
+
/>
|
|
148
|
+
) : (
|
|
149
|
+
<span className="flex items-center gap-1.5">
|
|
150
|
+
{status === "recording" ? (
|
|
151
|
+
<span
|
|
152
|
+
aria-hidden
|
|
153
|
+
className="size-1.5 shrink-0 rounded-full bg-og-status-failed animate-og-pulse motion-reduce:animate-none"
|
|
154
|
+
/>
|
|
155
|
+
) : null}
|
|
156
|
+
<VoiceWaveform
|
|
157
|
+
stream={status === "recording" ? transcription.stream : null}
|
|
158
|
+
mode={status === "transcribing" ? "transcribing" : "recording"}
|
|
159
|
+
/>
|
|
160
|
+
</span>
|
|
161
|
+
)}
|
|
162
|
+
{status === "recording" ? (
|
|
163
|
+
<>
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
onClick={() => transcription.cancel()}
|
|
167
|
+
aria-label={messages.cancel}
|
|
168
|
+
aria-keyshortcuts="Escape"
|
|
169
|
+
title={messages.cancel}
|
|
170
|
+
className={cn(
|
|
171
|
+
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
172
|
+
"text-og-fg-muted transition-colors duration-150 motion-reduce:transition-none",
|
|
173
|
+
"hover:bg-og-surface-3 hover:text-og-fg pointer-coarse:size-9",
|
|
174
|
+
)}
|
|
175
|
+
>
|
|
176
|
+
<XIcon className="size-3.5" />
|
|
177
|
+
</button>
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={() => transcription.stop()}
|
|
181
|
+
aria-label={messages.stop}
|
|
182
|
+
title={messages.stop}
|
|
183
|
+
className={cn(
|
|
184
|
+
"inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
|
|
185
|
+
"bg-og-fg text-og-bg transition-colors duration-150 motion-reduce:transition-none",
|
|
186
|
+
"hover:bg-og-fg-muted pointer-coarse:size-9",
|
|
187
|
+
)}
|
|
188
|
+
>
|
|
189
|
+
<SquareIcon className="size-2.5 fill-current" />
|
|
190
|
+
</button>
|
|
191
|
+
</>
|
|
192
|
+
) : status === "transcribing" ? (
|
|
193
|
+
<span className="og-shimmer-text px-1.5 text-og-xs font-medium whitespace-nowrap">
|
|
194
|
+
{messages.transcribing}
|
|
195
|
+
</span>
|
|
196
|
+
) : (
|
|
197
|
+
<span className="px-1.5 text-og-xs text-og-fg-muted whitespace-nowrap">
|
|
198
|
+
{messages.requestingPermission}
|
|
199
|
+
</span>
|
|
200
|
+
)}
|
|
201
|
+
</motion.span>
|
|
166
202
|
) : (
|
|
167
|
-
<
|
|
203
|
+
<motion.button
|
|
204
|
+
key="idle"
|
|
205
|
+
type="button"
|
|
206
|
+
initial={{ opacity: 0, scale: 0.96 }}
|
|
207
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
208
|
+
exit={{ opacity: 0, scale: 0.96 }}
|
|
209
|
+
transition={{ duration: 0.14, ease: [0.22, 1, 0.36, 1] }}
|
|
210
|
+
onClick={start}
|
|
211
|
+
aria-label={idleLabel}
|
|
212
|
+
aria-pressed={false}
|
|
213
|
+
aria-disabled={unavailableMessage !== null}
|
|
214
|
+
title={idleLabel}
|
|
215
|
+
className={cn(
|
|
216
|
+
"inline-flex size-8 shrink-0 items-center justify-center rounded-og-md pointer-coarse:size-11",
|
|
217
|
+
"text-og-fg-muted transition-colors duration-150 motion-reduce:transition-none",
|
|
218
|
+
unavailableMessage
|
|
219
|
+
? "cursor-not-allowed opacity-45"
|
|
220
|
+
: "hover:bg-og-surface-2 hover:text-og-fg",
|
|
221
|
+
)}
|
|
222
|
+
>
|
|
223
|
+
<MicIcon className="size-4" />
|
|
224
|
+
</motion.button>
|
|
168
225
|
)}
|
|
169
|
-
</
|
|
170
|
-
{
|
|
226
|
+
</AnimatePresence>
|
|
227
|
+
{status === "error" && errorMessage ? (
|
|
171
228
|
<span
|
|
172
229
|
aria-hidden="true"
|
|
173
|
-
title={
|
|
174
|
-
className=
|
|
175
|
-
"max-w-48 truncate text-og-xs text-og-fg-muted max-sm:max-w-28",
|
|
176
|
-
status === "error" && "text-og-status-failed",
|
|
177
|
-
)}
|
|
230
|
+
title={errorMessage}
|
|
231
|
+
className="max-w-40 truncate text-og-xs text-og-status-failed max-sm:max-w-24"
|
|
178
232
|
>
|
|
179
|
-
{
|
|
233
|
+
{errorMessage}
|
|
180
234
|
</span>
|
|
181
235
|
) : null}
|
|
182
236
|
<span className="sr-only" role={status === "error" ? "alert" : "status"} aria-live="polite">
|
|
@@ -186,26 +240,120 @@ export function ComposerTranscriptionControl({
|
|
|
186
240
|
);
|
|
187
241
|
}
|
|
188
242
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
243
|
+
function VoiceWaveform({
|
|
244
|
+
stream,
|
|
245
|
+
mode,
|
|
246
|
+
}: {
|
|
247
|
+
stream: MediaStream | null;
|
|
248
|
+
mode: "recording" | "transcribing";
|
|
249
|
+
}) {
|
|
250
|
+
const [levels, setLevels] = useState<number[]>(() => restingLevels(WAVEFORM_BARS));
|
|
251
|
+
const [live, setLive] = useState(false);
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (!stream || mode !== "recording") {
|
|
255
|
+
setLive(false);
|
|
256
|
+
setLevels(restingLevels(WAVEFORM_BARS));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const AudioCtx =
|
|
261
|
+
window.AudioContext ??
|
|
262
|
+
(window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
|
|
263
|
+
if (!AudioCtx) return;
|
|
264
|
+
|
|
265
|
+
let cancelled = false;
|
|
266
|
+
let frame = 0;
|
|
267
|
+
let context: AudioContext | null = null;
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
context = new AudioCtx();
|
|
271
|
+
const source = context.createMediaStreamSource(stream);
|
|
272
|
+
const analyser = context.createAnalyser();
|
|
273
|
+
analyser.fftSize = 64;
|
|
274
|
+
analyser.smoothingTimeConstant = 0.8;
|
|
275
|
+
source.connect(analyser);
|
|
276
|
+
const bins = new Uint8Array(analyser.frequencyBinCount);
|
|
277
|
+
|
|
278
|
+
const tick = () => {
|
|
279
|
+
if (cancelled) return;
|
|
280
|
+
analyser.getByteFrequencyData(bins);
|
|
281
|
+
const next = new Array<number>(WAVEFORM_BARS);
|
|
282
|
+
const usable = Math.max(8, Math.floor(bins.length * 0.65));
|
|
283
|
+
for (let i = 0; i < WAVEFORM_BARS; i++) {
|
|
284
|
+
const start = Math.floor((i / WAVEFORM_BARS) * usable);
|
|
285
|
+
const end = Math.max(start + 1, Math.floor(((i + 1) / WAVEFORM_BARS) * usable));
|
|
286
|
+
let sum = 0;
|
|
287
|
+
for (let j = start; j < end; j++) sum += bins[j] ?? 0;
|
|
288
|
+
const avg = sum / (end - start) / 255;
|
|
289
|
+
next[i] = Math.min(1, 0.14 + avg * 1.65);
|
|
290
|
+
}
|
|
291
|
+
setLevels(next);
|
|
292
|
+
setLive(true);
|
|
293
|
+
frame = requestAnimationFrame(tick);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
void context.resume().then(() => {
|
|
297
|
+
if (!cancelled) frame = requestAnimationFrame(tick);
|
|
298
|
+
});
|
|
299
|
+
} catch {
|
|
300
|
+
setLive(false);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return () => {
|
|
304
|
+
cancelled = true;
|
|
305
|
+
cancelAnimationFrame(frame);
|
|
306
|
+
void context?.close();
|
|
307
|
+
};
|
|
308
|
+
}, [stream, mode]);
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<span
|
|
312
|
+
aria-hidden
|
|
313
|
+
data-voice-waveform={live ? "live" : "fallback"}
|
|
314
|
+
className="flex h-4 w-[3.75rem] items-center justify-center gap-px"
|
|
315
|
+
>
|
|
316
|
+
{WAVEFORM_BAR_KEYS.map((key, index) => {
|
|
317
|
+
const level = levels[index] ?? 0;
|
|
318
|
+
return (
|
|
319
|
+
<span
|
|
320
|
+
key={key}
|
|
321
|
+
className={cn(
|
|
322
|
+
"w-0.5 rounded-full bg-og-fg-muted/90 origin-center",
|
|
323
|
+
!live && "animate-og-waveform motion-reduce:animate-none",
|
|
324
|
+
mode === "transcribing" && "opacity-45",
|
|
325
|
+
)}
|
|
326
|
+
style={{
|
|
327
|
+
height: live ? `${Math.max(3, Math.round(level * 14))}px` : "14px",
|
|
328
|
+
animationDelay: live ? undefined : `${(index % 9) * 0.07}s`,
|
|
329
|
+
opacity: live ? 0.55 + level * 0.45 : undefined,
|
|
330
|
+
}}
|
|
331
|
+
/>
|
|
332
|
+
);
|
|
333
|
+
})}
|
|
334
|
+
</span>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function restingLevels(count: number): number[] {
|
|
339
|
+
return Array.from({ length: count }, (_, index) => 0.22 + ((index * 17) % 5) * 0.06);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function transcriptionErrorMessage(code: string, messages: ComposerTranscriptionMessages): string {
|
|
193
343
|
switch (code) {
|
|
194
344
|
case "permission_denied":
|
|
195
345
|
return messages.errorPermissionDenied;
|
|
196
346
|
case "not_supported":
|
|
197
347
|
return messages.errorNotSupported;
|
|
198
|
-
case "
|
|
199
|
-
return messages.
|
|
200
|
-
case "
|
|
201
|
-
return messages.
|
|
202
|
-
case "
|
|
203
|
-
return messages.
|
|
204
|
-
case "timeout":
|
|
205
|
-
return messages.errorTimeout;
|
|
206
|
-
case "cancelled":
|
|
207
|
-
return messages.errorCancelled;
|
|
348
|
+
case "unavailable":
|
|
349
|
+
return messages.errorUnavailable;
|
|
350
|
+
case "too_large":
|
|
351
|
+
return messages.errorTooLarge;
|
|
352
|
+
case "invalid_audio":
|
|
353
|
+
return messages.errorInvalidAudio;
|
|
208
354
|
case "unknown":
|
|
209
355
|
return messages.errorUnknown;
|
|
356
|
+
default:
|
|
357
|
+
return messages.errorUnknown;
|
|
210
358
|
}
|
|
211
359
|
}
|
|
@@ -43,7 +43,8 @@ import {
|
|
|
43
43
|
type SlashCommandContext,
|
|
44
44
|
} from "../hooks/use-slash-commands";
|
|
45
45
|
import { cn } from "../lib/cn";
|
|
46
|
-
import { formatBytes, formatRelativeTime } from "../lib/format";
|
|
46
|
+
import { composerSubmissionErrorMessage, formatBytes, formatRelativeTime } from "../lib/format";
|
|
47
|
+
import type { PickerModelRow } from "../model-policy";
|
|
47
48
|
import { CommandPalette as CommandPaletteView } from "./command-palette";
|
|
48
49
|
import { ModelPicker as ModelPickerView } from "./model-picker";
|
|
49
50
|
|
|
@@ -191,6 +192,8 @@ export const defaultChatComposerMessages: ChatComposerMessages = {
|
|
|
191
192
|
draftConflict: "This draft changed in another tab. Your local draft is still here.",
|
|
192
193
|
useOtherDraft: "Use other draft",
|
|
193
194
|
keepMine: "Keep mine",
|
|
195
|
+
// Kept for embedder message overrides / back-compat. Routine autosave is
|
|
196
|
+
// silent — only draft conflicts surface under the composer.
|
|
194
197
|
savingDraft: "Saving draft…",
|
|
195
198
|
formatBytes,
|
|
196
199
|
formatRelativeTime,
|
|
@@ -470,7 +473,7 @@ export function useChatComposerController({
|
|
|
470
473
|
tone: "error" as const,
|
|
471
474
|
message: /control changed|paused while/i.test(delivery.error.message)
|
|
472
475
|
? messages.controlChangedError
|
|
473
|
-
: delivery.error
|
|
476
|
+
: composerSubmissionErrorMessage(delivery.error) || messages.sendFailedError,
|
|
474
477
|
}
|
|
475
478
|
: null);
|
|
476
479
|
useEffect(() => {
|
|
@@ -698,9 +701,9 @@ export const Surface = forwardRef<HTMLDivElement, ComposerSurfaceProps>(function
|
|
|
698
701
|
onDragLeave={controller.attachments ? controller.handleDragLeave : undefined}
|
|
699
702
|
onDrop={controller.attachments ? controller.handleDrop : undefined}
|
|
700
703
|
className={cn(
|
|
701
|
-
"relative rounded-og-lg border border-og-border bg-og-surface-1 shadow-og-sm",
|
|
702
|
-
"transition-[border-color,box-shadow] duration-200",
|
|
703
|
-
"focus-within:border-og-accent/
|
|
704
|
+
"relative rounded-og-lg border border-og-border/90 bg-og-surface-1 shadow-og-sm",
|
|
705
|
+
"transition-[border-color,box-shadow] duration-200 ease-og-out",
|
|
706
|
+
"focus-within:border-og-accent/50 focus-within:shadow-og-glow",
|
|
704
707
|
controller.dragging && "border-dashed border-og-accent",
|
|
705
708
|
className,
|
|
706
709
|
)}
|
|
@@ -814,7 +817,7 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
|
|
|
814
817
|
paletteOpen ? `${controller.listboxId}-option-${controller.palette.highlight}` : undefined
|
|
815
818
|
}
|
|
816
819
|
className={cn(
|
|
817
|
-
"block w-full resize-none bg-transparent px-
|
|
820
|
+
"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",
|
|
818
821
|
"text-og-fg placeholder:text-og-fg-subtle focus:outline-none focus-visible:outline-none",
|
|
819
822
|
"disabled:cursor-not-allowed disabled:opacity-60",
|
|
820
823
|
className,
|
|
@@ -848,7 +851,7 @@ export const Footer = forwardRef<HTMLDivElement, ComposerFooterProps>(function C
|
|
|
848
851
|
<div
|
|
849
852
|
{...props}
|
|
850
853
|
ref={ref}
|
|
851
|
-
className={cn("flex items-end gap-2 px-2.5 pb-2.5
|
|
854
|
+
className={cn("flex items-end gap-1.5 px-2 pb-2 pt-0.5 sm:px-2.5 sm:pb-2.5", className)}
|
|
852
855
|
/>
|
|
853
856
|
);
|
|
854
857
|
});
|
|
@@ -949,29 +952,37 @@ export const AttachButton = forwardRef<HTMLButtonElement, ComposerAttachButtonPr
|
|
|
949
952
|
);
|
|
950
953
|
|
|
951
954
|
export type ComposerModelPickerProps = {
|
|
952
|
-
models
|
|
955
|
+
models?: ClientModel[] | undefined;
|
|
956
|
+
/** Catalog-backed rows preferred over legacy provider-grouped models. */
|
|
957
|
+
rows?: PickerModelRow[] | undefined;
|
|
953
958
|
value?: string | undefined;
|
|
954
959
|
onChange?: ((modelId: string) => void) | undefined;
|
|
955
960
|
label?: string | undefined;
|
|
956
961
|
className?: string | undefined;
|
|
962
|
+
/** Filter to Codex models for remote_v2-locked sessions. */
|
|
963
|
+
codexOnly?: boolean | undefined;
|
|
957
964
|
};
|
|
958
965
|
|
|
959
966
|
export function ModelPicker({
|
|
960
967
|
models,
|
|
968
|
+
rows,
|
|
961
969
|
value,
|
|
962
970
|
onChange,
|
|
963
971
|
label,
|
|
964
972
|
className,
|
|
973
|
+
codexOnly,
|
|
965
974
|
}: ComposerModelPickerProps) {
|
|
966
975
|
const controller = useComposerController();
|
|
967
976
|
return (
|
|
968
977
|
<ModelPickerView
|
|
969
978
|
models={models}
|
|
979
|
+
rows={rows}
|
|
970
980
|
value={value}
|
|
971
981
|
onChange={(modelId) => onChange?.(modelId)}
|
|
972
982
|
disabled={controller.disabled}
|
|
973
983
|
label={label ?? controller.messages.modelLabel}
|
|
974
984
|
className={className}
|
|
985
|
+
codexOnly={codexOnly}
|
|
975
986
|
/>
|
|
976
987
|
);
|
|
977
988
|
}
|
|
@@ -1119,8 +1130,6 @@ export function Status() {
|
|
|
1119
1130
|
{controller.messages.keepMine}
|
|
1120
1131
|
</button>
|
|
1121
1132
|
</div>
|
|
1122
|
-
) : controller.draftSaving ? (
|
|
1123
|
-
<p className="px-1 pt-1 text-og-xs text-og-fg-subtle">{controller.messages.savingDraft}</p>
|
|
1124
1133
|
) : null}
|
|
1125
1134
|
</>
|
|
1126
1135
|
);
|
|
@@ -1135,11 +1144,7 @@ function ComposerAnnouncements() {
|
|
|
1135
1144
|
{controller.activeNotice.message}
|
|
1136
1145
|
</p>
|
|
1137
1146
|
) : null}
|
|
1138
|
-
{controller.draftConflict ?
|
|
1139
|
-
<p role="alert">{controller.messages.draftConflict}</p>
|
|
1140
|
-
) : controller.draftSaving ? (
|
|
1141
|
-
<p role="status">{controller.messages.savingDraft}</p>
|
|
1142
|
-
) : null}
|
|
1147
|
+
{controller.draftConflict ? <p role="alert">{controller.messages.draftConflict}</p> : null}
|
|
1143
1148
|
</div>
|
|
1144
1149
|
);
|
|
1145
1150
|
}
|