aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts7
-rw-r--r--src/client/views/MarqueeAnnotator.tsx1
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx2
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx18
-rw-r--r--src/client/views/nodes/ImageBox.scss21
-rw-r--r--src/client/views/nodes/ImageBox.tsx232
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx47
-rw-r--r--src/client/views/pdf/Annotation.scss32
-rw-r--r--src/client/views/pdf/PDFViewer.scss21
-rw-r--r--src/client/views/pdf/PDFViewer.tsx52
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>
);
}