diff options
Diffstat (limited to 'src/client/views')
| -rw-r--r-- | src/client/views/nodes/DiagramBox.scss | 88 | ||||
| -rw-r--r-- | src/client/views/nodes/DiagramBox.tsx | 192 |
2 files changed, 188 insertions, 92 deletions
diff --git a/src/client/views/nodes/DiagramBox.scss b/src/client/views/nodes/DiagramBox.scss index e69de29bb..d2749f1ad 100644 --- a/src/client/views/nodes/DiagramBox.scss +++ b/src/client/views/nodes/DiagramBox.scss @@ -0,0 +1,88 @@ +.DIYNodeBox { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .DIYNodeBox-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .DIYNodeBox { + /* existing code */ + + .DIYNodeBox-iframe { + height: 100%; + width: 100%; + border: none; + + } + } + + .search-bar { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: 10px; + + input[type="text"] { + flex: 1; + margin-right: 10px; + } + + button { + padding: 5px 10px; + } + } + + .content { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width:100%; + height:100%; + .diagramBox{ + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width:100%; + height:100%; + svg{ + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width:100%; + height:100%; + } + } + } + + .loading-circle { + position: relative; + width: 50px; + height: 50px; + border-radius: 50%; + border: 3px solid #ccc; + border-top-color: #333; + animation: spin 1s infinite linear; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + } +}
\ No newline at end of file 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> + ); } } |
