import { makeObservable, observable, action ,reaction} from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './DiagramBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import mermaid from 'mermaid'; import { Doc, DocListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ContextMenu } from '../ContextMenu'; import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT'; import { ChatCompletionMessageParam } from 'openai/resources/chat/completions'; import OpenAI, { ClientOptions } from 'openai'; import { line } from 'd3'; import { InkingStroke } from '../InkingStroke'; import { DocumentManager } from '../../util/DocumentManager'; import { C } from '@fullcalendar/core/internal-common'; import { Docs } from '../../documents/Documents'; import { NumCast } from '../../../fields/Types'; @observer export class DiagramBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DiagramBox, fieldKey); } private _ref: React.RefObject = React.createRef(); private _dragRef = React.createRef(); constructor(props: FieldViewProps) { super(props); makeObservable(this); } @observable inputValue = ''; @observable loading = false; @observable errorMessage = ''; @observable mermaidCode = ''; @action handleInputChange = (e: React.ChangeEvent) => { this.inputValue = e.target.value; }; async componentDidMount() { this._props.setContentViewBox?.(this); mermaid.initialize({ securityLevel: 'loose', startOnLoad: true, flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' }, }); this.mermaidCode = 'asdasdasd'; let docArray: Doc[] = DocListCast(this.Document.data); let mermaidCodeDoc = docArray.filter(doc => doc.type == 'rich text') mermaidCodeDoc=mermaidCodeDoc.filter(doc=>(doc.text as RichTextField).Text=='mermaidCodeTitle') if(mermaidCodeDoc[0]){ if(typeof mermaidCodeDoc[0].title=='string'){ console.log(mermaidCodeDoc[0].title) if(mermaidCodeDoc[0].title!=""){ this.renderMermaidAsync(mermaidCodeDoc[0].title); } } } else{ DocumentManager.Instance.AddViewRenderedCb(this.Document, (docViewForYourCollection) => { if (docViewForYourCollection && docViewForYourCollection.ComponentView) { if (docViewForYourCollection.ComponentView.addDocument&&docViewForYourCollection.ComponentView.removeDocument) { let newDoc=Docs.Create.TextDocument("mermaidCodeTitle", { title: "", x: 9999 + NumCast(this.layoutDoc._width), y: 9999 }) docViewForYourCollection.ComponentView?.addDocument(newDoc) } } }); } reaction( () => DocListCast(this.Document.data), docs => { console.log("reaction happened") this.createMermaidCode(); }, { fireImmediately: true } ); } renderMermaid = async (str: string) => { try { const { svg, bindFunctions } = await this.mermaidDiagram(str); return { svg, bindFunctions }; } catch (error) { console.error('Error rendering mermaid diagram:', error); return { svg: '', bindFunctions: undefined }; } }; mermaidDiagram = async (str: string) => { return await mermaid.render('graph' + Date.now(), str); }; async renderMermaidAsync(mermaidCode: string) { try { const { svg, bindFunctions } = await this.renderMermaid(mermaidCode); const dashDiv = document.getElementById('dashDiv'); if (dashDiv) { dashDiv.innerHTML = svg; if (bindFunctions) { bindFunctions(dashDiv); } } } catch (error) { console.error('Error rendering Mermaid:', error); } } @action handleRenderClick = () => { // Call the GPT model and get the HTML output // const modelOutput = getHtmlOutput(this.inputValue); // this.htmlCode = modelOutput; this.generateMermaidCode(); }; printTitle(){ let docArray: Doc[] = DocListCast(this.Document.data); let mermaidCodeDoc = docArray.filter(doc => doc.type == 'rich text') mermaidCodeDoc=mermaidCodeDoc.filter(doc=>(doc.text as RichTextField).Text=='mermaidCodeTitle') console.log(mermaidCodeDoc[0].title) if(mermaidCodeDoc[0]){ console.log(mermaidCodeDoc[0].title) if(typeof mermaidCodeDoc[0].title=='string'){ console.log(mermaidCodeDoc[0].title) if(mermaidCodeDoc[0].title!=""){ console.log("you have to see me") } } } } @action async generateMermaidCode() { console.log('Generating Mermaid Code'); this.loading = true; let prompt="" // let docArray: Doc[] = DocListCast(this.Document.data); // let mermaidCodeDoc = docArray.filter(doc => doc.type == 'rich text') // mermaidCodeDoc=mermaidCodeDoc.filter(doc=>(doc.text as RichTextField).Text=='mermaidCodeTitle') // if(mermaidCodeDoc[0]){ // console.log(mermaidCodeDoc[0].title) // if(typeof mermaidCodeDoc[0].title=='string'){ // console.log(mermaidCodeDoc[0].title) // if(mermaidCodeDoc[0].title!=""){ // prompt="Edit this code "+this.inputValue+": "+mermaidCodeDoc[0].title // console.log("you have to see me") // } // } // } // else{ prompt="Write this in mermaid code and only give me the mermaid code: "+this.inputValue console.log("there is no text save") //} let res = await gptAPICall(prompt, GPTCallType.MERMAID); this.loading = false; if (res == 'Error connecting with API.') { // If GPT call failed console.error('GPT call failed'); this.errorMessage = 'GPT call failed; please try again.'; } else if (res != null) { // If GPT call succeeded, set htmlCode;;; TODO: check if valid html if (this.isValidCode(res)) { this.mermaidCode = res; console.log('GPT call succeeded:' + res); this.errorMessage = ''; } else { console.error('GPT call succeeded but invalid html; please try again.'); this.errorMessage = 'GPT call succeeded but invalid html; please try again.'; } } this.renderMermaidAsync.call(this, this.removeWords(this.mermaidCode)); this.loading = false; }; isValidCode = (html: string) => { return true; }; testInkingStroke = () => { if (this.Document.data instanceof List) { let docArray: Doc[] = DocListCast(this.Document.data); let lineArray = docArray.filter(doc => doc.title == 'line' || doc.title == 'stroke'); setTimeout(() => { let inkStrokeArray = lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke); console.log(inkStrokeArray); }); } }; removeWords(inputStr: string) { inputStr = inputStr.replace('```mermaid', ''); return inputStr.replace('```', ''); } async createMermaidCode() { let mermaidCode="" let diagramExists=false if (this.Document.data instanceof List) { let docArray: Doc[] = DocListCast(this.Document.data); let rectangleArray = docArray.filter(doc => doc.title == 'rectangle' || doc.title == 'circle'); let lineArray = docArray.filter(doc => doc.title == 'line' || doc.title == 'stroke'); let textArray = docArray.filter(doc => doc.type == 'rich text'); const timeoutPromise = () => new Promise(resolve => { setTimeout(resolve, 0); }); await timeoutPromise(); lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).forEach(inkView => { const componentView = inkView?.ComponentView; if (componentView) { console.log(componentView.constructor.name, componentView); // Print instance type and object } }); let inkStrokeArray = lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke); console.log(lineArray.length) console.log(inkStrokeArray.length) if (inkStrokeArray[0]) { mermaidCode = 'graph LR;'; let inkingStrokeArray = inkStrokeArray.map(stroke => stroke?.ComponentView); for (let i = 0; i < rectangleArray.length; i++) { const rectangle = rectangleArray[i]; for (let j = 0; j < lineArray.length; j++) { let inkScaleX = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleX; let inkScaleY = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleY; let inkStrokeXArray = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData.map(coord => coord.X).map(doc=>doc*inkScaleX); let inkStrokeYArray = (inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData.map(coord => coord.Y).map(doc=>doc*inkScaleY); //console.log(inkingStrokeArray.length) //console.log(lineArray.length) let minX: number = Math.min(...inkStrokeXArray); let minY: number = Math.min(...inkStrokeYArray); let startX = inkStrokeXArray[0] - minX + (lineArray[j]?.x as number); let startY = inkStrokeYArray[0] - minY + (lineArray[j]?.y as number); let endX = inkStrokeXArray[inkStrokeXArray.length - 1]- minX + (lineArray[j].x as number); let endY = inkStrokeYArray[inkStrokeYArray.length - 1]- minY + (lineArray[j].y as number); if (this.isPointInBox(rectangle, [startX, startY])) { for (let k = 0; k < rectangleArray.length; k++) { const rectangle2 = rectangleArray[k]; if (this.isPointInBox(rectangle2, [endX, endY]) && typeof rectangle.x === 'number' && typeof rectangle2.x === 'number') { diagramExists=true mermaidCode += Math.abs(rectangle.x) + this.getTextInBox(rectangle, textArray) + '---' + Math.abs(rectangle2.x) + this.getTextInBox(rectangle2, textArray) + ';'; } } } } } } } DocumentManager.Instance.AddViewRenderedCb(this.Document, (docViewForYourCollection) => { if (docViewForYourCollection && docViewForYourCollection.ComponentView) { if (docViewForYourCollection.ComponentView.addDocument&&docViewForYourCollection.ComponentView.removeDocument) { let docArray: Doc[] = DocListCast(this.Document.data); docArray=docArray.filter(doc => doc.type == 'rich text') let mermaidCodeDoc = docArray.filter(doc => (doc.text as RichTextField).Text == 'mermaidCodeTitle') if(mermaidCodeDoc[0]){ if(diagramExists){ mermaidCodeDoc[0].title=mermaidCode } else{ mermaidCodeDoc[0].title="" } } } } }); let docArray: Doc[] = DocListCast(this._props.Document.data) //console.log(mermaidCode) } getTextInBox = (box: Doc, richTextArray: Doc[]): string => { for (let i = 0; i < richTextArray.length; i++) { let textDoc = richTextArray[i]; if (typeof textDoc.x === 'number' && typeof textDoc.y === 'number' && typeof box.x === 'number' && typeof box.height === 'number' && typeof box.width === 'number' && typeof box.y === 'number') { if (textDoc.x > box.x && textDoc.x < box.x + box.width && textDoc.y > box.y && textDoc.y < box.y + box.height) { if (box.title == 'rectangle') { return '(' + (textDoc.text as RichTextField)?.Text + ')'; } if (box.title == 'circle') { return '((' + (textDoc.text as RichTextField)?.Text + '))'; } } } } return '( )'; }; isPointInBox = (box: Doc, line: number[]): boolean => { if (typeof line[0] === 'number' && typeof box.x === 'number' && typeof box.width === 'number' && typeof box.height === 'number' && typeof box.y === 'number' && typeof line[1] === 'number') { return line[0] < box.x + box.width && line[0] > box.x && line[1] > box.y && line[1] < box.y + box.height; } else { return false; } }; render() { return (
{this.mermaidCode ? (
) : (
{this.loading ?
:
{this.errorMessage ? this.errorMessage : 'Insert prompt to generate diagram'}
}
)}
); } }