aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgeireann <geireann.lindfield@gmail.com>2023-03-02 11:44:04 -0500
committergeireann <geireann.lindfield@gmail.com>2023-03-02 11:44:04 -0500
commitc6425a0469727305f76d00e3f8c545e04aad61cc (patch)
treeff8eb7d202f9f8c1305adcf2d4d5933c8c8dca63 /src
parent4a60851bd4d3495b2605863e3070c73129c9bc57 (diff)
parentd34d212ea550a3c1ca16747fadeb9e69c936cb5d (diff)
Merge branch 'pres-trail-sophie' of https://github.com/brown-dash/Dash-Web into pres-trail-sophie
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/Summarization.ts41
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/MarqueeAnnotator.tsx6
-rw-r--r--src/client/views/nodes/PDFBox.tsx1
-rw-r--r--src/client/views/nodes/WebBox.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx56
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx59
-rw-r--r--src/client/views/pdf/GPTPopup.scss91
-rw-r--r--src/client/views/pdf/GPTPopup.tsx115
-rw-r--r--src/client/views/pdf/PDFViewer.tsx22
11 files changed, 324 insertions, 78 deletions
diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts
index 931e0e48f..b65736237 100644
--- a/src/client/apis/gpt/Summarization.ts
+++ b/src/client/apis/gpt/Summarization.ts
@@ -1,23 +1,48 @@
import { Configuration, OpenAIApi } from 'openai';
-const gptSummarize = async (text: string) => {
- text += '.';
+enum GPTCallType {
+ SUMMARY = 'summary',
+ COMPLETION = 'completion',
+}
+
+type GPTCallOpts = {
+ model: string;
+ maxTokens: number;
+ temp: number;
+ prompt: string;
+};
+
+const callTypeMap: { [type: string]: GPTCallOpts } = {
+ summary: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: 'Summarize this text: ' },
+ completion: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: '' },
+};
+
+/**
+ * Calls the OpenAI API.
+ *
+ * @param inputText Text to process
+ * @returns AI Output
+ */
+const gptAPICall = async (inputText: string, callType: GPTCallType) => {
+ if (callType === GPTCallType.SUMMARY) inputText += '.';
+ const opts: GPTCallOpts = callTypeMap[callType];
try {
const configuration = new Configuration({
apiKey: process.env.OPENAI_KEY,
});
const openai = new OpenAIApi(configuration);
const response = await openai.createCompletion({
- model: 'text-curie-001',
- max_tokens: 256,
- temperature: 0.7,
- prompt: `Summarize this text in one sentence: ${text}`,
+ model: opts.model,
+ max_tokens: opts.maxTokens,
+ temperature: opts.temp,
+ prompt: `${opts.prompt}${inputText}`,
});
return response.data.choices[0].text;
} catch (err) {
console.log(err);
- return '';
+ return 'Error connecting with API.';
}
};
-export { gptSummarize };
+
+export { gptAPICall, GPTCallType};
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index da64e7c12..d7603cf5a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -245,6 +245,7 @@ export class MainView extends React.Component {
library.add(
...[
+ fa.faExclamationCircle,
fa.faEdit,
fa.faTrash,
fa.faTrashAlt,
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9e9f24393..8757ac7bf 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -28,8 +28,6 @@ export interface MarqueeAnnotatorProps {
docView: DocumentView;
savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
selectionText: () => string;
- summaryText: () => string;
- setSummaryText: () => Promise<void>;
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
getPageFromScroll?: (top: number) => number;
@@ -52,10 +50,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true);
AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true));
- AnchorMenu.Instance.OnSummary = async (e: PointerEvent) => {
- await this.props.setSummaryText();
- this.props.anchorMenuClick?.()?.(this.highlight('', true, undefined, undefined, true));
- };
AnchorMenu.Instance.OnAudio = unimplementedFunction;
AnchorMenu.Instance.Highlight = this.highlight;
AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b88ac113e..40c04c08f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -485,6 +485,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}}>
<PDFViewer
{...this.props}
+ sidebarAddDoc={this.sidebarAddDocument}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 996028ec8..43b3b0536 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -37,6 +37,7 @@ import './WebBox.scss';
import React = require('react');
import { DragManager } from '../../util/DragManager';
import { gptSummarize } from '../../apis/gpt/Summarization';
+import { GPTPopup } from '../pdf/GPTPopup';
const { CreateImage } = require('./WebBoxRenderer');
const _global = (window /* browser */ || global) /* node */ as any;
const htmlToText = require('html-to-text');
@@ -358,8 +359,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const sel = this._iframe.contentWindow.getSelection();
if (sel) {
this._selectionText = sel.toString();
+ AnchorMenu.Instance.setSelectedText(sel.toString());
this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale);
+ // Changing which document to add the annotation to (the currently selected WebBox)
+ GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}-${this._urlHash}-sidebar`);
+ GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
}
};
@@ -780,6 +785,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
+ console.log(annotationKey);
(doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.webUrl = this._url));
return this.addDocument(doc, annotationKey);
};
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index cbe0a465d..fd7fbb333 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -149,6 +149,10 @@ audiotag:hover {
}
}
+.gpt-typing-wrapper {
+ padding: 10px;
+}
+
// .menuicon {
// display: inline-block;
// border-right: 1px solid rgba(0, 0, 0, 0.2);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d4dffcb62..c6801bb79 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -64,8 +64,8 @@ import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
import { Configuration, OpenAIApi } from 'openai';
-import { CollectionSubView, SlowLoadDocuments } from '../../collections/CollectionSubView';
import { Networking } from '../../../Network';
+import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization';
const translateGoogleApi = require('translate-google-api');
export const GoogleRef = 'googleDocId';
@@ -174,6 +174,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
}
+ // State for GPT
+ @observable
+ private gptRes: string = '';
+
public static PasteOnLoad: ClipboardEvent | undefined;
public static SelectOnLoad = '';
public static DontSelectInitialText = false; // whether initial text should be selected or not
@@ -842,14 +846,46 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
+ optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' });
optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
};
+ mockGPT = async (): Promise<string> => {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve('Mock GPT Call.');
+ }, 2000);
+ });
+ };
+
+ animateRes = (resIndex: number) => {
+ if (resIndex < this.gptRes.length) {
+ this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
+ setTimeout(() => {
+ this.animateRes(resIndex + 1);
+ }, 20);
+ }
+ };
+
+ askGPT = action(async () => {
+ try {
+ let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+ // let res = await this.mockGPT();
+ if (res) {
+ this.gptRes = res;
+ this.animateRes(0);
+ }
+ } catch (err) {
+ console.log(err);
+ this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong';
+ }
+ });
+
generateImage = async () => {
- console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text);
+ console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
try {
const configuration = new Configuration({
apiKey: process.env.OPENAI_KEY,
@@ -858,13 +894,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const response = await openai.createImage({
prompt: (this.dataDoc.text as RichTextField)?.Text,
n: 1,
- size: "1024x1024",
+ size: '1024x1024',
});
let image_url = response.data.data[0].url;
console.log(image_url);
if (image_url) {
- const batch = UndoManager.StartBatch('generate openAI image');
- // const loadingDoc = SlowLoadDocuments(image_url, , [], "", emptyFunction, NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, NumCast(this.rootDoc.y), this.addDocument, this.props.Document._viewType === CollectionViewType.Freeform).then(batch.end);
const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] });
const source = Utils.prepend(accessPaths.agnostic.client);
const newDoc = Docs.Create.ImageDocument(source, {
@@ -872,11 +906,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
y: NumCast(this.rootDoc.y),
_height: 200,
_width: 200,
- // _nativeWidth: 200,
- // _nativeHeight: 200
})
- // Doc.GetProto(newDoc)["data-nativeHeight"] = 200;
- // Doc.GetProto(newDoc)["data-nativeWidth"] = 200;
if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) {
newDoc.overlayX = this.rootDoc.x;
newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
@@ -884,16 +914,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
} else {
this.props.addDocument?.(newDoc);
}
-
- DocUtils.MakeLink({doc: this.rootDoc}, {doc: newDoc}, "Dall-E");
+ // Create link between prompt and image
+ DocUtils.MakeLink({doc: this.rootDoc}, {doc: newDoc}, "Image Prompt");
}
} catch (err) {
console.log(err);
return '';
}
-
- }
-
+ };
breakupDictation = () => {
if (this._editorView && this._recording) {
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 6bcfbe4c2..04904b3b1 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -10,7 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
-import { gptSummarize } from '../../apis/gpt/Summarization';
+import { gptAPICall, GPTCallType } from '../../apis/gpt/Summarization';
import { GPTPopup } from './GPTPopup';
import './AnchorMenu.scss';
@@ -45,7 +45,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable public Highlighting: boolean = false;
@observable public Status: 'marquee' | 'annotation' | '' = '';
- // GPT additions (flow 2)
+ // GPT additions
@observable private summarizedText: string = '';
@observable private loadingSummary: boolean = false;
@observable private showGPTPopup: boolean = false;
@@ -63,7 +63,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
};
private selectedText: string = '';
- setSelectedText = (txt: string) => {
+ public setSelectedText = (txt: string) => {
this.selectedText = txt;
};
@@ -71,9 +71,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public OnCrop: (e: PointerEvent) => void = unimplementedFunction;
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
- public OnSummary: (e: PointerEvent) => Promise<void> = () => {
- return new Promise(() => {});
- };
public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
@@ -123,19 +120,33 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
);
}
- getGPTSummary = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnSummary?.(e));
+ /**
+ * Returns a mock summary simulating an API call.
+ * @returns A Promise that resolves into a string
+ */
+ mockSummarize = async (): Promise<string> => {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve('Mock summary. This is a summary of the highlighted text.');
+ }, 1000);
+ });
};
- invokeGPT = async (e: React.PointerEvent) => {
+ /**
+ * Invokes the API with the selected text and stores it in the summarized text.
+ * @param e pointer down event
+ */
+ gptSummarize = async (e: React.PointerEvent) => {
this.setGPTPopupVis(true);
this.setLoading(true);
- const res = await gptSummarize(this.selectedText);
+ const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
+ // const res = await this.mockSummarize();
if (res) {
this.setSummarizedText(res);
} else {
this.setSummarizedText('Something went wrong.');
}
+
this.setLoading(false);
};
@@ -233,6 +244,19 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
this.highlightColor = Utils.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 = (): boolean => {
+ const docs = SelectionManager.Docs();
+ if (docs.length > 0) {
+ return docs[0].type === 'pdf' || docs[0].type === 'web';
+ }
+ return false;
+ };
+
render() {
const buttons =
this.Status === 'marquee' ? (
@@ -243,12 +267,15 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<FontAwesomeIcon icon="comment-alt" size="lg" />
</button>
</Tooltip>
- <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}>
- <button className="antimodeMenu-button annotate" onPointerDown={this.invokeGPT} style={{ cursor: 'grab' }}>
- <FontAwesomeIcon icon="comment-dots" size="lg" />
- </button>
- </Tooltip>
- <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.summarizedText} loadingSummary={this.loadingSummary} />
+ {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
+ {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
+ <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}>
+ <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="comment-dots" size="lg" />
+ </button>
+ </Tooltip>
+ )}
+ <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.summarizedText} loadingSummary={this.loadingSummary} callApi={this.gptSummarize} />
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}>
<button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}>
diff --git a/src/client/views/pdf/GPTPopup.scss b/src/client/views/pdf/GPTPopup.scss
index 9605cfd07..7b7d2e3f7 100644
--- a/src/client/views/pdf/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup.scss
@@ -1,33 +1,104 @@
$textgrey: #707070;
-$bordergrey: #d3d3d3;
+$lighttextgrey: #a3a3a3;
+$greyborder: #d3d3d3;
+$lightgrey: #ececec;
+$button: #5b97ff;
.summary-box {
+ display: flex;
+ flex-direction: column;
background-color: #ffffff;
box-shadow: 0 2px 5px #7474748d;
color: $textgrey;
position: absolute;
bottom: 40px;
- width: 200px;
- height: 200px;
+ width: 250px;
border-radius: 15px;
- padding: 20px;
- overflow: auto;
+ padding: 15px;
+ padding-bottom: 0px;
.summary-heading {
display: flex;
align-items: center;
- border-bottom: 1px solid $bordergrey;
- margin-bottom: 10px;
+ border-bottom: 1px solid $greyborder;
padding-bottom: 5px;
.summary-text {
font-size: 12px;
font-weight: 500;
- letter-spacing: 1px;
- margin: 0;
- padding-right: 10px;
}
}
+
+ label {
+ color: $textgrey;
+ font-size: 12px;
+ font-weight: 400;
+ letter-spacing: 1px;
+ margin: 0;
+ padding-right: 5px;
+ }
+
+ a {
+ cursor: pointer;
+ }
+
+ .content-wrapper {
+ padding-top: 10px;
+ min-height: 50px;
+ max-height: 150px;
+ overflow-y: auto;
+ }
+
+ .btns-wrapper {
+ height: 50px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .summarizing {
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ button {
+ font-size: 9px;
+ padding: 10px;
+ color: #ffffff;
+ background-color: $button;
+ border-radius: 5px;
+ }
+
+ .text-btn {
+ &:hover {
+ background-color: $button;
+ }
+ }
+
+ .btn-secondary {
+ font-size: 8px;
+ padding: 10px 5px;
+ background-color: $lightgrey;
+ color: $textgrey;
+ &:hover {
+ background-color: $lightgrey;
+ }
+ }
+
+ .icon-btn {
+ background-color: #ffffff;
+ padding: 10px;
+ border-radius: 50%;
+ color: $button;
+ border: 1px solid $button;
+ }
+
+ .ai-warning {
+ padding: 10px 0;
+ font-size: 10px;
+ color: $lighttextgrey;
+ border-top: 1px solid $greyborder;
+ }
}
// Typist CSS
diff --git a/src/client/views/pdf/GPTPopup.tsx b/src/client/views/pdf/GPTPopup.tsx
index 76f3c8187..ec4fa58dc 100644
--- a/src/client/views/pdf/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup.tsx
@@ -1,30 +1,129 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import React = require('react');
import ReactLoading from 'react-loading';
import Typist from 'react-typist';
+import { Doc } from '../../../fields/Doc';
+import { Docs } from '../../documents/Documents';
import './GPTPopup.scss';
interface GPTPopupProps {
visible: boolean;
text: string;
loadingSummary: boolean;
+ callApi: (e: React.PointerEvent) => Promise<void>;
}
@observer
export class GPTPopup extends React.Component<GPTPopupProps> {
+ static Instance: GPTPopup;
+
+ @observable
+ private summaryDone: boolean = false;
+ @observable
+ private sidebarId: string = '';
+ @action
+ public setSummaryDone = (done: boolean) => {
+ this.summaryDone = done;
+ };
+ @action
+ public setSidebarId = (id: string) => {
+ this.sidebarId = id;
+ };
+
+ public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
+
+ /**
+ * Transfers the summarization text to a sidebar annotation text document.
+ */
+ private transferToText = () => {
+ const newDoc = Docs.Create.TextDocument(this.props.text.trim(), {
+ _width: 200,
+ _height: 50,
+ _fitWidth: true,
+ _autoHeight: true,
+ });
+ this.addDoc(newDoc, this.sidebarId);
+ };
+
+ constructor(props: GPTPopupProps) {
+ super(props);
+ GPTPopup.Instance = this;
+ }
+
+ componentDidUpdate = () => {
+ if (this.props.loadingSummary) {
+ this.setSummaryDone(false);
+ }
+ };
+
render() {
return (
- <div className="summary-box" style={{ display: this.props.visible ? 'block' : 'none' }}>
+ <div className="summary-box" style={{ display: this.props.visible ? 'flex' : 'none' }}>
<div className="summary-heading">
<label className="summary-text">SUMMARY</label>
- {this.props.loadingSummary ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <></>}
+ {this.props.loadingSummary && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />}
</div>
- {!this.props.loadingSummary ? (
- <Typist key={this.props.text} avgTypingDelay={20} cursor={{ hideWhenDone: true }}>
- {this.props.text}
- </Typist>
- ) : (
- <></>
+ <div className="content-wrapper">
+ {!this.props.loadingSummary &&
+ (!this.summaryDone ? (
+ <Typist
+ key={this.props.text}
+ avgTypingDelay={15}
+ cursor={{ hideWhenDone: true }}
+ onTypingDone={action(() => {
+ setTimeout(
+ action(() => {
+ this.summaryDone = true;
+ }),
+ 500
+ );
+ })}>
+ {this.props.text}
+ </Typist>
+ ) : (
+ this.props.text
+ ))}
+ </div>
+ {!this.props.loadingSummary && (
+ <div className="btns-wrapper">
+ {this.summaryDone ? (
+ <>
+ <button className="icon-btn" onPointerDown={e => this.props.callApi(e)}>
+ <FontAwesomeIcon icon="redo-alt" size="lg" />
+ </button>
+ <button
+ className="text-btn"
+ onClick={e => {
+ this.transferToText();
+ }}>
+ Transfer to Text
+ </button>
+ </>
+ ) : (
+ <div className="summarizing">
+ <label>Summarizing</label>
+ <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
+ <button
+ className="btn-secondary"
+ onClick={e => {
+ this.setSummaryDone(true);
+ }}>
+ Stop Animation
+ </button>
+ </div>
+ )}
+ </div>
+ )}
+ {this.summaryDone && (
+ <div className="ai-warning">
+ <FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} />
+ AI generated responses can contain inaccurate or misleading content.{' '}
+ <a target="_blank" href="https://www.nytimes.com/2023/02/08/technology/ai-chatbots-disinformation.html">
+ Learn More
+ </a>
+ </div>
)}
</div>
);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 324f31f23..88854debe 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -24,7 +24,7 @@ import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import './PDFViewer.scss';
import React = require('react');
-import { gptSummarize } from '../../apis/gpt/Summarization';
+import { GPTPopup } from './GPTPopup';
const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
const pdfjsLib = require('pdfjs-dist');
const _global = (window /* browser */ || global) /* node */ as any;
@@ -41,6 +41,7 @@ interface IViewerProps extends FieldViewProps {
fieldKey: string;
pdf: Pdfjs.PDFDocumentProxy;
url: string;
+ sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void);
@@ -83,19 +84,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
return AnchorMenu.Instance?.GetAnchor;
}
- // Fields for using GPT to summarize selected text
- private _summaryText: string = '';
- setSummaryText = async () => {
- try {
- const summary = await gptSummarize(this.selectionText());
- this._summaryText = `Summary: ${summary}`;
- } catch (err) {
- console.log(err);
- this._summaryText = 'Failed to fetch summary.';
- }
- };
- summaryText = () => this._summaryText;
-
selectionText = () => this._selectionText;
selectionContent = () => this._selectionContent;
@@ -435,6 +423,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
this.createTextAnnotation(sel, sel.getRangeAt(0));
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;
};
@action
@@ -614,8 +606,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
selectionText={this.selectionText}
- setSummaryText={this.setSummaryText}
- summaryText={this.summaryText}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current}
anchorMenuCrop={this._textSelecting ? undefined : this.crop}