This commit is contained in:
zsviczian
2023-11-26 18:38:31 +01:00
parent 8b1daed0ef
commit 9f8a9bfa8a
54 changed files with 1211 additions and 397 deletions

View File

@@ -1,7 +1,5 @@
/*
Based on https://github.com/SawyerHood/draw-a-ui
<iframe width="560" height="315" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg)
```js*/
@@ -32,7 +30,7 @@ const instructions = {
const defaultSystemPrompts = {
"Wireframe to code": `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
"Challenge my thinking": `The user will provide you with a screenshot of a whiteboard covering ideas about a subject. Your task is to understand the topic of the whiteboard, to give it a title, and then to identify ideas, web links, images, and any additional content that challenges, disputes, contradicts what is on the whiteboard, plus ideas that extend, add, builds on, and takes the thinking of the user further.`
"Challenge my thinking": `The user will provide you with a screenshot of a whiteboard. Your task is to generate a mermaid graph based on the whiteboard and return the resulting mermaid code in a codeblock. The whiteboard will cover ideas about a subject. On the mindmap identify ideas that challenge, dispute, and contradict what is on the whiteboard, as well as also include ideas that extend, add-to, build-on, and takes the thinking of the user further. Use the graph diagram type. Return a single message containing only the mermaid diagram in a codeblock. Avoid the use of () parenthesis in the mermaid script.`
}
const OPENAI_API_KEY = ea.plugin.settings.openAIAPIToken;
@@ -65,7 +63,7 @@ const getRequestObjFromSelectedElements = async (view) => {
new Notice ("Aborting because there is nothing selected.",4000);
return;
}
ea.copyViewElementsToEAforEditing(viewElements);
ea.copyViewElementsToEAforEditing(viewElements, true);
const bb = ea.getBoundingBox(viewElements);
const size = (bb.width*bb.height);
const minRatio = Math.sqrt(360000/size);
@@ -226,7 +224,7 @@ configModal.onOpen = () => {
dirty = true;
agentTask = value;
textArea.setValue(allSystemPrompts[value]);
promptTitle.setValue(value);
//promptTitle.setValue(value);
});
})

View File

@@ -1,109 +1,61 @@
/*
Based on https://github.com/SawyerHood/draw-a-ui
<iframe width="560" height="315" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg)
```js*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.2")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.4")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["OPENAI_API_KEY"]) {
// --------------------------------------
// Initialize values and settings
// --------------------------------------
let settings = ea.getScriptSettings();
if(!settings["Custom System Prompts"]) {
settings = {
"OPENAI_API_KEY" : {
value: "",
description: `Get your api key at <a href="https://platform.openai.com/">https://platform.openai.com/</a><br>⚠️ Note that the gpt-4-vision-preview
requires a minimum of $5 credit on your account.`
},
"FOLDER" : {
value: "GPTPlayground",
description: `The folder in your vault where you want to store generated html pages`
},
"FILENAME": {
value: "page",
description: `The base name of the html file that will be created. Each time you run the script
a new file will be created using the following pattern "filename_i" where i is a counter`
}
"Custom System Prompts" : {},
"Agent's Task": "Wireframe to code",
"Output Type": "html"
};
await ea.setScriptSettings(settings);
}
const OPENAI_API_KEY = settings["OPENAI_API_KEY"].value;
const FOLDER = settings["FOLDER"].value;
const FILENAME = settings["FILENAME"].value;
const instructions = {
"html": "Turn this into a single html file using tailwind. Return a single message containing only the html file in a codeblock.",
"mermaid": "Challenge my thinking. Return a single message containing only the mermaid diagram in a codeblock."
}
if(OPENAI_API_KEY==="") {
new Notice("Please set an OpenAI API key in plugin settings");
const defaultSystemPrompts = {
"Wireframe to code": `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
"Challenge my thinking": `The user will provide you with a screenshot of a whiteboard. Your task is to generate a mermaid graph based on the whiteboard and return the resulting mermaid code in a codeblock. The whiteboard will cover ideas about a subject. On the mindmap identify ideas that challenge, dispute, and contradict what is on the whiteboard, as well as also include ideas that extend, add-to, build-on, and takes the thinking of the user further. Use the graph diagram type. Return a single message containing only the mermaid diagram in a codeblock. Avoid the use of () parenthesis in the mermaid script.`
}
const OPENAI_API_KEY = ea.plugin.settings.openAIAPIToken;
if(!OPENAI_API_KEY || OPENAI_API_KEY === "") {
new Notice("You must first configure your API key in Excalidraw Plugin Settings");
return;
}
const systemPrompt = `You are an expert tailwind developer. A user will provide you with a
low-fidelity wireframe of an application and you will return
a single html file that uses tailwind to create the website.
Use creative license to make the application more fleshed out. Write the necessary javascript code.
If you need to insert an image, use placehold.co to create a placeholder image.
Respond only with the html file.`;
const post = async (request) => {
const { image } = await request.json();
const body = {
model: "gpt-4-vision-preview",
max_tokens: 4096,
messages: [
{
role: "system",
content: systemPrompt,
},
{
role: "user",
content: [
{
type: "image_url",
image_url: image,
},
"Turn this into a single html file using tailwind.",
],
},
],
};
let json = null;
try {
const resp = await requestUrl ({
url: "https://api.openai.com/v1/chat/completions",
method: "post",
contentType: "application/json",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
throw: false
});
json = resp.json;
} catch (e) {
console.log(e);
}
return json;
}
const blobToBase64 = async (blob) => {
const arrayBuffer = await blob.arrayBuffer()
const bytes = new Uint8Array(arrayBuffer)
var binary = '';
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
let customSystemPrompts = settings["Custom System Prompts"];
let agentTask = settings["Agent's Task"];
let outputType = settings["Output Type"];
if (!Object.keys(instructions).includes(outputType)) {
outputType = "html";
}
let allSystemPrompts = {
...defaultSystemPrompts,
...customSystemPrompts
};
if(!allSystemPrompts.hasOwnProperty(agentTask)) {
agentTask = Object.keys(defaultSystemPrompts)[0];
}
// --------------------------------------
// Generate image
// --------------------------------------
const getRequestObjFromSelectedElements = async (view) => {
await view.forceSave(true);
const viewElements = ea.getViewSelectedElements();
@@ -111,7 +63,7 @@ const getRequestObjFromSelectedElements = async (view) => {
new Notice ("Aborting because there is nothing selected.",4000);
return;
}
ea.copyViewElementsToEAforEditing(viewElements);
ea.copyViewElementsToEAforEditing(viewElements, true);
const bb = ea.getBoundingBox(viewElements);
const size = (bb.width*bb.height);
const minRatio = Math.sqrt(360000/size);
@@ -123,83 +75,217 @@ const getRequestObjFromSelectedElements = async (view) => {
? 1/maxRatio
: 1
);
const loader = ea.getEmbeddedFilesLoader(false);
const exportSettings = {
withBackground: true,
withTheme: true,
};
const img =
await ea.createPNG(
const dataURL =
await ea.createPNGBase64(
null,
scale,
exportSettings,
loader,
"light",
);
const dataURL = `data:image/png;base64,${await blobToBase64(img)}`;
ea.clear();
return { json: async () => ({ image: dataURL }) }
return { image: dataURL };
}
const extractHTMLFromString = (result) => {
if(!result) return null;
const start = result.indexOf('```html\n');
const end = result.lastIndexOf('```');
if (start !== -1 && end !== -1) {
const htmlString = result.substring(start + 8, end);
return htmlString.trim();
// --------------------------------------
// Submit Prompt
// --------------------------------------
const run = async () => {
const requestObject = await getRequestObjFromSelectedElements(ea.targetView);
requestObject.systemPrompt = allSystemPrompts[agentTask];
requestObject.instruction = instructions[outputType];
const spinner = await ea.convertStringToDataURL(`
<html><head><style>
html, body {width: 100%; height: 100%; color: ${ea.getExcalidrawAPI().getAppState().theme === "dark" ? "white" : "black"};}
body {display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; overflow: hidden;}
.Spinner {display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto;}
.Spinner svg {animation: rotate 1.6s linear infinite; transform-origin: center center; width: 40px; height: 40px;}
.Spinner circle {stroke: currentColor; animation: dash 1.6s linear 0s infinite; stroke-linecap: round;}
@keyframes rotate {100% {transform: rotate(360deg);}}
@keyframes dash {0% {stroke-dasharray: 1, 300; stroke-dashoffset: 0;} 50% {stroke-dasharray: 150, 300; stroke-dashoffset: -200;} 100% {stroke-dasharray: 1, 300; stroke-dashoffset: -280;}}
</style></head><body>
<div class="Spinner">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="46" stroke-width="8" fill="none" stroke-miter-limit="10"/>
</svg>
</div>
<div>Generating...</div>
</body></html>`);
const bb = ea.getBoundingBox(ea.getViewSelectedElements());
const id = ea.addEmbeddable(bb.topX+bb.width+100,bb.topY-(720-bb.height)/2,550,720,spinner);
await ea.addElementsToView(false,true);
ea.clear();
const embeddable = ea.getViewElements().filter(el=>el.id===id);
ea.copyViewElementsToEAforEditing(embeddable);
const els = ea.getViewSelectedElements();
ea.viewZoomToElements(false, els.concat(embeddable));
//Get result from GPT
const result = await ea.postOpenAI(requestObject);
const errorMessage = async () => {
const error = "Something went wrong! Check developer console for more.";
const errorDataURL = await ea.convertStringToDataURL(`
<html><head><style>
html, body {height: 100%;}
body {display: flex; flex-direction: column; align-items: center; justify-content: center; color: red;}
h1, h3 {margin-top: 0;margin-bottom: 0.5rem;}
</style></head><body>
<h1>Error!</h1>
<h3>${error}</h3>
</body></html>`);
new Notice (error);
ea.getElement(id).src = errorDataURL;
ea.addElementsToView(false,true);
}
return null;
}
const checkAndCreateFolder = async (folderpath) => {
const vault = app.vault;
folderpath = ea.obsidian.normalizePath(folderpath);
const folder = vault.getAbstractFileByPathInsensitive(folderpath);
if (folder) {
return folder;
if(!result?.json?.hasOwnProperty("choices")) {
await errorMessage();
return;
}
return await vault.createFolder(folderpath);
}
const getNewUniqueFilepath = (filename, folderpath) => {
let fname = ea.obsidian.normalizePath(`${folderpath}/${filename}.html`);
let file = app.vault.getAbstractFileByPath(fname);
let i = 0;
while (file) {
fname = ea.obsidian.normalizePath(`${folderpath}/${filename}_${i++}.html`);
file = app.vault.getAbstractFileByPath(fname);
}
return fname;
}
const requestObject = await getRequestObjFromSelectedElements(ea.targetView);
const result = await post(requestObject);
const errorMessage = () => {
new Notice ("Something went wrong! Check developer console for more.");
console.log(result);
let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
if(!content) {
await errorMessage();
return;
}
switch(outputType) {
case "html":
ea.getElement(id).link = await ea.convertStringToDataURL(content);
ea.addElementsToView(false,true);
break;
case "mermaid":
if(content.startsWith("mermaid")) {
content = content.replace(/^mermaid/,"").trim();
}
ea.getElement(id).isDeleted = true;
try {
await ea.addMermaid(content);
} catch (e) {
ea.addText(0,0,content);
}
ea.targetView.currentPosition = {x: bb.topX+bb.width+100, y: bb.topY-bb.height-100};
await ea.addElementsToView(true, false);
ea.clear();
if(content.startsWith("graph LR") || content.startsWith("graph TD")) {
try {
if(content.startsWith("graph LR") || content.startsWith("flowchart LR")) {
content = content.replaceAll("graph LR", "graph TD");
content = content.replaceAll("flowchart LR", "flowchart TD");
await ea.addMermaid(content);
} else if (content.startsWith("graph TD") || content.startsWith("flowchart TD")) {
content = content.replaceAll("graph TD", "graph LR");
content = content.replaceAll("flowchart TD", "flowchart LR");
await ea.addMermaid(content);
}
} catch (e) {
ea.addText(0,0,content);
}
ea.targetView.currentPosition = {x: bb.topX-100, y: bb.topY + 100};
ea.addElementsToView(true, true);
}
break;
}
}
if(!result?.hasOwnProperty("choices")) {
errorMessage();
return;
// --------------------------------------
// User Interface
// --------------------------------------
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
const configModal = new ea.obsidian.Modal(app);
let dirty=false;
configModal.onOpen = () => {
const contentEl = configModal.contentEl;
contentEl.createEl("h1", {text: "ExcaliAI"});
let textArea, promptTitle;
new ea.obsidian.Setting(contentEl)
.setName("Select Prompt")
.addDropdown(dropdown=>{
Object.keys(allSystemPrompts).forEach(key=>dropdown.addOption(key,key));
dropdown
.setValue(agentTask)
.onChange(value => {
dirty = true;
agentTask = value;
textArea.setValue(allSystemPrompts[value]);
//promptTitle.setValue(value);
});
})
new ea.obsidian.Setting(contentEl)
.setName("Select response type")
.addDropdown(dropdown=>{
Object.keys(instructions).forEach(key=>dropdown.addOption(key,key));
dropdown
.setValue(outputType)
.onChange(value => {
dirty = true;
outputType = value;
});
})
contentEl.createEl("h3", {text: "Customize Prompt"});
/* const titleSetting = new ea.obsidian.Setting(contentEl)
.addText(text => {
promptTitle = text;
text.inputEl.style.width = "100%";
text.setValue(agentTask);
})
titleSetting.nameEl.style.display = "none";
titleSetting.descEl.style.display = "none";
titleSetting.infoEl.style.display = "none";
*/
const textAreaSetting = new ea.obsidian.Setting(contentEl)
.addTextArea(text => {
textArea = text;
text.inputEl.style.minHeight = "10em";
text.inputEl.style.minWidth = "400px";
text.inputEl.style.width = "100%";
text.setValue(allSystemPrompts[agentTask]);
text.onChange(value => {
// dirty = true;
//Needs further work
allSystemPrompts[agentTask] = value;
})
})
textAreaSetting.nameEl.style.display = "none";
textAreaSetting.descEl.style.display = "none";
textAreaSetting.infoEl.style.display = "none";
new ea.obsidian.Setting(contentEl)
.addButton(button =>
button
.setButtonText("Run")
.onClick((event)=>{
setTimeout(()=>{run()},500); //Obsidian crashes otherwise, likely has to do with requesting an new frame for react
configModal.close();
})
);
}
const htmlContent = extractHTMLFromString(result.choices[0]?.message?.content);
if(!htmlContent) {
errorMessage();
return;
configModal.onClose = () => {
if(dirty) {
settings["Custom System Prompts"] = customSystemPrompts;
settings["Agent's Task"] = agentTask;
settings["Output Type"] = outputType;
ea.setScriptSettings(settings);
}
}
const folder = await checkAndCreateFolder(FOLDER);
const filepath = getNewUniqueFilepath(FILENAME,folder.path);
const file = await app.vault.create(filepath,htmlContent);
const url = app.vault.adapter.getFilePath(file.path).toString();
const bb = ea.getBoundingBox(ea.getViewSelectedElements());
ea.addEmbeddable(bb.topX+bb.width+40,bb.topY,600,800,url);
await ea.addElementsToView(false,true);
ea.viewZoomToElements([]);
configModal.open();

File diff suppressed because one or more lines are too long

View File

@@ -356,14 +356,13 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/ExcaliAI.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/ExcaliAI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/ExcaliAI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Various AI features based on GPT Vision.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
## GPT Draw-a-UI
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
## Hardware Eraser Support