Compare commits
5 Commits
6fe5f4175d
...
b0c487a4ef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0c487a4ef | ||
|
|
cbf59e3450 | ||
|
|
06e1d5ca19 | ||
|
|
64e93d25b8 | ||
|
|
bacafd66dc |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AGENTS.md
|
||||||
@@ -219,12 +219,18 @@ body {
|
|||||||
|
|
||||||
/* 气泡操作按钮 */
|
/* 气泡操作按钮 */
|
||||||
.bubble-action-btn {
|
.bubble-action-btn {
|
||||||
opacity: 0;
|
opacity: 1;
|
||||||
transition: opacity 0.2s;
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-bubble-ai:hover .bubble-action-btn {
|
.svg-placeholder-active {
|
||||||
opacity: 1;
|
border-color: #2563eb;
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-content-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
transform-origin: center top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 小手摇摆动画 */
|
/* 小手摇摆动画 */
|
||||||
@@ -349,3 +355,7 @@ body {
|
|||||||
.clear-history-btn:hover iconify-icon {
|
.clear-history-btn:hover iconify-icon {
|
||||||
animation: shake 0.5s ease-in-out;
|
animation: shake 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#send-button.terminate-mode {
|
||||||
|
border-color: #dc2626;
|
||||||
|
}
|
||||||
|
|||||||
12
index.html
12
index.html
@@ -95,9 +95,21 @@
|
|||||||
|
|
||||||
<!-- 底部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
<div class="p-3 border-t-3 border-gray-300 flex justify-end items-center gap-2 bg-gray-800">
|
<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">
|
<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>
|
<iconify-icon icon="mdi:download-outline" class="text-xl"></iconify-icon>
|
||||||
</button>
|
</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="导出为图片">
|
<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>
|
<iconify-icon icon="mdi:image-outline" class="text-xl"></iconify-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class APIClient {
|
|||||||
{ role: 'user', content: userRequest }
|
{ role: 'user', content: userRequest }
|
||||||
];
|
];
|
||||||
|
|
||||||
return await this.sendChatMessage(messages, { maxTokens: 3000 });
|
return await this.sendChatMessage(messages, { maxTokens: 18000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成SWOT分析的专用方法
|
// 生成SWOT分析的专用方法
|
||||||
@@ -144,7 +144,7 @@ class APIClient {
|
|||||||
{ role: 'user', content: userRequest }
|
{ role: 'user', content: userRequest }
|
||||||
];
|
];
|
||||||
|
|
||||||
return await this.sendChatMessage(messages, { maxTokens: 3000 });
|
return await this.sendChatMessage(messages, { maxTokens: 18000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流式生成产品画布
|
// 流式生成产品画布
|
||||||
@@ -155,7 +155,7 @@ class APIClient {
|
|||||||
{ role: 'user', content: userRequest }
|
{ role: 'user', content: userRequest }
|
||||||
];
|
];
|
||||||
|
|
||||||
return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete);
|
return this.sendChatMessageStream(messages, { maxTokens: 13000 }, onChunk, onComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流式生成SWOT分析
|
// 流式生成SWOT分析
|
||||||
@@ -166,7 +166,7 @@ class APIClient {
|
|||||||
{ role: 'user', content: userRequest }
|
{ role: 'user', content: userRequest }
|
||||||
];
|
];
|
||||||
|
|
||||||
return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete);
|
return this.sendChatMessageStream(messages, { maxTokens: 13000 }, onChunk, onComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流式发送聊天请求
|
// 流式发送聊天请求
|
||||||
@@ -190,7 +190,7 @@ class APIClient {
|
|||||||
const url = this.config.url.replace('/chat/completions', '/chat/completions');
|
const url = this.config.url.replace('/chat/completions', '/chat/completions');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Utils.createStreamRequest(
|
return Utils.createStreamRequest(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
133
js/utils.js
133
js/utils.js
@@ -21,18 +21,52 @@ function generateId(prefix = 'id') {
|
|||||||
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析SVG响应,提取SVG内容和前后文本
|
// 解析SVG响应,提取SVG内容和前后文本,容错缺失的结束反引号
|
||||||
function parseSVGResponse(response) {
|
function parseSVGResponse(response = '') {
|
||||||
const svgRegex = /```svg\s*([\s\S]*?)```/i;
|
const content = typeof response === 'string' ? response : String(response || '');
|
||||||
const match = response.match(svgRegex);
|
const svgFenceRegex = /```(?:svg)?\s*([\s\S]*?)```/i;
|
||||||
|
const fenceMatch = content.match(svgFenceRegex);
|
||||||
|
|
||||||
if (match) {
|
if (fenceMatch) {
|
||||||
const svgContent = match[1].trim();
|
const svgBody = fenceMatch[1].trim();
|
||||||
const beforeText = response.substring(0, match.index).trim();
|
const beforeText = content.substring(0, fenceMatch.index).trim();
|
||||||
const afterText = response.substring(match.index + match[0].length).trim();
|
let afterText = content.substring(fenceMatch.index + fenceMatch[0].length).trim();
|
||||||
|
afterText = afterText.replace(/^\s*```/, '').trim();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
svgContent,
|
svgContent: svgBody,
|
||||||
|
beforeText,
|
||||||
|
afterText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容缺失结束反引号的情况
|
||||||
|
const svgStartRegex = /```(?:svg)?\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(/```(?:svg)?\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,
|
beforeText,
|
||||||
afterText
|
afterText
|
||||||
};
|
};
|
||||||
@@ -40,7 +74,7 @@ function parseSVGResponse(response) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
svgContent: null,
|
svgContent: null,
|
||||||
beforeText: response,
|
beforeText: content.trim(),
|
||||||
afterText: ''
|
afterText: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -204,6 +238,15 @@ class StreamProcessor {
|
|||||||
this.onChunk = onChunk;
|
this.onChunk = onChunk;
|
||||||
this.onComplete = onComplete;
|
this.onComplete = onComplete;
|
||||||
this.buffer = '';
|
this.buffer = '';
|
||||||
|
this.completed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
complete(info = {}) {
|
||||||
|
if (this.completed) return;
|
||||||
|
this.completed = true;
|
||||||
|
if (typeof this.onComplete === 'function') {
|
||||||
|
this.onComplete(info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理数据块
|
// 处理数据块
|
||||||
@@ -221,7 +264,7 @@ class StreamProcessor {
|
|||||||
if (line.startsWith('data: ')) {
|
if (line.startsWith('data: ')) {
|
||||||
const data = line.slice(6);
|
const data = line.slice(6);
|
||||||
if (data === '[DONE]') {
|
if (data === '[DONE]') {
|
||||||
this.onComplete();
|
this.complete({ aborted: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,36 +280,56 @@ class StreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建流式请求
|
// 创建流式请求
|
||||||
async function createStreamRequest(url, options, onChunk, onComplete) {
|
function createStreamRequest(url, options, onChunk, onComplete) {
|
||||||
const processor = new StreamProcessor(onChunk, onComplete);
|
const processor = new StreamProcessor(onChunk, onComplete);
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
try {
|
const fetchPromise = (async () => {
|
||||||
const response = await fetch(url, {
|
try {
|
||||||
...options,
|
const response = await fetch(url, {
|
||||||
headers: {
|
...options,
|
||||||
...options.headers,
|
signal: controller.signal,
|
||||||
'Accept': 'text/event-stream',
|
headers: {
|
||||||
'Cache-Control': 'no-cache'
|
...options.headers,
|
||||||
|
'Accept': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
const reader = response.body.getReader();
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
return {
|
||||||
const decoder = new TextDecoder();
|
cancel: () => controller.abort(),
|
||||||
|
finished: fetchPromise
|
||||||
while (true) {
|
};
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
const chunk = decoder.decode(value, { stream: true });
|
|
||||||
processor.processChunk(chunk);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出工具函数
|
// 导出工具函数
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
|
|
||||||
请用中文回复,并在回复中包含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">
|
<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>
|
<defs>
|
||||||
@@ -71,12 +75,6 @@
|
|||||||
<g transform="translate(75, 20)">
|
<g transform="translate(75, 20)">
|
||||||
<text class="title" fill="#f57c00">独特卖点</text>
|
<text class="title" fill="#f57c00">独特卖点</text>
|
||||||
</g>
|
</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="120" class="content-bold">对用户价值:</text>
|
||||||
<text x="10" y="132" class="content">• 扫码即用,操作超简单</text>
|
<text x="10" y="132" class="content">• 扫码即用,操作超简单</text>
|
||||||
<text x="10" y="144" class="content">• 价格透明,立即到账</text>
|
<text x="10" y="144" class="content">• 价格透明,立即到账</text>
|
||||||
|
|||||||
Reference in New Issue
Block a user