aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/chatbot/chatboxcomponents
diff options
context:
space:
mode:
authoreleanor-park <eleanor_park@brown.edu>2024-12-04 23:03:22 -0500
committereleanor-park <eleanor_park@brown.edu>2024-12-04 23:03:22 -0500
commitb33ffac90703834a4d0c9255aa25702aafe3dfda (patch)
tree396952a2a006dd04079dc0075cbfdeea15a8c64e /src/client/views/nodes/chatbot/chatboxcomponents
parent2577ff5a88be04a840c4ac55360265b35d08fe19 (diff)
parent2fee49a7cd342331633bf39d1ac837838d252a18 (diff)
Merge branch 'master' into eleanor-gptdraw
Diffstat (limited to 'src/client/views/nodes/chatbot/chatboxcomponents')
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss172
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx108
-rw-r--r--src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx34
3 files changed, 189 insertions, 125 deletions
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
index 50111f678..9cf760a12 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
@@ -1,42 +1,34 @@
-@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
-$primary-color: #4a90e2;
-$secondary-color: #f5f8fa;
-$text-color: #333;
-$light-text-color: #777;
-$border-color: #e1e8ed;
+$primary-color: #3f51b5;
+$secondary-color: #f0f0f0;
+$text-color: #2e2e2e;
+$light-text-color: #6d6d6d;
+$border-color: #dcdcdc;
$shadow-color: rgba(0, 0, 0, 0.1);
-$transition: all 0.3s ease;
+$transition: all 0.2s ease-in-out;
+
.chat-box {
display: flex;
flex-direction: column;
height: 100%;
background-color: #fff;
- font-family:
- 'Atkinson Hyperlegible',
- -apple-system,
- BlinkMacSystemFont,
- 'Segoe UI',
- Roboto,
- Helvetica,
- Arial,
- sans-serif;
- border-radius: 12px;
+ font-family: 'Inter', sans-serif;
+ border-radius: 8px;
overflow: hidden;
- box-shadow: 0 4px 12px $shadow-color;
+ box-shadow: 0 2px 8px $shadow-color;
position: relative;
.chat-header {
background-color: $primary-color;
- color: white;
- padding: 15px;
+ color: #fff;
+ padding: 16px;
text-align: center;
- box-shadow: 0 2px 4px $shadow-color;
- height: fit-content;
+ box-shadow: 0 1px 4px $shadow-color;
h2 {
margin: 0;
- font-size: 1.3em;
+ font-size: 1.5em;
font-weight: 500;
}
}
@@ -44,30 +36,30 @@ $transition: all 0.3s ease;
.chat-messages {
flex-grow: 1;
overflow-y: auto;
- padding: 20px;
+ padding: 16px;
display: flex;
flex-direction: column;
- gap: 10px; // Added to give space between elements
+ gap: 12px;
&::-webkit-scrollbar {
- width: 6px;
+ width: 8px;
}
&::-webkit-scrollbar-thumb {
- background-color: $border-color;
- border-radius: 3px;
+ background-color: rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
}
}
.chat-input {
display: flex;
- padding: 20px;
+ padding: 12px;
border-top: 1px solid $border-color;
background-color: #fff;
input {
flex-grow: 1;
- padding: 12px 15px;
+ padding: 12px 16px;
border: 1px solid $border-color;
border-radius: 24px;
font-size: 15px;
@@ -78,6 +70,11 @@ $transition: all 0.3s ease;
border-color: $primary-color;
box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
}
+
+ &:disabled {
+ background-color: $secondary-color;
+ cursor: not-allowed;
+ }
}
.submit-button {
@@ -89,31 +86,31 @@ $transition: all 0.3s ease;
height: 48px;
margin-left: 10px;
cursor: pointer;
- transition: $transition;
display: flex;
align-items: center;
justify-content: center;
- position: relative;
+ transition: $transition;
&:hover {
background-color: darken($primary-color, 10%);
}
&:disabled {
- background-color: $light-text-color;
+ background-color: lighten($primary-color, 20%);
cursor: not-allowed;
}
.spinner {
- height: 24px;
- width: 24px;
+ width: 20px;
+ height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top: 3px solid #fff;
border-radius: 50%;
- animation: spin 2s linear infinite;
+ animation: spin 0.6s linear infinite;
}
}
}
+
.citation-popup {
position: fixed;
bottom: 50px;
@@ -144,23 +141,24 @@ $transition: all 0.3s ease;
}
.message {
- max-width: 80%;
- margin-bottom: 20px;
- padding: 16px 20px;
- border-radius: 18px;
+ max-width: 75%;
+ padding: 12px 16px;
+ border-radius: 12px;
font-size: 15px;
- line-height: 1.5;
- box-shadow: 0 2px 4px $shadow-color;
- word-wrap: break-word; // To handle long words
+ line-height: 1.6;
+ box-shadow: 0 1px 3px $shadow-color;
+ word-wrap: break-word;
+ display: flex;
+ flex-direction: column;
&.user {
align-self: flex-end;
background-color: $primary-color;
- color: white;
+ color: #fff;
border-bottom-right-radius: 4px;
}
- &.chatbot {
+ &.assistant {
align-self: flex-start;
background-color: $secondary-color;
color: $text-color;
@@ -168,37 +166,80 @@ $transition: all 0.3s ease;
}
.toggle-info {
+ margin-top: 10px;
background-color: transparent;
color: $primary-color;
border: 1px solid $primary-color;
- width: 100%;
- height: fit-content;
border-radius: 8px;
- padding: 10px 16px;
+ padding: 8px 12px;
font-size: 14px;
cursor: pointer;
transition: $transition;
- margin-top: 10px;
+ margin-bottom: 16px;
&:hover {
background-color: rgba($primary-color, 0.1);
}
}
+
+ .processing-info {
+ margin-bottom: 12px;
+ padding: 10px 15px;
+ background-color: #f9f9f9;
+ border-radius: 8px;
+ box-shadow: 0 1px 3px $shadow-color;
+ font-size: 14px;
+
+ .processing-item {
+ margin-bottom: 5px;
+ font-size: 14px;
+ color: $light-text-color;
+ }
+ }
+
+ .message-content {
+ background-color: inherit;
+ padding: 10px;
+ border-radius: 8px;
+ font-size: 15px;
+ line-height: 1.5;
+
+ .citation-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ background-color: rgba(0, 0, 0, 0.1);
+ color: $text-color;
+ font-size: 12px;
+ font-weight: bold;
+ margin-left: 5px;
+ cursor: pointer;
+ transition: $transition;
+
+ &:hover {
+ background-color: rgba($primary-color, 0.2);
+ color: #fff;
+ }
+ }
+ }
}
.follow-up-questions {
- margin-top: 15px;
+ margin-top: 12px;
h4 {
font-size: 15px;
font-weight: 600;
- margin-bottom: 10px;
+ margin-bottom: 8px;
}
.questions-list {
display: flex;
flex-direction: column;
- gap: 10px;
+ gap: 8px;
}
.follow-up-button {
@@ -206,15 +247,11 @@ $transition: all 0.3s ease;
color: $primary-color;
border: 1px solid $primary-color;
border-radius: 8px;
- padding: 10px 16px;
+ padding: 10px 14px;
font-size: 14px;
cursor: pointer;
transition: $transition;
text-align: left;
- white-space: normal;
- word-wrap: break-word;
- width: 100%;
- height: fit-content;
&:hover {
background-color: $primary-color;
@@ -223,27 +260,6 @@ $transition: all 0.3s ease;
}
}
-.citation-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background-color: rgba(0, 0, 0, 0.1);
- color: $text-color;
- font-size: 12px;
- font-weight: bold;
- margin-left: 5px;
- cursor: pointer;
- transition: $transition;
- vertical-align: middle;
-
- &:hover {
- background-color: rgba(0, 0, 0, 0.2);
- }
-}
-
.uploading-overlay {
position: absolute;
top: 0;
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index 44c231c87..3ef6bdd8b 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -20,7 +20,7 @@ import { CsvCast, DocCast, PDFCast, RTFCast, StrCast } from '../../../../../fiel
import { Networking } from '../../../../Network';
import { DocUtils } from '../../../../documents/DocUtils';
import { DocumentType } from '../../../../documents/DocumentTypes';
-import { Docs } from '../../../../documents/Documents';
+import { Docs, DocumentOptions } from '../../../../documents/Documents';
import { DocumentManager } from '../../../../util/DocumentManager';
import { LinkManager } from '../../../../util/LinkManager';
import { ViewBoxAnnotatableComponent } from '../../../DocComponent';
@@ -33,6 +33,7 @@ import { Vectorstore } from '../vectorstore/Vectorstore';
import './ChatBox.scss';
import MessageComponentBox from './MessageComponent';
import { ProgressBar } from './ProgressBar';
+import { RichTextField } from '../../../../../fields/RichTextField';
dotenv.config();
@@ -89,7 +90,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.vectorstore_id = StrCast(this.dataDoc.vectorstore_id);
}
this.vectorstore = new Vectorstore(this.vectorstore_id, this.retrieveDocIds);
- this.agent = new Agent(this.vectorstore, this.retrieveSummaries, this.retrieveFormattedHistory, this.retrieveCSVData, this.addLinkedUrlDoc, this.createCSVInDash);
+ this.agent = new Agent(this.vectorstore, this.retrieveSummaries, this.retrieveFormattedHistory, this.retrieveCSVData, this.addLinkedUrlDoc, this.createDocInDash, this.createCSVInDash);
this.messagesRef = React.createRef<HTMLDivElement>();
// Reaction to update dataDoc when chat history changes
@@ -354,29 +355,11 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const linkDoc = Docs.Create.LinkDocument(this.Document, doc);
LinkManager.Instance.addLink(linkDoc);
- let canDisplay;
-
- try {
- // Fetch the URL content through the proxy
- const { data } = await Networking.PostToServer('/proxyFetch', { url });
-
- // Simulating header behavior since we can't fetch headers via proxy
- const xFrameOptions = data.headers?.['x-frame-options'];
-
- if (xFrameOptions && xFrameOptions.toUpperCase() === 'SAMEORIGIN') {
- canDisplay = false;
- } else {
- canDisplay = true;
- }
- } catch (error) {
- console.error('Error fetching the URL from the server:', error);
- }
const chunkToAdd = {
chunkId: id,
chunkType: CHUNK_TYPE.URL,
url: url,
- canDisplay: canDisplay,
};
doc.chunk_simpl = JSON.stringify({ chunks: [chunkToAdd] });
@@ -411,6 +394,68 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
/**
+ * Creates a text document in the dashboard and adds it for analysis.
+ * @param title The title of the doc.
+ * @param text_content The text of the document.
+ * @param options Other optional document options (e.g. color)
+ * @param id The unique ID for the document.
+ */
+ @action
+ createDocInDash = async (doc_type: string, data: string | undefined, options: DocumentOptions, id: string) => {
+ let doc;
+
+ switch (doc_type.toLowerCase()) {
+ case 'text':
+ doc = Docs.Create.TextDocument(data || '', options);
+ break;
+ case 'image':
+ doc = Docs.Create.ImageDocument(data || '', options);
+ break;
+ case 'pdf':
+ doc = Docs.Create.PdfDocument(data || '', options);
+ break;
+ case 'video':
+ doc = Docs.Create.VideoDocument(data || '', options);
+ break;
+ case 'audio':
+ doc = Docs.Create.AudioDocument(data || '', options);
+ break;
+ case 'web':
+ doc = Docs.Create.WebDocument(data || '', options);
+ break;
+ case 'equation':
+ doc = Docs.Create.EquationDocument(data || '', options);
+ break;
+ case 'functionplot':
+ case 'function_plot':
+ doc = Docs.Create.FunctionPlotDocument([], options);
+ break;
+ case 'dataviz':
+ case 'data_viz': {
+ const { fileUrl, id } = await Networking.PostToServer('/createCSV', {
+ filename: (options.title as string).replace(/\s+/g, '') + '.csv',
+ data: data,
+ });
+ doc = Docs.Create.DataVizDocument(fileUrl, { ...options, text: RTFCast(data) });
+ this.addCSVForAnalysis(doc, id);
+ break;
+ }
+ case 'chat':
+ doc = Docs.Create.ChatDocument(options);
+ break;
+ // Add more cases for other document types
+ default:
+ console.error('Unknown or unsupported document type:', doc_type);
+ return;
+ }
+ const linkDoc = Docs.Create.LinkDocument(this.Document, doc);
+ LinkManager.Instance.addLink(linkDoc);
+
+ doc && this._props.addDocument?.(doc);
+ await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {});
+ };
+
+ /**
* Event handler to manage citations click in the message components.
* @param citation The citation object clicked by the user.
*/
@@ -462,11 +507,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
});
break;
case CHUNK_TYPE.URL:
- if (!foundChunk.canDisplay) {
- window.open(StrCast(doc.displayUrl), '_blank');
- } else if (foundChunk.canDisplay) {
- DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {});
- }
+ DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {});
+
break;
case CHUNK_TYPE.CSV:
DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {});
@@ -709,22 +751,16 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</div>
<div className="chat-messages" ref={this.messagesRef}>
{this.history.map((message, index) => (
- <MessageComponentBox key={index} message={message} index={index} onFollowUpClick={this.handleFollowUpClick} onCitationClick={this.handleCitationClick} updateMessageCitations={this.updateMessageCitations} />
+ <MessageComponentBox key={index} message={message} onFollowUpClick={this.handleFollowUpClick} onCitationClick={this.handleCitationClick} updateMessageCitations={this.updateMessageCitations} />
))}
{this.current_message && (
- <MessageComponentBox
- key={this.history.length}
- message={this.current_message}
- index={this.history.length}
- onFollowUpClick={this.handleFollowUpClick}
- onCitationClick={this.handleCitationClick}
- updateMessageCitations={this.updateMessageCitations}
- />
+ <MessageComponentBox key={this.history.length} message={this.current_message} onFollowUpClick={this.handleFollowUpClick} onCitationClick={this.handleCitationClick} updateMessageCitations={this.updateMessageCitations} />
)}
</div>
+
<form onSubmit={this.askGPT} className="chat-input">
- <input type="text" name="messageInput" autoComplete="off" placeholder="Type your message here..." value={this.inputValue} onChange={e => (this.inputValue = e.target.value)} />
- <button className="submit-button" type="submit" disabled={this.isLoading}>
+ <input type="text" name="messageInput" autoComplete="off" placeholder="Type your message here..." value={this.inputValue} onChange={e => (this.inputValue = e.target.value)} disabled={this.isLoading} />
+ <button className="submit-button" type="submit" disabled={this.isLoading || !this.inputValue.trim()}>
{this.isLoading ? (
<div className="spinner"></div>
) : (
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx
index e463d15bf..1a3d4dbc6 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx
@@ -11,6 +11,7 @@ import React, { useState } from 'react';
import { observer } from 'mobx-react';
import { AssistantMessage, Citation, MessageContent, PROCESSING_TYPE, ProcessingInfo, TEXT_TYPE } from '../types/types';
import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
/**
* Props for the MessageComponentBox.
@@ -50,16 +51,27 @@ const MessageComponentBox: React.FC<MessageComponentProps> = ({ message, onFollo
const citation_ids = item.citation_ids || [];
return (
<span key={i} className="grounded-text">
- <ReactMarkdown>{item.text}</ReactMarkdown>
- {citation_ids.map((id, idx) => {
- const citation = message.citations?.find(c => c.citation_id === id);
- if (!citation) return null;
- return (
- <button key={i + idx} className="citation-button" onClick={() => onCitationClick(citation)}>
- {i + 1}
- </button>
- );
- })}
+ <ReactMarkdown
+ remarkPlugins={[remarkGfm]}
+ components={{
+ p: ({ node, children }) => (
+ <span className="grounded-text">
+ {children}
+ {citation_ids.map((id, idx) => {
+ const citation = message.citations?.find(c => c.citation_id === id);
+ if (!citation) return null;
+ return (
+ <button key={i + idx} className="citation-button" onClick={() => onCitationClick(citation)} style={{ display: 'inline-flex', alignItems: 'center', marginLeft: '4px' }}>
+ {i + idx + 1}
+ </button>
+ );
+ })}
+ <br />
+ </span>
+ ),
+ }}>
+ {item.text}
+ </ReactMarkdown>
</span>
);
}
@@ -68,7 +80,7 @@ const MessageComponentBox: React.FC<MessageComponentProps> = ({ message, onFollo
else if (item.type === TEXT_TYPE.NORMAL) {
return (
<span key={i} className="normal-text">
- <ReactMarkdown>{item.text}</ReactMarkdown>
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{item.text}</ReactMarkdown>
</span>
);
}