diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 00000000..fa3f483b --- /dev/null +++ b/README_CN.md @@ -0,0 +1,21 @@ +## 这是一个使用 tldraw 和 gpt-4-vision API 来根据你绘制的线框生成 HTML 的应用程序。 + +我目前正在开发一个托管版本的 draw-a-ui。你可以在 draw-a-ui.com 加入等待名单。它的核心将始终是开源的,并在这里提供。 + +## 应用程序演示 + +这个应用程序通过将当前画布的 SVG 转换为 PNG,并将该 PNG 发送给 gpt-4-vision,并指示返回一个带有 tailwind 的单个 HTML 文件来工作。 + +免责声明:这是一个演示,不适用于生产环境。它没有任何授权,所以如果你部署它,你会破产。 + +## 入门 +这是一个 Next.js 应用程序。要开始,请在项目根目录运行以下命令。你将需要一个具有 GPT-4 Vision API 访问权限的 OpenAI API 密钥。 + +注意这使用 Next.js 14,需要 node 版本高于 18.17。在此了解更多。 + +```bash +echo "OPENAI_API_KEY=sk-your-key" > .env.local +npm install +npm run dev +``` +在浏览器中打开 http://localhost:3000 查看结果。 \ No newline at end of file diff --git a/app/api/toHtml/route.ts b/app/api/toHtml/route.ts index 7cddb5eb..678160c6 100644 --- a/app/api/toHtml/route.ts +++ b/app/api/toHtml/route.ts @@ -1,10 +1,25 @@ +import axios from 'axios'; + 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. if you need to insert an image, use placehold.co to create a placeholder image. Respond only with the html file.`; + +async function encodeImageFromUrl(imageUrl: string): Promise { + const response = await axios.get(imageUrl, { responseType: 'arraybuffer' }); + if (response.status !== 200) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const base64 = Buffer.from(response.data, 'binary').toString('base64'); + return base64; +} + + export async function POST(request: Request) { const { image } = await request.json(); + const imageBase64 = await encodeImageFromUrl(image); + console.log(imageBase64); const body: GPT4VCompletionRequest = { model: "gpt-4-vision-preview", max_tokens: 4096, @@ -18,9 +33,13 @@ export async function POST(request: Request) { content: [ { type: "image_url", - image_url: { url: image, detail: "high" }, + image_url: { url: 'data:image/jpeg;base64,'+imageBase64, detail: "high" }, + }, + { + type: "text", + text: "Turn this into a single html file using tailwind.", }, - "Turn this into a single html file using tailwind.", + ], }, ], @@ -28,7 +47,7 @@ export async function POST(request: Request) { let json = null; try { - const resp = await fetch("https://api.openai.com/v1/chat/completions", { + const resp = await fetch(process.env.OPENAI_BASE_URL+"/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/package-lock.json b/package-lock.json index 79e77765..79b7880f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@tldraw/tldraw": "2.0.0-alpha.17", + "axios": "^1.6.2", "canvas-size": "^1.2.6", "next": "14.0.1", "prismjs": "^1.29.0", @@ -2212,6 +2213,11 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.16", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", @@ -2270,6 +2276,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -2498,6 +2514,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2615,6 +2642,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3356,6 +3391,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3365,6 +3419,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -4332,6 +4399,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4892,6 +4978,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index f1d7a163..9b7bd3e7 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@tldraw/tldraw": "2.0.0-alpha.17", + "axios": "^1.6.2", "canvas-size": "^1.2.6", "next": "14.0.1", "prismjs": "^1.29.0",