From 29280559aab38da496dd2c8e0cacec9a9a0cadc2 Mon Sep 17 00:00:00 2001 From: kingapex Date: Wed, 10 Apr 2024 15:24:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=96=87=E8=BD=AC=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 15 +- ...ation.java => PageBuilderApplication.java} | 4 +- .../itbuilder/image2code/api/ActionType.java | 12 + .../image2code/api/AnalysisSetting.java | 35 +++ .../itbuilder/image2code/api/FileUtils.java | 48 +++ .../itbuilder/image2code/api/MessageType.java | 13 + .../api/PageGenerateController.java | 16 + .../api/PageHtmlResponseHandler.java | 66 +++++ .../itbuilder/image2code/api/PageSetting.java | 35 +++ .../itbuilder/image2code/api/TextToHtml.java | 114 +++++++ .../image2code/api/TextWebSocketHandler.java | 140 +++++++++ .../image2code/api/WebSocketConfig.java | 3 +- .../image2code/api/WorkFlowContext.java | 23 ++ .../itbuilder/image2code/api/WsResponse.java | 1 + .../resources/prompts/by-text/analysis.txt | 6 + .../resources/prompts/by-text/bootstrap.txt | 19 ++ .../resources/prompts/by-text/page-create.txt | 6 + .../prompts/by-text/vue_tailwind.txt | 33 +++ .../resources/response/analysis-result.txt | 28 ++ src/main/resources/static/PageBuilder.js | 279 ++++++++++++++++++ src/main/resources/static/image2code.html | 206 +++++++++++++ src/main/resources/static/index.html | 223 +++++++------- src/main/resources/static/style.css | 168 +++++++++++ .../image2code/api/TextToHtmlTest.java | 86 ++++++ 24 files changed, 1458 insertions(+), 121 deletions(-) rename src/main/java/com/enation/itbuilder/image2code/{Image2CodeApplication.java => PageBuilderApplication.java} (69%) create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/ActionType.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/AnalysisSetting.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/FileUtils.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/MessageType.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/PageGenerateController.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/PageHtmlResponseHandler.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/PageSetting.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/TextToHtml.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/TextWebSocketHandler.java create mode 100644 src/main/java/com/enation/itbuilder/image2code/api/WorkFlowContext.java create mode 100644 src/main/resources/prompts/by-text/analysis.txt create mode 100644 src/main/resources/prompts/by-text/bootstrap.txt create mode 100644 src/main/resources/prompts/by-text/page-create.txt create mode 100644 src/main/resources/prompts/by-text/vue_tailwind.txt create mode 100644 src/main/resources/response/analysis-result.txt create mode 100644 src/main/resources/static/PageBuilder.js create mode 100644 src/main/resources/static/image2code.html create mode 100644 src/main/resources/static/style.css create mode 100644 src/test/java/com/enation/itbuilder/image2code/api/TextToHtmlTest.java diff --git a/pom.xml b/pom.xml index c34454a..7aa4484 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,9 @@ com.enation.itbuilder.image2code - image2code + page-builder 1.0.0 - image2code + Page Builder image2code use openai GPT-4 Vision 17 @@ -60,6 +60,17 @@ org.springframework.boot spring-boot-starter-websocket + + dev.langchain4j + langchain4j + 0.28.0 + + + dev.langchain4j + langchain4j-open-ai + 0.28.0 + + diff --git a/src/main/java/com/enation/itbuilder/image2code/Image2CodeApplication.java b/src/main/java/com/enation/itbuilder/image2code/PageBuilderApplication.java similarity index 69% rename from src/main/java/com/enation/itbuilder/image2code/Image2CodeApplication.java rename to src/main/java/com/enation/itbuilder/image2code/PageBuilderApplication.java index 91c9d94..55acdcc 100644 --- a/src/main/java/com/enation/itbuilder/image2code/Image2CodeApplication.java +++ b/src/main/java/com/enation/itbuilder/image2code/PageBuilderApplication.java @@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class Image2CodeApplication { +public class PageBuilderApplication { public static void main(String[] args) { - SpringApplication.run(Image2CodeApplication.class, args); + SpringApplication.run(PageBuilderApplication.class, args); } } diff --git a/src/main/java/com/enation/itbuilder/image2code/api/ActionType.java b/src/main/java/com/enation/itbuilder/image2code/api/ActionType.java new file mode 100644 index 0000000..3a31942 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/ActionType.java @@ -0,0 +1,12 @@ +package com.enation.itbuilder.image2code.api; + +/** + * action type + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +public enum ActionType { + analysis,create; +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/AnalysisSetting.java b/src/main/java/com/enation/itbuilder/image2code/api/AnalysisSetting.java new file mode 100644 index 0000000..eaa1f7c --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/AnalysisSetting.java @@ -0,0 +1,35 @@ +package com.enation.itbuilder.image2code.api; + +import lombok.Data; + +/** + * 分析参数 + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +@Data +public class AnalysisSetting { + // 行业 + private String industry; + + // 页面类型 + private String pageType; + + // 业务 + private String business; + + // 功能特色,亮点、优势等 + private String features; + + private String codeType; + + private String actionType; + + //openai apikey + private String apiKey; + + private String analysisResult; + +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/FileUtils.java b/src/main/java/com/enation/itbuilder/image2code/api/FileUtils.java new file mode 100644 index 0000000..f5ee17a --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/FileUtils.java @@ -0,0 +1,48 @@ +package com.enation.itbuilder.image2code.api; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ + +public class FileUtils { + + /** + * 读取文件内容 + * @param fileName 相对于resources下的路径 + * @return 文件内容 + */ + public static String readFile(String fileName) { + // 使用类加载器获取文件输入流 + InputStream is = FileUtils.class.getResourceAsStream(fileName); + if (is == null) { + System.out.println("文件未找到: " + fileName); + return null; + } + + // 使用StringBuilder来存储文件内容 + StringBuilder content = new StringBuilder(); + + // 使用BufferedReader读取文件内容 + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return content.toString(); + } + +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/MessageType.java b/src/main/java/com/enation/itbuilder/image2code/api/MessageType.java new file mode 100644 index 0000000..009358f --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/MessageType.java @@ -0,0 +1,13 @@ +package com.enation.itbuilder.image2code.api; + +/** + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +public enum MessageType { + initCodeTypes, + analyze, + create +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/PageGenerateController.java b/src/main/java/com/enation/itbuilder/image2code/api/PageGenerateController.java new file mode 100644 index 0000000..849cff8 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/PageGenerateController.java @@ -0,0 +1,16 @@ +package com.enation.itbuilder.image2code.api; + +/** + * 页面生产 api + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +public class PageGenerateController { + + public String generatePage(PageSetting pageSetting){ + + return ""; + } +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/PageHtmlResponseHandler.java b/src/main/java/com/enation/itbuilder/image2code/api/PageHtmlResponseHandler.java new file mode 100644 index 0000000..fa33937 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/PageHtmlResponseHandler.java @@ -0,0 +1,66 @@ +package com.enation.itbuilder.image2code.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.langchain4j.model.StreamingResponseHandler; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.model.output.TokenUsage; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; + +/** + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +public class PageHtmlResponseHandler implements StreamingResponseHandler { + + private static ObjectMapper jsonObjMapper = new ObjectMapper(); + private ActionType actionType; + private WebSocketSession session; + public PageHtmlResponseHandler(WebSocketSession session,ActionType actionType) { + this.actionType=actionType; + this.session = session; + } + @Override + public void onNext(String data) { +// System.out.println(data); + try { + WsResponse success = WsResponse.builder().type(this.actionType.name()).status("success").body(data).build(); + String jsonString = jsonObjMapper.writeValueAsString(success); + + session.sendMessage(new TextMessage(jsonString)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onComplete(Response response) { + + + try { + WsResponse complete = WsResponse.builder().type(this.actionType.name()).status("complete").body("success").build(); + String jsonString = jsonObjMapper.writeValueAsString(complete); + + session.sendMessage(new TextMessage(jsonString)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + + TokenUsage tokenUsage = response.tokenUsage(); + Integer total = tokenUsage.totalTokenCount(); + System.out.println("------花费-----"); + System.out.println(total); + } + + @Override + public void onError(Throwable throwable) { + + } +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/PageSetting.java b/src/main/java/com/enation/itbuilder/image2code/api/PageSetting.java new file mode 100644 index 0000000..e2d0170 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/PageSetting.java @@ -0,0 +1,35 @@ +package com.enation.itbuilder.image2code.api; + +import lombok.Data; + +/** + * 页面基本设定 + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ + +@Data +public class PageSetting { + + // 行业 + private String industry; + // 页面类型 + private String pageType; + // 板块规划 + private String sectionPlanning; + // 是否自动规划板块 + private boolean autoPlanSection; + // 颜色 + private String color; + // 风格 + private String style; + // 其他需求 + private String otherRequirements; + + private String codeType; + + //openai apikey + private String apiKey; +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/TextToHtml.java b/src/main/java/com/enation/itbuilder/image2code/api/TextToHtml.java new file mode 100644 index 0000000..6444146 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/TextToHtml.java @@ -0,0 +1,114 @@ +package com.enation.itbuilder.image2code.api; + +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.UserMessage; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.TemplateExceptionHandler; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static com.enation.itbuilder.image2code.api.FileUtils.readFile; + +/** + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +public class TextToHtml { + + public static final DefaultResourceLoader DEFAULT_RESOURCE_LOADER = new DefaultResourceLoader(); + + private static final String promptsLocation = "classpath:/prompts/by-text"; + + /** + * 组织分析需求的提示词 + * @param analysisSetting + * @return + */ + public static List convertAnalyzeMessages(AnalysisSetting analysisSetting) { + + String sysPrompt = readFile("/prompts/by-text/analysis.txt"); + String userPrompt = "一个${analysisSetting.industry}行业的${analysisSetting.pageType},主营业务为${analysisSetting.business},产品或服务的优势和特色为:${analysisSetting.features}"; + + //用freemarker解析模版 + userPrompt = parsePrompt(userPrompt, analysisSetting,"analysisSetting"); + + SystemMessage systemMessage = SystemMessage.from(sysPrompt); + UserMessage userMessage = UserMessage.from(userPrompt ); + + List messageList = new ArrayList<>(); + messageList.add(systemMessage); + messageList.add(userMessage); + + return messageList; + } + + public static List convertCreateMessage(AnalysisSetting analysisSetting,String analysisResult ) { + String codeType = analysisSetting.getCodeType(); + + + codeType = "/prompts/by-text/"+codeType+".txt"; + + String sysPrompt = readFile(codeType); + String userPrompt = "一个${analysisSetting.industry}行业的${analysisSetting.pageType},主营业务为${analysisSetting.business},产品或服务的优势和特色为:${analysisSetting.features}"; + //用freemarker解析模版 + userPrompt = parsePrompt(userPrompt, analysisSetting,"analysisSetting"); + + userPrompt = userPrompt + "\n" + analysisResult; + + SystemMessage systemMessage = SystemMessage.from(sysPrompt); + UserMessage userMessage = UserMessage.from(userPrompt ); + + List messageList = new ArrayList<>(); + messageList.add(systemMessage); + messageList.add(userMessage); + + return messageList; + + } + + + public static String parsePrompt( String templateString,Object pageSetting,String modelName ) { + // 配置Freemarker + Configuration cfg = new Configuration(Configuration.VERSION_2_3_29); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + cfg.setDefaultEncoding("UTF-8"); + cfg.setWrapUncheckedExceptions(true); + + + // 使用Template类的构造器直接从字符串创建模板 + Template template = null; + try { + template = new Template("name", new StringReader(templateString), cfg); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // 准备数据模型 + Map dataModel = new HashMap<>(); + dataModel.put(modelName, pageSetting); + + // 合并模板和数据模型 + Writer out = new StringWriter(); + try { + template.process(dataModel, out); + } catch (TemplateException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return out.toString(); + } +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/TextWebSocketHandler.java b/src/main/java/com/enation/itbuilder/image2code/api/TextWebSocketHandler.java new file mode 100644 index 0000000..97fbcf5 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/TextWebSocketHandler.java @@ -0,0 +1,140 @@ +package com.enation.itbuilder.image2code.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.StreamingChatLanguageModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.model.output.TokenUsage; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSources; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +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 TextWebSocketHandler extends org.springframework.web.socket.handler.TextWebSocketHandler { + + public static final DefaultResourceLoader DEFAULT_RESOURCE_LOADER = new DefaultResourceLoader(); + private static ObjectMapper mapper = new ObjectMapper(); + + 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 TextWebSocketHandler(String apiAddress) { + this(apiAddress, "classpath:/prompts/by-text"); + } + + public TextWebSocketHandler(String apiAddress, String promptsLocation) { + this.apiAddress = apiAddress; + this.promptsLocation = promptsLocation; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + session.setBinaryMessageSizeLimit(10 * 1024 * 1024); + session.setTextMessageSizeLimit(10 * 1024 * 1024); + + session.sendMessage(new TextMessage(mapper.writeValueAsString(Map.of( + "type", "initCodeTypes", + "data", CODE_TYPES + )))); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + // 处理接收到的消息 + // 这里可以根据需要处理消息 + String payload = message.getPayload(); + + + AnalysisSetting generateParameter = mapper.readValue(payload, AnalysisSetting.class); + + sse(session, generateParameter); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + + } + + + public void sse(WebSocketSession session, AnalysisSetting generateParameter) throws Exception { + OkHttpClient client = new OkHttpClient + .Builder() + .connectTimeout(100, TimeUnit.SECONDS) + .writeTimeout(300, TimeUnit.SECONDS) + .readTimeout(300, TimeUnit.SECONDS) + .build(); + Map mainMap = new HashMap<>(); + + mainMap.put("model", "gpt-4-0125-preview"); + + + String userIdea = ""; + + + StreamingChatLanguageModel streamModel = OpenAiStreamingChatModel.builder() + .apiKey(generateParameter.getApiKey())// Please use your own OpenAI API key + .modelName("gpt-4-0125-preview") + .timeout(Duration.ofMinutes(5)) + .maxTokens(4096) + .temperature(0D) +// .responseFormat("{ \"type\": \"json_object\" }") + .build(); + + ActionType actionType = ActionType.valueOf(generateParameter.getActionType()); + List messageList = null; + if (actionType.equals(ActionType.analysis)) { + messageList=TextToHtml.convertAnalyzeMessages(generateParameter); + } + + if (actionType.equals(ActionType.create)) { + messageList=TextToHtml.convertCreateMessage(generateParameter,generateParameter.getAnalysisResult()); + } + + PageHtmlResponseHandler pageHtmlResponseHandler = new PageHtmlResponseHandler(session,actionType); + + streamModel.generate(messageList,pageHtmlResponseHandler); + + + System.out.println("api req"); + } + + + + 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 3124652..98f46c7 100644 --- a/src/main/java/com/enation/itbuilder/image2code/api/WebSocketConfig.java +++ b/src/main/java/com/enation/itbuilder/image2code/api/WebSocketConfig.java @@ -24,7 +24,8 @@ public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(new MyWebSocketHandler(apiAddress, promptsLocation), "/ws").setAllowedOrigins("*"); +// registry.addHandler(new MyWebSocketHandler(apiAddress, promptsLocation), "/ws").setAllowedOrigins("*"); + registry.addHandler(new TextWebSocketHandler(apiAddress, promptsLocation), "/ws").setAllowedOrigins("*"); } diff --git a/src/main/java/com/enation/itbuilder/image2code/api/WorkFlowContext.java b/src/main/java/com/enation/itbuilder/image2code/api/WorkFlowContext.java new file mode 100644 index 0000000..6adf378 --- /dev/null +++ b/src/main/java/com/enation/itbuilder/image2code/api/WorkFlowContext.java @@ -0,0 +1,23 @@ +package com.enation.itbuilder.image2code.api; + +import java.util.HashMap; +import java.util.Map; + +/** + * 工作流上下文 + * @author kingapex + * @version 1.0 + * @data 2022/12/23 12:02 + * @since 7.3.0 + **/ +public class WorkFlowContext { + private static final ThreadLocal> actionResult = ThreadLocal.withInitial(() -> new HashMap<>()); + + public static void setResult(ActionType type,Object result) { + actionResult.get().put(type,result); + } + + public static Object getResult(ActionType type) { + return actionResult.get().get(type); + } +} diff --git a/src/main/java/com/enation/itbuilder/image2code/api/WsResponse.java b/src/main/java/com/enation/itbuilder/image2code/api/WsResponse.java index a94ed86..ef7cd0e 100644 --- a/src/main/java/com/enation/itbuilder/image2code/api/WsResponse.java +++ b/src/main/java/com/enation/itbuilder/image2code/api/WsResponse.java @@ -15,4 +15,5 @@ import lombok.Data; public class WsResponse { private String status; private String body; + private String type; } diff --git a/src/main/resources/prompts/by-text/analysis.txt b/src/main/resources/prompts/by-text/analysis.txt new file mode 100644 index 0000000..281589d --- /dev/null +++ b/src/main/resources/prompts/by-text/analysis.txt @@ -0,0 +1,6 @@ +你是一名网页产品设计师(Product Designer) +# 用户将给你他网站所在的行业及页面类型,你帮他规划、设计这个页面 +# 你需要帮助客户分析做出如下规划 +- 内容规划:基于需求分析,规划网站的内容结构,设计信息架构 +- 视觉设计:设计网页的版面布局、颜色方案、字体、图标等视觉元素 +- 交互设计:规划用户的交互路径,设计易用且直观的导航系统 \ No newline at end of file diff --git a/src/main/resources/prompts/by-text/bootstrap.txt b/src/main/resources/prompts/by-text/bootstrap.txt new file mode 100644 index 0000000..e8a02c4 --- /dev/null +++ b/src/main/resources/prompts/by-text/bootstrap.txt @@ -0,0 +1,19 @@ +You are an expert Bootstrap developer +You take Requirements of web page from the user, and then build single page apps using Vue and Tailwind 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. + +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/by-text/page-create.txt b/src/main/resources/prompts/by-text/page-create.txt new file mode 100644 index 0000000..ff4ad19 --- /dev/null +++ b/src/main/resources/prompts/by-text/page-create.txt @@ -0,0 +1,6 @@ +- 所处行业: ${pageSetting.industry} +- 页面类型: ${pageSetting.pageType} +- 板块规划: ${pageSetting.sectionPlanning} +- 颜色: ${pageSetting.color} +- 风格:${pageSetting.sectionPlanning} +- 其他需求: ${pageSetting.sectionPlanning} \ No newline at end of file diff --git a/src/main/resources/prompts/by-text/vue_tailwind.txt b/src/main/resources/prompts/by-text/vue_tailwind.txt new file mode 100644 index 0000000..7b29425 --- /dev/null +++ b/src/main/resources/prompts/by-text/vue_tailwind.txt @@ -0,0 +1,33 @@ +You are an expert Vue/Tailwind developer +You take Requirements of web page from the user, and then build single page apps using Vue and Tailwind CSS. +For users' content planning, you should try to provide as comprehensive and enriching support as possible. + +- 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 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. +The return result must only include the code. \ No newline at end of file diff --git a/src/main/resources/response/analysis-result.txt b/src/main/resources/response/analysis-result.txt new file mode 100644 index 0000000..0a34e1b --- /dev/null +++ b/src/main/resources/response/analysis-result.txt @@ -0,0 +1,28 @@ +为了设计一个吸引人且功能齐全的软件产品服务商行业的首页,我们需要将重点放在展示Javashop电商系统的核心优势和特色上。以下是对内容规划、视觉设计和交互设计的具体建议: + +### 内容规划 + +1. **首页概览**:首页应该迅速向访问者介绍Javashop电商系统的主要优势,如全端开源、支持多种业务模式和客户端平台等。 +2. **产品特色与优势**:详细介绍Javashop的特色,如基于SpringBoot的商城系统、微服务商城、商城中台、SAAS商城等,以及支持的多种业务模式和客户端平台。 +3. **业务模式介绍**:清晰地展示支持的业务模式,如B2B2C、S2B2C、O2O、B2B、多语言商城、跨境电商、分销商城、积分商城等,每种模式下的特点和优势。 +4. **案例研究/成功故事**:展示使用Javashop电商系统成功案例,增加信任感。 +5. **技术支持与服务**:介绍提供的技术支持、定制服务和培训等。 +6. **联系方式与咨询**:提供明显的联系方式和在线咨询功能,方便潜在客户快速联系。 + +### 视觉设计 + +1. **色彩方案**:使用现代、科技感的色彩方案,如蓝色调配合灰色或白色,传达专业和可靠的形象。 +2. **字体选择**:使用清晰易读的字体,保证信息传达的清晰性。 +3. **图标和图形**:设计简洁明了的图标和图形,用于解释产品特色和业务模式。 +4. **图片和视频**:使用高质量的图片和视频展示产品界面和使用场景,增加吸引力。 +5. **版面布局**:采用清晰的布局,确保用户可以轻松找到他们感兴趣的信息。 + +### 交互设计 + +1. **导航系统**:设计简单直观的顶部导航栏,包括首页、产品特色、业务模式、案例研究、技术支持等主要部分。 +2. **滚动视差**:在首页使用滚动视差效果,让用户在滚动过程中逐步了解Javashop的特色和优势。 +3. **动态效果**:在关键信息点使用动态效果(如悬停动画),增加互动性和吸引力。 +4. **响应式设计**:确保网站在不同设备上(如手机、平板、PC)都能提供良好的浏览体验。 +5. **易用性**:确保所有的交互元素(如按钮、链接、表单)都易于使用,提供清晰的反馈。 + +通过上述规划,我们可以设计出一个既展示了Javashop电商系统优势和特色,又易于用户浏览和交互的首页,从而吸引更多潜在客户并提升转化率。 \ No newline at end of file diff --git a/src/main/resources/static/PageBuilder.js b/src/main/resources/static/PageBuilder.js new file mode 100644 index 0000000..3189820 --- /dev/null +++ b/src/main/resources/static/PageBuilder.js @@ -0,0 +1,279 @@ + +var accumulatedHtml = ''; // 初始化累积的HTML +var iframe ; + +function addContent(text) { + var $div = $('.output'); + $div.text($div.text()+text); + $div.scrollLeft($div.prop('scrollWidth')); +} +function downloadHtml() { +// 假设你的HTML内容存储在pageHtml变量中 + + +// 创建一个新的blob对象 + var blob = new Blob([accumulatedHtml], {type: "text/html;charset=utf-8"}); + +// 创建一个新的a元素 + var a = document.createElement("a"); + +// 使用URL.createObjectURL()方法创建一个指向blob的URL + a.href = URL.createObjectURL(blob); + +// 设置下载的文件名 + a.download = "page.html"; + +// 添加a元素到文档 + document.body.appendChild(a); + +// 模拟点击a元素 + a.click(); + +// 移除a元素 + document.body.removeChild(a); + +} +function receiveHtmlChar(char) { + accumulatedHtml += char; // 将新字符追加到累积的HTML中 + addContent(char) + if (accumulatedHtml.endsWith('>')) { + updateIframeContent(); + } +} +// 更新iframe中的HTML内容 +function updateIframeContent() { + // 由于直接操作 DOM 可能导致重绘和性能问题,此处我们更新整个文档内容 + var doc; + + if (iframe.contentDocument) { + doc = iframe.contentDocument; // For NS6 + } else if (iframe.contentWindow) { + doc = iframe.contentWindow.document; // For IE5.5 and IE6 + } + doc.open(); + doc.write(accumulatedHtml ); // 确保文档结构完整 + doc.close(); + if (doc.body.scrollHeight) { + doc.body.scrollTo(0, doc.body.scrollHeight) + } +} + +function receiveAnalysis(text) { + let item = $(".page-analysis .detail-item"); + item.append(text) + item.scrollTop(item[0].scrollHeight); + +} +const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; +const host = window.location.host; // 包括域名和端口(如果有) + +// 构建WebSocket的URL +const wsUrl = `${protocol}//${host}/ws`; + +// 创建WebSocket连接 +const socket = new WebSocket(wsUrl); + + +// 当连接成功建立的回调函数 +socket.onopen = function(event) { + console.log('连接已开启:', event); +}; + +// 监听接收到的消息 +socket.onmessage = function(event) { + // console.log('接收到消息:', event); + const message = JSON.parse(event.data); + if (message.type === 'initCodeTypes') { + handleInitCodeTypes(message) + return + } + + let $chat = $("#chat-content"); + $chat.scrollTop($chat[0].scrollHeight); + + if (message.type === 'analysis') { + + if (message.status === 'success') { + $(".page-analysis").show(); + receiveAnalysis(message.body); + } + + if (message.status === 'complete') { + sendMessage('create') + + $(".ai-tip-coding").show(); + $(".output").show(); + + } + + } + + if (message.type === 'create') { + if (message.status === 'success') { + receiveHtmlChar(message.body); + } + + if (message.status === 'complete') { + + $(".menu").show(); + } + + if (message.status === 'error') { + alert(message.body); + } + } + + +}; + +// 监听错误事件 +socket.onerror = function(event) { + console.error('WebSocket错误:', event); +}; + +// 监听连接关闭事件 +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( actionType) { + 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 + } + + + // 创建一个空对象来存储表单数据 + let formData = { + industry: document.getElementById('industry').value, + pageType: document.getElementById('pageType').value, + business: document.getElementById('business').value, + features: document.getElementById('features').value, + apiKey: apiKey, + codeType: codeType, + actionType: actionType + }; + + if (actionType === 'create') { + formData.analysisResult=$(".page-analysis .detail-item").text(); + } + + + // 将对象转换为JSON字符串 + let jsonStr = JSON.stringify(formData); + + if (actionType === 'analysis') { + $("span.industry").text(formData.industry); + $("span.pageType").text(formData.pageType); + $("span.business").text(formData.business); + $("span.features").text(formData.features); + + $(".form-req").hide(); + $(".base-req").show(); + $(".ai-tip-analysis").show(); + + } + + socket.send(jsonStr); + + $(".upload-area").hide(); + $(".preview-block").show() + +} + + +$(function (){ + + iframe = document.getElementById('preview'); + + + document.getElementById('fileInput').addEventListener('change', function() { + var file = this.files[0]; + + if (file) { + + if(file.size > 10485760) { // 10MB = 10485760 Bytes + alert('文件大小不能超过10MB'); + return false; + } + + let reader = new FileReader(); + reader.onload = function(event) { + let base64String = event.target.result; + sendMessage(base64String); + }; + reader.readAsDataURL(file); + + var imgUrl = URL.createObjectURL(file); + $("#imagePreview").show().attr("src", imgUrl); + + } + }); + + $("#settingSaveBtn").click(function (){ + let openaiKey = $("#openaiKey").val(); + localStorage.setItem('openaiKey', openaiKey); + + }); + + $("#createBtn").click(function (){ + sendMessage("analysis") + + }); + + + const myModalEl = document.getElementById('settingModal') + myModalEl.addEventListener('show.bs.modal', event => { + let openaiKey = localStorage.getItem('openaiKey'); + $("#openaiKey").val(openaiKey); + }) + + + $("#downloadBtn").click(function (){ + downloadHtml(); + }); + + + $("#resetBtn").click(function (){ + $(".menu").hide(); + $('.output').hide(); + + $(".upload-area").show(); + $(".preview-block").hide() + $("#imagePreview").hide().attr("src","") + iframe.src = iframe.src; + + // Clear html preview + accumulatedHtml = '' + // Reset the file input + document.getElementById('fileInput').value = '' + }); + + + + + +}); \ No newline at end of file diff --git a/src/main/resources/static/image2code.html b/src/main/resources/static/image2code.html new file mode 100644 index 0000000..ed7b6a9 --- /dev/null +++ b/src/main/resources/static/image2code.html @@ -0,0 +1,206 @@ + + + + + + Page Builder + + + + + + + + + + +
+
+
+
+

Page Builder

+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ Drag & drop a screenshot here,
+ or paste from clipboard,
+ or click to upload +
+
+ +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 4e64a3d..8c1d4a0 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -3,132 +3,47 @@ - image2code + Page Builder + + + - +
+
-
+ +
+ +
+
-

Image to Code

+

Page Builder

@@ -139,8 +54,86 @@ -
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+

基础需求

+
+ 行业: + +
+
+ 页面类型: + +
+
+ 业务: + +
+
+ 功能特色,亮点、优势等: + +
+
+ + +
+
+
+ +
+
+ 正在为您规划页面中 +
+
+
+ +
+

页面规划

+
+
+ +
+ + +
+
+
+ +
+
+ 正在书写代码 +
+
+
+
+ +