fix(minimap): 修复页面加载时小地图自动展开的问题
- 添加初始化标志跳过首次 viewport 变化检测,避免页面加载时误触发 - 优化 AIInputBar 提交冷却时间从 3 秒改为 1 秒 - 修复 SW 执行路径提前 return 导致冷却定时器未启动的问题 - 在 postProcessingCompleted 事件中立即重置 isSubmitting 状态 - 注释掉多个调试日志减少控制台噪音 - 更新 CLAUDE.md 添加分页加载与虚拟滚动功能文档 - 更新 CODEBUDDY.md 添加多分支函数共享逻辑的最佳实践 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
2d88b988ec
commit
9cd009dfb9
9 files changed
+251
-55
No files matched your search
@@ -1,10 +1,10 @@
|
||||
# Update CODEBUDDY.md
|
||||
# Update CLAUDE.md
|
||||
|
||||
分析代码审查反馈或开发过程中发现的问题,提取通用规则并更新 CODEBUDDY.md。
|
||||
分析代码审查反馈或开发过程中发现的问题,提取通用规则并更新 CLAUDE.md。
|
||||
|
||||
## 说明
|
||||
|
||||
这个命令实现了 Boris Cherny 第 5 条技巧:Code Review 自动更新规则到 CODEBUDDY.md,让 AI 越来越懂你的项目。形成知识沉淀的飞轮效应。
|
||||
这个命令实现了 Boris Cherny 第 5 条技巧:Code Review 自动更新规则到 CLAUDE.md,让 AI 越来越懂你的项目。形成知识沉淀的飞轮效应。
|
||||
|
||||
## 参数
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
### 4. 定位更新位置
|
||||
|
||||
读取 CODEBUDDY.md,确定规则应该添加到哪个章节:
|
||||
读取 CLAUDE.md,确定规则应该添加到哪个章节:
|
||||
|
||||
- 开发规范相关 → `### Development Rules`
|
||||
- 编码标准相关 → `### Coding Standards`
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
### 6. 更新文件
|
||||
|
||||
使用 Edit 工具更新 CODEBUDDY.md:
|
||||
使用 Edit 工具更新 CLAUDE.md:
|
||||
- 保持现有格式
|
||||
- 在适当位置插入新规则
|
||||
- 如果需要,创建新章节
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
显示更新内容摘要:
|
||||
```markdown
|
||||
## 已更新 CODEBUDDY.md
|
||||
## 已更新 CLAUDE.md
|
||||
|
||||
### 新增规则
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
**项目信息:**
|
||||
- **名称**: Aitu (爱图) - AI 图片与视频创作工具
|
||||
- **版本**: 0.4.0
|
||||
- **版本**: 0.5.0
|
||||
- **许可证**: MIT
|
||||
- **标语**: 爱上图片,爱上创作
|
||||
- **官网**: https://aitu.tu-zi.com
|
||||
@@ -125,7 +125,7 @@ packages/drawnix/
|
||||
│ ├── components/ # UI 组件 (43个子目录)
|
||||
│ ├── services/ # 业务逻辑服务 (32个服务)
|
||||
│ ├── plugins/ # 功能插件 (15个插件)
|
||||
│ ├── hooks/ # React Hooks (24个)
|
||||
│ ├── hooks/ # React Hooks (27个)
|
||||
│ ├── useWorkflowSubmission.ts # 工作流提交(核心)
|
||||
│ ├── useAutoInsertToCanvas.ts # 自动插入画布
|
||||
│ ├── utils/ # 工具函数 (33个模块)
|
||||
@@ -177,6 +177,9 @@ components/
|
||||
├── settings-dialog/ # 设置对话框
|
||||
├── project-drawer/ # 项目抽屉
|
||||
├── task-queue/ # 任务队列 UI
|
||||
│ └── VirtualTaskList.tsx # 虚拟任务列表(支持分页和虚拟滚动)
|
||||
├── lazy-image/ # 懒加载图片组件
|
||||
│ └── LazyImage.tsx # 基于 IntersectionObserver 的图片懒加载
|
||||
├── video-frame-selector/ # 视频帧选择器
|
||||
├── generation-history/ # 生成历史
|
||||
├── minimap/ # 小地图
|
||||
@@ -254,6 +257,9 @@ hooks/
|
||||
├── useMention.ts # @提及功能
|
||||
├── useMediaCache.ts # 媒体缓存
|
||||
├── useViewportScale.ts # 视口缩放
|
||||
├── useInfinitePagination.ts # 无限滚动分页
|
||||
├── useVirtualList.ts # 虚拟列表(封装 @tanstack/react-virtual)
|
||||
├── useImageLazyLoad.ts # 图片懒加载
|
||||
└── ...其他 Hooks
|
||||
```
|
||||
|
||||
@@ -971,6 +977,100 @@ const result = await backupRestoreService.importFromZip(file, onProgress);
|
||||
- 虚拟 URL(`/asset-library/`)从 Cache API 获取
|
||||
- 导入时区分本地素材和 AI 生成素材,存储位置不同
|
||||
|
||||
### 分页加载与虚拟滚动
|
||||
|
||||
支持任务队列和素材库的分页加载与虚拟滚动,优化大数据量场景下的性能。
|
||||
|
||||
**核心文件**:
|
||||
- `hooks/useInfinitePagination.ts` - 无限滚动分页 Hook
|
||||
- `hooks/useVirtualList.ts` - 虚拟列表 Hook(封装 @tanstack/react-virtual)
|
||||
- `hooks/useImageLazyLoad.ts` - 图片懒加载 Hook
|
||||
- `components/lazy-image/LazyImage.tsx` - 懒加载图片组件
|
||||
- `components/task-queue/VirtualTaskList.tsx` - 虚拟任务列表组件
|
||||
- `components/media-library/VirtualAssetGrid.tsx` - 虚拟素材网格组件
|
||||
- `apps/web/src/sw/task-queue/storage.ts` - IndexedDB 游标分页查询
|
||||
|
||||
**功能特点**:
|
||||
- IndexedDB 游标分页:支持大数据量的高效分页查询
|
||||
- 无限滚动:滚动到底部自动加载更多数据
|
||||
- 虚拟滚动:只渲染可见区域的元素,大幅减少 DOM 节点
|
||||
- 图片懒加载:基于 IntersectionObserver,进入视口才加载图片
|
||||
- 实时更新:支持 `prependItems`、`updateItem`、`removeItem` 操作
|
||||
|
||||
**使用示例**:
|
||||
```typescript
|
||||
// 无限滚动分页
|
||||
const {
|
||||
items,
|
||||
isLoading,
|
||||
isLoadingMore,
|
||||
hasMore,
|
||||
loadMore,
|
||||
reset,
|
||||
} = useInfinitePagination({
|
||||
fetcher: async ({ offset, limit }) => {
|
||||
const result = await swTaskQueueClient.requestPaginatedTasks({
|
||||
offset,
|
||||
limit,
|
||||
status: filterStatus,
|
||||
});
|
||||
return {
|
||||
items: result.tasks,
|
||||
total: result.total,
|
||||
hasMore: result.hasMore,
|
||||
};
|
||||
},
|
||||
pageSize: 50,
|
||||
getItemKey: (task) => task.id,
|
||||
deps: [filterStatus],
|
||||
});
|
||||
|
||||
// 虚拟列表
|
||||
const { parentRef, virtualItems, totalSize, getItem } = useVirtualList({
|
||||
items: tasks,
|
||||
estimateSize: 200,
|
||||
overscan: 3,
|
||||
});
|
||||
```
|
||||
|
||||
**IndexedDB 分页查询**:
|
||||
```typescript
|
||||
// Service Worker 中的游标分页实现
|
||||
async getPaginatedTasks(params: PaginationParams): Promise<PaginatedResult> {
|
||||
const { offset = 0, limit = 50, status } = params;
|
||||
const db = await this.getDB();
|
||||
const tx = db.transaction('tasks', 'readonly');
|
||||
const store = tx.objectStore('tasks');
|
||||
const index = store.index('by-createdAt');
|
||||
|
||||
let cursor = await index.openCursor(null, 'prev');
|
||||
let skipped = 0;
|
||||
const items: Task[] = [];
|
||||
|
||||
while (cursor && items.length < limit) {
|
||||
if (status && cursor.value.status !== status) {
|
||||
cursor = await cursor.continue();
|
||||
continue;
|
||||
}
|
||||
if (skipped < offset) {
|
||||
skipped++;
|
||||
cursor = await cursor.continue();
|
||||
continue;
|
||||
}
|
||||
items.push(cursor.value);
|
||||
cursor = await cursor.continue();
|
||||
}
|
||||
|
||||
return { items, total, hasMore: offset + items.length < total };
|
||||
}
|
||||
```
|
||||
|
||||
**性能优化**:
|
||||
- 默认每页 50 条数据
|
||||
- 虚拟列表 overscan 设置为 3-5 个元素
|
||||
- 图片懒加载 rootMargin 设置为 200px(提前加载)
|
||||
- 使用 `getItemKey` 进行去重,避免重复数据
|
||||
|
||||
---
|
||||
|
||||
## 开发规范
|
||||
|
||||
@@ -314,6 +314,68 @@ useEffect(() => {
|
||||
|
||||
**原因**: 事件处理中的清理操作(如删除 UI 元素)通常通过 ref 引用,不依赖于事件携带的数据。如果在检查事件数据有效性时过早 `return`,会导致清理逻辑被跳过,造成 UI 残留(如 WorkZone 面板未关闭)。应该将独立的操作分离,确保关键清理逻辑总能执行。
|
||||
|
||||
#### 多分支函数中提前 return 导致共享逻辑未执行
|
||||
**场景**: 异步函数有多个执行路径(如 SW 执行和主线程 fallback),每个分支都需要执行某些共享逻辑(如重置状态、启动定时器)
|
||||
|
||||
❌ **错误示例**:
|
||||
```typescript
|
||||
const handleGenerate = async () => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const { usedSW } = await submitWorkflowToSW(params);
|
||||
if (usedSW) {
|
||||
// SW 执行成功
|
||||
setPrompt('');
|
||||
return; // ❌ 提前返回,跳过了冷却定时器
|
||||
}
|
||||
} catch (error) {
|
||||
// fallback to main thread
|
||||
}
|
||||
|
||||
// 主线程执行...
|
||||
await executeInMainThread();
|
||||
|
||||
// 启动冷却定时器(只有主线程路径能执行到这里)
|
||||
submitCooldownRef.current = setTimeout(() => {
|
||||
setIsSubmitting(false); // ❌ SW 路径永远不会重置!
|
||||
}, 1000);
|
||||
};
|
||||
```
|
||||
|
||||
✅ **正确示例**:
|
||||
```typescript
|
||||
const handleGenerate = async () => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const { usedSW } = await submitWorkflowToSW(params);
|
||||
if (usedSW) {
|
||||
// SW 执行成功
|
||||
setPrompt('');
|
||||
|
||||
// ✅ 在 return 前执行共享逻辑
|
||||
submitCooldownRef.current = setTimeout(() => {
|
||||
setIsSubmitting(false);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// fallback to main thread
|
||||
}
|
||||
|
||||
// 主线程执行...
|
||||
await executeInMainThread();
|
||||
|
||||
// 主线程路径也执行相同的冷却逻辑
|
||||
submitCooldownRef.current = setTimeout(() => {
|
||||
setIsSubmitting(false);
|
||||
}, 1000);
|
||||
};
|
||||
```
|
||||
|
||||
**原因**: 当函数有多个执行路径时,每个路径都可能需要执行某些共享逻辑(如重置状态)。如果在某个分支中提前 `return` 而忘记执行共享逻辑,会导致状态不一致(如 `isSubmitting` 永远为 `true`,输入框被禁用)。解决方案:1) 在每个 `return` 前确保执行共享逻辑;2) 或将共享逻辑提取到 `finally` 块中。
|
||||
|
||||
#### CSS/SCSS Guidelines
|
||||
- Use BEM naming convention
|
||||
- Prefer design system CSS variables (see Brand Guidelines below)
|
||||
|
||||
@@ -522,6 +522,14 @@ export const AIInputBar: React.FC<AIInputBarProps> = React.memo(({ className, is
|
||||
if (event.type === 'postProcessingCompleted') {
|
||||
const position = event.result.firstElementPosition;
|
||||
|
||||
// 立即重置提交状态,允许用户继续输入
|
||||
// console.log('[AIInputBar] postProcessingCompleted: resetting isSubmitting');
|
||||
if (submitCooldownRef.current) {
|
||||
clearTimeout(submitCooldownRef.current);
|
||||
submitCooldownRef.current = null;
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
|
||||
// 关闭 ChatDrawer(如果是由 AIInputBar 触发的对话)
|
||||
// 注意:这里使用 setTimeout 确保消息更新后再关闭
|
||||
setTimeout(() => {
|
||||
@@ -829,8 +837,12 @@ export const AIInputBar: React.FC<AIInputBarProps> = React.memo(({ className, is
|
||||
// Handle generation
|
||||
const handleGenerate = useCallback(async () => {
|
||||
if (!prompt.trim() && allContent.length === 0) return;
|
||||
if (isSubmitting) return; // 仅防止快速重复点击
|
||||
if (isSubmitting) {
|
||||
// console.log('[AIInputBar] handleGenerate blocked: isSubmitting=true');
|
||||
return; // 仅防止快速重复点击
|
||||
}
|
||||
|
||||
// console.log('[AIInputBar] handleGenerate: setting isSubmitting=true');
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
@@ -1056,12 +1068,22 @@ export const AIInputBar: React.FC<AIInputBarProps> = React.memo(({ className, is
|
||||
const hasSelection = allContent.length > 0;
|
||||
addPromptHistory(prompt.trim(), hasSelection);
|
||||
}
|
||||
// 清空输入并关闭面板
|
||||
// 清空输入,保持面板打开以便用户继续创作
|
||||
setPrompt('');
|
||||
setSelectedContent([]);
|
||||
setUploadedContent([]);
|
||||
setIsFocused(false);
|
||||
inputRef.current?.blur();
|
||||
|
||||
// 启动 1 秒冷却定时器,之后允许用户继续输入
|
||||
if (submitCooldownRef.current) {
|
||||
clearTimeout(submitCooldownRef.current);
|
||||
}
|
||||
console.log('[AIInputBar] SW execution success, starting 1s cooldown timer');
|
||||
submitCooldownRef.current = setTimeout(() => {
|
||||
console.log('[AIInputBar] 1s cooldown expired: setting isSubmitting=false');
|
||||
setIsSubmitting(false);
|
||||
submitCooldownRef.current = null;
|
||||
}, 1000);
|
||||
|
||||
return; // 提前返回
|
||||
}
|
||||
} catch (swError) {
|
||||
@@ -1294,13 +1316,11 @@ export const AIInputBar: React.FC<AIInputBarProps> = React.memo(({ className, is
|
||||
}
|
||||
}
|
||||
|
||||
// 清空输入并关闭面板
|
||||
// 清空输入,保持面板打开以便用户继续创作
|
||||
setPrompt('');
|
||||
setSelectedContent([]);
|
||||
setUploadedContent([]); // 同时清空用户上传内容
|
||||
setIsFocused(false);
|
||||
// 让输入框失去焦点
|
||||
inputRef.current?.blur();
|
||||
// 不关闭面板,让用户可以继续输入
|
||||
} catch (error) {
|
||||
console.error('Failed to create generation task:', error);
|
||||
// 中止工作流
|
||||
@@ -1308,15 +1328,17 @@ export const AIInputBar: React.FC<AIInputBarProps> = React.memo(({ className, is
|
||||
// 出错时立即允许重试
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
// 成功提交后,3秒内不允许重复提交(防止误操作双击)
|
||||
// 成功提交后,1秒内不允许重复提交(防止误操作双击)
|
||||
// 清除之前的定时器
|
||||
if (submitCooldownRef.current) {
|
||||
clearTimeout(submitCooldownRef.current);
|
||||
}
|
||||
console.log('[AIInputBar] Starting 1s cooldown timer');
|
||||
submitCooldownRef.current = setTimeout(() => {
|
||||
console.log('[AIInputBar] 1s cooldown expired: setting isSubmitting=false');
|
||||
setIsSubmitting(false);
|
||||
submitCooldownRef.current = null;
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
}, [prompt, allContent, isSubmitting, selectedModel, workflowControl, submitWorkflowToSW, addPromptHistory, selectedSize]);
|
||||
|
||||
// 处理工作流重试(从指定步骤开始)
|
||||
|
||||
@@ -77,6 +77,8 @@ export const Minimap: React.FC<MinimapProps> = ({
|
||||
offsetX: board.viewport.offsetX,
|
||||
offsetY: board.viewport.offsetY,
|
||||
});
|
||||
// 是否已完成初始化(跳过首次 viewport 变化检测,避免页面加载时误触发)
|
||||
const initializedRef = useRef<boolean>(false);
|
||||
|
||||
/**
|
||||
* 获取所有元素的边界
|
||||
@@ -179,6 +181,13 @@ export const Minimap: React.FC<MinimapProps> = ({
|
||||
offsetX: current.offsetX,
|
||||
offsetY: current.offsetY,
|
||||
};
|
||||
|
||||
// 首次检测到变化时,只更新 ref,不展开小地图(跳过初始化阶段的 viewport 变化)
|
||||
if (!initializedRef.current) {
|
||||
initializedRef.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
lastInteractionRef.current = Date.now();
|
||||
|
||||
// 有交互时自动展开小地图
|
||||
|
||||
@@ -322,7 +322,7 @@ export const WorkZoneTransforms = {
|
||||
Transforms.insertNode(board, workzoneElement, [board.children.length]);
|
||||
});
|
||||
|
||||
console.log('[WorkZone] Inserted (without history):', workzoneElement.id);
|
||||
// console.log('[WorkZone] Inserted (without history):', workzoneElement.id);
|
||||
return workzoneElement;
|
||||
},
|
||||
|
||||
@@ -352,7 +352,7 @@ export const WorkZoneTransforms = {
|
||||
PlaitHistoryBoard.withoutSaving(board, () => {
|
||||
Transforms.removeNode(board, [index]);
|
||||
});
|
||||
console.log('[WorkZone] Removed (without history):', elementId);
|
||||
// console.log('[WorkZone] Removed (without history):', elementId);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ export async function handleSplitAndInsertTask(
|
||||
const insertionPoint = config.insertionPoint || getDefaultInsertionPoint();
|
||||
|
||||
if (result.success) {
|
||||
console.log(`[MediaResultHandler] Split success, calling completePostProcessing for task ${taskId}`);
|
||||
workflowCompletionService.completePostProcessing(
|
||||
taskId,
|
||||
result.count,
|
||||
@@ -128,6 +129,7 @@ export async function handleSplitAndInsertTask(
|
||||
|
||||
const insertResult = await insertImageGroup([url], insertionPoint);
|
||||
if (insertResult.success) {
|
||||
console.log(`[MediaResultHandler] Fallback insert success, calling completePostProcessing for task ${taskId}`);
|
||||
workflowCompletionService.completePostProcessing(taskId, 1, insertionPoint);
|
||||
return { success: true, count: 1 };
|
||||
} else {
|
||||
|
||||
@@ -108,8 +108,8 @@ export class SWCapabilitiesHandler {
|
||||
*/
|
||||
private findWorkZoneForMCP(board: PlaitBoard, mcpTool: string): PlaitWorkZone | null {
|
||||
const allWorkZones = WorkZoneTransforms.getAllWorkZones(board);
|
||||
console.log('[SWCapabilities] findWorkZoneForMCP:', mcpTool, 'total WorkZones:', allWorkZones.length);
|
||||
|
||||
// console.log('[SWCapabilities] findWorkZoneForMCP:', mcpTool, 'total WorkZones:', allWorkZones.length);
|
||||
|
||||
if (allWorkZones.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -118,14 +118,14 @@ export class SWCapabilitiesHandler {
|
||||
for (const workzone of allWorkZones) {
|
||||
const hasMatchingStep = workzone.workflow.steps?.some(step => step.mcp === mcpTool);
|
||||
if (hasMatchingStep) {
|
||||
console.log('[SWCapabilities] Found WorkZone with matching MCP step:', workzone.id);
|
||||
// console.log('[SWCapabilities] Found WorkZone with matching MCP step:', workzone.id);
|
||||
return workzone;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: return the most recent WorkZone
|
||||
const sortedWorkZones = allWorkZones.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
||||
console.log('[SWCapabilities] Using most recent WorkZone:', sortedWorkZones[0]?.id);
|
||||
// console.log('[SWCapabilities] Using most recent WorkZone:', sortedWorkZones[0]?.id);
|
||||
return sortedWorkZones[0] || null;
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ export class SWCapabilitiesHandler {
|
||||
return { success: false, error: '缺少 mermaid 参数', type: 'error' };
|
||||
}
|
||||
|
||||
console.log('[SWCapabilities] handleMermaid called, mermaid length:', mermaid.length);
|
||||
// console.log('[SWCapabilities] handleMermaid called, mermaid length:', mermaid.length);
|
||||
|
||||
try {
|
||||
// Dynamic import mermaid-to-drawnix
|
||||
@@ -217,7 +217,7 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
// Extract mermaid code from markdown code block if present
|
||||
const mermaidCode = this.extractCodeBlock(mermaid, 'mermaid');
|
||||
console.log('[SWCapabilities] Extracted mermaid code length:', mermaidCode.length);
|
||||
// console.log('[SWCapabilities] Extracted mermaid code length:', mermaidCode.length);
|
||||
|
||||
// Parse mermaid to drawnix elements
|
||||
let result;
|
||||
@@ -235,15 +235,15 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
// Find WorkZone associated with this mermaid operation
|
||||
const targetWorkZone = this.findWorkZoneForMCP(board, 'insert_mermaid');
|
||||
console.log('[SWCapabilities] Target WorkZone:', targetWorkZone?.id, 'expectedInsertPosition:', targetWorkZone?.expectedInsertPosition);
|
||||
// console.log('[SWCapabilities] Target WorkZone:', targetWorkZone?.id, 'expectedInsertPosition:', targetWorkZone?.expectedInsertPosition);
|
||||
|
||||
// Get viewport center as insertion point for better centering
|
||||
const viewportCenter = this.getViewportCenter(board);
|
||||
console.log('[SWCapabilities] Viewport center:', viewportCenter);
|
||||
// console.log('[SWCapabilities] Viewport center:', viewportCenter);
|
||||
|
||||
// Insert elements to canvas at viewport center
|
||||
const insertResult = this.insertElementsToCanvasAtPoint(board, elements, viewportCenter);
|
||||
console.log('[SWCapabilities] Insert result:', insertResult);
|
||||
// console.log('[SWCapabilities] Insert result:', insertResult);
|
||||
|
||||
if (insertResult.success) {
|
||||
// Center the inserted elements in viewport after a short delay
|
||||
@@ -253,10 +253,10 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
if (targetWorkZone) {
|
||||
// Remove the WorkZone after successful insertion
|
||||
console.log('[SWCapabilities] Removing WorkZone:', targetWorkZone.id);
|
||||
// console.log('[SWCapabilities] Removing WorkZone:', targetWorkZone.id);
|
||||
setTimeout(() => {
|
||||
WorkZoneTransforms.removeWorkZone(board, targetWorkZone!.id);
|
||||
console.log('[SWCapabilities] WorkZone removed successfully');
|
||||
// console.log('[SWCapabilities] WorkZone removed successfully');
|
||||
|
||||
// Dispatch event to notify AI input bar that generation is complete
|
||||
window.dispatchEvent(new CustomEvent('ai-generation-complete', {
|
||||
@@ -296,7 +296,7 @@ export class SWCapabilitiesHandler {
|
||||
return { success: false, error: '缺少 markdown 参数', type: 'error' };
|
||||
}
|
||||
|
||||
console.log('[SWCapabilities] handleMindmap called, markdown length:', markdown.length);
|
||||
// console.log('[SWCapabilities] handleMindmap called, markdown length:', markdown.length);
|
||||
|
||||
try {
|
||||
// Dynamic import markdown-to-drawnix
|
||||
@@ -304,7 +304,7 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
// Extract markdown code
|
||||
const markdownCode = this.extractCodeBlock(markdown, 'markdown');
|
||||
console.log('[SWCapabilities] Extracted markdown code length:', markdownCode.length);
|
||||
// console.log('[SWCapabilities] Extracted markdown code length:', markdownCode.length);
|
||||
|
||||
// Parse markdown to mindmap element
|
||||
let mindElement;
|
||||
@@ -323,15 +323,15 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
// Find WorkZone associated with this mindmap operation
|
||||
const targetWorkZone = this.findWorkZoneForMCP(board, 'insert_mindmap');
|
||||
console.log('[SWCapabilities] Target WorkZone:', targetWorkZone?.id, 'expectedInsertPosition:', targetWorkZone?.expectedInsertPosition);
|
||||
// console.log('[SWCapabilities] Target WorkZone:', targetWorkZone?.id, 'expectedInsertPosition:', targetWorkZone?.expectedInsertPosition);
|
||||
|
||||
// Get viewport center as insertion point for better centering
|
||||
const viewportCenter = this.getViewportCenter(board);
|
||||
console.log('[SWCapabilities] Viewport center:', viewportCenter);
|
||||
// console.log('[SWCapabilities] Viewport center:', viewportCenter);
|
||||
|
||||
// Insert to canvas at viewport center
|
||||
const insertResult = this.insertElementsToCanvasAtPoint(board, [mindElement], viewportCenter);
|
||||
console.log('[SWCapabilities] Insert result:', insertResult);
|
||||
// console.log('[SWCapabilities] Insert result:', insertResult);
|
||||
|
||||
if (insertResult.success) {
|
||||
// Center the inserted mindmap in viewport after a short delay
|
||||
@@ -341,10 +341,10 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
if (targetWorkZone) {
|
||||
// Remove the WorkZone after successful insertion
|
||||
console.log('[SWCapabilities] Removing WorkZone:', targetWorkZone.id);
|
||||
// console.log('[SWCapabilities] Removing WorkZone:', targetWorkZone.id);
|
||||
setTimeout(() => {
|
||||
WorkZoneTransforms.removeWorkZone(board, targetWorkZone!.id);
|
||||
console.log('[SWCapabilities] WorkZone removed successfully');
|
||||
// console.log('[SWCapabilities] WorkZone removed successfully');
|
||||
|
||||
// Dispatch event to notify AI input bar that generation is complete
|
||||
window.dispatchEvent(new CustomEvent('ai-generation-complete', {
|
||||
@@ -744,7 +744,7 @@ export class SWCapabilitiesHandler {
|
||||
insertPoint: Point
|
||||
): { success: boolean; elementsCount?: number; error?: string } {
|
||||
try {
|
||||
console.log('[SWCapabilities] insertElementsToCanvasAtPoint called, point:', insertPoint, 'elements count:', elements.length);
|
||||
// console.log('[SWCapabilities] insertElementsToCanvasAtPoint called, point:', insertPoint, 'elements count:', elements.length);
|
||||
|
||||
// Use board.insertFragment to correctly insert elements
|
||||
// This handles element positioning and ID generation properly
|
||||
@@ -759,7 +759,7 @@ export class SWCapabilitiesHandler {
|
||||
scrollToPointIfNeeded(board, insertPoint);
|
||||
});
|
||||
|
||||
console.log('[SWCapabilities] Elements inserted successfully at point:', insertPoint);
|
||||
// console.log('[SWCapabilities] Elements inserted successfully at point:', insertPoint);
|
||||
return { success: true, elementsCount: elements.length };
|
||||
} catch (error: any) {
|
||||
console.error('[SWCapabilities] insertElementsToCanvasAtPoint failed:', error);
|
||||
@@ -786,7 +786,7 @@ export class SWCapabilitiesHandler {
|
||||
const centerX = origination[0] + containerRect.width / (2 * zoom);
|
||||
const centerY = origination[1] + containerRect.height / (2 * zoom);
|
||||
|
||||
console.log('[SWCapabilities] getViewportCenter:', { centerX, centerY, zoom, origination });
|
||||
// console.log('[SWCapabilities] getViewportCenter:', { centerX, centerY, zoom, origination });
|
||||
return [centerX, centerY];
|
||||
} catch (error) {
|
||||
console.warn('[SWCapabilities] Error getting viewport center:', error);
|
||||
@@ -811,7 +811,7 @@ export class SWCapabilitiesHandler {
|
||||
|
||||
// Calculate the bounding rectangle of all inserted elements
|
||||
const boundingRect = getRectangleByElements(board, insertedElements as any[], false);
|
||||
console.log('[SWCapabilities] Inserted elements bounding rect:', boundingRect);
|
||||
// console.log('[SWCapabilities] Inserted elements bounding rect:', boundingRect);
|
||||
|
||||
// Get viewport dimensions
|
||||
const boardContainer = PlaitBoardUtils.getBoardContainer(board);
|
||||
@@ -831,14 +831,14 @@ export class SWCapabilitiesHandler {
|
||||
// Don't zoom in too much if elements are small
|
||||
optimalZoom = Math.min(Math.max(optimalZoom, 0.2), 1.5);
|
||||
|
||||
console.log('[SWCapabilities] Calculated optimal zoom:', {
|
||||
boundingRect,
|
||||
availableWidth,
|
||||
availableHeight,
|
||||
zoomX,
|
||||
zoomY,
|
||||
optimalZoom,
|
||||
});
|
||||
// console.log('[SWCapabilities] Calculated optimal zoom:', {
|
||||
// boundingRect,
|
||||
// availableWidth,
|
||||
// availableHeight,
|
||||
// zoomX,
|
||||
// zoomY,
|
||||
// optimalZoom,
|
||||
// });
|
||||
|
||||
// Calculate the center of the inserted elements
|
||||
const elementsCenterX = boundingRect.x + boundingRect.width / 2;
|
||||
@@ -848,11 +848,11 @@ export class SWCapabilitiesHandler {
|
||||
const newOriginationX = elementsCenterX - containerRect.width / (2 * optimalZoom);
|
||||
const newOriginationY = elementsCenterY - containerRect.height / (2 * optimalZoom);
|
||||
|
||||
console.log('[SWCapabilities] Centering and fitting elements in viewport:', {
|
||||
elementsCenter: [elementsCenterX, elementsCenterY],
|
||||
newOrigination: [newOriginationX, newOriginationY],
|
||||
optimalZoom,
|
||||
});
|
||||
// console.log('[SWCapabilities] Centering and fitting elements in viewport:', {
|
||||
// elementsCenter: [elementsCenterX, elementsCenterY],
|
||||
// newOrigination: [newOriginationX, newOriginationY],
|
||||
// optimalZoom,
|
||||
// });
|
||||
|
||||
// Update viewport with optimal zoom and centered position
|
||||
BoardTransforms.updateViewport(board, [newOriginationX, newOriginationY], optimalZoom);
|
||||
|
||||
@@ -163,6 +163,7 @@ class WorkflowCompletionService {
|
||||
|
||||
this.postProcessingResults.set(taskId, result);
|
||||
|
||||
console.log(`[WorkflowCompletion] Emitting postProcessingCompleted event for task ${taskId}`);
|
||||
this.completionEvents$.next({
|
||||
taskId,
|
||||
batchId: this.taskToBatch.get(taskId),
|
||||
|
||||
Reference in new issue
Block a user