From 8a51c777eff6ba4444d577e80a921252b16d168d Mon Sep 17 00:00:00 2001 From: John Zhang Date: Fri, 8 Mar 2024 17:54:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=B1=BB=E5=9E=8B=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=AF=B9=E5=BA=94prompt=E6=A8=A1=E6=9D=BF=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=8C=87=E5=AE=9A=E6=A8=A1=E6=9D=BF=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../itbuilder/image2code/api/CodeType.java | 25 +++++++ .../image2code/api/GenerateParameter.java | 1 + .../image2code/api/MyWebSocketHandler.java | 66 ++++++++++++++----- .../image2code/api/WebSocketConfig.java | 5 +- src/main/resources/application.yml | 4 +- src/main/resources/prompts/bootstrap.txt | 22 +++++++ src/main/resources/prompts/react_tailwind.txt | 26 ++++++++ src/main/resources/prompts/vue_elementui.txt | 34 ++++++++++ .../{vue.txt => prompts/vue_tailwind.txt} | 0 src/main/resources/prompts/vue_uniapp.txt | 24 +++++++ src/main/resources/static/image2code.js | 36 +++++++++- src/main/resources/static/index.html | 4 -- 12 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/CodeType.java create mode 100644 src/main/resources/prompts/bootstrap.txt create mode 100644 src/main/resources/prompts/react_tailwind.txt create mode 100644 src/main/resources/prompts/vue_elementui.txt rename src/main/resources/{vue.txt => prompts/vue_tailwind.txt} (100%) create mode 100644 src/main/resources/prompts/vue_uniapp.txt diff --git a/src/main/java/com/enation/itbuilder/image2code/api/CodeType.java b/src/main/java/com/enation/itbuilder/image2code/api/CodeType.java new file mode 100644 index 0000000..8ffb1a8 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/CodeType.java @@ -0,0 +1,25 @@ +package com.enation.itbuilder.image2code.api; + +import lombok.Getter; + +/** + * This guy is busy, nothing left + * + * @author John Zhang + */ +@Getter +public enum CodeType { + + bootstrap("Bootstrap"), + vue_elementui("Vue + Element UI"), + vue_uniapp("Vue + UniApp"), + vue_tailwind("Vue + Tailwind"), + react_tailwind("React + Tailwind"); + + private final String displayText; + + CodeType(String displayText) { + this.displayText = displayText; + } + +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/GenerateParameter.java b/src/main/java/com/enation/itbuilder/image2code/api/GenerateParameter.java index 7d57959..3450580 100644 --- a/src/main/java/com/enation/itbuilder/image2code/api/GenerateParameter.java +++ b/src/main/java/com/enation/itbuilder/image2code/api/GenerateParameter.java @@ -17,5 +17,6 @@ public class GenerateParameter { //openai apikey private String apiKey; + private String codeType; } diff --git a/src/main/java/com/enation/itbuilder/image2code/api/MyWebSocketHandler.java b/src/main/java/com/enation/itbuilder/image2code/api/MyWebSocketHandler.java index 8ee4113..5237f92 100644 --- a/src/main/java/com/enation/itbuilder/image2code/api/MyWebSocketHandler.java +++ b/src/main/java/com/enation/itbuilder/image2code/api/MyWebSocketHandler.java @@ -8,8 +8,7 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSources; -import org.apache.commons.io.IOUtils; -import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; @@ -22,21 +21,39 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class MyWebSocketHandler extends TextWebSocketHandler { + public static final DefaultResourceLoader DEFAULT_RESOURCE_LOADER = new DefaultResourceLoader(); private static ObjectMapper mapper = new ObjectMapper(); - private String apiAddress ; + private static final Map CODE_TYPES = Stream.of(CodeType.values()) + .collect(Collectors.toMap(CodeType::toString, CodeType::getDisplayText)); + + private String apiAddress; + + private final String promptsLocation; public MyWebSocketHandler(String apiAddress) { + this(apiAddress, "classpath:/prompts/"); + } + + public MyWebSocketHandler(String apiAddress, String promptsLocation) { this.apiAddress = apiAddress; + this.promptsLocation = promptsLocation; } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { - session.setBinaryMessageSizeLimit(10* 1024 * 1024); + session.setBinaryMessageSizeLimit(10 * 1024 * 1024); session.setTextMessageSizeLimit(10 * 1024 * 1024); + + session.sendMessage(new TextMessage(mapper.writeValueAsString(Map.of( + "type", "initCodeTypes", + "data", CODE_TYPES + )))); } @Override @@ -57,8 +74,7 @@ public class MyWebSocketHandler extends TextWebSocketHandler { } - - public void sse(WebSocketSession session, GenerateParameter generateParameter) { + public void sse(WebSocketSession session, GenerateParameter generateParameter) throws Exception { OkHttpClient client = new OkHttpClient .Builder() .connectTimeout(100, TimeUnit.SECONDS) @@ -69,12 +85,32 @@ public class MyWebSocketHandler extends TextWebSocketHandler { mainMap.put("model", "gpt-4-vision-preview"); - String prompt = readPrompt("vue.txt"); + // 利用枚举验证codeType,因为会作为文件名,尽量避免被恶意攻击 + CodeType codeType; + try { + codeType = CodeType.valueOf(generateParameter.getCodeType()); + } catch (IllegalArgumentException e) { + session.sendMessage(new TextMessage(""" + { "success": false, "body": "Invalid code type" } + """)); + return; + } + + //读取prompt文件,若读取失败给出友好提示 + String prompt = null; + try { + prompt = readPrompt(codeType); + } catch (IOException e) { + session.sendMessage(new TextMessage(""" + { "success": false, "body": "Sorry that code type doesn't work right now. Please try another one." } + """)); + return; + } List> messagesList = new ArrayList<>(); Map message1 = new HashMap<>(); message1.put("role", "system"); - message1.put("content",prompt); + message1.put("content", prompt); messagesList.add(message1); Map message2 = new HashMap<>(); @@ -119,15 +155,11 @@ public class MyWebSocketHandler extends TextWebSocketHandler { System.out.println("api req"); } - public String readPrompt(String name) { - ClassPathResource resource = new ClassPathResource(name); - String content = null; - try { - content = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - return content; + + public String readPrompt(CodeType codeType) throws IOException { + return DEFAULT_RESOURCE_LOADER + .getResource(promptsLocation + codeType.toString() + ".txt") + .getContentAsString(StandardCharsets.UTF_8); } } diff --git a/src/main/java/com/enation/itbuilder/image2code/api/WebSocketConfig.java b/src/main/java/com/enation/itbuilder/image2code/api/WebSocketConfig.java index af56dad..3124652 100644 --- a/src/main/java/com/enation/itbuilder/image2code/api/WebSocketConfig.java +++ b/src/main/java/com/enation/itbuilder/image2code/api/WebSocketConfig.java @@ -19,9 +19,12 @@ public class WebSocketConfig implements WebSocketConfigurer { @Value("${image2code.api-address}") private String apiAddress; + @Value("${image2code.promptsLocation:classpath:/prompts/}") + private String promptsLocation; + @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(new MyWebSocketHandler(apiAddress), "/ws").setAllowedOrigins("*"); + registry.addHandler(new MyWebSocketHandler(apiAddress, promptsLocation), "/ws").setAllowedOrigins("*"); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6c66520..a40d219 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,4 +6,6 @@ logging: com.enation: DEBUG # 替换为你自己的包名 image2code: - api-address: https://api.openai.com/v1/chat/completions \ No newline at end of file + api-address: https://api.openai.com/v1/chat/completions +# promptsLocation: file:/opt/image2code/prompts/ + promptsLocation: classpath:/prompts/ diff --git a/src/main/resources/prompts/bootstrap.txt b/src/main/resources/prompts/bootstrap.txt new file mode 100644 index 0000000..c041b75 --- /dev/null +++ b/src/main/resources/prompts/bootstrap.txt @@ -0,0 +1,22 @@ +You are an expert Bootstrap developer +You take screenshots of a reference web page from the user, and then build single page apps +using Bootstrap, HTML and JS. +You might also be given a screenshot(The second image) of a web page that you have already built, and asked to +update it to look more like the reference image(The first image). + +- Make sure the app looks exactly like the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- Use the exact text from the screenshot. +- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE. +- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "" or bad things will happen. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. + +In terms of libraries, + +- Use this script to include Bootstrap: +- You can use Google Fonts +- Font Awesome for icons: + +Return only the full code in tags. +Do not include markdown "```" or "```html" at the start or end. \ No newline at end of file diff --git a/src/main/resources/prompts/react_tailwind.txt b/src/main/resources/prompts/react_tailwind.txt new file mode 100644 index 0000000..0e32766 --- /dev/null +++ b/src/main/resources/prompts/react_tailwind.txt @@ -0,0 +1,26 @@ +You are an expert React/Tailwind developer +You take screenshots of a reference web page from the user, and then build single page apps +using React and Tailwind CSS. +You might also be given a screenshot(The second image) of a web page that you have already built, and asked to +update it to look more like the reference image(The first image). + +- Make sure the app looks exactly like the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- Use the exact text from the screenshot. +- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE. +- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "" or bad things will happen. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. + +In terms of libraries, + +- Use these script to include React so that it can run on a standalone page: + + + +- Use this script to include Tailwind: +- You can use Google Fonts +- Font Awesome for icons: + +Return only the full code in tags. +Do not include markdown "```" or "```html" at the start or end. \ No newline at end of file diff --git a/src/main/resources/prompts/vue_elementui.txt b/src/main/resources/prompts/vue_elementui.txt new file mode 100644 index 0000000..094df6c --- /dev/null +++ b/src/main/resources/prompts/vue_elementui.txt @@ -0,0 +1,34 @@ +You are an expert Vue/ElementUI developer +You take screenshots of a reference web page from the user, and then build single page apps using Vue and ElementUI CSS. + +- Make sure the app looks exactly like the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- Use the exact text from the screenshot. +- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE. +- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "" or bad things will happen. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. +- Use Vue using the global build like so: + +
{{ message }}
+ + +In terms of libraries, + +- Use these script to include Vue so that it can run on a standalone page: + +- Use this script to include ElementUI CSS:, and JavaScript: + +Return only the full code in tags. +Do not include markdown "```" or "```html" at the start or end. +The return result must only include the code. \ No newline at end of file diff --git a/src/main/resources/vue.txt b/src/main/resources/prompts/vue_tailwind.txt similarity index 100% rename from src/main/resources/vue.txt rename to src/main/resources/prompts/vue_tailwind.txt diff --git a/src/main/resources/prompts/vue_uniapp.txt b/src/main/resources/prompts/vue_uniapp.txt new file mode 100644 index 0000000..425d106 --- /dev/null +++ b/src/main/resources/prompts/vue_uniapp.txt @@ -0,0 +1,24 @@ +You are an expert Vue/UniApp developer +You take screenshots of a reference web page from the user, and then build single page apps using Vue and UniApp. + +- Make sure the app looks exactly like the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- Use the exact text from the screenshot. +- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE. +- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "" or bad things will happen. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. +- Use Vue using the global build like so: + +
{{ message }}
+ \ No newline at end of file diff --git a/src/main/resources/static/image2code.js b/src/main/resources/static/image2code.js index fd99b73..af11960 100644 --- a/src/main/resources/static/image2code.js +++ b/src/main/resources/static/image2code.js @@ -42,8 +42,12 @@ socket.onopen = function(event) { // 监听接收到的消息 socket.onmessage = function(event) { - // console.log('接收到消息:', event.data); - var message = JSON.parse(event.data); + // console.log('接收到消息:', event); + const message = JSON.parse(event.data); + if (message.type === 'initCodeTypes') { + handleInitCodeTypes(message) + return + } if (message.status === 'success') { receiveHtmlChar( message.body); }else { @@ -62,13 +66,39 @@ socket.onclose = function(event) { console.log('连接已关闭:', event); }; +function handleInitCodeTypes(message) { + console.log('handleInitCodeTypes:', message) + const selectEle = document.querySelector('select.code-type') + const codeTypes = message.data + if (codeTypes && Object.keys(codeTypes).length > 0) { + selectEle.innerHTML = '' + for (const key of Object.keys(codeTypes)) { + const opt = document.createElement('option') + opt.value = key + opt.innerHTML = codeTypes[key] + selectEle.appendChild(opt); + } + } +} function sendMessage(base64String) { + const codeType = document.querySelector('select.code-type').value + if (!codeType) { + console.log('require code type') + alert('Please select the output code type') + return + } const apiKey = localStorage.getItem('openaiKey'); + if (!apiKey) { + console.log('require OpenAI APIKey') + alert('Please set the OpenAI APIKey') + return + } // 创建一个对象 const obj = { base64String: base64String, - apiKey: apiKey + apiKey: apiKey, + codeType: codeType }; const jsonString = JSON.stringify(obj); socket.send(jsonString); diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index d7c84c6..2edbeef 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -117,10 +117,6 @@ Image preview... -- Gitee