@longline/aqua-ui 1.0.331 → 1.0.335
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/hooks/useSpeechAIRecorder/index.d.ts +1 -0
- package/hooks/useSpeechAIRecorder/index.js +1 -0
- package/hooks/useSpeechAIRecorder/stories/SpeechAIAudioInput.d.ts +6 -0
- package/hooks/{useAssemblyAIRecorder/stories/AssemblyAudioInput.js → useSpeechAIRecorder/stories/SpeechAIAudioInput.js} +4 -4
- package/hooks/useSpeechAIRecorder/useSpeechAIRecorder.d.ts +29 -0
- package/hooks/{useAssemblyAIRecorder/useAssemblyAIRecorder.js → useSpeechAIRecorder/useSpeechAIRecorder.js} +13 -11
- package/hooks/useTextAIStream/index.d.ts +1 -0
- package/hooks/useTextAIStream/index.js +1 -0
- package/hooks/{useOpenAIStream/stories/OpenAIStreamInput.d.ts → useTextAIStream/stories/TextAIStreamInput.d.ts} +3 -3
- package/hooks/{useOpenAIStream/stories/OpenAIStreamInput.js → useTextAIStream/stories/TextAIStreamInput.js} +6 -6
- package/hooks/useTextAIStream/useTextAIStream.d.ts +26 -0
- package/hooks/{useOpenAIStream/useOpenAIStream.js → useTextAIStream/useTextAIStream.js} +16 -4
- package/inputs/DateInput/Selector.js +17 -10
- package/inputs/Editor/Editor.d.ts +7 -6
- package/inputs/Editor/Editor.js +1 -1
- package/inputs/Editor/buttons/{SpeechButton.d.ts → SpeechAIButton.d.ts} +2 -2
- package/inputs/Editor/buttons/{SpeechButton.js → SpeechAIButton.js} +5 -5
- package/inputs/Editor/buttons/TextAIButton.d.ts +28 -0
- package/inputs/Editor/buttons/{OpenAIButton.js → TextAIButton.js} +13 -14
- package/inputs/Editor/buttons/{OpenAIMenu.d.ts → TextAIMenu.d.ts} +2 -2
- package/inputs/Editor/buttons/{OpenAIMenu.js → TextAIMenu.js} +3 -3
- package/inputs/Editor/menu/MenuBar.d.ts +6 -6
- package/inputs/Editor/menu/MenuBar.js +7 -7
- package/package.json +4 -5
- package/hooks/useAssemblyAIRecorder/index.d.ts +0 -1
- package/hooks/useAssemblyAIRecorder/index.js +0 -1
- package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.d.ts +0 -6
- package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.d.ts +0 -27
- package/hooks/useOpenAIRecorder/index.d.ts +0 -1
- package/hooks/useOpenAIRecorder/index.js +0 -1
- package/hooks/useOpenAIRecorder/old/AudioInputNoHook.d.ts +0 -8
- package/hooks/useOpenAIRecorder/old/AudioInputNoHook.js +0 -192
- package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.d.ts +0 -5
- package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.js +0 -170
- package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.d.ts +0 -8
- package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.js +0 -17
- package/hooks/useOpenAIRecorder/useOpenAIRecorder.d.ts +0 -22
- package/hooks/useOpenAIRecorder/useOpenAIRecorder.js +0 -223
- package/hooks/useOpenAIStream/index.d.ts +0 -1
- package/hooks/useOpenAIStream/index.js +0 -1
- package/hooks/useOpenAIStream/useOpenAIStream.d.ts +0 -14
- package/inputs/Editor/buttons/OpenAIButton.d.ts +0 -29
- /package/hooks/{useAssemblyAIRecorder → useSpeechAIRecorder}/SpeechManager.d.ts +0 -0
- /package/hooks/{useAssemblyAIRecorder → useSpeechAIRecorder}/SpeechManager.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSpeechAIRecorder } from './useSpeechAIRecorder';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSpeechAIRecorder } from './useSpeechAIRecorder';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
var
|
|
2
|
+
import { useSpeechAIRecorder } from '../useSpeechAIRecorder';
|
|
3
|
+
var SpeechAIAudioInput = function (props) {
|
|
4
4
|
var _a = useState('initial'), transcript = _a[0], setTranscript = _a[1];
|
|
5
|
-
var _b =
|
|
5
|
+
var _b = useSpeechAIRecorder(props.url, setTranscript), recordingStatus = _b.recordingStatus, toggleRecording = _b.toggleRecording;
|
|
6
6
|
var getLabel = function (recordingStatus) {
|
|
7
7
|
switch (recordingStatus) {
|
|
8
8
|
case 'connecting': return "Connecting";
|
|
@@ -14,4 +14,4 @@ var AssemblyAudioInput = function (props) {
|
|
|
14
14
|
React.createElement("button", { onClick: toggleRecording }, getLabel(recordingStatus)),
|
|
15
15
|
React.createElement("p", { style: { border: 'solid 1px blue', minHeight: '200px', color: 'white' } }, transcript)));
|
|
16
16
|
};
|
|
17
|
-
export {
|
|
17
|
+
export { SpeechAIAudioInput };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type TranscriptCallback = (text: string) => void;
|
|
2
|
+
type TRecordingStatus = 'idle' | 'connecting' | 'recording';
|
|
3
|
+
/**
|
|
4
|
+
* useSpeechAIRecorder
|
|
5
|
+
*
|
|
6
|
+
* A custom React hook for real-time audio transcription. The current
|
|
7
|
+
* implementation streams audio over a WebSocket to AssemblyAI's v3 streaming
|
|
8
|
+
* endpoint, having first obtained a temporary token from the supplied
|
|
9
|
+
* back-end URL. Switching providers would mean rewriting this hook's
|
|
10
|
+
* networking — the public interface is provider-agnostic.
|
|
11
|
+
*
|
|
12
|
+
* When recording is toggled on, it takes a few moments to establish a
|
|
13
|
+
* WebSocket connection. During this time, the recording status will be
|
|
14
|
+
* `'connecting'`.
|
|
15
|
+
*
|
|
16
|
+
* @param url - Back-end URL for obtaining a temporary streaming token.
|
|
17
|
+
* @param onTranscript - Callback that receives updated transcript text.
|
|
18
|
+
* @returns An object containing:
|
|
19
|
+
* - recordingStatus: 'idle' | 'connecting' | 'recording'
|
|
20
|
+
* - toggleRecording: function to start or stop recording
|
|
21
|
+
*
|
|
22
|
+
* The `SpeechManager` singleton tracks the active recorder; when a new
|
|
23
|
+
* recording is started, the previous one is stopped.
|
|
24
|
+
*/
|
|
25
|
+
declare const useSpeechAIRecorder: (url: string, onTranscript: TranscriptCallback, authToken?: string) => {
|
|
26
|
+
recordingStatus: TRecordingStatus;
|
|
27
|
+
toggleRecording: () => Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
export { useSpeechAIRecorder, TRecordingStatus };
|
|
@@ -46,26 +46,28 @@ import { SpeechManager } from './SpeechManager';
|
|
|
46
46
|
//
|
|
47
47
|
var moduleScript = /* js */ "\nconst MAX_16BIT_INT = 32767\nclass AudioProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n\n // Input is browser sample rate (typically 48000 Hz in Firefox)\n // This variable is globally available inside the AudioWorkletProcessor\n // scope.\n this.inputSampleRate = sampleRate;\n console.log('Detected input sample rate:', sampleRate);\n this.targetSampleRate = 16000;\n this.ratio = this.inputSampleRate / this.targetSampleRate;\n\n // Buffer of input samples for resampling\n this.buffer = [];\n\n // Number of samples per output chunk\n this.chunkSize = 1600; // ~100ms at 16kHz\n }\n\n /**\n * Resample the input buffer to the target sample rate using linear interpolation.\n */\n resample(inputBuffer) {\n const outputLength = Math.floor(inputBuffer.length / this.ratio);\n const output = new Float32Array(outputLength);\n\n for (let i = 0; i < outputLength; i++) {\n const idx = i * this.ratio;\n const idxInt = Math.floor(idx);\n const idxFrac = idx - idxInt;\n\n const sample1 = inputBuffer[idxInt] || 0;\n const sample2 = inputBuffer[idxInt + 1] || 0;\n output[i] = sample1 + (sample2 - sample1) * idxFrac;\n }\n\n return output;\n }\n\n /**\n * Converts Float32 PCM [-1, 1] to 16-bit signed PCM\n */\n floatTo16BitPCM(float32Array) {\n const int16Array = new Int16Array(float32Array.length);\n for (let i = 0; i < float32Array.length; i++) {\n let s = Math.max(-1, Math.min(1, float32Array[i]));\n int16Array[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;\n }\n return int16Array;\n }\n\n /**\n * Called automatically by browser on each audio frame (128 samples per channel).\n */\n process(inputs, outputs, parameters) {\n try {\n const input = inputs[0];\n if (!input || input.length === 0) return true;\n\n // Read from mono channel\n const channelData = input[0];\n this.buffer.push(...channelData);\n\n // Only process if we have enough input to produce a full output chunk\n const neededInputSamples = this.chunkSize * this.ratio;\n if (this.buffer.length >= neededInputSamples) {\n const inputForChunk = this.buffer.slice(0, neededInputSamples);\n const resampled = this.resample(inputForChunk);\n const pcm = this.floatTo16BitPCM(resampled);\n\n this.port.postMessage({ audio_data: pcm }); // Send Int16Array to main thread\n\n // Remove consumed input samples\n this.buffer = this.buffer.slice(neededInputSamples);\n }\n\n return true; // Keep processor alive\n } catch (error) {\n console.error(error)\n return false\n }\n }\n}\n\nregisterProcessor('audio-processor', AudioProcessor);\n";
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* useSpeechAIRecorder
|
|
50
50
|
*
|
|
51
|
-
* A custom React hook for real-time audio transcription
|
|
52
|
-
* streaming
|
|
51
|
+
* A custom React hook for real-time audio transcription. The current
|
|
52
|
+
* implementation streams audio over a WebSocket to AssemblyAI's v3 streaming
|
|
53
|
+
* endpoint, having first obtained a temporary token from the supplied
|
|
54
|
+
* back-end URL. Switching providers would mean rewriting this hook's
|
|
55
|
+
* networking — the public interface is provider-agnostic.
|
|
53
56
|
*
|
|
54
|
-
* When recording is toggled on, it takes a few moments to establish a
|
|
55
|
-
*
|
|
57
|
+
* When recording is toggled on, it takes a few moments to establish a
|
|
58
|
+
* WebSocket connection. During this time, the recording status will be
|
|
56
59
|
* `'connecting'`.
|
|
57
60
|
*
|
|
58
|
-
* @param url - Back-end URL for obtaining temporary token
|
|
61
|
+
* @param url - Back-end URL for obtaining a temporary streaming token.
|
|
59
62
|
* @param onTranscript - Callback that receives updated transcript text.
|
|
60
63
|
* @returns An object containing:
|
|
61
64
|
* - recordingStatus: 'idle' | 'connecting' | 'recording'
|
|
62
65
|
* - toggleRecording: function to start or stop recording
|
|
63
66
|
*
|
|
64
|
-
* The `SpeechManager` singleton
|
|
65
|
-
*
|
|
66
|
-
* the current one is stopped.
|
|
67
|
+
* The `SpeechManager` singleton tracks the active recorder; when a new
|
|
68
|
+
* recording is started, the previous one is stopped.
|
|
67
69
|
*/
|
|
68
|
-
var
|
|
70
|
+
var useSpeechAIRecorder = function (url, onTranscript, authToken) {
|
|
69
71
|
// State to track the current recording status
|
|
70
72
|
var _a = useState('idle'), recordingStatus = _a[0], setRecordingStatus = _a[1];
|
|
71
73
|
// WebSocket connection to AssemblyAI
|
|
@@ -294,4 +296,4 @@ var useAssemblyAIRecorder = function (url, onTranscript, authToken) {
|
|
|
294
296
|
toggleRecording: toggleRecording,
|
|
295
297
|
};
|
|
296
298
|
};
|
|
297
|
-
export {
|
|
299
|
+
export { useSpeechAIRecorder };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useTextAIStream } from './useTextAIStream';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useTextAIStream } from './useTextAIStream';
|
|
@@ -5,7 +5,7 @@ interface IProps {
|
|
|
5
5
|
*/
|
|
6
6
|
url: string;
|
|
7
7
|
/**
|
|
8
|
-
* Prompt to send
|
|
8
|
+
* Prompt to send.
|
|
9
9
|
*/
|
|
10
10
|
prompt: string;
|
|
11
11
|
/**
|
|
@@ -19,5 +19,5 @@ interface IProps {
|
|
|
19
19
|
*/
|
|
20
20
|
top_p?: number;
|
|
21
21
|
}
|
|
22
|
-
declare const
|
|
23
|
-
export {
|
|
22
|
+
declare const TextAIStreamInput: (props: IProps) => React.JSX.Element;
|
|
23
|
+
export { TextAIStreamInput };
|
|
@@ -35,16 +35,16 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
import React, { useState } from 'react';
|
|
38
|
-
import {
|
|
39
|
-
var
|
|
40
|
-
var
|
|
38
|
+
import { useTextAIStream } from '../useTextAIStream';
|
|
39
|
+
var TextAIStreamInput = function (props) {
|
|
40
|
+
var stream = useTextAIStream(props.url).stream;
|
|
41
41
|
var _a = useState(""), result = _a[0], setResult = _a[1];
|
|
42
42
|
var handleClick = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
43
43
|
return __generator(this, function (_a) {
|
|
44
44
|
switch (_a.label) {
|
|
45
45
|
case 0:
|
|
46
46
|
setResult("");
|
|
47
|
-
return [4 /*yield*/,
|
|
47
|
+
return [4 /*yield*/, stream(props.prompt, function (chunk) { return __awaiter(void 0, void 0, void 0, function () {
|
|
48
48
|
return __generator(this, function (_a) {
|
|
49
49
|
setResult(function (prev) { return prev + chunk; });
|
|
50
50
|
return [2 /*return*/];
|
|
@@ -60,7 +60,7 @@ var OpenAIStreamInput = function (props) {
|
|
|
60
60
|
React.createElement("div", null,
|
|
61
61
|
"Prompt: ",
|
|
62
62
|
props.prompt),
|
|
63
|
-
React.createElement("button", { onClick: handleClick }, "
|
|
63
|
+
React.createElement("button", { onClick: handleClick }, "Stream"),
|
|
64
64
|
React.createElement("p", { style: { border: 'solid 1px blue', minHeight: '200px', color: 'white' } }, result)));
|
|
65
65
|
};
|
|
66
|
-
export {
|
|
66
|
+
export { TextAIStreamInput };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
interface ITextAIStreamOptions {
|
|
2
|
+
temperature: number;
|
|
3
|
+
top_p: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Hook to stream text from a backend AI endpoint.
|
|
7
|
+
*
|
|
8
|
+
* The backend is expected to relay/translate its provider's output into
|
|
9
|
+
* OpenAI chat-completions Server-Sent Events:
|
|
10
|
+
*
|
|
11
|
+
* data: {"choices":[{"delta":{"content":"..."}}]}\n\n
|
|
12
|
+
* ...
|
|
13
|
+
* data: [DONE]\n\n
|
|
14
|
+
*
|
|
15
|
+
* This is the de-facto streaming format and is provider-agnostic in practice
|
|
16
|
+
* (many vendors expose OpenAI-compatible endpoints). Backends that wrap other
|
|
17
|
+
* providers (e.g. Anthropic) should translate their native SSE into this
|
|
18
|
+
* shape so the hook stays unchanged.
|
|
19
|
+
*
|
|
20
|
+
* @returns `stream`: a function that takes a prompt and streaming callback.
|
|
21
|
+
*/
|
|
22
|
+
declare const useTextAIStream: (url: string, authToken?: string) => {
|
|
23
|
+
stream: (prompt: string, onText: (text: string) => Promise<void>, options?: ITextAIStreamOptions) => Promise<void>;
|
|
24
|
+
cancel: () => void;
|
|
25
|
+
};
|
|
26
|
+
export { useTextAIStream, ITextAIStreamOptions };
|
|
@@ -45,11 +45,23 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
45
45
|
};
|
|
46
46
|
import { useCallback, useRef } from 'react';
|
|
47
47
|
/**
|
|
48
|
-
* Hook to stream
|
|
48
|
+
* Hook to stream text from a backend AI endpoint.
|
|
49
|
+
*
|
|
50
|
+
* The backend is expected to relay/translate its provider's output into
|
|
51
|
+
* OpenAI chat-completions Server-Sent Events:
|
|
52
|
+
*
|
|
53
|
+
* data: {"choices":[{"delta":{"content":"..."}}]}\n\n
|
|
54
|
+
* ...
|
|
55
|
+
* data: [DONE]\n\n
|
|
56
|
+
*
|
|
57
|
+
* This is the de-facto streaming format and is provider-agnostic in practice
|
|
58
|
+
* (many vendors expose OpenAI-compatible endpoints). Backends that wrap other
|
|
59
|
+
* providers (e.g. Anthropic) should translate their native SSE into this
|
|
60
|
+
* shape so the hook stays unchanged.
|
|
49
61
|
*
|
|
50
62
|
* @returns `stream`: a function that takes a prompt and streaming callback.
|
|
51
63
|
*/
|
|
52
|
-
var
|
|
64
|
+
var useTextAIStream = function (url, authToken) {
|
|
53
65
|
var abortRef = useRef(null);
|
|
54
66
|
/**
|
|
55
67
|
* Streams a prompt to a backend AI endpoint and handles streamed chunks.
|
|
@@ -92,7 +104,7 @@ var useOpenAIStream = function (url, authToken) {
|
|
|
92
104
|
case 1:
|
|
93
105
|
response = _f.sent();
|
|
94
106
|
if (!response.ok) {
|
|
95
|
-
console.error("
|
|
107
|
+
console.error("Text AI stream request failed: ".concat(response.statusText));
|
|
96
108
|
return [2 /*return*/];
|
|
97
109
|
}
|
|
98
110
|
if (!response.body) {
|
|
@@ -159,4 +171,4 @@ var useOpenAIStream = function (url, authToken) {
|
|
|
159
171
|
}, []);
|
|
160
172
|
return { stream: stream, cancel: cancel };
|
|
161
173
|
};
|
|
162
|
-
export {
|
|
174
|
+
export { useTextAIStream };
|
|
@@ -23,18 +23,25 @@ var Selector = function (props) {
|
|
|
23
23
|
setYear(props.value ? props.value.getFullYear().toString() : "");
|
|
24
24
|
}
|
|
25
25
|
}, [props.value]);
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
// Emit synchronously from the onChange handler rather than via a useEffect
|
|
27
|
+
// on [day, month, year]. Passive effects don't run until after paint, so
|
|
28
|
+
// an effect-based emit can be skipped if the next browser event (e.g. a
|
|
29
|
+
// click on Save) arrives before React's effect phase — losing the
|
|
30
|
+
// most-recent keystroke.
|
|
31
|
+
var emit = function (d, m, y) {
|
|
32
|
+
var numDay = parseInt(d);
|
|
33
|
+
var numMonth = parseInt(m);
|
|
34
|
+
var numYear = parseInt(y);
|
|
35
|
+
if (isNaN(numDay) || isNaN(numMonth) || isNaN(numYear))
|
|
32
36
|
return;
|
|
33
37
|
var date = new Date(numYear, numMonth - 1, numDay);
|
|
34
38
|
// Overwrite year, because Date.new converts 0-99 to 1900-1999.
|
|
35
39
|
date.setFullYear(numYear);
|
|
36
40
|
props.onChange(date);
|
|
37
|
-
}
|
|
41
|
+
};
|
|
42
|
+
var handleDayChange = function (v) { setDay(v); emit(v, month, year); };
|
|
43
|
+
var handleMonthChange = function (v) { setMonth(v); emit(day, v, year); };
|
|
44
|
+
var handleYearChange = function (v) { setYear(v); emit(day, month, v); };
|
|
38
45
|
// Move focus to month input.
|
|
39
46
|
var focusMonth = function () {
|
|
40
47
|
monthRef.current.focus();
|
|
@@ -46,10 +53,10 @@ var Selector = function (props) {
|
|
|
46
53
|
yearRef.current.select();
|
|
47
54
|
};
|
|
48
55
|
return (React.createElement(InputWrapper, { fluid: true, error: props.error, disabled: props.disabled, ghost: props.ghost, transparent: props.transparent, onClear: (props.clearable && props.value) ? props.onClear : null, icon: { url: SVG.Icons.Calendar, color: theme.colors.primary[3], onClick: props.onCalendar }, iconPosition: 'right' },
|
|
49
|
-
React.createElement(NumericInput, { disabled: props.disabled, width: 38, maxLength: 2, placeholder: "dd", value: day, onChange:
|
|
56
|
+
React.createElement(NumericInput, { disabled: props.disabled, width: 38, maxLength: 2, placeholder: "dd", value: day, onChange: handleDayChange, onBlur: focusMonth }),
|
|
50
57
|
"/",
|
|
51
|
-
React.createElement(NumericInput, { disabled: props.disabled, width: 38, maxLength: 2, placeholder: "mm", value: month, onChange:
|
|
58
|
+
React.createElement(NumericInput, { disabled: props.disabled, width: 38, maxLength: 2, placeholder: "mm", value: month, onChange: handleMonthChange, ref: monthRef, onBlur: focusYear }),
|
|
52
59
|
"/",
|
|
53
|
-
React.createElement(NumericInput, { disabled: props.disabled, width: 44, maxLength: 4, placeholder: "yyyy", value: year, onChange:
|
|
60
|
+
React.createElement(NumericInput, { disabled: props.disabled, width: 44, maxLength: 4, placeholder: "yyyy", value: year, onChange: handleYearChange, ref: yearRef })));
|
|
54
61
|
};
|
|
55
62
|
export { Selector };
|
|
@@ -50,15 +50,16 @@ interface IEditorProps extends ITestable {
|
|
|
50
50
|
*/
|
|
51
51
|
codeButtons?: boolean;
|
|
52
52
|
/**
|
|
53
|
-
* URL for
|
|
54
|
-
* If not present, no
|
|
53
|
+
* URL for the back-end Text AI endpoint (streamed text generation).
|
|
54
|
+
* If not present, no Text AI controls will be available.
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
textAIurl?: string;
|
|
57
57
|
/**
|
|
58
|
-
* URL for
|
|
59
|
-
*
|
|
58
|
+
* URL for the back-end Speech AI endpoint (issues a temporary streaming
|
|
59
|
+
* token for the speech-to-text provider).
|
|
60
|
+
* If not present, no Speech AI controls will be available.
|
|
60
61
|
*/
|
|
61
|
-
|
|
62
|
+
speechAIurl?: string;
|
|
62
63
|
/**
|
|
63
64
|
* Optional Bearer token for authenticated AI requests.
|
|
64
65
|
*/
|
package/inputs/Editor/Editor.js
CHANGED
|
@@ -91,7 +91,7 @@ var EditorBase = function (props) {
|
|
|
91
91
|
React.createElement("div", { "data-testid": props['data-testid'] || "Editor", className: props.className, ref: wrapperRef },
|
|
92
92
|
React.createElement(OverlayScrollbarsComponent, { className: "scroller ".concat(fullscreen ? 'fullscreen' : ''), defer: true, options: { scrollbars: { theme: 'os-theme-dark', autoHide: 'leave' } } },
|
|
93
93
|
!props.disabled && !props.ghost &&
|
|
94
|
-
React.createElement(MenuBar, { allowFullscreen: props.allowFullscreen, codeButtons: props.codeButtons, fullscreen: fullscreen, editor: editor, onToggleFullscreen: handleToggleFullscreen,
|
|
94
|
+
React.createElement(MenuBar, { allowFullscreen: props.allowFullscreen, codeButtons: props.codeButtons, fullscreen: fullscreen, editor: editor, onToggleFullscreen: handleToggleFullscreen, textAIurl: props.textAIurl, speechAIurl: props.speechAIurl, authToken: props.authToken }),
|
|
95
95
|
React.createElement(EditorContent, { editor: editor, ref: setRef })))));
|
|
96
96
|
};
|
|
97
97
|
var pulse = keyframes(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n from {\n opacity: .15;\n }\n to {\n opacity: 1;\n }\n"], ["\n from {\n opacity: .15;\n }\n to {\n opacity: 1;\n }\n"])));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { MenuButton } from '../menu/MenuButton';
|
|
3
3
|
import { SVG } from '../../../svg';
|
|
4
|
-
import {
|
|
5
|
-
var
|
|
4
|
+
import { useSpeechAIRecorder } from '../../../hooks/useSpeechAIRecorder';
|
|
5
|
+
var SpeechAIButton = function (props) {
|
|
6
6
|
var fromRef = useRef(null);
|
|
7
7
|
var transcriptRef = useRef("");
|
|
8
8
|
var onTranscript = function (text) {
|
|
@@ -12,7 +12,7 @@ var SpeechButton = function (props) {
|
|
|
12
12
|
view.dispatch(transaction);
|
|
13
13
|
transcriptRef.current = text;
|
|
14
14
|
};
|
|
15
|
-
var _a =
|
|
15
|
+
var _a = useSpeechAIRecorder(props.url, onTranscript, props.authToken), recordingStatus = _a.recordingStatus, toggleRecording = _a.toggleRecording;
|
|
16
16
|
useEffect(function () {
|
|
17
17
|
if (recordingStatus == 'recording') {
|
|
18
18
|
props.editor.setOptions({ editable: false });
|
|
@@ -26,6 +26,6 @@ var SpeechButton = function (props) {
|
|
|
26
26
|
var handleClick = function () {
|
|
27
27
|
toggleRecording();
|
|
28
28
|
};
|
|
29
|
-
return (React.createElement(MenuButton, { editor: props.editor, hint: React.createElement("span", { style: { whiteSpace: "nowrap" } }, "
|
|
29
|
+
return (React.createElement(MenuButton, { editor: props.editor, hint: React.createElement("span", { style: { whiteSpace: "nowrap" } }, "Speech-to-text"), onClick: handleClick, disabled: false, active: recordingStatus == 'connecting', highlighted: recordingStatus == 'recording', icon: { url: SVG.Icons.Microphone } }));
|
|
30
30
|
};
|
|
31
|
-
export {
|
|
31
|
+
export { SpeechAIButton };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Editor } from '@tiptap/react';
|
|
3
|
+
interface IProps {
|
|
4
|
+
editor: Editor;
|
|
5
|
+
/**
|
|
6
|
+
* URL for AI requests.
|
|
7
|
+
*/
|
|
8
|
+
url: string;
|
|
9
|
+
/**
|
|
10
|
+
* Optional Bearer token for authenticated requests.
|
|
11
|
+
*/
|
|
12
|
+
authToken?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `TextAIButton` provides a menu button that allows the user to run various
|
|
16
|
+
* AI-powered text transformations on selected editor content.
|
|
17
|
+
*
|
|
18
|
+
* Available transformations include:
|
|
19
|
+
* - Summarizing selected content
|
|
20
|
+
* - Extracting key points
|
|
21
|
+
* - Translating
|
|
22
|
+
*
|
|
23
|
+
* When triggered, the selected content is cut, sent to the configured Text AI
|
|
24
|
+
* back-end, and replaced with the transformed version. Streaming support
|
|
25
|
+
* allows real-time insertion with animated progress.
|
|
26
|
+
*/
|
|
27
|
+
declare const TextAIButton: (props: IProps) => React.JSX.Element;
|
|
28
|
+
export { TextAIButton };
|
|
@@ -37,24 +37,23 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
37
37
|
import React, { useState } from 'react';
|
|
38
38
|
import { marked } from 'marked';
|
|
39
39
|
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
40
|
+
import { useTextAIStream } from '../../../hooks/useTextAIStream';
|
|
41
|
+
import { TextAIMenu } from './TextAIMenu';
|
|
42
42
|
/**
|
|
43
|
-
* `
|
|
44
|
-
* AI-powered transformations on selected editor content.
|
|
43
|
+
* `TextAIButton` provides a menu button that allows the user to run various
|
|
44
|
+
* AI-powered text transformations on selected editor content.
|
|
45
45
|
*
|
|
46
46
|
* Available transformations include:
|
|
47
|
-
* - Telling a joke
|
|
48
47
|
* - Summarizing selected content
|
|
49
48
|
* - Extracting key points
|
|
50
|
-
* - Translating
|
|
49
|
+
* - Translating
|
|
51
50
|
*
|
|
52
|
-
* When triggered, the selected content is cut, sent to
|
|
53
|
-
* with the transformed version. Streaming support
|
|
54
|
-
* with animated progress.
|
|
51
|
+
* When triggered, the selected content is cut, sent to the configured Text AI
|
|
52
|
+
* back-end, and replaced with the transformed version. Streaming support
|
|
53
|
+
* allows real-time insertion with animated progress.
|
|
55
54
|
*/
|
|
56
|
-
var
|
|
57
|
-
var
|
|
55
|
+
var TextAIButton = function (props) {
|
|
56
|
+
var streamTextAI = useTextAIStream(props.url, props.authToken).stream;
|
|
58
57
|
var _a = useState(false), streaming = _a[0], setStreaming = _a[1];
|
|
59
58
|
// The DOM parser is only created once, then used many times as content
|
|
60
59
|
// is streamed in.
|
|
@@ -154,7 +153,7 @@ var OpenAIButton = function (props) {
|
|
|
154
153
|
// Create initial node.
|
|
155
154
|
_a.sent();
|
|
156
155
|
text = "";
|
|
157
|
-
return [4 /*yield*/,
|
|
156
|
+
return [4 /*yield*/, streamTextAI(prompt, function (chunk) { return __awaiter(void 0, void 0, void 0, function () {
|
|
158
157
|
return __generator(this, function (_a) {
|
|
159
158
|
switch (_a.label) {
|
|
160
159
|
case 0:
|
|
@@ -203,6 +202,6 @@ var OpenAIButton = function (props) {
|
|
|
203
202
|
props.editor.setOptions({ editable: true });
|
|
204
203
|
});
|
|
205
204
|
};
|
|
206
|
-
return (React.createElement(
|
|
205
|
+
return (React.createElement(TextAIMenu, { editor: props.editor, onClick: function (p) { return transformStream(p); }, animated: streaming }));
|
|
207
206
|
};
|
|
208
|
-
export {
|
|
207
|
+
export { TextAIButton };
|
|
@@ -5,5 +5,5 @@ interface IProps {
|
|
|
5
5
|
onClick: (prompt: (text: string) => string) => void;
|
|
6
6
|
animated: boolean;
|
|
7
7
|
}
|
|
8
|
-
declare const
|
|
9
|
-
export {
|
|
8
|
+
declare const TextAIMenu: (props: IProps) => React.JSX.Element;
|
|
9
|
+
export { TextAIMenu };
|
|
@@ -4,7 +4,7 @@ import { MenuButton } from '../menu/MenuButton';
|
|
|
4
4
|
import { List, ListRow } from '../../../containers/List';
|
|
5
5
|
import { SVG } from '../../../svg';
|
|
6
6
|
import { Icon } from '../../../controls/Icon';
|
|
7
|
-
var
|
|
7
|
+
var TextAIMenu = function (props) {
|
|
8
8
|
var _a = React.useState(false), open = _a[0], setOpen = _a[1];
|
|
9
9
|
var _b = React.useState(false), langOpen = _b[0], setLangOpen = _b[1];
|
|
10
10
|
// With an empty selection, some AI commands cannot work, because they
|
|
@@ -19,7 +19,7 @@ var OpenAIMenu = function (props) {
|
|
|
19
19
|
setLangOpen(false);
|
|
20
20
|
}, [open]);
|
|
21
21
|
var languages = ["US English", "UK English", "Portuguese", "Spanish", "French", "German", "Dutch", "Simplified Chinese"];
|
|
22
|
-
return (React.createElement(Openable, { width: 300, toggle: React.createElement(MenuButton, { editor: props.editor, onClick: function () { return setOpen(!open); }, hint: React.createElement("span", { style: { whiteSpace: "nowrap" } }, "
|
|
22
|
+
return (React.createElement(Openable, { width: 300, toggle: React.createElement(MenuButton, { editor: props.editor, onClick: function () { return setOpen(!open); }, hint: React.createElement("span", { style: { whiteSpace: "nowrap" } }, "Text AI"), icon: { url: SVG.Editor.AI, animated: props.animated } }), content: React.createElement("div", { style: { display: 'flex', gap: '8px' } },
|
|
23
23
|
React.createElement("div", { style: { minWidth: '150px' } },
|
|
24
24
|
React.createElement(List, { maxItems: 4, contract: true },
|
|
25
25
|
React.createElement(ListRow, { disabled: selectionIsEmpty, onClick: function () { return handleClick(function (text) {
|
|
@@ -39,4 +39,4 @@ var OpenAIMenu = function (props) {
|
|
|
39
39
|
}); } }, lang);
|
|
40
40
|
})))), open: open, onClose: function () { return setOpen(false); } }));
|
|
41
41
|
};
|
|
42
|
-
export {
|
|
42
|
+
export { TextAIMenu };
|
|
@@ -16,15 +16,15 @@ interface IProps {
|
|
|
16
16
|
*/
|
|
17
17
|
codeButtons?: boolean;
|
|
18
18
|
/**
|
|
19
|
-
* URL for
|
|
20
|
-
* If not present, no AI controls will be available.
|
|
19
|
+
* URL for the back-end Text AI endpoint.
|
|
20
|
+
* If not present, no Text AI controls will be available.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
textAIurl?: string;
|
|
23
23
|
/**
|
|
24
|
-
* URL for
|
|
25
|
-
* If not present, no AI controls will be available.
|
|
24
|
+
* URL for the back-end Speech AI endpoint.
|
|
25
|
+
* If not present, no Speech AI controls will be available.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
speechAIurl?: string;
|
|
28
28
|
/**
|
|
29
29
|
* Optional Bearer token for authenticated AI requests.
|
|
30
30
|
*/
|
|
@@ -13,8 +13,8 @@ import { OrderedListButton } from '../buttons/OrderedListButton';
|
|
|
13
13
|
import { BulletListButton } from '../buttons/BulletListButton';
|
|
14
14
|
import { CodeBlockButton } from '../buttons/CodeBlockButton';
|
|
15
15
|
import { FullscreenButton } from '../buttons/FullscreenButton';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { TextAIButton } from '../buttons/TextAIButton';
|
|
17
|
+
import { SpeechAIButton } from '../buttons/SpeechAIButton';
|
|
18
18
|
var MenuBarBase = function (props) {
|
|
19
19
|
if (!props.editor) {
|
|
20
20
|
return null;
|
|
@@ -31,11 +31,11 @@ var MenuBarBase = function (props) {
|
|
|
31
31
|
React.createElement(MenuSeparator, null),
|
|
32
32
|
React.createElement(CodeButton, { editor: props.editor }),
|
|
33
33
|
React.createElement(CodeBlockButton, { editor: props.editor })),
|
|
34
|
-
(props.
|
|
35
|
-
props.
|
|
36
|
-
React.createElement(
|
|
37
|
-
props.
|
|
38
|
-
React.createElement(
|
|
34
|
+
(props.textAIurl || props.allowFullscreen) && React.createElement(MenuSeparator, null),
|
|
35
|
+
props.textAIurl &&
|
|
36
|
+
React.createElement(TextAIButton, { url: props.textAIurl, editor: props.editor, authToken: props.authToken }),
|
|
37
|
+
props.speechAIurl &&
|
|
38
|
+
React.createElement(SpeechAIButton, { url: props.speechAIurl, editor: props.editor, authToken: props.authToken }),
|
|
39
39
|
props.allowFullscreen &&
|
|
40
40
|
React.createElement(FullscreenButton, { fullscreen: props.fullscreen, editor: props.editor, onClick: props.onToggleFullscreen }))));
|
|
41
41
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longline/aqua-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.335",
|
|
4
4
|
"description": "AquaUI",
|
|
5
5
|
"author": "Alexander van Oostenrijk / Longline Environment",
|
|
6
6
|
"license": "Commercial",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "webpack --config webpack.config.js && tsc --version && tsc",
|
|
20
|
-
"storybook": "cross-env
|
|
21
|
-
"build-storybook": "cross-env
|
|
22
|
-
"build-storybook-gitlab": "cross-env
|
|
20
|
+
"storybook": "cross-env TEXTAI_RESPONSE_URL=http://api.flow/api/ai/anthropic/stream SPEECHAI_TOKEN_URL=http://api.flow/api/ai/assemblyai/get-token storybook dev -p 6006",
|
|
21
|
+
"build-storybook": "cross-env TEXTAI_RESPONSE_URL=http://api.flow/api/ai/anthropic/stream SPEECHAI_TOKEN_URL=http://api.flow/api/ai/assemblyai/get-token storybook build",
|
|
22
|
+
"build-storybook-gitlab": "cross-env TEXTAI_RESPONSE_URL=https://aquarisk.dev.longline.uk/api/ai/anthropic/stream SPEECHAI_TOKEN_URL=https://aquarisk.dev.longline.uk/api/ai/assemblyai/get-token storybook build",
|
|
23
23
|
"tsc": "tsc",
|
|
24
24
|
"pub": "npm version patch && webpack --config webpack.config.js && tsc && cd dist && npm publish"
|
|
25
25
|
},
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"gl-matrix": "^3.4.3",
|
|
59
59
|
"mapbox-gl": "^3.9.4",
|
|
60
60
|
"marked": "^15.0.12",
|
|
61
|
-
"openai": "^5.0.1",
|
|
62
61
|
"overlayscrollbars-react": "^0.5.6",
|
|
63
62
|
"react": "^18.3.1",
|
|
64
63
|
"react-dom": "^18.3.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useAssemblyAIRecorder } from './useAssemblyAIRecorder';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useAssemblyAIRecorder } from './useAssemblyAIRecorder';
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
type TranscriptCallback = (text: string) => void;
|
|
2
|
-
type TRecordingStatus = 'idle' | 'connecting' | 'recording';
|
|
3
|
-
/**
|
|
4
|
-
* useAssemblyAIRecorder
|
|
5
|
-
*
|
|
6
|
-
* A custom React hook for real-time audio transcription using AssemblyAI's
|
|
7
|
-
* streaming API.
|
|
8
|
-
*
|
|
9
|
-
* When recording is toggled on, it takes a few moments to establish a web
|
|
10
|
-
* socket connection. During this time, the recording status will be
|
|
11
|
-
* `'connecting'`.
|
|
12
|
-
*
|
|
13
|
-
* @param url - Back-end URL for obtaining temporary token
|
|
14
|
-
* @param onTranscript - Callback that receives updated transcript text.
|
|
15
|
-
* @returns An object containing:
|
|
16
|
-
* - recordingStatus: 'idle' | 'connecting' | 'recording'
|
|
17
|
-
* - toggleRecording: function to start or stop recording
|
|
18
|
-
*
|
|
19
|
-
* The `SpeechManager` singleton is use to keep track of which
|
|
20
|
-
* AssemblyAIRecorder is currently recording; when a new recording is started,
|
|
21
|
-
* the current one is stopped.
|
|
22
|
-
*/
|
|
23
|
-
declare const useAssemblyAIRecorder: (url: string, onTranscript: TranscriptCallback, authToken?: string) => {
|
|
24
|
-
recordingStatus: TRecordingStatus;
|
|
25
|
-
toggleRecording: () => Promise<void>;
|
|
26
|
-
};
|
|
27
|
-
export { useAssemblyAIRecorder, TRecordingStatus };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useOpenAIRecorder } from './useOpenAIRecorder';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useOpenAIRecorder } from './useOpenAIRecorder';
|