import { action, computed, makeObservable, observable, observe, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import OpenAI, { ClientOptions } from 'openai'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { CsvCast, DocCast, PDFCast, StrCast } from '../../../../fields/Types'; import { Networking } from '../../../Network'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { LinkManager } from '../../../util/LinkManager'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import './ChatBox.scss'; import MessageComponentBox from './MessageComponent'; import { ASSISTANT_ROLE, AssistantMessage, AI_Document, convertToAIDocument, Citation, CHUNK_TYPE } from './types'; import { Vectorstore } from './vectorstore/VectorstoreUpload'; import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { Agent } from './Agent'; import dotenv from 'dotenv'; import { DocData } from '../../../../fields/DocSymbols'; import { DocumentView } from '../DocumentView'; import { AnswerParser } from './AnswerParser'; dotenv.config(); @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 = ''; @observable private _visibleDocs: Doc[] = []; private openai: OpenAI; // private vectorstore_id: string; private documents: AI_Document[] = []; private _oldWheel: any; private vectorstore: Vectorstore; private agent: Agent; // Add the ChatBot instance public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ChatBox, fieldKey); } constructor(props: FieldViewProps) { super(props); makeObservable(this); this.history = [{ role: ASSISTANT_ROLE.ASSISTANT, text_content: 'Welcome to the Document Analyser Assistant! Link a document or ask questions to get started.' }]; this.openai = this.initializeOpenAI(); this.vectorstore = new Vectorstore(); this.agent = new Agent(this.vectorstore); // Initialize the Agent reaction( () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text_content: msg.text_content, follow_up_questions: msg.follow_up_questions, citations: msg.citations })), serializableHistory => { this.dataDoc.data = JSON.stringify(serializableHistory); } ); } @action addDocsToVectorstore = async (visible_docs: Doc[]) => { await this.vectorstore.addAIDocs(visible_docs); this.isInitializing = false; }; @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(); } }; // getAssistantResponse() { // return Docs.Create.MessageDocument(text, {}); // } @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_content: trimmedText }); this.isLoading = true; }); const response = await this.agent.askAgent(trimmedText); // Use the chatbot to get the response runInAction(() => { this.history.push(AnswerParser.parse(response)); }); this.dataDoc.data = JSON.stringify(this.history); } catch (err) { console.error('Error:', err); runInAction(() => { this.history.push({ role: ASSISTANT_ROLE.ASSISTANT, text_content: 'Sorry, I encountered an error while processing your request.' }); }); } finally { runInAction(() => { this.isLoading = false; }); } } }; @action updateMessageCitations = (index: number, citations: Citation[]) => { if (this.history[index]) { this.history[index].citations = citations; } }; @action handleCitationClick = (citation: Citation) => { console.log('Citation clicked:', citation); // You can implement additional functionality here, such as showing a modal with the full citation content }; // @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_content: msg.text_content, follow_up_questions: msg.follow_up_questions, citations: msg.citations, })) ); }); } catch (e) { console.error('Failed to parse history from dataDoc:', e); } } reaction( () => this.visibleDocs, visibleDocs => { this._visibleDocs.push(...visibleDocs.filter(visibleDoc => !this._visibleDocs.includes(visibleDoc))); } ); 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._visibleDocs, 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[]); console.log('here!'); this.addDocsToVectorstore((change as any).added as Doc[]); } // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); break; case 'update': // let oldValue = change.oldValue; default: } }, true ); runInAction(() => { if (!this._visibleDocs.length) { this.isInitializing = false; } }); } @computed get visibleDocs() { //return (CollectionFreeFormDocumentView.from(this._props.DocumentView?.())?._props.parent as CollectionFreeFormView)?.childDocs.filter(doc => doc != this.Document) ?? []; return LinkManager.Instance.getAllRelatedLinks(this.Document) .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) .map(d => DocCast(d?.annotationOn, d)) .filter(d => d); } @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) => ( // ))} {this.current_message && ( )}
(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: '' }, });