aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf
diff options
context:
space:
mode:
authorNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-06-03 13:33:37 -0400
committerNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-06-03 13:33:37 -0400
commit9e77f980e7704999ef0a1c1845d660bccb13ff8a (patch)
tree14ca0da5915e4382a7bcb15f7d0b241941c8291f /src/client/views/pdf
parent1be63695875c9242fba43d580465e8765cf3991d (diff)
parent202e994515392892676f8f080852db1e32b8dbd3 (diff)
Merge branch 'master' into nathan-starter
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx86
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss43
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx146
-rw-r--r--src/client/views/pdf/PDFViewer.tsx5
4 files changed, 248 insertions, 32 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index a837969aa..2f6824466 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -7,13 +7,14 @@ import { ColorResult } from 'react-color';
import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
+import { Docs } from '../../documents/Documents';
import { SettingsManager } from '../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
-import './AnchorMenu.scss';
-import { GPTPopup } from './GPTPopup/GPTPopup';
import { DocumentView } from '../nodes/DocumentView';
+import './AnchorMenu.scss';
+import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -36,10 +37,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable public Status: 'marquee' | 'annotation' | '' = '';
// GPT additions
- @observable private selectedText: string = '';
+ @observable private _selectedText: string = '';
@action
public setSelectedText = (txt: string) => {
- this.selectedText = txt;
+ this._selectedText = txt.trim();
};
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -59,6 +60,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public get Active() {
return this._left > 0;
}
+ public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
componentWillUnmount() {
this._disposer?.();
@@ -76,8 +78,62 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* @param e pointer down event
*/
gptSummarize = async () => {
- GPTPopup.Instance?.setSelectedText(this.selectedText);
- GPTPopup.Instance.generateSummary();
+ 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);
};
pointerDown = (e: React.PointerEvent) => {
@@ -140,13 +196,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this.highlightColor = ClientUtils.colorString(col);
};
- /**
- * Returns whether the selected text can be summarized. The goal is to have
- * all selected text available to summarize but its only supported for pdf and web ATM.
- * @returns Whether the GPT icon for summarization should appear
- */
- canSummarize = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
-
render() {
const buttons =
this.Status === 'marquee' ? (
@@ -161,7 +210,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
</div>
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
- {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
+ {this._selectedText && (
<IconButton
tooltip="Summarize with AI" //
onPointerDown={this.gptSummarize}
@@ -169,6 +218,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
)}
+ {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */}
+ <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" //
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 5d966395c..6d8793f82 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -11,8 +11,8 @@ $highlightedText: #82e0ff;
right: 10px;
width: 250px;
min-height: 200px;
- border-radius: 15px;
- padding: 15px;
+ border-radius: 16px;
+ padding: 16px;
padding-bottom: 0;
z-index: 999;
display: flex;
@@ -55,16 +55,29 @@ $highlightedText: #82e0ff;
overflow-y: auto;
}
- .btns-wrapper {
+ .btns-wrapper-gpt {
height: 50px;
display: flex;
- justify-content: space-between;
+ justify-content: center;
align-items: center;
+ transform: translateY(30px);
+
+
+ .searchBox-input{
+ transform: translateY(-15px);
+ height: 50px;
+ border-radius: 10px;
+ border-color: #5b97ff;
+ }
+
+
.summarizing {
display: flex;
align-items: center;
}
+
+
}
button {
@@ -111,6 +124,28 @@ $highlightedText: #82e0ff;
}
}
+.loading-spinner {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100px;
+ font-size: 20px;
+ font-weight: bold;
+ color: #666;
+}
+
+
+
+
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+
+
.image-content-wrapper {
display: flex;
flex-direction: column;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index c1bfdf176..cb5aad32d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -17,12 +17,16 @@ import { DocUtils } from '../../../documents/DocUtils';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { AnchorMenu } from '../AnchorMenu';
import './GPTPopup.scss';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
export enum GPTPopupMode {
SUMMARY,
EDIT,
IMAGE,
+ FLASHCARD,
DATA,
+ SORT,
}
interface GPTPopupProps {}
@@ -32,6 +36,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
// eslint-disable-next-line no-use-before-define
static Instance: GPTPopup;
@observable private chatMode: boolean = false;
+ private correlatedColumns: string[] = [];
@observable
public visible: boolean = false;
@@ -99,6 +104,14 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
this.chatMode = false;
};
+ @observable
+ private sortDone: boolean = false; // this is so redundant but the og done variable was causing weird unknown problems and im just a girl
+
+ @action
+ public setSortDone = (done: boolean) => {
+ this.sortDone = done;
+ };
+
// change what can be a ref into a ref
@observable
private sidebarId: string = '';
@@ -121,10 +134,48 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
this.textAnchor = anchor;
};
+ @observable
+ public sortDesc: string = '';
+
+ @action public setSortDesc = (t: string) => {
+ this.sortDesc = t;
+ };
+
+ @observable onSortComplete?: (sortResult: string) => void;
+ @observable cardsDoneLoading = false;
+
+ @action setCardsDoneLoading(done: boolean) {
+ console.log(done + 'HI HIHI');
+ this.cardsDoneLoading = done;
+ }
+
public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
+ public createFilteredDoc: (axes?: any) => boolean = () => false;
public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
/**
+ * Sorts cards in the CollectionCardDeckView
+ */
+ generateSort = async () => {
+ this.setLoading(true);
+ this.setSortDone(false);
+
+ try {
+ const res = await gptAPICall(this.sortDesc, GPTCallType.SORT);
+ // Trigger the callback with the result
+ if (this.onSortComplete) {
+ this.onSortComplete(res || 'Something went wrong :(');
+ console.log(res);
+ }
+ } catch (err) {
+ console.error(err);
+ }
+
+ this.setLoading(false);
+ this.setSortDone(true);
+ };
+
+ /**
* Generates a Dalle image and uploads it to the server.
*/
generateImage = async () => {
@@ -136,12 +187,9 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
try {
const imageUrls = await gptImageCall(this.imgDesc);
- console.log('Image urls: ', imageUrls);
if (imageUrls && imageUrls[0]) {
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [imageUrls[0]] });
- console.log('Upload result: ', result);
const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
- console.log('Upload source: ', source);
this.setImgUrls([[imageUrls[0], source]]);
}
} catch (err) {
@@ -151,6 +199,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
return undefined;
};
+ /**
+ * Completes an API call to generate a summary of
+ * this.selectedText in the popup.
+ */
generateSummary = async () => {
GPTPopup.Instance.setVisible(true);
GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
@@ -165,12 +217,21 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
GPTPopup.Instance.setLoading(false);
};
+ /**
+ * Completes an API call to generate an analysis of
+ * this.dataJson in the popup.
+ */
generateDataAnalysis = async () => {
GPTPopup.Instance.setVisible(true);
GPTPopup.Instance.setLoading(true);
try {
const res = await gptAPICall(this.dataJson, GPTCallType.DATA, this.dataChatPrompt);
- GPTPopup.Instance.setText(res || 'Something went wrong.');
+ const json = JSON.parse(res! as string);
+ const keys = Object.keys(json);
+ this.correlatedColumns = [];
+ this.correlatedColumns.push(json[keys[0]]);
+ this.correlatedColumns.push(json[keys[1]]);
+ GPTPopup.Instance.setText(json[keys[2]] || 'Something went wrong.');
} catch (err) {
console.error(err);
}
@@ -188,6 +249,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
_layout_autoHeight: true,
});
this.addDoc(newDoc, this.sidebarId);
+ // newDoc.data = 'Hello world';
const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false);
if (anchor) {
DocUtils.MakeLink(newDoc, anchor, {
@@ -197,6 +259,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
};
/**
+ * Creates a histogram to show the correlation relationship that was found
+ */
+ private createVisualization = () => {
+ this.createFilteredDoc(this.correlatedColumns);
+ };
+
+ /**
* Transfers the image urls to actual image docs
*/
private transferToImage = (source: string) => {
@@ -245,6 +314,59 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
}
};
+ sortBox = () => (
+ <>
+ <div>
+ {this.heading('SORTING')}
+ {this.loading ? (
+ <div className="content-wrapper">
+ <div className="loading-spinner">
+ <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+ <span>Loading...</span>
+ </div>
+ </div>
+ ) : (
+ <>
+ {!this.cardsDoneLoading ? (
+ <div className="content-wrapper">
+ <div className="loading-spinner">
+ <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+ <span>Reading Cards...</span>
+ </div>
+ </div>
+ ) : (
+ !this.sortDone && (
+ <div className="btns-wrapper-gpt">
+ <Button
+ tooltip="Have ChatGPT sort your cards for you!"
+ text="Sort!"
+ onClick={this.generateSort}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ type={Type.TERT}
+ style={{
+ width: '90%', // Almost as wide as the container
+ textAlign: 'center',
+ color: '#ffffff', // White text
+ fontSize: '16px', // Adjust font size as needed
+ }}
+ />
+ </div>
+ )
+ )}
+
+ {this.sortDone && (
+ <div>
+ <div className="content-wrapper">
+ <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : 'Sorting done! Feel free to move things around / regenerate :) !'}</p>
+ <IconButton tooltip="Generate Again" onClick={() => this.setSortDone(false)} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+ </div>
+ </div>
+ )}
+ </>
+ )}
+ </div>
+ </>
+ );
imageBox = () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
{this.heading('GENERATED IMAGE')}
@@ -291,8 +413,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
<div className="btns-wrapper">
{this.done ? (
<>
- <IconButton tooltip="Generate Again" onClick={this.generateSummary} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ <IconButton tooltip="Generate Again" onClick={this.generateSummary /* this.callSummaryApi */} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} />
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SettingsManager.userVariantColor)} type={Type.TERT} />
</>
) : (
<div className="summarizing">
@@ -303,7 +425,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
onClick={() => {
this.setDone(true);
}}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={StrCast(SettingsManager.userVariantColor)}
type={Type.TERT}
/>
</div>
@@ -356,8 +478,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
/>
) : (
<>
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
- <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
</>
)
) : (
@@ -369,7 +491,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
onClick={() => {
this.setDone(true);
}}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={StrCast(SnappingManager.userVariantColor)}
type={Type.TERT}
/>
</div>
@@ -390,14 +512,14 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
heading = (headingText: string) => (
<div className="summary-heading">
<label className="summary-text">{headingText}</label>
- {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
+ {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
</div>
);
render() {
return (
<div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
- {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : null}
+ {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : null}
</div>
);
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2327ee0d8..6c1617c38 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -24,11 +24,13 @@ import { FieldViewProps } from '../nodes/FieldView';
import { FocusViewOptions } from '../nodes/FocusViewOptions';
import { LinkInfo } from '../nodes/LinkDocPreview';
import { PDFBox } from '../nodes/PDFBox';
+import { ComparisonBox } from '../nodes/ComparisonBox';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProp';
import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
+import { Docs } from '../../documents/Documents';
import './PDFViewer.scss';
// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -430,9 +432,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
}
- // Changing which document to add the annotation to (the currently selected PDF)
GPTPopup.Instance.setSidebarId('data_sidebar');
GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
+ // allows for creating collection
+ AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
};
@action