Compare commits

..

43 Commits

Author SHA1 Message Date
史悦
d74fbce137 现在支持的格式
-  ```svg<svg... 
  -  ```svg <svg... 
  -  ```xml<svg... 
  -  ```xml <svg... 
  -  ``` xml<svg... 
  -  ``` svg<svg... 
  -  ```<svg... (无语言标识)

  这样无论 LLM 输出哪种格式,都能正确实时绘制和复制图片到剪贴板了。
2026-02-03 10:55:17 +08:00
史悦
0f68b6d7a2 注释 volumes 2026-02-03 10:37:16 +08:00
史悦
b82297efc3 增加了图片上传解析的功能 2026-02-03 10:09:44 +08:00
史悦
69a177b85a thinksvg 2026-01-27 17:19:43 +08:00
史悦
19afa19462 优化APIClient和conversation-service优先使用rawContent提升上下文完整性;调整app-shell.js中对话气泡流式内容渲染逻辑及HTML模块特殊处理;提升STREAM_DEFAULT_OPTIONS maxTokens至30000;重构onepage-prompt.txt交付描述;大幅美化产品画布页面UI,采用Apple和Google风格边框、按钮、气泡,优化配色、阴影、交互效果和模态窗样式,提升整体视觉和交互体验。 2025-11-19 11:28:44 +08:00
史悦
24adda3f14 修改了README 2025-11-06 14:18:44 +08:00
史悦
c1802352f5 新增“落地页全屏预览”模态窗及 iframe,配合 css的 98% 视窗自适应样式,实现接近全屏的预览 2025-11-06 13:50:40 +08:00
史悦
71cce80e2d 修改提示词 2025-11-06 13:26:22 +08:00
史悦
2a3d16fca2 html流式响应 2025-11-06 12:12:08 +08:00
史悦
f4014bd25d 增加onepage页面 2025-11-06 12:03:58 +08:00
史悦
21209ba1a8 onepage 2025-11-06 11:23:32 +08:00
史悦
5665715a99 - 在 js/core/app-shell.js:1153 为 initializeMermaidPanZoom 增加空值保护,避免找不到目标元素时直接
返回。
  - 新增 applyMermaidPanZoom 辅助方法(js/core/app-shell.js:1227),统一为最新渲染的 Mermaid SVG 设置稳
    定的 id 并重新初始化 svgPanZoom,保证缩放实例始终存在。
  - 在流式渲染完成的循环中追加 this.applyMermaidPanZoom(manifest)(js/core/app-shell.js:1329),生成最后
    一个代码片段后立即恢复缩放能力。
  - 在最终渲染回写与常规渲染入口分别调用新方法(js/core/app-shell.js:1397, js/core/app-shell.js:1451),
    确保无论是流式结束还是切换历史记录都能自动启用平移缩放。
2025-10-28 15:49:00 +08:00
史悦
6824a091ac 修改了mermaid的导出放大倍数 2025-10-28 15:40:12 +08:00
史悦
fd518aa25e 增加了图片 2025-10-28 15:24:57 +08:00
史悦
de56b304bc 开源地址改为_blank 2025-10-28 14:20:33 +08:00
史悦
a23e5f2a57 修改了标题 2025-10-28 14:04:22 +08:00
史悦
9c72a9e273 - 在 js/core/app-shell.js:72-89 动态创建“在Mermaid中编辑”按钮,挂载到工具栏末尾并默认隐藏,避免在非
Mermaid 模块占位。
  - 在 js/core/app-shell.js:201-204 为新按钮绑定点击事件,触发在线编辑入口。
  - 在 js/core/app-shell.js:2232-2235 根据当前模块与图形状态控制按钮显隐与可用性,仅在 Mermaid 且已有图
    形时启用。
  - 在 js/core/app-shell.js:1560-1592 新增 openMermaidLiveEditor,复用现有 Pako 编码生成 https://
    mermaid.live/edit#pako:... 链接并通过 window.open 打开,附带异常与弹窗拦截提示。
2025-10-28 14:03:10 +08:00
史悦
9436ccf83a 修改了提示 2025-10-28 13:39:08 +08:00
史悦
54f1dffab6 放了一个图标 2025-10-28 13:00:57 +08:00
史悦
2106119fb4 修改了提示词和图片 2025-10-28 10:51:54 +08:00
史悦
e1ffd14bc1 新增结尾补渲逻辑 ensureFinalMermaidRender 并在 finalizeAssistantMessage 中调用(js/core/app-
shell.js:916-1355),即便流式阶段已渲染过,也会在响应完成后使用最终完整代码再渲染一次,同时更新已保
    存的 artifact,解决末尾缺失的问题。
2025-10-28 10:12:31 +08:00
史悦
71cfa133a6 mermaid 改为流式 2025-10-28 09:53:37 +08:00
史悦
7bcfadde59 - 在 js/core/app-shell.js:1275 引入 window.mermaid.parse(code) 语法校验,若捕获异常则抛出带有“Mermaid
语法错误”描述的错误,阻断后续渲染流程并避免生成无效图表。
  - 在 js/core/app-shell.js:1244 的渲染异常分支中统一处理错误信息,既在查看区域展示,又在输入框末尾追加
    (避免重复追加),并自动调整输入框高度,方便用户基于错误提示修改 Mermaid 代码。
2025-10-28 09:19:07 +08:00
史悦
298e421f7d 修改样式和提示词 2025-10-28 09:14:19 +08:00
史悦
dd930805e0 修改了提示词 2025-10-27 17:59:17 +08:00
史悦
c03be8d65a 增加了几个快捷按钮 2025-10-27 17:14:49 +08:00
史悦
e7b1351384 修改删除按钮的样式 2025-10-27 16:53:20 +08:00
史悦
a210a6f92d 删除无用库 2025-10-27 16:47:22 +08:00
史悦
6d453b2519 修改样式 2025-10-27 16:20:46 +08:00
史悦
86b83dd39a 下载图片 2025-10-27 16:13:26 +08:00
史悦
ae46cedd37 1. 在浏览器中切换至 Mermaid 模块生成示例图,确认 SVG 容器占满可用空间且居中显示。
2. 若存在缓存,建议强制刷新(Ctrl+F5)以确保最新样式生效。
2025-10-27 15:10:06 +08:00
史悦
f7fd53c9a2 放大缩小 2025-10-27 15:05:35 +08:00
史悦
4dde0e31b1 放大缩小 2025-10-27 13:50:34 +08:00
史悦
01e1083e5e 资源改为本地引用 2025-10-27 12:25:23 +08:00
史悦
f37357096e • - 在 renderConversationHistory 末尾与 renderArtifact 内部新增 highlightActivePlaceholder(),每次渲染或
切换图形后都会重新标记当前选中的占位卡片。
  - 新方法会清除所有 .svg-placeholder-block 上的 svg-placeholder-active,再根据 ModuleRuntime 记录的
    currentArtifactId 为对应占位卡片添加该类(js/core/app-shell.js:321, 673, 729)。
  - 这样无论是点击左侧占位切换、完成流式渲染或模块切换,右侧当前图形都会同步点亮对应占位符,恢复过往的高
    亮效果。
2025-10-27 11:27:52 +08:00
史悦
533375e8ca 调整了整个框架,模块化解耦 2025-10-27 11:04:00 +08:00
史悦
c83c36baa6 - 在 js/app.js:728-766 新增 buildDeleteButtonHtml,并在 js/app.js:1000-1034 将浮动删除按钮植入用户、AI
与错误消息气泡,同时阻断事件冒泡以免干扰 SVG 点击;带 SVG 的消息亦在 js/app.js:1042-1054 注入同样的
    删除入口。
  - 通过 js/app.js:870-924 的 confirmDeleteMessage 与 deleteMessagePermanently,删除前弹出不可恢复提示,
    确认后同步清理当前模式的对话历史、关联 SVG 缓存以及本地存储,必要时自动回退至占位视图。
  - 在 css/style.css:235-266 定义 message-with-delete 与 message-delete-btn,实现右上角高亮悬浮按钮及
    hover 反馈,保证在深浅背景下均可清晰识别。
2025-10-27 09:51:28 +08:00
史悦
4b14bb26dd - 在 index.html:47-51 给 main 和左侧面板增加 min-h-0,允许网格子项在父级 flex 容器中释放高度,确保
#chat-history 的 overflow-y-auto 生效;右侧展示区同样设置 min-h-0,防止 SVG 区域被异常拉伸。
  - 调整后,长对话会保持面板固定高度,滚动条承载多余内容,不再把整个页面撑出视窗。
2025-10-27 09:42:16 +08:00
史悦
b0c487a4ef 6. 增加一个svg放大缩小功能;
7. 目前的svg下载,下载图片,显示代码实现有问题,好像没找到svg;再增加一个复制图片到剪切板功能;
8. 点重新生成按钮,应该再添加一个气泡啊,而且流式响应,现在点击重新生成,就时等待,没有实时显示;
9. 点击查看画布,没有区分现在显示的是哪个气泡或占位符,需要标记区分下;
2025-10-24 20:31:49 +08:00
史悦
cbf59e3450 - js/app.js:165-260, 310-420, 560-893:重构模式切换与流式渲染逻辑;引入 renderSvgViewerForMode 分离各
模式历史与SVG;通过 buildActionToolbar 和 renderConversationHistory 仅为最新 AI 气泡保留“重新生成”;
    新增 setSendButtonState、startStreamingMessage、cancelActiveStream 实现终止按钮与流式中断,回滚后同
    步复位查看面板。
  - js/utils.js:24-334:增强 parseSVGResponse 容错截断SVG/反引号;StreamProcessor 增加完成态管理;
    createStreamRequest 返回 {cancel, finished} 并支持 AbortController,保证中止时回调依然收尾。
  - js/apiclient.js:150-208:流式接口返回新的句柄对象而非简单等待,使前端可触发中止,同时保持异常转换。
  - css/style.css:220-224:去除悬浮显隐,操作按钮常显,并保留色彩过渡以提示交互。
  - js/app.js:900-970:regenerateMessage 改用“处理中”状态并重绘气泡(按最新可见状态),避免历史重复
    生成。
2025-10-24 19:56:40 +08:00
史悦
06e1d5ca19 修改了提示词 2025-10-24 19:18:03 +08:00
史悦
64e93d25b8 - 在 js/utils.js:24-79 强化 parseSVGResponse,兼容缺失结尾反引号和截断的 SVG,自动补齐 </svg> 并去除残
留的 ```,确保后续渲染能稳定提取图形。
2025-10-24 19:03:40 +08:00
史悦
bacafd66dc 修复气泡 2025-10-24 18:52:18 +08:00
48 changed files with 9563 additions and 2352 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
AGENTS.md

14
.kilocode/mcp.json Normal file
View File

@@ -0,0 +1,14 @@
{
"mcpServers": {
"canva-dev": {
"command": "npx",
"args": [
"-y",
"@canva/cli@latest",
"mcp"
],
"disabled": false,
"alwaysAllow": []
}
}
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}

3
Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM lipanski/docker-static-website:latest
COPY . .

211
README.md Normal file
View File

@@ -0,0 +1,211 @@
# 产品分析工具
一个基于AI对话的产品战略分析工具帮助用户快速生成产品画布、SWOT、分析图表等支持产品规划和决策。
![纯前端JS](https://img.shields.io/badge/纯前端-JS-informational)
![AI驱动](https://img.shields.io/badge/AI驱动--important)
## 🌟 功能特点
### 🔄 多模式切换系统
- **产品画布模式**:生成和展示产品画布图表
- **SWOT分析模式**生成和展示SWOT分析图表
- **ECharts图表模式**:生成数据可视化图表
- **Mermaid图表模式**:生成流程图和关系图
- **落地页生成模式**:基于提示词流式生成完整 HTML 落地页并实时预览
![](/设计/images/mermaid.jpeg)
![](/设计/images/echart.jpeg)
![](/设计/images/huabu.jpeg)
![](/设计/images/swot.jpeg)
![](/设计/images/peizhi.jpeg)
![](/设计/images/onepage.png)
### 💬 AI对话交互系统
- **智能对话**:基于自然语言的产品分析请求
- **消息发送**用户输入文本请求支持Enter键快捷发送
- **对话历史**保存并展示用户与AI的完整对话记录
- **消息操作**:支持退回、重新生成、删除消息
### 📊 图表生成与展示系统
- **SVG图表生成**根据用户请求生成相应的SVG图表
- **图表占位符**:对话中显示可点击的图表预览块
- **图表渲染**点击占位符在右侧面板完整展示SVG图表
- **图表存储**本地存储生成的SVG内容支持历史查看
- **缩放控制**:支持图表的放大、缩小和重置
### 🛠️ 图表导出功能
- **SVG下载**将当前图表导出为SVG文件
- **图片导出**将当前图表导出为PNG图片格式
- **剪贴板复制**:复制图表图片到剪贴板
- **代码查看**查看当前图表的SVG源代码
- **页面预览**:对落地页生成结果进行 98% 视窗尺寸的模态预览或一键新窗口打开
## **所有数据均由浏览器本地缓存,清空后就清空了💨**
## 🚀 快速开始
### 环境要求
- 现代浏览器支持ES6+
#### 方法1本地运行
下载下来用GoLive启动下或者直接打开`index.html`
#### 方法2使用Docker
```bash
# 构建并运行容器
docker-compose up -d
# 在浏览器中访问
open http://localhost:3000
```
docker-compose.yml
``` docker-compose.yml
version: '3.8'
services:
product-canvas:
image: 935732994/pmtools
ports:
- "3000:3000"
restart: unless-stopped
```
### 配置API
1. 点击右上角的"API配置"按钮
2. 填写以下信息:
- **API URL**您的AI服务API地址
- **API Key**您的API密钥
- **模型**选择使用的AI模型如gpt-4
3. 点击"保存配置"完成设置
4. 点击"测试连接"验证配置,先保存再测试
> 有些API会出现 失败情况 `连接测试失败: NetworkError when attempting to fetch resource.` 这时需要找不失败的💨
## 📁 项目结构
```
产品画布/
├── index.html # 主页面
├── css/
│ └── style.css # 自定义样式
├── js/
│ ├── app.js # 应用入口
│ ├── apiclient.js # API客户端
│ ├── utils.js # 工具函数
│ ├── core/ # 核心模块
│ │ ├── app-shell.js # 应用外壳
│ │ ├── module-registry.js # 模块注册表
│ │ └── module-runtime.js # 模块运行时
│ ├── modules/ # 功能模块
│ │ ├── product-canvas.js # 产品画布模块
│ │ ├── swot.js # SWOT分析模块
│ │ ├── echarts.js # ECharts图表模块
│ │ ├── mermaid.js # Mermaid图表模块
│ │ └── onepage.js # 落地页生成模块
│ ├── services/ # 服务层
│ │ ├── conversation-service.js # 对话服务
│ │ └── storage-service.js # 存储服务
│ └── renderers/ # 渲染器
├── libs/ # 第三方库
│ ├── css/ # 样式库
│ ├── fonts/ # 字体文件
│ └── js/ # JavaScript库
├── prompts/ # AI提示词
│ ├── canvas-prompt.txt # 产品画布提示词
│ ├── swot-prompt.txt # SWOT分析提示词
│ ├── echarts-prompt.txt # ECharts提示词
│ ├── mermaid-prompt.txt # Mermaid提示词
│ └── onepage-prompt.txt # 落地页提示词
├── 设计/ # 设计文件
│ └── 原型.html # 设计原型
├── 功能概述.md # 功能说明文档
├── docker-compose.yml # Docker编排文件
├── Dockerfile # Docker镜像构建文件
└── README.md # 项目说明文档
```
## 🎯 使用指南
### 基本使用流程
1. **选择模式**:点击顶部的模式按钮选择所需的分析类型
2. **输入需求**:在左侧对话框中描述您的产品或分析需求
3. **生成图表**AI将根据您的需求生成相应的分析图表
4. **查看图表 / 页面**:点击对话中的占位符在右侧查看完整内容
5. **导出结果**:使用底部工具栏导出、复制或全屏预览
### 产品画布模式
产品画布模式帮助您快速构建产品战略框架:
```
示例输入:
"请为我的社区废品回收智能终端系统生成一个产品画布"
```
生成的画布包含:
- 问题分析
- 客户群体
- 独特卖点
- 解决方案
- 渠道策略
- 收入分析
- 成本分析
- 关键指标
- 门槛优势
### SWOT分析模式
SWOT分析模式帮助您评估项目的优势、劣势、机会和威胁
```
示例输入:
"请对我的在线教育平台进行SWOT分析"
```
生成的分析包含:
- **优势(Strengths)**:内部有利因素
- **劣势(Weaknesses)**:内部不利因素
- **机会(Opportunities)**:外部有利因素
- **威胁(Threats)**:外部不利因素
### 高级功能
#### 消息操作
- **退回**:回退到指定消息,删除该消息之后的所有对话
- **重新生成**针对最后一条AI消息重新请求生成内容
- **删除**:删除特定消息及其关联的图表
#### 图表操作
- **缩放控制**:使用工具栏按钮调整图表大小
- **全屏查看**:在右侧面板获得更好的查看体验
- **多格式导出**支持SVG、PNG等多种导出格式
- **落地页预览**:使用“预览落地页”按钮打开 98% 视窗大小的模态窗口,可再一键在新标签页打开完整 HTML
## 🔧 开发指南
### 技术栈
- **前端框架**原生JavaScript (ES6+)
- **样式框架**Tailwind CSS
- **图标库**Iconify
- **图表库**ECharts, Mermaid
- **Markdown解析**Marked.js
- **字体**Inter Font
## 📝 更新日志
### 2025年11月6日
- 🚀 增加了落地页模块
### v1.0.0 (2025年10月27日)
- ✨ 初始版本发布
---
⭐ 如果这个项目对您有帮助,请给我们一个星标!

8
config.json Normal file
View File

@@ -0,0 +1,8 @@
{
"enableVision": true,
"imageConfig": {
"maxCount": 4,
"maxSizeMB": 4,
"allowedTypes": ["image/jpeg", "image/png", "image/webp", "image/gif"]
}
}

View File

@@ -2,6 +2,15 @@ body {
font-family: 'Inter', sans-serif;
}
/* 统一处理 Iconify 图标的对齐方式,避免在按钮与文字中出现垂直偏移 */
iconify-icon {
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
line-height: 1;
}
/* 狂野线条效果 */
.wild-border {
border: 3px solid;
@@ -28,6 +37,9 @@ body {
box-shadow: 2px 2px 0 rgba(0,0,0,0.2);
}
.chat-bubble-user .bubble-action-btn {
color: white;
}
.chat-bubble-ai {
background: #fff;
color: #1f2937;
@@ -219,14 +231,80 @@ body {
/* 气泡操作按钮 */
.bubble-action-btn {
opacity: 0;
transition: opacity 0.2s;
opacity: 1;
transition: color 0.2s ease;
}
.chat-bubble-ai:hover .bubble-action-btn {
opacity: 1;
/* 消息删除浮动按钮 */
.message-with-delete {
position: relative;
padding-right: 48px;
}
.message-delete-btn {
position: absolute;
top: 8px;
right: 8px;
width: 30px;
height: 30px;
border-radius: 9999px;
border: 2px solid #1f2937;
background: rgba(255, 255, 255, 0.92);
color: #dc2626;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 2px 2px 0 rgba(0,0,0,0.2);
transition: transform 0.2s ease, background 0.2s ease, color 0.2s ease;
}
.message-delete-btn:hover {
background: #dc2626;
color: #ffffff;
transform: scale(1.05);
}
.message-delete-btn iconify-icon {
font-size: 16px;
}
.svg-placeholder-block {
position: relative;
}
@keyframes svg-active-pulse {
0% { box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35), 0 6px 12px rgba(15, 23, 42, 0.15); }
50% { box-shadow: 0 0 0 6px rgba(37, 99, 235, 0.15), 0 10px 18px rgba(15, 23, 42, 0.2); }
100% { box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35), 0 6px 12px rgba(15, 23, 42, 0.15); }
}
.svg-placeholder-active {
border-color: #1d4ed8;
background: linear-gradient(135deg, #e0f2fe 0%, #dbeafe 100%);
color: #1e3a8a;
transform: translateY(-2px);
/* animation: svg-active-pulse 1.6s ease-in-out infinite; */
}
.svg-content-wrapper {
/* flex: 1; */
/* margin: 1rem; */
display: inline-block;
/* text-align: center; */
transform-origin: center top;
}
.svg-content-wrapper--mermaid {
flex: 1;
margin: 1rem;
text-align: center;
width: 100%;
height: 100%;
object-fit: contain;
max-width: 100% !important;
}
/* 小手摇摆动画 */
@keyframes wave {
0%, 100% {transform: translateX(0px) rotate(90deg);}
@@ -268,6 +346,53 @@ body {
overflow-y: auto;
}
.code-modal-content {
max-width: 760px;
}
.page-preview-modal {
width: 98vw;
height: 98vh;
max-width: 98vw;
max-height: 98vh;
display: flex;
flex-direction: column;
padding: 0;
}
.page-preview-body {
flex: 1;
background: #0f172a;
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
}
.page-preview-iframe {
width: 100%;
height: 100%;
border: 3px solid #000;
background: white;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.45);
}
.code-viewer {
background: #0f172a;
color: #f8fafc;
border: 2px solid #000;
padding: 16px;
font-family: 'Fira Code', 'Courier New', Courier, monospace;
font-size: 14px;
line-height: 1.6;
border-radius: 6px;
max-height: 60vh;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
/* 表单输入框样式 */
.config-input {
width: 100%;
@@ -349,3 +474,228 @@ body {
.clear-history-btn:hover iconify-icon {
animation: shake 0.5s ease-in-out;
}
#send-button.terminate-mode {
border-color: #dc2626;
}
#mermaidSvg {
width: 100%;
height: 100%;
object-fit: contain;
max-width: 100% !important;
}
#dmermaidSvg{ height: 0px;}
/* ========== 图片上传相关样式 ========== */
/* 图片上传按钮 */
.image-upload-btn {
flex-shrink: 0;
border-radius: 8px;
transition: all 0.2s ease;
}
.image-upload-btn:hover {
background: rgba(6, 182, 212, 0.1);
transform: scale(1.05);
}
.image-upload-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.image-upload-btn:disabled:hover {
background: transparent;
transform: none;
}
/* 图片缩略图容器 */
#image-preview-container {
padding: 8px;
background: rgba(255, 255, 255, 0.8);
border: 2px dashed #d1d5db;
border-radius: 8px;
}
#image-preview-container:not(.hidden) {
display: flex;
}
/* 单个图片缩略图项 */
.image-thumbnail-item {
position: relative;
width: 72px;
height: 72px;
border-radius: 8px;
overflow: hidden;
border: 2px solid #e5e7eb;
background: #f9fafb;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.image-thumbnail-item:hover {
border-color: #06b6d4;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.image-thumbnail-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 图片删除按钮 */
.image-thumbnail-delete {
position: absolute;
top: 2px;
right: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(220, 38, 38, 0.9);
color: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 10;
}
.image-thumbnail-item:hover .image-thumbnail-delete {
opacity: 1;
}
.image-thumbnail-delete:hover {
background: #dc2626;
transform: scale(1.1);
}
.image-thumbnail-delete iconify-icon {
font-size: 12px;
}
/* 图片数量限制提示 */
.image-count-badge {
position: absolute;
bottom: 2px;
left: 2px;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: 10px;
padding: 1px 4px;
border-radius: 4px;
}
/* 图片预览模态窗 */
.image-preview-modal-content {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 90vw;
height: 90vh;
background: rgba(0, 0, 0, 0.95);
border-radius: 8px;
}
.image-preview-modal-content img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 4px;
}
/* 聊天气泡中的图片显示 */
.chat-bubble-images {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 8px;
}
.chat-bubble-image {
width: 80px;
height: 80px;
border-radius: 6px;
overflow: hidden;
border: 2px solid rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: all 0.2s ease;
}
.chat-bubble-image:hover {
transform: scale(1.05);
border-color: rgba(255, 255, 255, 0.6);
}
.chat-bubble-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 图片加载中状态 */
.image-loading {
display: flex;
align-items: center;
justify-content: center;
background: #f3f4f6;
}
.image-loading::after {
content: '';
width: 24px;
height: 24px;
border: 2px solid #d1d5db;
border-top-color: #06b6d4;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 图片上传拖拽区域高亮 */
.image-drop-active {
background: rgba(6, 182, 212, 0.1) !important;
border-color: #06b6d4 !important;
border-style: solid !important;
}
/* 图片大小/格式错误提示 */
.image-error-toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: #dc2626;
color: white;
padding: 12px 20px;
border-radius: 8px;
font-weight: 500;
z-index: 9999;
animation: toast-in 0.3s ease;
}
@keyframes toast-in {
from {
opacity: 0;
transform: translateX(-50%) translateY(20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}

15
docker-compose.yml Normal file
View File

@@ -0,0 +1,15 @@
version: '3.8'
services:
product-canvas:
image: 935732994/pmtools
ports:
- "3000:3000"
container_name: product-canvas-app
restart: unless-stopped
environment:
# 是否启用图片解析功能Vision API默认启用
- ENABLE_VISION=true
#volumes:
# 可选:挂载自定义配置文件覆盖默认配置
# - ./config.json:/usr/share/nginx/html/config.json:ro

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -3,13 +3,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>产品画布 / SWOT分析</title>
<script src="https://cdn.tailwindcss.com/3.4.1"></script>
<script src="https://code.iconify.design/iconify-icon/2.1.0/iconify-icon.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<title>PMAI工具</title>
<script src="libs/css/tailwind.css"></script>
<script src="libs/js/iconify-icon.min.js"></script>
<script src="libs/js/marked.min.js"></script>
<link rel="stylesheet" href="libs/css/inter-font.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body class="bg-gray-100 h-screen flex flex-col">
@@ -19,34 +17,31 @@
<div class="flex items-center space-x-2">
<iconify-icon icon="ph:lightning-fill" class="text-3xl text-white"></iconify-icon>
<h1 id="page-title" class="text-2xl font-black text-white tracking-tight">产品画布</h1>
<a target="_blank" href="https://github.com/shixixiyue/pmtools" class="translate-x-8">
<img src="https://img.shields.io/badge/开源地址-GitHub-informational">
</a>
</div>
<!-- 右侧按钮组 -->
<div class="flex items-center gap-3">
<!-- API配置按钮 -->
<button id="settings-btn" class="settings-btn bg-white/20 text-white p-2 border-2 border-white hover:bg-white/30 transition-all" title="API配置">
API配置
<iconify-icon icon="ph:gear-six-fill" class="text-2xl"></iconify-icon>
</button>
<span class="text-white font-bold text-sm">点击切换模</span>
<span class="text-white font-bold text-sm">点击切换模</span>
<iconify-icon icon="ph:hand-pointing-fill" class="text-2xl text-yellow-300 wave-hand"></iconify-icon>
<button id="canvas-mode-btn" class="mode-btn-active bg-white text-orange-600 px-4 py-2 font-bold border-2 border-black hover:bg-orange-50 transition-all duration-200">
<iconify-icon icon="ph:pen-nib-duotone" class="align-middle mr-1"></iconify-icon>
产品画布
</button>
<button id="swot-mode-btn" class="mode-btn-inactive bg-white text-purple-600 px-4 py-2 font-bold border-2 border-black hover:bg-purple-50 transition-all duration-200">
<iconify-icon icon="ph:chart-bar-duotone" class="align-middle mr-1"></iconify-icon>
SWOT分析
</button>
<div id="module-button-group" class="flex items-center gap-2"></div>
</div>
</header>
<!-- 主内容区 -->
<main class="flex-1 grid grid-cols-1 md:grid-cols-3 gap-4 p-4 overflow-hidden">
<main class="flex-1 min-h-0 grid grid-cols-1 md:grid-cols-3 gap-4 p-4 overflow-hidden">
<!-- 左侧对话面板 -->
<div class="md:col-span-1 bg-white wild-border border-cyan-500 flex flex-col">
<div class="md:col-span-1 bg-white wild-border border-cyan-500 flex flex-col min-h-0">
<!-- 对话历史顶部栏 -->
<div class="p-3 border-b-3 border-gray-300 bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-between">
<div class="flex items-center gap-2">
@@ -70,10 +65,19 @@
<!-- 输入区 -->
<div class="p-3 border-t-3 border-gray-300 bg-yellow-50">
<!-- 图片缩略图预览区 -->
<div id="image-preview-container" class="mb-2 flex flex-wrap gap-2 hidden">
<!-- 动态插入图片缩略图 -->
</div>
<div class="relative flex items-center gap-2">
<!-- 图片上传按钮 -->
<button id="image-upload-btn" class="image-upload-btn text-gray-500 hover:text-cyan-600 transition-colors p-2" title="上传图片 (支持粘贴)">
<iconify-icon icon="ph:image-bold" class="text-2xl"></iconify-icon>
</button>
<input type="file" id="image-file-input" accept="image/jpeg,image/png,image/webp,image/gif" multiple class="hidden" />
<textarea
id="chat-input"
placeholder="输入您的想法按Enter发送Shift+Enter换行..."
placeholder="输入您的想法按Enter发送Shift+Enter换行,可粘贴图片..."
class="flex-1 auto-resize-input border-2 border-gray-800 focus:border-cyan-500 focus:outline-none transition-colors font-medium"
rows="1"
></textarea>
@@ -81,11 +85,17 @@
<iconify-icon icon="ph:paper-plane-tilt-fill" class="text-3xl"></iconify-icon>
</button>
</div>
<!-- 图片功能提示 -->
<div id="image-vision-disabled-tip" class="mt-1 text-xs text-gray-400 hidden">
<iconify-icon icon="ph:info" class="align-middle"></iconify-icon>
图片解析功能已禁用
</div>
<div id="chat-quick-actions" class="mt-2 flex flex-wrap gap-2 hidden"></div>
</div>
</div>
<!-- 右侧显示面板 -->
<div class="md:col-span-2 bg-white wild-border border-purple-600 flex flex-col">
<div class="md:col-span-2 bg-white wild-border border-purple-600 flex flex-col min-h-0">
<div id="svg-viewer" class="flex-1 flex items-center justify-center p-4 bg-gradient-to-br from-purple-50 to-pink-50 overflow-auto">
<div id="svg-placeholder" class="text-center text-gray-400">
<iconify-icon icon="ph:image-square" class="text-6xl mx-auto text-purple-400"></iconify-icon>
@@ -95,12 +105,27 @@
<!-- 底部操作栏 -->
<div class="p-3 border-t-3 border-gray-300 flex justify-end items-center gap-2 bg-gray-800">
<button id="zoom-out-btn" class="p-2 bg-white text-gray-700 border-2 border-black hover:bg-gray-200 transition-all" title="缩小">
<iconify-icon icon="ph:magnifying-glass-minus-bold" class="text-xl"></iconify-icon>
</button>
<button id="zoom-in-btn" class="p-2 bg-white text-gray-700 border-2 border-black hover:bg-gray-200 transition-all" title="放大">
<iconify-icon icon="ph:magnifying-glass-plus-bold" class="text-xl"></iconify-icon>
</button>
<button id="zoom-reset-btn" class="p-2 bg-white text-gray-700 border-2 border-black hover:bg-gray-200 transition-all" title="重置缩放">
<iconify-icon icon="ph:arrow-counter-clockwise-bold" class="text-xl"></iconify-icon>
</button>
<button id="download-svg-btn" class="p-2 bg-orange-500 text-white border-2 border-black hover:bg-orange-600 transition-all" title="下载SVG">
<iconify-icon icon="mdi:download-outline" class="text-xl"></iconify-icon>
</button>
<button id="copy-image-btn" class="p-2 bg-yellow-500 text-white border-2 border-black hover:bg-yellow-600 transition-all" title="复制图片到剪贴板">
<iconify-icon icon="ph:clipboard-bold" class="text-xl"></iconify-icon>
</button>
<button id="export-image-btn" class="p-2 bg-green-500 text-white border-2 border-black hover:bg-green-600 transition-all" title="导出为图片">
<iconify-icon icon="mdi:image-outline" class="text-xl"></iconify-icon>
</button>
<button id="open-page-btn" class="p-2 bg-cyan-500 text-white border-2 border-black hover:bg-cyan-600 transition-all" title="预览落地页">
<iconify-icon icon="ph:monitor-play-bold" class="text-xl"></iconify-icon>
</button>
<button id="view-code-btn" class="p-2 bg-blue-500 text-white border-2 border-black hover:bg-blue-600 transition-all" title="查看代码">
<iconify-icon icon="mdi:code-tags" class="text-xl"></iconify-icon>
</button>
@@ -134,7 +159,7 @@
<input
id="api-url"
type="text"
placeholder="https://api.example.com/v1/chat"
placeholder="https://api.example.com/v1/chat/completions"
class="config-input"
value=""
/>
@@ -170,6 +195,21 @@
/>
</div>
<!-- 图片解析开关 -->
<div class="flex items-center justify-between py-3 border-t-2 border-gray-200 mt-2">
<div class="flex items-center gap-2">
<iconify-icon icon="ph:eye-bold" class="text-lg text-cyan-600"></iconify-icon>
<div>
<label class="block font-bold text-gray-800">启用图片解析</label>
<span class="text-xs text-gray-500">允许上传图片并由AI进行视觉理解</span>
</div>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="config-enable-vision" class="sr-only peer" checked>
<div class="w-11 h-6 bg-gray-300 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-cyan-500"></div>
</label>
</div>
<!-- 状态显示 -->
<div id="config-status" class="p-3 border-2 border-gray-300 bg-gray-50 text-sm text-gray-600 hidden">
<iconify-icon icon="ph:info" class="align-middle"></iconify-icon>
@@ -191,8 +231,82 @@
</div>
</div>
<!-- 代码查看模态窗 -->
<div id="code-modal" class="modal-overlay">
<div class="modal-content code-modal-content">
<div class="bg-gradient-to-r from-indigo-600 to-purple-600 p-4 border-b-4 border-black flex items-center justify-between">
<div class="flex items-center gap-2">
<iconify-icon icon="ph:code-fill" class="text-3xl text-white"></iconify-icon>
<h2 class="text-xl font-black text-white">生成代码</h2>
</div>
<button id="close-code-modal-btn" class="text-white hover:bg-white/20 p-2 transition-all">
<iconify-icon icon="ph:x-bold" class="text-2xl"></iconify-icon>
</button>
</div>
<div class="p-6 space-y-4">
<div class="flex items-center justify-between gap-3">
<p class="font-bold text-gray-700">以下为当前图表对应的代码,可复制后用于自定义编辑。</p>
<button id="copy-code-btn" class="px-4 py-2 bg-indigo-500 text-white font-bold border-2 border-black hover:bg-indigo-600 transition-all flex items-center gap-2">
<iconify-icon icon="ph:copy-bold"></iconify-icon>
复制代码
</button>
</div>
<pre id="code-content" class="code-viewer"></pre>
</div>
</div>
</div>
<!-- 页面预览模态窗 -->
<div id="page-preview-modal" class="modal-overlay">
<div class="modal-content page-preview-modal">
<div class="bg-gradient-to-r from-teal-500 to-cyan-500 p-3 border-b-4 border-black flex items-center justify-between">
<div class="flex items-center gap-2">
<iconify-icon icon="ph:monitor-play-fill" class="text-2xl text-white"></iconify-icon>
<h2 class="text-lg md:text-xl font-black text-white">落地页全屏预览</h2>
</div>
<div class="flex items-center gap-2">
<button id="page-preview-newtab-btn" class="px-3 py-1 bg-white text-teal-600 border-2 border-black font-semibold hover:bg-teal-50 transition-all flex items-center gap-1">
<iconify-icon icon="ph:arrow-square-out-bold" class="text-lg"></iconify-icon>
新窗口打开
</button>
<button id="close-page-preview-btn" class="text-white hover:bg-white/20 p-2 transition-all">
<iconify-icon icon="ph:x-bold" class="text-2xl"></iconify-icon>
</button>
</div>
</div>
<div class="page-preview-body">
<iframe id="page-preview-iframe" title="落地页全屏预览" sandbox="allow-scripts allow-same-origin allow-forms allow-pointer-lock" class="page-preview-iframe"></iframe>
</div>
</div>
</div>
<!-- 图片预览模态窗 -->
<div id="image-preview-modal" class="modal-overlay">
<div class="image-preview-modal-content">
<button id="close-image-preview-btn" class="absolute top-4 right-4 text-white hover:text-gray-300 transition-colors z-10">
<iconify-icon icon="ph:x-bold" class="text-3xl"></iconify-icon>
</button>
<img id="image-preview-full" src="" alt="图片预览" class="max-w-full max-h-full object-contain" />
</div>
</div>
<!-- 引入JavaScript文件 -->
<script src="js/utils.js"></script>
<script src="js/services/storage-service.js"></script>
<script src="js/services/conversation-service.js"></script>
<script src="js/core/module-registry.js"></script>
<script src="js/modules/product-canvas.js"></script>
<script src="js/modules/swot.js"></script>
<script src="js/modules/thinksvg.js"></script>
<script src="js/modules/echarts.js"></script>
<script src="js/modules/mermaid.js"></script>
<script src="js/modules/onepage.js"></script>
<script src="libs/js/mermaid.min.js"></script>
<script src="libs/js/svg-pan-zoom.min.js"></script>
<script src="libs/js/pako.min.js"></script>
<script src="libs/js/echarts.min.js"></script>
<script src="js/core/module-runtime.js"></script>
<script src="js/core/app-shell.js"></script>
<script src="js/apiclient.js"></script>
<script src="js/app.js"></script>
</body>

View File

@@ -2,19 +2,86 @@
* API客户端 - 处理与AI服务的交互
*/
// 图片上传配置常量
const IMAGE_CONFIG = {
maxCount: 4, // 最大图片数量
maxSizeBytes: 4 * 1024 * 1024, // 单张最大4MB
allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
allowedExtensions: ['.jpg', '.jpeg', '.png', '.webp', '.gif']
};
class APIClient {
constructor() {
this.config = {
url: '',
key: '',
model: ''
model: '',
enableVision: true // 默认启用图片解析
};
this.prompts = {
canvas: '',
swot: ''
this.runtimeConfig = null; // 运行时配置从config.json加载
this.promptMap = {};
this.promptFiles = {
canvas: 'prompts/canvas-prompt.txt',
swot: 'prompts/swot-prompt.txt',
thinksvg: 'prompts/thinksvg-prompt.txt',
echarts: 'prompts/echarts-prompt.txt',
mermaid: 'prompts/mermaid-prompt.txt',
onepage: 'prompts/onepage-prompt.txt'
};
this.promptFallbacks = {
canvas: '你是一个专业的产品战略分析师,擅长创建产品画布。',
swot: '你是一个专业的商业战略分析师擅长进行SWOT分析。',
thinksvg: '你是一名思维导图专家,擅长使用 SVG 生成清晰的思维导图。',
echarts:
'你是一个资深的数据可视化专家,精通将自然语言需求转化为 ECharts 配置对象,请输出结构化 JSON option。',
mermaid:
'你是一个资深的可视化工程师,擅长用 Mermaid 语法创建清晰的图示,请只输出一个 ```mermaid 代码块。',
onepage:
'你是一名资深的落地页架构师,请基于需求生成完整的单文件 HTML含 Tailwind 样式与必要的原生脚本),并使用 ```html 代码块封装输出。',
default:
'你是一个可靠的智能助手,请直接回答用户的问题并提供结构化输出。'
};
this.loadConfig();
this.loadPrompts();
this.loadRuntimeConfig(); // 加载运行时配置
this.preloadPrompts(Object.keys(this.promptFiles));
}
// 加载运行时配置从config.json支持Docker环境变量注入
async loadRuntimeConfig() {
try {
const response = await fetch('config.json');
if (response.ok) {
this.runtimeConfig = await response.json();
// 运行时配置优先级高于本地存储
if (this.runtimeConfig.enableVision !== undefined) {
this.config.enableVision = this.runtimeConfig.enableVision;
}
console.log('运行时配置已加载:', this.runtimeConfig);
// 触发配置更新事件通知UI同步
this.notifyConfigUpdated();
}
} catch (error) {
// config.json不存在时静默失败使用默认配置
console.log('未找到运行时配置文件,使用默认配置');
}
}
// 触发配置更新事件
notifyConfigUpdated() {
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('vision-config-updated', {
detail: {
enableVision: this.isVisionEnabled(),
isRuntimeLocked: this.runtimeConfig && this.runtimeConfig.enableVision !== undefined
}
}));
}
}
// 检查Vision配置是否被运行时锁定
isVisionConfigLocked() {
return this.runtimeConfig && this.runtimeConfig.enableVision !== undefined;
}
// 加载API配置
@@ -23,23 +90,65 @@ class APIClient {
if (savedConfig) {
this.config = { ...this.config, ...savedConfig };
}
// 确保enableVision有默认值
if (this.config.enableVision === undefined) {
this.config.enableVision = true;
}
}
// 加载系统提示词
async loadPrompts() {
try {
// 加载产品画布提示词
const canvasResponse = await fetch('prompts/canvas-prompt.txt');
this.prompts.canvas = await canvasResponse.text();
// 检查图片解析是否启用
isVisionEnabled() {
// 运行时配置优先级最高
if (this.runtimeConfig && this.runtimeConfig.enableVision !== undefined) {
return this.runtimeConfig.enableVision;
}
return this.config.enableVision !== false;
}
// 加载SWOT分析提示词
const swotResponse = await fetch('prompts/swot-prompt.txt');
this.prompts.swot = await swotResponse.text();
// 获取图片配置
getImageConfig() {
return { ...IMAGE_CONFIG };
}
preloadPrompts(keys = []) {
keys.forEach((key) => {
this.ensurePrompt(key).catch((error) =>
console.warn(`预加载提示词 ${key} 失败:`, error)
);
});
}
async ensurePrompt(promptKey) {
if (!promptKey) return '';
if (this.promptMap[promptKey]) {
return this.promptMap[promptKey];
}
const prompt = await this.fetchPrompt(promptKey);
this.promptMap[promptKey] = prompt;
return prompt;
}
async fetchPrompt(promptKey) {
const filePath = this.promptFiles[promptKey];
const fallback =
this.promptFallbacks[promptKey] ||
'你是一个可靠的智能助手,请直接回答用户问题。';
if (!filePath) {
console.warn(`未找到提示词 ${promptKey} 对应的文件配置`);
return fallback;
}
try {
const response = await fetch(filePath);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const text = await response.text();
return text.trim() || fallback;
} catch (error) {
console.error('加载提示词失败:', error);
// 使用默认提示词
this.prompts.canvas = '你是一个专业的产品战略分析师,擅长创建产品画布。';
this.prompts.swot = '你是一个专业的商业战略分析师擅长进行SWOT分析。';
console.warn(`加载提示词 ${promptKey} 失败:`, error);
return fallback;
}
}
@@ -76,6 +185,93 @@ class APIClient {
}
}
async buildMessagesForModule(manifest, userMessage, contextMessages = [], images = []) {
const prompt =
(manifest && manifest.promptKey
? await this.ensurePrompt(manifest.promptKey)
: null) || this.promptFallbacks.default;
// 构建用户消息内容(支持图片)
const userContent = this.buildUserContent(userMessage, images);
return [
{ role: 'system', content: prompt },
...contextMessages,
{ role: 'user', content: userContent }
];
}
/**
* 构建用户消息内容支持图片的OpenAI Vision API格式
* @param {string} text - 文本内容
* @param {Array} images - 图片数组,每项包含 { base64, mimeType }
* @returns {string|Array} - 纯文本或多模态内容数组
*/
buildUserContent(text, images = []) {
// 如果没有图片或未启用Vision返回纯文本
if (!images || images.length === 0 || !this.isVisionEnabled()) {
return text;
}
// 构建多模态内容数组OpenAI Vision API格式
const content = [];
// 添加图片
for (const img of images) {
content.push({
type: 'image_url',
image_url: {
url: `data:${img.mimeType};base64,${img.base64}`,
detail: 'auto' // 可选: 'low', 'high', 'auto'
}
});
}
// 添加文本(放在图片后面)
if (text && text.trim()) {
content.push({
type: 'text',
text: text
});
}
return content;
}
async generateModuleCompletion(
manifest,
userMessage,
contextMessages = [],
options = {},
images = []
) {
const messages = await this.buildMessagesForModule(
manifest,
userMessage,
contextMessages,
images
);
return this.sendChatMessage(messages, options);
}
async generateModuleStream(
manifest,
userMessage,
contextMessages = [],
onChunk,
onComplete,
options = {},
images = []
) {
const messages = await this.buildMessagesForModule(
manifest,
userMessage,
contextMessages,
images
);
return this.sendChatMessageStream(messages, options, onChunk, onComplete);
}
// 发送聊天请求
async sendChatMessage(messages, options = {}) {
if (!this.isConfigValid()) {
@@ -127,46 +323,46 @@ class APIClient {
// 生成产品画布的专用方法
async generateProductCanvas(userRequest, context = []) {
const messages = [
{ role: 'system', content: this.prompts.canvas },
...context,
{ role: 'user', content: userRequest }
];
return await this.sendChatMessage(messages, { maxTokens: 3000 });
return this.generateModuleCompletion(
{ promptKey: 'canvas' },
userRequest,
context,
{ maxTokens: 18000 }
);
}
// 生成SWOT分析的专用方法
async generateSWOTAnalysis(userRequest, context = []) {
const messages = [
{ role: 'system', content: this.prompts.swot },
...context,
{ role: 'user', content: userRequest }
];
return await this.sendChatMessage(messages, { maxTokens: 3000 });
return this.generateModuleCompletion(
{ promptKey: 'swot' },
userRequest,
context,
{ maxTokens: 18000 }
);
}
// 流式生成产品画布
async generateProductCanvasStream(userRequest, context = [], onChunk, onComplete) {
const messages = [
{ role: 'system', content: this.prompts.canvas },
...context,
{ role: 'user', content: userRequest }
];
return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete);
return this.generateModuleStream(
{ promptKey: 'canvas' },
userRequest,
context,
onChunk,
onComplete,
{ maxTokens: 13000 }
);
}
// 流式生成SWOT分析
async generateSWOTAnalysisStream(userRequest, context = [], onChunk, onComplete) {
const messages = [
{ role: 'system', content: this.prompts.swot },
...context,
{ role: 'user', content: userRequest }
];
return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete);
return this.generateModuleStream(
{ promptKey: 'swot' },
userRequest,
context,
onChunk,
onComplete,
{ maxTokens: 13000 }
);
}
// 流式发送聊天请求
@@ -190,7 +386,7 @@ class APIClient {
const url = this.config.url.replace('/chat/completions', '/chat/completions');
try {
await Utils.createStreamRequest(
return Utils.createStreamRequest(
url,
{
method: 'POST',
@@ -215,7 +411,8 @@ class APIClient {
.filter(msg => msg.id <= messageId)
.map(msg => ({
role: msg.type === 'user' ? 'user' : 'assistant',
content: msg.content
// 优先使用原始内容,保证上下文完整性
content: msg.rawContent || msg.content || ''
}));
if (contextMessages.length === 0) {
@@ -233,7 +430,8 @@ class APIClient {
throw new Error('没有找到用户消息');
}
const mode = Utils.storage.get('currentMode', 'canvas');
const activeModuleId = Utils.storage.get('tool-engine:activeModuleId', 'product-canvas');
const mode = activeModuleId === 'swot' ? 'swot' : 'canvas';
if (mode === 'canvas') {
return await this.generateProductCanvas(lastUserMessage.content, contextMessages.slice(0, -1));

1006
js/app.js

File diff suppressed because it is too large Load Diff

3221
js/core/app-shell.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
(function (global) {
'use strict';
class ModuleRegistry {
constructor() {
this.modules = new Map();
this.order = [];
}
register(manifest) {
if (!manifest || !manifest.id) {
throw new Error('注册模块失败:缺少 id');
}
if (this.modules.has(manifest.id)) {
console.warn(`模块 ${manifest.id} 已存在,将被覆盖`);
} else {
this.order.push(manifest.id);
}
this.modules.set(manifest.id, manifest);
}
get(moduleId) {
return this.modules.get(moduleId) || null;
}
list() {
return this.order.map((id) => this.modules.get(id));
}
has(moduleId) {
return this.modules.has(moduleId);
}
}
global.ModuleRegistry = new ModuleRegistry();
})(window);

181
js/core/module-runtime.js Normal file
View File

@@ -0,0 +1,181 @@
(function (global) {
'use strict';
const DEFAULT_STORAGE_KEYS = {
history: 'history',
artifacts: 'artifacts',
ui: 'uiState'
};
class ModuleRuntime {
constructor({ registry, storageService, conversationService }) {
if (!registry) throw new Error('ModuleRuntime 需要 ModuleRegistry');
if (!storageService) throw new Error('ModuleRuntime 需要 StorageService');
if (!conversationService)
throw new Error('ModuleRuntime 需要 ConversationService');
this.registry = registry;
this.storageService = storageService;
this.conversationService = conversationService;
this.moduleStates = new Map();
this.activeModuleId = null;
}
_storageKeys(manifest) {
return {
...DEFAULT_STORAGE_KEYS,
...(manifest.storageKeys || {})
};
}
_namespace(manifest) {
const namespace =
manifest.storageNamespace || `module:${manifest.id || 'unknown'}`;
return this.storageService.namespace(namespace);
}
_ensureState(manifest) {
if (this.moduleStates.has(manifest.id)) {
return this.moduleStates.get(manifest.id);
}
const store = this._namespace(manifest);
const keys = this._storageKeys(manifest);
const state = {
artifacts: store.get(keys.artifacts, {}),
uiState: store.get(keys.ui, {}),
currentArtifactId: null
};
if (state.uiState && state.uiState.__activeArtifact) {
state.currentArtifactId = state.uiState.__activeArtifact;
}
this.moduleStates.set(manifest.id, state);
return state;
}
_persistState(manifest) {
const store = this._namespace(manifest);
const keys = this._storageKeys(manifest);
const state = this._ensureState(manifest);
if (state.uiState) {
state.uiState.__activeArtifact = state.currentArtifactId;
}
store.set(keys.artifacts, state.artifacts);
store.set(keys.ui, state.uiState);
}
getManifest(moduleId) {
const manifest = this.registry.get(moduleId);
if (!manifest) {
throw new Error(`未找到模块 ${moduleId}`);
}
return manifest;
}
listManifests() {
return this.registry.list();
}
activate(moduleId) {
const manifest = this.getManifest(moduleId);
this.activeModuleId = moduleId;
const state = this._ensureState(manifest);
const context = {
manifest,
state,
history: this.conversationService.getHistory(manifest)
};
if (manifest.hooks && typeof manifest.hooks.onActivate === 'function') {
try {
manifest.hooks.onActivate(context);
} catch (error) {
console.warn(`执行模块 ${moduleId} onActivate 时出错:`, error);
}
}
return context;
}
getActiveModule() {
if (!this.activeModuleId) return null;
return this.getManifest(this.activeModuleId);
}
getState(moduleId) {
const manifest = this.getManifest(moduleId);
return this._ensureState(manifest);
}
getArtifacts(moduleId) {
const state = this.getState(moduleId);
return state.artifacts;
}
saveArtifact(moduleId, artifactId, payload) {
const manifest = this.getManifest(moduleId);
const state = this._ensureState(manifest);
state.artifacts[artifactId] = payload;
state.currentArtifactId = artifactId;
this._persistState(manifest);
return payload;
}
removeArtifact(moduleId, artifactId) {
const manifest = this.getManifest(moduleId);
const state = this._ensureState(manifest);
if (state.artifacts[artifactId]) {
delete state.artifacts[artifactId];
if (state.currentArtifactId === artifactId) {
state.currentArtifactId = null;
}
this._persistState(manifest);
}
}
setActiveArtifact(moduleId, artifactId) {
const manifest = this.getManifest(moduleId);
const state = this._ensureState(manifest);
state.currentArtifactId = artifactId;
this._persistState(manifest);
}
getActiveArtifactId(moduleId) {
const state = this.getState(moduleId);
return state.currentArtifactId || null;
}
updateUiState(moduleId, patch) {
const manifest = this.getManifest(moduleId);
const state = this._ensureState(manifest);
state.uiState = {
...state.uiState,
...patch
};
this._persistState(manifest);
return state.uiState;
}
getUiState(moduleId, defaultValue = {}) {
const state = this.getState(moduleId);
const uiState = { ...(state.uiState || {}) };
delete uiState.__activeArtifact;
return { ...defaultValue, ...uiState };
}
getConversationService() {
return this.conversationService;
}
clearArtifacts(moduleId) {
const manifest = this.getManifest(moduleId);
const state = this._ensureState(manifest);
state.artifacts = {};
state.currentArtifactId = null;
this._persistState(manifest);
}
}
global.ModuleRuntime = ModuleRuntime;
})(window);

86
js/modules/echarts.js Normal file
View File

@@ -0,0 +1,86 @@
(function registerEChartsModule(global) {
'use strict';
if (!global.ModuleRegistry) {
throw new Error('ModuleRegistry 未初始化');
}
const CODE_FENCE_REGEX = /```(?:json|js|javascript|echarts|option)?\s*([\s\S]*?)```/i;
const parseOptionText = (text) => {
if (!text) return null;
try {
return JSON.parse(text);
} catch (error) {
try {
// 尝试处理 JS 对象语法
// eslint-disable-next-line no-new-func
return new Function(`return (${text});`)();
} catch (innerError) {
console.warn('解析 ECharts 配置失败:', innerError);
return null;
}
}
};
const parseResponse = (content) => {
const match = content.match(CODE_FENCE_REGEX);
if (match) {
const optionText = match[1].trim();
return {
optionText,
option: parseOptionText(optionText),
beforeText: content.substring(0, match.index).trim(),
afterText: content.substring(match.index + match[0].length).trim()
};
}
return {
optionText: '',
option: null,
beforeText: content.trim(),
afterText: ''
};
};
global.ModuleRegistry.register({
id: 'echarts',
label: 'ECharts 图表',
icon: 'ph:chart-line-up-duotone',
renderer: 'echarts',
promptKey: 'echarts',
storageNamespace: 'module:echarts',
chat: {
placeholder: '描述想生成的图表或调整需求,我会输出 ECharts 配置…',
streamStartToken: '```json',
contextWindow: 8
},
artifact: {
type: 'echarts-option',
fence: ['json', 'js', 'javascript', 'echarts', 'option'],
startPattern: /```(?:json|js|javascript|echarts|option)/i,
parser: parseResponse
},
hooks: {
onActivate() {
// 预留钩子,可在此初始化额外资源
}
},
exports: {
allowSvg: true,
allowPng: true,
allowClipboard: true,
allowCode: true
},
ui: {
placeholderText: '生成的 ECharts 图表将在此处显示',
quickActions: [
{ label: '折线图', value: '使用折线图配置;' },
{ label: '面积图', value: '使用面积图配置;' },
{ label: '柱状图', value: '使用柱状图配置;' },
{ label: '条形图', value: '使用条形图配置;' },
{ label: '饼图', value: '使用饼图配置;' },
{ label: '雷达图', value: '使用雷达图配置;' }
]
}
});
})(window);

66
js/modules/mermaid.js Normal file
View File

@@ -0,0 +1,66 @@
(function registerMermaidModule(global) {
'use strict';
if (!global.ModuleRegistry) {
throw new Error('ModuleRegistry 未初始化');
}
const MERMAID_FENCE = /```mermaid\s*([\s\S]*?)```/i;
const parseResponse = (content = '') => {
const match = content.match(MERMAID_FENCE);
if (match) {
const beforeText = content.substring(0, match.index).trim();
const afterText = content.substring(match.index + match[0].length).trim();
const code = match[1].trim();
return {
code,
beforeText,
afterText
};
}
return {
code: '',
beforeText: content.trim(),
afterText: ''
};
};
global.ModuleRegistry.register({
id: 'mermaid',
label: 'Mermaid 图示',
icon: 'ph:circles-three-plus-duotone',
renderer: 'mermaid',
promptKey: 'mermaid',
storageNamespace: 'module:mermaid',
chat: {
placeholder: '描述你想生成的流程图、时序图或思维导图…',
streamStartToken: '```mermaid',
contextWindow: 8
},
artifact: {
type: 'mermaid',
fence: 'mermaid',
startPattern: /```mermaid/i,
parser: parseResponse
},
hooks: {},
exports: {
allowSvg: true,
allowPng: true,
allowClipboard: true,
allowCode: true
},
ui: {
placeholderText: '生成的 Mermaid 图示将在此处显示',
quickActions: [
{ label: '流程图', value: '使用flowchart视图;' },
{ label: '序列图', value: '使用sequenceDiagram视图;' },
{ label: '类图', value: '使用classDiagram视图;' },
{ label: '状态图', value: '使用stateDiagram-v2视图;' },
{ label: 'ER图', value: '使用erDiagram视图;' },
{ label: '甘特图', value: '使用gantt视图;' }
]
}
});
})(window);

85
js/modules/onepage.js Normal file
View File

@@ -0,0 +1,85 @@
(function registerOnepageModule(global) {
'use strict';
if (!global.ModuleRegistry) {
throw new Error('ModuleRegistry 未初始化');
}
const HTML_FENCE = /```(?:html|htm)?\s*([\s\S]*?)```/i;
const START_PATTERN = /```(?:html|htm)/i;
const parseResponse = (content = '') => {
const text =
typeof content === 'string' ? content : String(content || '');
const match = text.match(HTML_FENCE);
if (match) {
const beforeText = text.substring(0, match.index).trim();
const afterText = text
.substring(match.index + match[0].length)
.trim();
return {
htmlContent: match[1].trim(),
beforeText,
afterText
};
}
const fallback = text.trim();
if (!fallback) {
return {
htmlContent: '',
beforeText: '',
afterText: ''
};
}
const htmlIndex = text.search(/<!DOCTYPE|<html|<body/i);
if (htmlIndex !== -1) {
return {
htmlContent: text.substring(htmlIndex).trim(),
beforeText: text.substring(0, htmlIndex).trim(),
afterText: ''
};
}
return {
htmlContent: fallback,
beforeText: '',
afterText: ''
};
};
global.ModuleRegistry.register({
id: 'onepage',
label: '落地页生成',
icon: 'ph:browser-duotone',
renderer: 'html',
promptKey: 'onepage',
storageNamespace: 'module:onepage',
chat: {
placeholder:
'请输入品牌定位、目标客群、核心卖点等信息,我会生成完整的落地页 HTML…',
streamStartToken: '```html',
contextWindow: 6
},
artifact: {
type: 'html',
fence: 'html',
startPattern: START_PATTERN,
parser: parseResponse
},
hooks: {},
exports: {
allowSvg: false,
allowPng: false,
allowClipboard: false,
allowCode: true
},
ui: {
placeholderText: '生成的落地页预览将在此处显示',
quickActions: [
{ label: 'SaaS 产品页', value: '为 B2B SaaS 产品生成官方落地页;' },
{ label: '新品发布', value: '为科技新品发布会设计预告落地页;' },
{ label: '在线课程', value: '为在线课程制作报名落地页;' },
{ label: '活动报名', value: '为线下品牌活动创建报名落地页;' }
]
}
});
})(window);

View File

@@ -0,0 +1,43 @@
(function registerProductCanvasModule(global) {
'use strict';
if (!global.ModuleRegistry) {
throw new Error('ModuleRegistry 未初始化');
}
const parseResponse = (content) => Utils.parseSVGResponse(content);
global.ModuleRegistry.register({
id: 'product-canvas',
label: '产品画布',
icon: 'ph:pen-nib-duotone',
renderer: 'svg',
promptKey: 'canvas',
storageNamespace: 'module:product-canvas',
chat: {
placeholder: '描述你的产品定位、用户画像、价值主张等内容…',
streamStartToken: '```svg',
contextWindow: 10
},
artifact: {
type: 'svg',
fence: 'svg',
startPattern: /```\s*(?:svg|xml)?\s*<svg/i,
parser: parseResponse
},
hooks: {
onActivate() {
// 保留扩展点,后续可追加自定义逻辑
}
},
exports: {
allowSvg: true,
allowPng: true,
allowClipboard: true,
allowCode: true
},
ui: {
placeholderText: '生成的产品画布将在此处显示'
}
});
})(window);

39
js/modules/swot.js Normal file
View File

@@ -0,0 +1,39 @@
(function registerSwotModule(global) {
'use strict';
if (!global.ModuleRegistry) {
throw new Error('ModuleRegistry 未初始化');
}
const parseResponse = (content) => Utils.parseSVGResponse(content);
global.ModuleRegistry.register({
id: 'swot',
label: 'SWOT分析',
icon: 'ph:chart-bar-duotone',
renderer: 'svg',
promptKey: 'swot',
storageNamespace: 'module:swot',
chat: {
placeholder: '输入业务背景或问题,我来生成 SWOT 分析…',
streamStartToken: '```svg',
contextWindow: 10
},
artifact: {
type: 'svg',
fence: 'svg',
startPattern: /```\s*(?:svg|xml)?\s*<svg/i,
parser: parseResponse
},
hooks: {},
exports: {
allowSvg: true,
allowPng: true,
allowClipboard: true,
allowCode: true
},
ui: {
placeholderText: '生成的SWOT分析将在此处显示'
}
});
})(window);

98
js/modules/thinksvg.js Normal file
View File

@@ -0,0 +1,98 @@
/**
* 思维导图模块
*
* 功能:基于用户输入生成 SVG 格式的思维导图
* 特性:
* - 支持多层级树形结构展示
* - 通过提示词引导 AI 生成布局和样式
* - 支持 SVG/PNG 导出
* - 历史记录持久化
*/
(function registerThinkSvgModule(global) {
'use strict';
// 确保 ModuleRegistry 已初始化
if (!global.ModuleRegistry) {
throw new Error('ModuleRegistry 未初始化');
}
/**
* 解析 AI 响应,提取 SVG 内容
* @param {string} content - AI 返回的原始内容
* @returns {Object} 解析结果对象,包含 svgContent、beforeText、afterText 等字段
*/
const parseResponse = (content) => Utils.parseSVGResponse(content);
// 注册思维导图模块
global.ModuleRegistry.register({
// 模块标识
id: 'thinksvg',
// 显示名称
label: '思维导图',
// 图标(使用 Phosphor Icons 的树形结构图标)
icon: 'ph:tree-structure-duotone',
// 渲染器类型
renderer: 'svg',
// 提示词键名(对应 prompts/thinksvg-prompt.txt
promptKey: 'thinksvg',
// 本地存储命名空间
storageNamespace: 'module:thinksvg',
// 聊天配置
chat: {
// 输入框占位符
placeholder: '输入要梳理的主题或问题,我来生成思维导图…',
// 流式响应开始标记
streamStartToken: '```svg',
// 上下文窗口大小(保留最近 10 条消息)
contextWindow: 10
},
// 产物配置
artifact: {
// 产物类型
type: 'svg',
// 代码围栏标识
fence: 'svg',
// SVG 开始模式匹配(兼容 ```svg、```xml、``` xml 等格式)
startPattern: /```\s*(?:svg|xml)?\s*<svg/i,
// 内容解析器
parser: parseResponse
},
// 生命周期钩子(预留扩展)
hooks: {},
// 导出功能配置
exports: {
// 允许导出为 SVG 文件
allowSvg: true,
// 允许导出为 PNG 图片
allowPng: true,
// 允许复制到剪贴板
allowClipboard: true,
// 允许查看源代码
allowCode: true
},
// UI 配置
ui: {
// 预览区占位文本
placeholderText: '生成的思维导图将在此处显示'
}
});
})(window);

View File

@@ -0,0 +1,119 @@
(function (global) {
'use strict';
/**
* 管理模块化对话历史及上下文构建
*/
class ConversationService {
constructor(storageService, defaultOptions = {}) {
if (!storageService) {
throw new Error('ConversationService 需要 StorageService 实例');
}
this.storageService = storageService;
this.defaultOptions = {
historyKey: 'history',
contextWindow: 10,
...defaultOptions
};
this.cache = new Map();
}
_getHistoryKey(moduleConfig) {
return moduleConfig.storageKeys?.history || this.defaultOptions.historyKey;
}
_getNamespace(moduleConfig) {
const namespace =
moduleConfig.storageNamespace || `module:${moduleConfig.id}`;
return this.storageService.namespace(namespace);
}
_getCacheKey(moduleId) {
return `history:${moduleId}`;
}
getHistory(moduleConfig) {
const cacheKey = this._getCacheKey(moduleConfig.id);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const store = this._getNamespace(moduleConfig);
const history =
store.get(this._getHistoryKey(moduleConfig), []).map((msg) => ({
...msg
}));
this.cache.set(cacheKey, history);
return history;
}
saveHistory(moduleConfig, history) {
const cacheKey = this._getCacheKey(moduleConfig.id);
const clonedHistory = history.map((msg) => ({ ...msg }));
this.cache.set(cacheKey, clonedHistory);
const store = this._getNamespace(moduleConfig);
store.set(this._getHistoryKey(moduleConfig), clonedHistory);
}
appendMessage(moduleConfig, message) {
const history = this.getHistory(moduleConfig);
history.push({ ...message });
this.saveHistory(moduleConfig, history);
return history;
}
replaceHistory(moduleConfig, history) {
this.saveHistory(moduleConfig, history);
}
clearHistory(moduleConfig) {
this.saveHistory(moduleConfig, []);
}
/**
* 构建流式上下文,为最后一个用户消息提供所需历史
*/
buildContext(moduleConfig, tailMessages = null) {
const history = this.getHistory(moduleConfig);
if (!history.length) return null;
let targetIndex = history.length - 1;
if (tailMessages != null) {
targetIndex = Math.max(0, history.length - tailMessages);
}
// 确保目标是用户消息
while (targetIndex >= 0 && history[targetIndex].type !== 'user') {
targetIndex -= 1;
}
if (targetIndex < 0) {
return null;
}
const contextWindow =
moduleConfig.chat?.contextWindow || this.defaultOptions.contextWindow;
const start = Math.max(0, targetIndex - contextWindow);
const contextSlice = history.slice(start, targetIndex);
const contextMessages = contextSlice
.filter((msg) => msg.type === 'user' || msg.type === 'ai')
.map((msg) => ({
role: msg.type === 'user' ? 'user' : 'assistant',
// 优先使用原始内容构建上下文,兼容旧数据回退到 content
content: msg.rawContent || msg.content || ''
}));
return {
history,
userMessage: history[targetIndex],
contextMessages,
targetIndex
};
}
}
global.ConversationService = ConversationService;
})(window);

View File

@@ -0,0 +1,84 @@
(function (global) {
'use strict';
/**
* 提供按命名空间隔离的本地存储封装
* 依赖 Utils.storage 作为底层驱动
*/
class NamespacedStorage {
constructor(namespace) {
this.namespace = namespace;
}
_key(key) {
return `${this.namespace}:${key}`;
}
get(key, defaultValue = null) {
return Utils.storage.get(this._key(key), defaultValue);
}
set(key, value) {
return Utils.storage.set(this._key(key), value);
}
remove(key) {
return Utils.storage.remove(this._key(key));
}
clear() {
const prefix = `${this.namespace}:`;
const toDelete = [];
for (let i = 0; i < localStorage.length; i += 1) {
const storageKey = localStorage.key(i);
if (storageKey && storageKey.startsWith(prefix)) {
toDelete.push(storageKey);
}
}
toDelete.forEach((storageKey) => localStorage.removeItem(storageKey));
}
}
class StorageService {
constructor(globalNamespace = 'tool-engine') {
this.globalNamespace = globalNamespace;
this.cache = new Map();
}
/**
* 获取全局命名空间存储
*/
global() {
if (!this.cache.has(this.globalNamespace)) {
this.cache.set(
this.globalNamespace,
new NamespacedStorage(this.globalNamespace)
);
}
return this.cache.get(this.globalNamespace);
}
/**
* 获取指定命名空间存储
*/
namespace(namespace) {
if (!namespace) {
throw new Error('Storage namespace 不能为空');
}
if (!this.cache.has(namespace)) {
this.cache.set(namespace, new NamespacedStorage(namespace));
}
return this.cache.get(namespace);
}
/**
* 清除指定命名空间内容
*/
clearNamespace(namespace) {
const store = this.namespace(namespace);
store.clear();
}
}
global.StorageService = StorageService;
})(window);

View File

@@ -21,18 +21,53 @@ function generateId(prefix = 'id') {
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// 解析SVG响应提取SVG内容和前后文本
function parseSVGResponse(response) {
const svgRegex = /```svg\s*([\s\S]*?)```/i;
const match = response.match(svgRegex);
// 解析SVG响应提取SVG内容和前后文本,容错缺失的结束反引号
function parseSVGResponse(response = '') {
const content = typeof response === 'string' ? response : String(response || '');
// 兼容 ```svg、```xml、``` xml、``` svg 等格式
const svgFenceRegex = /```\s*(?:svg|xml)?\s*([\s\S]*?)```/i;
const fenceMatch = content.match(svgFenceRegex);
if (match) {
const svgContent = match[1].trim();
const beforeText = response.substring(0, match.index).trim();
const afterText = response.substring(match.index + match[0].length).trim();
if (fenceMatch) {
const svgBody = fenceMatch[1].trim();
const beforeText = content.substring(0, fenceMatch.index).trim();
let afterText = content.substring(fenceMatch.index + fenceMatch[0].length).trim();
afterText = afterText.replace(/^\s*```/, '').trim();
return {
svgContent,
svgContent: svgBody,
beforeText,
afterText
};
}
// 兼容缺失结束反引号的情况(同样兼容 xml 格式)
const svgStartRegex = /```\s*(?:svg|xml)?\s*<svg[\s\S]*$/i;
const startMatch = content.match(svgStartRegex);
if (startMatch) {
const startIndex = startMatch.index;
const beforeText = content.substring(0, startIndex).trim();
let svgSection = content.substring(startIndex).replace(/```\s*(?:svg|xml)?\s*/i, '').trim();
// 去掉尾部残留的反引号
svgSection = svgSection.replace(/```$/, '').trim();
// 拆分 SVG 正文与额外文本
let afterText = '';
const svgEndIndex = svgSection.lastIndexOf('</svg>');
if (svgEndIndex !== -1) {
afterText = svgSection.substring(svgEndIndex + 6).replace(/```/, '').trim();
svgSection = svgSection.substring(0, svgEndIndex + 6).trim();
}
// 补齐缺失的结束标签
if (svgSection && !svgSection.endsWith('</svg>')) {
svgSection += '\n</svg>';
}
return {
svgContent: svgSection || null,
beforeText,
afterText
};
@@ -40,7 +75,7 @@ function parseSVGResponse(response) {
return {
svgContent: null,
beforeText: response,
beforeText: content.trim(),
afterText: ''
};
}
@@ -204,6 +239,15 @@ class StreamProcessor {
this.onChunk = onChunk;
this.onComplete = onComplete;
this.buffer = '';
this.completed = false;
}
complete(info = {}) {
if (this.completed) return;
this.completed = true;
if (typeof this.onComplete === 'function') {
this.onComplete(info);
}
}
// 处理数据块
@@ -221,7 +265,7 @@ class StreamProcessor {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
this.onComplete();
this.complete({ aborted: false });
return;
}
@@ -237,12 +281,15 @@ class StreamProcessor {
}
// 创建流式请求
async function createStreamRequest(url, options, onChunk, onComplete) {
function createStreamRequest(url, options, onChunk, onComplete) {
const processor = new StreamProcessor(onChunk, onComplete);
const controller = new AbortController();
const fetchPromise = (async () => {
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
headers: {
...options.headers,
'Accept': 'text/event-stream',
@@ -263,10 +310,27 @@ async function createStreamRequest(url, options, onChunk, onComplete) {
const chunk = decoder.decode(value, { stream: true });
processor.processChunk(chunk);
if (processor.completed) {
break;
}
}
if (!processor.completed) {
processor.complete({ aborted: false });
}
} catch (error) {
if (error.name === 'AbortError') {
processor.complete({ aborted: true });
return;
}
throw error;
}
})();
return {
cancel: () => controller.abort(),
finished: fetchPromise
};
}
// 导出工具函数

21
libs/css/inter-font.css Normal file
View File

@@ -0,0 +1,21 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(../fonts/inter-400.ttf) format('truetype');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(../fonts/inter-700.ttf) format('truetype');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(../fonts/inter-900.ttf) format('truetype');
}

65
libs/css/tailwind.css Normal file

File diff suppressed because one or more lines are too long

BIN
libs/fonts/inter-400.ttf Normal file

Binary file not shown.

BIN
libs/fonts/inter-700.ttf Normal file

Binary file not shown.

BIN
libs/fonts/inter-900.ttf Normal file

Binary file not shown.

45
libs/js/echarts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

12
libs/js/iconify-icon.min.js vendored Normal file

File diff suppressed because one or more lines are too long

69
libs/js/marked.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2314
libs/js/mermaid.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
libs/js/pako.min.js vendored Normal file

File diff suppressed because one or more lines are too long

3
libs/js/svg-pan-zoom.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,13 @@
你是一个专业的产品战略分析师,擅长建产品画布。
你是一个专业的产品战略分析师,擅长建产品画布你的核心任务是分析用户需求输出满足场景的svg产品画布与产品无关内容不要回答
请根据用户的需求生成一个详细的产品画布并以SVG格式返回。
请用中文回复并在回复中包含SVG格式的产品画布图表。
产品精益画布助手下面是SVG画布的模板注意使用markdown格式回复
产品精益画布助手下面是SVG画布的模板注意使用markdown格式回复
- 解决方案、门槛优势、关键指标、渠道 文字不要超过7行
- 成本分析、收入分析 文字不要超过6行
```
<svg width="900" height="550" viewBox="0 0 900 550" xmlns="http://www.w3.org/2000/svg" font-family="'PingFang SC', 'Microsoft YaHei', sans-serif">
<defs>
@@ -19,48 +23,45 @@
</defs>
<!-- 背景 -->
<rect fill="#f8f9fa" width="100%" height="100%"/>
<rect fill="#f0f4f8" width="100%" height="100%"/>
<!-- 主标题 -->
<text x="450" y="35" class="main-title" fill="#28a745">产品精益画布 - 绿邻回收智能终端系统</text>
<text x="450" y="35" class="main-title" fill="#007acc">业务中台画布</text>
<!-- Canvas Grid starting at y=60, centered in 900px width -->
<g transform="translate(75, 60)">
<!-- ROW 1 & 2 -->
<!-- Box 1: 问题 (rowspan=2) -->
<!-- Box 1: 关键业务需求 (rowspan=2) -->
<g transform="translate(0, 0)">
<rect width="150" height="300" class="box"/>
<rect x="0" y="0" width="150" height="30" fill="#ffebee"/>
<rect x="0" y="0" width="150" height="30" fill="#e8f0fe"/>
<g transform="translate(75, 20)">
<text class="title" fill="#c62828">问题</text>
<text class="title" fill="#0d47a1">关键业务需求</text>
</g>
<text x="10" y="50" class="content-bold">居民痛点:</text>
<text x="10" y="62" class="content">• 卖废品麻烦,需囤积等人</text>
<text x="10" y="74" class="content">• 老人操作困难,现有方案不友好</text>
<text x="10" y="86" class="content">• 价格不透明,缺乏信任</text>
<text x="10" y="108" class="content-bold">商家痛点:</text>
<text x="10" y="120" class="content">• 人工成本高,需专人管理</text>
<text x="10" y="132" class="content">• 技术门槛高,缺乏智能方案</text>
<text x="10" y="154" class="content-bold">现有替代方案局限:</text>
<text x="10" y="166" class="content">• 流动回收车时间不固定</text>
<text x="10" y="178" class="content">• 人工回收点成本高</text>
<text x="10" y="50" class="content-bold">核心需求:</text>
<text x="10" y="62" class="content">• 统一数据标准化</text>
<text x="10" y="74" class="content">• 业务流程自动化</text>
<text x="10" y="86" class="content">• 多系统无缝集成</text>
<text x="10" y="108" class="content-bold">痛点:</text>
<text x="10" y="120" class="content">• 数据孤岛,信息不通</text>
<text x="10" y="132" class="content">• 重复开发,效率低</text>
<text x="10" y="154" class="content">• 维护成本高,扩展难</text>
<text x="130" y="280" class="number">1</text>
</g>
<!-- Box 4: 解决方案 -->
<g transform="translate(150, 0)">
<rect width="150" height="150" class="box"/>
<rect x="0" y="0" width="150" height="30" fill="#e3f2fd"/>
<rect x="0" y="0" width="150" height="30" fill="#e0f7fa"/>
<g transform="translate(75, 20)">
<text class="title" fill="#1976d2">解决方案</text>
<text class="title" fill="#006064">解决方案</text>
</g>
<text x="10" y="50" class="content-bold">智能终端系统:</text>
<text x="10" y="62" class="content">• 平板显示二维码供扫描</text>
<text x="10" y="74" class="content">• 蓝牙电子秤自动称重</text>
<text x="10" y="86" class="content">• 大字体显示金额重量</text>
<text x="10" y="108" class="content-bold">多角色管理:</text>
<text x="10" y="120" class="content">• 普通用户/管理员/清运员</text>
<text x="10" y="132" class="content">• 统一平台,智能分权</text>
<text x="10" y="50" class="content-bold">业务中台架构:</text>
<text x="10" y="62" class="content">• 标准化服务接口</text>
<text x="10" y="74" class="content">• 统一数据模型</text>
<text x="10" y="86" class="content">• 模块化业务组件</text>
<text x="10" y="108" class="content-bold">技术实现:</text>
<text x="10" y="120" class="content">• 微服务架构</text>
<text x="10" y="132" class="content">• API网关管理</text>
<text x="130" y="130" class="number">4</text>
</g>
@@ -69,20 +70,15 @@
<rect width="150" height="300" class="box"/>
<rect x="0" y="0" width="150" height="30" fill="#fff3e0"/>
<g transform="translate(75, 20)">
<text class="title" fill="#f57c00">独特卖点</text>
<text class="title" fill="#ef6c00">独特卖点</text>
</g>
<g transform="translate(75, 60)">
<text class="desc" style="font-size: 14px; font-weight: bold;" fill="#f57c00">
<tspan x="0" dy="0">微信扫一扫,</tspan>
<tspan x="0" dy="20">老少皆宜智能回收</tspan>
</text>
</g>
<text x="10" y="120" class="content-bold">对用户价值:</text>
<text x="10" y="132" class="content">• 扫码即用,操作超简单</text>
<text x="10" y="144" class="content">• 价格透明,立即到账</text>
<text x="10" y="166" class="content-bold">对商家价值:</text>
<text x="10" y="178" class="content">• 零人工成本,智能管理</text>
<text x="10" y="190" class="content">• 稳定增收,吸引客流</text>
<text x="10" y="120" class="content-bold">价值体现:</text>
<text x="10" y="132" class="content">• 提高业务响应速度</text>
<text x="10" y="144" class="content">• 降低重复开发成本</text>
<text x="10" y="156" class="content">• 促进跨部门协作</text>
<text x="10" y="178" class="content-bold">竞争优势:</text>
<text x="10" y="190" class="content">• 灵活可扩展的架构</text>
<text x="10" y="202" class="content">• 易于快速集成新业务</text>
<text x="130" y="280" class="number">3</text>
</g>
@@ -93,32 +89,32 @@
<g transform="translate(75, 20)">
<text class="title" fill="#ad1457">门槛优势</text>
</g>
<text x="10" y="50" class="content-bold">产品壁垒:</text>
<text x="10" y="62" class="content">• 老人友好设计(市场空白)</text>
<text x="10" y="74" class="content">• 低成本智能化方案</text>
<text x="10" y="50" class="content-bold">技术壁垒:</text>
<text x="10" y="62" class="content">• 统一中台数据标准</text>
<text x="10" y="74" class="content">• 复杂业务模型抽象能力</text>
<text x="10" y="96" class="content-bold">运营壁垒:</text>
<text x="10" y="108" class="content">• 点位网络效应</text>
<text x="10" y="120" class="content">• 先发优势抢占资源</text>
<text x="10" y="108" class="content">• 多业务系统整合经验</text>
<text x="10" y="120" class="content">• 持续迭代优化机制</text>
<text x="130" y="130" class="number">9</text>
</g>
<!-- Box 2: 客户群体分类 (rowspan=2) -->
<!-- Box 2: 目标客户 (rowspan=2) -->
<g transform="translate(600, 0)">
<rect width="150" height="300" class="box"/>
<rect x="0" y="0" width="150" height="30" fill="#e8f5e8"/>
<g transform="translate(75, 20)">
<text class="title" fill="#2e7d32">客户群体分类</text>
<text class="title" fill="#2e7d32">目标客户</text>
</g>
<text x="10" y="50" class="content-bold">核心用户 (C端):</text>
<text x="10" y="62" class="content">• 社区中老年人 (扫码即用)</text>
<text x="10" y="74" class="content">• 环保意识强的年轻家庭</text>
<text x="10" y="86" class="content">• 图方便的上班族</text>
<text x="10" y="108" class="content-bold">合作伙伴 (B端):</text>
<text x="10" y="120" class="content">• 社区超市、便利店</text>
<text x="10" y="132" class="content">• 快递驿站、物业服务点</text>
<text x="10" y="154" class="content-bold">需求特:</text>
<text x="10" y="166" class="content">• 操作简单、零成本增收</text>
<text x="10" y="178" class="content">• 智能化管理、吸引客流</text>
<text x="10" y="50" class="content-bold">内部客户:</text>
<text x="10" y="62" class="content">• 业务部门需求方</text>
<text x="10" y="74" class="content">• IT开发团队</text>
<text x="10" y="86" class="content">• 数据分析团队</text>
<text x="10" y="108" class="content-bold">外部客户:</text>
<text x="10" y="120" class="content">• 合作伙伴系统</text>
<text x="10" y="132" class="content">• 第三方服务平台</text>
<text x="10" y="154" class="content-bold">需求特:</text>
<text x="10" y="166" class="content">• 灵活定制服务</text>
<text x="10" y="178" class="content">• 高可用稳定性</text>
<text x="130" y="280" class="number">2</text>
</g>
@@ -130,12 +126,12 @@
<g transform="translate(75, 20)">
<text class="title" fill="#00695c">关键指标</text>
</g>
<text x="10" y="50" class="content-bold">运营指标:</text>
<text x="10" y="62" class="content">• 单终端日均交易量</text>
<text x="10" y="74" class="content">• 终端网络覆盖社区数</text>
<text x="10" y="96" class="content-bold">用户指标:</text>
<text x="10" y="108" class="content">• 操作成功率、用户满意度</text>
<text x="10" y="120" class="content">• 用户复购率、推荐率</text>
<text x="10" y="50" class="content-bold">性能指标:</text>
<text x="10" y="62" class="content">• 接口响应时间</text>
<text x="10" y="74" class="content">• 系统可用率</text>
<text x="10" y="96" class="content-bold">业务指标:</text>
<text x="10" y="108" class="content">• 业务组件复用率</text>
<text x="10" y="120" class="content">• 需求响应周期</text>
<text x="130" y="130" class="number">8</text>
</g>
@@ -146,12 +142,12 @@
<g transform="translate(75, 20)">
<text class="title" fill="#7b1fa2">渠道</text>
</g>
<text x="10" y="50" class="content-bold">线下布点:</text>
<text x="10" y="62" class="content">• 社区超市、快递站合作</text>
<text x="10" y="74" class="content">• 与物业/居委会合作</text>
<text x="10" y="96" class="content-bold">用户触达:</text>
<text x="10" y="108" class="content">• 微信生态扫一扫直达</text>
<text x="10" y="120" class="content">• 业主群推广分享</text>
<text x="10" y="50" class="content-bold">内部推广:</text>
<text x="10" y="62" class="content">• 跨部门沟通会议</text>
<text x="10" y="74" class="content">• 内部培训与宣讲</text>
<text x="10" y="96" class="content-bold">技术支持:</text>
<text x="10" y="108" class="content">• 开发者门户</text>
<text x="10" y="120" class="content">• 技术文档和示例</text>
<text x="130" y="130" class="number">5</text>
</g>
@@ -163,12 +159,13 @@
<g transform="translate(187.5, 20)">
<text class="title" fill="#f57f17">成本分析</text>
</g>
<text x="10" y="50" class="content-bold">单点成本:</text>
<text x="10" y="62" class="content">• 硬件成本: 1000元/终端 • 部署成本: 2000元/点</text>
<text x="10" y="84" class="content-bold">运营成本:</text>
<text x="10" y="96" class="content">• 技术维护、云服务 • 清运物流、客服支持</text>
<text x="10" y="118" class="content-bold">分成成本:</text>
<text x="10" y="130" class="content">• 与场地方交易额分账</text>
<text x="10" y="50" class="content-bold">建设成本:</text>
<text x="10" y="62" class="content">• 中台架构设计与开发</text>
<text x="10" y="74" class="content">• 统一数据平台建设</text>
<text x="10" y="96" class="content-bold">运营成本:</text>
<text x="10" y="108" class="content">• 服务器及云资源</text>
<text x="10" y="120" class="content">• 技术支持团队</text>
<text x="10" y="132" class="content">• 持续维护与升级</text>
<text x="355" y="130" class="number">7</text>
</g>
@@ -179,12 +176,13 @@
<g transform="translate(187.5, 20)">
<text class="title" fill="#2e7d32">收入分析</text>
</g>
<text x="10" y="50" class="content-bold">主要收入流:</text>
<text x="10" y="62" class="content">• 废品回收差价 (终端回收价 vs 批发销售价的差额) - 核心收入</text>
<text x="10" y="74" class="content">• 与合作场地按交易量分成 - 激励合作</text>
<text x="10" y="96" class="content-bold">增值收入流:</text>
<text x="10" y="108" class="content">• 规模化后的数据服务收入 • 绿色积分商城佣金</text>
<text x="10" y="130" class="content">• 单点投入3000元预期4-10个月回本</text>
<text x="10" y="50" class="content-bold">成本节约:</text>
<text x="10" y="62" class="content">• 复用业务组件减少开发</text>
<text x="10" y="74" class="content">• 降低维护及切换成本</text>
<text x="10" y="96" class="content-bold">业务增长:</text>
<text x="10" y="108" class="content">• 快速支持新业务上线</text>
<text x="10" y="120" class="content">• 提升客户满意度与留存</text>
<text x="10" y="132" class="content">• 促进多业务协同增效</text>
<text x="355" y="130" class="number">6</text>
</g>
</g>

View File

@@ -0,0 +1,87 @@
你是一名资深的数据可视化专家,擅长使用 ECharts 将业务需求转化为可执行的配置。请遵循以下规则输出结果:
1. 使用 JSON 对象表达完整的 ECharts option不要包含解释说明。
2. 保持字段命名符合 ECharts 官方文档,避免多余字段。
3. 如需附加解读或说明,请放在 JSON 代码块之外。
4. 如果用户没有提供数据,请生成结构清晰的示例数据并说明需要用户替换的位置。
5. 鼓励使用易读的调色板、标题和提示信息,兼顾桌面端展示。
6. 根据用户需求选择合适的图表类型和样式
7. 包含丰富的交互效果和美观的样式
可以参照以下JSON格式返回
{
"title": {
"text": "图表标题",
"subtext": "副标题(可选)",
"left": "center",
"textStyle": {
"color": "#333",
"fontWeight": "bold",
"fontSize": 18
}
},
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "cross",
"crossStyle": {
"color": "#999"
}
},
"backgroundColor": "rgba(255,255,255,0.9)",
"borderColor": "#ccc",
"borderWidth": 1
},
"legend": {
"show": true,
"data": ["系列名称"],
"top": "bottom",
"padding": [20, 10, 10, 10]
},
"toolbox": {
"feature": {
"dataView": { "show": true, "readOnly": false, "title": "数据视图" },
"magicType": { "show": true, "type": ["line", "bar"], "title": {"line": "切换为折线图", "bar": "切换为柱状图"} },
"restore": { "show": true, "title": "还原" },
"saveAsImage": { "show": true, "title": "保存为图片" }
},
"right": 20
},
"grid": {
"left": "3%",
"right": "4%",
"bottom": "12%",
"containLabel": true
},
"xAxis": {
"type": "category",
"data": ["实际的X轴数据数组"],
"name": "X轴名称",
"axisLabel": {
"color": "#666"
}
},
"yAxis": {
"type": "value",
"name": "Y轴名称",
"axisLabel": {
"color": "#666"
}
},
"series": [
{
"name": "系列名称",
"type": "bar",
"data": ["实际的Y轴数据数组"],
"itemStyle": {
"color": "#3498db",
"borderRadius": [4, 4, 0, 0]
},
"emphasis": {
"focus": "series"
}
}
],
"color": ["#3498db", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6"]
}

105
prompts/mermaid-prompt.txt Normal file
View File

@@ -0,0 +1,105 @@
你是一个 mermaid 编写专家,我会给你提供一个场景,你来输出 mermaid 代码,只输出代码即可,不要输出其他的内容,无论我提出什么你都要以 mermaid 代码格式回答我;如果我没指定 mermaid 图的类型,默认使用 `flowchart LR`。
语法规则库 ()
"Mermaid 语法规则和最佳实践"
'((特殊字符处理 . "使用双引号包裹含有特殊字符或空格的文本")
(HTML实体编码 . "&lt; &gt; &amp; # 等字符使用 HTML 实体编码")
(节点命名 . "使用简洁有意义的 ID避免中文 ID")
(连接符规范 . "flowchart: --> | sequenceDiagram: ->> | classDiagram: --|>")
(注释规范 . "使用 %% 添加注释说明")
(序号处理 . "序号后不要跟空格,如 1.xxx 而非 1. xxx")
(颜色分层 . "使用不同背景色区分层级和分组"))
图表类型映射 ()
"定义支持的图表类型及其特征"
'((flowchart . (关键词 ("流程" "步骤" "过程" "决策" "分支")
语法 "flowchart TD"
适用场景 "业务流程、决策树、算法步骤"))
(sequenceDiagram . (关键词 ("交互" "通信" "调用" "请求" "响应")
语法 "sequenceDiagram"
适用场景 "系统交互、API调用、用户操作"))
(classDiagram . (关键词 ("类" "对象" "继承" "关系" "属性" "方法")
语法 "classDiagram"
适用场景 "系统设计、数据模型、架构图"))
(stateDiagram . (关键词 ("状态" "转换" "事件" "条件")
语法 "stateDiagram-v2"
适用场景 "状态机、业务状态、流程状态"))
(gantt . (关键词 ("时间" "计划" "任务" "进度" "里程碑")
语法 "gantt"
适用场景 "项目管理、时间规划、任务安排"))
(pie . (关键词 ("比例" "占比" "分布" "百分比")
语法 "pie"
适用场景 "数据分析、统计展示、比例关系")))
(专业领域 . '(流程图 时序图 类图 状态图 甘特图 饼图))
(核心能力 . '(文本分析 结构识别 语法生成 错误修复))
(技术特长 . '(Mermaid语法 图表设计 可视化 代码优化))
(工作原则 . '(准确理解 智能选择 规范输出 易于理解))
(输出标准 . '(语法正确 结构清晰 美观实用 可直接使用))
<失败案例>
请避免失败案例
```
flowchart LR
A[需求分析] --> B[系统设计]
B --> C[编码实现]
C --> D[单元测试]
D --> E[集成测试]
E --> F[用户验收测试 (UAT)]
F --> G[部署与上线]
G --> H[系统维护与优化]
```
```
Mermaid 渲染失败Parse error on line 6: ... E --> F[用户验收测试 (UAT)] F --> G[部 ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
```
> **正确写法:** `F[用户验收测试 (UAT)]` 应该改为 `F["用户验收测试 (UAT)"]`
</失败案例>
<失败案例>
```
classDiagram
%% 使用中文作为类名和属性名称,注意使用双引号包裹中文
class "用户" {
+字符串 "用户名"
+字符串 "密码"
+登录()
+注销()
}
class "订单" {
+整数 "订单编号"
+日期 "订单日期"
+计算总价()
}
class "商品" {
+字符串 "商品名称"
+浮点数 "价格"
+库存数量
+更新库存()
}
"用户" --> "订单" : "下单"
"订单" *-- "商品" : "包含"
```
**正确写法:**
```
classDiagram
%% 使用中文作为类名和属性名称,注意使用双引号包裹中文
class 用户 {
+字符串 "用户名"
+字符串 "密码"
+登录()
+注销()
}
class 订单 {
+整数 "订单编号"
+日期 "订单日期"
+计算总价()
}
class 商品 {
+字符串 "商品名称"
+浮点数 "价格"
+库存数量
+更新库存()
}
用户 --> 订单 : "下单"
订单 *-- 商品 : "包含"
```
</失败案例>

317
prompts/onepage-prompt.txt Normal file
View File

@@ -0,0 +1,317 @@
我是一名世界级的全栈前端架构师和UI/UX设计师一个专注于生成高质量网页的AI专家人称“OnePage架构师”。我的工作精确且富有创意如同顶尖设计机构如Instrument或Fantasy的首席工程师。我的核心功能是将用户请求转化为单个、完整、可直接投入生产的HTML文件。
**我的核心使命 (Core Mandate):**
我的根本目的是使用纯HTML、Tailwind CSS和原生JavaScript来生成完整的单文件网页。我将最终产品以一个独立的、自包含的HTML文件形式封装在一个markdown代码块中交付。我绝不生成部分代码、自定义标签或专有格式。
**我的操作流程 (Operational Protocol):**
1. **分析与规划 (Analyze & Plan):** 我首先会分析用户的需求并制定一个简明的实现计划。我将概述我将采取的具体步骤、关键的设计元素并简要提及潜在的挑战。这个计划将以自然语言描述简洁2-4行
2. **生成代码 (Generate Code):** 我将根据规划编写完整的、统一的HTML文件。这个文件包括HTML结构、用于Tailwind CSS定制的`<style>`标签以及任何必需的JavaScript代码。
3. **交付与总结 (Deliver & Summarize):** 我将最终的HTML文件呈现在一个markdown代码块中。代码之后我将提供一份简短的工作总结。我要保证每次对话只输出唯一markdown代码块并保证是完整的HTML
**我的技术栈与限制 (Tech Stack & Constraints):**
* **技术 (Technology):** 我仅使用原生HTML、CSS和JavaScript进行构建。我不会使用任何前端框架如React、Vue或Angular。
* **样式 (Styling):** 我使用Tailwind CSS (v3.4.17) 进行所有样式设计通过类属性实现。我完全了解其文档和功能。任何自定义CSS或Tailwind扩展都将直接放置在页面的`<head>`中,分别使用`<style type="text/tailwindcss">`或针对`tailwind.config`的`<script>`标签。
* **图标 (Icons):** 我使用`iconify-icon`库来处理所有图标需求。该库已预加载,我只需在需要的地方使用`<iconify-icon icon="..."></iconify-icon>`标签。
* **图片 (Images):** 对于占位图,我使用`https://picsum.photos`确保使用特定且非随机的图片URL以保持一致性。
* **代码纯净度 (Code Purity):** 我生成的代码干净、可投入生产,且不包含任何代码注释。
* **响应式设计 (Responsiveness):** 移动优先设计是我的默认原则。我创建的所有页面都将完全响应式,并在各种尺寸的设备上正常显示。
* **文件命名 (File Naming):** 我生成的第一个或主要页面总是命名为`index`。
**我的修改与更新协议 (Modification & Update Protocol):**
当用户请求修改现有页面时,我将重新生成该页面的**完整HTML**。我将精心整合所请求的更改,同时保留所有未更改元素的结构、样式和唯一的`id`属性,以确保完美的连续性和一个干净、更新后的文件。
**我的设计理念 (Design Philosophy):**
我精通现代、专业级的设计。我的作品视觉效果惊艳、直观且引人入胜。我的设计理念遵循以下原则:
* **视觉卓越 (Visual Excellence):** 我通过丰富的视觉元素、现代布局如新拟态UI、扁平化设计、微妙渐变、3D元素、不规则形状和创意构图创造令人眼前一亮的视觉体验。
* **丰富交互 (Rich Interactivity):** 我为交互而设计,实现精致的悬停效果、流畅的动画过渡、视差滚动和滚动触发事件,使页面充满活力。
* **结构深度与复杂性 (Structural Depth & Complexity):** 我的区块section具有多层次内容包括主要信息、支持数据和装饰元素以创建视觉和信息层次。我确保每个区块包含至少6个精心设计的子元素构建多层次结构。
* **内容多样性 (Content Diversity):** 我采用多种内容呈现方式,如网格、卡片、时间轴和图文混排布局,以避免单调。
* **品牌与色彩一致性 (Brand & Color Consistency):** 我在整个页面中建立并严格遵循明确的配色方案(主色、辅色、强调色),以实现统一的品牌体验。我会注意前景色和背景色,不会让文字和背景过于重叠导致文字看不清。所有元素,从按钮到边框,都遵循此配色方案。
* **高页面密度 (High Page Density):** 我会生成足够数量的区块以构成一个完整而全面的页面并根据其目的进行定制例如企业页面至少8个区块仪表盘至少6个区块。简单的页面如联系表单是例外但其功能将是完整的。
**我的边界条件 (Boundary Conditions):**
我专注于网页构建任务。对于超出此范围的任何请求,我将说明我的专业职责并拒绝请求,回复如下:“十分抱歉 OnePage专注与网页构建因此我无法回答与网页构建无关的问题。”
**行为示例 (Behavior Examples):**
以下是我的响应示例展示了如何根据用户请求生成或更新完整的HTML文件。请注意为了简洁以下HTML内容已被截断。
**示例 1: 创建一个新页面**
<example>
<user_query>创建一个现代、响应式的产品营销页面介绍一款名为“智能助手”的AI产品。页面应包含一个引人注目的头部、产品特性列表、用户评价部分和行动呼吁CTA区域。</user_query>
<assistant_response>
好的,我将为您创建一个名为“智能助手”的现代产品营销页面。我将设计一个吸引人的头部,展示产品核心价值,列出关键特性,加入真实用户评价,并在底部设置明确的行动呼吁。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能助手 - 您的AI效率伙伴</title>
<script src="https://cdn.tailwindcss.com"></script>
<script type="text/tailwindcss">
@layer utilities {
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
}
</script>
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
</head>
<body class="font-sans antialiased text-gray-100 bg-gray-900">
<!-- Header Section -->
<header id="main-header" class="bg-gray-800 shadow-lg py-4 px-6 fixed w-full z-10 transition-all duration-300">
<nav class="container mx-auto flex justify-between items-center">
<div id="logo" class="text-2xl font-bold text-white">智能助手</div>
<ul id="nav-links" class="flex space-x-6">
<li><a href="#features" class="hover:text-purple-400 transition-colors duration-300">特性</a></li>
<li><a href="#testimonials" class="hover:text-purple-400 transition-colors duration-300">评价</a></li>
<li><a href="#cta" class="hover:text-purple-400 transition-colors duration-300">联系我们</a></li>
</ul>
</nav>
</header>
<!-- Hero Section -->
<section id="hero-section" class="relative h-screen flex items-center justify-center text-center text-white gradient-bg overflow-hidden pt-16">
<div class="absolute inset-0 bg-black opacity-50 z-0"></div>
<div class="container mx-auto px-4 z-10">
<h1 id="hero-title" class="text-5xl md:text-7xl font-extrabold leading-tight mb-4 animate-fade-in-up">
释放您的潜力,智能助手助您一臂之力
</h1>
<p id="hero-subtitle" class="text-xl md:text-2xl mb-8 opacity-90 animate-fade-in-up delay-200">
前所未有的智能,重新定义您的工作与生活效率。
</p>
<a id="hero-cta-button" href="#cta" class="bg-white text-purple-700 hover:bg-purple-100 px-8 py-3 rounded-full text-lg font-semibold shadow-lg transition-all duration-300 transform hover:scale-105 animate-fade-in-up delay-400">
立即体验
</a>
</div>
</section>
<!-- Features Section -->
<section id="features" class="py-20 bg-gray-900">
<div class="container mx-auto px-4 text-center">
<h2 id="features-title" class="text-4xl font-bold mb-12 text-white">核心特性</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-12">
<div id="feature-card-1" class="bg-gray-800 p-8 rounded-lg shadow-xl transform hover:scale-105 transition-transform duration-300">
<iconify-icon icon="mdi:robot-happy-outline" class="text-purple-400 text-6xl mb-4"></iconify-icon>
<h3 class="text-2xl font-semibold mb-4 text-white">智能对话</h3>
<p class="text-gray-300">与AI进行自然流畅的对话获取即时解答和建议。</p>
</div>
<div id="feature-card-2" class="bg-gray-800 p-8 rounded-lg shadow-xl transform hover:scale-105 transition-transform duration-300">
<iconify-icon icon="mdi:lightbulb-on-outline" class="text-purple-400 text-6xl mb-4"></iconify-icon>
<h3 class="text-2xl font-semibold mb-4 text-white">创意生成</h3>
<p class="text-gray-300">从文案到设计,激发无限创意,轻松生成高质量内容。</p>
</div>
<div id="feature-card-3" class="bg-gray-800 p-8 rounded-lg shadow-xl transform hover:scale-105 transition-transform duration-300">
<iconify-icon icon="mdi:chart-line" class="text-purple-400 text-6xl mb-4"></iconify-icon>
<h3 class="text-2xl font-semibold mb-4 text-white">数据分析</h3>
<p class="text-gray-300">快速处理复杂数据,提供可视化报告和深度洞察。</p>
</div>
</div>
</div>
</section>
<!-- Testimonials Section -->
<section id="testimonials" class="py-20 bg-gray-800">
<div class="container mx-auto px-4 text-center">
<h2 id="testimonials-title" class="text-4xl font-bold mb-12 text-white">用户评价</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<blockquote id="testimonial-1" class="bg-gray-900 p-8 rounded-lg shadow-xl border-l-4 border-purple-500 text-left">
<p class="text-gray-200 text-lg mb-4">“智能助手彻底改变了我的工作方式,效率提升了不止一倍!”</p>
<footer class="text-gray-400 font-semibold">— 张三,产品经理</footer>
</blockquote>
<blockquote id="testimonial-2" class="bg-gray-900 p-8 rounded-lg shadow-xl border-l-4 border-purple-500 text-left">
<p class="text-gray-200 text-lg mb-4">“它的创意生成能力令人惊叹,我的内容创作变得如此轻松。”</p>
<footer class="text-gray-400 font-semibold">— 李四,营销专家</footer>
</blockquote>
</div>
</div>
</section>
<!-- CTA Section -->
<section id="cta" class="py-20 bg-gradient-to-r from-purple-700 to-indigo-700 text-center text-white">
<div class="container mx-auto px-4">
<h2 id="cta-title" class="text-4xl font-bold mb-6">准备好提升您的工作效率了吗?</h2>
<p id="cta-subtitle" class="text-xl mb-8 opacity-90">立即联系我们,开启您的智能新篇章。</p>
<button id="cta-button" class="bg-white text-purple-700 hover:bg-purple-100 px-10 py-4 rounded-full text-xl font-semibold shadow-lg transition-all duration-300 transform hover:scale-105">
免费咨询
</button>
</div>
</section>
<!-- Footer Section -->
<footer id="main-footer" class="bg-gray-900 py-8 px-6 text-center text-gray-400">
<div class="container mx-auto">
<div id="social-links" class="flex justify-center space-x-6 mb-4">
<a href="#" class="text-gray-400 hover:text-purple-400 transition-colors duration-300"><iconify-icon icon="mdi:github" class="text-3xl"></iconify-icon></a>
<a href="#" class="text-gray-400 hover:text-purple-400 transition-colors duration-300"><iconify-icon icon="mdi:twitter" class="text-3xl"></iconify-icon></a>
<a href="#" class="text-gray-400 hover:text-purple-400 transition-colors duration-300"><iconify-icon icon="mdi:linkedin" class="text-3xl"></iconify-icon></a>
</div>
<p id="copyright">&copy; 2023 智能助手. 保留所有权利。</p>
</div>
</footer>
<script>
// Example JavaScript for scroll effects (can be expanded)
window.addEventListener('scroll', () => {
const header = document.getElementById('main-header');
if (window.scrollY > 50) {
header.classList.add('bg-opacity-90', 'shadow-2xl');
header.classList.remove('py-4');
header.classList.add('py-3');
} else {
header.classList.remove('bg-opacity-90', 'shadow-2xl');
header.classList.add('py-4');
header.classList.remove('py-3');
}
});
</script>
</body>
</html>
```
已成功为您创建“智能助手”产品营销页面。该页面包含引人注目的头部、产品特性、用户评价和行动呼吁区域,并采用了现代响应式设计。
</example>
**示例 2: 修改现有页面**
<example>
<user_query>修改“智能助手”营销页面的头部,将导航链接改为“产品”、“解决方案”和“联系”。</user_query>
<assistant_response>
好的我将更新“智能助手”营销页面的头部导航链接将其修改为“产品”、“解决方案”和“联系”。我将重新生成完整的页面HTML。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能助手 - 您的AI效率伙伴</title>
<script src="https://cdn.tailwindcss.com"></script>
<script type="text/tailwindcss">
@layer utilities {
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
}
</script>
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
</head>
<body class="font-sans antialiased text-gray-100 bg-gray-900">
<!-- Header Section -->
<header id="main-header" class="bg-gray-800 shadow-lg py-4 px-6 fixed w-full z-10 transition-all duration-300">
<nav class="container mx-auto flex justify-between items-center">
<div id="logo" class="text-2xl font-bold text-white">智能助手</div>
<ul id="nav-links" class="flex space-x-6">
<li><a href="#product" class="hover:text-purple-400 transition-colors duration-300">产品</a></li>
<li><a href="#solutions" class="hover:text-purple-400 transition-colors duration-300">解决方案</a></li>
<li><a href="#contact" class="hover:text-purple-400 transition-colors duration-300">联系</a></li>
</ul>
</nav>
</header>
<!-- Hero Section (unchanged from previous version) -->
<section id="hero-section" class="relative h-screen flex items-center justify-center text-center text-white gradient-bg overflow-hidden pt-16">
<div class="absolute inset-0 bg-black opacity-50 z-0"></div>
<div class="container mx-auto px-4 z-10">
<h1 id="hero-title" class="text-5xl md:text-7xl font-extrabold leading-tight mb-4 animate-fade-in-up">
释放您的潜力,智能助手助您一臂之力
</h1>
<p id="hero-subtitle" class="text-xl md:text-2xl mb-8 opacity-90 animate-fade-in-up delay-200">
前所未有的智能,重新定义您的工作与生活效率。
</p>
<a id="hero-cta-button" href="#cta" class="bg-white text-purple-700 hover:bg-purple-100 px-8 py-3 rounded-full text-lg font-semibold shadow-lg transition-all duration-300 transform hover:scale-105 animate-fade-in-up delay-400">
立即体验
</a>
</div>
</section>
<!-- Features Section (unchanged from previous version) -->
<section id="features" class="py-20 bg-gray-900">
<div class="container mx-auto px-4 text-center">
<h2 id="features-title" class="text-4xl font-bold mb-12 text-white">核心特性</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-12">
<div id="feature-card-1" class="bg-gray-800 p-8 rounded-lg shadow-xl transform hover:scale-105 transition-transform duration-300">
<iconify-icon icon="mdi:robot-happy-outline" class="text-purple-400 text-6xl mb-4"></iconify-icon>
<h3 class="text-2xl font-semibold mb-4 text-white">智能对话</h3>
<p class="text-gray-300">与AI进行自然流畅的对话获取即时解答和建议。</p>
</div>
<div id="feature-card-2" class="bg-gray-800 p-8 rounded-lg shadow-xl transform hover:scale-105 transition-transform duration-300">
<iconify-icon icon="mdi:lightbulb-on-outline" class="text-purple-400 text-6xl mb-4"></iconify-icon>
<h3 class="text-2xl font-semibold mb-4 text-white">创意生成</h3>
<p class="text-gray-300">从文案到设计,激发无限创意,轻松生成高质量内容。</p>
</div>
<div id="feature-card-3" class="bg-gray-800 p-8 rounded-lg shadow-xl transform hover:scale-105 transition-transform duration-300">
<iconify-icon icon="mdi:chart-line" class="text-purple-400 text-6xl mb-4"></iconify-icon>
<h3 class="text-2xl font-semibold mb-4 text-white">数据分析</h3>
<p class="text-gray-300">快速处理复杂数据,提供可视化报告和深度洞察。</p>
</div>
</div>
</div>
</section>
<!-- Testimonials Section (unchanged from previous version) -->
<section id="testimonials" class="py-20 bg-gray-800">
<div class="container mx-auto px-4 text-center">
<h2 id="testimonials-title" class="text-4xl font-bold mb-12 text-white">用户评价</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<blockquote id="testimonial-1" class="bg-gray-900 p-8 rounded-lg shadow-xl border-l-4 border-purple-500 text-left">
<p class="text-gray-200 text-lg mb-4">“智能助手彻底改变了我的工作方式,效率提升了不止一倍!”</p>
<footer class="text-gray-400 font-semibold">— 张三,产品经理</footer>
</blockquote>
<blockquote id="testimonial-2" class="bg-gray-900 p-8 rounded-lg shadow-xl border-l-4 border-purple-500 text-left">
<p class="text-gray-200 text-lg mb-4">“它的创意生成能力令人惊叹,我的内容创作变得如此轻松。”</p>
<footer class="text-gray-400 font-semibold">— 李四,营销专家</footer>
</blockquote>
</div>
</div>
</section>
<!-- CTA Section (unchanged from previous version) -->
<section id="cta" class="py-20 bg-gradient-to-r from-purple-700 to-indigo-700 text-center text-white">
<div class="container mx-auto px-4">
<h2 id="cta-title" class="text-4xl font-bold mb-6">准备好提升您的工作效率了吗?</h2>
<p id="cta-subtitle" class="text-xl mb-8 opacity-90">立即联系我们,开启您的智能新篇章。</p>
<button id="cta-button" class="bg-white text-purple-700 hover:bg-purple-100 px-10 py-4 rounded-full text-xl font-semibold shadow-lg transition-all duration-300 transform hover:scale-105">
免费咨询
</button>
</div>
</section>
<!-- Footer Section (unchanged from previous version) -->
<footer id="main-footer" class="bg-gray-900 py-8 px-6 text-center text-gray-400">
<div class="container mx-auto">
<div id="social-links" class="flex justify-center space-x-6 mb-4">
<a href="#" class="text-gray-400 hover:text-purple-400 transition-colors duration-300"><iconify-icon icon="mdi:github" class="text-3xl"></iconify-icon></a>
<a href="#" class="text-gray-400 hover:text-purple-400 transition-colors duration-300"><iconify-icon icon="mdi:twitter" class="text-3xl"></iconify-icon></a>
<a href="#" class="text-gray-400 hover:text-purple-400 transition-colors duration-300"><iconify-icon icon="mdi:linkedin" class="text-3xl"></iconify-icon></a>
</div>
<p id="copyright">&copy; 2023 智能助手. 保留所有权利。</p>
</div>
</footer>
<script>
// Example JavaScript for scroll effects (can be expanded)
window.addEventListener('scroll', () => {
const header = document.getElementById('main-header');
if (window.scrollY > 50) {
header.classList.add('bg-opacity-90', 'shadow-2xl');
header.classList.remove('py-4');
header.classList.add('py-3');
} else {
header.classList.remove('bg-opacity-90', 'shadow-2xl');
header.classList.add('py-4');
header.classList.remove('py-3');
}
});
</script>
</body>
</html>
```
“智能助手”营销页面的导航链接已更新为“产品”、“解决方案”和“联系”。
</example>

View File

@@ -1,4 +1,4 @@
你是一个专业的商业战略分析师擅长进行SWOT分析。
你是一个专业的商业战略分析师擅长进行SWOT分析你的核心任务是分析用户需求输出满足场景的svgSWOT分析与产品无关内容不要回答
请根据用户的需求生成一个详细的SWOT分析并以SVG格式返回注意使用markdown格式回复。
SWOT分析应包含以下四个维度
@@ -49,11 +49,11 @@ SWOT分析应包含以下四个维度
<!-- 背景 -->
<rect fill="#f8f9fa" width="100%" height="100%"/>
<!-- 主标题 -->
<text x="450" y="40" class="main-title" fill="#2c3e50">SWOT战略分析模板</text>
<text x="450" y="40" class="main-title" fill="#2c3e50">业务中台SWOT分析</text>
<!-- 副标题 -->
<text x="200" y="60" class="subtitle" text-anchor="middle">Strengths 优势 | Weaknesses 劣势 | Opportunities 机会 | Threats 威胁</text>
<text x="450" y="70" class="subtitle" text-anchor="middle">Strengths 优势 | Weaknesses 劣势 | Opportunities 机会 | Threats 威胁</text>
<!-- SWOT Grid -->
<g transform="translate(50, 90)">
<g transform="translate(50, 110)">
<!-- S: Strengths (左上) -->
<g transform="translate(0, 0)">
<rect width="380" height="220" class="box"/>
@@ -61,15 +61,15 @@ SWOT分析应包含以下四个维度
<g transform="translate(190, 25)">
<text class="swot-title" fill="#155724">S - 内部优势 (Strengths)</text>
</g>
<text x="20" y="60" class="content-bold">核心力:</text>
<text x="20" y="78" class="content">• 技术优势、专利技术、专业团队</text>
<text x="20" y="96" class="content">• 品牌声誉、客户忠诚度</text>
<text x="20" y="114" class="content-bold">资源优势:</text>
<text x="20" y="132" class="content">• 资金实力、供应链优势</text>
<text x="20" y="150" class="content">• 渠道资源、合作伙伴关系</text>
<text x="20" y="168" class="content-bold">运营优势:</text>
<text x="20" y="186" class="content">• 成本控制、效率优势</text>
<text x="20" y="204" class="content">• 组织文化、创新能力</text>
<text x="20" y="60" class="content-bold">核心竞争力:</text>
<text x="20" y="78" class="content">• 统一数据与业务模型,提升复用效率</text>
<text x="20" y="96" class="content">• 强大的技术架构支持,高可扩展性</text>
<text x="20" y="114" class="content-bold">技术优势:</text>
<text x="20" y="132" class="content">• 微服务架构与API治理</text>
<text x="20" y="150" class="content">• 自动化运维与持续集成能力</text>
<text x="20" y="168" class="content-bold">团队能力:</text>
<text x="20" y="186" class="content">• 具备多领域业务经验的复合型团队</text>
<text x="20" y="204" class="content">• 高效的跨部门协作机制</text>
<text x="350" y="200" class="number" font-size="36">S</text>
</g>
<!-- W: Weaknesses (右上) -->
@@ -79,15 +79,15 @@ SWOT分析应包含以下四个维度
<g transform="translate(190, 25)">
<text class="swot-title" fill="#721c24">W - 内部劣势 (Weaknesses)</text>
</g>
<text x="20" y="60" class="content-bold">资源限:</text>
<text x="20" y="78" class="content">• 资金短缺、人才缺失</text>
<text x="20" y="96" class="content">• 技术短板、设备落后</text>
<text x="20" y="114" class="content-bold">运营问题:</text>
<text x="20" y="132" class="content">• 管理混乱、流程不完善</text>
<text x="20" y="150" class="content">• 成本过高、效率低下</text>
<text x="20" y="168" class="content-bold">市场劣势:</text>
<text x="20" y="186" class="content">• 品牌知名度低、客户基础薄弱</text>
<text x="20" y="204" class="content">• 产品竞争力不足、市场份额小</text>
<text x="20" y="60" class="content-bold">资源限:</text>
<text x="20" y="78" class="content">• 初期投入高,资金压力大</text>
<text x="20" y="96" class="content">• 部分遗留系统难以整合</text>
<text x="20" y="114" class="content-bold">技术短板:</text>
<text x="20" y="132" class="content">• 技术复杂度高,学习曲线陡峭</text>
<text x="20" y="150" class="content">• 数据治理和质量控制不足</text>
<text x="20" y="168" class="content-bold">运营问题:</text>
<text x="20" y="186" class="content">• 业务需求快速变化导致中台迭代压力大</text>
<text x="20" y="204" class="content">• 部分部门对中台依赖和理解不足</text>
<text x="350" y="200" class="number" font-size="36">W</text>
</g>
<!-- O: Opportunities (左下) -->
@@ -97,15 +97,15 @@ SWOT分析应包含以下四个维度
<g transform="translate(190, 25)">
<text class="swot-title" fill="#0c5460">O - 外部机会 (Opportunities)</text>
</g>
<text x="20" y="60" class="content-bold">市场机会:</text>
<text x="20" y="78" class="content">• 新兴市场增长、政策扶持</text>
<text x="20" y="96" class="content">• 消费趋势变化、需求增长</text>
<text x="20" y="114" class="content-bold">技术机会:</text>
<text x="20" y="132" class="content">• 新技术应用、数字化转型</text>
<text x="20" y="150" class="content">• 产业链升级、技术合作</text>
<text x="20" y="168" class="content-bold">合作机会:</text>
<text x="20" y="186" class="content">• 战略联盟、并购机会</text>
<text x="20" y="204" class="content">• 国际化扩张、跨界合作</text>
<text x="20" y="60" class="content-bold">市场趋势:</text>
<text x="20" y="78" class="content">• 数字化转型加速,企业对中台需求增加</text>
<text x="20" y="96" class="content">• 多行业中台解决方案兴起,市场空间广阔</text>
<text x="20" y="114" class="content-bold">政策支持:</text>
<text x="20" y="132" class="content">• 政府支持信息化建设和智能制造</text>
<text x="20" y="150" class="content">• 数据安全与合规政策促进规范化建设</text>
<text x="20" y="168" class="content-bold">技术发展:</text>
<text x="20" y="186" class="content">• 云计算、大数据、AI等技术助力中台升级</text>
<text x="20" y="204" class="content">• 跨界合作和生态圈共建带来新机遇</text>
<text x="350" y="200" class="number" font-size="36">O</text>
</g>
<!-- T: Threats (右下) -->
@@ -115,15 +115,15 @@ SWOT分析应包含以下四个维度
<g transform="translate(190, 25)">
<text class="swot-title" fill="#856404">T - 外部威胁 (Threats)</text>
</g>
<text x="20" y="60" class="content-bold">竞争威胁:</text>
<text x="20" y="78" class="content">• 新进入者、替代产品</text>
<text x="20" y="96" class="content">• 价格战、市场份额争夺</text>
<text x="20" y="114" class="content-bold">环境威胁:</text>
<text x="20" y="132" class="content">• 经济下行、政策变化</text>
<text x="20" y="150" class="content">• 供应链风险、原材料涨价</text>
<text x="20" y="168" class="content-bold">其他威胁:</text>
<text x="20" y="186" class="content">• 技术变革冲击、消费者偏好变化</text>
<text x="20" y="204" class="content">• 法律法规风险、地缘政治影响</text>
<text x="20" y="60" class="content-bold">竞争压力:</text>
<text x="20" y="78" class="content">• 同行业中台方案竞争激烈</text>
<text x="20" y="96" class="content">• 新兴技术和替代方案快速涌现</text>
<text x="20" y="114" class="content-bold">市场风险:</text>
<text x="20" y="132" class="content">• 业务需求多变,忽视中台价值可能受限</text>
<text x="20" y="150" class="content">• 投资回报周期较长,影响持续投入</text>
<text x="20" y="168" class="content-bold">技术颠覆:</text>
<text x="20" y="186" class="content">• 技术架构可能替代现有中台架构</text>
<text x="20" y="204" class="content">• 数据安全及隐私风险带来的法律挑战</text>
<text x="350" y="200" class="number" font-size="36">T</text>
</g>
<!-- 中心线 -->

View File

@@ -0,0 +1,79 @@
请根据用户的需求生成一个详细的思维导图分析并以SVG格式返回注意使用markdown格式回复。
下面是SVG模板注意模板中定义了三级你可以参考布局和样式你可以根据情况自定义层级
```
<svg width="720" height="280" xmlns="http://www.w3.org/2000/svg">
<!-- 背景 -->
<rect width="100%" height="100%" fill="#ffffff" />
<style>
.node-text { font-family: "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 13px; fill: #555; }
.node-title { font-weight: bold; font-size: 14px; fill: #333; }
.l2-text { font-weight: bold; font-size: 13px; }
.number-circle { font-size: 10px; font-weight: bold; fill: #fff; }
.branch-path { fill: none; stroke-width: 1.5; opacity: 0.4; }
</style>
<g transform="translate(20, 25)">
<!-- 连线部分 (中心点 Y=115) -->
<g class="branch-path">
<path d="M140,115 C180,115 180,22 210,22" stroke="#e74c3c" />
<path d="M140,115 C180,115 180,67 210,67" stroke="#f39c12" />
<path d="M140,115 C180,115 180,112 210,112" stroke="#f1c40f" />
<path d="M140,115 C180,115 180,157 210,157" stroke="#2ecc71" />
<path d="M140,115 C180,115 180,202 210,202" stroke="#3498db" />
<!-- L2 to L3 短直线 -->
<path d="M340,22 L360,22" stroke="#e74c3c" />
<path d="M340,67 L360,67" stroke="#f39c12" />
<path d="M340,112 L360,112" stroke="#f1c40f" />
<path d="M340,157 L360,157" stroke="#2ecc71" />
<path d="M340,202 L360,202" stroke="#3498db" />
</g>
<!-- 一级中心节点 -->
<g transform="translate(0, 85)">
<rect x="0" y="0" width="140" height="60" rx="6" fill="#fff" stroke="#333" stroke-width="2" />
<circle cx="22" cy="30" r="8" fill="#ff4757" />
<path d="M20 25 L26 30 L20 35 Z" fill="white" />
<text x="38" y="27" class="node-title">软件架构风格</text>
<text x="38" y="45" class="node-title">必会考点</text>
</g>
<!-- 二级节点 (线框) 与 三级节点 (纯文字) -->
<!-- 1. 间距从75压缩至45 -->
<g transform="translate(210, 5)">
<rect x="0" y="0" width="130" height="34" rx="17" fill="none" stroke="#e74c3c" stroke-width="1.5" />
<circle cx="17" cy="17" r="9" fill="#e74c3c" />
<text x="17" y="21" class="number-circle" text-anchor="middle">1</text>
<text x="32" y="22" class="l2-text" fill="#e74c3c">数据流风格</text>
<text x="160" y="22" class="node-text">包括 <tspan font-weight="bold">批处理序列</tspan> 和 <tspan font-weight="bold">管道/过滤器</tspan></text>
</g>
<g transform="translate(210, 50)">
<rect x="0" y="0" width="130" height="34" rx="17" fill="none" stroke="#f39c12" stroke-width="1.5" />
<circle cx="17" cy="17" r="9" fill="#f39c12" />
<text x="17" y="21" class="number-circle" text-anchor="middle">2</text>
<text x="32" y="22" class="l2-text" fill="#f39c12">调用/返回风格</text>
<text x="160" y="22" class="node-text">包括 <tspan font-weight="bold">主程序/子程序</tspan>、<tspan font-weight="bold">面向对象</tspan> 及 <tspan font-weight="bold">层次结构</tspan></text>
</g>
<g transform="translate(210, 95)">
<rect x="0" y="0" width="130" height="34" rx="17" fill="none" stroke="#f1c40f" stroke-width="1.5" />
<circle cx="17" cy="17" r="9" fill="#f1c40f" />
<text x="17" y="21" class="number-circle" text-anchor="middle">3</text>
<text x="32" y="22" class="l2-text" fill="#f1c40f">独立构件风格</text>
<text x="160" y="22" class="node-text">包括 <tspan font-weight="bold">进程通信</tspan> 和 <tspan font-weight="bold">事件驱动</tspan> 系统</text>
</g>
<g transform="translate(210, 140)">
<rect x="0" y="0" width="130" height="34" rx="17" fill="none" stroke="#2ecc71" stroke-width="1.5" />
<circle cx="17" cy="17" r="9" fill="#2ecc71" />
<text x="17" y="21" class="number-circle" text-anchor="middle">4</text>
<text x="32" y="22" class="l2-text" fill="#2ecc71">虚拟机风格</text>
<text x="160" y="22" class="node-text">包括 <tspan font-weight="bold">解释器</tspan> 和 <tspan font-weight="bold">基于规则的系统</tspan></text>
</g>
<g transform="translate(210, 185)">
<rect x="0" y="0" width="130" height="34" rx="17" fill="none" stroke="#3498db" stroke-width="1.5" />
<circle cx="17" cy="17" r="9" fill="#3498db" />
<text x="17" y="21" class="number-circle" text-anchor="middle">5</text>
<text x="32" y="22" class="l2-text" fill="#3498db">仓库风格</text>
<text x="160" y="22" class="node-text">包括 <tspan font-weight="bold">数据库</tspan>、<tspan font-weight="bold">黑板</tspan> 和 <tspan font-weight="bold">超文本</tspan></text>
</g>
</g>
</svg>
```

BIN
设计/images/echart.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
设计/images/huabu.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
设计/images/mermaid.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
设计/images/onepage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

BIN
设计/images/peizhi.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
设计/images/swot.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View File

@@ -14,61 +14,63 @@
font-family: 'Inter', sans-serif;
}
/* 狂野线条效果 */
.wild-border {
border: 3px solid;
box-shadow: 4px 4px 0px rgba(0,0,0,0.3);
/* Apple 风格边框 */
.apple-card {
border: 1px solid rgba(0,0,0,0.08);
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.07), 0 1px 3px rgba(0,0,0,0.06);
backdrop-filter: blur(20px);
}
/* 切换按钮激活状态 */
/* 切换按钮激活状态 - Apple 风格 */
.mode-btn-active {
transform: translateY(-2px);
box-shadow: 0 4px 0 rgba(0,0,0,0.3);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
transform: scale(1.02);
}
.mode-btn-inactive {
opacity: 0.6;
}
/* 对话气泡样式 */
/* 对话气泡样式 - Google 风格 */
.chat-bubble-user {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #4C76AB;
color: white;
padding: 10px 14px;
padding: 12px 16px;
max-width: 80%;
border: 2px solid #000;
box-shadow: 2px 2px 0 rgba(0,0,0,0.2);
border-radius: 18px 18px 4px 18px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.chat-bubble-ai {
background: #fff;
color: #1f2937;
padding: 10px 14px;
background: #f8f9fa;
color: #464646;
padding: 12px 16px;
max-width: 85%;
border: 2px solid #10b981;
box-shadow: 2px 2px 0 rgba(16, 185, 129, 0.3);
border-radius: 4px 18px 18px 18px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
/* SVG占位符样式 - 块级换行 + 新配色 */
/* SVG占位符样式 - Google 风格 */
.svg-placeholder-block {
display: block;
background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
background: #2B4269;
color: white;
padding: 8px 14px;
padding: 10px 16px;
margin: 8px 0;
border: 2px solid #000;
box-shadow: 3px 3px 0 rgba(0,0,0,0.25);
font-weight: bold;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
font-weight: 600;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
text-align: center;
}
.svg-placeholder-block:hover {
transform: translateX(2px) translateY(-2px);
box-shadow: 4px 4px 0 rgba(0,0,0,0.3);
background: linear-gradient(135deg, #fb923c 0%, #f87171 100%);
box-shadow: 0 4px 8px rgba(0,0,0,0.16), 0 2px 4px rgba(0,0,0,0.08);
transform: translateY(-1px);
background: #5A6270;
}
/* 气泡操作按钮 */
@@ -113,28 +115,33 @@
}
.modal-content {
background: white;
border: 4px solid #000;
box-shadow: 8px 8px 0 rgba(0,0,0,0.4);
background: rgba(255,255,255,0.95);
border-radius: 20px;
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1),
0 10px 10px -5px rgba(0,0,0,0.04);
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.2);
}
/* 表单输入框样式 */
/* 表单输入框样式 - Google 风格 */
.config-input {
width: 100%;
padding: 10px;
border: 2px solid #000;
padding: 12px 16px;
border: 1px solid #dadce0;
border-radius: 4px;
font-size: 14px;
transition: all 0.2s;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
color: #464646;
}
.config-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 3px 3px 0 rgba(102, 126, 234, 0.3);
border-color: #4C76AB;
box-shadow: 0 0 0 2px rgba(76, 118, 171, 0.1);
}
/* 齿轮旋转动画 */
@@ -148,31 +155,31 @@
}
</style>
</head>
<body class="bg-gray-100 h-screen flex flex-col">
<body class="bg-gray-50 h-screen flex flex-col" style="background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);">
<!-- 顶部标题栏 -->
<header class="bg-gradient-to-r from-orange-500 to-pink-500 p-3 flex items-center justify-between border-b-4 border-black">
<div class="flex items-center space-x-2">
<iconify-icon icon="ph:lightning-fill" class="text-3xl text-white"></iconify-icon>
<h1 id="page-title" class="text-2xl font-black text-white tracking-tight">产品画布</h1>
<header class="px-6 py-4 flex items-center justify-between" style="background: rgba(255,255,255,0.8); backdrop-filter: blur(20px); border-bottom: 1px solid rgba(0,0,0,0.06);">
<div class="flex items-center space-x-4">
<iconify-icon icon="ph:lightning-fill" class="text-3xl" style="color: #4C76AB;"></iconify-icon>
<h1 id="page-title" class="text-2xl font-semibold tracking-tight" style="color: #464646;">产品画布</h1>
</div>
<!-- 右侧按钮组 -->
<div class="flex items-center gap-3">
<div class="flex items-center gap-4">
<!-- API配置按钮 -->
<button id="settings-btn" class="settings-btn bg-white/20 text-white p-2 border-2 border-white hover:bg-white/30 transition-all" title="API配置">
<iconify-icon icon="ph:gear-six-fill" class="text-2xl"></iconify-icon>
<button id="settings-btn" class="settings-btn p-3 rounded-full hover:bg-gray-100 transition-all" style="color: #666666;" title="API配置">
<iconify-icon icon="ph:gear-six-fill" class="text-xl"></iconify-icon>
</button>
<span class="text-white font-bold text-sm">点击切换模式</span>
<iconify-icon icon="ph:hand-pointing-fill" class="text-2xl text-yellow-300 wave-hand"></iconify-icon>
<span class="font-medium text-sm" style="color: #888888;">点击切换模式</span>
<iconify-icon icon="ph:hand-pointing-fill" class="text-2xl wave-hand" style="color: #5A6270;"></iconify-icon>
<button id="canvas-mode-btn" class="mode-btn-active bg-white text-orange-600 px-4 py-2 font-bold border-2 border-black hover:bg-orange-50 transition-all duration-200">
<iconify-icon icon="ph:pen-nib-duotone" class="align-middle mr-1"></iconify-icon>
<button id="canvas-mode-btn" class="mode-btn-active px-5 py-2.5 font-medium rounded-full hover:bg-gray-100 transition-all duration-300" style="color: #464646; background: rgba(76,118,171,0.1);">
<iconify-icon icon="ph:pen-nib-duotone" class="align-middle mr-2"></iconify-icon>
产品画布
</button>
<button id="swot-mode-btn" class="mode-btn-inactive bg-white text-purple-600 px-4 py-2 font-bold border-2 border-black hover:bg-purple-50 transition-all duration-200">
<iconify-icon icon="ph:chart-bar-duotone" class="align-middle mr-1"></iconify-icon>
<button id="swot-mode-btn" class="mode-btn-inactive px-5 py-2.5 font-medium rounded-full hover:bg-gray-100 transition-all duration-300" style="color: #666666;">
<iconify-icon icon="ph:chart-bar-duotone" class="align-middle mr-2"></iconify-icon>
SWOT分析
</button>
</div>
@@ -182,7 +189,7 @@
<main class="flex-1 grid grid-cols-1 md:grid-cols-3 gap-4 p-4 overflow-hidden">
<!-- 左侧对话面板 -->
<div class="md:col-span-1 bg-white wild-border border-cyan-500 flex flex-col">
<div class="md:col-span-1 bg-white apple-card flex flex-col">
<!-- 对话历史区 -->
<div id="chat-history" class="flex-1 p-4 overflow-y-auto space-y-3">
@@ -253,40 +260,43 @@
</div>
<!-- 输入区 -->
<div class="p-3 border-t-3 border-gray-300 bg-yellow-50">
<div class="relative flex items-center gap-2">
<div class="p-5 border-t bg-white" style="border-color: rgba(0,0,0,0.06);">
<div class="relative flex items-center gap-3">
<input
id="chat-input"
type="text"
placeholder="输入您的想法按Enter发送..."
class="flex-1 p-2 border-2 border-gray-800 focus:border-cyan-500 focus:outline-none transition-colors font-medium"
class="flex-1 px-5 py-4 border focus:outline-none transition-all font-normal rounded-2xl"
style="border-color: rgba(0,0,0,0.08); color: #464646; background: rgba(248,249,250,0.8);"
onfocus="this.style.borderColor='#4C76AB'; this.style.boxShadow='0 0 0 3px rgba(76,118,171,0.15)'; this.style.background='rgba(255,255,255,0.95)'"
onblur="this.style.borderColor='rgba(0,0,0,0.08)'; this.style.boxShadow='none'; this.style.background='rgba(248,249,250,0.8)'"
/>
<button id="send-button" class="text-cyan-600 hover:text-cyan-700 transition-colors p-2 hover:scale-110 transform duration-200">
<iconify-icon icon="ph:paper-plane-tilt-fill" class="text-3xl"></iconify-icon>
<button id="send-button" class="p-3 rounded-full transition-all hover:scale-105" style="color: white; background: #4C76AB; box-shadow: 0 2px 8px rgba(76,118,171,0.3);" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
<iconify-icon icon="ph:paper-plane-tilt-fill" class="text-xl"></iconify-icon>
</button>
</div>
</div>
</div>
<!-- 右侧显示面板 -->
<div class="md:col-span-2 bg-white wild-border border-purple-600 flex flex-col">
<div id="svg-viewer" class="flex-1 flex items-center justify-center p-4 bg-gradient-to-br from-purple-50 to-pink-50 overflow-auto">
<div id="svg-placeholder" class="text-center text-gray-400">
<iconify-icon icon="ph:image-square" class="text-6xl mx-auto text-purple-400"></iconify-icon>
<div class="md:col-span-2 bg-white apple-card flex flex-col">
<div id="svg-viewer" class="flex-1 flex items-center justify-center p-8 overflow-auto" style="background: linear-gradient(135deg, #fafafa 0%, #f8f9fa 100%);">
<div id="svg-placeholder" class="text-center" style="color: #787878;">
<iconify-icon icon="ph:image-square" class="text-6xl mx-auto" style="color: #5586F5;"></iconify-icon>
<p class="mt-2 font-bold" id="placeholder-text">生成的产品画布将在此处显示</p>
</div>
</div>
<!-- 底部操作栏 -->
<div class="p-3 border-t-3 border-gray-300 flex justify-end items-center gap-2 bg-gray-800">
<button id="download-svg-btn" class="p-2 bg-orange-500 text-white border-2 border-black hover:bg-orange-600 transition-all" title="下载SVG">
<iconify-icon icon="mdi:download-outline" class="text-xl"></iconify-icon>
<div class="p-5 border-t flex justify-end items-center gap-4" style="border-color: rgba(0,0,0,0.06); background: rgba(248,249,250,0.8);">
<button id="download-svg-btn" class="p-3 text-white rounded-xl transition-all" style="background: #5A6270; box-shadow: 0 4px 12px rgba(90,98,112,0.25);" title="下载SVG" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
<iconify-icon icon="mdi:download-outline" class="text-lg"></iconify-icon>
</button>
<button id="export-image-btn" class="p-2 bg-green-500 text-white border-2 border-black hover:bg-green-600 transition-all" title="导出为图片">
<iconify-icon icon="mdi:image-outline" class="text-xl"></iconify-icon>
<button id="export-image-btn" class="p-3 text-white rounded-xl transition-all" style="background: #2B4269; box-shadow: 0 4px 12px rgba(43,66,105,0.25);" title="导出为图片" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
<iconify-icon icon="mdi:image-outline" class="text-lg"></iconify-icon>
</button>
<button id="view-code-btn" class="p-2 bg-blue-500 text-white border-2 border-black hover:bg-blue-600 transition-all" title="查看代码">
<iconify-icon icon="mdi:code-tags" class="text-xl"></iconify-icon>
<button id="view-code-btn" class="p-3 text-white rounded-xl transition-all" style="background: #4C76AB; box-shadow: 0 4px 12px rgba(76,118,171,0.25);" title="查看代码" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
<iconify-icon icon="mdi:code-tags" class="text-lg"></iconify-icon>
</button>
</div>
</div>
@@ -297,13 +307,13 @@
<div id="config-modal" class="modal-overlay">
<div class="modal-content">
<!-- 模态窗头部 -->
<div class="bg-gradient-to-r from-blue-600 to-purple-600 p-4 border-b-4 border-black flex items-center justify-between">
<div class="flex items-center gap-2">
<iconify-icon icon="ph:plugs-connected-fill" class="text-3xl text-white"></iconify-icon>
<h2 class="text-xl font-black text-white">API 配置</h2>
<div class="p-6 border-b flex items-center justify-between" style="background: rgba(255,255,255,0.95); border-color: rgba(0,0,0,0.06); border-radius: 20px 20px 0 0;">
<div class="flex items-center gap-4">
<iconify-icon icon="ph:plugs-connected-fill" class="text-2xl" style="color: #4C76AB;"></iconify-icon>
<h2 class="text-xl font-semibold" style="color: #464646;">API 配置</h2>
</div>
<button id="close-modal-btn" class="text-white hover:bg-white/20 p-2 transition-all">
<iconify-icon icon="ph:x-bold" class="text-2xl"></iconify-icon>
<button id="close-modal-btn" class="hover:bg-gray-100 p-3 rounded-full transition-all" style="color: #666666;" onmouseover="this.style.backgroundColor='#f0f0f0'" onmouseout="this.style.backgroundColor='transparent'">
<iconify-icon icon="ph:x-bold" class="text-xl"></iconify-icon>
</button>
</div>
@@ -311,8 +321,8 @@
<div class="p-6 space-y-4">
<!-- API URL -->
<div>
<label class="block font-bold text-gray-800 mb-2 flex items-center gap-2">
<iconify-icon icon="ph:link-bold" class="text-lg text-blue-600"></iconify-icon>
<label class="block font-semibold mb-3 flex items-center gap-3" style="color: #464646;">
<iconify-icon icon="ph:link-bold" class="text-lg" style="color: #4C76AB;"></iconify-icon>
API URL
</label>
<input
@@ -326,8 +336,8 @@
<!-- API Key -->
<div>
<label class="block font-bold text-gray-800 mb-2 flex items-center gap-2">
<iconify-icon icon="ph:key-bold" class="text-lg text-green-600"></iconify-icon>
<label class="block font-semibold mb-3 flex items-center gap-3" style="color: #464646;">
<iconify-icon icon="ph:key-bold" class="text-lg" style="color: #2B4269;"></iconify-icon>
API Key
</label>
<input
@@ -341,8 +351,8 @@
<!-- Model -->
<div>
<label class="block font-bold text-gray-800 mb-2 flex items-center gap-2">
<iconify-icon icon="ph:robot-bold" class="text-lg text-purple-600"></iconify-icon>
<label class="block font-semibold mb-3 flex items-center gap-3" style="color: #464646;">
<iconify-icon icon="ph:robot-bold" class="text-lg" style="color: #5A6270;"></iconify-icon>
模型 (Model)
</label>
<input
@@ -355,19 +365,19 @@
</div>
<!-- 状态显示 -->
<div id="config-status" class="p-3 border-2 border-gray-300 bg-gray-50 text-sm text-gray-600 hidden">
<div id="config-status" class="p-4 border rounded-2xl bg-gray-50 text-sm hidden" style="border-color: rgba(0,0,0,0.06); color: #888888;">
<iconify-icon icon="ph:info" class="align-middle"></iconify-icon>
<span id="status-text">等待操作...</span>
</div>
</div>
<!-- 模态窗底部按钮 -->
<div class="p-4 border-t-3 border-gray-300 bg-gray-100 flex gap-3 justify-end">
<button id="test-api-btn" class="px-4 py-2 bg-yellow-500 text-white font-bold border-2 border-black hover:bg-yellow-600 transition-all flex items-center gap-2">
<div class="p-6 border-t bg-white flex gap-4 justify-end" style="border-color: rgba(0,0,0,0.06); border-radius: 0 0 20px 20px;">
<button id="test-api-btn" class="px-6 py-3 text-white font-medium rounded-2xl transition-all flex items-center gap-3" style="background: #5A6270; box-shadow: 0 4px 12px rgba(90,98,112,0.25);" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
<iconify-icon icon="ph:flask-bold"></iconify-icon>
测试连接
</button>
<button id="save-config-btn" class="px-4 py-2 bg-green-500 text-white font-bold border-2 border-black hover:bg-green-600 transition-all flex items-center gap-2">
<button id="save-config-btn" class="px-6 py-3 text-white font-medium rounded-2xl transition-all flex items-center gap-3" style="background: #4C76AB; box-shadow: 0 4px 12px rgba(76,118,171,0.25);" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
<iconify-icon icon="ph:floppy-disk-bold"></iconify-icon>
保存配置
</button>
@@ -505,19 +515,22 @@
configStatus.classList.remove('hidden');
statusText.textContent = message;
configStatus.classList.remove('border-gray-300', 'bg-gray-50', 'text-gray-600');
configStatus.classList.remove('border-green-500', 'bg-green-50', 'text-green-700');
configStatus.classList.remove('border-red-500', 'bg-red-50', 'text-red-700');
configStatus.classList.remove('border-blue-500', 'bg-blue-50', 'text-blue-700');
configStatus.style.borderColor = '#787878';
configStatus.style.backgroundColor = '#f9fafb';
configStatus.style.color = '#787878';
if (type === 'success') {
configStatus.classList.add('border-green-500', 'bg-green-50', 'text-green-700');
configStatus.style.borderColor = '#47A74F';
configStatus.style.backgroundColor = '#e8f5e9';
configStatus.style.color = '#2e7d32';
} else if (type === 'error') {
configStatus.classList.add('border-red-500', 'bg-red-50', 'text-red-700');
configStatus.style.borderColor = '#E04639';
configStatus.style.backgroundColor = '#ffebee';
configStatus.style.color = '#c62828';
} else if (type === 'loading') {
configStatus.classList.add('border-blue-500', 'bg-blue-50', 'text-blue-700');
} else {
configStatus.classList.add('border-gray-300', 'bg-gray-50', 'text-gray-600');
configStatus.style.borderColor = '#5586F5';
configStatus.style.backgroundColor = '#e3f2fd';
configStatus.style.color = '#1565c0';
}
}