diff options
Diffstat (limited to 'src/client/views/nodes/trails/PresBox.tsx')
-rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 162 |
1 files changed, 160 insertions, 2 deletions
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 9e5ea9524..b2059b185 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -35,6 +35,7 @@ import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; +import ReactLoading from 'react-loading'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; export interface pinDataTypes { scrollable?: boolean; @@ -103,7 +104,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable _treeViewMap: Map<Doc, number> = new Map(); @observable _presKeyEvents: boolean = false; @observable _forceKeyEvents: boolean = false; - @computed get isTreeOrStack() { + + // GPT + private _inputref: HTMLTextAreaElement | null = null; + @observable chatActive: boolean = false; + @observable chatInput: string = ''; + public slideToModify: Doc | null = null; + @observable isRecording: boolean = false; + @observable isLoading: boolean = false; + + @action + setChatInput = (input: string) => { + this.chatInput = input; + }; + + @action + setIsLoading = (isLoading: boolean) => { + this.isLoading = isLoading; + }; + + @action + public setChatActive = (active: boolean) => { + this.chatActive = active; + }; + + @action + public setIsRecording = (isRecording: boolean) => { + this.isRecording = isRecording; + }; + + @computed + get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any); } @computed get isTree() { @@ -226,6 +257,71 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } }; + // GPT + + 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 = (abort: boolean) => { + this.setIsRecording(false); + DictationManager.Controls.stop(!abort); + }; + + setDictationContent = (value: string) => { + this.setChatInput(value); + // // Get the current cursor position + // if (!this._inputref) return; + // const cursorPosition = this._inputref.selectionStart; + // const currentValue = this.chatInput; + + // // split before and after + // const textBeforeCursor = currentValue.slice(0, cursorPosition); + // const textAfterCursor = currentValue.slice(cursorPosition); + + // // insertion + // const updatedText = textBeforeCursor + value + textAfterCursor; + + // // Update the textarea value + // this.setChatInput(updatedText); + + // // set new cursor pos + // const newCursorPosition = cursorPosition + value.length; + // this._inputref.setSelectionRange(newCursorPosition, newCursorPosition); + }; + + @action + customizeWithGPT = async (input: string) => { + // const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect'; + if (!this.slideToModify) return; + this.setIsRecording(false); + this.setIsLoading(true); + try { + const res = await gptTrailSlideCustomization(input); + if (typeof res === 'string') { + const resObj = JSON.parse(res); + console.log('Result ', resObj); + // this.activeItem + for (let key in resObj) { + if (resObj[key]) { + this.slideToModify[key] = resObj[key]; + } + } + } + } catch (err) { + console.error(err); + } + this.setIsLoading(false); + }; + //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions // No more frames in current doc and next slide is defined, therefore move to next slide @@ -732,6 +828,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { */ navigateToActiveItem = (afterNav?: () => void) => { const activeItem: Doc = this.activeItem; + // GPT update + this.slideToModify = activeItem; const targetDoc: Doc = this.targetDoc; const finished = () => { afterNav?.(); @@ -1189,6 +1287,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action keyEvents = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement) return; + if (e.target instanceof HTMLTextAreaElement) return; let handled = false; const anchorNode = document.activeElement as HTMLDivElement; if (anchorNode && anchorNode.className?.includes('lm_title')) return; @@ -2623,7 +2722,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { /> ) : null} </div> - {/* { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}> @@ -2633,6 +2731,66 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </Tooltip> } */} </div> + {/* presbox chatbox */} + {this.chatActive && ( + <div className="presBox-chatbox"> + <div style={{ alignSelf: 'flex-end' }}> + {this.isLoading ? ( + <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} width={14} height={14} /> + ) : ( + <IconButton + type={Type.PRIM} + color={StrCast(Doc.UserDoc().userVariantColor)} + tooltip="Close" + icon={<BiX size={'16px'} />} + onClick={() => { + this.setChatActive(false); + }} + /> + )} + </div> + <TextareaAutosize + ref={r => (this._inputref = r)} + minRows={3} + placeholder="Customize..." + className="pres-chatbox" + autoFocus={true} + value={this.chatInput} + onChange={e => { + this.setChatInput(e.target.value); + }} + onKeyDown={e => { + this.stopDictation(true); + e.stopPropagation(); + }} + /> + {/* <input className="chatbox" placeholder="Customize..." value={this.chatInput} onChange={e => this.setChatInput(e.target.value)} /> */} + <div style={{ alignSelf: 'flex-end', display: 'flex', gap: '8px' }}> + <IconButton + type={Type.TERT} + color={this.isRecording ? 'red' : StrCast(Doc.UserDoc().userVariantColor)} + tooltip="Record" + icon={<BiMicrophone size={'16px'} />} + onClick={() => { + if (!this.isRecording) { + this.recordDictation(); + } else { + this.stopDictation(true); + } + }} + /> + <IconButton + type={Type.TERT} + color={!this.isLoading ? StrCast(Doc.UserDoc().userVariantColor) : '#7c7c7c'} + tooltip="Send" + icon={<AiOutlineSend size={'16px'} />} + onClick={() => { + this.customizeWithGPT(this.chatInput); + }} + /> + </div> + </div> + )} </div> ); } |