import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorResult } from 'react-color'; import ReactLoading from 'react-loading'; import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ComparisonBox } from '../nodes/ComparisonBox'; import { DocumentView } from '../nodes/DocumentView'; import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import './AnchorMenu.scss'; import { GPTPopup } from './GPTPopup/GPTPopup'; @observer export class AnchorMenu extends AntimodeMenu { // eslint-disable-next-line no-use-before-define static Instance: AnchorMenu; private _disposer: IReactionDisposer | undefined; private _commentRef = React.createRef(); private _cropRef = React.createRef(); @observable private _loading = false; constructor(props: AntimodeMenuProps) { super(props); makeObservable(this); AnchorMenu.Instance = this; AnchorMenu.Instance._canFade = false; } @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)'; @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions @observable private _selectedText: string = ''; @observable private _x: number = 0; @observable private _y: number = 0; @observable private _isLoading: boolean = false; @action public setSelectedText = (txt: string) => { this._selectedText = txt.trim(); }; @action public setLocation = (x: number, y: number) => { this._x = x; this._y = y; }; @computed public get selectedText() { return this._selectedText; } public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string) => void = emptyFunction; public GetAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = emptyFunction; public Delete: () => void = unimplementedFunction; public PinToPres: () => void = unimplementedFunction; public MakeTargetToggle: () => void = unimplementedFunction; public ShowTargetTrail: () => void = unimplementedFunction; public IsTargetToggler: () => boolean = returnFalse; public gptFlashcards: () => void = unimplementedFunction; public makeLabels: () => void = unimplementedFunction; public marqueeWidth = 0; public marqueeHeight = 0; public get Active() { return this._left > 0; } public AddDrawingAnnotation: (doc: Doc) => void = unimplementedFunction; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; componentWillUnmount() { this._disposer?.(); } componentDidMount() { this._disposer = reaction( () => DocumentView.Selected().slice(), () => AnchorMenu.Instance.fadeOut(true) ); } /** * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ gptSummarize = () => GPTPopup.Instance.generateSummary(this._selectedText); /* * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them. */ transferToFlashcard = (text: string, x: number, y: number) => { ComparisonBox.createFlashcardDeck(text, 250, 200, 'data_front', 'data_back').then( action(newCol => { newCol.x = x; newCol.y = y; newCol.zIndex = 1000; this.addToCollection?.(newCol); this._loading = false; }) ); }; /** * Creates a GPT drawing based on selected text. */ gptDraw = (e: React.PointerEvent) => { try { SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation; runInAction(() => (this._isLoading = true)); SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true)?.then( action(() => { this._isLoading = false; }) ); } catch (err) { console.error(err); } }; /** * Defines how a GPT drawing should be added to the current document. */ @undoBatch createDrawingAnnotation = action((drawing: Doc, opts: DrawingOptions, gptRes: string) => { this.AddDrawingAnnotation(drawing); const docData = drawing[DocData]; docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text; docData.ai_drawing_input = opts.text; docData.ai_drawing_complexity = opts.complexity; docData.ai_drawing_colored = opts.autoColor; docData.ai_drawing_size = opts.size; docData.ai_drawing_data = gptRes; docData.ai = 'gpt'; }); pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, (moveEv: PointerEvent) => { this.StartDrag(moveEv, this._commentRef.current!); return true; }, returnFalse, clickEv => this.OnClick?.(clickEv) ); }; audioDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv)); }; cropDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, (moveEv: PointerEvent) => { this.StartCropDrag(moveEv, this._cropRef.current!); return true; }, returnFalse, clickev => this.OnCrop?.(clickev) ); }; @action highlightClicked = () => { this.Highlight(this.highlightColor); AnchorMenu.Instance.fadeOut(true); }; @computed get highlighter() { return ( } tooltip="Click to Highlight" onClick={this.highlightClicked} colorPicker={this.highlightColor} color={SettingsManager.userColor} /> ); } @action changeHighlightColor = (color: string) => { const col: ColorResult = { hex: color, hsl: { a: 0, h: 0, s: 0, l: 0 }, rgb: { a: 0, r: 0, b: 0, g: 0 }, }; this.highlightColor = ClientUtils.colorString(col); }; render() { const buttons = this.Status === 'marquee' ? ( <> {this.highlighter}
} color={SettingsManager.userColor} />
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */} {this._selectedText && ( } color={SettingsManager.userColor} /> )} {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */} } color={SettingsManager.userColor} /> } color={SettingsManager.userColor} /> {this._selectedText && ( this.gptDraw(e)} icon={this._isLoading ? : } color={SettingsManager.userColor} /> )} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( } color={SettingsManager.userColor} /> )} } popup={} color={SettingsManager.userColor} /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
} color={SettingsManager.userColor} />
)} ) : ( <> {this.Delete !== returnFalse && ( } color={SettingsManager.userColor} /> )} {this.PinToPres !== returnFalse && ( } color={SettingsManager.userColor} /> )} {this.ShowTargetTrail !== returnFalse && ( } color={SettingsManager.userColor} /> )} {this.IsTargetToggler !== returnFalse && ( } color={SettingsManager.userColor} /> )} ); return this.getElement(buttons); } }