package app import ( "context" "fmt" "log" "sort" "strings" "time" "infinite-canvas/backend/internal/model" ) // generateIntelligence 生成智能洞察 func (ci *CanvasIntelligence) generateIntelligence(nodes []model.CanvasNode, snapshot CanvasSnapshot) CanvasIntelligenceInsights { return CanvasIntelligenceInsights{ Summary: ci.generateSummary(nodes, snapshot), Suggestions: ci.generateSuggestions(nodes, snapshot), Patterns: ci.recognizePatterns(nodes, snapshot), Issues: ci.diagnoseIssues(nodes, snapshot), Trends: ci.analyzeTrends(nodes), } } // generateSummary 生成画布总结 func (ci *CanvasIntelligence) generateSummary(nodes []model.CanvasNode, snapshot CanvasSnapshot) CanvasSummary { // 提取主要主题 mainTopics := ci.extractMainTopics(nodes) // 评估进度 progress := ci.assessProgress(nodes) // 计算完整性 completeness := ci.calculateCompleteness(nodes, snapshot) // 生成一句话总结 oneSentence := ci.generateOneSentenceSummary(nodes, snapshot, mainTopics) return CanvasSummary{ OneSentence: oneSentence, MainTopics: mainTopics, Progress: progress, Completeness: completeness, } } // extractMainTopics 提取主要主题 func (ci *CanvasIntelligence) extractMainTopics(nodes []model.CanvasNode) []string { // 收集所有关键词 allKeywords := make(map[string]int) for _, node := range nodes { keywords := ci.extractKeywords(node.Content) for _, kw := range keywords { allKeywords[kw]++ } } // 排序取前3个 type kv struct { Key string Value int } var sorted []kv for k, v := range allKeywords { sorted = append(sorted, kv{k, v}) } sort.Slice(sorted, func(i, j int) bool { return sorted[i].Value > sorted[j].Value }) topics := make([]string, 0, 3) for i := 0; i < len(sorted) && i < 3; i++ { topics = append(topics, sorted[i].Key) } return topics } // assessProgress 评估进度 func (ci *CanvasIntelligence) assessProgress(nodes []model.CanvasNode) string { if len(nodes) < 5 { return "early" } // 统计完成状态 completed := 0 total := 0 for _, node := range nodes { category := ci.categorizeNode(node) if category == "task" { total++ sentiment := ci.analyzeSentiment(node.Content) if sentiment == "positive" { completed++ } } } if total == 0 { // 没有任务节点,根据节点数量判断 if len(nodes) < 10 { return "early" } else if len(nodes) < 30 { return "mid" } else { return "mature" } } completionRate := float64(completed) / float64(total) if completionRate < 0.3 { return "early" } else if completionRate < 0.7 { return "mid" } else { return "mature" } } // calculateCompleteness 计算完整性 func (ci *CanvasIntelligence) calculateCompleteness(nodes []model.CanvasNode, snapshot CanvasSnapshot) float64 { score := 0.0 // 1. 节点数量(20%) if len(nodes) > 0 { score += 0.2 } // 2. 结构完整性(20%) if len(snapshot.Hierarchy.RootNodes) > 0 { score += 0.2 } // 3. 内容质量(30%) contentScore := 0.0 for _, node := range nodes { if len(node.Content) > 50 { contentScore += 1.0 } } if len(nodes) > 0 { contentScore /= float64(len(nodes)) score += contentScore * 0.3 } // 4. 关系完整性(30%) if len(snapshot.Relationships) > 0 { relationshipRatio := float64(len(snapshot.Relationships)) / float64(len(nodes)) if relationshipRatio > 1 { relationshipRatio = 1 } score += relationshipRatio * 0.3 } return score } // generateOneSentenceSummary 生成一句话总结 func (ci *CanvasIntelligence) generateOneSentenceSummary(nodes []model.CanvasNode, snapshot CanvasSnapshot, mainTopics []string) string { nodeCount := len(nodes) progress := ci.assessProgress(nodes) topicsStr := "various topics" if len(mainTopics) > 0 { topicsStr = strings.Join(mainTopics, ", ") } progressMap := map[string]string{ "early": "刚开始", "mid": "进行中", "mature": "接近完成", } return fmt.Sprintf("包含 %d 个节点的画布,主题:%s,状态:%s", nodeCount, topicsStr, progressMap[progress]) } // generateSuggestions 生成行动建议 func (ci *CanvasIntelligence) generateSuggestions(nodes []model.CanvasNode, snapshot CanvasSnapshot) []ActionSuggestion { suggestions := make([]ActionSuggestion, 0) // 1. 孤立节点建议 orphans := ci.findOrphanNodes(nodes, snapshot) if len(orphans) > 0 { suggestions = append(suggestions, ActionSuggestion{ Priority: 3, Action: "连接孤立节点", Reason: fmt.Sprintf("发现 %d 个孤立节点,建立关联可以增强结构", len(orphans)), Impact: "提升画布结构完整性", }) } // 2. 布局优化建议 if snapshot.Layout.Density > 0.8 { suggestions = append(suggestions, ActionSuggestion{ Priority: 2, Action: "优化布局密度", Reason: "当前布局密度过高,可能影响可读性", Impact: "改善视觉体验和导航效率", }) } // 3. 内容完善建议 emptyNodes := ci.findEmptyOrShortNodes(nodes) if len(emptyNodes) > 0 { suggestions = append(suggestions, ActionSuggestion{ Priority: 4, Action: "完善节点内容", Reason: fmt.Sprintf("发现 %d 个内容较少的节点", len(emptyNodes)), Impact: "提高画布信息密度", }) } // 4. 分类整理建议 if len(nodes) > 20 && len(snapshot.Layout.Clusters) < 3 { suggestions = append(suggestions, ActionSuggestion{ Priority: 3, Action: "添加分类节点", Reason: "节点数量较多但缺少明确分类", Impact: "改善信息架构和可维护性", }) } // 5. 任务跟进建议 incompleteTasks := ci.findIncompleteTasks(nodes) if len(incompleteTasks) > 0 { suggestions = append(suggestions, ActionSuggestion{ Priority: 5, Action: "跟进待办任务", Reason: fmt.Sprintf("有 %d 个待办任务需要处理", len(incompleteTasks)), Impact: "推进工作进度", }) } // 按优先级排序 sort.Slice(suggestions, func(i, j int) bool { return suggestions[i].Priority > suggestions[j].Priority }) return suggestions } // recognizePatterns 识别模式 func (ci *CanvasIntelligence) recognizePatterns(nodes []model.CanvasNode, snapshot CanvasSnapshot) []PatternRecognition { patterns := make([]PatternRecognition, 0) // 1. 思维导图模式 if ci.isMindMapPattern(snapshot) { patterns = append(patterns, PatternRecognition{ Pattern: "mindmap", Confidence: 0.85, Evidence: snapshot.Hierarchy.RootNodes, Description: "检测到思维导图结构:以中心主题向外扩展", }) } // 2. 看板模式 if ci.isKanbanPattern(snapshot) { patterns = append(patterns, PatternRecognition{ Pattern: "kanban", Confidence: 0.75, Evidence: []string{}, Description: "检测到看板模式:按阶段组织的任务流", }) } // 3. 时间线模式 if ci.isTimelinePattern(nodes, snapshot) { patterns = append(patterns, PatternRecognition{ Pattern: "timeline", Confidence: 0.7, Evidence: []string{}, Description: "检测到时间线模式:按时间顺序排列的事件", }) } // 4. 层级结构模式 if snapshot.Hierarchy.MaxDepth >= 3 { patterns = append(patterns, PatternRecognition{ Pattern: "hierarchy", Confidence: 0.8, Evidence: snapshot.Hierarchy.RootNodes, Description: fmt.Sprintf("检测到层级结构:最大深度 %d 层", snapshot.Hierarchy.MaxDepth), }) } // 5. 协作模式 if ci.isCollaborativePattern(nodes) { patterns = append(patterns, PatternRecognition{ Pattern: "collaborative", Confidence: 0.65, Evidence: []string{}, Description: "检测到协作模式:多人参与编辑", }) } return patterns } // diagnoseIssues 诊断问题 func (ci *CanvasIntelligence) diagnoseIssues(nodes []model.CanvasNode, snapshot CanvasSnapshot) []CanvasIssue { issues := make([]CanvasIssue, 0) // 1. 孤立节点 orphans := ci.findOrphanNodes(nodes, snapshot) if len(orphans) > 0 { issues = append(issues, CanvasIssue{ Severity: "medium", Type: "orphan", Description: fmt.Sprintf("发现 %d 个孤立节点,没有与其他节点建立关系", len(orphans)), AffectedNodes: orphans, FixSuggestion: "使用 canvas_relationship_create 工具建立关联", }) } // 2. 重复内容 duplicates := ci.findDuplicateNodes(nodes) if len(duplicates) > 0 { issues = append(issues, CanvasIssue{ Severity: "low", Type: "duplicate", Description: fmt.Sprintf("发现 %d 组可能重复的节点", len(duplicates)), AffectedNodes: ci.flattenDuplicates(duplicates), FixSuggestion: "合并或删除重复节点", }) } // 3. 空节点 emptyNodes := ci.findEmptyOrShortNodes(nodes) if len(emptyNodes) > 3 { issues = append(issues, CanvasIssue{ Severity: "low", Type: "incomplete", Description: fmt.Sprintf("发现 %d 个内容较少的节点", len(emptyNodes)), AffectedNodes: emptyNodes, FixSuggestion: "补充节点内容或删除无用节点", }) } // 4. 布局问题 if snapshot.Layout.Density > 0.9 { issues = append(issues, CanvasIssue{ Severity: "medium", Type: "layout", Description: "节点密度过高,可能影响可读性", AffectedNodes: []string{}, FixSuggestion: "使用 canvas_layout 工具重新布局", }) } // 5. 深度过深 if snapshot.Hierarchy.MaxDepth > 5 { issues = append(issues, CanvasIssue{ Severity: "low", Type: "hierarchy", Description: fmt.Sprintf("层级深度 %d 过深,可能难以导航", snapshot.Hierarchy.MaxDepth), AffectedNodes: []string{}, FixSuggestion: "考虑扁平化结构或添加中间层级", }) } return issues } // analyzeTrends 分析趋势 func (ci *CanvasIntelligence) analyzeTrends(nodes []model.CanvasNode) TrendAnalysis { // 计算最近活动 recentActivity := ci.calculateRecentActivity(nodes) // 计算增长率(简化实现:基于节点创建时间) growthRate := ci.calculateGrowthRate(nodes) // 识别热门区域 hotAreas := ci.identifyHotAreas(nodes) // 协作地图 collaborationMap := ci.buildCollaborationMap(nodes) return TrendAnalysis{ RecentActivity: recentActivity, GrowthRate: growthRate, HotAreas: hotAreas, CollaborationMap: collaborationMap, } } // 辅助方法 func (ci *CanvasIntelligence) findOrphanNodes(nodes []model.CanvasNode, snapshot CanvasSnapshot) []string { connected := make(map[string]bool) // 标记有关系的节点 for _, rel := range snapshot.Relationships { connected[rel.From] = true connected[rel.To] = true } // 找出孤立节点 orphans := make([]string, 0) for _, node := range nodes { if !connected[node.ID] && node.ParentID == nil && len(node.Children) == 0 { orphans = append(orphans, node.ID) } } return orphans } func (ci *CanvasIntelligence) findDuplicateNodes(nodes []model.CanvasNode) [][]string { // 简化实现:基于内容哈希 contentMap := make(map[string][]string) for _, node := range nodes { if len(node.Content) < 10 { continue } hash := hashContentSHA256(node.Content) contentMap[hash] = append(contentMap[hash], node.ID) } duplicates := make([][]string, 0) for _, ids := range contentMap { if len(ids) > 1 { duplicates = append(duplicates, ids) } } return duplicates } func (ci *CanvasIntelligence) flattenDuplicates(duplicates [][]string) []string { result := make([]string, 0) for _, group := range duplicates { result = append(result, group...) } return result } func (ci *CanvasIntelligence) findEmptyOrShortNodes(nodes []model.CanvasNode) []string { result := make([]string, 0) for _, node := range nodes { if len(strings.TrimSpace(node.Content)) < 10 { result = append(result, node.ID) } } return result } func (ci *CanvasIntelligence) findIncompleteTasks(nodes []model.CanvasNode) []string { result := make([]string, 0) for _, node := range nodes { category := ci.categorizeNode(node) sentiment := ci.analyzeSentiment(node.Content) if category == "task" && sentiment == "todo" { result = append(result, node.ID) } } return result } func (ci *CanvasIntelligence) isMindMapPattern(snapshot CanvasSnapshot) bool { // 思维导图特征:单一根节点,多层级 return len(snapshot.Hierarchy.RootNodes) == 1 && snapshot.Hierarchy.MaxDepth >= 2 } func (ci *CanvasIntelligence) isKanbanPattern(snapshot CanvasSnapshot) bool { // 看板特征:多个根节点(列),每个根节点下有多个子节点(卡片) if len(snapshot.Hierarchy.RootNodes) < 2 { return false } // 检查每个根节点是否有子节点 for _, rootID := range snapshot.Hierarchy.RootNodes { if treeNode, ok := snapshot.Hierarchy.Tree[rootID]; ok { if len(treeNode.Children) == 0 { return false } } } return true } func (ci *CanvasIntelligence) isTimelinePattern(nodes []model.CanvasNode, snapshot CanvasSnapshot) bool { // 时间线特征:节点按时间顺序排列(X轴) if len(nodes) < 3 { return false } // 检查节点是否按X坐标排序 positions := make([]float64, 0) timestamps := make([]int64, 0) for _, node := range nodes { if node.Position != nil { positions = append(positions, node.Position.X) timestamps = append(timestamps, node.CreatedAt.Unix()) } } if len(positions) < 3 { return false } // 检查位置和时间是否相关 posIncreasing := true timeIncreasing := true for i := 1; i < len(positions); i++ { if positions[i] < positions[i-1] { posIncreasing = false } if timestamps[i] < timestamps[i-1] { timeIncreasing = false } } return posIncreasing && timeIncreasing } func (ci *CanvasIntelligence) isCollaborativePattern(nodes []model.CanvasNode) bool { // 协作特征:多个不同的更新者 updaters := make(map[string]bool) for _, node := range nodes { if node.Metadata != nil { if updatedBy, ok := node.Metadata["updatedBy"].(string); ok && updatedBy != "" { updaters[updatedBy] = true } } } return len(updaters) >= 2 } func (ci *CanvasIntelligence) calculateRecentActivity(nodes []model.CanvasNode) ActivityLevel { now := time.Now() recentUpdates := 0 for _, node := range nodes { if now.Sub(node.UpdatedAt) < 1*time.Hour { recentUpdates++ } } level := "low" if recentUpdates > 10 { level = "high" } else if recentUpdates > 3 { level = "medium" } lastUpdated := int64(0) for _, node := range nodes { if node.UpdatedAt.Unix() > lastUpdated { lastUpdated = node.UpdatedAt.Unix() } } return ActivityLevel{ Level: level, LastUpdated: lastUpdated, UpdateCount: recentUpdates, } } func (ci *CanvasIntelligence) calculateGrowthRate(nodes []model.CanvasNode) float64 { if len(nodes) == 0 { return 0 } // 简化实现:计算最近1天的增长 now := time.Now() recentNodes := 0 for _, node := range nodes { if now.Sub(node.CreatedAt) < 24*time.Hour { recentNodes++ } } return float64(recentNodes) / float64(len(nodes)) } func (ci *CanvasIntelligence) identifyHotAreas(nodes []model.CanvasNode) []string { // 简化实现:找出最近更新最频繁的区域(聚类) recentNodes := make([]model.CanvasNode, 0) now := time.Now() for _, node := range nodes { if node.Position != nil && now.Sub(node.UpdatedAt) < 1*time.Hour { recentNodes = append(recentNodes, node) } } if len(recentNodes) == 0 { return []string{} } for index := range recentNodes { if recentNodes[index].Position == nil { recentNodes[index].Position = &model.CanvasPosition{} } } // 使用聚类找出热门区域 clusterCount := 3 if len(recentNodes) < clusterCount { clusterCount = len(recentNodes) } clusters := ci.simpleKMeans(recentNodes, clusterCount) hotAreas := make([]string, 0) for _, cluster := range clusters { if len(cluster.NodeIDs) > 0 { hotAreas = append(hotAreas, cluster.Label) } } return hotAreas } func (ci *CanvasIntelligence) buildCollaborationMap(nodes []model.CanvasNode) map[string]int { collaborationMap := make(map[string]int) for _, node := range nodes { if node.Metadata != nil { if updatedBy, ok := node.Metadata["updatedBy"].(string); ok && updatedBy != "" { collaborationMap[updatedBy]++ } } } return collaborationMap } // buildCapabilityMatrix 构建能力矩阵 func (ci *CanvasIntelligence) buildCapabilityMatrix(userID, canvasID string) CanvasCapabilityMatrix { return CanvasCapabilityMatrix{ SupportedNodeTypes: ci.getSupportedNodeTypes(), Permissions: ci.getCanvasPermissions(userID, canvasID), Limits: ci.getCanvasLimits(), Features: ci.getFeatures(), Extensions: ci.getExtensions(), } } func (ci *CanvasIntelligence) getSupportedNodeTypes() []NodeTypeCapability { return []NodeTypeCapability{ {Type: "text", DisplayName: "文本", Description: "纯文本内容", Creatable: true, Operations: []string{"read", "update", "delete", "move", "duplicate"}}, {Type: "markdown", DisplayName: "Markdown", Description: "Markdown格式文本", Creatable: true, Operations: []string{"read", "update", "delete", "move", "duplicate"}}, {Type: "code", DisplayName: "代码", Description: "代码片段", Creatable: true, Operations: []string{"read", "update", "delete", "move", "duplicate"}}, {Type: "image", DisplayName: "图片", Description: "图片节点", Creatable: true, Operations: []string{"read", "update", "delete", "move", "duplicate"}}, {Type: "link", DisplayName: "链接", Description: "外部链接", Creatable: true, Operations: []string{"read", "update", "delete", "move", "duplicate"}}, {Type: "diagram", DisplayName: "图表", Description: "图表节点", Creatable: true, Operations: []string{"read", "update", "delete", "move"}}, {Type: "mindmap", DisplayName: "思维导图", Description: "思维导图节点", Creatable: true, Operations: []string{"read", "update", "delete", "move"}}, {Type: "task", DisplayName: "任务", Description: "待办任务", Creatable: true, Operations: []string{"read", "update", "delete", "move", "duplicate"}}, } } func (ci *CanvasIntelligence) getCanvasPermissions(userID, canvasID string) CanvasPermissions { // CanvasProjectForUser uses the persisted canvas store and enforces the same // owner scope as every other Agent canvas operation. if _, err := ci.service.repo.CanvasProjectForUser(userID, canvasID); err != nil { log.Printf("[Agent] failed to get canvas permissions for %s: %v", canvasID, err) return CanvasPermissions{ CanRead: true, CanCreate: false, CanUpdate: false, CanDelete: false, CanMove: false, CanShare: false, RequireApproval: []string{"canvas_node_create", "canvas_node_update", "canvas_node_delete", "canvas_bulk_operation"}, } } return CanvasPermissions{CanRead: true, CanCreate: true, CanUpdate: true, CanDelete: true, CanMove: true, CanShare: true, RequireApproval: []string{}} } func (ci *CanvasIntelligence) getCanvasLimits() CanvasLimits { return CanvasLimits{ MaxNodes: 10000, MaxDepth: 10, MaxContentSize: 1024 * 1024, // 1MB MaxBatchSize: 50, } } func (ci *CanvasIntelligence) getFeatures() map[string]bool { return map[string]bool{ "real_time_collaboration": true, "version_history": true, "ai_suggestions": true, "auto_layout": true, "search": true, "export": true, "templates": true, } } func (ci *CanvasIntelligence) getExtensions() []ExtensionPoint { return []ExtensionPoint{ {Name: "custom_node_types", Type: "plugin", Enabled: true, Description: "支持自定义节点类型"}, {Name: "webhooks", Type: "hook", Enabled: true, Description: "画布事件webhook"}, {Name: "integrations", Type: "plugin", Enabled: true, Description: "第三方集成"}, } } // getRealtimeState 获取实时状态 func (ci *CanvasIntelligence) getRealtimeState(ctx context.Context, document map[string]any) CanvasRealtimeState { // Realtime presence is optional. Keep the Agent request scoped to the saved // document instead of querying the unpersisted Canvas extension model. state := CanvasRealtimeState{ OnlineUsers: []string{}, ActiveCursors: make(map[string]Position), LockedNodes: []string{}, PendingOperations: 0, SyncStatus: "synced", } metadata, ok := document["metadata"].(map[string]any) if !ok { return state } if onlineUsers, ok := metadata["onlineUsers"].([]any); ok { for _, user := range onlineUsers { if userStr, ok := user.(string); ok { state.OnlineUsers = append(state.OnlineUsers, userStr) } } } if cursors, ok := metadata["activeCursors"].(map[string]any); ok { for userID, cursorData := range cursors { if cursorMap, ok := cursorData.(map[string]any); ok { state.ActiveCursors[userID] = Position{X: numberValue(cursorMap["x"], 0), Y: numberValue(cursorMap["y"], 0)} } } } if lockedNodes, ok := metadata["lockedNodes"].([]any); ok { for _, node := range lockedNodes { if nodeStr, ok := node.(string); ok { state.LockedNodes = append(state.LockedNodes, nodeStr) } } } if syncStatus, ok := metadata["syncStatus"].(string); ok { state.SyncStatus = syncStatus } return state }