【AI JUST AI】Stable Disffusion | 配合Chrome插件,与Notion API完美联动
- 第一步、Stable Diffusion 链接 CMS
- 开发Chrome插件
- 在合适的位置增加一个`发送至Notion`的按钮
- 编写按钮的逻辑部分
- 使用GitHub作为图床
- 图片上传 API
- 第二步,使用Chat GPT优化样式
Stable Diffusion是一个扩散模型,最早由德国CompVis发表并推出相关程式。后来AUTOMATIC1111开发了图形化界面:【stable diffusion web ui】,成为最多人使用的版本。下面简称SD WebUI。
简单来说,SD WebUI是能用AI技术绘图的开源软件,只要給定一組描述文字,AI就会画图出来;亦能模仿現有的图片,画出另一張图片。甚至給它一部分涂黑的圖片,AI也能按照你的意愿將图片填上适当的內容。除此之外还支援自行訓練模型加強绘制效果。
与其他的AI绘图软件相比,SD最大的优势在于:
- 可以免费在自己的服务器上跑,只要遵照Creative ML OpenRAIL-M授权条款,就可以无限次使用。
其中,SD WebUI支持Linux、Windows、MacOS系统,以及Nvidia/AMD/Apple Silicon M的GPU。其图形界面可通过局域网IP进行网页访问的,上手无难度,还有社群制作的中文界面。
不过需要注意的时,AI绘图十分吃GPU性能,以Windows系统为例,建议电脑操作系统在Windows10以上,需配置独立显卡为Nvidia GTX1050 Ti (VRAM 4GB)以上等級,RAM大于8GB。
但是Stable Diffusion生成的图像只能保存在本地,并没有一键保存到云端的功能,如果需要保存在云端,我还需要再从本地文件夹中将图片上传到云端。
且我们都生成了这么多美丽的图片,却没有一个系统的内容管理,俗称CMS(Content management system),如果我们进行了一个系统的内容管理,那我们可以看到他的效果如下:
你可以把它作为你的作品集。
如果我们将这个内容管理系统扩散开来,成为一个社区,所造成的影响巨大。任何人都可以简单方便地在本地将自己的内容分享到CMS中,AI By The People,AI For The People。
第一步、Stable Diffusion 链接 CMS
想要实现想要的效果,我们首先得先对原有的WebUI界面进行改造:
增加一个按钮,当我们点击它时,会将我们生成好的图片以及相关的参数上传对应的CMS中。
我们将使用Chrome插件对WebUI进行改造(后续可能会考虑直接进行Stable Diffusion WebUI的扩展开发),使用Notion作为我们的CMS(Notion作为CMS具有易用、灵活、协作和跨平台等优势,特别是其Database功能,不仅让你白嫖免费v数据库,而且界面美观,让你可以更加高效地管理你的内容)。
Chrome谷歌浏览器开发文档
Notion官网
话不多说,我们直接开始进入正题!
开发Chrome插件
首先,我们利用Chrome插件在我们的Stable Diffusion WebUI页面中选择一个合适的位置插入一个“发送到Notion”的按钮,如下图所示:
在合适的位置增加一个发送至Notion
的按钮
-
要实现这个效果,我们首先需要编写
manifest.json
:- 设置插件的基本信息
- 只在
127.0.0.1:7860
(Stable Diffusion WebUI默认地址)上运行 - 设置插件的权限
代码如下:
{ "manifest_version": 2, // 浏览器扩展清单文件的版本号,2 表示使用 Manifest V2 版本 "name": "Stable Diffusion To Notion", // 扩展程序的名称 "version": "1.0", // 扩展程序的版本号 "description": "将网页中的特定信息发送给Notion", // 扩展程序的描述 "content_scripts": [ // 注册要注入到浏览器当前页面的脚本 { "matches": [ // 注入的条件,数组里面的 URL 与当前页面 URL 匹配 "http://127.0.0.1:7860/*" // 要注入的页面 URL,这里匹配了所有以 http://127.0.0.1:7860/ 开头的 URL ], "js": [ // 注入的 JavaScript 文件,这里只有一个 content.js "content.js" ] } ], "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", // 安全策略 "background": { // 后台脚本 "scripts": [ // 执行的脚本文件,这里只有一个 background.js "background.js" ], "persistent": false // 是否常驻后台,false 表示不常驻 }, "browser_action": { // 扩展程序的浏览器操作区域,即点击图标后显示的弹出框 "default_popup": "popup.html" // 弹出框所需的 HTML 文件 }, "permissions": [ // 扩展程序需要的权限 "http://127.0.0.1:7860/", // 可以访问的 URL "activeTab", // 可以获取当前激活的标签页的信息 "tabs", // 可以获取所有标签页的信息 "storage", // 可以使用浏览器存储 API "https://api.notion.com/*" // 可以访问 Notion API 的 URL "https://api.github.com/*" // 可以访问 GitHub API 的 URL ] }
需要注意的是:
您在
manifest.json
中添加了content_security_policy
配置,该配置指定了扩展的内容安全策略。默认情况下,Chrome 扩展的内容脚本会在一个沙箱环境中运行,以防止不受信任的代码在扩展环境中执行,并且默认情况下不允许使用
eval()
等动态代码执行函数,以防止脚本注入攻击。 然而,在某些情况下,这可能会阻止一些正常的脚本行为。在您的情况下,您的
background.js
文件尝试使用chrome.runtime.sendMessage()
函数与您的content.js
文件进行通信,但是由于默认的内容安全策略阻止了eval()
函数的使用,因此该函数无法正常运行。 通过添加content_security_policy
配置并将script-src
指定为'unsafe-eval'
,您允许了动态代码执行函数的使用,因此chrome.runtime.sendMessage()
函数能够正常运行。需要注意的是,虽然在某些情况下禁用默认的内容安全策略可能是必要的,但这可能会导致您的扩展面临更大的安全风险。在使用
content_security_policy
配置时,请确保您仅允许必要的资源,并避免允许来自不受信任的源的代码执行。本回答由Chat GPT生成
-
编写
background.js
代码:- 后台监控
- 将
content.js
注入到页面中 - 监听来自
content.js
的消息
代码如下:
// 当插件安装时,输出一条信息到控制台 chrome.runtime.onInstalled.addListener(function () { console.log("Extension installed"); }); // 监听标签页更新事件 chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { // 如果当前标签页的 URL 以 http://127.0.0.1:7860/ 开头且加载完成 if (tab && tab.url && changeInfo.status == "complete" && tab.url.indexOf("http://127.0.0.1:7860/") == 0) { // 在当前标签页中注入 content.js 脚本 chrome.tabs.executeScript(tabId, { file: "content.js" }); console.log("插入content.js"); } else { console.log("未插入content.js"); } });
-
编写
conten.js
代码:-
从页面中获取需要发送的信息(这里主要指的是图片以及图片相关的参数)
-
将
按钮
插入至Stable Diffusion WebUI中合适位置,并获取Web中的同类型的ClassName,并套用在嵌入的按钮元素上获取页面元素位置:
右键,复制其JS路径,即:document.querySelector("body > gradio-app").shadowRoot.querySelector("#image_buttons_txt2img")
复制同类型的样式
代码如下:// 监听加载完毕的事件 window.addEventListener("load", function () { console.log("页面加载完毕,延时1s后开始插入button") // 创建一个按钮元素 const button = document.createElement("button"); // 延迟 1 秒钟执行按钮插入操作(等待页面加载完毕) setTimeout(function () { button.innerHTML = "发送到 Notion"; // 获取Web中的同类型的ClassName,并套用在嵌入的按钮元素上 button.className = "gr-button gr-button-lg gr-button-secondary"; //增加元素的title button.setAttribute("title", "将图像写入Notion"); // 将按钮添加到页面中的特定位置 const container = document.querySelector("body > gradio-app").shadowRoot.querySelector("#component-229") if (container) { container.appendChild(button); console.log("已经将button插入指定位置"); } else { console.log("未找到容器元素,将按钮添加到页面底部"); // 将按钮添加到页面底部 document.body.appendChild(button); } }, 1000); });
至此,我们就可以实现将“发送至Notion”按钮嵌入到Stable Diffusion WebUI中的效果了!
-
编写按钮的逻辑部分
外观效果有了,接下来,让我们来实现点击按钮的逻辑部分,即点击按钮后,我们生成好的图片以及相关的参数都会上传至Notion对应的数据库中
-
继续编写
content.js
代码- 增加获取页面数据的方法-
getInfoFromPage()
,获取包括img、prompt、negativePrompt和parameter信息
代码如下:
// 从页面中获取需要发送的信息 function getInfoFromPage() { // 在这里编写代码来获取信息 console.log("执行getInfoFromPage") const img = document.querySelector("body > gradio-app").shadowRoot.querySelector("#txt2img_gallery > div.overflow-y-auto.h-full.p-2.min-h-\\[350px\\].max-h-\\[55vh\\].xl\\:min-h-\\[450px\\] > div > button > img") console.log(img) const all_msg = document.querySelector("body > gradio-app").shadowRoot.querySelector("#html_info_txt2img > p"); let all_msg_str = all_msg.textContent.trim(); console.log(all_msg_str); // 匹配需要的信息,包括prompt、negativePrompt和parameter const regex = /^(?:([^<\n]*)(?:\nNegative prompt: ([^<\n]*))?)?\n(.+)$/m; const match = all_msg_str.match(regex); console.log(match); let prompt = ''; let negativePrompt = ''; let parameter = ''; if (match) { if (match[1].includes("Negative prompt: ")) { prompt = ''; negativePrompt = match[1] || ''; parameter = match[3] || ''; } else { prompt = match[1] || ''; negativePrompt = match[2] || ''; parameter = match[3] || ''; } console.log(prompt); console.log(negativePrompt); console.log(parameter); } else { console.log('No match found.'); } // 返回需要发送的信息 return { "img": img.src, "prompt": prompt, "negativePrompt": negativePrompt, "parameter": parameter } }
- 继续编写
content.js
代码
- 添加按钮点击事件的监听器,放在按钮创建完成之后
- 向后台脚本发送消息,指示其打开popup.html界面,并将
getInfoFromPage()
中的信息传递给它
代码如下:
// 延迟 1 秒钟执行按钮插入操作(等待页面加载完毕) setTimeout(function () { // 创建一个按钮元素 const button = document.createElement("button"); button.innerHTML = "发送到 Notion"; // 获取Web中的同类型的ClassName,并套用在嵌入的按钮元素上 button.className = "gr-button gr-button-lg gr-button-secondary"; //增加元素的title button.setAttribute("title", "将图像写入Notion"); // 将按钮添加到页面中的特定位置 const container = document.querySelector("body > gradio-app").shadowRoot.querySelector("#component-229") if (container) { container.appendChild(button); console.log("已经将button插入指定位置"); } else { console.log("未找到容器元素,将按钮添加到页面底部"); // 将按钮添加到页面底部 document.body.appendChild(button); } // 添加按钮点击事件的监听器 button.addEventListener("click", function () { // 从页面中获取需要发送的信息 const info = getInfoFromPage(); console.log(info) // 向后台脚本发送一个消息,指示它打开popup.html并将信息传递给它 chrome.runtime.sendMessage({ action: "open_popup", info: info }, function (response) { console.log(response); }); }); }, 1000);
- 编写
background.js
代码
- 监听来自content.js的消息
- 将信息传递给popup.html界面
代码如下:
// 监听来自content.js的消息 chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { // 如果请求是打开popup.html并传递信息 if (request.action === "open_popup") { // 创建一个新的窗口,打开popup.html chrome.windows.create({ url: chrome.extension.getURL("popup.html"), type: "popup", width: 600, height: 400 }, function (window) { // 在窗口加载完成后向它发送一个消息,将信息传递给它 chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (changeInfo.status === "complete") { chrome.tabs.sendMessage(tabId, { info: request.info }, function (response) { console.log(response); }); } }); }); } });
- 编写popup.html界面
- 设置基本样式,主要包含:确定发送至Notion -
sendToNotion按钮
,展示从页面中获取的数据
代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Stable Diffusion To Notion</title> <!-- 样式定义 --> <style> body { width: 400px; height: 400px; } #info { overflow: auto; height: 300px; } </style> <!-- 加载脚本文件 --> <script src="popup.js"></script> </head> <body> <!-- 标题 --> <h1>请确认以下信息发送到Notion?</h1> <!-- 发送到 Notion 按钮 --> <button id="sendToNotion">确定</button> <!-- 从页面中获取的信息 --> <h3>从页面中获取的信息:</h3> <div id="info_img">img</div> <div id="info_prompt">Prompt</div> <div id="info_negativePrompt">Negative Prompt</div> <div id="info_parameter">Parameter</div> </body> </html>
- 编写popup.js代码
在第3步,我们在background.js
中编写了一个监听事件,当popup.html加载完毕时,会将获取到的Stable Diffusion WebUI的页面数据info
发送给popup.js,因此我们这里需要编写一个监听事件监听数据接收,并将接收的数据渲染到popup.html界面中
-
编写
send2Notion(info)
方法,将popup.html渲染好的数据(包括img、prompt、negativePrompt和parameter)发送至Notion关于Notion API的详细使用,我们要学会看官方文档
监听sendToNotion按钮点击事件,当sendToNotion按钮被点击时,获取信息并调用将信息发送至Notion方法 - send2Notion(info)
代码如下:document.addEventListener('DOMContentLoaded', function () { console.log('Hello, world!'); // 监听发送到 Notion 按钮的点击事件 document.getElementById('sendToNotion').addEventListener('click', function () { // 获取信息 const info = { negativePrompt: document.getElementById('info_negativePrompt').textContent, parameter: document.getElementById('info_parameter').textContent, prompt: document.getElementById('info_prompt').textContent, }; console.log(info); // 发送信息到 Notion send2Notion(info); }); // 监听来自 background.js 的消息 chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { console.log(request); if (request.info) { // 找到要渲染的元素 var info_img = document.getElementById("info_img"); var info_negativePrompt = document.getElementById("info_negativePrompt"); var info_parameter = document.getElementById("info_parameter"); var info_prompt = document.getElementById("info_prompt"); // 将接收到的信息渲染到该元素中 info_img.textContent = request.info.img; info_negativePrompt.textContent = request.info.negativePrompt; info_parameter.textContent = request.info.parameter; info_prompt.textContent = request.info.prompt; sendResponse("message received"); } }); }); async function send2Notion(info) { console.log("data", info); let notion_api_key = "你的Notion API Key" let databaseID = "你的数据库ID" // 发送 POST 请求到 Notion API let dataResponse = await fetch('https://api.notion.com/v1/pages', { method: 'POST', headers: { 'Authorization': 'Bearer ' + notion_api_key, 'Notion-Version': '2022-06-28', 'Content-Type': 'application/json' }, referrerPolicy: "no-referrer-when-downgrade", mode: "same-origin", body: JSON.stringify({ "parent": { "database_id": databaseID }, "properties": { "Name": { "title": [ { "text": { "content": "Detail" } } ] }, "Prompt": { "rich_text": [ { "text": { "content": info.prompt } } ] }, "Negative Prompt": { "rich_text": [ { "text": { "content": info.negativePrompt } } ] }, "Parameter": { "rich_text": [ { "text": { "content": info.parameter } } ] } } }) }).then(response => response.json()); if (dataResponse) { // 显示弹窗 alert("信息已成功发送至 Notion!"); // 关闭 popup.html 窗口 window.close(); } else { // 发生错误,打印错误信息 console.log("发送信息至 Notion 时发生错误:", dataResponse); } }
至此,本地的Stable Diffusion WebUI已经可以通过上传至Notion按钮与Notion对应的数据库链接,这里给大家看一下实际效果:
- 增加获取页面数据的方法-
接下来我们再来看看数据库这边,
可以看到,数据确实添加进去了,但是没有图片!
原因很明显,我们没有上传图片,而且也Notion也不支持从外部上传图片到其数据库中,其只支持从外部链接读取文件(图片)。
因此我们需要使用外部的文件托管服务(图床),这里我们使用Github为我提供文件托管服务。
使用GitHub作为图床
关于如何使用GitHub API上传文件以及用GitHub做图床,可以参考这篇:
『转载』使用GitHub API上传文件及GitHub做图床 – 邱少羽梦
基于 Github API 的图床 Chrome 插件开发全纪录 - 掘金
图片上传 API
图片上传使用了 content API 的 create-a-file 接口,通过 PUT 发送一条文件内容为 base64 的请求到指定的仓库目录
这里着重圈出了必须把文件进行 base64 编码,否则接口调用将会出错。
在传输数据时,如果使用二进制方式传输,数据传输过程中可能会出现乱码或者数据丢失的问题,而Base64编码能够将二进制数据转换成ASCII字符,不会出现这样的问题,同时也能够减小数据大小,减小传输量。所以在调用API接口时,将图片转换为base64编码是常见手段。
如果是更新文件,需要先获取待更新文件的sha
更新文件的sha可以通过直接Get请求拿到,如下:
然后再切换成PUT,附带参数发送过去即可
使用APIPost7调试
在搭好图床以及了解好GitHub API后,我们要明确我们的业务需求:
- 选择图片和把图片转化成 base64
- 将图片的base64上传至GitHub
现在我们来开始搭建实现我们的业务需求:
-
选择图片和把图片转化成base64
这里我们首先使用在
popup.html
上编写相应的图片展示
代码如下:<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Stable Diffusion To Notion</title> <style> body { width: 400px; height: 400px; } #info { overflow: auto; height: 300px; } </style> <script src="popup.js"></script> </head> <body> <h1>请确认以下信息发送到Notion?</h1> <button id="sendToNotion">确定</button> <h3>从页面中获取的信息:</h3> <img id="info_img" /> <div id="info_prompt">Prompt</div> <div id="info_negativePrompt">Negative Prompt</div> <div id="info_parameter">Parameter</div> </body> </html>
然后在popup.js中编写代码获取其img元素,接着就是对这个img进行base64转换的操作
如何把文件进行base64编码?
可以使用 JavaScript 中的btoa()
方法将 “hello world” 转换为 Base64 编码。btoa()
方法将字符串转换为 Base64 编码字符串。以下是一个将 “hello world” 转换为 Base64 的示例代码:
const str = "hello world"; const base64 = btoa(str); console.log(base64); // 输出 "aGVsbG8gd29ybGQ="
在此示例中,
str
是要转换的字符串,base64
是转换后的 Base64 编码字符串。您可以使用console.log()
输出base64
。在我们的示例中,您也可以通过创建一个Image对象并将其设置为在popup.html中定义的img元素来获取图片数据并将其转换为base64编码。
以下是一个将图片文转换为 Base64 编码字符串的示例代码:
// 将图片解码至base64 let img2Notion_url = "" function translate() { // 创建一个Promise对象 return new Promise((resolve, reject) => { // 获取页面中的图片元素 const img = document.getElementById('info_img'); // 创建一个新的Image对象 const image = new Image(); // 设置Image对象的src属性为图片元素的src属性 image.src = img.src; // 分割图片src属性的字符串并获取图片的名称 const splitArray = image.src.split("/"); const img_name = splitArray[splitArray.length - 2] + "/" + splitArray[splitArray.length - 1]; console.log(img_name); // 构建图片在GitHub上的url地址 img2Notion_url = "https://github.com/Mr-KID-github/Stable-Diffusion-Open-Community/blob/main/images/" + img_name + "?raw=true" // 当图片加载完成后执行回调函数 image.onload = function () { // 创建一个canvas元素并设置其宽度和高度为图片的宽度和高度 const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; // 获取canvas的2D绘图上下文对象 const context = canvas.getContext('2d'); // 在canvas上绘制图片 context.drawImage(image, 0, 0); // 将canvas内容转换为base64格式的字符串 const base64 = canvas.toDataURL().split(',')[1]; console.log(base64); // 将解码后的base64格式的图片和图片名称通过resolve方法返回给调用方 resolve({ data: base64, name: img_name }); }; }); }
同时,这里需要注意一个细节点,导入Notion中链接格式应该为如下格式:
https://github.com/Mr-KID-github/Stable-Diffusion-Open-Community/blob/main/images/" + img_name + "?raw=true"
本回答由Chat GPT辅助生成
-
在我们将图片base64编码后,即可尝试图片上传Github操作了
同样,我们这次用APIPost测试,如图所示即是成功:
接着,我们就可以开始编写我们的将图片文件上传到 GitHub 仓库中方法了,代码如下:
// 定义一个异步函数,用于将图片文件上传到 GitHub 仓库中 async function uploadFileToGithub() { // 将图片解码至 base64 格式 const imgbase64 = await translate(); console.log(imgbase64.data); // 构造上传图片所需的 GitHub API 请求 URL const Github_url = "https://api.github.com/repos/Mr-KID-github/Stable-Diffusion-Open-Community/contents/images/" + imgbase64.name; // 发送 PUT 请求将图片文件上传到 GitHub 仓库 return window.fetch(Github_url, { method: 'PUT', headers: { "Accept": "application/vnd.github+json ", "Content-Type": "application/json", "X-GitHub-Api-Version": "2022-11-28", "Authorization": "Bearer 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" }, body: JSON.stringify({ "message": "Stable Diffusion 上传图片 " + imgbase64.name, "content": imgbase64.data }) }) // 处理上传结果 .then(async res => { if (res.status >= 200 && res.status < 400) { // 如果上传成功,返回成功信息和响应内容 return { status: res.status, data: await res.json() } } else { // 如果上传失败,弹出提示框,并返回失败信息和响应内容 alert("图片上传GitHub图床失败!"); console.log(res); return { status: res.status, data: null } } }) // 捕捉可能的异常 .catch(e => e); }
-
在send2Notion方法中增加Github图床中的对应图片链接
...... let dataResponse = await fetch('https://api.notion.com/v1/pages', { method: 'POST', headers: { 'Authorization': 'Bearer ' + notion_api_key, 'Notion-Version': '2022-06-28', 'Content-Type': 'application/json' }, referrerPolicy: "no-referrer-when-downgrade", mode: "same-origin", body: JSON.stringify({ "parent": { "database_id": databaseID }, "properties": { "Name": { "title": [ { "text": { "content": "Detail" } } ] }, "Prompt": { "rich_text": [ { "text": { "content": info.prompt } } ] }, "Negative Prompt": { "rich_text": [ { "text": { "content": info.negativePrompt } } ] }, "Parameter": { "rich_text": [ { "text": { "content": info.parameter } } ] } }, "children": [ { "object": "block", "type": "image", "image": { "type": "external", "external": { "url": img2Notion_url } } }, ] }) }) ......
第二步,使用Chat GPT优化样式
如图所示:
最后确定的样式如下:
至此,我们的工作就全部完成啦!你上传的所有图片都会在这个平台展示出来:
Stable Diffusion Open Community
当然,你也可以在这个平台看到所有人开源出来的图片以及相关的提词!
所有源代码全部放置Github开源平台!如果喜欢的话请点击Star,让更多人关注到我们,一起加入光荣的进化吧!
https://github.com/Mr-KID-github/Stable-Diffusion-Open-Community