From 35bd13af3e2eff6ceb01fab3e41ac47b00eacad3 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Mon, 9 Jun 2025 19:18:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 40 ++++ pom.xml | 148 ++++++++++++++ .../com/lanyuanxiaoyao/ChatClientFactory.java | 66 ++++++ .../lanyuanxiaoyao/FunctionInformation.java | 15 ++ .../com/lanyuanxiaoyao/OutputInformation.java | 17 ++ .../lanyuanxiaoyao/PrecariatApplication.java | 188 ++++++++++++++++++ src/main/resources/application.yml | 12 ++ 7 files changed, 486 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/lanyuanxiaoyao/ChatClientFactory.java create mode 100644 src/main/java/com/lanyuanxiaoyao/FunctionInformation.java create mode 100644 src/main/java/com/lanyuanxiaoyao/OutputInformation.java create mode 100644 src/main/java/com/lanyuanxiaoyao/PrecariatApplication.java create mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30ef8ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +.idea \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c4757df --- /dev/null +++ b/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + com.lanyuanxiaoyao + precariat + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + 3.4.3 + 2024.0.1 + 1.0.0 + 3.3.1 + 11.1.0 + 5.8.27 + + + + + org.springframework.ai + spring-ai-starter-model-openai + + + org.projectlombok + lombok + + + cn.hutool + hutool-all + + + org.apache.poi + poi-ooxml + 5.4.1 + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + dev.failsafe + failsafe + 3.3.1 + + + org.eclipse.collections + eclipse-collections + ${eclipse-collections.version} + runtime + + + org.eclipse.collections + eclipse-collections-api + ${eclipse-collections.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + com.yomahub + liteflow-spring-boot-starter + 2.13.2 + + + org.noear + solon-ai + ${solon-ai.version} + + + org.noear + solon-ai-dialect-openai + ${solon-ai.version} + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + ${releases.id} + ${releases.name} + ${releases.url} + + + ${snapshots.id} + ${snapshots.name} + ${snapshots.url} + + + + \ No newline at end of file diff --git a/src/main/java/com/lanyuanxiaoyao/ChatClientFactory.java b/src/main/java/com/lanyuanxiaoyao/ChatClientFactory.java new file mode 100644 index 0000000..ebfb9ba --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/ChatClientFactory.java @@ -0,0 +1,66 @@ +package com.lanyuanxiaoyao; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author lanyuanxiaoyao + * @version 20250609 + */ +@Configuration +public class ChatClientFactory { + private static final String SILICONFLOW_URL = "https://api.siliconflow.cn"; + private static final String SILICONFLOW_API_KEY = "sk-xrguybusoqndpqvgzgvllddzgjamksuecyqdaygdwnrnqfwo"; + private static final String ZHIPU_URL = "https://open.bigmodel.cn/api/paas"; + private static final String ZHIPU_API_KEY = "d1e97306540d12bb2f834be961fcacb1.SNBShlCxWYJCx0qZ"; + + @Bean + public ChatClient chatClient() { + return ChatClient.builder( + OpenAiChatModel.builder() + .openAiApi( + OpenAiApi.builder() + .baseUrl(SILICONFLOW_URL) + .apiKey(SILICONFLOW_API_KEY) + .build() + ) + .defaultOptions( + OpenAiChatOptions.builder() + // .model("deepseek-ai/DeepSeek-R1") + .model("Qwen/Qwen3-8B") + // .model("Qwen/Qwen3-32B") + .build() + ) + .build() + ) + .build(); + } + + @Bean + public ChatClient visualChatClient() { + return ChatClient.builder( + OpenAiChatModel.builder() + .openAiApi( + OpenAiApi.builder() + .baseUrl(ZHIPU_URL) + .apiKey(ZHIPU_API_KEY) + .completionsPath("/v4/chat/completions") + // .baseUrl(SILICONFLOW_URL) + // .apiKey(SILICONFLOW_API_KEY) + .build() + ) + .defaultOptions( + OpenAiChatOptions.builder() + .model("glm-4v-flash") + // .model("Qwen/Qwen2.5-VL-72B-Instruct") + .build() + ) + .build() + ) + .build(); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/FunctionInformation.java b/src/main/java/com/lanyuanxiaoyao/FunctionInformation.java new file mode 100644 index 0000000..0e076a9 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/FunctionInformation.java @@ -0,0 +1,15 @@ +package com.lanyuanxiaoyao; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author lanyuanxiaoyao + * @version 20250609 + */ +@Data +@AllArgsConstructor +public class FunctionInformation { + private String name; + private String imagesPath; +} diff --git a/src/main/java/com/lanyuanxiaoyao/OutputInformation.java b/src/main/java/com/lanyuanxiaoyao/OutputInformation.java new file mode 100644 index 0000000..ebf7f67 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/OutputInformation.java @@ -0,0 +1,17 @@ +package com.lanyuanxiaoyao; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author lanyuanxiaoyao + * @version 20250609 + */ +@Data +@AllArgsConstructor +public class OutputInformation { + private String group; + private String name; + private String subName; + private String description; +} diff --git a/src/main/java/com/lanyuanxiaoyao/PrecariatApplication.java b/src/main/java/com/lanyuanxiaoyao/PrecariatApplication.java new file mode 100644 index 0000000..8f1cd01 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/PrecariatApplication.java @@ -0,0 +1,188 @@ +package com.lanyuanxiaoyao; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.hutool.poi.excel.ExcelUtil; +import cn.hutool.poi.excel.ExcelWriter; +import jakarta.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.content.Media; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.io.FileSystemResource; +import org.springframework.util.MimeTypeUtils; + +/** + * @author lanyuanxiaoyao + * @version 20250609 + */ +@Slf4j +@SpringBootApplication +public class PrecariatApplication implements ApplicationRunner { + private final List informationList = List.of( + // new FunctionInformation("数据中心-数据集管理", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/数据中心/数据集管理"), + // new FunctionInformation("数据中心-数据网盘", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/数据中心/数据网盘"), + // new FunctionInformation("数据中心-数据审批", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/数据中心/数据审批") + new FunctionInformation("标注中心-标签模板", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/标注中心/标签模板"), + new FunctionInformation("标注中心-我的任务", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/标注中心/我的任务"), + new FunctionInformation("标注中心-标注项目", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/标注中心/标注项目"), + new FunctionInformation("标注中心-标注场景", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/标注中心/标注场景"), + new FunctionInformation("标注中心-标注总览", "/Users/lanyuanxiaoyao/Pictures/星海AI中台/标注中心/标注总览") + ); + @Resource + private ChatClient chatClient; + @Resource + private ChatClient visualChatClient; + + public static void main(String[] args) { + SpringApplication.run(PrecariatApplication.class, args); + } + + private String describeScreenshoot(String imagePath) { + return describeScreenshoot(imagePath, null); + } + + private String describeScreenshoot(String imagePath, String extraPrompt) { + // language=TEXT + String prompt = """ + 任务:你是一个视觉大模型,需要精确分析并描述提供的网页截图内容。 + 要求:严格遵循以下指令进行客观描述: + + 1.核心要求: + 客观详尽: 只描述图像中实际可见的文字、布局、视觉元素及其关系。务必详尽、细致,避免任何遗漏。 + 零猜测/推断: 绝对禁止猜测功能、意图或页面未显示的信息。禁止任何推断性描述。 + 零额外内容: 仅输出对图像内容的描述,禁止任何解释、评论、总结、建议或与图像无关的信息。 + + 2.描述重点 (按优先级和结构): + 整体布局: 描述页面主要区域划分(如顶部栏、侧边栏、主内容区、底部栏)及其相对位置和大小比例(例如:左侧窄导航栏,右侧宽主内容区)。 + 文字内容: + 清晰、完整地转录所有可见文字(标题、标签、按钮文字、提示文本、列表内容文字等)。 + 保持文字出现的原始位置和视觉分组关系。 + 层级关系 (关键): + 菜单/导航栏: 精确描述菜单项及其层级。明确标注: + 哪些项是顶级菜单? + 哪些项是子菜单?隶属于哪个父项? + 菜单的当前展开/折叠状态(根据图像显示)。 + 选中的菜单项(如有高亮显示)。 + 页面结构: 描述区域间的包含关系(如:主内容区包含一个标题和一个数据列表)。 + 列表/表格 (关键): + 清晰列出所有表头/列标题的名称。 + 描述每列内容的形式(纯文本、数字、日期、带图标的标签、按钮等)。 + 避免列举具体数据行内容。 + 明确指出行末尾或行内是否存在可见的操作元素(如图标按钮、文字链接按钮),并描述其视觉特征(如:每行末尾有“编辑”图标按钮和“删除”文字链接)。 + 交互元素: 描述所有可见按钮、输入框、下拉选择框、单选框、复选框、选项卡等的文字标签和视觉状态(如:激活/未激活、选中/未选中)。 + 视觉元素: 描述图标(注明其旁边的文字或位置)、分割线、背景色块、图片占位符等及其位置。 + + 3.禁止事项 (强化): + 禁止遗漏: 确保扫描并描述了页面的所有可见区域和元素。 + 禁止功能猜测: 仅描述元素是什么(如:“删除”图标按钮),禁止描述其可能的功能(如:“用于删除数据的按钮”)。 + 禁止推断内容: 只描述图像中实际渲染出的文字和元素。例如,如果下拉菜单未展开,禁止描述其隐藏的选项。 + 禁止联想: 不基于页面内容联想其所属系统或业务场景。 + 禁止缩写: 转录文字时使用完整原文,除非原文本身就是缩写。 + 禁止输出无关信息: 严格只输出对图像内容的客观描述。 + + 输出: 你的输出应该是纯粹的、结构化的、详尽的图像内容描述文本,完全基于视觉输入。 + """; + if (StrUtil.isNotBlank(extraPrompt)) { + prompt += """ + 以下是关于该图片内容的补充描述: + + """ + extraPrompt; + } + return visualChatClient.prompt() + .messages( + UserMessage.builder() + .text(prompt) + .media( + Media.builder() + .mimeType(MimeTypeUtils.IMAGE_PNG) + .data(new FileSystemResource(imagePath)) + .build() + ) + .build() + ) + .call() + .content(); + } + + @Override + public void run(ApplicationArguments args) { + List lines = new ArrayList<>(); + for (FunctionInformation information : informationList) { + List descriptions = FileUtil.listFileNames(information.getImagesPath()) + .parallelStream() + .filter(name -> StrUtil.equals(FileUtil.extName(name), "png")) + .map(name -> FileUtil.file(information.getImagesPath(), name)) + .map(FileUtil::getAbsolutePath) + .map(imagePath -> { + String description = describeScreenshoot(imagePath); + log.info("{}\n{}", imagePath, description); + return description; + }) + .toList(); + String response = chatClient.prompt(StrUtil.format( + // language=TEXT + """ + 你将被提供某个管理系统关于“{}”功能的所有相关网页内容描述。 + 这些描述包含页面元素、功能名称、以及同一页面在不同操作(如点击按钮、选择下拉框)后的状态变化。请仔细分析,合并描述中相同的页面内容和功能。 + + 1.功能列表分析要求 + 核心目标:从提供的网页描述中,详尽识别并抽象出所有业务逻辑功能。 + 分析原则: + 聚焦逻辑: 避免直接罗列按钮、下拉框等操作元素。必须将用户操作(点击、选择)或基础的UI功能(列表分页,表单项校验如字数、格式等)抽象总结为它们所实现的核心业务逻辑功能(例如,“点击‘保存’按钮”应抽象为“保存XX信息”功能)。 + 识别功能层级:区分主功能(父功能)和其下的子功能(操作、步骤、分支逻辑)。 + 合并相同功能:同一逻辑功能在不同页面或状态下被多次描述时,应合并为一项。 + 排除视觉/样式:忽略对颜色、字体、简单布局等纯粹视觉或样式元素的描述,它们不构成独立功能。 + 严格基于描述:所有输出的功能必须明确来源于提供的页面描述内容,禁止臆测或添加描述中不存在的信息。 + 详尽无遗漏:务必仔细梳理所有描述,确保识别出每一个描述中体现的逻辑功能点,避免遗漏。 + 输出内容:详细的功能列表及其描述。 + + 2.输出格式要求 + 使用严格的表格形式输出功能列表。 + 表格包含三列: + 1.功能名称: 主功能的名称,表述其核心业务目的。 + 2.子功能名称: 隶属于主功能的子操作、步骤或分支功能的名称。如果主功能没有子功能或本身是原子操作,此栏可为空。 + 3.功能描述: 对该功能(主功能或子功能)做什么进行清晰、详细的描述,保证在没有上下文的前提下也能够将功能、逻辑、业务等信息描述完整。 + 输出符合如下json结构的结果 + [ + {"name": "功能名称", "sub_name": "子功能名称", "description": "功能描述"} + ] + + 3.输入数据 + 以下内容是严格基于视觉大模型读取的网页信息生成的详细描述。不同的页面(或同一页面的不同状态)使用“------”分隔: + {}""", + StrUtil.join("\n------\n", descriptions) + )) + .call() + .content(); + log.info(response); + JSONArray array = JSONUtil.parseArray(StrUtil.removeAny(StrUtil.trim(response), "```json", "```")); + for (Object o : array) { + JSONObject obj = (JSONObject) o; + lines.add(new OutputInformation( + information.getName(), + obj.getStr("name"), + obj.getStr("sub_name"), + obj.getStr("description") + )); + } + } + try (ExcelWriter writer = ExcelUtil.getWriter(StrUtil.format("/Users/lanyuanxiaoyao/Project/Precariat/{}.xlsx", DateUtil.current()))) { + writer.addHeaderAlias("group", "一级功能"); + writer.addHeaderAlias("name", "二级功能"); + writer.addHeaderAlias("subName", "三级功能"); + writer.addHeaderAlias("description", "功能描述"); + writer.write(lines); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..f47dce0 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,12 @@ +spring: + main: + web-application-type: none + autoconfigure: + exclude: | + org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration, + org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration, + org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration, + org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration, + org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration, + org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration, + org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration