+
📊 点击查看 ${this.currentMode === 'canvas' ? '产品画布' : 'SWOT分析'} SVG
${afterHtml}
@@ -889,19 +1058,19 @@ class ProductCanvasApp {
Utils.storage.set('swotHistory', this.conversationHistory.swot || []);
}
+ this.setActivePlaceholder(this.currentSvgId);
Utils.scrollToBottom(this.chatHistory);
}
// 显示SVG
viewSVG(svgId) {
- if (!this.svgStorage[this.currentMode][svgId]) {
+ const store = this.svgStorage[this.currentMode] || {};
+ if (!store[svgId]) {
console.error('SVG not found:', svgId);
return;
}
-
- this.currentSvgId = svgId;
- const svgContent = this.svgStorage[this.currentMode][svgId].content;
- this.svgViewer.innerHTML = svgContent;
+
+ this.renderSvgContent(svgId);
}
// 退回到指定消息
@@ -943,64 +1112,194 @@ class ProductCanvasApp {
// 重新生成消息
async regenerateMessage(messageId) {
if (this.isProcessing) return;
-
+
+ const history = this.conversationHistory[this.currentMode] || [];
+ const targetIndex = history.findIndex(msg => msg.id === messageId && msg.type === 'ai');
+ if (targetIndex === -1) {
+ console.warn('未找到可重新生成的消息');
+ return;
+ }
+
+ let userIndex = -1;
+ for (let i = targetIndex - 1; i >= 0; i--) {
+ if (history[i].type === 'user') {
+ userIndex = i;
+ break;
+ }
+ }
+
+ if (userIndex === -1) {
+ alert('未找到对应的用户消息,无法重新生成');
+ return;
+ }
+
+ const payload = this.buildContextForUserMessage(userIndex);
+ if (!payload) {
+ alert('无法构建对话上下文,请稍后重试');
+ return;
+ }
+
+ this.pendingCancel = false;
this.isProcessing = true;
- this.setSendButtonState('busy');
- this.sendButton.disabled = true;
-
+ this.setSendButtonState('streaming');
+ this.sendButton.disabled = false;
+
try {
- // 重新生成响应
- const response = await window.apiClient.regenerateResponse(messageId, this.conversationHistory[this.currentMode]);
-
- // 退回到指定消息
- this.rollbackToMessage(messageId);
-
- // 添加新的AI回复
- this.addAIMessage(response);
-
+ await this.startStreamingMessage(payload.userMessage, payload.contextMessages);
} catch (error) {
console.error('重新生成失败:', error);
this.addErrorMessage(error.message);
- } finally {
this.isProcessing = false;
this.setSendButtonState('idle');
this.activeStreamHandle = null;
+ this.sendButton.disabled = false;
+ }
+ }
+
+ getActiveSvgRecord(showWarning = true) {
+ const store = this.svgStorage[this.currentMode] || {};
+ if (this.currentSvgId && store[this.currentSvgId]) {
+ return { id: this.currentSvgId, ...store[this.currentSvgId] };
+ }
+
+ if (showWarning) {
+ alert('请先生成并选择一个图表');
+ }
+ return null;
+ }
+
+ parseSvgDimensions(svgContent) {
+ try {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(svgContent, 'image/svg+xml');
+ const svgEl = doc.querySelector('svg');
+ if (!svgEl) {
+ return { width: 1024, height: 768 };
+ }
+
+ const parseLength = (value) => {
+ if (!value) return null;
+ const match = /([0-9.]+)/.exec(value);
+ return match ? parseFloat(match[1]) : null;
+ };
+
+ let width = parseLength(svgEl.getAttribute('width'));
+ let height = parseLength(svgEl.getAttribute('height'));
+ const viewBox = svgEl.getAttribute('viewBox');
+
+ if ((!width || !height) && viewBox) {
+ const parts = viewBox.split(/\s+/).map(Number).filter(n => !Number.isNaN(n));
+ if (parts.length === 4) {
+ width = width || parts[2];
+ height = height || parts[3];
+ }
+ }
+
+ return {
+ width: width || 1024,
+ height: height || 768
+ };
+ } catch (error) {
+ console.warn('解析SVG尺寸失败:', error);
+ return { width: 1024, height: 768 };
+ }
+ }
+
+ loadImageFromUrl(url) {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = () => resolve(img);
+ img.onerror = (err) => reject(err);
+ img.src = url;
+ });
+ }
+
+ async convertSvgToPngBlob(svgContent) {
+ const { width, height } = this.parseSvgDimensions(svgContent);
+ const svgBlob = new Blob([svgContent], { type: 'image/svg+xml' });
+ const url = URL.createObjectURL(svgBlob);
+
+ try {
+ const img = await this.loadImageFromUrl(url);
+ const canvas = document.createElement('canvas');
+ const canvasWidth = Math.max(1, img.naturalWidth || width || 1024);
+ const canvasHeight = Math.max(1, img.naturalHeight || height || 768);
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+
+ const ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+ ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
+
+ return await new Promise((resolve, reject) => {
+ canvas.toBlob((blob) => {
+ if (blob) {
+ resolve(blob);
+ } else {
+ reject(new Error('无法生成PNG图像'));
+ }
+ }, 'image/png');
+ });
+ } finally {
+ URL.revokeObjectURL(url);
+ }
+ }
+
+ async copySvgToClipboard() {
+ const record = this.getActiveSvgRecord();
+ if (!record) return;
+
+ if (!navigator.clipboard || typeof ClipboardItem === 'undefined') {
+ alert('当前浏览器不支持复制图片到剪贴板,请使用下载功能。');
+ return;
+ }
+
+ try {
+ const blob = await this.convertSvgToPngBlob(record.content);
+ await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
+ alert('图像已复制到剪贴板');
+ } catch (error) {
+ console.error('复制图片失败:', error);
+ alert('复制失败,请稍后重试。');
}
}
// 下载SVG
downloadSVG() {
- if (!this.currentSvgId) {
- alert('请先生成SVG图表');
- return;
- }
-
- const svgContent = this.svgStorage[this.currentMode][this.currentSvgId].content;
+ const record = this.getActiveSvgRecord();
+ if (!record) return;
+
const filename = `${this.currentMode}-${Utils.formatDateTime().replace(/[/:]/g, '-')}.svg`;
- Utils.downloadFile(svgContent, filename, 'image/svg+xml');
+ Utils.downloadFile(record.content, filename, 'image/svg+xml');
}
// 导出为图片
- exportAsImage() {
- if (!this.currentSvgId) {
- alert('请先生成SVG图表');
- return;
+ async exportAsImage() {
+ const record = this.getActiveSvgRecord();
+ if (!record) return;
+
+ try {
+ const blob = await this.convertSvgToPngBlob(record.content);
+ const filename = `${this.currentMode}-${Utils.formatDateTime().replace(/[/:]/g, '-')}.png`;
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ link.click();
+ URL.revokeObjectURL(url);
+ } catch (error) {
+ console.error('导出图片失败:', error);
+ alert('导出图片失败,请稍后重试。');
}
-
- // 这里可以实现SVG转PNG的功能
- // 由于需要额外的库,这里先提示用户
- alert('SVG转PNG功能需要额外的库支持,您可以使用下载SVG功能,然后使用在线工具转换。');
}
// 查看SVG代码
viewSVGCode() {
- if (!this.currentSvgId) {
- alert('请先生成SVG图表');
- return;
- }
-
- const svgContent = this.svgStorage[this.currentMode][this.currentSvgId].content;
-
+ const record = this.getActiveSvgRecord();
+ if (!record) return;
+
+ const svgContent = record.content;
+
// 创建代码查看模态窗
const modal = document.createElement('div');
modal.className = 'modal-overlay active';