Files
ttstd-infinite-canvas/backend/internal/app/cloud_agent_pi_canvas_intelligence.go

522 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"context"
"fmt"
"strings"
"time"
"infinite-canvas/backend/internal/model"
)
// Canvas Intelligence Layer - 让 Agent 深度理解画布
// 这是重构的核心:从"工具调用"升级到"画布认知"
// CanvasIntelligence 画布智能层
type CanvasIntelligence struct {
service *Service
}
// NewCanvasIntelligence 创建画布智能层
func (s *Service) NewCanvasIntelligence() *CanvasIntelligence {
return &CanvasIntelligence{service: s}
}
// EnhancedCanvasContext 增强的画布上下文(传递给Pi)
type EnhancedCanvasContext struct {
// 基础信息
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
// 结构化快照
Snapshot CanvasSnapshot `json:"snapshot"`
// 智能洞察
Intelligence CanvasIntelligenceInsights `json:"intelligence"`
// 能力矩阵
Capabilities CanvasCapabilityMatrix `json:"capabilities"`
// 实时状态
RealtimeState CanvasRealtimeState `json:"realtimeState"`
}
// CanvasSnapshot 画布快照(结构化)
type CanvasSnapshot struct {
Timestamp int64 `json:"timestamp"`
TotalNodes int `json:"totalNodes"`
FocusNodes []EnhancedNodeView `json:"focusNodes"`
ContextNodes []EnhancedNodeView `json:"contextNodes"` // 焦点节点周边相关节点
Relationships []RelationshipView `json:"relationships"`
Layout LayoutAnalysis `json:"layout"`
Hierarchy HierarchyView `json:"hierarchy"`
}
// EnhancedNodeView 增强的节点视图(更智能的表示)
type EnhancedNodeView struct {
// 标识
ID string `json:"id"`
Type string `json:"type"`
// 内容摘要(智能压缩)
ContentPreview string `json:"contentPreview"` // 前200字符
ContentHash string `json:"contentHash"`
ContentLength int `json:"contentLength"`
HasFullContent bool `json:"hasFullContent"` // 是否在焦点中(有完整内容)
// 完整内容(仅焦点节点)
FullContent string `json:"fullContent,omitempty"`
// 空间信息
Position *Position `json:"position,omitempty"`
Size *Size `json:"size,omitempty"`
ZIndex int `json:"zIndex"`
// 关系
ParentID string `json:"parentId,omitempty"`
Children []string `json:"children,omitempty"`
Siblings []string `json:"siblings,omitempty"` // 同级节点
// 语义标签
Tags []string `json:"tags,omitempty"`
Category string `json:"category,omitempty"` // 自动分类
Sentiment string `json:"sentiment,omitempty"` // 情感:positive/neutral/negative/todo
Keywords []string `json:"keywords,omitempty"` // 关键词提取
// 状态
IsFocus bool `json:"isFocus"`
IsModified bool `json:"isModified"` // 最近是否被修改
Status string `json:"status"` // draft/review/approved/archived
// 元数据
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
UpdatedBy string `json:"updatedBy,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// Position 位置
type Position struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
// Size 尺寸
type Size struct {
Width float64 `json:"width"`
Height float64 `json:"height"`
}
// RelationshipView 关系视图
type RelationshipView struct {
From string `json:"from"`
To string `json:"to"`
Type string `json:"type"` // parent/reference/dependency/flow/semantic
Strength float64 `json:"strength"` // 0-1,关系强度
Bidirection bool `json:"bidirection"` // 是否双向
Label string `json:"label,omitempty"`
}
// LayoutAnalysis 布局分析
type LayoutAnalysis struct {
Algorithm string `json:"algorithm"` // detected: tree/grid/force/freeform
Direction string `json:"direction"` // horizontal/vertical/radial
Density float64 `json:"density"` // 0-1,节点密度
Clusters []ClusterInfo `json:"clusters"` // 聚类分析
Bounds Bounds `json:"bounds"` // 画布边界
ViewportFit string `json:"viewportFit"` // 视口适配建议
}
// ClusterInfo 聚类信息
type ClusterInfo struct {
ID string `json:"id"`
NodeIDs []string `json:"nodeIds"`
Center Position `json:"center"`
Label string `json:"label"` // 聚类主题
Cohesion float64 `json:"cohesion"` // 0-1,内聚度
}
// Bounds 边界
type Bounds struct {
MinX float64 `json:"minX"`
MinY float64 `json:"minY"`
MaxX float64 `json:"maxX"`
MaxY float64 `json:"maxY"`
}
// HierarchyView 层级视图
type HierarchyView struct {
MaxDepth int `json:"maxDepth"`
RootNodes []string `json:"rootNodes"`
Tree map[string]TreeNode `json:"tree"` // nodeID -> TreeNode
}
// TreeNode 树节点
type TreeNode struct {
ID string `json:"id"`
Depth int `json:"depth"`
Children []string `json:"children"`
Path []string `json:"path"` // 从根到当前节点的路径
}
// CanvasIntelligenceInsights 画布智能洞察
type CanvasIntelligenceInsights struct {
// 自动总结
Summary CanvasSummary `json:"summary"`
// 建议
Suggestions []ActionSuggestion `json:"suggestions"`
// 模式识别
Patterns []PatternRecognition `json:"patterns"`
// 问题诊断
Issues []CanvasIssue `json:"issues"`
// 趋势分析
Trends TrendAnalysis `json:"trends"`
}
// CanvasSummary 画布总结
type CanvasSummary struct {
OneSentence string `json:"oneSentence"` // 一句话总结
MainTopics []string `json:"mainTopics"` // 主要主题
Progress string `json:"progress"` // 进度评估:early/mid/mature
Completeness float64 `json:"completeness"` // 0-1,完整性评分
}
// ActionSuggestion 行动建议
type ActionSuggestion struct {
Priority int `json:"priority"` // 1-5
Action string `json:"action"` // 建议的操作
Reason string `json:"reason"` // 原因
Impact string `json:"impact"` // 预期影响
}
// PatternRecognition 模式识别
type PatternRecognition struct {
Pattern string `json:"pattern"` // 识别的模式名称
Confidence float64 `json:"confidence"` // 0-1,置信度
Evidence []string `json:"evidence"` // 证据节点
Description string `json:"description"` // 描述
}
// CanvasIssue 画布问题
type CanvasIssue struct {
Severity string `json:"severity"` // low/medium/high/critical
Type string `json:"type"` // orphan/duplicate/inconsistent/incomplete
Description string `json:"description"` // 问题描述
AffectedNodes []string `json:"affectedNodes"` // 受影响的节点
FixSuggestion string `json:"fixSuggestion"` // 修复建议
}
// TrendAnalysis 趋势分析
type TrendAnalysis struct {
RecentActivity ActivityLevel `json:"recentActivity"`
GrowthRate float64 `json:"growthRate"` // 节点增长率
HotAreas []string `json:"hotAreas"` // 热门区域
CollaborationMap map[string]int `json:"collaborationMap"` // userID -> 贡献数
}
// ActivityLevel 活动水平
type ActivityLevel struct {
Level string `json:"level"` // low/medium/high
LastUpdated int64 `json:"lastUpdated"` // 最后更新时间
UpdateCount int `json:"updateCount"` // 更新次数(最近1小时)
}
// CanvasCapabilityMatrix 画布能力矩阵
type CanvasCapabilityMatrix struct {
// 节点类型支持
SupportedNodeTypes []NodeTypeCapability `json:"supportedNodeTypes"`
// 操作权限
Permissions CanvasPermissions `json:"permissions"`
// 限制
Limits CanvasLimits `json:"limits"`
// 特性开关
Features map[string]bool `json:"features"`
// 扩展点
Extensions []ExtensionPoint `json:"extensions"`
}
// NodeTypeCapability 节点类型能力
type NodeTypeCapability struct {
Type string `json:"type"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Icon string `json:"icon,omitempty"`
Creatable bool `json:"creatable"`
Operations []string `json:"operations"` // read/update/delete/move/duplicate
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// CanvasPermissions 画布权限
type CanvasPermissions struct {
CanRead bool `json:"canRead"`
CanCreate bool `json:"canCreate"`
CanUpdate bool `json:"canUpdate"`
CanDelete bool `json:"canDelete"`
CanMove bool `json:"canMove"`
CanShare bool `json:"canShare"`
RequireApproval []string `json:"requireApproval"` // 需要审批的操作
}
// CanvasLimits 画布限制
type CanvasLimits struct {
MaxNodes int `json:"maxNodes"`
MaxDepth int `json:"maxDepth"`
MaxContentSize int `json:"maxContentSize"` // bytes
MaxBatchSize int `json:"maxBatchSize"` // 批量操作限制
}
// ExtensionPoint 扩展点
type ExtensionPoint struct {
Name string `json:"name"`
Type string `json:"type"` // hook/plugin/custom
Enabled bool `json:"enabled"`
Description string `json:"description"`
}
// CanvasRealtimeState 画布实时状态
type CanvasRealtimeState struct {
OnlineUsers []string `json:"onlineUsers"`
ActiveCursors map[string]Position `json:"activeCursors"` // userID -> Position
LockedNodes []string `json:"lockedNodes"`
PendingOperations int `json:"pendingOperations"`
SyncStatus string `json:"syncStatus"` // synced/syncing/conflict
}
// BuildEnhancedCanvasContext constructs the Agent context from CanvasProject,
// the application's persisted canvas document. The extension Canvas/CanvasNode
// models are not part of the production schema.
func (ci *CanvasIntelligence) BuildEnhancedCanvasContext(ctx context.Context, userID, canvasID string, focusNodeIDs []string) (*EnhancedCanvasContext, error) {
canvas, err := ci.service.repo.CanvasProjectForUser(userID, canvasID)
if err != nil {
return nil, fmt.Errorf("get canvas: %w", err)
}
document, err := creationDocument(canvas.PayloadJSON)
if err != nil {
return nil, fmt.Errorf("parse canvas document: %w", err)
}
nodes := canvasIntelligenceNodes(canvas, document)
// 2. 构建结构化快照
snapshot := ci.buildSnapshot(nodes, focusNodeIDs, canvasIntelligenceRelationships(document, nodes))
// 3. 生成智能洞察
intelligence := ci.generateIntelligence(nodes, snapshot)
// 4. 构建能力矩阵
capabilities := ci.buildCapabilityMatrix(userID, canvasID)
// 5. 获取实时状态
realtimeState := ci.getRealtimeState(ctx, document)
return &EnhancedCanvasContext{
ID: canvasID,
Name: canvas.Title,
Description: stringValue(document["description"]),
Snapshot: snapshot,
Intelligence: intelligence,
Capabilities: capabilities,
RealtimeState: realtimeState,
}, nil
}
// buildSnapshot 构建画布快照
func (ci *CanvasIntelligence) buildSnapshot(nodes []model.CanvasNode, focusNodeIDs []string, explicitRelationships ...[]RelationshipView) CanvasSnapshot {
focusMap := make(map[string]bool)
for _, id := range focusNodeIDs {
focusMap[id] = true
}
// 构建节点视图
focusNodes := make([]EnhancedNodeView, 0)
contextNodes := make([]EnhancedNodeView, 0)
for _, node := range nodes {
view := ci.buildEnhancedNodeView(node, focusMap[node.ID])
if focusMap[node.ID] {
focusNodes = append(focusNodes, view)
} else {
// 判断是否是上下文节点(焦点节点的相邻节点)
if ci.isContextNode(node, focusNodeIDs, nodes) {
contextNodes = append(contextNodes, view)
}
}
}
// 构建关系
relationships := ci.buildRelationships(nodes)
if len(explicitRelationships) > 0 {
relationships = append(explicitRelationships[0], relationships...)
}
// 分析布局
layout := ci.analyzeLayout(nodes)
// 构建层级
hierarchy := ci.buildHierarchy(nodes)
return CanvasSnapshot{
Timestamp: time.Now().Unix(),
TotalNodes: len(nodes),
FocusNodes: focusNodes,
ContextNodes: contextNodes,
Relationships: relationships,
Layout: layout,
Hierarchy: hierarchy,
}
}
func canvasIntelligenceNodes(canvas *model.CanvasProject, document map[string]any) []model.CanvasNode {
rawNodes := creationMaps(document["nodes"])
nodes := make([]model.CanvasNode, 0, len(rawNodes))
children := make(map[string][]string)
for _, raw := range rawNodes {
id := stringValue(raw["id"])
if id == "" {
continue
}
metadata := canvasIntelligenceMetadata(raw)
content := firstNonEmpty(
stringValue(raw["content"]),
stringValue(metadata["content"]),
stringValue(raw["title"]),
)
var parentID *string
if value := stringValue(raw["parentId"]); value != "" {
parentID = &value
children[value] = append(children[value], id)
}
nodes = append(nodes, model.CanvasNode{
ID: id, CanvasID: canvas.ID, Type: stringValue(raw["type"]), Content: content,
Position: canvasIntelligencePosition(raw), Size: canvasIntelligenceSize(raw), ParentID: parentID, Metadata: metadata,
CreatedAt: canvas.CreatedAt, UpdatedAt: canvas.UpdatedAt,
})
}
for index := range nodes {
nodes[index].Children = children[nodes[index].ID]
}
return nodes
}
func canvasIntelligenceMetadata(raw map[string]any) map[string]any {
metadata := map[string]any{}
if value, ok := raw["metadata"].(map[string]any); ok {
for key, item := range value {
metadata[key] = item
}
}
if value, ok := raw["data"].(map[string]any); ok {
for key, item := range value {
if _, exists := metadata[key]; !exists {
metadata[key] = item
}
}
}
return metadata
}
func canvasIntelligencePosition(raw map[string]any) *model.CanvasPosition {
position, _ := raw["position"].(map[string]any)
x, y := numberValue(raw["x"], 0), numberValue(raw["y"], 0)
if position != nil {
x, y = numberValue(position["x"], x), numberValue(position["y"], y)
}
if position == nil && raw["x"] == nil && raw["y"] == nil {
return nil
}
return &model.CanvasPosition{X: x, Y: y}
}
func canvasIntelligenceSize(raw map[string]any) *model.CanvasSize {
size, _ := raw["size"].(map[string]any)
width, height := numberValue(raw["width"], 0), numberValue(raw["height"], 0)
if size != nil {
width, height = numberValue(size["width"], width), numberValue(size["height"], height)
}
if size == nil && raw["width"] == nil && raw["height"] == nil {
return nil
}
return &model.CanvasSize{Width: width, Height: height}
}
func canvasIntelligenceRelationships(document map[string]any, nodes []model.CanvasNode) []RelationshipView {
known := make(map[string]bool, len(nodes))
for _, node := range nodes {
known[node.ID] = true
}
relationships := make([]RelationshipView, 0)
for _, edge := range creationMaps(document["connections"]) {
from, to := stringValue(edge["fromNodeId"]), stringValue(edge["toNodeId"])
if from == "" || to == "" || !known[from] || !known[to] {
continue
}
typeName := strings.TrimSpace(stringValue(edge["type"]))
if typeName == "" {
typeName = "flow"
}
relationships = append(relationships, RelationshipView{
From: from, To: to, Type: typeName, Strength: 1, Label: stringValue(edge["label"]),
})
}
return relationships
}
// buildEnhancedNodeView 构建增强节点视图
func (ci *CanvasIntelligence) buildEnhancedNodeView(node model.CanvasNode, isFocus bool) EnhancedNodeView {
// 智能内容摘要
preview := node.Content
if len(preview) > 200 {
preview = preview[:200] + "..."
}
// 提取关键词(简化实现)
keywords := ci.extractKeywords(node.Content)
// 自动分类
category := ci.categorizeNode(node)
// 情感分析(简化)
sentiment := ci.analyzeSentiment(node.Content)
view := EnhancedNodeView{
ID: node.ID,
Type: node.Type,
ContentPreview: preview,
ContentHash: hashContentSHA256(node.Content),
ContentLength: len(node.Content),
HasFullContent: isFocus,
Position: ci.convertPosition(node.Position),
Size: ci.convertSize(node.Size),
ZIndex: ci.getZIndex(node),
ParentID: ci.getParentID(node.ParentID),
Children: node.Children,
Tags: ci.extractTags(node),
Category: category,
Sentiment: sentiment,
Keywords: keywords,
IsFocus: isFocus,
IsModified: ci.isRecentlyModified(node),
Status: ci.getNodeStatus(node),
CreatedAt: node.CreatedAt.Unix(),
UpdatedAt: node.UpdatedAt.Unix(),
Metadata: node.Metadata,
}
if isFocus {
view.FullContent = node.Content
}
return view
}
// Continue in next chunk...