From 4e34335e600b5d9d29e8a4af99fda6b2a6d3ba69 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Tue, 10 Sep 2024 10:54:29 -0400 Subject: more chat features --- src/client/views/pdf/GPTPopup/GPTPopup.scss | 56 ++++- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 319 +++++++++++++++++++++------- 2 files changed, 295 insertions(+), 80 deletions(-) (limited to 'src/client/views/pdf/GPTPopup') diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index eaa3eaebf..1defd1a7f 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -78,15 +78,63 @@ $highlightedText: #82e0ff; align-items: center; flex-direction: column; - transform: translateY(30px); + .inputWrapper{ + display: flex; + justify-content: center; + align-items: center; + height: 60px; + position: absolute; + bottom: 0; + width: 100%; + background-color: white; + + } .searchBox-input{ - transform: translateY(-15px); - height: 50px; + height: 40px; border-radius: 10px; + position: absolute; + bottom: 10px; border-color: #5b97ff; + width: 90% + } + + .chat-wrapper { + display: flex; + flex-direction: column; + width: 100%; + max-height: calc(100vh - 80px); /* Height minus the input box and some padding */ + overflow-y: auto; + padding-bottom: 60px; /* Space for the input */ + } + + .chat-bubbles { + margin-top: 20px; + display: flex; + flex-direction: column; + flex-grow: 1; + } + + .chat-bubble { + padding: 10px; + margin-bottom: 10px; + border-radius: 10px; + max-width: 60%; } + + .user-message { + background-color: #283d53; + align-self: flex-end; + color: whitesmoke; + } + + .chat-message { + background-color: #367ae7; + align-self: flex-start; + color:whitesmoke; + } + @@ -98,6 +146,8 @@ $highlightedText: #82e0ff; + + } // button { diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index d216cc421..1b44508d7 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -34,6 +34,13 @@ export enum GPTPopupMode { QUIZ } +export enum GPTQuizType { + CURRENT = 0, + CHOOSE = 1, + MULTIPLE = 2 + +} + @@ -151,7 +158,8 @@ export class GPTPopup extends ObservableReactComponent { this.sortDesc = t; }; - @observable onSortComplete?: (sortResult: string) => void; + @observable onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; + @observable onQuizRandom?: () => void; @observable cardsDoneLoading = false; @action setCardsDoneLoading(done: boolean) { @@ -165,6 +173,9 @@ export class GPTPopup extends ObservableReactComponent { this.sortRespText = resp } + //mode where gpt says to select a specific card + // + @observable chatSortPrompt: string = "" @@ -179,39 +190,65 @@ export class GPTPopup extends ObservableReactComponent { this.quizAnswer = e.target.value; }); + + @observable correctAnswer: string = '' + @observable setCorrectAnswer = (s: string) => { + this.correctAnswer = s + } + generateQuiz = async () => { this.setLoading(true); this.setSortDone(false); + const quizType = this.quizMode; const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = 'Question: ' + StrCast(selected['gptInputText']); - if (StrCast(selected['gptRubric']) === '') { - const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected) - } + const questionText = 'Question: ' + StrCast(selected['gptInputText']); - const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric'])) - // const rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); - const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; + if (StrCast(selected['gptRubric']) === '') { + const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected) + } - try { - const res = await gptAPICall(queryText, GPTCallType.QUIZ); - if (!res) { - console.error('GPT call failed'); - return; - } - console.log(res) - this.setQuizResp(res) + const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric'])) + // const rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); + const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; - this.setLoading(false); - this.setSortDone(true); + try { + const res = await gptAPICall(queryText, GPTCallType.QUIZ); + if (!res) { + console.error('GPT call failed'); + return; + } + console.log(res) + this.setQuizResp(res) + this.conversationArray.push(res) + + this.setLoading(false); + this.setSortDone(true); + + // this._outputValue = res; + } catch (err) { + console.error('GPT call failed'); + } + + + if (this.onQuizRandom){ + this.onQuizRandom() + } - // this._outputValue = res; - } catch (err) { - console.error('GPT call failed'); - } + + + + + // switch(quizType){ + // default: + + // } + + + } @@ -254,11 +291,7 @@ export class GPTPopup extends ObservableReactComponent { } - - /** - * Sorts cards in the CollectionCardDeckView - */ - generateSort = async () => { + generateCard = async () => { console.log(this.chatSortPrompt + "USER PROMPT") this.setLoading(true); this.setSortDone(false); @@ -268,17 +301,50 @@ export class GPTPopup extends ObservableReactComponent { } try { - const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); + // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); + const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); + const questionNumber = questionType.split(' ')[0] + console.log(questionType) + let res = '' + + switch (questionNumber) { + case '1': + case '2': + case '4': + res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt); + break + case '6': + res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); + break + default: + + const selected = DocumentView.SelectedDocs().lastElement(); + const questionText = StrCast(selected!['gptInputText']); + + + res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); + break + } + + + // const res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt); // Trigger the callback with the result if (this.onSortComplete) { - this.onSortComplete(res || 'Something went wrong :('); + this.onSortComplete(res || 'Something went wrong :(', questionNumber, questionType.split(' ').slice(1).join(' ')); + + let explanation = res + + if (questionType != '5' && questionType != '3'){ // Extract explanation surrounded by ------ at the top or both at the top and bottom const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || []; - const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found'; + explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found'; + } // Set the extracted explanation to sortRespText this.setSortRespText(explanation); + this.conversationArray.push(this.sortRespText) + this.scrollToBottom() console.log(res); } @@ -289,6 +355,43 @@ export class GPTPopup extends ObservableReactComponent { this.setLoading(false); this.setSortDone(true); }; + + + // /** + // * Sorts cards in the CollectionCardDeckView + // */ + // generateSort = async () => { + // console.log(this.chatSortPrompt + "USER PROMPT") + // this.setLoading(true); + // this.setSortDone(false); + + // if (this.regenerateCallback) { + // await this.regenerateCallback(); + // } + + // try { + // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); + + // // Trigger the callback with the result + // if (this.onSortComplete) { + // this.onSortComplete(res || 'Something went wrong :('); + + // // Extract explanation surrounded by ------ at the top or both at the top and bottom + // const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || []; + // const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found'; + + // // Set the extracted explanation to sortRespText + // this.setSortRespText(explanation); + + // console.log(res); + // } + // } catch (err) { + // console.error(err); + // } + + // this.setLoading(false); + // this.setSortDone(true); + // }; /** @@ -422,19 +525,40 @@ export class GPTPopup extends ObservableReactComponent { super(props); makeObservable(this); GPTPopup.Instance = this; + this.messagesEndRef = React.createRef(); + } + private messagesEndRef: React.RefObject; + + + scrollToBottom = () => { + setTimeout(() => { + // Code to execute after 1 second (1000 ms) + if (this.messagesEndRef.current) { + + this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' }); + } + + }, 50); + } + componentDidUpdate = () => { if (this.loading) { this.setDone(false); } }; + @observable quizMode : GPTQuizType = GPTQuizType.CURRENT + @action setQuizMode (g: GPTQuizType) { + this.quizMode = g + } + cardMenu = () => (
) + + @observable conversationArray: string[] = ["Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. If you're not sure if something is possible-- just ask!"] + + + handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => { + if (e.key === 'Enter') { + e.stopPropagation(); + + if (isSort) { + this.conversationArray.push(this.chatSortPrompt); + await this.generateCard(); + this.chatSortPrompt = '' + + } else { + this.conversationArray.push(this.quizAnswer); + await this.generateQuiz(); + this.quizAnswer = '' + + } + + this.scrollToBottom(); + } + } cardActual = (opt: GPTPopupMode) => { const isSort = opt === GPTPopupMode.SORT // if (opt === GPTPopupMode.SORT) { return ( - !this.sortDone ? ( - <> -
+ // !this.sortDone ? ( + // <> +
+ {/* Chat bubble container with scroll */} +
+
+ {this.conversationArray.map((message, index) => ( +
+ {message} +
+ ))} + + {/* Conditional Loading message */} + {(!this.cardsDoneLoading || this.loading) && ( +
+ ... +
+ )} +
+ +
+ +
+ +
{ - if (e.key === 'Enter') { - isSort ? this.generateSort() : this.generateQuiz(); + onKeyDown={(e) => { + this.handleKeyPress(e, isSort) + + } - e.stopPropagation(); - }} + } type="text" - placeholder={`${isSort ? 'How do you want to sort your cards?' : 'Define the selected card?'}`} - id="search-input" - style={{ width: '100%' }} - /> - {/*
-
*/} -
- - ) : ( -
-
-

{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}

- this.setSortDone(false)} - icon={} - color={StrCast(Doc.UserDoc().userVariantColor)} + placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`} />
- ) + + + // + // ) : ( + //
+ //
+ //

{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}

+ // this.setSortDone(false)} + // icon={} + // color={StrCast(Doc.UserDoc().userVariantColor)} + // /> + //
+ //
+ // ) ); // } else if (opt === GPTPopupMode.QUIZ) { // return ( @@ -563,10 +728,10 @@ export class GPTPopup extends ObservableReactComponent { }; sortBox = () => ( -
+
{this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} <> - {!this.cardsDoneLoading || this.loading ? ( + {!this.cardsDoneLoading? (
@@ -737,7 +902,7 @@ export class GPTPopup extends ObservableReactComponent { tooltip="back" icon={} onClick={() => this.mode = GPTPopupMode.CARD} - style = {{right: '-20%'}} + style = {{right: '50px', position: 'absolute'}} /> )}