diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-04-13 01:13:33 -0400 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-04-13 01:13:33 -0400 |
| commit | db582e135fceb6162d0c9cf00e2580fb1349fddb (patch) | |
| tree | a072ab129241e5ed06fb09d582d5339be3edb889 /src/client/views/pdf/AnchorMenu.tsx | |
| parent | a0ae93e3b14069c0de419fc5dcade84d460a0b30 (diff) | |
added text edits
Diffstat (limited to 'src/client/views/pdf/AnchorMenu.tsx')
| -rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 147 |
1 files changed, 127 insertions, 20 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 1b30e1f68..b66f294f4 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,10 +10,11 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; -import { gptAPICall, GPTCallType } from '../../apis/gpt/Summarization'; -import { GPTPopup } from './GPTPopup'; -import './AnchorMenu.scss'; +import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT'; +import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup'; import { LightboxView } from '../LightboxView'; +import { EditorView } from 'prosemirror-view'; +import './AnchorMenu.scss'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -46,27 +47,55 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions - @observable private summarizedText: string = ''; + @observable private GPTpopupText: string = ''; @observable private loadingSummary: boolean = false; @observable private showGPTPopup: boolean = false; + @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY; + @observable private selectedText: string = ''; + @observable private editorView?: EditorView; + @observable private textDoc?: Doc; + @observable private highlightRange: number[] | undefined; + private selectionRange: number[] | undefined; + @action setGPTPopupVis = (vis: boolean) => { this.showGPTPopup = vis; }; @action - setSummarizedText = (txt: string) => { - this.summarizedText = txt; + setGPTMode = (mode: GPTPopupMode) => { + this.GPTMode = mode; + }; + + @action + setGPTPopupText = (txt: string) => { + this.GPTpopupText = txt; }; + @action setLoading = (loading: boolean) => { this.loadingSummary = loading; }; - private selectedText: string = ''; + @action + setHighlightRange(r: number[] | undefined) { + this.highlightRange = r; + } + + @action public setSelectedText = (txt: string) => { this.selectedText = txt; }; + @action + public setEditorView = (editor: EditorView) => { + this.editorView = editor; + }; + + @action + public setTextDoc = (textDoc: Doc) => { + this.textDoc = textDoc; + }; + public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; @@ -104,7 +133,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { if (!opacity) { this._showLinkPopup = false; this.setGPTPopupVis(false); - this.setSummarizedText(''); + this.setGPTPopupText(''); } }, { fireImmediately: true } @@ -114,20 +143,20 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { selected => { this._showLinkPopup = false; this.setGPTPopupVis(false); - this.setSummarizedText(''); + this.setGPTPopupText(''); AnchorMenu.Instance.fadeOut(true); } ); } /** - * Returns a mock summary simulating an API call. + * Returns a mock api response. * @returns A Promise that resolves into a string */ - mockSummarize = async (): Promise<string> => { + mockGPTCall = async (): Promise<string> => { return new Promise((resolve, reject) => { setTimeout(() => { - resolve('Mock summary. This is a summary of the highlighted text.'); + resolve('test'); }, 1000); }); }; @@ -137,19 +166,68 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * @param e pointer down event */ gptSummarize = async (e: React.PointerEvent) => { + this.setHighlightRange(undefined); + this.setGPTPopupVis(true); + this.setGPTMode(GPTPopupMode.SUMMARY); + this.setLoading(true); + + try { + const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); + if (res) { + this.setGPTPopupText(res); + } else { + this.setGPTPopupText('Something went wrong.'); + } + } catch (err) { + console.error(err); + } + + this.setLoading(false); + }; + + /** + * Makes a GPT call to edit selected text. + * @returns nothing + */ + gptEdit = async () => { + if (!this.editorView) return; + this.setHighlightRange(undefined); + const state = this.editorView.state; + const sel = state.selection; + 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); + this.setGPTMode(GPTPopupMode.EDIT); this.setLoading(true); - const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); - // const res = await this.mockSummarize(); - if (res) { - this.setSummarizedText(res); - } else { - this.setSummarizedText('Something went wrong.'); + + try { + let res = await gptAPICall(selectedText, GPTCallType.EDIT); + // let res = await this.mockGPTCall(); + res = res.trim(); + const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to); + + if (res) { + this.setGPTPopupText(resultText); + this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]); + } else { + this.setGPTPopupText('Something went wrong.'); + } + } catch (err) { + console.error(err); } this.setLoading(false); }; + /** + * Replaces text suggestions from GPT. + */ + replaceText = (replacement: string) => { + if (!this.editorView || !this.textDoc) return; + this.textDoc.text = replacement; + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -250,7 +328,19 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { canSummarize = (): boolean => { const docs = SelectionManager.Docs(); if (docs.length > 0) { - return docs[0].type === 'pdf' || docs[0].type === 'web'; + return docs.some(doc => doc.type === 'pdf' || doc.type === 'web'); + } + return false; + }; + + /** + * Returns whether the selected text can be edited. + * @returns Whether the GPT icon for summarization should appear + */ + canEdit = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs.some(doc => doc.type === 'rtf'); } return false; }; @@ -273,7 +363,17 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { </button> </Tooltip> )} - <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.summarizedText} loadingSummary={this.loadingSummary} callApi={this.gptSummarize} /> + <GPTPopup + key="gptpopup" + visible={this.showGPTPopup} + text={this.GPTpopupText} + highlightRange={this.highlightRange} + loading={this.loadingSummary} + callSummaryApi={this.gptSummarize} + callEditApi={this.gptEdit} + replaceText={this.replaceText} + mode={this.GPTMode} + /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}> <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}> @@ -281,6 +381,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { </button> </Tooltip> )} + {this.canEdit() && ( + <Tooltip key="gpttextedit" title={<div className="dash-tooltip">Edit text with AI</div>}> + <button className="antimodeMenu-button annotate" onPointerDown={this.gptEdit} style={{ cursor: 'grab' }}> + <FontAwesomeIcon icon="pencil-alt" size="lg" /> + </button> + </Tooltip> + )} <Tooltip key="link" title={<div className="dash-tooltip">Find document to link to selected text</div>}> <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup}> <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" /> |
