diff options
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 157 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.scss | 12 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 102 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 5 |
6 files changed, 130 insertions, 150 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a129c35cf..f6829a4cb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -67,6 +67,7 @@ import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; import GenerativeFill from './nodes/generativeFill/GenerativeFill'; +import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -1032,6 +1033,7 @@ export class MainView extends React.Component { <InkTranscription /> {this.snapLines} <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> + <GPTPopup key="gptpopup" /> <GenerativeFill imageEditorOpen={this.imageEditorOpen} imageEditorSource={this.imageEditorSource} imageRootDoc={this.imageRootDoc} addDoc={this.addDoc} /> {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */} </div> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 20a176c58..212d24165 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -909,7 +909,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps // breakupdictiation for reference // prepareForTyping // stored marks - userMark and timestamp, setStoredMarks - //the._editorView.dispatch(state.tr.setSelection(updatted).insertText('\n, to)) + // the._editorView.dispatch(state.tr.setSelection(updated).insertText('\n, to)) askGPT = action(async () => { try { diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 07b2afd91..e6b9cb382 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -48,9 +48,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions - @observable private GPTpopupText: string = ''; - @observable private loadingGPT: boolean = false; - @observable private showGPTPopup: boolean = false; @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY; @observable private selectedText: string = ''; @observable private editorView?: EditorView; @@ -59,25 +56,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { private selectionRange: number[] | undefined; @action - setGPTPopupVis = (vis: boolean) => { - this.showGPTPopup = vis; - }; - @action setGPTMode = (mode: GPTPopupMode) => { this.GPTMode = mode; }; @action - setGPTPopupText = (txt: string) => { - this.GPTpopupText = txt; - }; - - @action - setLoading = (loading: boolean) => { - this.loadingGPT = loading; - }; - - @action setHighlightRange(r: number[] | undefined) { this.highlightRange = r; } @@ -130,19 +113,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer2 = reaction( () => this._opacity, - opacity => { - if (!opacity) { - this.setGPTPopupVis(false); - this.setGPTPopupText(''); - } - }, + opacity => {}, { fireImmediately: true } ); this._disposer = reaction( () => SelectionManager.Views().slice(), selected => { - this.setGPTPopupVis(false); - this.setGPTPopupText(''); AnchorMenu.Instance.fadeOut(true); } ); @@ -153,23 +129,22 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * @param e pointer down event */ gptSummarize = async (e: React.PointerEvent) => { + GPTPopup.Instance.setVisible(true); this.setHighlightRange(undefined); - this.setGPTPopupVis(true); this.setGPTMode(GPTPopupMode.SUMMARY); - this.setLoading(true); + GPTPopup.Instance.setLoading(true); try { const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); if (res) { - this.setGPTPopupText(res); + GPTPopup.Instance.setText(res); } else { - this.setGPTPopupText('Something went wrong.'); + GPTPopup.Instance.setText('Something went wrong.'); } } catch (err) { console.error(err); } - - this.setLoading(false); + GPTPopup.Instance.setLoading(false); }; /** @@ -184,9 +159,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n'); const selectedText = state.doc.textBetween(sel.from, sel.to); - this.setGPTPopupVis(true); + GPTPopup.Instance.setVisible(true); this.setGPTMode(GPTPopupMode.EDIT); - this.setLoading(true); + GPTPopup.Instance.setLoading(true); try { let res = await gptAPICall(selectedText, GPTCallType.EDIT); @@ -196,16 +171,16 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1); if (res) { - this.setGPTPopupText(resultText); + GPTPopup.Instance.setText(resultText); this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]); } else { - this.setGPTPopupText('Something went wrong.'); + GPTPopup.Instance.setText('Something went wrong.'); } } catch (err) { console.error(err); } - this.setLoading(false); + GPTPopup.Instance.setLoading(false); }; /** @@ -253,21 +228,18 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { }; @computed get highlighter() { - return <Group> - <IconButton - icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />} - tooltip={'Click to Highlight'} - onClick={this.highlightClicked} - colorPicker={this.highlightColor} - color={StrCast(Doc.UserDoc().userColor)} - /> - <ColorPicker - colorPickerType={'github'} - selectedColor={this.highlightColor} - setSelectedColor={color => this.changeHighlightColor(color)} - size={Size.XSMALL} - /> - </Group> + return ( + <Group> + <IconButton + icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />} + tooltip={'Click to Highlight'} + onClick={this.highlightClicked} + colorPicker={this.highlightColor} + color={StrCast(Doc.UserDoc().userColor)} + /> + <ColorPicker colorPickerType={'github'} selectedColor={this.highlightColor} setSelectedColor={color => this.changeHighlightColor(color)} size={Size.XSMALL} /> + </Group> + ); } @action changeHighlightColor = (color: string) => { @@ -312,12 +284,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { this.Status === 'marquee' ? ( <> {this.highlighter} - <IconButton - tooltip={'Drag to Place Annotation'} - onPointerDown={this.pointerDown} - icon={<FontAwesomeIcon icon="comment-alt"/>} - color={StrCast(Doc.UserDoc().userColor)} - /> + <IconButton tooltip={'Drag to Place Annotation'} onPointerDown={this.pointerDown} icon={<FontAwesomeIcon icon="comment-alt" />} color={StrCast(Doc.UserDoc().userColor)} /> {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}> @@ -326,7 +293,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { </button> </Tooltip> )} - <GPTPopup + {/* <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.GPTpopupText} @@ -336,68 +303,32 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { callEditApi={this.gptEdit} replaceText={this.replaceText} mode={this.GPTMode} - /> + /> */} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( - <IconButton - tooltip={'Click to Record Annotation'} - onPointerDown={this.audioDown} - icon={<FontAwesomeIcon icon="microphone" />} - color={StrCast(Doc.UserDoc().userColor)} - /> - )} - {this.canEdit() && ( - <IconButton - tooltip={'AI edit suggestions'} - onPointerDown={this.gptEdit} - icon={<FontAwesomeIcon icon="pencil-alt" />} - color={StrCast(Doc.UserDoc().userColor)} - /> + <IconButton tooltip={'Click to Record Annotation'} onPointerDown={this.audioDown} icon={<FontAwesomeIcon icon="microphone" />} color={StrCast(Doc.UserDoc().userColor)} /> )} - <Popup - tooltip='Find document to link to selected text' - type={Type.PRIM} - icon={<FontAwesomeIcon icon={'search'} />} - popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} - color={StrCast(Doc.UserDoc().userColor)} - /> + {this.canEdit() && <IconButton tooltip={'AI edit suggestions'} onPointerDown={this.gptEdit} icon={<FontAwesomeIcon icon="pencil-alt" />} color={StrCast(Doc.UserDoc().userColor)} />} + <Popup tooltip="Find document to link to selected text" type={Type.PRIM} icon={<FontAwesomeIcon icon={'search'} />} popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} color={StrCast(Doc.UserDoc().userColor)} /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : ( - <IconButton - tooltip={'Click/Drag to create cropped image'} - onPointerDown={this.cropDown} - icon={<FontAwesomeIcon icon="image"/>} - color={StrCast(Doc.UserDoc().userColor)} - /> + <IconButton tooltip={'Click/Drag to create cropped image'} onPointerDown={this.cropDown} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} /> )} </> ) : ( <> - {this.Delete !== returnFalse && <IconButton - tooltip={'Remove Link Anchor'} - onPointerDown={this.Delete} - icon={<FontAwesomeIcon icon="trash-alt" />} - color={StrCast(Doc.UserDoc().userColor)} - />} - {this.PinToPres !== returnFalse && <IconButton - tooltip={'Pin to Presentation'} - onPointerDown={this.PinToPres} - icon={<FontAwesomeIcon icon="map-pin" />} - color={StrCast(Doc.UserDoc().userColor)} - />} - {this.ShowTargetTrail !== returnFalse && <IconButton - tooltip={'Show Linked Trail'} - onPointerDown={this.ShowTargetTrail} - icon={<FontAwesomeIcon icon="taxi" />} - color={StrCast(Doc.UserDoc().userColor)} - />} - {this.IsTargetToggler !== returnFalse && <Toggle - tooltip={'Make target visibility toggle on click'} - type={Type.PRIM} - toggleType={ToggleType.BUTTON} - toggleStatus={this.IsTargetToggler()} - onClick={this.MakeTargetToggle} - icon={<FontAwesomeIcon icon="thumbtack" />} - color={StrCast(Doc.UserDoc().userColor)} - />} + {this.Delete !== returnFalse && <IconButton tooltip={'Remove Link Anchor'} onPointerDown={this.Delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={StrCast(Doc.UserDoc().userColor)} />} + {this.PinToPres !== returnFalse && <IconButton tooltip={'Pin to Presentation'} onPointerDown={this.PinToPres} icon={<FontAwesomeIcon icon="map-pin" />} color={StrCast(Doc.UserDoc().userColor)} />} + {this.ShowTargetTrail !== returnFalse && <IconButton tooltip={'Show Linked Trail'} onPointerDown={this.ShowTargetTrail} icon={<FontAwesomeIcon icon="taxi" />} color={StrCast(Doc.UserDoc().userColor)} />} + {this.IsTargetToggler !== returnFalse && ( + <Toggle + tooltip={'Make target visibility toggle on click'} + type={Type.PRIM} + toggleType={ToggleType.BUTTON} + toggleStatus={this.IsTargetToggler()} + onClick={this.MakeTargetToggle} + icon={<FontAwesomeIcon icon="thumbtack" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} </> ); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 44413ede7..2f0ff83e2 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -6,12 +6,6 @@ $button: #5b97ff; $highlightedText: #82e0ff; .summary-box { - display: flex; - flex-direction: column; - justify-content: space-between; - background-color: #ffffff; - box-shadow: 0 2px 5px #7474748d; - color: $textgrey; position: fixed; bottom: 10px; right: 10px; @@ -21,6 +15,12 @@ $highlightedText: #82e0ff; padding: 15px; padding-bottom: 0; z-index: 999; + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #ffffff; + box-shadow: 0 2px 5px #7474748d; + color: $textgrey; .summary-heading { display: flex; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 8bd060d4f..9f28cb5d1 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -5,56 +5,96 @@ import { observer } from 'mobx-react'; import ReactLoading from 'react-loading'; import Typist from 'react-typist'; import { Doc } from '../../../../fields/Doc'; -import { Docs } from '../../../documents/Documents'; +import { DocUtils, Docs } from '../../../documents/Documents'; import './GPTPopup.scss'; +import { Button, IconButton } from 'browndash-components'; +import { StrCast } from '../../../../fields/Types'; export enum GPTPopupMode { SUMMARY, EDIT, } -interface GPTPopupProps { - visible: boolean; - text: string; - loading: boolean; - mode: GPTPopupMode; - callSummaryApi: (e: React.PointerEvent) => Promise<void>; - callEditApi: (e: React.PointerEvent) => Promise<void>; - replaceText: (replacement: string) => void; - highlightRange?: number[]; -} +interface GPTPopupProps {} @observer export class GPTPopup extends React.Component<GPTPopupProps> { static Instance: GPTPopup; @observable - private done: boolean = false; + public visible: boolean = false; + @action + public setVisible = (vis: boolean) => { + this.visible = vis; + }; @observable - private sidebarId: string = ''; + public loading: boolean = false; + @action + public setLoading = (loading: boolean) => { + this.loading = loading; + }; + @observable + public text: string = ''; + @action + public setText = (text: string) => { + this.text = text; + }; + @observable + public mode: GPTPopupMode = GPTPopupMode.SUMMARY; + @action + public setMode = (mode: GPTPopupMode) => { + this.mode = mode; + }; + @observable + public highlightRange: number[] = []; + @action callSummaryApi = () => {}; + @action callEditApi = () => {}; + @action replaceText = (replacement: string) => {}; + + @observable + private done: boolean = false; @action public setDone = (done: boolean) => { this.done = done; }; + @observable + private sidebarId: string = ''; @action public setSidebarId = (id: string) => { this.sidebarId = id; }; + // reference + // if (focusNode) { + // const anchor = srcWeb?.ComponentView?.getAnchor?.(true); + // anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); + // } + @observable + private pdfAnchor: Doc | undefined; + @action + public setPdfAnchor = (anchor: Doc) => { + this.pdfAnchor = anchor; + }; + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; /** * Transfers the summarization text to a sidebar annotation text document. */ private transferToText = () => { - const newDoc = Docs.Create.TextDocument(this.props.text.trim(), { + const newDoc = Docs.Create.TextDocument(this.text.trim(), { _width: 200, _height: 50, _layout_fitWidth: true, _layout_autoHeight: true, }); this.addDoc(newDoc, this.sidebarId); + if (this.pdfAnchor) { + DocUtils.MakeLink(newDoc, this.pdfAnchor, { + link_relationship: 'GPT Summary', + }); + } }; constructor(props: GPTPopupProps) { @@ -63,7 +103,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> { } componentDidUpdate = () => { - if (this.props.loading) { + if (this.loading) { this.setDone(false); } }; @@ -73,10 +113,10 @@ export class GPTPopup extends React.Component<GPTPopupProps> { <div> {this.heading('SUMMARY')} <div className="content-wrapper"> - {!this.props.loading && + {!this.loading && (!this.done ? ( <Typist - key={this.props.text} + key={this.text} avgTypingDelay={15} cursor={{ hideWhenDone: true }} onTypingDone={() => { @@ -84,18 +124,18 @@ export class GPTPopup extends React.Component<GPTPopupProps> { this.setDone(true); }, 500); }}> - {this.props.text} + {this.text} </Typist> ) : ( - this.props.text + this.text ))} </div> </div> - {!this.props.loading && ( + {!this.loading && ( <div className="btns-wrapper"> {this.done ? ( <> - <button className="icon-btn" onPointerDown={e => this.props.callSummaryApi(e)}> + {/* <button className="icon-btn" onClick={this.callSummaryApi}> <FontAwesomeIcon icon="redo-alt" size="lg" /> </button> <button @@ -104,7 +144,9 @@ export class GPTPopup extends React.Component<GPTPopupProps> { this.transferToText(); }}> Transfer to Text - </button> + </button> */} + <IconButton onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} /> + <Button text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} /> </> ) : ( <div className="summarizing"> @@ -125,7 +167,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> { ); editBox = () => { - const hr = this.props.highlightRange; + const hr = this.highlightRange; return ( <> <div> @@ -133,22 +175,22 @@ export class GPTPopup extends React.Component<GPTPopupProps> { <div className="content-wrapper"> {hr && ( <div> - {this.props.text.slice(0, hr[0])} <span className="highlighted-text">{this.props.text.slice(hr[0], hr[1])}</span> {this.props.text.slice(hr[1])} + {this.text.slice(0, hr[0])} <span className="highlighted-text">{this.text.slice(hr[0], hr[1])}</span> {this.text.slice(hr[1])} </div> )} </div> </div> - {hr && !this.props.loading && ( + {hr && !this.loading && ( <> <div className="btns-wrapper"> <> - <button className="icon-btn" onPointerDown={e => this.props.callEditApi(e)}> + <button className="icon-btn" onPointerDown={this.callEditApi}> <FontAwesomeIcon icon="redo-alt" size="lg" /> </button> <button className="text-btn" onClick={e => { - this.props.replaceText(this.props.text); + this.replaceText(this.text); }}> Replace Text </button> @@ -174,14 +216,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> { heading = (headingText: string) => ( <div className="summary-heading"> <label className="summary-text">{headingText}</label> - {this.props.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />} + {this.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />} </div> ); render() { return ( - <div className="summary-box" style={{ display: this.props.visible ? 'flex' : 'none' }}> - {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} + <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}> + {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} </div> ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0fd93868a..4fc31ffe3 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -423,6 +423,11 @@ export class PDFViewer extends React.Component<IViewerProps> { // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarId('data_sidebar'); + const anchor = this._getAnchor(undefined, false); + if (anchor) { + console.log(anchor); + GPTPopup.Instance.setPdfAnchor(anchor); + } GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; |