diff options
author | Zachary Zhang <zacharyzhang7@gmail.com> | 2024-04-04 13:47:40 -0400 |
---|---|---|
committer | Zachary Zhang <zacharyzhang7@gmail.com> | 2024-04-04 13:47:40 -0400 |
commit | 1809abeb1cb837ef9d43d19b7cf286b33ad8789d (patch) | |
tree | dc3d3773b667d84f59baeeca6e9bfdeea89e50f0 /src | |
parent | 427406dc60f0f037d69e0992fbb3205476daf89e (diff) |
add ai mermaids
Diffstat (limited to 'src')
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 6 | ||||
-rw-r--r-- | src/client/views/nodes/DiagramBox.tsx | 207 |
2 files changed, 128 insertions, 85 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index fb51278ae..49c56f04e 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -4,6 +4,7 @@ enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', + MERMAID='mermaid' } type GPTCallOpts = { @@ -17,8 +18,10 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, + mermaid:{model:'gpt-3.5-turbo-instruct',maxTokens:256,temp:0,prompt:"Write this in mermaid code (keep in note thatPut this at the end of the code to add colors heres an example and be very care about the order:pie title Example Pie Chart \"Red\" : 25 \"Blue\" : 75%%{init: {'theme': 'base', 'themeVariables': { 'pie1': '#0000FF', 'pie2': '#FF0000'}}}%%): "} }; + /** * Calls the OpenAI API. * @@ -30,7 +33,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration: ClientOptions = { - apiKey: process.env.OPENAI_KEY, + apiKey: "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", dangerouslyAllowBrowser: true, }; const openai = new OpenAI(configuration); @@ -39,6 +42,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { max_tokens: opts.maxTokens, temperature: opts.temp, prompt: `${opts.prompt}${inputText}`, + }); return response.choices[0].text; } catch (err) { diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 3b9e9d952..64d0d6d78 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -1,4 +1,4 @@ -import { makeObservable, observable } from 'mobx'; +import { makeObservable, observable, action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; @@ -10,10 +10,11 @@ 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'; @observer export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DiagramBox, fieldKey); } @@ -26,7 +27,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im componentDidMount() { this._props.setContentViewBox?.(this); mermaid.initialize({ - securityLevel: "loose", + securityLevel: 'loose', startOnLoad: true, flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' }, }); @@ -36,110 +37,148 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im const { svg, bindFunctions } = await mermaidDiagram(str); return { svg, bindFunctions }; } catch (error) { - console.error("Error rendering mermaid diagram:", error); - return { svg: "", bindFunctions: undefined }; + console.error('Error rendering mermaid diagram:', error); + return { svg: '', bindFunctions: undefined }; } }; const mermaidDiagram = async (str: string) => { - return await mermaid.render("graph" + Date.now(), str); + return await mermaid.render('graph' + Date.now(), str); }; - renderMermaid(this.createMermaidCode()).then(({ svg, bindFunctions }) => { - const dashDiv = document.getElementById('dashDiv'); - if (dashDiv) { - dashDiv.innerHTML = svg; - if (bindFunctions) { - bindFunctions(dashDiv); + 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); + } } + } catch (error) { + console.error('Error rendering Mermaid:', error); } - }); + } + + renderMermaidAsync.call(this); } - createMermaidCode = (): string => { - 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)+ ';'; + + 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; + } else { + console.log(res) + return this.removeWords(res); + } + } catch (err) { + console.error('Error:', err); + return; + } + }; + + 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; + } + + 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) + ';'; + } } } } } } } - console.log(mermaidCode); + //console.log(mermaidCode); return 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+")" + }; + + 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+"))" + if (box.title == 'circle') { + return '((' + (textDoc.text as RichTextField)?.Text + '))'; } } } } - return "( )"; - } - isLineInFirstBox=(box: Doc,line: Doc):boolean=>{ - if( - typeof line.x === 'number' && - typeof box.x === 'number' && - typeof box.width === 'number' && - typeof box.height === 'number' && - typeof box.y === 'number' && - typeof line.y === 'number') { - return line.x < box.x + box.width + (box.width + box.x) * 0.1 && - line.x > box.x && - line.y > box.y && - line.y < box.y + box.height - } - else{ + return '( )'; + }; + isLineInFirstBox = (box: Doc, line: Doc): boolean => { + if (typeof line.x === 'number' && typeof box.x === 'number' && typeof box.width === 'number' && typeof box.height === 'number' && typeof box.y === 'number' && typeof line.y === 'number') { + return line.x < box.x + box.width + (box.width + box.x) * 0.1 && line.x > box.x && line.y > box.y && line.y < box.y + box.height; + } else { return false; } - } - isLineInSecondBox=(box:Doc,line:Doc):boolean=>{ - if ( - typeof line.x === 'number' && - typeof line.width === 'number' && - typeof box.x === 'number' && - typeof box.width === 'number' && - typeof box.y === 'number' && - typeof box.height === 'number' && - typeof line.y === 'number') { - return line.x + line.width > box.x - (box.x - box.width) * 0.1 && - line.x + line.width < box.x + box.width && - line.y > box.y && - line.y < box.y + box.height; - } - else{ + }; + isLineInSecondBox = (box: Doc, line: Doc): boolean => { + if (typeof line.x === 'number' && typeof line.width === 'number' && typeof box.x === 'number' && typeof box.width === 'number' && typeof box.y === 'number' && typeof box.height === 'number' && typeof line.y === 'number') { + return line.x + line.width > box.x - (box.x - box.width) * 0.1 && line.x + line.width < box.x + box.width && line.y > box.y && line.y < box.y + box.height; + } else { return false; } - } + }; + render() { - return ( - <div id="dashDiv"></div> - ); + return <div id="dashDiv" className="diagramBox"></div>; } } - |