diff options
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 7 | ||||
-rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 1 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 18 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 21 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 232 | ||||
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 47 | ||||
-rw-r--r-- | src/client/views/pdf/Annotation.scss | 32 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.scss | 21 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 52 |
12 files changed, 383 insertions, 56 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index b036349dc..7bcd541c7 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -25,7 +25,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { // newest model: gpt-4 summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' }, edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' }, - flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' }, + flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ' }, completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." }, mermaid: { model: 'gpt-4-turbo', @@ -120,7 +120,7 @@ const gptGetEmbedding = async (src: string): Promise<number[]> => { return []; } }; -const gptImageLabel = async (src: string): Promise<string> => { +const gptImageLabel = async (src: string, prompt: string): Promise<string> => { try { const response = await openai.chat.completions.create({ model: 'gpt-4o', @@ -128,7 +128,7 @@ const gptImageLabel = async (src: string): Promise<string> => { { role: 'user', content: [ - { type: 'text', text: 'Give three to five labels to describe this image.' }, + { type: 'text', text: prompt }, { type: 'image_url', image_url: { @@ -141,6 +141,7 @@ const gptImageLabel = async (src: string): Promise<string> => { ], }); if (response.choices[0].message.content) { + console.log(response.choices[0].message.content); return response.choices[0].message.content; } return 'Missing labels'; diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c18ac6738..c7ffce2b4 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -34,6 +34,7 @@ export interface MarqueeAnnotatorProps { getPageFromScroll?: (top: number) => number; finishMarquee: (x?: number, y?: number) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); + anchorMenuFlashcard?: () => Promise<String>; anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined; highlightDragSrcColor?: string; } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index de46180e6..50af9df9e 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -373,7 +373,7 @@ export class CollectionCardView extends CollectionSubView() { const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; try { const hrefBase64 = await CollectionCardView.imageUrlToBase64(hrefComplete); - const response = await gptImageLabel(hrefBase64); + const response = await gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.'); image[DocData].description = response.trim(); return response; // Return the response from gptImageLabel } catch (error) { diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f2d634eaa..6976deea5 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -46,14 +46,14 @@ export class CollectionCarouselView extends CollectionSubView() { @observable private _filterMessage: string | undefined; get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore get sideField() { return "_" + this.fieldKey + "_usePath"; } // prettier-ignore - get starField() { return this.fieldKey + "_star"; } // prettier-ignore + get starField() { return "star"; } // prettier-ignore constructor(props: any) { super(props); makeObservable(this); // this.setModes(); this.layoutDoc.filterOp = cardMode.ALL; - Doc.setDocFilter(this.Document, 'data_star', undefined, 'match'); + Doc.setDocFilter(this.Document, 'star', undefined, 'match'); this.layoutDoc.practiceMode = practiceMode.NORMAL; this.layoutDoc._carousel_index = 0; } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index dc15c83c5..0afda3e64 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -440,7 +440,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); return CollectionCardView.imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 => !hrefBase64 ? undefined : - gptImageLabel(hrefBase64).then(labels => + gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.').then(labels => Promise.all(labels.split('\n').map(label => gptGetEmbedding(label))).then(embeddings => ({ doc, embeddings, labels }))) ); // prettier-ignore }); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 2fc297bec..3d33ff862 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -92,6 +92,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place + // this.childActive = false; return added; } return undefined; @@ -257,7 +258,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() newCol['x'] = this.layoutDoc['x']; newCol['y'] = NumCast(this.layoutDoc['y']) + 50; newCol.type_collection = 'carousel'; - console.log(newCol.data); + // console.log(newCol.data); if (gpt) { this._props.DocumentView?.()._props.addDocument?.(newCol); @@ -270,13 +271,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } gptFlashcardPile = async () => { - var text = (await this.askGPT(GPTCallType.FLASHCARD)) || ''; + var text = await this.askGPT(GPTCallType.FLASHCARD); - var senArr = text.trim().split('Question: '); + var senArr = text?.split('Question: '); var collectionArr: Doc[] = []; - for (let i = 1; i < senArr.length; i++) { - const newDoc = Docs.Create.ComparisonDocument(senArr[i].trim(), { _layout_isFlashcard: true, _width: 300, _height: 300 }); - newDoc.text = senArr[i].trim(); + for (let i = 1; i < senArr?.length!; i++) { + const newDoc = Docs.Create.ComparisonDocument(senArr![i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); + newDoc.text = senArr![i]; collectionArr.push(newDoc); } this.createFlashcardPile(collectionArr, true); @@ -451,13 +452,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (callType == GPTCallType.QUIZ) this._outputValue = res; // DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res; // this._outputValue = res; - - if (callType === GPTCallType.FLASHCARD) { + else if (callType === GPTCallType.FLASHCARD) { // console.log(res); this._loading = false; return res; } - console.log(res); + // console.log(res); } catch (err) { console.error('GPT call failed'); } diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3ffda5a35..be68ac8cd 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -139,3 +139,24 @@ .imageBox-fadeBlocker-hover { opacity: 0; } + +.loading-spinner { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + // left: 50%; + // top: 50%; + z-index: 200; + font-size: 20px; + font-weight: bold; + color: #17175e; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index e4b3a1b9b..ff938df78 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -35,6 +35,15 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; +import { URLField } from '../../../fields/URLField'; +import { gptImageLabel } from '../../apis/gpt/GPT'; +import ReactLoading from 'react-loading'; +import { FollowLinkScript } from '../../documents/DocUtils'; +import { basename } from 'path'; +import { ImageUtility } from './generativeFill/generativeFillUtils/ImageHandler'; +import { dropActionType } from '../../util/DropActionTypes'; +import { canvasSize } from './generativeFill/generativeFillUtils/generativeFillConstants'; +import axios from 'axios'; export class ImageEditorData { // eslint-disable-next-line no-use-before-define @@ -59,6 +68,8 @@ export class ImageEditorData { public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore } + +const API_URL = 'https://api.unsplash.com/search/photos'; @observer export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { @@ -73,9 +84,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { private _marqueeref = React.createRef<MarqueeAnnotator>(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); + private _imageRef: HTMLImageElement | null = null; // <video> ref + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable private searchInput = ''; @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @observable _curSuffix = ''; @observable _error = ''; + @observable private _loading = false; @observable _isHovering = false; // flag to switch between primary and alternate images on hover _ffref = React.createRef<CollectionFreeFormView>(); @@ -148,6 +164,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { Object.values(this._disposers).forEach(disposer => disposer?.()); } + fetchImages = async () => { + try { + const { data } = await axios.get(`${API_URL}?query=${this.searchInput}&page=1&per_page=${1}&client_id=${process.env.VITE_API_KEY}`); + console.log('data', data); + console.log(data.results); + const imageSnapshot = Docs.Create.ImageDocument(data.results[0].urls.small, { + _nativeWidth: Doc.NativeWidth(this.layoutDoc), + _nativeHeight: Doc.NativeHeight(this.layoutDoc), + x: NumCast(this.layoutDoc.x), + y: NumCast(this.layoutDoc.y), + onClick: FollowLinkScript(), + _width: 150, + _height: 150, + title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-', + }); + this._props.addDocument?.(imageSnapshot); + } catch (error) { + console.log(error); + } + }; + + handleSelection = async (selection: string) => { + this.searchInput = selection; + const images = await this.fetchImages(); + }; + @undoBatch drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { @@ -259,10 +301,189 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return cropping; }; + createCanvas = async (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => { + const width = NumCast(this.layoutDoc._width); + const canvas = document.createElement('canvas'); + // canvas.width = 640; + // canvas.height = (640 * Doc.NativeHeight(this.layoutDoc)) / (Doc.NativeWidth(this.layoutDoc) || 1); + canvas.width = NumCast(this.layoutDoc._width); + canvas.height = NumCast(this.layoutDoc._height); + const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions + if (ctx) { + // this._imageRef && ctx.drawImage(this._imageRef, 0, 0, canvas.width, canvas.height); + this._imageRef && ctx.drawImage(this._imageRef, NumCast(this._marqueeref.current?.left), NumCast(this._marqueeref.current?.top), this._width, this._height, 0, 0, 1000, 1000); + //this._imageRef && ctx.drawImage(this._imageRef, 0, 0, 2000, 1000, 0, 0, canvas.width, canvas.height); + // console.log(NumCast(this._marqueeref.current?.left) + 100); + } + const blob = await ImageUtility.canvasToBlob(canvas); + return ImageBox.selectUrlToBase64(blob); + + // if (this._imageRef) { + // const canv = ImageUtility.getCroppedImg(this._imageRef, this._imageRef.width, this._imageRef.height); + // console.log(this._imageRef.width); + // if (canv) { + // const blob = await ImageUtility.canvasToBlob(canv); + // return ImageBox.selectUrlToBase64(blob); + // } + // } + if (!this._imageRef) { + const b = Docs.Create.LabelDocument({ + x: NumCast(this.layoutDoc.x) + width, + y: NumCast(this.layoutDoc.y, 1), + _width: 150, + _height: 50, + // title: (this.layoutDoc._layout_currentTimecode || 0).toString(), + onClick: FollowLinkScript(), + }); + this._props.addDocument?.(b); + DocUtils.MakeLink(b, this.Document, { link_relationship: 'image snapshot' }); + } else { + // convert to desired file format + // const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' + // // if you want to preview the captured image, + // const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, ''); + // const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_')); + // const filename = basename(encodedFilename); + // ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY)); + } + // convert to desired file format + + // const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' + // // if you want to preview the captured image, + // const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, ''); + // const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_')); + // const filename = basename(encodedFilename); + //ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY)); + // } + // const docViewContent = this.DocumentView?.().ContentDiv!; + // if (docViewContent instanceof HTMLCanvasElement) { + // const canvas = docViewContent; + // const img = document.createElement('img'); // create a Image Element + // img.src = canvas.toDataURL(); // image sourcez + // img.style.width = canvas.style.width; + // img.style.height = canvas.style.height; + // const parEle = newCan.parentElement as HTMLElement; + // parEle.removeChild(newCan); + // parEle.appendChild(img); + // } + }; + + createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => { + const url = !imagePath.startsWith('/') ? ClientUtils.CorsProxy(imagePath) : imagePath; + const width = NumCast(this.layoutDoc._width) || 1; + const height = NumCast(this.layoutDoc._height); + const imageSnapshot = Docs.Create.ImageDocument(url, { + _nativeWidth: Doc.NativeWidth(this.layoutDoc), + _nativeHeight: Doc.NativeHeight(this.layoutDoc), + x: NumCast(this.layoutDoc.x) + width, + y: NumCast(this.layoutDoc.y), + onClick: FollowLinkScript(), + _width: 150, + _height: (height / width) * 150, + title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-', + }); + Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc)); + Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc)); + this._props.addDocument?.(imageSnapshot); + DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' }); + // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1 + setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true)); + }; + + /** + * + if (oldDiv instanceof HTMLCanvasElement) { + const canvas = oldDiv; + const img = document.createElement('img'); // create a Image Element + img.src = canvas.toDataURL(); // image sourcez + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + */ + + static selectUrlToBase64 = async (blob: Blob): Promise<string> => { + try { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = error => reject(error); + }); + } catch (error) { + console.error('Error:', error); + throw error; + } + }; + + static imageUrlToBase64 = async (imageUrl: string): Promise<string> => { + try { + const response = await fetch(imageUrl); + const blob = await response.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = error => reject(error); + }); + } catch (error) { + console.error('Error:', error); + throw error; + } + }; + + getImageDesc = async () => { + // if (StrCast(this.dataDoc.description)) return StrCast(this.dataDoc.description); // Return existing description + const { href } = (this.dataDoc.data as URLField).url; + const hrefParts = href.split('.'); + const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; + this._loading = true; + try { + // const hrefBase64 = await ImageBox.imageUrlToBase64(hrefComplete); + const hrefBase64 = await this.createCanvas(); + const response = await gptImageLabel(hrefBase64, 'Tell me what words you see on this image.'); + //const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this text with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: '); + console.log(response); + // AnchorMenu.Instance.transferToFlashcard(response); + // this.Document[DocData].description = response.trim(); + // return response; // Return the response + } catch (error) { + console.log('Error'); + } + this._loading = false; + // return ''; + }; + + @action + setRef = (iref: HTMLImageElement | null) => { + this._imageRef = iref; + // if (iref) { + // this._videoRef!.ontimeupdate = this.updateTimecode; + // // @ts-ignore + // // vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); + // this._disposers.reactionDisposer?.(); + // this._disposers.reactionDisposer = reaction( + // () => NumCast(this.layoutDoc._layout_currentTimecode), + // time => { + // !this._playing && (vref.currentTime = time); + // }, + // { fireImmediately: true } + // ); + + // (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoThumbnails.DENSE) && this.getVideoThumbnails(); + // } + }; + specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; + // funcs.push({ description: 'Create ai flashcards', event: () => this.getImageDesc(), icon: 'id-card' }); + funcs.push({ description: 'Get Images', event: () => this.handleSelection('Cats'), icon: 'redo-alt' }); funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' }); funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' }); @@ -391,6 +612,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <div className="imageBox-fader" style={{ opacity: backAlpha }}> <img alt="" + ref={this.setRef} key="paths" src={srcpath} style={{ transform, transformOrigin }} @@ -432,6 +654,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { action(moveEv => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]); + this._width = moveEv.clientX; + this._height = moveEv.clientY; return true; }), returnFalse, @@ -443,6 +667,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action finishMarquee = () => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; + AnchorMenu.Instance.gptFlashcards = this.getImageDesc; + AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument; this._marqueeref.current?.onTerminateSelection(); this._props.select(false); }; @@ -500,6 +726,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { addDocument={this.addDocument}> {this.content} </CollectionFreeFormView> + {this._loading ? ( + <div className="loading-spinner" style={{ position: 'absolute' }}> + <ReactLoading type="spin" height={50} width={50} color={'blue'} /> + </div> + ) : null} {this.annotationLayer} {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : ( <MarqueeAnnotator @@ -518,6 +749,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { marqueeContainer={this._mainCont.current} highlightDragSrcColor="" anchorMenuCrop={this.crop} + // anchorMenuFlashcard={() => this.getImageDesc()} /> )} </div> diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index c1198b4f7..777117f26 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -54,6 +54,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { this._y = y; }; + @computed public get selectedText() { + return this._selectedText; + } public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; @@ -68,6 +71,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { public MakeTargetToggle: () => void = unimplementedFunction; public ShowTargetTrail: () => void = unimplementedFunction; public IsTargetToggler: () => boolean = returnFalse; + public gptFlashcards: () => void = unimplementedFunction; public get Active() { return this._left > 0; } @@ -110,23 +114,23 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { * Invokes the API with the selected text and stores it in the selected text. * @param e pointer down event */ - gptFlashcards = async (e: React.PointerEvent) => { - const queryText = this._selectedText; - this._loading = true; - 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); - }; + // gptPDFFlashcards = async () => { + // const queryText = this._selectedText; + // this._loading = true; + // 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. - */ + // /* + // * 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 var senArr = text.trim().split('Question: '); @@ -236,12 +240,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { /> )} {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */} - <IconButton - tooltip="Create flashcards" // - onPointerDown={e => this.gptFlashcards(e)} - icon={<FontAwesomeIcon icon="id-card" size="lg" />} - color={SettingsManager.userColor} - /> + <IconButton tooltip="Create flashcards" onPointerDown={this.gptFlashcards} icon={<FontAwesomeIcon icon="id-card" size="lg" />} color={SettingsManager.userColor} /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( <IconButton tooltip="Click to Record Annotation" // @@ -267,11 +266,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { /> </div> )} - {this._loading ? ( + {/* {this._loading ? ( <div className="loading-spinner" style={{ position: 'absolute' }}> <ReactLoading type="spin" height={30} width={30} color={'white'} /> </div> - ) : null} + ) : null} */} </> ) : ( <> diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss index 329819ea2..26856b74e 100644 --- a/src/client/views/pdf/Annotation.scss +++ b/src/client/views/pdf/Annotation.scss @@ -8,20 +8,20 @@ cursor: pointer; } } -.loading-spinner { - display: flex; - justify-content: center; - align-items: center; - height: 90%; - width: 93%; - left: 10; - font-size: 20px; - font-weight: bold; - color: #0b0a0a; -} +// .loading-spinner { +// display: flex; +// justify-content: center; +// align-items: center; +// height: 90%; +// width: 93%; +// left: 10; +// font-size: 20px; +// font-weight: bold; +// color: #0b0a0a; +// } -@keyframes spin { - to { - transform: rotate(360deg); - } -} +// @keyframes spin { +// to { +// transform: rotate(360deg); +// } +// } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index d3dd9f727..e70102ce9 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -107,3 +107,24 @@ .pdfViewerDash-interactive { pointer-events: all; } + +.loading-spinner { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + // left: 50%; + // top: 50%; + z-index: 200; + font-size: 20px; + font-weight: bold; + color: #17175e; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index af7a5774d..befbee48b 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -32,6 +32,8 @@ import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; import { Docs } from '../../documents/Documents'; import './PDFViewer.scss'; +import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; +import ReactLoading from 'react-loading'; // pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. @@ -70,6 +72,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { @observable _textSelecting = true; @observable _showWaiting = true; @observable Index: number = -1; + @observable private _loading = false; private _pdfViewer: any; private _styleRule: any; // stylesheet rule for making hyperlinks clickable @@ -406,6 +409,49 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } }; + gptPDFFlashcards = async () => { + const queryText = this._selectionText; + this._loading = true; + try { + const res = await gptAPICall(queryText, GPTCallType.FLASHCARD); + console.log(res); + // GPTPopup.Instance.setText(res || 'Something went wrong.'); + AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong'); + // this.transferToFlashcard(res || 'Something went wrong'); + } catch (err) { + console.error(err); + } + this._loading = false; + // GPTPopup.Instance.setLoading(false); + }; + + // transferToFlashcard = (text: string) => { + // // put each question generated by GPT on the front of the flashcard + // var senArr = text.trim().split('Question: '); + // var 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, + // }); + + // newCol.x = this._props.layoutDoc['x']; + // newCol.y = this._props.layoutDoc['y']; + // newCol.zIndex = 100; + + // this._props.DocumentView?.()._props.addDocument?.(newCol); + // console.log('HERE'); + // this._loading = false; + // }; + @action finishMarquee = (/* x?: number, y?: number */) => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; @@ -437,6 +483,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { GPTPopup.Instance.addDoc = this._props.sidebarAddDoc; // allows for creating collection AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument; + AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards; }; @action @@ -625,6 +672,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { /> )} </div> + {this._loading ? ( + <div className="loading-spinner" style={{ position: 'absolute' }}> + <ReactLoading type="spin" height={80} width={80} color={'blue'} /> + </div> + ) : null} </div> ); } |