522 lines
16 KiB
Go
522 lines
16 KiB
Go
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...
|