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 {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.chat-bubble-ai:hover .bubble-action-btn {
|
||||
opacity: 1;
|
||||
.svg-placeholder-active {
|
||||
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 {
|
||||
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">
|
||||
<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>
|
||||
|
||||
@@ -133,7 +133,7 @@ class APIClient {
|
||||
{ role: 'user', content: userRequest }
|
||||
];
|
||||
|
||||
return await this.sendChatMessage(messages, { maxTokens: 3000 });
|
||||
return await this.sendChatMessage(messages, { maxTokens: 18000 });
|
||||
}
|
||||
|
||||
// 生成SWOT分析的专用方法
|
||||
@@ -144,7 +144,7 @@ class APIClient {
|
||||
{ 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 }
|
||||
];
|
||||
|
||||
return await this.sendChatMessageStream(messages, { maxTokens: 3000 }, onChunk, onComplete);
|
||||
return this.sendChatMessageStream(messages, { maxTokens: 13000 }, onChunk, onComplete);
|
||||
}
|
||||
|
||||
// 流式生成SWOT分析
|
||||
@@ -166,7 +166,7 @@ class APIClient {
|
||||
{ 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');
|
||||
|
||||
try {
|
||||
await Utils.createStreamRequest(
|
||||
return Utils.createStreamRequest(
|
||||
url,
|
||||
{
|
||||
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)}`;
|
||||
}
|
||||
|
||||
// 解析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 || '');
|
||||
const svgFenceRegex = /```(?:svg)?\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
|
||||
};
|
||||
}
|
||||
|
||||
// 兼容缺失结束反引号的情况
|
||||
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,
|
||||
afterText
|
||||
};
|
||||
@@ -40,7 +74,7 @@ function parseSVGResponse(response) {
|
||||
|
||||
return {
|
||||
svgContent: null,
|
||||
beforeText: response,
|
||||
beforeText: content.trim(),
|
||||
afterText: ''
|
||||
};
|
||||
}
|
||||
@@ -204,6 +238,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 +264,7 @@ class StreamProcessor {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
this.onComplete();
|
||||
this.complete({ aborted: false });
|
||||
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 controller = new AbortController();
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Accept': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache'
|
||||
const fetchPromise = (async () => {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Accept': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
const reader = response.body.getReader();
|
||||
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();
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
cancel: () => controller.abort(),
|
||||
finished: fetchPromise
|
||||
};
|
||||
}
|
||||
|
||||
// 导出工具函数
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
|
||||
请用中文回复,并在回复中包含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>
|
||||
@@ -71,12 +75,6 @@
|
||||
<g transform="translate(75, 20)">
|
||||
<text class="title" fill="#f57c00">独特卖点</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>
|
||||
|
||||
Reference in New Issue
Block a user