@longline/aqua-ui 1.0.196 → 1.0.198

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.
Files changed (69) hide show
  1. package/aquaui-sprites.svg +1 -1
  2. package/containers/List/ListRow.d.ts +5 -0
  3. package/containers/List/ListRow.js +3 -3
  4. package/containers/Openable/Openable.d.ts +26 -0
  5. package/containers/Openable/Openable.js +76 -0
  6. package/containers/Openable/index.d.ts +1 -0
  7. package/containers/Openable/index.js +1 -0
  8. package/controls/Fab/Fab.d.ts +7 -1
  9. package/controls/Fab/Fab.js +21 -4
  10. package/controls/SpeechRecognizer/EditorRegistry.d.ts +8 -0
  11. package/controls/SpeechRecognizer/EditorRegistry.js +24 -0
  12. package/controls/SpeechRecognizer/SpeechRecognizer.d.ts +3 -0
  13. package/controls/SpeechRecognizer/SpeechRecognizer.js +118 -0
  14. package/controls/SpeechRecognizer/index.d.ts +1 -0
  15. package/controls/SpeechRecognizer/index.js +1 -0
  16. package/formatters/CountryFormatter/Countries.js +0 -1
  17. package/hooks/useAssemblyAIRecorder/SpeechManager.d.ts +12 -0
  18. package/hooks/useAssemblyAIRecorder/SpeechManager.js +26 -0
  19. package/hooks/useAssemblyAIRecorder/index.d.ts +1 -0
  20. package/hooks/useAssemblyAIRecorder/index.js +1 -0
  21. package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.d.ts +6 -0
  22. package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.js +17 -0
  23. package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.d.ts +27 -0
  24. package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.js +294 -0
  25. package/hooks/useOpenAIRecorder/index.d.ts +1 -0
  26. package/hooks/useOpenAIRecorder/index.js +1 -0
  27. package/hooks/useOpenAIRecorder/old/AudioInputNoHook.d.ts +8 -0
  28. package/hooks/useOpenAIRecorder/old/AudioInputNoHook.js +192 -0
  29. package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.d.ts +5 -0
  30. package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.js +170 -0
  31. package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.d.ts +8 -0
  32. package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.js +17 -0
  33. package/hooks/useOpenAIRecorder/useOpenAIRecorder.d.ts +22 -0
  34. package/hooks/useOpenAIRecorder/useOpenAIRecorder.js +223 -0
  35. package/hooks/useOpenAIStream/index.d.ts +1 -0
  36. package/hooks/useOpenAIStream/index.js +1 -0
  37. package/hooks/useOpenAIStream/stories/OpenAIStreamInput.d.ts +23 -0
  38. package/hooks/useOpenAIStream/stories/OpenAIStreamInput.js +66 -0
  39. package/hooks/useOpenAIStream/useOpenAIStream.d.ts +14 -0
  40. package/hooks/useOpenAIStream/useOpenAIStream.js +159 -0
  41. package/inputs/Editor/Editor.d.ts +13 -3
  42. package/inputs/Editor/Editor.js +27 -24
  43. package/inputs/Editor/buttons/CodeBlockButton.js +1 -1
  44. package/inputs/Editor/buttons/OpenAIButton.d.ts +25 -0
  45. package/inputs/Editor/buttons/OpenAIButton.js +208 -0
  46. package/inputs/Editor/buttons/OpenAIMenu.d.ts +9 -0
  47. package/inputs/Editor/buttons/OpenAIMenu.js +42 -0
  48. package/inputs/Editor/buttons/SpeechButton.d.ts +8 -0
  49. package/inputs/Editor/buttons/SpeechButton.js +31 -0
  50. package/inputs/Editor/extensions/MarkElement.d.ts +9 -0
  51. package/inputs/Editor/extensions/MarkElement.js +35 -0
  52. package/inputs/Editor/extensions/StreamNode.d.ts +3 -0
  53. package/inputs/Editor/extensions/StreamNode.js +33 -0
  54. package/inputs/Editor/menu/MenuBar.d.ts +14 -3
  55. package/inputs/Editor/menu/MenuBar.js +14 -15
  56. package/inputs/Editor/menu/MenuButton.d.ts +7 -1
  57. package/inputs/Editor/menu/MenuButton.js +4 -5
  58. package/inputs/Editor/menu/MenuSeparator.js +1 -1
  59. package/package.json +10 -3
  60. package/svg/editor/index.d.ts +1 -1
  61. package/svg/editor/index.js +1 -1
  62. package/svg/icons/index.d.ts +1 -0
  63. package/svg/icons/index.js +1 -0
  64. package/inputs/Editor/AudioVisualizer.d.ts +0 -7
  65. package/inputs/Editor/AudioVisualizer.js +0 -81
  66. package/inputs/Editor/SpeechRecognizer.d.ts +0 -9
  67. package/inputs/Editor/SpeechRecognizer.js +0 -39
  68. package/inputs/Editor/buttons/RecordButton.d.ts +0 -9
  69. package/inputs/Editor/buttons/RecordButton.js +0 -8
@@ -0,0 +1,159 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
38
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
39
+ if (ar || !(i in from)) {
40
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
41
+ ar[i] = from[i];
42
+ }
43
+ }
44
+ return to.concat(ar || Array.prototype.slice.call(from));
45
+ };
46
+ import { useCallback, useRef } from 'react';
47
+ /**
48
+ * Hook to stream OpenAI responses via a backend endpoint that acts as a proxy.
49
+ *
50
+ * @returns `stream`: a function that takes a prompt and streaming callback.
51
+ */
52
+ var useOpenAIStream = function (url) {
53
+ var abortRef = useRef(null);
54
+ /**
55
+ * Streams a prompt to a backend AI endpoint and handles streamed chunks.
56
+ *
57
+ * @param prompt - The input prompt to send.
58
+ * @param onText - A callback invoked for each streamed text chunk.
59
+ */
60
+ var stream = useCallback(function (prompt_1, onText_1) {
61
+ var args_1 = [];
62
+ for (var _i = 2; _i < arguments.length; _i++) {
63
+ args_1[_i - 2] = arguments[_i];
64
+ }
65
+ return __awaiter(void 0, __spreadArray([prompt_1, onText_1], args_1, true), void 0, function (prompt, onText, options) {
66
+ var controller, response, reader, decoder, buffer, isDone, _a, done, value, parts, _b, parts_1, part, jsonStr, data, content, err_1;
67
+ var _c, _d, _e;
68
+ if (options === void 0) { options = { temperature: 0, top_p: 0 }; }
69
+ return __generator(this, function (_f) {
70
+ switch (_f.label) {
71
+ case 0:
72
+ controller = new AbortController();
73
+ abortRef.current = controller;
74
+ return [4 /*yield*/, fetch(url, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Content-Type': 'application/json',
78
+ 'Accept': 'text/event-stream',
79
+ 'Connection': 'keep-alive',
80
+ 'Cache-Control': 'no-cache, no-transform'
81
+ },
82
+ body: JSON.stringify({
83
+ input: prompt,
84
+ temperature: options.temperature || 0,
85
+ top_p: options.top_p || 0
86
+ }),
87
+ signal: controller.signal
88
+ })];
89
+ case 1:
90
+ response = _f.sent();
91
+ if (!response.ok) {
92
+ console.error("OpenAI stream request failed: ".concat(response.statusText));
93
+ return [2 /*return*/];
94
+ }
95
+ if (!response.body) {
96
+ console.error('No response body (ReadableStream) found.');
97
+ return [2 /*return*/];
98
+ }
99
+ reader = response.body.getReader();
100
+ decoder = new TextDecoder('utf-8');
101
+ buffer = '';
102
+ isDone = false;
103
+ _f.label = 2;
104
+ case 2:
105
+ if (!!isDone) return [3 /*break*/, 11];
106
+ return [4 /*yield*/, reader.read()];
107
+ case 3:
108
+ _a = _f.sent(), done = _a.done, value = _a.value;
109
+ if (done)
110
+ return [3 /*break*/, 11];
111
+ buffer += decoder.decode(value, { stream: true });
112
+ parts = buffer.split('\n\n');
113
+ buffer = parts.pop(); // Keep partial data for next chunk
114
+ _b = 0, parts_1 = parts;
115
+ _f.label = 4;
116
+ case 4:
117
+ if (!(_b < parts_1.length)) return [3 /*break*/, 10];
118
+ part = parts_1[_b];
119
+ if (!part.startsWith('data: ')) return [3 /*break*/, 9];
120
+ jsonStr = part.slice(6).trim();
121
+ if (jsonStr === '[DONE]') {
122
+ isDone = true;
123
+ return [3 /*break*/, 10];
124
+ }
125
+ _f.label = 5;
126
+ case 5:
127
+ _f.trys.push([5, 8, , 9]);
128
+ data = JSON.parse(jsonStr);
129
+ content = (_e = (_d = (_c = data.choices) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.delta) === null || _e === void 0 ? void 0 : _e.content;
130
+ if (!content) return [3 /*break*/, 7];
131
+ return [4 /*yield*/, onText(content)];
132
+ case 6:
133
+ _f.sent();
134
+ _f.label = 7;
135
+ case 7: return [3 /*break*/, 9];
136
+ case 8:
137
+ err_1 = _f.sent();
138
+ console.error('Failed to parse chunk:', jsonStr);
139
+ return [3 /*break*/, 9];
140
+ case 9:
141
+ _b++;
142
+ return [3 /*break*/, 4];
143
+ case 10: return [3 /*break*/, 2];
144
+ case 11: return [2 /*return*/];
145
+ }
146
+ });
147
+ });
148
+ }, [url]);
149
+ /**
150
+ * Cancels any ongoing stream.
151
+ */
152
+ var cancel = useCallback(function () {
153
+ var _a;
154
+ (_a = abortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
155
+ abortRef.current = null;
156
+ }, []);
157
+ return { stream: stream, cancel: cancel };
158
+ };
159
+ export { useOpenAIStream };
@@ -45,16 +45,26 @@ interface IEditorProps {
45
45
  */
46
46
  allowFullscreen?: boolean;
47
47
  /**
48
- * If set, speech recognition will be available (if the browser supports it).
48
+ * If set, adds code and code block buttons to menu bar.
49
49
  */
50
- allowSpeechRecognition?: boolean;
50
+ codeButtons?: boolean;
51
+ /**
52
+ * URL for OpenAI requests.
53
+ * If not present, no OpenAI controls will be available.
54
+ */
55
+ openAIurl?: string;
56
+ /**
57
+ * URL for AssemblyAI temporary token requests.
58
+ * If not present, no AssemblyAI controls will be available.
59
+ */
60
+ assemblyAIurl?: string;
51
61
  /**
52
62
  * Listeners are notified whenever the user interacts with the Editor.
53
63
  */
54
64
  onChange?: (value: any) => void;
55
65
  }
56
66
  declare const Editor: {
57
- ({ fluid, error, disabled, transparent, ghost, allowFullscreen, ...props }: IEditorProps): React.JSX.Element;
67
+ ({ fluid, error, disabled, transparent, ghost, allowFullscreen, codeButtons, ...props }: IEditorProps): React.JSX.Element;
58
68
  displayName: string;
59
69
  };
60
70
  export { Editor, IEditorProps };
@@ -25,35 +25,26 @@ var __rest = (this && this.__rest) || function (s, e) {
25
25
  return t;
26
26
  };
27
27
  import * as React from 'react';
28
- import styled, { css } from 'styled-components';
28
+ import styled, { css, keyframes } from 'styled-components';
29
29
  import { useEditor, EditorContent } from '@tiptap/react';
30
30
  import StarterKit from '@tiptap/starter-kit';
31
+ import { MarkElement } from './extensions/MarkElement';
32
+ import { StreamNode } from './extensions/StreamNode';
31
33
  import { MenuBar } from './menu/MenuBar';
32
34
  import { InputWrapper } from '../Input/InputWrapper';
33
- import { SpeechRecognizer } from './SpeechRecognizer';
35
+ import { EditorRegistry } from '../../controls/SpeechRecognizer/EditorRegistry';
34
36
  // define your extension array
35
- var extensions = [StarterKit];
37
+ var extensions = [StarterKit, MarkElement, StreamNode];
36
38
  var EditorBase = function (props) {
37
39
  // Ref to wrapper; used for fullscreen mode.
38
40
  var wrapperRef = React.useRef(null);
41
+ // Ref to actual editor div (with contentEditable).
42
+ var contentEditableRef = React.useRef(null);
39
43
  var _a = React.useState(false), fullscreen = _a[0], setFullscreen = _a[1];
40
- var _b = React.useState(false), recording = _b[0], setRecording = _b[1];
41
- // Clicking anywhere will cancel recording.
42
- var handleMousedown = function (e) {
43
- e.stopPropagation();
44
- setRecording(false);
45
- };
46
- React.useEffect(function () {
47
- document.addEventListener('click', handleMousedown);
48
- return function () { return document.removeEventListener('click', handleMousedown); };
49
- }, [recording]);
50
44
  var handleUpdate = function (p) {
51
45
  if (props.onChange)
52
46
  props.onChange(p.editor.getHTML());
53
47
  };
54
- var handleTranscript = function (transcript) {
55
- editor.commands.insertContent(transcript);
56
- };
57
48
  var handleFullscreenChange = function () {
58
49
  setFullscreen(document.fullscreenElement != null);
59
50
  };
@@ -78,19 +69,31 @@ var EditorBase = function (props) {
78
69
  React.useEffect(function () {
79
70
  editor.commands.setContent(props.value, true, { preserveWhitespace: "full" });
80
71
  }, [props.value]);
72
+ // Upon creation, an editor's contentEditable div is added to the
73
+ // EditorRegistry.
74
+ var setRef = function (r) {
75
+ if (r == null || r == contentEditableRef.current)
76
+ return;
77
+ contentEditableRef.current = r.querySelector('.tiptap');
78
+ EditorRegistry.addEditor(contentEditableRef.current, editor);
79
+ };
80
+ // Upon unmount, an editor is removed from the EditorRegistry.
81
+ React.useEffect(function () { return function () {
82
+ EditorRegistry.removeEditor(contentEditableRef.current);
83
+ }; }, []);
81
84
  return (React.createElement(InputWrapper, { fluid: props.fluid, ghost: props.ghost, error: props.error, disabled: props.disabled, transparent: props.transparent, flex: props.flex },
82
85
  React.createElement("div", { className: props.className, ref: wrapperRef },
83
- !props.disabled && !props.ghost &&
84
- React.createElement(MenuBar, { allowFullscreen: props.allowFullscreen, fullscreen: fullscreen, editor: editor, onToggleFullscreen: handleToggleFullscreen, allowSpeechRecognition: props.allowSpeechRecognition, recording: recording, onRecord: function () { return setRecording(!recording); } }),
85
86
  React.createElement(Wrapper, { className: fullscreen ? 'fullscreen' : '' },
86
- React.createElement(EditorContent, { editor: editor }),
87
- recording && React.createElement(SpeechRecognizer, { onTranscript: handleTranscript })))));
87
+ !props.disabled && !props.ghost &&
88
+ React.createElement(MenuBar, { allowFullscreen: props.allowFullscreen, codeButtons: props.codeButtons, fullscreen: fullscreen, editor: editor, onToggleFullscreen: handleToggleFullscreen, openAIurl: props.openAIurl, assemblyAIurl: props.assemblyAIurl }),
89
+ React.createElement(EditorContent, { editor: editor, ref: setRef })))));
88
90
  };
89
- var Wrapper = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n\n &.fullscreen {\n padding: 32px 32px 32px 32px;\n }\n\n .tiptap {\n border: none;\n outline: none;\n // Define colors for placeholder text.\n ::placeholder {\n color: rgb(from ", " r g b / 50%);\n opacity: 1 !important; /* Firefox applies opacity */\n }\n // Define colors for selected text.\n ::selection {\n background-color: ", ";\n color: ", ";\n } \n }\n\n // Content styles:\n .tiptap {\n code {\n background-color: ", ";\n color: ", ";\n outline: solid 1px ", ";\n border-radius: 4px;\n padding: 0 4px 0px 4px;\n }\n\n pre {\n background-color: ", ";\n border-radius: 4px;\n color: ", ";\n outline: solid 1px ", ";\n padding: 8px 12px 8px 12px;\n code {\n background-color: transparent;\n outline: none;\n border-radius: none;\n padding: 0 0 0 0;\n }\n }\n }\n"], ["\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n\n &.fullscreen {\n padding: 32px 32px 32px 32px;\n }\n\n .tiptap {\n border: none;\n outline: none;\n // Define colors for placeholder text.\n ::placeholder {\n color: rgb(from ", " r g b / 50%);\n opacity: 1 !important; /* Firefox applies opacity */\n }\n // Define colors for selected text.\n ::selection {\n background-color: ", ";\n color: ", ";\n } \n }\n\n // Content styles:\n .tiptap {\n code {\n background-color: ", ";\n color: ", ";\n outline: solid 1px ", ";\n border-radius: 4px;\n padding: 0 4px 0px 4px;\n }\n\n pre {\n background-color: ", ";\n border-radius: 4px;\n color: ", ";\n outline: solid 1px ", ";\n padding: 8px 12px 8px 12px;\n code {\n background-color: transparent;\n outline: none;\n border-radius: none;\n padding: 0 0 0 0;\n }\n }\n }\n"])), function (p) { return p.theme.colors.primary[3]; }, function (p) { return p.theme.colors.primary[2]; }, function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.theme.colors.primary[3]; }, function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.theme.colors.primary[1]; }, function (p) { return p.theme.colors.primary[3]; }, function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.theme.colors.primary[1]; });
90
- var EditorStyled = styled(EditorBase)(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n position: relative;\n box-sizing: border-box;\n width: 100%;\n background-color: ", ";\n\n // Editor has a fixed height, unless it flexes; then it will fill its \n // container vertically.\n height: 120px;\n ", "\n overflow-y: scroll;\n\n // When a menu bar is displayed, allow space for it.\n ", "\n"], ["\n position: relative;\n box-sizing: border-box;\n width: 100%;\n background-color: ", ";\n\n // Editor has a fixed height, unless it flexes; then it will fill its \n // container vertically.\n height: 120px;\n ", "\n overflow-y: scroll;\n\n // When a menu bar is displayed, allow space for it.\n ", "\n"])), function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.flex && css(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n height: 100%; \n "], ["\n height: 100%; \n "]))); }, function (p) { return !p.disabled && !p.ghost && css(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n ", " {\n margin-top: 30px;\n }\n "], ["\n ", " {\n margin-top: 30px;\n }\n "])), Wrapper); });
91
+ 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"])));
92
+ var Wrapper = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n overflow-y: scroll;\n\n &.fullscreen {\n padding: 32px 32px 32px 32px;\n }\n\n .tiptap {\n border: none;\n outline: none;\n // Define colors for placeholder text.\n ::placeholder {\n color: rgb(from ", " r g b / 50%);\n opacity: 1 !important; /* Firefox applies opacity */\n }\n // Define colors for selected text.\n ::selection {\n background-color: ", ";\n color: ", ";\n } \n }\n\n // Content styles:\n .tiptap {\n code {\n background-color: ", ";\n color: ", ";\n outline: solid 1px ", ";\n border-radius: 4px;\n padding: 0 4px 0px 4px;\n }\n\n pre {\n background-color: ", ";\n border-radius: 4px;\n color: ", ";\n outline: solid 1px ", ";\n padding: 8px 12px 8px 12px;\n code {\n background-color: transparent;\n outline: none;\n border-radius: none;\n padding: 0 0 0 0;\n }\n }\n\n // The 'mark' is used to show a small animated circle inside the last\n // element being rendered from AI input.\n mark {\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n color: transparent;\n background-color: ", ";\n animation: ", " 1s infinite alternate ease-in-out;\n vertical-align: middle;\n margin-left: 4px;\n margin-right: 4px;\n }\n }\n"], ["\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n overflow-y: scroll;\n\n &.fullscreen {\n padding: 32px 32px 32px 32px;\n }\n\n .tiptap {\n border: none;\n outline: none;\n // Define colors for placeholder text.\n ::placeholder {\n color: rgb(from ", " r g b / 50%);\n opacity: 1 !important; /* Firefox applies opacity */\n }\n // Define colors for selected text.\n ::selection {\n background-color: ", ";\n color: ", ";\n } \n }\n\n // Content styles:\n .tiptap {\n code {\n background-color: ", ";\n color: ", ";\n outline: solid 1px ", ";\n border-radius: 4px;\n padding: 0 4px 0px 4px;\n }\n\n pre {\n background-color: ", ";\n border-radius: 4px;\n color: ", ";\n outline: solid 1px ", ";\n padding: 8px 12px 8px 12px;\n code {\n background-color: transparent;\n outline: none;\n border-radius: none;\n padding: 0 0 0 0;\n }\n }\n\n // The 'mark' is used to show a small animated circle inside the last\n // element being rendered from AI input.\n mark {\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n color: transparent;\n background-color: ", ";\n animation: ", " 1s infinite alternate ease-in-out;\n vertical-align: middle;\n margin-left: 4px;\n margin-right: 4px;\n }\n }\n"])), function (p) { return p.theme.colors.primary[3]; }, function (p) { return p.theme.colors.primary[2]; }, function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.theme.colors.primary[3]; }, function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.theme.colors.primary[1]; }, function (p) { return p.theme.colors.primary[3]; }, function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.theme.colors.primary[1]; }, function (p) { return p.theme.colors.primary[1]; }, pulse);
93
+ var EditorStyled = styled(EditorBase)(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n position: relative;\n box-sizing: border-box;\n width: 100%;\n background-color: ", ";\n\n // Editor has a fixed height, unless it flexes; then it will fill its \n // container vertically.\n height: 120px;\n ", "\n"], ["\n position: relative;\n box-sizing: border-box;\n width: 100%;\n background-color: ", ";\n\n // Editor has a fixed height, unless it flexes; then it will fill its \n // container vertically.\n height: 120px;\n ", "\n"])), function (p) { return p.theme.colors.neutral[100]; }, function (p) { return p.flex && css(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n height: 100%; \n "], ["\n height: 100%; \n "]))); });
91
94
  var Editor = function (_a) {
92
- var _b = _a.fluid, fluid = _b === void 0 ? false : _b, _c = _a.error, error = _c === void 0 ? false : _c, _d = _a.disabled, disabled = _d === void 0 ? false : _d, _e = _a.transparent, transparent = _e === void 0 ? false : _e, _f = _a.ghost, ghost = _f === void 0 ? false : _f, _g = _a.allowFullscreen, allowFullscreen = _g === void 0 ? false : _g, props = __rest(_a, ["fluid", "error", "disabled", "transparent", "ghost", "allowFullscreen"]);
93
- return React.createElement(EditorStyled, __assign({ fluid: fluid, error: error, disabled: disabled, transparent: transparent, ghost: ghost, allowFullscreen: allowFullscreen }, props));
95
+ var _b = _a.fluid, fluid = _b === void 0 ? false : _b, _c = _a.error, error = _c === void 0 ? false : _c, _d = _a.disabled, disabled = _d === void 0 ? false : _d, _e = _a.transparent, transparent = _e === void 0 ? false : _e, _f = _a.ghost, ghost = _f === void 0 ? false : _f, _g = _a.allowFullscreen, allowFullscreen = _g === void 0 ? false : _g, _h = _a.codeButtons, codeButtons = _h === void 0 ? false : _h, props = __rest(_a, ["fluid", "error", "disabled", "transparent", "ghost", "allowFullscreen", "codeButtons"]);
96
+ return React.createElement(EditorStyled, __assign({ fluid: fluid, error: error, disabled: disabled, transparent: transparent, ghost: ghost, allowFullscreen: allowFullscreen, codeButtons: codeButtons }, props));
94
97
  };
95
98
  Editor.displayName = 'Editor';
96
99
  export { Editor };
@@ -2,6 +2,6 @@ import * as React from 'react';
2
2
  import { MenuButton } from '../menu/MenuButton';
3
3
  import { SVG } from '../../../svg';
4
4
  var CodeBlockButton = function (props) {
5
- return (React.createElement(MenuButton, { editor: props.editor, hint: "Code", keys: ["Ctrl", "Alt", "C"], onClick: function () { return props.editor.chain().focus().toggleCodeBlock().run(); }, active: props.editor.isActive('codeBlock'), icon: SVG.Editor.CodeBlock }));
5
+ return (React.createElement(MenuButton, { editor: props.editor, hint: "Code block", keys: ["Ctrl", "Alt", "C"], onClick: function () { return props.editor.chain().focus().toggleCodeBlock().run(); }, active: props.editor.isActive('codeBlock'), icon: SVG.Editor.CodeBlock }));
6
6
  };
7
7
  export { CodeBlockButton };
@@ -0,0 +1,25 @@
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
+ /**
11
+ * `AIButton` provides a menu button that allows the user to run various
12
+ * AI-powered transformations on selected editor content.
13
+ *
14
+ * Available transformations include:
15
+ * - Telling a joke
16
+ * - Summarizing selected content
17
+ * - Extracting key points
18
+ * - Translating to Portuguese
19
+ *
20
+ * When triggered, the selected content is cut, sent to OpenAI, and replaced
21
+ * with the transformed version. Streaming support allows real-time insertion
22
+ * with animated progress.
23
+ */
24
+ declare const OpenAIButton: (props: IProps) => React.JSX.Element;
25
+ export { OpenAIButton };
@@ -0,0 +1,208 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import React, { useState } from 'react';
38
+ import { marked } from 'marked';
39
+ import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
40
+ import { useOpenAIStream } from '../../../hooks/useOpenAIStream';
41
+ import { OpenAIMenu } from './OpenAIMenu';
42
+ /**
43
+ * `AIButton` provides a menu button that allows the user to run various
44
+ * AI-powered transformations on selected editor content.
45
+ *
46
+ * Available transformations include:
47
+ * - Telling a joke
48
+ * - Summarizing selected content
49
+ * - Extracting key points
50
+ * - Translating to Portuguese
51
+ *
52
+ * When triggered, the selected content is cut, sent to OpenAI, and replaced
53
+ * with the transformed version. Streaming support allows real-time insertion
54
+ * with animated progress.
55
+ */
56
+ var OpenAIButton = function (props) {
57
+ var streamOpenAI = useOpenAIStream(props.url).stream;
58
+ var _a = useState(false), streaming = _a[0], setStreaming = _a[1];
59
+ // The DOM parser is only created once, then used many times as content
60
+ // is streamed in.
61
+ var domParser = new DOMParser();
62
+ /**
63
+ * Copy the selected content, then remove it. The selection may be empty.
64
+ * @returns {string} The plain text that was selected and removed.
65
+ */
66
+ var cutEditorSelection = function () {
67
+ var _a = props.editor, state = _a.state, view = _a.view;
68
+ var selection = state.selection;
69
+ var slice = selection.content();
70
+ var text = slice.content.textBetween(0, slice.content.size, '\n');
71
+ view.dispatch(state.tr.deleteSelection());
72
+ return text;
73
+ };
74
+ /**
75
+ * Converts Markdown to HTML, optionally appending a `<mark>` element at the
76
+ * end to signal a live streaming update.
77
+ *
78
+ * TipTap requires HTML nodes to have content or they will be stripped out,
79
+ * so a dummy `"x"` is added to `<mark>`.
80
+ *
81
+ * @param text - The markdown text to convert.
82
+ * @param withMark - Whether to add an animated marker to the last element.
83
+ * @returns A parsed HTML Document containing the rendered result.
84
+ */
85
+ var markdownToHTML = function (text, withMark) { return __awaiter(void 0, void 0, void 0, function () {
86
+ var parsedText, html, lastChild, mark, error_1;
87
+ return __generator(this, function (_a) {
88
+ switch (_a.label) {
89
+ case 0:
90
+ _a.trys.push([0, 2, , 3]);
91
+ return [4 /*yield*/, marked.parse(text)];
92
+ case 1:
93
+ parsedText = _a.sent();
94
+ html = domParser.parseFromString(parsedText, 'text/html');
95
+ lastChild = html.querySelector("body *:last-child");
96
+ if (withMark && lastChild) {
97
+ mark = html.createElement('mark');
98
+ mark.innerHTML = "x"; // Mark must have content or TipTap will remove it.
99
+ lastChild.appendChild(mark);
100
+ }
101
+ return [2 /*return*/, html];
102
+ case 2:
103
+ error_1 = _a.sent();
104
+ return [2 /*return*/, domParser.parseFromString("<p>Error parsing markdown.</p>", "text/html")];
105
+ case 3: return [2 /*return*/];
106
+ }
107
+ });
108
+ }); };
109
+ var addNode = function (id, text, marker) { return __awaiter(void 0, void 0, void 0, function () {
110
+ var initialContent, initialContentNode, _a, state, view, from, node, tr;
111
+ return __generator(this, function (_b) {
112
+ switch (_b.label) {
113
+ case 0: return [4 /*yield*/, markdownToHTML(text, marker)];
114
+ case 1:
115
+ initialContent = _b.sent();
116
+ initialContentNode = ProseMirrorDOMParser.fromSchema(props.editor.schema).parse(initialContent.body);
117
+ _a = props.editor, state = _a.state, view = _a.view;
118
+ from = state.selection.from;
119
+ node = state.schema.nodes.stream.create({ streamId: id }, initialContentNode.content);
120
+ tr = state.tr.insert(from, node);
121
+ view.dispatch(tr);
122
+ return [2 /*return*/];
123
+ }
124
+ });
125
+ }); };
126
+ var removeNode = function (id) {
127
+ // Find the node with the ID:
128
+ var nodePos = null;
129
+ props.editor.state.doc.descendants(function (node, pos) {
130
+ if (node.type.name === 'stream' && node.attrs.streamId === id) {
131
+ nodePos = pos;
132
+ return false; // stop iteration
133
+ }
134
+ return true;
135
+ });
136
+ // Abort if not found.
137
+ if (nodePos == null)
138
+ return;
139
+ // Remove the node from the document.
140
+ var _a = props.editor, state = _a.state, view = _a.view;
141
+ var node = state.doc.nodeAt(nodePos);
142
+ var tr = state.tr.delete(nodePos, nodePos + node.nodeSize);
143
+ view.dispatch(tr);
144
+ };
145
+ var stream = function (prompt) { return __awaiter(void 0, void 0, void 0, function () {
146
+ var id, text;
147
+ return __generator(this, function (_a) {
148
+ switch (_a.label) {
149
+ case 0:
150
+ id = "stream-".concat(Date.now());
151
+ // Create initial node.
152
+ return [4 /*yield*/, addNode(id, "...", true)];
153
+ case 1:
154
+ // Create initial node.
155
+ _a.sent();
156
+ text = "";
157
+ return [4 /*yield*/, streamOpenAI(prompt, function (chunk) { return __awaiter(void 0, void 0, void 0, function () {
158
+ return __generator(this, function (_a) {
159
+ switch (_a.label) {
160
+ case 0:
161
+ text += chunk;
162
+ removeNode(id);
163
+ return [4 /*yield*/, addNode(id, text, true)];
164
+ case 1:
165
+ _a.sent();
166
+ return [2 /*return*/];
167
+ }
168
+ });
169
+ }); })];
170
+ case 2:
171
+ _a.sent();
172
+ // Replace the node one more time, this time without a blinking <mark>
173
+ // cursor.
174
+ removeNode(id);
175
+ return [4 /*yield*/, addNode(id, text, false)];
176
+ case 3:
177
+ _a.sent();
178
+ return [2 /*return*/];
179
+ }
180
+ });
181
+ }); };
182
+ /**
183
+ * Performs a streamed AI transformation.
184
+ * Replaces the selected text with streamed output in real-time.
185
+ *
186
+ * The editor is disabled while the transformation runs to prevent user
187
+ * interference.
188
+ *
189
+ * @param promptFn - A function that generates the prompt based on selected
190
+ * content.
191
+ */
192
+ var transformStream = function (promptFn) {
193
+ // Disable the editor
194
+ props.editor.setOptions({ editable: false });
195
+ // Copy the selected content, then remove it.
196
+ // Feed it into prompt.
197
+ var prompt = promptFn(cutEditorSelection());
198
+ setStreaming(true);
199
+ stream(prompt)
200
+ .finally(function () {
201
+ setStreaming(false);
202
+ // Re-enable the editor:
203
+ props.editor.setOptions({ editable: true });
204
+ });
205
+ };
206
+ return (React.createElement(OpenAIMenu, { editor: props.editor, onClick: function (p) { return transformStream(p); }, animated: streaming }));
207
+ };
208
+ export { OpenAIButton };
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ import { Editor } from '@tiptap/react';
3
+ interface IProps {
4
+ editor: Editor;
5
+ onClick: (prompt: (text: string) => string) => void;
6
+ animated: boolean;
7
+ }
8
+ declare const OpenAIMenu: (props: IProps) => React.JSX.Element;
9
+ export { OpenAIMenu };
@@ -0,0 +1,42 @@
1
+ import * as React from 'react';
2
+ import { Openable } from '../../../containers/Openable';
3
+ import { MenuButton } from '../menu/MenuButton';
4
+ import { List, ListRow } from '../../../containers/List';
5
+ import { SVG } from '../../../svg';
6
+ import { Icon } from '../../../controls/Icon';
7
+ var OpenAIMenu = function (props) {
8
+ var _a = React.useState(false), open = _a[0], setOpen = _a[1];
9
+ var _b = React.useState(false), langOpen = _b[0], setLangOpen = _b[1];
10
+ // With an empty selection, some AI commands cannot work, because they
11
+ // need a source text.
12
+ var selectionIsEmpty = props.editor.state.selection.empty;
13
+ var handleClick = function (promptFn) {
14
+ setOpen(false);
15
+ props.onClick(promptFn);
16
+ };
17
+ React.useEffect(function () {
18
+ if (!open)
19
+ setLangOpen(false);
20
+ }, [open]);
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" } }, "OpenAI functions"), icon: { url: SVG.Editor.AI, animated: props.animated } }), content: React.createElement("div", { style: { display: 'flex', gap: '8px' } },
23
+ React.createElement("div", { style: { minWidth: '150px' } },
24
+ React.createElement(List, { maxItems: 4, contract: true },
25
+ React.createElement(ListRow, { disabled: selectionIsEmpty, onClick: function () { return handleClick(function (text) {
26
+ return "Produce a list of the key points in the following text, returning only the result: \n\n".concat(text);
27
+ }); } }, "Summarize"),
28
+ React.createElement(ListRow, { disabled: selectionIsEmpty, onClick: function () { return handleClick(function (text) {
29
+ return "Produce a list of the key points in the following text, returning only the result: \n\n".concat(text);
30
+ }); } }, "Key points"),
31
+ React.createElement(ListRow, { disabled: selectionIsEmpty, onClick: function () { return setLangOpen(!langOpen); } },
32
+ "Translate ",
33
+ React.createElement(Icon, { color: "#333", disabled: true, rotated: -90, url: SVG.Icons.Caret })))),
34
+ langOpen &&
35
+ React.createElement("div", { style: { minWidth: '150px' } },
36
+ React.createElement(List, { maxItems: 4, contract: true }, languages.map(function (lang) {
37
+ return React.createElement(ListRow, { key: lang, disabled: selectionIsEmpty, onClick: function () { return handleClick(function (text) {
38
+ return "Translate this text to ".concat(lang, ", returning only the result: \n\n").concat(text);
39
+ }); } }, lang);
40
+ })))), open: open, onClose: function () { return setOpen(false); } }));
41
+ };
42
+ export { OpenAIMenu };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { Editor } from '@tiptap/react';
3
+ interface IProps {
4
+ editor: Editor;
5
+ url: string;
6
+ }
7
+ declare const SpeechButton: (props: IProps) => React.JSX.Element;
8
+ export { SpeechButton };
@@ -0,0 +1,31 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { MenuButton } from '../menu/MenuButton';
3
+ import { SVG } from '../../../svg';
4
+ import { useAssemblyAIRecorder } from '../../../hooks/useAssemblyAIRecorder';
5
+ var SpeechButton = function (props) {
6
+ var fromRef = useRef(null);
7
+ var transcriptRef = useRef("");
8
+ var onTranscript = function (text) {
9
+ var _a = props.editor, state = _a.state, view = _a.view;
10
+ var transaction = state.tr
11
+ .insertText(text, fromRef.current, fromRef.current + transcriptRef.current.length);
12
+ view.dispatch(transaction);
13
+ transcriptRef.current = text;
14
+ };
15
+ var _a = useAssemblyAIRecorder(props.url, onTranscript), recordingStatus = _a.recordingStatus, toggleRecording = _a.toggleRecording;
16
+ useEffect(function () {
17
+ if (recordingStatus == 'recording') {
18
+ props.editor.setOptions({ editable: false });
19
+ fromRef.current = props.editor.state.selection.from;
20
+ transcriptRef.current = "";
21
+ }
22
+ if (recordingStatus != 'recording') {
23
+ props.editor.setOptions({ editable: true });
24
+ }
25
+ }, [recordingStatus]);
26
+ var handleClick = function () {
27
+ toggleRecording();
28
+ };
29
+ return (React.createElement(MenuButton, { editor: props.editor, hint: React.createElement("span", { style: { whiteSpace: "nowrap" } }, "AssemblyAI speech-to-text"), onClick: handleClick, disabled: false, active: recordingStatus == 'connecting', highlighted: recordingStatus == 'recording', icon: { url: SVG.Icons.Microphone } }));
30
+ };
31
+ export { SpeechButton };
@@ -0,0 +1,9 @@
1
+ import { Mark } from '@tiptap/core';
2
+ export interface MarkOptions {
3
+ HTMLAttributes: Record<string, any>;
4
+ }
5
+ /**
6
+ * Custom TipTap extension. This supports the `<mark>` element.
7
+ */
8
+ declare const MarkElement: Mark<MarkOptions, any>;
9
+ export { MarkElement };