aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/DictationManager.ts5
-rw-r--r--src/client/views/DictationButton.tsx57
-rw-r--r--src/client/views/DictationOverlay.tsx1
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx31
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx53
5 files changed, 101 insertions, 46 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 831afe538..897366757 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -71,7 +71,6 @@ export namespace DictationManager {
let current: string | undefined;
let sessionResults: string[] = [];
- // eslint-disable-next-line new-cap
const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
export type InterimResultHandler = (results: string) => void;
@@ -257,7 +256,6 @@ export namespace DictationManager {
if (entry) {
let success = false;
const { restrictTo } = entry;
- // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
// eslint-disable-next-line no-await-in-loop
@@ -268,7 +266,6 @@ export namespace DictationManager {
return success;
}
- // eslint-disable-next-line no-restricted-syntax
for (const depEntry of Dependent) {
const regex = depEntry.expression;
const matches = regex.exec(phrase);
@@ -276,7 +273,6 @@ export namespace DictationManager {
if (matches !== null) {
let success = false;
const { restrictTo } = depEntry;
- // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
// eslint-disable-next-line no-await-in-loop
@@ -307,7 +303,6 @@ export namespace DictationManager {
};
const validate = (target: DocumentView, types: DocumentType[]) => {
- // eslint-disable-next-line no-restricted-syntax
for (const type of types) {
if (tryCast(target, type)) {
return true;
diff --git a/src/client/views/DictationButton.tsx b/src/client/views/DictationButton.tsx
new file mode 100644
index 000000000..0ce586df4
--- /dev/null
+++ b/src/client/views/DictationButton.tsx
@@ -0,0 +1,57 @@
+import { IconButton, Type } from '@dash/components';
+import { action, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { BiMicrophone } from 'react-icons/bi';
+import { DictationManager } from '../util/DictationManager';
+import { SnappingManager } from '../util/SnappingManager';
+
+export interface DictationButtonProps {
+ setInput: (val: string) => void;
+ inputRef?: HTMLInputElement | null | undefined;
+}
+@observer
+export class DictationButton extends React.Component<DictationButtonProps> {
+ @observable private _isRecording = false;
+ constructor(props: DictationButtonProps) {
+ super(props);
+ makeObservable(this);
+ }
+
+ stopDictation = action(() => {
+ this._isRecording = false;
+ DictationManager.Controls.stop();
+ });
+
+ render() {
+ return (
+ <IconButton
+ type={Type.TERT}
+ color={this._isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
+ tooltip="Record"
+ icon={<BiMicrophone size="16px" />}
+ onClick={action(() => {
+ if (!this._isRecording) {
+ this._isRecording = true;
+ DictationManager.Controls.listen({
+ interimHandler: (value: string) => {
+ this.props.setInput(value);
+ if (this.props.inputRef) {
+ this.props.inputRef.focus();
+ this.props.inputRef.scrollLeft = 1000000;
+ }
+ },
+ continuous: { indefinite: false },
+ }).then(results => {
+ if (results && [DictationManager.Controls.Infringed].includes(results)) {
+ DictationManager.Controls.stop();
+ }
+ });
+ } else {
+ this.stopDictation();
+ }
+ })}
+ />
+ );
+ }
+}
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index e33049d3b..66831ec7f 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -14,7 +14,6 @@ export class DictationOverlay extends React.Component {
@observable private _dictationDisplayState = false;
@observable private _dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
- // eslint-disable-next-line react/no-unused-class-component-methods
public hasActiveModal = false;
constructor(props: object) {
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index 810e1793b..4453fe140 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -22,17 +22,18 @@ import { CollectionViewType, DocumentType } from '../../../../documents/Document
import { Docs, DocumentOptions } from '../../../../documents/Documents';
import { DocumentManager } from '../../../../util/DocumentManager';
import { LinkManager } from '../../../../util/LinkManager';
+import { DictationButton } from '../../../DictationButton';
import { ViewBoxAnnotatableComponent } from '../../../DocComponent';
import { DocumentView } from '../../DocumentView';
import { FieldView, FieldViewProps } from '../../FieldView';
import { PDFBox } from '../../PDFBox';
import { Agent } from '../agentsystem/Agent';
+import { supportedDocumentTypes } from '../tools/CreateDocumentTool';
import { ASSISTANT_ROLE, AssistantMessage, CHUNK_TYPE, Citation, ProcessingInfo, SimplifiedChunk, TEXT_TYPE } from '../types/types';
import { Vectorstore } from '../vectorstore/Vectorstore';
import './ChatBox.scss';
import MessageComponentBox from './MessageComponent';
import { ProgressBar } from './ProgressBar';
-import { supportedDocumentTypes } from '../tools/CreateDocumentTool';
dotenv.config();
@@ -64,6 +65,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
private vectorstore: Vectorstore;
private agent: Agent;
private messagesRef: React.RefObject<HTMLDivElement>;
+ private _textInputRef: HTMLInputElement | undefined | null;
/**
* Static method that returns the layout string for the field.
@@ -73,6 +75,10 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return FieldView.LayoutString(ChatBox, fieldKey);
}
+ setChatInput = action((input: string) => {
+ this._inputValue = input;
+ });
+
/**
* Constructor initializes the component, sets up OpenAI, vector store, and agent instances,
* and observes changes in the chat history to save the state in dataDoc.
@@ -820,6 +826,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._inputValue = question;
};
+ _dictation: DictationButton | null = null;
/**
* Renders the chat interface, including the message list, input field, and other UI elements.
*/
@@ -847,8 +854,19 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</div>
<form onSubmit={this.askGPT} className="chat-input">
- <input type="text" name="messageInput" autoComplete="off" placeholder="Type your message here..." value={this._inputValue} onChange={action(e => (this._inputValue = e.target.value))} disabled={this._isLoading} />
- <button className="submit-button" type="submit" disabled={this._isLoading || !this._inputValue.trim()}>
+ <input
+ ref={r => {
+ this._textInputRef = r;
+ }}
+ type="text"
+ name="messageInput"
+ autoComplete="off"
+ placeholder="Type your message here..."
+ value={this._inputValue}
+ onChange={action(e => (this._inputValue = e.target.value))}
+ disabled={this._isLoading}
+ />
+ <button className="submit-button" onClick={() => this._dictation?.stopDictation()} type="submit" disabled={this._isLoading || !this._inputValue.trim()}>
{this._isLoading ? (
<div className="spinner"></div>
) : (
@@ -858,6 +876,13 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</svg>
)}
</button>
+ <DictationButton
+ ref={r => {
+ this._dictation = r;
+ }}
+ setInput={this.setChatInput}
+ inputRef={this._textInputRef}
+ />
</form>
{/* Popup for citation */}
{this._citationPopup.visible && (
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3ce4dc6cb..b98a7f96e 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -7,7 +7,6 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
-import { BiMicrophone } from 'react-icons/bi';
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa';
import ReactLoading from 'react-loading';
import ReactTextareaAutosize from 'react-textarea-autosize';
@@ -26,12 +25,12 @@ import { DocServer } from '../../../DocServer';
import { getSlideTransitionSuggestions, gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/PresCustomization';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
-import { DictationManager } from '../../../util/DictationManager';
import { dropActionType } from '../../../util/DropActionTypes';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SerializationHelper } from '../../../util/SerializationHelper';
import { SnappingManager } from '../../../util/SnappingManager';
import { UndoManager, undoBatch, undoable } from '../../../util/UndoManager';
+import { DictationButton } from '../../DictationButton';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { pinDataTypes as dataTypes } from '../../PinFuncs';
import { CollectionView } from '../../collections/CollectionView';
@@ -79,6 +78,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
_keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
_unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
_presTimer: NodeJS.Timeout | undefined;
+ _animationDictation: DictationButton | null = null;
+ _slideDictation: DictationButton | null = null;
// eslint-disable-next-line no-use-before-define
@observable public static Instance: PresBox;
@@ -108,7 +109,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _chatActive: boolean = false;
@observable _animationChat: string = '';
@observable _chatInput: string = '';
- @observable _isRecording: boolean = false;
@observable _isLoading: boolean = false;
@observable generatedAnimations: AnimationSettings[] = [
@@ -147,7 +147,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
setChatInput = action((input: string) => { this._chatInput = input; }); // prettier-ignore
setAnimationChat = action((input: string) => { this._animationChat = input; }); // prettier-ignore
setIsLoading = action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
- setIsRecording = action((input: boolean) => { this._isRecording = input; }); // prettier-ignore
setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
@@ -280,24 +279,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
- // Recording for GPT customization
- recordDictation = () => {
- this.setIsRecording(true);
- this.setChatInput('');
- DictationManager.Controls.listen({
- interimHandler: this.setDictationContent,
- continuous: { indefinite: false },
- }).then(results => {
- if (results && [DictationManager.Controls.Infringed].includes(results)) {
- DictationManager.Controls.stop();
- }
- });
- };
- stopDictation = () => {
- this.setIsRecording(false);
- DictationManager.Controls.stop();
- };
-
setDictationContent = (value: string) => this.setChatInput(value);
customizeAnimations = action(() => {
@@ -310,7 +291,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
customizeWithGPT = action((input: string) => {
// const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
- this.setIsRecording(false);
this.setIsLoading(true);
const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 };
const currSlideProperties = gptSlideProperties.reduce(
@@ -1785,7 +1765,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.setAnimationChat(e.target.value);
}}
onKeyDown={e => {
- this.stopDictation();
+ this._animationDictation?.stopDictation();
e.stopPropagation();
}}
/>
@@ -1799,6 +1779,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
color={SnappingManager.userVariantColor}
onClick={this.customizeAnimations}
/>
+ <DictationButton
+ ref={r => {
+ this._animationDictation = r;
+ }}
+ setInput={this.setAnimationChat}
+ />
</div>
<div style={{ alignItems: 'center' }}>
Click a box to use the effect.
@@ -1878,22 +1864,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.setChatInput(e.target.value);
}}
onKeyDown={e => {
- this.stopDictation();
+ this._slideDictation?.stopDictation();
e.stopPropagation();
}}
/>
- <IconButton
- type={Type.TERT}
- color={this._isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
- tooltip="Record"
- icon={<BiMicrophone size="16px" />}
- onClick={() => {
- if (!this._isRecording) {
- this.recordDictation();
- } else {
- this.stopDictation();
- }
+ <DictationButton
+ ref={r => {
+ this._slideDictation = r;
}}
+ setInput={this.setChatInput}
/>
</div>
<Button
@@ -1904,7 +1883,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
iconPlacement="right"
color={SnappingManager.userVariantColor}
onClick={() => {
- this.stopDictation();
+ this._slideDictation?.stopDictation();
this.customizeWithGPT(this._chatInput);
}}
/>