diff options
| author | aidahosa1 <aisosa_idahosa@brown.edu> | 2024-09-10 10:54:29 -0400 |
|---|---|---|
| committer | aidahosa1 <aisosa_idahosa@brown.edu> | 2024-09-10 10:54:29 -0400 |
| commit | 4e34335e600b5d9d29e8a4af99fda6b2a6d3ba69 (patch) | |
| tree | a2abb796aaeac1588375c109dea1d3927bc55ef8 /src/client/views/pdf/GPTPopup | |
| parent | 593a894cf3b707d60b4baf1c400f138bd8b3a783 (diff) | |
more chat features
Diffstat (limited to 'src/client/views/pdf/GPTPopup')
| -rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.scss | 56 | ||||
| -rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 319 |
2 files changed, 295 insertions, 80 deletions
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<GPTPopupProps> { 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<GPTPopupProps> { this.sortRespText = resp } + //mode where gpt says to select a specific card + // + @observable chatSortPrompt: string = "" @@ -179,39 +190,65 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { 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<GPTPopupProps> { } - - /** - * 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<GPTPopupProps> { } 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<GPTPopupProps> { 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<GPTPopupProps> { super(props); makeObservable(this); GPTPopup.Instance = this; + this.messagesEndRef = React.createRef(); + } + private messagesEndRef: React.RefObject<HTMLDivElement>; + + + 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 = () => ( <div className="btns-wrapper-gpt"> <Button - tooltip="Have ChatGPT sort your cards for you!" - text="Sort Cards!" + tooltip="Have ChatGPT sort, tag, define, or filter your cards for you!" + text="Modify/Sort Cards!" onClick={() => this.setMode(GPTPopupMode.SORT)} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} @@ -450,7 +574,16 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { <Button tooltip="Test your knowledge with ChatGPT!" text="Quiz Cards!" - onClick={() => this.setMode(GPTPopupMode.QUIZ)} + onClick={() => { + this.conversationArray = ['Define the selected card!'] + this.setMode(GPTPopupMode.QUIZ) + if (this.onQuizRandom){ + this.onQuizRandom() + } + + + + }} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} style={{ @@ -464,62 +597,94 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { /> </div> ) + + @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 ? ( - <> - <div className="btns-wrapper-gpt"> + // !this.sortDone ? ( + // <> + <div className="btns-wrapper-gpt"> + {/* Chat bubble container with scroll */} + <div className="chat-wrapper"> + <div className="chat-bubbles"> + {this.conversationArray.map((message, index) => ( + <div + key={index} + className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`} + > + {message} + </div> + ))} + + {/* Conditional Loading message */} + {(!this.cardsDoneLoading || this.loading) && ( + <div className={`chat-bubble chat-message`}> + ... + </div> + )} + </div> + + <div ref={this.messagesEndRef} style= {{height: '100px'}} /> + + </div> + + <div className="inputWrapper"> <input className="searchBox-input" defaultValue="" autoComplete="off" onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged} - onKeyDown={e => { - 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%' }} - /> - {/* </div> - <div className="btns-wrapper-gpt"> */} - <Button - tooltip={`${isSort ? 'HaveChatGPT sort ypur cards for you!' : 'See how close you get to the right answer!'}`} - text={`${isSort ? 'Sort!' : 'Check!'}`} - onClick={isSort ? this.generateSort : this.generateQuiz} - color={StrCast(Doc.UserDoc().userVariantColor)} - type={Type.TERT} - style={{ - width: '100%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - height: '40%', - - }} - /> - </div> - </> - ) : ( - <div> - <div className="content-wrapper"> - <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p> - <IconButton - tooltip="Generate Again" - onClick={() => this.setSortDone(false)} - icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} - color={StrCast(Doc.UserDoc().userVariantColor)} + placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`} /> </div> </div> - ) + + + // </> + // ) : ( + // <div> + // <div className="content-wrapper"> + // <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p> + // <IconButton + // tooltip="Generate Again" + // onClick={() => this.setSortDone(false)} + // icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} + // color={StrCast(Doc.UserDoc().userVariantColor)} + // /> + // </div> + // </div> + // ) ); // } else if (opt === GPTPopupMode.QUIZ) { // return ( @@ -563,10 +728,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { }; sortBox = () => ( - <div> + <div style = {{height: '80%'}}> {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} <> - {!this.cardsDoneLoading || this.loading ? ( + {!this.cardsDoneLoading? ( <div className="content-wrapper"> <div className="loading-spinner"> <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> @@ -737,7 +902,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> { tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => this.mode = GPTPopupMode.CARD} - style = {{right: '-20%'}} + style = {{right: '50px', position: 'absolute'}} /> )} <IconButton |
