From db582e135fceb6162d0c9cf00e2580fb1349fddb Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 13 Apr 2023 01:13:33 -0400
Subject: added text edits

---
 src/client/views/pdf/GPTPopup/GPTPopup.tsx | 186 +++++++++++++++++++++++++++++
 1 file changed, 186 insertions(+)
 create mode 100644 src/client/views/pdf/GPTPopup/GPTPopup.tsx

(limited to 'src/client/views/pdf/GPTPopup/GPTPopup.tsx')

diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
new file mode 100644
index 000000000..91bc0a7ff
--- /dev/null
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -0,0 +1,186 @@
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import ReactLoading from 'react-loading';
+import Typist from 'react-typist';
+import { Doc } from '../../../../fields/Doc';
+import { Docs } from '../../../documents/Documents';
+import './GPTPopup.scss';
+
+export enum GPTPopupMode {
+    SUMMARY,
+    EDIT,
+}
+
+interface GPTPopupProps {
+    visible: boolean;
+    text: string;
+    loading: boolean;
+    mode: GPTPopupMode;
+    callSummaryApi: (e: React.PointerEvent) => Promise<void>;
+    callEditApi: (e: React.PointerEvent) => Promise<void>;
+    replaceText: (replacement: string) => void;
+    highlightRange?: number[];
+}
+
+@observer
+export class GPTPopup extends React.Component<GPTPopupProps> {
+    static Instance: GPTPopup;
+
+    @observable
+    private done: boolean = false;
+    @observable
+    private sidebarId: string = '';
+
+    @action
+    public setDone = (done: boolean) => {
+        this.done = 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.loading) {
+            this.setDone(false);
+        }
+    };
+
+    summaryBox = () => (
+        <>
+            <div>
+                {this.heading('SUMMARY')}
+                <div className="content-wrapper">
+                    {!this.props.loading &&
+                        (!this.done ? (
+                            <Typist
+                                key={this.props.text}
+                                avgTypingDelay={15}
+                                cursor={{ hideWhenDone: true }}
+                                onTypingDone={() => {
+                                    setTimeout(() => {
+                                        this.setDone(true);
+                                    }, 500);
+                                }}>
+                                {this.props.text}
+                            </Typist>
+                        ) : (
+                            this.props.text
+                        ))}
+                </div>
+            </div>
+            {!this.props.loading && (
+                <div className="btns-wrapper">
+                    {this.done ? (
+                        <>
+                            <button className="icon-btn" onPointerDown={e => this.props.callSummaryApi(e)}>
+                                <FontAwesomeIcon icon="redo-alt" size="lg" />
+                            </button>
+                            <button
+                                className="text-btn"
+                                onClick={e => {
+                                    this.transferToText();
+                                }}>
+                                Transfer to Text
+                            </button>
+                        </>
+                    ) : (
+                        <div className="summarizing">
+                            <span>Summarizing</span>
+                            <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
+                            <button
+                                className="btn-secondary"
+                                onClick={e => {
+                                    this.setDone(true);
+                                }}>
+                                Stop Animation
+                            </button>
+                        </div>
+                    )}
+                </div>
+            )}
+        </>
+    );
+
+    editBox = () => {
+        const hr = this.props.highlightRange;
+        return (
+            hr && (
+                <>
+                    <div>
+                        {this.heading('TEXT EDIT SUGGESTIONS')}
+                        <div className="content-wrapper">
+                            <div>
+                                {this.props.text.slice(0, hr[0])} <span className="highlighted-text">{this.props.text.slice(hr[0], hr[1])}</span> {this.props.text.slice(hr[1])}
+                            </div>
+                        </div>
+                    </div>
+                    {!this.props.loading && (
+                        <div className="btns-wrapper">
+                            <>
+                                <button className="icon-btn" onPointerDown={e => this.props.callEditApi(e)}>
+                                    <FontAwesomeIcon icon="redo-alt" size="lg" />
+                                </button>
+                                <button
+                                    className="text-btn"
+                                    onClick={e => {
+                                        this.props.replaceText(this.props.text);
+                                    }}>
+                                    Replace Text
+                                </button>
+                            </>
+                        </div>
+                    )}
+                    {this.aiWarning()}
+                </>
+            )
+        );
+    };
+
+    aiWarning = () =>
+        this.done ? (
+            <div className="ai-warning">
+                <FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} />
+                AI generated responses can contain inaccurate or misleading content.
+            </div>
+        ) : (
+            <></>
+        );
+
+    heading = (headingText: string) => (
+        <div className="summary-heading">
+            <label className="summary-text">{headingText}</label>
+            {this.props.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />}
+        </div>
+    );
+
+    render() {
+        return (
+            <div className="summary-box" style={{ display: this.props.visible ? 'flex' : 'none' }}>
+                {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()}
+            </div>
+        );
+    }
+}
-- 
cgit v1.2.3-70-g09d2

