From 8f388d6f7cba5964547fd6a401b4a6c661cf76dc Mon Sep 17 00:00:00 2001
From: Zachary Zhang <zacharyzhang7@gmail.com>
Date: Tue, 30 Apr 2024 14:55:36 -0400
Subject: ui changes for ai mermaid

---
 src/client/views/nodes/DiagramBox.tsx | 192 ++++++++++++++++++----------------
 1 file changed, 100 insertions(+), 92 deletions(-)

(limited to 'src/client/views/nodes/DiagramBox.tsx')

diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx
index 64d0d6d78..902134fb7 100644
--- a/src/client/views/nodes/DiagramBox.tsx
+++ b/src/client/views/nodes/DiagramBox.tsx
@@ -12,18 +12,30 @@ 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';
 
 @observer
 export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(DiagramBox, fieldKey);
     }
+    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+    private _dragRef = React.createRef<HTMLDivElement>();
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         //this.chartContent = 'flowchart LR;A-->B:::wide;B-->C:::wide; B-->D[hello];';
     }
 
+    @observable inputValue = '';
+    @observable loading = false;
+    @observable errorMessage = '';
+    @observable mermaidCode = '';
+
+    @action handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this.inputValue = e.target.value;
+    };
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         mermaid.initialize({
@@ -31,112 +43,91 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
             startOnLoad: true,
             flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
         });
+    }
+    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);
+    };
 
-        const renderMermaid = async (str: string) => {
-            try {
-                const { svg, bindFunctions } = await mermaidDiagram(str);
-                return { svg, bindFunctions };
-            } catch (error) {
-                console.error('Error rendering mermaid diagram:', error);
-                return { svg: '', bindFunctions: undefined };
-            }
-        };
-        const mermaidDiagram = async (str: string) => {
-            return await mermaid.render('graph' + Date.now(), str);
-        };
-        async function renderMermaidAsync(this: DiagramBox) {
-            try {
-                const mermaidCode: string = await this.createMermaidCode();
-                const { svg, bindFunctions } = await renderMermaid(mermaidCode);
-                const dashDiv = document.getElementById('dashDiv');
-                if (dashDiv) {
-                    dashDiv.innerHTML = svg;
-                    if (bindFunctions) {
-                        bindFunctions(dashDiv);
-                    }
+    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);
             }
+        } catch (error) {
+            console.error('Error rendering Mermaid:', error);
         }
-
-        renderMermaidAsync.call(this);
     }
+    @action handleRenderClick = () => {
+        // Call the GPT model and get the HTML output
+        // const modelOutput = getHtmlOutput(this.inputValue);
+        // this.htmlCode = modelOutput;
+        this.generateMermaidCode();
+    };
+    @action generateMermaidCode=async()=>{
+        console.log('Generating Mermaid Code');
+        this.loading = true;
 
-    askGPT = async (): Promise<string | undefined> => {
-        try {
-            let notes = 'mermaid syntax for pie charts do not include %';
-            let text = notes + (DocListCast(this.Document.data)[0].text as RichTextField)?.Text;
-            if (!text) {
-                console.error('Text extraction failed');
-                return;
-            }
-
-            let res = await gptAPICall(text, GPTCallType.MERMAID);
-            if (!res) {
-                console.error('GPT call failed');
-                return;
+        let res = await gptAPICall(this.inputValue, 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.log(res)
-                return this.removeWords(res);
+                console.error('GPT call succeeded but invalid html; please try again.');
+                this.errorMessage = 'GPT call succeeded but invalid html; please try again.';
             }
-        } catch (err) {
-            console.error('Error:', err);
-            return;
         }
+        this.renderMermaidAsync.call(this,this.removeWords(this.mermaidCode));
+        this.loading = false;
+    }
+    isValidCode = (html: string) => {
+        return true;
     };
 
+
     removeWords(inputStr:string) {
-        let presetArray=["pie","flowchart","diagramChart","graph TD","flowchart TD","flowchart LR","graph LR"]
-        const lines = inputStr.split('\n');
-        let foundPresetWord = false;
-        let result = '';
-    
-        for (const line of lines) {
-            if (!foundPresetWord) {
-                for (const word of presetArray) {
-                    if (line.toLowerCase().includes(word.toLowerCase())) {
-                        foundPresetWord = true;
-                        result += line + '\n';
-                        break;
-                    }
-                }
-            } else {
-                result += line + '\n';
-            }
-        }
-    
-        return result;
+        inputStr=inputStr.replace("```mermaid","")
+        return inputStr.replace("```","");
     }
+
     
     createMermaidCode = async (): Promise<string> => {
-        let mermaidCode = '';
-        let docArray: Doc[] = DocListCast(this.Document.data);
-        if (docArray.length === 1) {
-            if (docArray[0].type == 'rich text') {
-                const gptResponse = await this.askGPT();
-                console.log(gptResponse);
-                if (gptResponse) {
-                    mermaidCode = gptResponse;
-                }
-            }
-        } else {
-            let mermaidCode = 'graph LR;';
-            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');
-                for (let i = 0; i < rectangleArray.length; i++) {
-                    const rectangle = rectangleArray[i];
-                    for (let j = 0; j < lineArray.length; j++) {
-                        const line = lineArray[j];
-                        if (this.isLineInFirstBox(rectangle, line)) {
-                            for (let k = 0; k < rectangleArray.length; k++) {
-                                const rectangle2 = rectangleArray[k];
-                                if (this.isLineInSecondBox(rectangle2, line) && typeof rectangle.x === 'number' && typeof rectangle2.x === 'number') {
-                                    mermaidCode += Math.abs(rectangle.x) + this.getTextInBox(rectangle, textArray) + '---' + Math.abs(rectangle2.x) + this.getTextInBox(rectangle2, textArray) + ';';
-                                }
+        let mermaidCode = 'graph LR;';
+        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');
+            for (let i = 0; i < rectangleArray.length; i++) {
+                const rectangle = rectangleArray[i];
+                for (let j = 0; j < lineArray.length; j++) {
+                    const line = lineArray[j];
+                    if (this.isLineInFirstBox(rectangle, line)) {
+                        for (let k = 0; k < rectangleArray.length; k++) {
+                            const rectangle2 = rectangleArray[k];
+                            if (this.isLineInSecondBox(rectangle2, line) && typeof rectangle.x === 'number' && typeof rectangle2.x === 'number') {
+                                mermaidCode += Math.abs(rectangle.x) + this.getTextInBox(rectangle, textArray) + '---' + Math.abs(rectangle2.x) + this.getTextInBox(rectangle2, textArray) + ';';
                             }
                         }
                     }
@@ -179,6 +170,23 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     };
 
     render() {
-        return <div id="dashDiv" className="diagramBox"></div>;
+        console.log(this.loading)
+        return(
+        <div ref={this._ref} className="DIYNodeBox">
+            <div ref={this._dragRef} className="DIYNodeBox-wrapper">
+                <div className="search-bar">
+                    <input type="text" value={this.inputValue} onChange={this.handleInputChange} />
+                    <button onClick={this.handleRenderClick}>Generate</button>
+                </div>
+                <div className="content">
+                    {this.mermaidCode ? (
+                        <div id="dashDiv" className="diagramBox"></div>
+                    ) : (
+                        <div>{this.loading ? <div className="loading-circle"></div> : <div>{this.errorMessage ? this.errorMessage : 'Insert prompt to generate diagram'}</div>}</div>
+                    )}
+                </div>
+            </div>
+        </div>
+        );
     }
 }
-- 
cgit v1.2.3-70-g09d2

