diff options
Diffstat (limited to 'src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx')
-rw-r--r-- | src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx | 137 |
1 files changed, 104 insertions, 33 deletions
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index b22f2455e..baa4ad521 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -34,6 +34,11 @@ import './ChatBox.scss'; import MessageComponentBox from './MessageComponent'; import { ProgressBar } from './ProgressBar'; import { RichTextField } from '../../../../../fields/RichTextField'; +import { VideoBox } from '../../VideoBox'; +import { AudioBox } from '../../AudioBox'; +import { DiagramBox } from '../../DiagramBox'; +import { ImageField } from '../../../../../fields/URLField'; +import { DashUploadUtils } from '../../../../../server/DashUploadUtils'; dotenv.config(); @@ -402,13 +407,15 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { */ @action createDocInDash = async (doc_type: string, data: string | undefined, options: DocumentOptions, id: string) => { - let doc; + let doc: Doc; switch (doc_type.toLowerCase()) { case 'text': doc = Docs.Create.TextDocument(data || '', options); break; case 'image': + console.log('imageURL: ' + data); + //DashUploadUtils.UploadImage(data!); doc = Docs.Create.ImageDocument(data || '', options); break; case 'pdf': @@ -417,6 +424,13 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { case 'video': doc = Docs.Create.VideoDocument(data || '', options); break; + case 'mermaid_diagram': + doc = Docs.Create.DiagramDocument(data, options); + DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => { + const firstView = Array.from(doc[DocViews])[0] as DocumentView; + (firstView.ComponentView as DiagramBox)?.renderMermaid?.(data!); + }); + break; case 'audio': doc = Docs.Create.AudioDocument(data || '', options); break; @@ -426,12 +440,10 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 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, @@ -467,12 +479,13 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { if (foundChunk) { // Handle media chunks specifically - if (foundChunk.chunkType === CHUNK_TYPE.MEDIA) { - const directMatchSegment = this.getDirectMatchingSegment(doc, citation.direct_text || ''); - if (directMatchSegment) { + if (doc.ai_type == 'video' || doc.ai_type == 'audio') { + const directMatchSegmentStart = this.getDirectMatchingSegmentStart(doc, citation.direct_text || '', foundChunk.indexes || []); + + if (directMatchSegmentStart) { // Navigate to the segment's start time in the media player - await this.goToMediaTimestamp(doc, directMatchSegment.start_time); + await this.goToMediaTimestamp(doc, directMatchSegmentStart, doc.ai_type); } else { console.error('No direct matching segment found for the citation.'); } @@ -485,29 +498,53 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; - /** - * Finds the first segment with a direct match to the citation text. - * A match occurs if the segment's text is a subset of the citation's direct text or vice versa. - * @param doc The document containing media metadata. - * @param citationText The citation text to find a matching segment for. - * @returns The segment with the direct match or null if no match is found. - */ - getDirectMatchingSegment = (doc: Doc, citationText: string): { start_time: number; end_time: number; text: string } | null => { - const mediaMetadata = JSON.parse(StrCast(doc.segments)); // Assuming segments are stored in metadata + getDirectMatchingSegmentStart = (doc: Doc, citationText: string, indexesOfSegments: string[]): number => { + const originalSegments = JSON.parse(StrCast(doc.original_segments!)).map((segment: any, index: number) => ({ + index: index.toString(), + text: segment.text, + start: segment.start, + end: segment.end, + })); - if (!Array.isArray(mediaMetadata) || mediaMetadata.length === 0) { - return null; + if (!Array.isArray(originalSegments) || originalSegments.length === 0 || !Array.isArray(indexesOfSegments)) { + return 0; } - for (const segment of mediaMetadata) { - const segmentText = segment.text || ''; - // Check if the segment's text is a subset of the citation text or vice versa - if (citationText.includes(segmentText) || segmentText.includes(citationText)) { - return segment; // Return the first matching segment + // Create itemsToSearch array based on indexesOfSegments + const itemsToSearch = indexesOfSegments.map((indexStr: string) => { + const index = parseInt(indexStr, 10); + const segment = originalSegments[index]; + return { text: segment.text, start: segment.start }; + }); + + console.log('Constructed itemsToSearch:', itemsToSearch); + + // Helper function to calculate word overlap score + const calculateWordOverlap = (text1: string, text2: string): number => { + const words1 = new Set(text1.toLowerCase().split(/\W+/)); + const words2 = new Set(text2.toLowerCase().split(/\W+/)); + const intersection = new Set([...words1].filter(word => words2.has(word))); + return intersection.size / Math.max(words1.size, words2.size); // Jaccard similarity + }; + + // Search for the best matching segment + let bestMatchStart = 0; + let bestScore = 0; + + console.log(`Searching for best match for query: "${citationText}"`); + itemsToSearch.forEach(item => { + const score = calculateWordOverlap(citationText, item.text); + console.log(`Comparing query to segment: "${item.text}" | Score: ${score}`); + if (score > bestScore) { + bestScore = score; + bestMatchStart = item.start; } - } + }); - return null; // No match found + console.log('Best match found with score:', bestScore, '| Start time:', bestMatchStart); + + // Return the start time of the best match + return bestMatchStart; }; /** @@ -515,15 +552,20 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { * @param doc The document containing the media file. * @param timestamp The timestamp to navigate to. */ - goToMediaTimestamp = async (doc: Doc, timestamp: number) => { + goToMediaTimestamp = async (doc: Doc, timestamp: number, type: 'video' | 'audio') => { try { // Show the media document in the viewer - await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }); - - // Simulate navigation to the timestamp - const firstView = Array.from(doc[DocViews])[0] as DocumentView; - (firstView.ComponentView as any)?.gotoTimestamp?.(timestamp); - + if (type == 'video') { + DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => { + const firstView = Array.from(doc[DocViews])[0] as DocumentView; + (firstView.ComponentView as VideoBox)?.Seek?.(timestamp); + }); + } else { + DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => { + const firstView = Array.from(doc[DocViews])[0] as DocumentView; + (firstView.ComponentView as AudioBox)?.playFrom?.(timestamp); + }); + } console.log(`Navigated to timestamp: ${timestamp}s in document ${doc.id}`); } catch (error) { console.error('Error navigating to media timestamp:', error); @@ -538,6 +580,32 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { */ handleOtherChunkTypes = (foundChunk: SimplifiedChunk, citation: Citation, doc: Doc) => { switch (foundChunk.chunkType) { + case CHUNK_TYPE.IMAGE: + case CHUNK_TYPE.TABLE: + { + const values = foundChunk.location?.replace(/[[\]]/g, '').split(','); + + if (values?.length !== 4) { + console.error('Location string must contain exactly 4 numbers'); + return; + } + if (foundChunk.startPage === undefined || foundChunk.endPage === undefined) { + DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {}); + return; + } + const x1 = parseFloat(values[0]) * Doc.NativeWidth(doc); + const y1 = parseFloat(values[1]) * Doc.NativeHeight(doc) + foundChunk.startPage * Doc.NativeHeight(doc); + const x2 = parseFloat(values[2]) * Doc.NativeWidth(doc); + const y2 = parseFloat(values[3]) * Doc.NativeHeight(doc) + foundChunk.startPage * Doc.NativeHeight(doc); + + const annotationKey = Doc.LayoutFieldKey(doc) + '_annotations'; + + const existingDoc = DocListCast(doc[DocData][annotationKey]).find(d => d.citation_id === citation.citation_id); + const highlightDoc = existingDoc ?? this.createImageCitationHighlight(x1, y1, x2, y2, citation, annotationKey, doc); + + DocumentManager.Instance.showDocument(highlightDoc, { willZoomCentered: true }, () => {}); + } + break; case CHUNK_TYPE.TEXT: this.citationPopup = { text: citation.direct_text ?? 'No text available', visible: true }; setTimeout(() => (this.citationPopup.visible = false), 3000); @@ -686,7 +754,10 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) .map(d => DocCast(d?.annotationOn, d)) .filter(d => d) - .filter(d => d.ai_doc_id) + .filter(d => { + console.log(d.ai_doc_id); + return d.ai_doc_id; + }) .map(d => StrCast(d.ai_doc_id)); } |