diff options
author | Zachary Zhang <zacharyzhang7@gmail.com> | 2024-04-30 14:55:36 -0400 |
---|---|---|
committer | Zachary Zhang <zacharyzhang7@gmail.com> | 2024-04-30 14:55:36 -0400 |
commit | 8f388d6f7cba5964547fd6a401b4a6c661cf76dc (patch) | |
tree | 1f42cea31734a2fcbf7382e5319d5a1cc73509dc /src | |
parent | 1809abeb1cb837ef9d43d19b7cf286b33ad8789d (diff) |
ui changes for ai mermaid
Diffstat (limited to 'src')
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 21 | ||||
-rw-r--r-- | src/client/views/nodes/DiagramBox.scss | 88 | ||||
-rw-r--r-- | src/client/views/nodes/DiagramBox.tsx | 192 |
3 files changed, 202 insertions, 99 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 49c56f04e..027c10e28 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,4 +1,5 @@ import { ClientOptions, OpenAI } from 'openai'; +import { ChatCompletionMessageParam } from 'openai/resources'; enum GPTCallType { SUMMARY = 'summary', @@ -18,11 +19,11 @@ 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'}}}%%): "} + mermaid:{model:'gpt-4-turbo',maxTokens:2048,temp:0,prompt:"Write this in mermaid code and only give me the mermaid code (Heres an example of changing color of a pie chart to help you pie title Example \"Red\": 20 \"Blue\": 50 \"Green\": 30 %%{init: {'theme': 'base', 'themeVariables': {'pie1': '#0000FF', 'pie2': '#00FF00', 'pie3': '#FF0000'}}}%% keep in mind that pie1 is the highest since its sorted in descending order. Heres an example of a mindmap: mindmap root((mindmap)) Origins Long history ::icon(fa fa-book) Popularisation British popular psychology author Tony Buzan Research On effectivness<br/>and features On Automatic creation Uses Creative techniques Strategic planning Argument mapping Tools Pen and paper Mermaid. "} }; -/** +/**` * Calls the OpenAI API. * * @param inputText Text to process @@ -37,14 +38,20 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { dangerouslyAllowBrowser: true, }; const openai = new OpenAI(configuration); - const response = await openai.completions.create({ + + let messages: ChatCompletionMessageParam[] = [ + { role: 'system', content: opts.prompt }, + { role: 'user', content: inputText }, + ]; + + const response = await openai.chat.completions.create({ model: opts.model, - max_tokens: opts.maxTokens, + messages: messages, temperature: opts.temp, - prompt: `${opts.prompt}${inputText}`, - + max_tokens: opts.maxTokens, }); - return response.choices[0].text; + const content = response.choices[0].message.content; + return content; } catch (err) { console.log(err); return 'Error connecting with API.'; 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> + ); } } |