diff options
Diffstat (limited to 'src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx')
-rw-r--r-- | src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx | 290 |
1 files changed, 255 insertions, 35 deletions
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index 44c231c87..542d8ea58 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -16,11 +16,11 @@ import { v4 as uuidv4 } from 'uuid'; import { ClientUtils } from '../../../../../ClientUtils'; import { Doc, DocListCast } from '../../../../../fields/Doc'; import { DocData, DocViews } from '../../../../../fields/DocSymbols'; -import { CsvCast, DocCast, PDFCast, RTFCast, StrCast } from '../../../../../fields/Types'; +import { CsvCast, DocCast, PDFCast, RTFCast, StrCast, NumCast } from '../../../../../fields/Types'; 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,253 @@ 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 + private createCollectionWithChildren = async (data: any, insideCol: boolean): Promise<Doc[]> => { + // Create an array of promises for each document + const childDocPromises = data.map(async doc => { + const parsedDoc = doc; + if (parsedDoc.doc_type !== 'collection') { + // Handle non-collection documents + return await this.whichDoc(parsedDoc.doc_type, parsedDoc.data, { backgroundColor: parsedDoc.backgroundColor, _width: parsedDoc.width, _height: parsedDoc.height }, parsedDoc.id, insideCol); + } else { + // Recursively process collections + const nestedDocs = await this.createCollectionWithChildren(parsedDoc.data, true); + const collectionOptions: DocumentOptions = { + title: parsedDoc.title, + backgroundColor: parsedDoc.backgroundColor, + _width: parsedDoc.width, + _height: parsedDoc.height, + _layout_fitWidth: true, + _freeform_backgroundGrid: true, + }; + const collectionDoc = DocCast(Docs.Create.FreeformDocument(nestedDocs, collectionOptions)); + return collectionDoc; + } + }); + + // Await all child document creations concurrently + const nestedResults = await Promise.all(childDocPromises); + // Flatten any nested arrays from recursive collection calls + const childDocs = nestedResults.flat() as Doc[]; + childDocs.forEach(doc => { + console.log(DocCast(doc)); + console.log(DocCast(doc)[DocData].data); + console.log(DocCast(doc)[DocData].data); + }); + return childDocs; + }; + + // @action + // createSingleFlashcard = (data: any, options: DocumentOptions) => { + + // } + + @action + whichDoc = async (doc_type: string, data: string, options: DocumentOptions, id: string, insideCol: boolean): Promise<Doc> => { + let doc; + switch (doc_type) { + case 'text': + doc = DocCast(Docs.Create.TextDocument(data, options)); + break; + case 'flashcard': + doc = this.createFlashcard(data, options); + break; + case 'deck': + doc = this.createDeck(data, options); + break; + case 'image': + doc = DocCast(Docs.Create.ImageDocument(data, options)); + break; + case 'equation': + doc = DocCast(Docs.Create.EquationDocument(data, options)); + break; + case 'noteboard': + doc = DocCast(Docs.Create.NoteTakingDocument([], options)); + break; + case 'simulation': + doc = DocCast(Docs.Create.SimulationDocument(options)); + break; + case 'collection': { + const arr = await this.createCollectionWithChildren(data, true); + options._layout_fitWidth = true; + options._freeform_backgroundGrid = true; + if (options.type_collection == 'tree') { + doc = DocCast(Docs.Create.TreeDocument(arr, options)); + } else if (options.type_collection == 'masonry') { + doc = DocCast(Docs.Create.MasonryDocument(arr, options)); + } else if (options.type_collection == 'card') { + doc = DocCast(Docs.Create.CardDeckDocument(arr, options)); + } else if (options.type_collection == 'carousel') { + doc = DocCast(Docs.Create.CarouselDocument(arr, options)); + } else if (options.type_collection == '3d-carousel') { + doc = DocCast(Docs.Create.Carousel3DDocument(arr, options)); + } else if (options.type_collection == 'multicolumn') { + doc = DocCast(Docs.Create.CarouselDocument(arr, options)); + } else { + doc = DocCast(Docs.Create.FreeformDocument(arr, options)); + } + break; + } + case 'web': + options.data_useCors = true; + doc = DocCast(Docs.Create.WebDocument(data, options)); + break; + case 'comparison': + doc = this.createComparison(data, options); + break; + case 'diagram': + doc = Docs.Create.DiagramDocument(options); + break; + case 'audio': + doc = Docs.Create.AudioDocument(data, options); + break; + case 'map': + doc = Docs.Create.MapDocument([], options); + break; + case 'screengrab': + doc = Docs.Create.ScreenshotDocument(options); + break; + case 'webcam': + doc = Docs.Create.WebCamDocument('', options); + break; + case 'button': + doc = Docs.Create.ButtonDocument(options); + break; + case 'script': + doc = Docs.Create.ScriptingDocument(null, options); + break; + case 'dataviz': + doc = Docs.Create.DataVizDocument('/users/rz/Downloads/addresses.csv', options); + break; + case 'chat': + doc = Docs.Create.ChatDocument(options); + break; + case 'trail': + doc = Docs.Create.PresDocument(options); + break; + case 'tab': + doc = Docs.Create.FreeformDocument([], options); + break; + case 'slide': + doc = Docs.Create.TreeDocument([], options); + break; + default: + doc = DocCast(Docs.Create.TextDocument(data, options)); + } + doc!.x = NumCast(options.x ?? 0) + (insideCol ? 0 : NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc.width)) + 100; + doc!.y = NumCast(options.y) + (insideCol ? 0 : NumCast(this.layoutDoc.y)); + return doc; + }; + + /** + * Creates a document in the dashboard. + * + * @param {string} doc_type - The type of document to create. + * @param {string} data - The data used to generate the document. + * @param {DocumentOptions} options - Configuration options for the document. + * @param {string} id - Unique identifier for the document. + * @returns {Promise<void>} A promise that resolves once the document is created and displayed. + */ + @action + createDocInDash = async (doc_type: string, data: string, options: DocumentOptions, id: string) => { + const doc = await this.whichDoc(doc_type, data, options, id); + const linkDoc = Docs.Create.LinkDocument(this.Document, doc); + LinkManager.Instance.addLink(linkDoc); + doc && this._props.addDocument?.(doc); + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {}); + }; + + /** + * Creates a deck of flashcards. + * + * @param {any} data - The data used to generate the flashcards. Can be a string or an object. + * @param {DocumentOptions} options - Configuration options for the flashcard deck. + * @returns {Doc} A carousel document containing the flashcard deck. + */ + @action + createDeck = (data: any, options: DocumentOptions) => { + const flashcardDeck: Doc[] = []; + // Parse `data` only if it’s a string + const deckData = typeof data === 'string' ? JSON.parse(data) : data; + const flashcardArray = Array.isArray(deckData) ? deckData : Object.values(deckData); + // Process each flashcard document in the `deckData` array + if (flashcardArray.length == 2 && flashcardArray[0].doc_type == 'text' && flashcardArray[1].doc_type == 'text') { + this.createFlashcard(flashcardArray, options); + } else { + flashcardArray.forEach(doc => { + const flashcardDoc = this.createFlashcard(doc, options); + if (flashcardDoc) flashcardDeck.push(flashcardDoc); + }); + } + + // Create a carousel to contain the flashcard deck + const carouselDoc = DocCast( + Docs.Create.CarouselDocument(flashcardDeck, { + title: options.title || 'Flashcard Deck', + _width: options._width || 300, + _height: options._height || 300, + _layout_fitWidth: false, + _layout_autoHeight: true, + }) + ); + return carouselDoc; + }; + + /** + * Creates a single flashcard document. + * + * @param {any} data - The data used to generate the flashcard. Can be a string or an object. + * @param {any} options - Configuration options for the flashcard. + * @returns {Doc | undefined} The created flashcard document, or undefined if the flashcard cannot be created. + */ + @action + createFlashcard = (data: any, options: any) => { + const deckData = typeof data === 'string' ? JSON.parse(data) : data; + const flashcardArray = Array.isArray(deckData) ? deckData : Object.values(deckData)[2]; + const [front, back] = flashcardArray; + + if (front.doc_type === 'text' && back.doc_type === 'text') { + const sideOptions: DocumentOptions = { + backgroundColor: options.backgroundColor, + _width: options._width, + _height: options._height, + }; + + // Create front and back text documents + const side1 = Docs.Create.CenteredTextCreator(front.title, front.data, sideOptions); + const side2 = Docs.Create.CenteredTextCreator(back.title, back.data, sideOptions); + + // Create the flashcard document with both sides + const flashcardDoc = DocCast(Docs.Create.FlashcardDocument(data.title, side1, side2, sideOptions)); + return flashcardDoc; + } + }; + + /** + * Creates a comparison document. + * + * @param {any} doc - The document data containing left and right components for comparison. + * @param {any} options - Configuration options for the comparison document. + * @returns {Doc} The created comparison document. + */ + @action + createComparison = (doc: any, options: any) => { + const comp = Docs.Create.ComparisonDocument(options.title, { _width: options.width, _height: options.height | 300, backgroundColor: options.backgroundColor }); + const [left, right] = doc; + const docLeft = DocCast(Docs.Create.TextDocument(left.data, { backgroundColor: left.backgroundColor, _width: left.width, _height: left.height })); + const docRight = DocCast(Docs.Create.TextDocument(right.data, { backgroundColor: right.backgroundColor, _width: right.width, _height: right.height })); + comp[DocData].data_back = docLeft; + comp[DocData].data_front = docRight; + return comp; + }; + + /** * Event handler to manage citations click in the message components. * @param citation The citation object clicked by the user. */ @@ -462,11 +692,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,17 +936,10 @@ 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"> |