aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf/AnchorMenu.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-05-22 11:25:32 -0400
committerbobzel <zzzman@gmail.com>2023-05-22 11:25:32 -0400
commitbed3309e1fda6597b2a8fea10ad82cd3a0402051 (patch)
treefe599bbdc5fca2c221e1e0f7a60995b7cd39f870 /src/client/views/pdf/AnchorMenu.tsx
parent887a4f7e0fc25fde87b20a5de2e7b0aee561cc78 (diff)
parent3d26d5b2654841a9b92f3d66b28d1dc8e36cca6a (diff)
merged physics with master
Diffstat (limited to 'src/client/views/pdf/AnchorMenu.tsx')
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx203
1 files changed, 192 insertions, 11 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index c53cc608c..5480600b0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -10,6 +10,10 @@ 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/GPT';
+import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { LightboxView } from '../LightboxView';
+import { EditorView } from 'prosemirror-view';
import './AnchorMenu.scss';
@observer
@@ -40,9 +44,58 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
@observable private _showLinkPopup: boolean = false;
- @observable public Highlighting: boolean = false;
@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;
+ @observable private textDoc?: Doc;
+ @observable private highlightRange: number[] | undefined;
+ 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;
+ }
+
+ @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;
@@ -76,18 +129,94 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer2 = reaction(
() => this._opacity,
- opacity => !opacity && (this._showLinkPopup = false),
+ opacity => {
+ if (!opacity) {
+ this._showLinkPopup = false;
+ this.setGPTPopupVis(false);
+ this.setGPTPopupText('');
+ }
+ },
{ fireImmediately: true }
);
this._disposer = reaction(
- () => SelectionManager.Views(),
+ () => SelectionManager.Views().slice(),
selected => {
this._showLinkPopup = false;
+ this.setGPTPopupVis(false);
+ this.setGPTPopupText('');
AnchorMenu.Instance.fadeOut(true);
}
);
}
+ /**
+ * Invokes the API with the selected text and stores it in the summarized text.
+ * @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);
+
+ try {
+ let res = await gptAPICall(selectedText, GPTCallType.EDIT);
+ // let res = await this.mockGPTCall();
+ if (!res) return;
+ res = res.trim();
+ const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1);
+
+ 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,
@@ -120,9 +249,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@action
highlightClicked = (e: React.MouseEvent) => {
- if (!this.Highlight(this.highlightColor, false, undefined, true) && this.Pinned) {
- this.Highlighting = !this.Highlighting;
- }
+ this.Highlight(this.highlightColor, false, undefined, true);
AnchorMenu.Instance.fadeOut(true);
};
@@ -137,7 +264,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
const button = (
<button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
<div className="anchor-color-preview">
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: this.Highlighting ? '' : 'rotate(-45deg)' }} />
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />
<div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
</div>
</button>
@@ -182,6 +309,31 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this.highlightColor = Utils.colorString(col);
};
+ /**
+ * Returns whether the selected text can be summarized. The goal is to have
+ * all selected text available to summarize but its only supported for pdf and web ATM.
+ * @returns Whether the GPT icon for summarization should appear
+ */
+ canSummarize = (): boolean => {
+ const docs = SelectionManager.Docs();
+ if (docs.length > 0) {
+ 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;
+ };
+
render() {
const buttons =
this.Status === 'marquee' ? (
@@ -192,6 +344,25 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<FontAwesomeIcon icon="comment-alt" size="lg" />
</button>
</Tooltip>
+ {/* 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>}>
+ <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="comment-dots" size="lg" />
+ </button>
+ </Tooltip>
+ )}
+ <GPTPopup
+ key="gptpopup"
+ visible={this.showGPTPopup}
+ text={this.GPTpopupText}
+ highlightRange={this.highlightRange}
+ loading={this.loadingGPT}
+ 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' }}>
@@ -199,6 +370,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
</button>
</Tooltip>
)}
+ {this.canEdit() && (
+ <Tooltip key="gpttextedit" title={<div className="dash-tooltip">AI edit suggestions</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" />
@@ -217,22 +395,25 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
) : (
<>
<Tooltip key="trash" title={<div className="dash-tooltip">Remove Link Anchor</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.Delete}>
+ <button className="antimodeMenu-button" style={{ display: this.Delete === returnFalse ? 'none' : undefined }} onPointerDown={this.Delete}>
<FontAwesomeIcon icon="trash-alt" size="lg" />
</button>
</Tooltip>
<Tooltip key="Pin" title={<div className="dash-tooltip">Pin to Presentation</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.PinToPres}>
+ <button className="antimodeMenu-button" style={{ display: this.PinToPres === returnFalse ? 'none' : undefined }} onPointerDown={this.PinToPres}>
<FontAwesomeIcon icon="map-pin" size="lg" />
</button>
</Tooltip>
<Tooltip key="trail" title={<div className="dash-tooltip">Show Linked Trail</div>}>
- <button className="antimodeMenu-button" onPointerDown={this.ShowTargetTrail}>
+ <button className="antimodeMenu-button" style={{ display: this.ShowTargetTrail === returnFalse ? 'none' : undefined }} onPointerDown={this.ShowTargetTrail}>
<FontAwesomeIcon icon="taxi" size="lg" />
</button>
</Tooltip>
<Tooltip key="toggle" title={<div className="dash-tooltip">make target visibility toggle on click</div>}>
- <button className="antimodeMenu-button" style={{ color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }} onPointerDown={this.MakeTargetToggle}>
+ <button
+ className="antimodeMenu-button"
+ style={{ display: this.IsTargetToggler === returnFalse ? 'none' : undefined, color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }}
+ onPointerDown={this.MakeTargetToggle}>
<FontAwesomeIcon icon="thumbtack" size="lg" />
</button>
</Tooltip>