@bitbaum/chatkit 0.2.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -8
- package/bin/chatkit-check-file-inputs.mjs +87 -0
- package/dist/attachments.d.ts +37 -0
- package/dist/attachments.d.ts.map +1 -1
- package/dist/attachments.js +75 -1
- package/dist/attachments.js.map +1 -1
- package/dist/composer.d.ts +4 -1
- package/dist/composer.d.ts.map +1 -1
- package/dist/composer.js +4 -2
- package/dist/composer.js.map +1 -1
- package/dist/dictation.d.ts +62 -2
- package/dist/dictation.d.ts.map +1 -1
- package/dist/dictation.js +103 -0
- package/dist/dictation.js.map +1 -1
- package/dist/image.d.ts +7 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +41 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/react/AttachMenu.d.ts +37 -0
- package/dist/react/AttachMenu.d.ts.map +1 -0
- package/dist/react/AttachMenu.js +70 -0
- package/dist/react/AttachMenu.js.map +1 -0
- package/dist/react/Composer.d.ts +24 -2
- package/dist/react/Composer.d.ts.map +1 -1
- package/dist/react/Composer.js +59 -10
- package/dist/react/Composer.js.map +1 -1
- package/dist/react/Thread.d.ts +27 -2
- package/dist/react/Thread.d.ts.map +1 -1
- package/dist/react/Thread.js +17 -4
- package/dist/react/Thread.js.map +1 -1
- package/dist/react/attach.d.ts +17 -0
- package/dist/react/attach.d.ts.map +1 -0
- package/dist/react/attach.js +16 -0
- package/dist/react/attach.js.map +1 -0
- package/dist/react/hooks.d.ts +8 -1
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +27 -3
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/icons.d.ts +3 -0
- package/dist/react/icons.d.ts.map +1 -1
- package/dist/react/icons.js +3 -0
- package/dist/react/icons.js.map +1 -1
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/use-attachments.d.ts.map +1 -1
- package/dist/react/use-attachments.js +6 -34
- package/dist/react/use-attachments.js.map +1 -1
- package/dist/react/use-dictation.d.ts +23 -0
- package/dist/react/use-dictation.d.ts.map +1 -1
- package/dist/react/use-dictation.js +175 -13
- package/dist/react/use-dictation.js.map +1 -1
- package/dist/replies.d.ts +41 -0
- package/dist/replies.d.ts.map +1 -0
- package/dist/replies.js +84 -0
- package/dist/replies.js.map +1 -0
- package/package.json +9 -1
- package/styles.css +179 -3
package/dist/dictation.js
CHANGED
|
@@ -52,6 +52,19 @@ export function problemForRecording(error) {
|
|
|
52
52
|
return "mic";
|
|
53
53
|
return "unavailable";
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The words on screen while a take is live: everything the recogniser has
|
|
57
|
+
* settled on, then what it is still guessing at — one line, read as it is
|
|
58
|
+
* spoken. A preview only: when the server leg transcribes, ITS words are
|
|
59
|
+
* what lands in the box; these are what the person watches meanwhile, so a
|
|
60
|
+
* take is never a timer and a wave with nothing to show for twenty seconds.
|
|
61
|
+
*/
|
|
62
|
+
export function liveWords(finals, interim) {
|
|
63
|
+
return [...finals, interim]
|
|
64
|
+
.map((s) => s.trim())
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join(" ");
|
|
67
|
+
}
|
|
55
68
|
/** A working recogniser fires `start` well inside this once the mic is allowed. */
|
|
56
69
|
export const START_TIMEOUT_MS = 4000;
|
|
57
70
|
/**
|
|
@@ -84,10 +97,100 @@ export const RECORDING_MIME_CANDIDATES = [
|
|
|
84
97
|
"audio/mp4",
|
|
85
98
|
"audio/ogg",
|
|
86
99
|
];
|
|
100
|
+
/**
|
|
101
|
+
* A recording that already exists — a voice memo from the phone's recorder, a
|
|
102
|
+
* meeting someone taped — is dictation that happened earlier. It goes to the
|
|
103
|
+
* same server leg as the mic and its words land in the box the same way.
|
|
104
|
+
*
|
|
105
|
+
* The cap is on the upload, not the recording: the server compresses and
|
|
106
|
+
* splits long audio before Whisper, but a phone on mobile data still has to
|
|
107
|
+
* send the bytes. A one-hour memo from a phone recorder is about 60 MB; this
|
|
108
|
+
* takes that with room to spare, and refuses the ten-hour one in words rather
|
|
109
|
+
* than after a ten-minute upload that fails.
|
|
110
|
+
*/
|
|
111
|
+
export const MAX_AUDIO_FILE_BYTES = 120 * 1024 * 1024;
|
|
112
|
+
const AUDIO_EXT = /\.(m4a|mp3|wav|ogg|oga|opus|webm|aac|flac|amr|3gp|3gpp|mp4|caf|aiff?|wma)$/i;
|
|
113
|
+
/**
|
|
114
|
+
* Does this file hold a recording? By MIME where the browser gives one, by
|
|
115
|
+
* extension where it does not: Android's picker hands over `.m4a` from the
|
|
116
|
+
* stock recorder with an empty type often enough that mime alone would call
|
|
117
|
+
* it a text file and inline the bytes into a prompt.
|
|
118
|
+
*/
|
|
119
|
+
export function isAudioFile(file) {
|
|
120
|
+
const type = (file.type ?? "").toLowerCase();
|
|
121
|
+
if (type.startsWith("audio/"))
|
|
122
|
+
return true;
|
|
123
|
+
if (type.startsWith("image/") || type.startsWith("text/"))
|
|
124
|
+
return false;
|
|
125
|
+
return AUDIO_EXT.test(file.name ?? "");
|
|
126
|
+
}
|
|
127
|
+
/** The name a recording travels under. Whisper services read the container
|
|
128
|
+
* from the extension, so a mic take keeps the type's extension and a file
|
|
129
|
+
* keeps its own name. */
|
|
130
|
+
export function audioFileName(audio) {
|
|
131
|
+
if (audio.name && AUDIO_EXT.test(audio.name))
|
|
132
|
+
return audio.name;
|
|
133
|
+
const type = (audio.type ?? "").toLowerCase();
|
|
134
|
+
const ext = type.includes("mp4") || type.includes("m4a")
|
|
135
|
+
? "m4a"
|
|
136
|
+
: type.includes("ogg")
|
|
137
|
+
? "ogg"
|
|
138
|
+
: type.includes("mpeg") || type.includes("mp3")
|
|
139
|
+
? "mp3"
|
|
140
|
+
: type.includes("wav")
|
|
141
|
+
? "wav"
|
|
142
|
+
: "webm";
|
|
143
|
+
return `voice.${ext}`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A server leg that refused, with the server's own words when it gave any.
|
|
147
|
+
*
|
|
148
|
+
* A failed transcription used to reach the person as one sentence whatever
|
|
149
|
+
* went wrong — and in OrangeCat as "check your connection", while the server
|
|
150
|
+
* had answered plainly that it was busy (2026-10-07). `reason` is what the
|
|
151
|
+
* server said, for the UI to show beside the generic line; a custom
|
|
152
|
+
* `transcribe` may throw one too.
|
|
153
|
+
*/
|
|
154
|
+
export class TranscriptionError extends Error {
|
|
155
|
+
reason;
|
|
156
|
+
status;
|
|
157
|
+
constructor(reason, status = null) {
|
|
158
|
+
super(reason ?? `transcription failed${status ? ` (${status})` : ""}`);
|
|
159
|
+
this.name = "TranscriptionError";
|
|
160
|
+
this.reason = reason;
|
|
161
|
+
this.status = status;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** The server's sentence from a failed transcription, if it sent one. Reads
|
|
165
|
+
* the shapes servers actually answer with: `{ error }`, `{ message }`,
|
|
166
|
+
* `{ error: { message } }`. Capped, because it is shown in a status line. */
|
|
167
|
+
export function reasonFromBody(body) {
|
|
168
|
+
if (!body || typeof body !== "object")
|
|
169
|
+
return null;
|
|
170
|
+
const b = body;
|
|
171
|
+
const pick = typeof b.error === "string"
|
|
172
|
+
? b.error
|
|
173
|
+
: b.error &&
|
|
174
|
+
typeof b.error === "object" &&
|
|
175
|
+
typeof b.error.message === "string"
|
|
176
|
+
? b.error.message
|
|
177
|
+
: typeof b.message === "string"
|
|
178
|
+
? b.message
|
|
179
|
+
: null;
|
|
180
|
+
const t = pick?.trim();
|
|
181
|
+
return t ? t.slice(0, 200) : null;
|
|
182
|
+
}
|
|
183
|
+
/** What a thrown transcription error says, for any thrower: ours, or an
|
|
184
|
+
* app's `transcribe` that sets `reason` on whatever it throws. */
|
|
185
|
+
export function reasonOf(error) {
|
|
186
|
+
const r = error?.reason;
|
|
187
|
+
return typeof r === "string" && r.trim() ? r.trim().slice(0, 200) : null;
|
|
188
|
+
}
|
|
87
189
|
/** The default words for each problem. Override per app (and per language). */
|
|
88
190
|
export const DEFAULT_DICTATION_MESSAGES = {
|
|
89
191
|
mic: "The microphone is blocked. Allow it for this site and try again.",
|
|
90
192
|
silence: "Nothing was heard. Try again, a little closer to the mic.",
|
|
91
193
|
unavailable: "Voice input is not available right now. Type instead, or try again later.",
|
|
194
|
+
fileTooLarge: `That recording is too large to transcribe here (max ${Math.round(MAX_AUDIO_FILE_BYTES / 1024 / 1024)} MB).`,
|
|
92
195
|
};
|
|
93
196
|
//# sourceMappingURL=dictation.js.map
|
package/dist/dictation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dictation.js","sourceRoot":"","sources":["../src/dictation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"dictation.js","sourceRoot":"","sources":["../src/dictation.ts"],"names":[],"mappings":"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"}
|
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draw the image no larger than `maxEdge` and encode it as JPEG, stepping the
|
|
3
|
+
* quality down until it fits `maxBytes`. Null when it cannot be made to fit,
|
|
4
|
+
* or the browser cannot decode it (no `createImageBitmap`, no canvas).
|
|
5
|
+
*/
|
|
6
|
+
export declare function shrinkImage(file: File, maxEdge: number, maxBytes: number): Promise<string | null>;
|
|
7
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBxB"}
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shrinking a picture in the browser before it leaves the device — for a chat
|
|
3
|
+
* attachment and for any uploader (an avatar, a product photo, a receipt).
|
|
4
|
+
* Browser-only: it needs `createImageBitmap` and a canvas, and returns null
|
|
5
|
+
* where they are missing rather than throwing.
|
|
6
|
+
*/
|
|
7
|
+
import { dataUrlBytes, fitWithin } from "./attachments.js";
|
|
8
|
+
/**
|
|
9
|
+
* Draw the image no larger than `maxEdge` and encode it as JPEG, stepping the
|
|
10
|
+
* quality down until it fits `maxBytes`. Null when it cannot be made to fit,
|
|
11
|
+
* or the browser cannot decode it (no `createImageBitmap`, no canvas).
|
|
12
|
+
*/
|
|
13
|
+
export async function shrinkImage(file, maxEdge, maxBytes) {
|
|
14
|
+
if (typeof createImageBitmap !== "function" || typeof document === "undefined")
|
|
15
|
+
return null;
|
|
16
|
+
const bitmap = await createImageBitmap(file);
|
|
17
|
+
try {
|
|
18
|
+
const size = fitWithin(bitmap.width, bitmap.height, maxEdge);
|
|
19
|
+
const canvas = document.createElement("canvas");
|
|
20
|
+
canvas.width = size.width;
|
|
21
|
+
canvas.height = size.height;
|
|
22
|
+
const ctx = canvas.getContext("2d");
|
|
23
|
+
if (!ctx)
|
|
24
|
+
return null;
|
|
25
|
+
// JPEG has no alpha: paint white first so a transparent PNG does not turn black.
|
|
26
|
+
ctx.fillStyle = "#fff";
|
|
27
|
+
ctx.fillRect(0, 0, size.width, size.height);
|
|
28
|
+
ctx.drawImage(bitmap, 0, 0, size.width, size.height);
|
|
29
|
+
let quality = 0.82;
|
|
30
|
+
let dataUrl = canvas.toDataURL("image/jpeg", quality);
|
|
31
|
+
while (dataUrlBytes(dataUrl) > maxBytes && quality > 0.4) {
|
|
32
|
+
quality -= 0.15;
|
|
33
|
+
dataUrl = canvas.toDataURL("image/jpeg", quality);
|
|
34
|
+
}
|
|
35
|
+
return dataUrlBytes(dataUrl) > maxBytes ? null : dataUrl;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
bitmap.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAU,EACV,OAAe,EACf,QAAgB;IAEhB,IAAI,OAAO,iBAAiB,KAAK,UAAU,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC5F,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QAC1B,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,iFAAiF;QACjF,GAAG,CAAC,SAAS,GAAG,MAAM,CAAC;QACvB,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,IAAI,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACtD,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,QAAQ,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC;YACzD,OAAO,IAAI,IAAI,CAAC;YAChB,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IAC3D,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type AttachSource } from "../attachments.js";
|
|
3
|
+
export type AttachMenuLabels = {
|
|
4
|
+
/** The paperclip's name. */
|
|
5
|
+
attach: string;
|
|
6
|
+
/** The sheet's title. */
|
|
7
|
+
title: string;
|
|
8
|
+
close: string;
|
|
9
|
+
sources: Record<AttachSource, string>;
|
|
10
|
+
};
|
|
11
|
+
export declare const DEFAULT_ATTACH_MENU_LABELS: AttachMenuLabels;
|
|
12
|
+
/**
|
|
13
|
+
* The paperclip, and what it opens on a phone: a sheet with Camera, Photos and
|
|
14
|
+
* Files — one input each. See `ATTACH_SOURCE_INPUT` for why three inputs and
|
|
15
|
+
* not one: a single mixed-`accept` input is what made a phone offer a recorder
|
|
16
|
+
* and no way to the screenshot.
|
|
17
|
+
*
|
|
18
|
+
* The sheet is a native `<dialog>` opened with `showModal()`: it renders in the
|
|
19
|
+
* browser's top layer, so a composer inside a transformed or overflow-clipped
|
|
20
|
+
* panel (a drawer, a floating rail) still gets a full-screen sheet — and the
|
|
21
|
+
* browser supplies Escape, the focus trap and focus restore.
|
|
22
|
+
*
|
|
23
|
+
* Every app that attaches anything gets this from the composer. An app with a
|
|
24
|
+
* custom box renders `<AttachMenu>` itself rather than a bare file input.
|
|
25
|
+
*/
|
|
26
|
+
export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, }: {
|
|
27
|
+
onFiles: (files: FileList | null) => void;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
labels?: AttachMenuLabels;
|
|
30
|
+
/** Which sources to offer, in order. An avatar or product-photo uploader
|
|
31
|
+
* passes `["camera", "photos"]`; one source skips the sheet entirely. */
|
|
32
|
+
sources?: readonly AttachSource[];
|
|
33
|
+
/** The button's content. Defaults to a paperclip icon; an uploader passes
|
|
34
|
+
* its own ("Add photo"). The button keeps chatkit's 44px target. */
|
|
35
|
+
trigger?: ReactNode;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
//# sourceMappingURL=AttachMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachMenu.d.ts","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC;AAeF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAgB,EAChB,MAAmC,EACnC,OAAiB,EACjB,OAAO,GACR,EAAE;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;8EAC0E;IAC1E,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC;yEACqE;IACrE,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,+BA4FA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { ATTACH_SOURCE_INPUT } from "../attachments.js";
|
|
5
|
+
import { IconCamera, IconFileUp, IconImage, IconPaperclip, IconX } from "./icons.js";
|
|
6
|
+
export const DEFAULT_ATTACH_MENU_LABELS = {
|
|
7
|
+
attach: "Attach a screenshot or file",
|
|
8
|
+
title: "Add to message",
|
|
9
|
+
close: "Close",
|
|
10
|
+
sources: { camera: "Camera", photos: "Photos", files: "Files" },
|
|
11
|
+
};
|
|
12
|
+
const SOURCES = ["camera", "photos", "files"];
|
|
13
|
+
const ICONS = {
|
|
14
|
+
camera: IconCamera,
|
|
15
|
+
photos: IconImage,
|
|
16
|
+
files: IconFileUp,
|
|
17
|
+
};
|
|
18
|
+
/** A touch screen gets the sheet; a mouse gets the file dialog in one click,
|
|
19
|
+
* because on a desktop Camera / Photos / Files are all the same dialog. */
|
|
20
|
+
function prefersSheet() {
|
|
21
|
+
return typeof window !== "undefined" && window.matchMedia?.("(pointer: coarse)").matches;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The paperclip, and what it opens on a phone: a sheet with Camera, Photos and
|
|
25
|
+
* Files — one input each. See `ATTACH_SOURCE_INPUT` for why three inputs and
|
|
26
|
+
* not one: a single mixed-`accept` input is what made a phone offer a recorder
|
|
27
|
+
* and no way to the screenshot.
|
|
28
|
+
*
|
|
29
|
+
* The sheet is a native `<dialog>` opened with `showModal()`: it renders in the
|
|
30
|
+
* browser's top layer, so a composer inside a transformed or overflow-clipped
|
|
31
|
+
* panel (a drawer, a floating rail) still gets a full-screen sheet — and the
|
|
32
|
+
* browser supplies Escape, the focus trap and focus restore.
|
|
33
|
+
*
|
|
34
|
+
* Every app that attaches anything gets this from the composer. An app with a
|
|
35
|
+
* custom box renders `<AttachMenu>` itself rather than a bare file input.
|
|
36
|
+
*/
|
|
37
|
+
export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_MENU_LABELS, sources = SOURCES, trigger, }) {
|
|
38
|
+
const inputs = useRef({});
|
|
39
|
+
const dialog = useRef(null);
|
|
40
|
+
const close = () => dialog.current?.close();
|
|
41
|
+
const pick = (source) => {
|
|
42
|
+
close();
|
|
43
|
+
inputs.current[source]?.click();
|
|
44
|
+
};
|
|
45
|
+
const open = () => {
|
|
46
|
+
const d = dialog.current;
|
|
47
|
+
if (sources.length > 1 && prefersSheet() && d && typeof d.showModal === "function")
|
|
48
|
+
d.showModal();
|
|
49
|
+
// A mouse gets the broadest picker offered: Files when it is one of the
|
|
50
|
+
// sources, else the last one (Photos, for an image uploader).
|
|
51
|
+
else
|
|
52
|
+
pick(sources.includes("files") ? "files" : sources[sources.length - 1]);
|
|
53
|
+
};
|
|
54
|
+
return (_jsxs(_Fragment, { children: [sources.map((source) => (_jsx("input", { ref: (el) => {
|
|
55
|
+
inputs.current[source] = el;
|
|
56
|
+
}, type: "file", hidden: true, multiple: source !== "camera", "data-ck-source": source, ...ATTACH_SOURCE_INPUT[source], onChange: (e) => {
|
|
57
|
+
onFiles(e.target.files);
|
|
58
|
+
e.target.value = "";
|
|
59
|
+
} }, source))), _jsx("button", { type: "button", className: trigger ? "ck-attach-trigger" : "ck-icon-btn", onClick: open, disabled: disabled, "aria-label": labels.attach, "aria-haspopup": "dialog", title: labels.attach, children: trigger ?? _jsx(IconPaperclip, {}) }), _jsx("dialog", { ref: dialog, className: "ck-sheet", "aria-label": labels.title,
|
|
60
|
+
// A tap on the backdrop lands on the <dialog> itself, never on the
|
|
61
|
+
// panel inside it: that is the scrim.
|
|
62
|
+
onClick: (e) => {
|
|
63
|
+
if (e.target === e.currentTarget)
|
|
64
|
+
close();
|
|
65
|
+
}, children: _jsxs("div", { className: "ck-sheet-panel", children: [_jsxs("div", { className: "ck-sheet-head", children: [_jsx("span", { className: "ck-sheet-title", children: labels.title }), _jsx("button", { type: "button", className: "ck-icon-btn ck-sheet-close", onClick: close, "aria-label": labels.close, title: labels.close, children: _jsx(IconX, {}) })] }), _jsx("div", { className: "ck-attach-sources", children: sources.map((source) => {
|
|
66
|
+
const Icon = ICONS[source];
|
|
67
|
+
return (_jsxs("button", { type: "button", className: "ck-attach-source", "data-ck-source": source, onClick: () => pick(source), children: [_jsx(Icon, {}), _jsx("span", { children: labels.sources[source] })] }, source));
|
|
68
|
+
}) })] }) })] }));
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=AttachMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachMenu.js","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/Composer.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type ComposerMode, type ComposerSendResult } from "../composer.js";
|
|
|
3
3
|
import { type DictationProblem } from "../dictation.js";
|
|
4
4
|
import { type Attachment, type AttachmentLimits, type AttachmentNotes } from "../attachments.js";
|
|
5
5
|
import { type UseDictationOptions } from "./use-dictation.js";
|
|
6
|
+
import { type AttachMenuLabels } from "./AttachMenu.js";
|
|
6
7
|
export type ComposerLabels = {
|
|
7
8
|
send: string;
|
|
8
9
|
stop: string;
|
|
@@ -13,8 +14,14 @@ export type ComposerLabels = {
|
|
|
13
14
|
listening: string;
|
|
14
15
|
transcribing: string;
|
|
15
16
|
attach: string;
|
|
17
|
+
/** The Camera / Photos / Files sheet the paperclip opens on a phone. */
|
|
18
|
+
attachMenu: Omit<AttachMenuLabels, "attach">;
|
|
19
|
+
/** The placeholder while a turn runs and the next message will be queued. */
|
|
20
|
+
queue: string;
|
|
16
21
|
remove: (name: string) => string;
|
|
17
22
|
dismiss: string;
|
|
23
|
+
/** Re-send a take whose transcription failed (it is kept). */
|
|
24
|
+
retryTake: string;
|
|
18
25
|
dictation: Record<DictationProblem, string>;
|
|
19
26
|
/** Why a file did not attach. */
|
|
20
27
|
attachNotes: AttachmentNotes;
|
|
@@ -22,9 +29,12 @@ export type ComposerLabels = {
|
|
|
22
29
|
export declare const DEFAULT_COMPOSER_LABELS: ComposerLabels;
|
|
23
30
|
export type ComposerVoice = Omit<UseDictationOptions, "onText">;
|
|
24
31
|
/** Overrides: every label is optional, including each dictation and attachment note. */
|
|
25
|
-
export type ComposerLabelOverrides = Partial<Omit<ComposerLabels, "dictation" | "attachNotes">> & {
|
|
32
|
+
export type ComposerLabelOverrides = Partial<Omit<ComposerLabels, "dictation" | "attachNotes" | "attachMenu">> & {
|
|
26
33
|
dictation?: Partial<ComposerLabels["dictation"]>;
|
|
27
34
|
attachNotes?: Partial<AttachmentNotes>;
|
|
35
|
+
attachMenu?: Partial<Omit<ComposerLabels["attachMenu"], "sources">> & {
|
|
36
|
+
sources?: Partial<ComposerLabels["attachMenu"]["sources"]>;
|
|
37
|
+
};
|
|
28
38
|
};
|
|
29
39
|
export type ComposerProps = {
|
|
30
40
|
/** Deliver what was written. Return `false` (or resolve to it) to keep the
|
|
@@ -41,6 +51,18 @@ export type ComposerProps = {
|
|
|
41
51
|
onStop?: () => void;
|
|
42
52
|
/** What an attachments-only send says. Omit and empty text cannot send. */
|
|
43
53
|
attachmentOnlyText?: string;
|
|
54
|
+
/** Attachments the APP holds rather than the composer — things chatkit has
|
|
55
|
+
* no model for (orangecat's references to the person's own listings, a
|
|
56
|
+
* photo already shrunk and staged by the app) and shows in `header`. They
|
|
57
|
+
* count toward "may this send?" exactly like the composer's own, so a
|
|
58
|
+
* message that is only such an attachment can be sent; without this the
|
|
59
|
+
* send button stayed dead for it. Pair with `attachmentOnlyText`. */
|
|
60
|
+
heldAttachments?: number;
|
|
61
|
+
/** The app takes a message while a turn runs and sends it when the turn
|
|
62
|
+
* ends. Then Send stays live beside Stop and the placeholder says the
|
|
63
|
+
* message will be queued — the next thought is typed while the last one is
|
|
64
|
+
* still being answered, never held until a spinner stops. */
|
|
65
|
+
queue?: boolean;
|
|
44
66
|
/** Attachments on, optionally with the app's own limits. Off by default:
|
|
45
67
|
* only offer what the app's API accepts. */
|
|
46
68
|
attach?: boolean | Partial<AttachmentLimits>;
|
|
@@ -81,5 +103,5 @@ export type ComposerProps = {
|
|
|
81
103
|
* Extracted from loki `components/composer/Composer.tsx` (itself the merge of
|
|
82
104
|
* four composers in one app), with heidi's microphone and orangecat's lessons.
|
|
83
105
|
*/
|
|
84
|
-
export declare function Composer({ onSend, placeholder, ariaLabel, disabled, sendBlockedReason, sending, onStop, attachmentOnlyText, attach, voice, modes, mode, onModeChange, onEmptySlash, value, onValueChange, defaultValue, inputRef, density, above, header, tools, trailing, footer, hint, labels: labelOverrides, autoFocus, }: ComposerProps): import("react").JSX.Element;
|
|
106
|
+
export declare function Composer({ onSend, placeholder, ariaLabel, disabled, sendBlockedReason, sending, onStop, attachmentOnlyText, heldAttachments, queue, attach, voice, modes, mode, onModeChange, onEmptySlash, value, onValueChange, defaultValue, inputRef, density, above, header, tools, trailing, footer, hint, labels: labelOverrides, autoFocus, }: ComposerProps): import("react").JSX.Element;
|
|
85
107
|
//# sourceMappingURL=Composer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../src/react/Composer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+B,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACpF,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,
|
|
1
|
+
{"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../src/react/Composer.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/Composer.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { appendTranscript, composerCanSend, composerOutgoingText, formatElapsed, shouldClearDraft, } from "../composer.js";
|
|
5
|
-
import { DEFAULT_DICTATION_MESSAGES } from "../dictation.js";
|
|
5
|
+
import { DEFAULT_DICTATION_MESSAGES, isAudioFile } from "../dictation.js";
|
|
6
6
|
import { DEFAULT_ATTACHMENT_NOTES, } from "../attachments.js";
|
|
7
7
|
import { useDictation } from "./use-dictation.js";
|
|
8
8
|
import { useAttachments } from "./use-attachments.js";
|
|
9
9
|
import { useAutoGrow } from "./hooks.js";
|
|
10
|
-
import {
|
|
10
|
+
import { AttachMenu, DEFAULT_ATTACH_MENU_LABELS } from "./AttachMenu.js";
|
|
11
|
+
import { IconArrowUp, IconCheck, IconFile, IconMic, IconSpinner, IconStop, IconX, } from "./icons.js";
|
|
11
12
|
export const DEFAULT_COMPOSER_LABELS = {
|
|
12
13
|
send: "Send",
|
|
13
14
|
stop: "Stop generating",
|
|
@@ -18,8 +19,15 @@ export const DEFAULT_COMPOSER_LABELS = {
|
|
|
18
19
|
listening: "Listening…",
|
|
19
20
|
transcribing: "Transcribing…",
|
|
20
21
|
attach: "Attach a screenshot or file",
|
|
22
|
+
attachMenu: {
|
|
23
|
+
title: DEFAULT_ATTACH_MENU_LABELS.title,
|
|
24
|
+
close: DEFAULT_ATTACH_MENU_LABELS.close,
|
|
25
|
+
sources: DEFAULT_ATTACH_MENU_LABELS.sources,
|
|
26
|
+
},
|
|
27
|
+
queue: "Queue a message…",
|
|
21
28
|
remove: (name) => `Remove ${name}`,
|
|
22
29
|
dismiss: "Dismiss",
|
|
30
|
+
retryTake: "Try again",
|
|
23
31
|
dictation: DEFAULT_DICTATION_MESSAGES,
|
|
24
32
|
attachNotes: DEFAULT_ATTACHMENT_NOTES,
|
|
25
33
|
};
|
|
@@ -31,6 +39,21 @@ function AttachmentStrip({ attachments, labels, }) {
|
|
|
31
39
|
return (_jsxs("div", { className: "ck-attach-item", children: [a.kind === "image" && a.previewUrl ? (_jsx("img", { src: a.previewUrl, alt: a.name, className: "ck-attach-thumb" })) : (_jsxs("span", { className: "ck-attach-file", children: [_jsx(IconFile, {}), _jsx("span", { className: "ck-truncate", children: a.name })] })), _jsx("button", { type: "button", className: "ck-attach-remove", onClick: () => attachments.remove(key), "aria-label": labels.remove(a.name), title: labels.remove(a.name), children: _jsx(IconX, {}) })] }, key));
|
|
32
40
|
}), attachments.note && (_jsx("button", { type: "button", className: "ck-note", onClick: attachments.clearNote, children: attachments.note }))] }));
|
|
33
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The words as they are heard, one line, always showing its end: the
|
|
44
|
+
* newest words are what the person is checking. Scrolled by hand rather
|
|
45
|
+
* than laid out right-to-left, so punctuation stays where the language
|
|
46
|
+
* puts it.
|
|
47
|
+
*/
|
|
48
|
+
function LiveWords({ text, dim }) {
|
|
49
|
+
const ref = useRef(null);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const el = ref.current;
|
|
52
|
+
if (el)
|
|
53
|
+
el.scrollLeft = el.scrollWidth;
|
|
54
|
+
}, [text]);
|
|
55
|
+
return (_jsx("span", { ref: ref, className: dim ? "ck-voice-live ck-voice-live-dim" : "ck-voice-live", children: text }));
|
|
56
|
+
}
|
|
34
57
|
function Elapsed({ since }) {
|
|
35
58
|
const [now, setNow] = useState(() => Date.now());
|
|
36
59
|
useEffect(() => {
|
|
@@ -49,12 +72,20 @@ function Elapsed({ since }) {
|
|
|
49
72
|
* Extracted from loki `components/composer/Composer.tsx` (itself the merge of
|
|
50
73
|
* four composers in one app), with heidi's microphone and orangecat's lessons.
|
|
51
74
|
*/
|
|
52
|
-
export function Composer({ onSend, placeholder, ariaLabel, disabled = false, sendBlockedReason = null, sending = false, onStop, attachmentOnlyText, attach = false, voice = {}, modes, mode, onModeChange, onEmptySlash, value, onValueChange, defaultValue = "", inputRef, density = "comfortable", above, header, tools, trailing, footer, hint, labels: labelOverrides, autoFocus, }) {
|
|
75
|
+
export function Composer({ onSend, placeholder, ariaLabel, disabled = false, sendBlockedReason = null, sending = false, onStop, attachmentOnlyText, heldAttachments = 0, queue = false, attach = false, voice = {}, modes, mode, onModeChange, onEmptySlash, value, onValueChange, defaultValue = "", inputRef, density = "comfortable", above, header, tools, trailing, footer, hint, labels: labelOverrides, autoFocus, }) {
|
|
53
76
|
const labels = {
|
|
54
77
|
...DEFAULT_COMPOSER_LABELS,
|
|
55
78
|
...labelOverrides,
|
|
56
79
|
dictation: { ...DEFAULT_COMPOSER_LABELS.dictation, ...labelOverrides?.dictation },
|
|
57
80
|
attachNotes: { ...DEFAULT_COMPOSER_LABELS.attachNotes, ...labelOverrides?.attachNotes },
|
|
81
|
+
attachMenu: {
|
|
82
|
+
...DEFAULT_COMPOSER_LABELS.attachMenu,
|
|
83
|
+
...labelOverrides?.attachMenu,
|
|
84
|
+
sources: {
|
|
85
|
+
...DEFAULT_COMPOSER_LABELS.attachMenu.sources,
|
|
86
|
+
...labelOverrides?.attachMenu?.sources,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
58
89
|
};
|
|
59
90
|
const [ownText, setOwnText] = useState(defaultValue);
|
|
60
91
|
const controlled = value !== undefined;
|
|
@@ -84,7 +115,7 @@ export function Composer({ onSend, placeholder, ariaLabel, disabled = false, sen
|
|
|
84
115
|
const voiceOn = voice !== false && dictation.supported;
|
|
85
116
|
const listening = dictation.status === "listening";
|
|
86
117
|
const transcribing = dictation.status === "transcribing";
|
|
87
|
-
const attachCount = attachOn ? attachments.attachments.length : 0;
|
|
118
|
+
const attachCount = (attachOn ? attachments.attachments.length : 0) + Math.max(0, heldAttachments);
|
|
88
119
|
const canSend = composerCanSend({
|
|
89
120
|
text,
|
|
90
121
|
attachmentCount: attachCount,
|
|
@@ -92,6 +123,7 @@ export function Composer({ onSend, placeholder, ariaLabel, disabled = false, sen
|
|
|
92
123
|
sending,
|
|
93
124
|
disabled,
|
|
94
125
|
blocked: Boolean(sendBlockedReason),
|
|
126
|
+
queue,
|
|
95
127
|
});
|
|
96
128
|
const submit = async () => {
|
|
97
129
|
if (!canSend)
|
|
@@ -107,9 +139,29 @@ export function Composer({ onSend, placeholder, ariaLabel, disabled = false, sen
|
|
|
107
139
|
setText("");
|
|
108
140
|
attachments.clear();
|
|
109
141
|
};
|
|
110
|
-
const fileInput = useRef(null);
|
|
111
142
|
const showModes = Boolean(modes && modes.length > 1);
|
|
112
|
-
|
|
143
|
+
// The paperclip takes recordings too (through Files), when there is
|
|
144
|
+
// somewhere to send them. A voice memo is not an attachment the model reads —
|
|
145
|
+
// it is words the person said, so it goes to the same server leg as the mic
|
|
146
|
+
// and lands in the box. Recordings are transcribed in the order picked;
|
|
147
|
+
// everything else is staged.
|
|
148
|
+
const takesAudio = voice !== false && dictation.canTranscribeFile;
|
|
149
|
+
const onFiles = (files) => {
|
|
150
|
+
if (!files)
|
|
151
|
+
return;
|
|
152
|
+
const all = Array.from(files);
|
|
153
|
+
const audio = takesAudio ? all.filter(isAudioFile) : [];
|
|
154
|
+
const rest = all.filter((f) => !audio.includes(f));
|
|
155
|
+
if (rest.length > 0)
|
|
156
|
+
attachments.addFiles(rest);
|
|
157
|
+
if (audio.length > 0) {
|
|
158
|
+
void (async () => {
|
|
159
|
+
for (const file of audio)
|
|
160
|
+
await dictation.transcribeFile(file);
|
|
161
|
+
})();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
return (_jsxs("div", { className: "ck-composer-wrap", children: [above, _jsxs("div", { className: "ck-composer-frame", children: [(listening || transcribing) && (_jsx("div", { className: "ck-voice-bar", role: "status", "aria-live": "polite", children: listening ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "ck-voice-dot", "aria-hidden": true }), dictation.liveText ? (_jsx(LiveWords, { text: dictation.liveText })) : (_jsx("span", { className: "ck-voice-wave", "aria-hidden": true, children: Array.from({ length: 9 }).map((_, i) => (_jsx("span", {}, i))) })), _jsx(Elapsed, { since: dictation.startedAt }), _jsx("button", { type: "button", className: "ck-icon-btn", onClick: dictation.cancel, "aria-label": labels.cancelRecording, title: labels.cancelRecording, children: _jsx(IconX, {}) }), _jsx("button", { type: "button", className: "ck-voice-confirm", onClick: dictation.stop, "aria-label": labels.confirmRecording, title: labels.confirmRecording, children: _jsx(IconCheck, {}) })] })) : (_jsxs(_Fragment, { children: [_jsx(IconSpinner, {}), dictation.liveText ? (_jsx(LiveWords, { text: dictation.liveText, dim: true })) : (_jsx("span", { className: "ck-voice-timer", children: labels.transcribing }))] })) })), _jsxs("div", { className: density === "compact" ? "ck-composer ck-composer-compact" : "ck-composer", children: [showModes && (_jsx("div", { className: "ck-modes", role: "group", children: modes.map((m) => (_jsx("button", { type: "button", className: "ck-mode", "aria-pressed": m.id === mode, title: m.hint, onClick: () => onModeChange?.(m.id), children: m.label }, m.id))) })), header, _jsx("textarea", { ref: textareaRef, className: "ck-input", rows: 1, value: text, autoFocus: autoFocus, disabled: disabled || transcribing, placeholder: listening ? labels.listening : sending && queue ? labels.queue : placeholder, "aria-label": ariaLabel ?? placeholder, enterKeyHint: "send", onChange: (e) => {
|
|
113
165
|
const next = e.target.value;
|
|
114
166
|
if (onEmptySlash && next === "/" && text === "") {
|
|
115
167
|
onEmptySlash();
|
|
@@ -124,9 +176,6 @@ export function Composer({ onSend, placeholder, ariaLabel, disabled = false, sen
|
|
|
124
176
|
e.preventDefault();
|
|
125
177
|
void submit();
|
|
126
178
|
}
|
|
127
|
-
} }), attachOn && _jsx(AttachmentStrip, { attachments: attachments, labels: labels }), _jsxs("div", { className: "ck-actions", children: [_jsxs("div", { className: "ck-tools", children: [attachOn && (_jsxs(_Fragment, { children: [_jsx("
|
|
128
|
-
attachments.addFiles(e.target.files);
|
|
129
|
-
e.target.value = "";
|
|
130
|
-
} }), _jsx("button", { type: "button", className: "ck-icon-btn", onClick: () => fileInput.current?.click(), disabled: disabled || attachments.full, "aria-label": labels.attach, title: labels.attach, children: _jsx(IconPaperclip, {}) })] })), voiceOn && (_jsx("button", { type: "button", className: listening ? "ck-icon-btn ck-mic ck-mic-on" : "ck-icon-btn ck-mic", onClick: dictation.toggle, disabled: disabled || transcribing, "aria-label": listening ? labels.voiceStop : labels.voice, "aria-pressed": listening, title: listening ? labels.voiceStop : labels.voice, children: transcribing ? _jsx(IconSpinner, {}) : _jsx(IconMic, {}) })), tools, hint && _jsx("span", { className: "ck-hint", children: hint })] }), _jsxs("div", { className: "ck-submit", children: [trailing, sending && onStop ? (_jsx("button", { type: "button", className: "ck-send ck-send-stop", onClick: onStop, "aria-label": labels.stop, title: labels.stop, children: _jsx(IconStop, {}) })) : (!listening && (_jsx("button", { type: "button", className: "ck-send", disabled: !canSend, onClick: () => void submit(), "aria-label": labels.send, title: sendBlockedReason ?? labels.send, children: sending ? _jsx(IconSpinner, {}) : _jsx(IconArrowUp, {}) })))] })] }), footer] })] }), dictation.problem && (_jsxs("p", { className: "ck-problem", role: "status", children: [_jsx("span", { children: labels.dictation[dictation.problem] }), _jsx("button", { type: "button", className: "ck-problem-x", onClick: dictation.clearProblem, "aria-label": labels.dismiss, children: _jsx(IconX, {}) })] }))] }));
|
|
179
|
+
} }), attachOn && _jsx(AttachmentStrip, { attachments: attachments, labels: labels }), _jsxs("div", { className: "ck-actions", children: [_jsxs("div", { className: "ck-tools", children: [attachOn && (_jsx(AttachMenu, { onFiles: onFiles, disabled: disabled || attachments.full, labels: { attach: labels.attach, ...labels.attachMenu } })), voiceOn && (_jsx("button", { type: "button", className: listening ? "ck-icon-btn ck-mic ck-mic-on" : "ck-icon-btn ck-mic", onClick: dictation.toggle, disabled: disabled || transcribing, "aria-label": listening ? labels.voiceStop : labels.voice, "aria-pressed": listening, title: listening ? labels.voiceStop : labels.voice, children: transcribing ? _jsx(IconSpinner, {}) : _jsx(IconMic, {}) })), tools, hint && _jsx("span", { className: "ck-hint", children: hint })] }), _jsxs("div", { className: "ck-submit", children: [trailing, sending && onStop ? (_jsxs(_Fragment, { children: [queue && canSend && (_jsx("button", { type: "button", className: "ck-send", onClick: () => void submit(), "aria-label": labels.send, title: labels.send, children: _jsx(IconArrowUp, {}) })), _jsx("button", { type: "button", className: "ck-send ck-send-stop", onClick: onStop, "aria-label": labels.stop, title: labels.stop, children: _jsx(IconStop, {}) })] })) : (!listening && (_jsx("button", { type: "button", className: "ck-send", disabled: !canSend, onClick: () => void submit(), "aria-label": labels.send, title: sendBlockedReason ?? labels.send, children: sending ? _jsx(IconSpinner, {}) : _jsx(IconArrowUp, {}) })))] })] }), footer] })] }), dictation.problem && (_jsxs("p", { className: "ck-problem", role: "status", children: [_jsxs("span", { children: [labels.dictation[dictation.problem], dictation.problemDetail && (_jsxs("span", { className: "ck-problem-detail", children: [" ", dictation.problemDetail] }))] }), dictation.canRetry && (_jsx("button", { type: "button", className: "ck-problem-retry", onClick: () => void dictation.retry(), disabled: dictation.status !== "idle", children: labels.retryTake })), _jsx("button", { type: "button", className: "ck-problem-x", onClick: dictation.clearProblem, "aria-label": labels.dismiss, children: _jsx(IconX, {}) })] }))] }));
|
|
131
180
|
}
|
|
132
181
|
//# sourceMappingURL=Composer.js.map
|