import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; 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 { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { DocumentView } from '../nodes/DocumentView'; import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import './AnchorMenu.scss'; import { GPTPopup, GPTPopupMode } 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(); 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 _isLoading: boolean = false; @action public setSelectedText = (txt: string) => { this._selectedText = txt.trim(); }; 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 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 = async () => { GPTPopup.Instance.setVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); GPTPopup.Instance.setLoading(true); try { const res = await gptAPICall(this._selectedText, GPTCallType.SUMMARY); GPTPopup.Instance.setText(res || 'Something went wrong.'); } catch (err) { console.error(err); } GPTPopup.Instance.setLoading(false); }; // gptSummarize = async () => { // GPTPopup.Instance?.setSelectedText(this._selectedText); // GPTPopup.Instance.generateSummary(); // }; /** * Invokes the API with the selected text and stores it in the selected text. * @param e pointer down event */ gptFlashcards = async () => { const queryText = this._selectedText; try { const res = await gptAPICall(queryText, GPTCallType.FLASHCARD); console.log(res); GPTPopup.Instance.setText(res || 'Something went wrong.'); this.transferToFlashcard(res || 'Something went wrong'); } catch (err) { console.error(err); } GPTPopup.Instance.setLoading(false); }; /* * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them. */ transferToFlashcard = (text: string) => { // put each question generated by GPT on the front of the flashcard const senArr = text.split('Question'); const collectionArr: Doc[] = []; for (let i = 1; i < senArr.length; i++) { console.log('Arr ' + i + ': ' + senArr[i]); const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); newDoc.text = senArr[i]; collectionArr.push(newDoc); } // create a new carousel collection of these flashcards const newCol = Docs.Create.CarouselDocument(collectionArr, { _width: 250, _height: 200, _layout_fitWidth: false, _layout_autoHeight: true, }); this.addToCollection?.(newCol); }; /** * Creates a GPT drawing based on selected text. */ gptDraw = async (e: React.PointerEvent) => { try { SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation; runInAction(() => (this._isLoading = true)); await SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true); runInAction(() => (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.drawingInput = opts.text; docData.drawingComplexity = opts.complexity; docData.drawingColored = opts.autoColor; docData.drawingSize = opts.size; docData.drawingData = gptRes; }); 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} /> {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); } }