diff options
Diffstat (limited to 'src/client/views/nodes/ChatBox/ChatBox.tsx')
-rw-r--r-- | src/client/views/nodes/ChatBox/ChatBox.tsx | 304 |
1 files changed, 75 insertions, 229 deletions
diff --git a/src/client/views/nodes/ChatBox/ChatBox.tsx b/src/client/views/nodes/ChatBox/ChatBox.tsx index b986c7393..9f4e6f07e 100644 --- a/src/client/views/nodes/ChatBox/ChatBox.tsx +++ b/src/client/views/nodes/ChatBox/ChatBox.tsx @@ -19,91 +19,82 @@ import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import './ChatBox.scss'; import MessageComponent from './MessageComponent'; -import { ANNOTATION_LINK_TYPE, ASSISTANT_ROLE, AssistantMessage, DOWNLOAD_TYPE } from './types'; +import { ASSISTANT_ROLE, AssistantMessage, AI_Document, convertToAIDocument } from './types'; import { Annotation } from 'mobx/dist/internal'; import { FormEvent } from 'react'; +import { url } from 'inspector'; +import { Vectorstore } from './vectorstore/VectorstoreUpload'; +import { DocumentView } from '../DocumentView'; +import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; @observer export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { - @observable modalStatus = false; - @observable currentFile = { url: '' }; @observable history: AssistantMessage[] = []; @observable.deep current_message: AssistantMessage | undefined = undefined; @observable isLoading: boolean = false; @observable isInitializing: boolean = true; - @observable expandedLogIndex: number | null = null; + @observable expandedScratchpadIndex: number | null = null; @observable linked_docs_to_add: Doc[] = []; @observable inputValue: string = ''; private openai: OpenAI; - private assistantID: string = ''; - private threadID: string = ''; + private documents: AI_Document[] = []; private _oldWheel: any; - private vectorStoreID: string = ''; - private mathJaxConfig: any; - private linkedCsvIDs: string[] = []; + private vectorstore: Vectorstore; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ChatBox, fieldKey); } + constructor(props: FieldViewProps) { super(props); makeObservable(this); this.openai = this.initializeOpenAI(); this.history = [{ role: ASSISTANT_ROLE.ASSISTANT, text: 'Welcome to the Document Analyser Assistant! Link a document or ask questions to get started.' }]; - this.threadID = StrCast(this.dataDoc.thread_id); - this.assistantID = StrCast(this.dataDoc.assistant_id); - this.vectorStoreID = StrCast(this.dataDoc.vector_store_id); this.openai = this.initializeOpenAI(); - if (this.assistantID === '' || this.threadID === '' || this.vectorStoreID === '') { - this.createAssistant(); - } else { - this.retrieveCsvUrls(); - this.isInitializing = false; - } - this.mathJaxConfig = { - loader: { load: ['input/asciimath'] }, - tex: { - inlineMath: [ - ['$', '$'], - ['\\(', '\\)'], - ], - displayMath: [ - ['$$', '$$'], - ['[', ']'], - ], - }, - }; + this.getLinkedDocs(); + this.vectorstore = new Vectorstore(); + reaction( - () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, image: msg.image, tool_logs: msg.tool_logs })), + () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, follow_up_questions: msg.follow_up_questions, citations: msg.citations })), serializableHistory => { this.dataDoc.data = JSON.stringify(serializableHistory); } ); } - @action - toggleToolLogs = (index: number) => { - this.expandedLogIndex = this.expandedLogIndex === index ? null : index; - }; + getLinkedDocs = async () => { + const visual_docs = (CollectionFreeFormDocumentView.from(this._props.DocumentView?.())?._props.parent as CollectionFreeFormView)?.childDocs.filter(doc => doc != this.Document); + console.log('All Docs:', visual_docs); - retrieveCsvUrls() { - const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) - .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) - .map(d => DocCast(d?.annotationOn, d)) - .filter(d => d); + visual_docs?.forEach(async doc => { + const local_file_path: string = CsvCast(doc.data, PDFCast(doc.data)).url?.pathname; - linkedDocs.forEach(doc => { - const aiFieldId = StrCast(doc[this.Document[Id] + '_ai_field_id']); - if (CsvCast(doc.data)) { - this.linkedCsvIDs.push(StrCast(aiFieldId)); - console.log(this.linkedCsvIDs); + if (local_file_path) { + const { document_json } = await Networking.PostToServer('/createDocument', { file_path: local_file_path }); + const ai_document: AI_Document = convertToAIDocument(document_json); + this.documents.push(ai_document); + await this.vectorstore.addDocument(ai_document); + doc['ai_document'] = document_json; } }); - } + }; + + @action + uploadNewDocument = async (newDoc: Doc) => { + const local_file_path: string = CsvCast(newDoc.data, PDFCast(newDoc.data)).url.pathname; + const { document_json } = await Networking.PostToServer('/createDocument', { file_path: local_file_path }); + this.documents.push(...document_json.map(convertToAIDocument)); + newDoc['ai_document'] = document_json; + }; + + @action + toggleToolLogs = (index: number) => { + this.expandedScratchpadIndex = this.expandedScratchpadIndex === index ? null : index; + }; initializeOpenAI() { - //console.log(process.env._CLIENT_OPENAI_KEY); const configuration: ClientOptions = { apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true, @@ -118,50 +109,6 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; @action - createAssistant = async () => { - this.isInitializing = true; - try { - const vectorStore = await this.openai.beta.vectorStores.create({ - name: 'Vector Store for Assistant', - }); - const assistant = await this.openai.beta.assistants.create({ - name: 'Document Analyser Assistant', - instructions: ` - You will analyse documents with which you are provided. You will answer questions and provide insights based on the information in the documents. - For writing math formulas: - You have a MathJax render environment. - - Write all in-line equations within a single dollar sign, $, to render them as TeX (this means any time you want to use a dollar sign to represent a dollar sign itself, you must escape it with a backslash: "$"); - - Use a double dollar sign, $$, to render equations on a new line; - Example: $$x^2 + 3x$$ is output for "x² + 3x" to appear as TeX.`, - model: 'gpt-4-turbo', - tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], - tool_resources: { - file_search: { - vector_store_ids: [vectorStore.id], - }, - code_interpreter: { - file_ids: this.linkedCsvIDs, - }, - }, - }); - const thread = await this.openai.beta.threads.create(); - - runInAction(() => { - this.dataDoc.assistant_id = assistant.id; - this.dataDoc.thread_id = thread.id; - this.dataDoc.vector_store_id = vectorStore.id; - this.assistantID = assistant.id; - this.threadID = thread.id; - this.vectorStoreID = vectorStore.id; - this.isInitializing = false; - }); - } catch (error) { - console.error('Initialization failed:', error); - this.isInitializing = false; - } - }; - - @action askGPT = async (event: React.FormEvent<HTMLFormElement>): Promise<void> => { event.preventDefault(); this.inputValue = ''; @@ -222,111 +169,6 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // // } // }; - downloadToComputer = (url: string, fileName: string) => { - fetch(url, { method: 'get', mode: 'no-cors', referrerPolicy: 'no-referrer' }) - .then(res => res.blob()) - .then(res => { - const aElement = document.createElement('a'); - aElement.setAttribute('download', fileName); - const href = URL.createObjectURL(res); - aElement.href = href; - aElement.setAttribute('target', '_blank'); - aElement.click(); - URL.revokeObjectURL(href); - }); - }; - - createDocumentInDash = async (url: string) => { - const fileSuffix = url.substring(url.lastIndexOf('.') + 1); - console.log(fileSuffix); - let doc: Doc | null = null; - switch (fileSuffix) { - case 'pdf': - doc = DocCast(await DocUtils.DocumentFromType('pdf', url, {})); - break; - case 'csv': - doc = DocCast(await DocUtils.DocumentFromType('csv', url, {})); - break; - case 'png': - case 'jpg': - case 'jpeg': - doc = DocCast(await DocUtils.DocumentFromType('image', url, {})); - break; - default: - console.error('Unsupported file type:', fileSuffix); - break; - } - if (doc) { - doc && this._props.addDocument?.(doc); - //add to overlay - await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {}); - } - }; - - downloadFile = async (fileInfo: string, downloadType: DOWNLOAD_TYPE) => { - try { - console.log(fileInfo); - const [fileId, fileName] = fileInfo.split(/!!!/); - const { file_path: filePath } = await Networking.PostToServer('/downloadFileFromOpenAI', { file_id: fileId, file_name: fileName }); - const fileLink = CsvCast(new CsvField(filePath)).url.href; - if (downloadType === DOWNLOAD_TYPE.DASH) { - this.createDocumentInDash(fileLink); - } else { - this.downloadToComputer(fileLink, fileName); - } - } catch (error) { - console.error('Error downloading file:', error); - } - }; - - handleDownloadToDevice = () => { - this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DEVICE); - this.modalStatus = false; // Close the modal after the action - this.currentFile = { url: '' }; // Reset the current file - }; - - handleAddToDash = () => { - // Assuming `downloadFile` is a method that handles adding to Dash - this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DASH); - this.modalStatus = false; // Close the modal after the action - this.currentFile = { url: '' }; // Reset the current file - }; - - renderModal = () => { - if (!this.modalStatus) return null; - - return ( - <div className="modal"> - <div className="modal-content"> - <h4>File Actions</h4> - <p>Choose an action for the file:</p> - <button type="button" onClick={this.handleDownloadToDevice}> - Download to Device - </button> - <button type="button" onClick={this.handleAddToDash}> - Add to Dash - </button> - <button - type="button" - onClick={() => { - this.modalStatus = false; - }}> - Cancel - </button> - </div> - </div> - ); - }; - @action - showModal = () => { - this.modalStatus = true; - }; - - @action - setCurrentFile = (file: { url: string }) => { - this.currentFile = file; - }; - componentDidMount() { this._props.setContentViewBox?.(this); if (this.dataDoc.data) { @@ -337,9 +179,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ...storedHistory.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, - quote: msg.quote, - tool_logs: msg.tool_logs, - image: msg.image, + follow_up_questions: msg.follow_up_questions, + citations: msg.citations, })) ); }); @@ -355,7 +196,6 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { .filter(d => d); return linkedDocs; }, - linked => this.linked_docs_to_add.push(...linked.filter(linkedDoc => !this.linked_docs_to_add.includes(linkedDoc))) ); @@ -370,7 +210,9 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { if ((change as any).addedCount > 0) { // maybe check here if its already in the urls datadoc array so doesn't add twice console.log((change as any).added as Doc[]); - this.uploadLinks((change as any).added as Doc[]); + ((change as any).added as Doc[]).forEach(doc => { + this.uploadNewDocument(doc); + }); } // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); break; @@ -392,7 +234,6 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { /** <MathJaxContext config={this.mathJaxConfig}> **/ <div className="chatBox"> {this.isInitializing && <div className="initializing-overlay">Initializing...</div>} - {this.renderModal()} <div className="scroll-box chat-content" ref={r => { @@ -401,32 +242,37 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); }}> <div className="messages"> - {this.history.map((message, index) => ( - <MessageComponent - key={index} - message={message} - toggleToolLogs={this.toggleToolLogs} - expandedLogIndex={this.expandedLogIndex} - index={index} - showModal={this.showModal} - goToLinkedDoc={() => {}} - setCurrentFile={this.setCurrentFile} - onFollowUpClick={this.handleFollowUpClick} - /> - ))} - {!this.current_message ? null : ( - <MessageComponent - key={this.history.length} - message={this.current_message} - toggleToolLogs={this.toggleToolLogs} - expandedLogIndex={this.expandedLogIndex} - index={this.history.length} - showModal={this.showModal} - goToLinkedDoc={() => {}} - setCurrentFile={this.setCurrentFile} - onFollowUpClick={this.handleFollowUpClick} - /> - )} + { + //this.history.map((message, index) => ( + // <MessageComponent + // key={index} + // message={message} + // toggleToolLogs={this.toggleToolLogs} + // expandedLogIndex={this.expandedLogIndex} + // index={index} + // showModal={this.showModal} + // goToLinkedDoc={() => {}} + // setCurrentFile={this.setCurrentFile} + // onFollowUpClick={this.handleFollowUpClick} + // /> + //) + //) + } + { + //!this.current_message ? null : ( + // <MessageComponent + // key={this.history.length} + // message={this.current_message} + // toggleToolLogs={this.toggleToolLogs} + // expandedLogIndex={this.expandedLogIndex} + // index={this.history.length} + // showModal={this.showModal} + // goToLinkedDoc={() => {}} + // setCurrentFile={this.setCurrentFile} + // onFollowUpClick={this.handleFollowUpClick} + // /> + //) + } </div> </div> <form onSubmit={this.askGPT} className="chat-form"> |