aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf/GPTPopup
diff options
context:
space:
mode:
authoraidahosa1 <aisosa_idahosa@brown.edu>2024-09-10 10:54:29 -0400
committeraidahosa1 <aisosa_idahosa@brown.edu>2024-09-10 10:54:29 -0400
commit4e34335e600b5d9d29e8a4af99fda6b2a6d3ba69 (patch)
treea2abb796aaeac1588375c109dea1d3927bc55ef8 /src/client/views/pdf/GPTPopup
parent593a894cf3b707d60b4baf1c400f138bd8b3a783 (diff)
more chat features
Diffstat (limited to 'src/client/views/pdf/GPTPopup')
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss56
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx319
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