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 0000000000000000000000000000000000000000..8ffb1a83e96ec468423aa4be66dfde6002986568 --- /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 7d5795942bbd04b8d505e31e471f346e3ff44879..3450580ac9875af67f588f0ba3703a98f5d31800 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 8ee411369602cc93b8039bb1f541420de6c2ee65..5237f92ca8924e6af33a60dc52e29d6772785b8a 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 af56dadf0b9e88224e1faa4e515d1e95572f6423..3124652f8f39eb5b870ba73939eb1224e6b68cd6 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 6c6652042f3196aeae3db82b4751748a9e386dd4..a40d2198145897537d81c7b321e3cbf681c4a457 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 0000000000000000000000000000000000000000..c041b75fa2016293e2e2f7e1ee1545df0650a1cd --- /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 0000000000000000000000000000000000000000..0e32766ebfd534423f22f29a27e83780ace5895b --- /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 0000000000000000000000000000000000000000..094df6c182cd96187856b14d85d88857962a9e17 --- /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 0000000000000000000000000000000000000000..425d106700ff20dd152a43b4e2cb0da02490fe9e --- /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 fd99b7348a14a1c77d38d1a7c7ec18f9ea453db3..af11960f8bb5380b79b9daec2a1ca23e63975711 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 d7c84c6261e69afa52e270db87914bde6b8c92c9..2edbeef0ea5aa3fb5116814ba438a4bad6438e1b 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -117,10 +117,6 @@ Image preview...