import { MathJaxContext } from 'better-react-mathjax'; import { action, makeObservable, observable, observe, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import OpenAI, { ClientOptions } from 'openai'; import { ImageFile, Message } from 'openai/resources/beta/threads/messages'; import { RunStep } from 'openai/resources/beta/threads/runs/steps'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { CsvCast, DocCast, PDFCast, StrCast } from '../../../../fields/Types'; import { CsvField } from '../../../../fields/URLField'; import { Networking } from '../../../Network'; import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { LinkManager } from '../../../util/LinkManager'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import './ChatBox.scss'; import MessageComponent from './MessageComponent'; 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() { @observable history: AssistantMessage[] = []; @observable.deep current_message: AssistantMessage | undefined = undefined; @observable isLoading: boolean = false; @observable isInitializing: boolean = true; @observable expandedScratchpadIndex: number | null = null; @observable linked_docs_to_add: Doc[] = []; @observable inputValue: string = ''; private openai: OpenAI; private documents: AI_Document[] = []; private _oldWheel: any; 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.openai = this.initializeOpenAI(); this.getLinkedDocs(); this.vectorstore = new Vectorstore(); reaction( () => 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); } ); } 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); visual_docs?.forEach(async doc => { const local_file_path: string = CsvCast(doc.data, PDFCast(doc.data)).url?.pathname; 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() { const configuration: ClientOptions = { apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true, }; return new OpenAI(configuration); } onPassiveWheel = (e: WheelEvent) => { if (this._props.isContentActive()) { e.stopPropagation(); } }; @action askGPT = async (event: React.FormEvent): Promise => { event.preventDefault(); this.inputValue = ''; const textInput = event.currentTarget.elements.namedItem('messageInput') as HTMLInputElement; const trimmedText = textInput.value.trim(); if (trimmedText) { try { textInput.value = ''; runInAction(() => { this.history.push({ role: ASSISTANT_ROLE.USER, text: trimmedText }); }); const { response } = await Networking.PostToServer('/askAgent', { input: trimmedText }); runInAction(() => { this.history.push({ role: ASSISTANT_ROLE.ASSISTANT, text: response }); }); this.dataDoc.data = JSON.stringify(this.history); } catch (err) { console.error('Error:', err); } } }; // @action // uploadLinks = async (linkedDocs: Doc[]) => { // if (this.isInitializing) { // console.log('Initialization in progress, upload aborted.'); // return; // } // const urls: string[] = linkedDocs.map(doc => CsvCast(doc.data, PDFCast(doc.data)).url.pathname); // const csvUrls: string[] = urls.filter(url => url.endsWith('.csv')); // console.log(this.assistantID, this.threadID, urls); // await Networking.PostToServer('/uploadPDFs', { file_path: urls[0] }); // // linkedDocs.forEach((doc, i) => { // // doc[this.Document[Id] + '_ai_field_id'] = openaiFileIds[i]; // // console.log('AI Field ID: ' + openaiFileIds[i]); // // }); // // if (csvUrls.length > 0) { // // for (let i = 0; i < csvUrls.length; i++) { // // this.linkedCsvIDs.push(openaiFileIds[urls.indexOf(csvUrls[i])]); // // } // // console.log('linked csvs:' + this.linkedCsvIDs); // // await this.openai.beta.assistants.update(this.assistantID, { // // tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], // // tool_resources: { // // file_search: { // // vector_store_ids: [this.vectorStoreID], // // }, // // code_interpreter: { // // file_ids: this.linkedCsvIDs, // // }, // // }, // // }); // // } // }; componentDidMount() { this._props.setContentViewBox?.(this); if (this.dataDoc.data) { try { const storedHistory = JSON.parse(StrCast(this.dataDoc.data)); runInAction(() => { this.history.push( ...storedHistory.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, follow_up_questions: msg.follow_up_questions, citations: msg.citations, })) ); }); } catch (e) { console.error('Failed to parse history from dataDoc:', e); } } reaction( () => { const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) .map(d => DocCast(d?.annotationOn, d)) .filter(d => d); return linkedDocs; }, linked => this.linked_docs_to_add.push(...linked.filter(linkedDoc => !this.linked_docs_to_add.includes(linkedDoc))) ); observe( // right now this skips during initialization which is necessary because it would be blank // However, it will upload the same link twice when it is this.linked_docs_to_add, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) switch (change.type as any) { case 'splice': 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[]); ((change as any).added as Doc[]).forEach(doc => { this.uploadNewDocument(doc); }); } // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); break; case 'update': // let oldValue = change.oldValue; default: } }, true ); } @action handleFollowUpClick = (question: string) => { console.log('Follow-up question clicked:', question); this.inputValue = question; }; render() { return ( /** **/
{this.isInitializing &&
Initializing...
}
{ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = r; r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); }}>
{ //this.history.map((message, index) => ( // {}} // setCurrentFile={this.setCurrentFile} // onFollowUpClick={this.handleFollowUpClick} // /> //) //) } { //!this.current_message ? null : ( // {}} // setCurrentFile={this.setCurrentFile} // onFollowUpClick={this.handleFollowUpClick} // /> //) }
(this.inputValue = e.target.value)} />
/**
**/ ); } } Docs.Prototypes.TemplateMap.set(DocumentType.CHAT, { layout: { view: ChatBox, dataField: 'data' }, options: { acl: '', chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' }, });