From ad32c1606395cdc71ba50eb9b51d3a9d3b707ca0 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Tue, 18 Jun 2024 15:23:12 -0400 Subject: working on various image flashcard ideas --- src/client/apis/gpt/GPT.ts | 7 +- src/client/views/MarqueeAnnotator.tsx | 1 + .../views/collections/CollectionCardDeckView.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 18 +- src/client/views/nodes/ImageBox.scss | 21 ++ src/client/views/nodes/ImageBox.tsx | 232 +++++++++++++++++++++ src/client/views/pdf/AnchorMenu.tsx | 47 ++--- src/client/views/pdf/Annotation.scss | 32 +-- src/client/views/pdf/PDFViewer.scss | 21 ++ src/client/views/pdf/PDFViewer.tsx | 52 +++++ 12 files changed, 383 insertions(+), 56 deletions(-) (limited to 'src') 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 => { return []; } }; -const gptImageLabel = async (src: string): Promise => { +const gptImageLabel = async (src: string, prompt: string): Promise => { try { const response = await openai.chat.completions.create({ model: 'gpt-4o', @@ -128,7 +128,7 @@ const gptImageLabel = async (src: string): Promise => { { 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 => { ], }); 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; 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 !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() 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() 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() } 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() 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() { public static LayoutString(fieldKey: string) { @@ -73,9 +84,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { private _marqueeref = React.createRef(); private _mainCont: React.RefObject = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); + private _imageRef: HTMLImageElement | null = null; //