aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-02-22 17:35:26 -0500
committerSophie Zhang <sophie_zhang@brown.edu>2023-02-22 17:35:26 -0500
commit4475adee0f13d9ec407aff6a47094c7ce808af0c (patch)
tree7a8bed802939042751c639db3038041e36770507 /src/client/views/pdf
parentc1e713b611f12b2070854e19e4838d6a44126c0b (diff)
added GPT summarization functionality
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx43
-rw-r--r--src/client/views/pdf/GPTPopup.scss9
-rw-r--r--src/client/views/pdf/GPTPopup.tsx19
-rw-r--r--src/client/views/pdf/PDFViewer.tsx22
4 files changed, 92 insertions, 1 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index c53cc608c..63c8f9145 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -10,6 +10,8 @@ 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 { GPTPopup } from './GPTPopup';
import './AnchorMenu.scss';
@observer
@@ -43,10 +45,30 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable public Highlighting: boolean = false;
@observable public Status: 'marquee' | 'annotation' | '' = '';
+ // GPT additions (flow 2)
+ @observable private summarizedText: string = '';
+ @observable private showGPTPopup: boolean = false;
+ @action
+ setGPTPopupVis = (vis: boolean) => {
+ this.showGPTPopup = vis;
+ };
+ @action
+ setSummarizedText = (txt: string) => {
+ this.summarizedText = txt;
+ };
+
+ private selectedText: string = '';
+ setSelectedText = (txt: string) => {
+ this.selectedText = txt;
+ };
+
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
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;
@@ -83,11 +105,26 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
() => SelectionManager.Views(),
selected => {
this._showLinkPopup = false;
+ this.setGPTPopupVis(false);
AnchorMenu.Instance.fadeOut(true);
}
);
}
+ getGPTSummary = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnSummary?.(e));
+ };
+
+ invokeGPT = async (e: React.PointerEvent) => {
+ this.setGPTPopupVis(true);
+ const res = await gptSummarize(this.selectedText);
+ if (res) {
+ this.setSummarizedText(res);
+ } else {
+ this.setSummarizedText('Something went wrong.');
+ }
+ };
+
pointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -192,6 +229,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<FontAwesomeIcon icon="comment-alt" size="lg" />
</button>
</Tooltip>
+ <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with GPT-3</div>}>
+ <button className="antimodeMenu-button annotate" onPointerDown={this.getGPTSummary} style={{ cursor: 'grab' }}>
+ <FontAwesomeIcon icon="comment-dots" size="lg" />
+ </button>
+ </Tooltip>
+ <GPTPopup key="gptpopup" visible={this.showGPTPopup} text={this.summarizedText} />
{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
new file mode 100644
index 000000000..6f2e39b7e
--- /dev/null
+++ b/src/client/views/pdf/GPTPopup.scss
@@ -0,0 +1,9 @@
+.summary-box {
+ background-color: #ffffff;
+ position: absolute;
+ top: 0;
+ width: 200px;
+ height: 200px;
+ padding: 20px;
+ overflow: auto;
+}
diff --git a/src/client/views/pdf/GPTPopup.tsx b/src/client/views/pdf/GPTPopup.tsx
new file mode 100644
index 000000000..110351126
--- /dev/null
+++ b/src/client/views/pdf/GPTPopup.tsx
@@ -0,0 +1,19 @@
+import { observer } from 'mobx-react';
+import React = require('react');
+import './GPTPopup.scss';
+
+interface GPTPopupProps {
+ visible: boolean;
+ text: string;
+}
+
+@observer
+export class GPTPopup extends React.Component<GPTPopupProps> {
+ render() {
+ return (
+ <div className="summary-box" style={{ display: this.props.visible ? 'block' : 'none' }}>
+ {`Summary: ${this.props.text}`}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b0b7816b8..324f31f23 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -24,6 +24,7 @@ import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import './PDFViewer.scss';
import React = require('react');
+import { gptSummarize } from '../../apis/gpt/Summarization';
const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
const pdfjsLib = require('pdfjs-dist');
const _global = (window /* browser */ || global) /* node */ as any;
@@ -42,7 +43,7 @@ interface IViewerProps extends FieldViewProps {
url: string;
loaded?: (nw: number, nh: number, np: number) => void;
setPdfViewer: (view: PDFViewer) => void;
- anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+ anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void);
crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
}
@@ -82,6 +83,19 @@ 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;
@@ -413,6 +427,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
+ if (sel) {
+ AnchorMenu.Instance.setSelectedText(sel.toString());
+ }
+
if (sel?.type === 'Range') {
this.createTextAnnotation(sel, sel.getRangeAt(0));
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -596,6 +614,8 @@ 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}