aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts29
-rw-r--r--src/client/apis/gpt/customization.ts61
-rw-r--r--src/client/apis/gpt/setup.ts26
-rw-r--r--src/client/views/PropertiesView.tsx45
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx32
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1
-rw-r--r--src/client/views/nodes/trails/PresBox.scss22
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx173
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx37
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss4
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx4
11 files changed, 384 insertions, 50 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 6bde7989b..9403e4d3a 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,23 +1,4 @@
-import { Configuration, OpenAIApi } from 'openai';
-
-enum GPTCallType {
- SUMMARY = 'summary',
- COMPLETION = 'completion',
- EDIT = 'edit',
-}
-
-type GPTCallOpts = {
- model: string;
- maxTokens: number;
- temp: number;
- prompt: string;
-};
-
-const callTypeMap: { [type: string]: GPTCallOpts } = {
- summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
- edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
- completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' },
-};
+import { GPTCallOpts, GPTCallType, callTypeMap, openai } from './setup';
/**
* Calls the OpenAI API.
@@ -29,10 +10,6 @@ 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: opts.model,
max_tokens: opts.maxTokens,
@@ -48,10 +25,6 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
const gptImageCall = async (prompt: string, n?: number) => {
try {
- const configuration = new Configuration({
- apiKey: process.env.OPENAI_KEY,
- });
- const openai = new OpenAIApi(configuration);
const response = await openai.createImage({
prompt: prompt,
n: n ?? 1,
diff --git a/src/client/apis/gpt/customization.ts b/src/client/apis/gpt/customization.ts
new file mode 100644
index 000000000..3d9a0ead3
--- /dev/null
+++ b/src/client/apis/gpt/customization.ts
@@ -0,0 +1,61 @@
+import { openai } from './setup';
+
+export enum CustomizationType {
+ PRES_TRAIL_SLIDE = 'trails',
+}
+
+interface PromptInfo {
+ description: string;
+ features: { name: string; description: string; values?: string[] }[];
+}
+const prompts: { [key: string]: PromptInfo } = {
+ trails: {
+ description:
+ 'We are adding customization to a slide in a presentation. Given a natural language input, translate it into a json with the required fields: [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection].',
+ features: [],
+ },
+};
+
+const setupPresSlideCustomization = () => {
+ addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'title', 'is the title/name of the slide.');
+ addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.');
+ addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Fade in', 'Flip', 'Rotate', 'Bounce', 'Roll']);
+};
+
+setupPresSlideCustomization();
+
+export const addCustomizationProperty = (type: CustomizationType, name: string, description: string, values?: string[]) => {
+ values ? prompts[type].features.push({ name, description, values }) : prompts[type].features.push({ name, description });
+};
+
+export const gptTrailSlideCustomization = async (inputText: string) => {
+ let prompt = prompts.trails.description;
+
+ prompts.trails.features.forEach(feature => {
+ prompt += feature.name + feature.description;
+ if (feature.values) {
+ prompt += `Its only possible values are [${feature.values.join(', ')}].`;
+ }
+ });
+
+ // prompt +=
+ // 'title is the title/name of the slide. presentation_transition is a number in milliseconds for how long it should take to transition/move to a slide. presentation_effect is an effect applied to the slide when we transition to it. Its only possible values are: [None, Fade in, Flip, Rotate, Bounce, Roll]. presentation_effectDirection is what direction the effect is applied. Its only possible values are: [Enter from left, Enter from right, Enter from bottom, Enter from Top, Enter from center]. config_zoom is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.';
+
+ prompt += 'If the input does not contain info a specific key, please set their value to null. Please only return the json with these keys and their values.';
+
+ try {
+ const response = await openai.createChatCompletion({
+ model: 'gpt-3.5-turbo',
+ messages: [
+ { role: 'system', content: prompt },
+ { role: 'user', content: inputText },
+ ],
+ temperature: 0.1,
+ max_tokens: 1000,
+ });
+ return response.data.choices[0].message?.content;
+ } catch (err) {
+ console.log(err);
+ return 'Error connecting with API.';
+ }
+};
diff --git a/src/client/apis/gpt/setup.ts b/src/client/apis/gpt/setup.ts
new file mode 100644
index 000000000..d1db6968a
--- /dev/null
+++ b/src/client/apis/gpt/setup.ts
@@ -0,0 +1,26 @@
+import { Configuration, OpenAIApi } from 'openai';
+
+export enum GPTCallType {
+ SUMMARY = 'summary',
+ COMPLETION = 'completion',
+ EDIT = 'edit',
+}
+
+export type GPTCallOpts = {
+ model: string;
+ maxTokens: number;
+ temp: number;
+ prompt: string;
+};
+
+export const callTypeMap: { [type: string]: GPTCallOpts } = {
+ summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
+ edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
+ completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' },
+};
+
+const configuration = new Configuration({
+ apiKey: process.env.OPENAI_KEY,
+});
+
+export const openai = new OpenAIApi(configuration);
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index d37971517..64b2a9d65 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -3,7 +3,7 @@ import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, Tooltip } from '@material-ui/core';
-import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
+import { ColorPicker, Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
import { concat } from 'lodash';
import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
@@ -39,6 +39,7 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
+import { FaFillDrip } from 'react-icons/fa';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -1245,10 +1246,23 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
'change link relationship'
);
+ handleColorChange = undoable(
+ action((value: string) => {
+ if (LinkManager.currentLink && this.selectedDoc) {
+ this.setColorValue(value);
+ }
+ }),
+ 'change link color'
+ );
+
@undoBatch
setDescripValue = action((value: string) => {
if (LinkManager.currentLink) {
Doc.GetProto(LinkManager.currentLink).link_description = value;
+
+ if (LinkManager.currentLink.show_description === undefined) {
+ LinkManager.currentLink.show_description = !LinkManager.currentLink.show_description;
+ }
}
});
@@ -1294,6 +1308,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
});
+ @undoBatch
+ setColorValue = action((value: string) => {
+ if (LinkManager.currentLink) {
+ Doc.GetProto(LinkManager.currentLink).link_color = value;
+ }
+ });
+
changeFollowBehavior = undoable((loc: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior');
@undoBatch
@@ -1462,6 +1483,28 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
</button>
</div>
+ <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+ <p>Show description</p>
+ <button
+ style={{ background: !LinkManager.currentLink?.show_description ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleLinkProp(e, 'show_description')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+ <p>Link color</p>
+ <ColorPicker
+ tooltip={'User Color'} //
+ color={SettingsManager.userColor}
+ type={Type.SEC}
+ icon={<FaFillDrip />}
+ selectedColor={LinkManager.currentLink?.link_color ? StrCast(LinkManager.currentLink?.link_color) : '#449ef7'}
+ setSelectedColor={this.handleColorChange}
+ setFinalColor={this.handleColorChange}
+ />
+ </div>
</div>
{!hasSelectedAnchor ? null : (
<div className="propertiesView-section">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 24a758d8c..470ff9527 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -239,11 +239,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
textX,
textY,
// fully connected
- // pt1,
- // pt2,
+ // pt1,
+ // pt2,
// this code adds space between links
- pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3*NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3*NumCast(link.link_displayArrow_scale, 3)] : pt1,
- pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3*NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3*NumCast(link.link_displayArrow_scale, 3)] : pt2,
+ pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt1,
+ pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt2,
};
}
@@ -262,19 +262,20 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const linkSize = Doc.noviceMode || currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
//access stroke color using index of the relationship in the color list (default black)
- const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex];
+ // const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex];
+ const stroke = link.link_color ? Field.toString(link.link_color as any as Field) : '#449ef7';
// const hexStroke = this.rgbToHex(stroke)
//calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
//thickness varies linearly from 3px to 12px for increasing link count
const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
- const arrowScale = NumCast(link.link_displayArrow_scale, 3)
+ const arrowScale = NumCast(link.link_displayArrow_scale, 3);
return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : (
<>
<defs>
- <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4*arrowScale}`} markerHeight={`${3*arrowScale}`} refX="0" refY={`${1.5*arrowScale}`} orient="auto">
- <polygon points={`0 0, ${3*arrowScale} ${1.5*arrowScale}, 0 ${3*arrowScale}`} fill={stroke} />
+ <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4 * arrowScale}`} markerHeight={`${3 * arrowScale}`} refX="0" refY={`${1.5 * arrowScale}`} orient="auto">
+ <polygon points={`0 0, ${3 * arrowScale} ${1.5 * arrowScale}, 0 ${3 * arrowScale}`} fill={stroke} />
</marker>
<filter id="outline">
<feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" />
@@ -301,13 +302,14 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
markerEnd={link.link_displayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
/>
- {textX === undefined || !linkDescription ? null : (
- <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
- <tspan>&nbsp;</tspan>
- <tspan dy="2">{linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')}</tspan>
- <tspan dy="2">&nbsp;</tspan>
- </text>
- )}
+ {link.show_description &&
+ (textX === undefined || !linkDescription ? null : (
+ <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
+ <tspan>&nbsp;</tspan>
+ <tspan dy="2">{linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')}</tspan>
+ <tspan dy="2">&nbsp;</tspan>
+ </text>
+ ))}
</>
);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 41b1c59b0..6535ac6de 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -966,6 +966,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
breakupDictation = () => {
+ console.log('breakup');
if (this._editorView && this._recordingDictation) {
this.stopDictation(true);
this._break = true;
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 0b51813a5..1b76a39ad 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -15,6 +15,28 @@
//overflow: hidden;
transition: 0.7s opacity ease;
+ .presBox-chatbox {
+ position: fixed;
+ bottom: 8px;
+ left: 8px;
+ width: calc(100% - 16px);
+ min-height: 100px;
+ border-radius: 16px;
+ padding: 16px;
+ gap: 8px;
+ z-index: 999;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ background-color: #ffffff;
+ box-shadow: 0 2px 5px #7474748d;
+
+ .pres-chatbox {
+ outline: none;
+ border: none;
+ }
+ }
+
.presBox-listCont {
position: relative;
height: calc(100% - 67px);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 05810b63a..87ffb0112 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,8 +36,14 @@ import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../Docum
import { FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
+import ReactLoading from 'react-loading';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-const { Howl } = require('howler');
+import { IconButton, Type } from 'browndash-components';
+import { AiOutlineSend } from 'react-icons/ai';
+import { gptTrailSlideCustomization } from '../../../apis/gpt/GPT';
+import { DictationManager } from '../../../util/DictationManager';
+import { BiMicrophone, BiX } from 'react-icons/bi';
+import TextareaAutosize from 'react-textarea-autosize';
export interface pinDataTypes {
scrollable?: boolean;
@@ -105,7 +111,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _treeViewMap: Map<Doc, number> = new Map();
@observable _presKeyEvents: boolean = false;
@observable _forceKeyEvents: boolean = false;
- @computed get isTreeOrStack() {
+
+ // GPT
+ private _inputref: HTMLTextAreaElement | null = null;
+ @observable chatActive: boolean = false;
+ @observable chatInput: string = '';
+ public slideToModify: Doc | null = null;
+ @observable isRecording: boolean = false;
+ @observable isLoading: boolean = false;
+
+ @action
+ setChatInput = (input: string) => {
+ this.chatInput = input;
+ };
+
+ @action
+ setIsLoading = (isLoading: boolean) => {
+ this.isLoading = isLoading;
+ };
+
+ @action
+ public setChatActive = (active: boolean) => {
+ this.chatActive = active;
+ };
+
+ @action
+ public setIsRecording = (isRecording: boolean) => {
+ this.isRecording = isRecording;
+ };
+
+ @computed
+ get isTreeOrStack() {
return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any);
}
@computed get isTree() {
@@ -241,6 +277,71 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
};
+ // GPT
+
+ recordDictation = () => {
+ this.setIsRecording(true);
+ this.setChatInput('');
+ DictationManager.Controls.listen({
+ interimHandler: this.setDictationContent,
+ continuous: { indefinite: false },
+ }).then(results => {
+ if (results && [DictationManager.Controls.Infringed].includes(results)) {
+ DictationManager.Controls.stop();
+ }
+ });
+ };
+ stopDictation = (abort: boolean) => {
+ this.setIsRecording(false);
+ DictationManager.Controls.stop(!abort);
+ };
+
+ setDictationContent = (value: string) => {
+ this.setChatInput(value);
+ // // Get the current cursor position
+ // if (!this._inputref) return;
+ // const cursorPosition = this._inputref.selectionStart;
+ // const currentValue = this.chatInput;
+
+ // // split before and after
+ // const textBeforeCursor = currentValue.slice(0, cursorPosition);
+ // const textAfterCursor = currentValue.slice(cursorPosition);
+
+ // // insertion
+ // const updatedText = textBeforeCursor + value + textAfterCursor;
+
+ // // Update the textarea value
+ // this.setChatInput(updatedText);
+
+ // // set new cursor pos
+ // const newCursorPosition = cursorPosition + value.length;
+ // this._inputref.setSelectionRange(newCursorPosition, newCursorPosition);
+ };
+
+ @action
+ customizeWithGPT = async (input: string) => {
+ // const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
+ if (!this.slideToModify) return;
+ this.setIsRecording(false);
+ this.setIsLoading(true);
+ try {
+ const res = await gptTrailSlideCustomization(input);
+ if (typeof res === 'string') {
+ const resObj = JSON.parse(res);
+ console.log('Result ', resObj);
+ // this.activeItem
+ for (let key in resObj) {
+ if (resObj[key]) {
+ this.slideToModify[key] = resObj[key];
+ }
+ }
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ this.setIsLoading(false);
+ };
+
//TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
// TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
// No more frames in current doc and next slide is defined, therefore move to next slide
@@ -764,6 +865,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
*/
navigateToActiveItem = (afterNav?: () => void) => {
const activeItem: Doc = this.activeItem;
+ // GPT update
+ this.slideToModify = activeItem;
const targetDoc: Doc = this.targetDoc;
const finished = () => {
afterNav?.();
@@ -1218,6 +1321,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
keyEvents = (e: KeyboardEvent) => {
if (e.target instanceof HTMLInputElement) return;
+ if (e.target instanceof HTMLTextAreaElement) return;
let handled = false;
const anchorNode = document.activeElement as HTMLDivElement;
if (anchorNode && anchorNode.className?.includes('lm_title')) return;
@@ -2667,7 +2771,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
/>
) : null}
</div>
-
{/* {
// if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
<Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}>
@@ -2677,6 +2780,70 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</Tooltip>
} */}
</div>
+ {/* presbox chatbox */}
+ {this.chatActive && (
+ <div className="presBox-chatbox">
+ <div style={{ alignSelf: 'flex-end' }}>
+ {this.isLoading ? (
+ <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} width={14} height={14} />
+ ) : (
+ <IconButton
+ type={Type.PRIM}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ tooltip="Close"
+ icon={<BiX size={'16px'} />}
+ onClick={() => {
+ this.setChatActive(false);
+ }}
+ />
+ )}
+ </div>
+ <TextareaAutosize
+ ref={r => (this._inputref = r)}
+ minRows={3}
+ placeholder="Customize..."
+ className="pres-chatbox"
+ autoFocus={true}
+ value={this.chatInput}
+ onChange={e => {
+ this.setChatInput(e.target.value);
+ }}
+ onKeyDown={e => {
+ this.stopDictation(true);
+ e.stopPropagation();
+ }}
+ onKeyPress={e => e.stopPropagation()}
+ onPointerDown={e => e.stopPropagation()}
+ onClick={e => e.stopPropagation()}
+ onPointerUp={e => e.stopPropagation()}
+ />
+ {/* <input className="chatbox" placeholder="Customize..." value={this.chatInput} onChange={e => this.setChatInput(e.target.value)} /> */}
+ <div style={{ alignSelf: 'flex-end', display: 'flex', gap: '8px' }}>
+ <IconButton
+ type={Type.TERT}
+ color={this.isRecording ? 'red' : StrCast(Doc.UserDoc().userVariantColor)}
+ tooltip="Record"
+ icon={<BiMicrophone size={'16px'} />}
+ onClick={() => {
+ if (!this.isRecording) {
+ this.recordDictation();
+ } else {
+ this.stopDictation(true);
+ }
+ }}
+ />
+ <IconButton
+ type={Type.TERT}
+ color={!this.isLoading ? StrCast(Doc.UserDoc().userVariantColor) : '#7c7c7c'}
+ tooltip="Send"
+ icon={<AiOutlineSend size={'16px'} />}
+ onClick={() => {
+ this.customizeWithGPT(this.chatInput);
+ }}
+ />
+ </div>
+ </div>
+ )}
</div>
);
}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 82ed9e8d5..482d38f59 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -28,6 +28,8 @@ import React = require('react');
import { TreeView } from '../../collections/TreeView';
import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
import { MultiToggle, Type } from 'browndash-components';
+import { gptTrailSlideCustomization } from '../../../apis/gpt/GPT';
+import { DictationManager } from '../../../util/DictationManager';
/**
* This class models the view a document added to presentation will have in the presentation.
* It involves some functionality for its buttons and options.
@@ -434,6 +436,27 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
return width;
}
+ // GPT
+
+ @action
+ customizeWithGPT = async (input: string) => {
+ const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
+
+ try {
+ const res = await gptTrailSlideCustomization(testInput);
+ if (typeof res === 'string') {
+ const resObj = JSON.parse(res);
+ console.log('Result ', resObj);
+
+ for (let key in resObj) {
+ this.rootDoc[key] = resObj[key];
+ }
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
@computed get presButtons() {
const presBox = this.presBox;
const presBoxColor = StrCast(presBox?._backgroundColor);
@@ -537,6 +560,20 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</Tooltip>
);
+ items.push(
+ <Tooltip key="customize" title={<div className="dash-tooltip">Customize</div>}>
+ <div
+ className={'slideButton'}
+ onClick={() => {
+ PresBox.Instance.setChatActive(true);
+ PresBox.Instance.slideToModify = this.rootDoc;
+ PresBox.Instance.recordDictation();
+ // this.customizeWithGPT('');
+ }}>
+ <FontAwesomeIcon icon={'message'} onPointerDown={e => e.stopPropagation()} />
+ </div>
+ </Tooltip>
+ );
return items;
}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 5d966395c..48659d0e7 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;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 038c45582..9b754588a 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -118,13 +118,15 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
try {
let image_urls = await gptImageCall(this.imgDesc);
if (image_urls && image_urls[0]) {
+ // need to fix this
const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+ console.log('Result', result);
+ console.log('Client', result.accessPaths.agnostic.client);
const source = Utils.prepend(result.accessPaths.agnostic.client);
this.setImgUrls([[image_urls[0], source]]);
}
} catch (err) {
console.log(err);
- return '';
}
GPTPopup.Instance.setLoading(false);
};