522 lines
16 KiB
Go
522 lines
16 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"yingce/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...
|