819 lines
22 KiB
Go
819 lines
22 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"crypto/sha256"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
|
||
"yingce/backend/internal/model"
|
||
)
|
||
|
||
// Canvas Tool Handlers - 工具处理器实现
|
||
|
||
// handleNodeCreate 处理创建节点
|
||
func (r *CanvasToolRegistry) handleNodeCreate(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
// 解析参数
|
||
nodeType, _ := args["type"].(string)
|
||
content, _ := args["content"].(string)
|
||
|
||
var position *model.CanvasPosition
|
||
if pos, ok := args["position"].(map[string]any); ok {
|
||
x, _ := pos["x"].(float64)
|
||
y, _ := pos["y"].(float64)
|
||
position = &model.CanvasPosition{X: x, Y: y}
|
||
}
|
||
|
||
var size *model.CanvasSize
|
||
if sz, ok := args["size"].(map[string]any); ok {
|
||
width, _ := sz["width"].(float64)
|
||
height, _ := sz["height"].(float64)
|
||
size = &model.CanvasSize{Width: width, Height: height}
|
||
}
|
||
|
||
var parentID *string
|
||
if pid, ok := args["parentId"].(string); ok && pid != "" {
|
||
parentID = &pid
|
||
}
|
||
|
||
metadata, _ := args["metadata"].(map[string]interface{})
|
||
|
||
// 获取画布ID
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 创建节点
|
||
node := model.CanvasNode{
|
||
Type: nodeType,
|
||
Content: content,
|
||
Position: position,
|
||
Size: size,
|
||
ParentID: parentID,
|
||
Metadata: metadata,
|
||
}
|
||
|
||
created, err := r.service.repo.CreateCanvasNode(userID, state.Request.CanvasID, node)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create node: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"nodeId": created.ID,
|
||
"node": created,
|
||
"message": fmt.Sprintf("✅ 已创建 %s 节点:%s", nodeType, created.ID),
|
||
}, nil
|
||
}
|
||
|
||
// handleNodeRead 处理读取节点
|
||
func (r *CanvasToolRegistry) handleNodeRead(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
nodeID, _ := args["nodeId"].(string)
|
||
includeChildren, _ := args["includeChildren"].(bool)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
node, err := r.service.repo.CanvasNode(userID, state.Request.CanvasID, nodeID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get node: %w", err)
|
||
}
|
||
|
||
result := map[string]any{
|
||
"success": true,
|
||
"node": node,
|
||
}
|
||
|
||
if includeChildren && len(node.Children) > 0 {
|
||
children := make([]model.CanvasNode, 0, len(node.Children))
|
||
for _, childID := range node.Children {
|
||
child, err := r.service.repo.CanvasNode(userID, state.Request.CanvasID, childID)
|
||
if err == nil {
|
||
children = append(children, child)
|
||
}
|
||
}
|
||
result["children"] = children
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// handleNodeUpdate 处理更新节点
|
||
func (r *CanvasToolRegistry) handleNodeUpdate(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
nodeID, _ := args["nodeId"].(string)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 构建更新对象
|
||
update := model.CanvasNodeUpdate{}
|
||
|
||
if content, ok := args["content"].(string); ok {
|
||
update.Content = &content
|
||
}
|
||
|
||
if pos, ok := args["position"].(map[string]any); ok {
|
||
x, _ := pos["x"].(float64)
|
||
y, _ := pos["y"].(float64)
|
||
update.Position = &model.CanvasPosition{X: x, Y: y}
|
||
}
|
||
|
||
if sz, ok := args["size"].(map[string]any); ok {
|
||
width, _ := sz["width"].(float64)
|
||
height, _ := sz["height"].(float64)
|
||
update.Size = &model.CanvasSize{Width: width, Height: height}
|
||
}
|
||
|
||
if metadata, ok := args["metadata"].(map[string]interface{}); ok {
|
||
update.Metadata = metadata
|
||
}
|
||
|
||
updated, err := r.service.repo.UpdateCanvasNode(userID, state.Request.CanvasID, nodeID, update)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("update node: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"node": updated,
|
||
"message": fmt.Sprintf("✅ 已更新节点:%s", nodeID),
|
||
}, nil
|
||
}
|
||
|
||
// handleNodeDelete 处理删除节点
|
||
func (r *CanvasToolRegistry) handleNodeDelete(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
nodeID, _ := args["nodeId"].(string)
|
||
reason, _ := args["reason"].(string)
|
||
recursive, _ := args["recursive"].(bool)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 删除节点
|
||
if err := r.service.repo.DeleteCanvasNode(userID, state.Request.CanvasID, nodeID, recursive); err != nil {
|
||
return nil, fmt.Errorf("delete node: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"message": fmt.Sprintf("✅ 已删除节点:%s(原因:%s)", nodeID, reason),
|
||
}, nil
|
||
}
|
||
|
||
// handleNodeMove 处理移动节点
|
||
func (r *CanvasToolRegistry) handleNodeMove(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
nodeID, _ := args["nodeId"].(string)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
update := model.CanvasNodeUpdate{}
|
||
|
||
// 更新位置
|
||
if pos, ok := args["position"].(map[string]any); ok {
|
||
x, _ := pos["x"].(float64)
|
||
y, _ := pos["y"].(float64)
|
||
update.Position = &model.CanvasPosition{X: x, Y: y}
|
||
}
|
||
|
||
// 更新父节点
|
||
if newParentID, ok := args["newParentId"].(string); ok {
|
||
if newParentID == "" {
|
||
emptyParentID := ""
|
||
update.ParentID = &emptyParentID
|
||
} else {
|
||
update.ParentID = &newParentID
|
||
}
|
||
}
|
||
|
||
updated, err := r.service.repo.UpdateCanvasNode(userID, state.Request.CanvasID, nodeID, update)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("move node: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"node": updated,
|
||
"message": fmt.Sprintf("✅ 已移动节点:%s", nodeID),
|
||
}, nil
|
||
}
|
||
|
||
// handleNodeDuplicate 处理复制节点
|
||
func (r *CanvasToolRegistry) handleNodeDuplicate(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
nodeID, _ := args["nodeId"].(string)
|
||
includeChildren, _ := args["includeChildren"].(bool)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 读取原节点
|
||
original, err := r.service.repo.CanvasNode(userID, state.Request.CanvasID, nodeID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get original node: %w", err)
|
||
}
|
||
|
||
// 创建副本
|
||
duplicate := model.CanvasNode{
|
||
Type: original.Type,
|
||
Content: original.Content + " (副本)",
|
||
Position: original.Position,
|
||
Size: original.Size,
|
||
ParentID: original.ParentID,
|
||
Metadata: original.Metadata,
|
||
}
|
||
|
||
// 调整位置(偏移)
|
||
if duplicate.Position != nil {
|
||
duplicate.Position.X += 50
|
||
duplicate.Position.Y += 50
|
||
}
|
||
|
||
// 如果指定了新位置
|
||
if pos, ok := args["position"].(map[string]any); ok {
|
||
x, _ := pos["x"].(float64)
|
||
y, _ := pos["y"].(float64)
|
||
duplicate.Position = &model.CanvasPosition{X: x, Y: y}
|
||
}
|
||
|
||
created, err := r.service.repo.CreateCanvasNode(userID, state.Request.CanvasID, duplicate)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create duplicate: %w", err)
|
||
}
|
||
|
||
// 递归复制子节点
|
||
if includeChildren {
|
||
if err := r.duplicateChildrenRecursive(userID, state.Request.CanvasID, nodeID, created.ID); err != nil {
|
||
log.Printf("[Agent] failed to duplicate children: %v", err)
|
||
// 不中断,继续返回主节点的复制结果
|
||
}
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"originalId": nodeID,
|
||
"duplicateId": created.ID,
|
||
"node": created,
|
||
"childrenCopied": includeChildren,
|
||
"message": fmt.Sprintf("✅ 已复制节点:%s -> %s", nodeID, created.ID),
|
||
}, nil
|
||
}
|
||
|
||
// duplicateChildrenRecursive 递归复制子节点
|
||
func (r *CanvasToolRegistry) duplicateChildrenRecursive(userID, canvasID, parentID, newParentID string) error {
|
||
// 获取原始父节点的所有子节点
|
||
children, err := r.service.repo.GetCanvasNodeChildren(userID, canvasID, parentID)
|
||
if err != nil {
|
||
return fmt.Errorf("get children of %s: %w", parentID, err)
|
||
}
|
||
|
||
for _, child := range children {
|
||
// 复制子节点
|
||
duplicate := child
|
||
duplicate.ID = "" // 清空 ID,让数据库生成新的
|
||
duplicate.ParentID = &newParentID
|
||
|
||
// 调整位置(稍微偏移)
|
||
if duplicate.Position != nil {
|
||
duplicate.Position.X += 10
|
||
duplicate.Position.Y += 10
|
||
}
|
||
|
||
created, err := r.service.repo.CreateCanvasNode(userID, canvasID, duplicate)
|
||
if err != nil {
|
||
log.Printf("[Agent] failed to duplicate child %s: %v", child.ID, err)
|
||
continue
|
||
}
|
||
|
||
// 递归复制这个子节点的子节点
|
||
if err := r.duplicateChildrenRecursive(userID, canvasID, child.ID, created.ID); err != nil {
|
||
log.Printf("[Agent] failed to duplicate descendants of %s: %v", child.ID, err)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// handleRelationshipCreate 处理创建关系
|
||
func (r *CanvasToolRegistry) handleRelationshipCreate(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
fromNodeID, _ := args["fromNodeId"].(string)
|
||
toNodeID, _ := args["toNodeId"].(string)
|
||
relType, _ := args["type"].(string)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 创建关系
|
||
relationship := model.CanvasRelationship{
|
||
From: fromNodeID,
|
||
To: toNodeID,
|
||
Type: relType,
|
||
}
|
||
|
||
if err := r.service.repo.CreateCanvasRelationship(userID, state.Request.CanvasID, relationship); err != nil {
|
||
return nil, fmt.Errorf("create relationship: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"message": fmt.Sprintf("✅ 已创建关系:%s -> %s (%s)", fromNodeID, toNodeID, relType),
|
||
}, nil
|
||
}
|
||
|
||
// handleRelationshipDelete 处理删除关系
|
||
func (r *CanvasToolRegistry) handleRelationshipDelete(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
fromNodeID, _ := args["fromNodeId"].(string)
|
||
toNodeID, _ := args["toNodeId"].(string)
|
||
relType, _ := args["type"].(string)
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
if err := r.service.repo.DeleteCanvasRelationship(userID, state.Request.CanvasID, fromNodeID, toNodeID, relType); err != nil {
|
||
return nil, fmt.Errorf("delete relationship: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"message": fmt.Sprintf("✅ 已删除关系:%s -> %s", fromNodeID, toNodeID),
|
||
}, nil
|
||
}
|
||
|
||
// handleLayout 处理自动布局
|
||
func (r *CanvasToolRegistry) handleLayout(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
algorithm, _ := args["algorithm"].(string)
|
||
direction, _ := args["direction"].(string)
|
||
spacing, _ := args["spacing"].(float64)
|
||
|
||
if spacing == 0 {
|
||
spacing = 100
|
||
}
|
||
|
||
var nodeIDs []string
|
||
if ids, ok := args["nodeIds"].([]interface{}); ok {
|
||
for _, id := range ids {
|
||
if s, ok := id.(string); ok {
|
||
nodeIDs = append(nodeIDs, s)
|
||
}
|
||
}
|
||
}
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 获取要布局的节点
|
||
var nodes []model.CanvasNode
|
||
if len(nodeIDs) > 0 {
|
||
for _, nodeID := range nodeIDs {
|
||
node, err := r.service.repo.CanvasNode(userID, state.Request.CanvasID, nodeID)
|
||
if err == nil {
|
||
nodes = append(nodes, node)
|
||
}
|
||
}
|
||
} else {
|
||
nodes, err = r.service.repo.CanvasNodes(userID, state.Request.CanvasID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get nodes: %w", err)
|
||
}
|
||
}
|
||
|
||
// 执行布局算法
|
||
layoutEngine := NewCanvasLayoutEngine(r.service)
|
||
newPositions, err := layoutEngine.ApplyLayout(algorithm, direction, spacing, nodes)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("apply layout: %w", err)
|
||
}
|
||
|
||
// 批量更新节点位置
|
||
updatedCount := 0
|
||
for nodeID, position := range newPositions {
|
||
update := model.CanvasNodeUpdate{
|
||
Position: position,
|
||
}
|
||
if _, err := r.service.repo.UpdateCanvasNode(userID, state.Request.CanvasID, nodeID, update); err == nil {
|
||
updatedCount++
|
||
}
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"message": fmt.Sprintf("✅ 已应用 %s 布局,更新 %d 个节点", algorithm, updatedCount),
|
||
"updated": updatedCount,
|
||
}, nil
|
||
}
|
||
|
||
// handleSearch 处理搜索
|
||
func (r *CanvasToolRegistry) handleSearch(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
query, _ := args["query"].(string)
|
||
nodeType, _ := args["type"].(string)
|
||
category, _ := args["category"].(string)
|
||
limit, _ := args["limit"].(float64)
|
||
|
||
if limit == 0 {
|
||
limit = 20
|
||
}
|
||
|
||
var tags []string
|
||
if t, ok := args["tags"].([]interface{}); ok {
|
||
for _, tag := range t {
|
||
if s, ok := tag.(string); ok {
|
||
tags = append(tags, s)
|
||
}
|
||
}
|
||
}
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
// 搜索节点
|
||
searchEngine := NewCanvasSearchEngine(r.service)
|
||
results, err := searchEngine.Search(userID, state.Request.CanvasID, CanvasSearchQuery{
|
||
Query: query,
|
||
Type: nodeType,
|
||
Category: category,
|
||
Tags: tags,
|
||
Limit: int(limit),
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("search: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"results": results,
|
||
"count": len(results),
|
||
"message": fmt.Sprintf("找到 %d 个匹配节点", len(results)),
|
||
}, nil
|
||
}
|
||
|
||
// handleBulkOperation 处理批量操作
|
||
func (r *CanvasToolRegistry) handleBulkOperation(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
operation, _ := args["operation"].(string)
|
||
reason, _ := args["reason"].(string)
|
||
params, _ := args["params"].(map[string]any)
|
||
|
||
var nodeIDs []string
|
||
if ids, ok := args["nodeIds"].([]interface{}); ok {
|
||
for _, id := range ids {
|
||
if s, ok := id.(string); ok {
|
||
nodeIDs = append(nodeIDs, s)
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(nodeIDs) == 0 {
|
||
return nil, fmt.Errorf("no node IDs provided")
|
||
}
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
successCount := 0
|
||
failCount := 0
|
||
|
||
switch operation {
|
||
case "update":
|
||
update := model.CanvasNodeUpdate{}
|
||
if content, ok := params["content"].(string); ok {
|
||
update.Content = &content
|
||
}
|
||
if metadata, ok := params["metadata"].(map[string]interface{}); ok {
|
||
update.Metadata = metadata
|
||
}
|
||
|
||
for _, nodeID := range nodeIDs {
|
||
if _, err := r.service.repo.UpdateCanvasNode(userID, state.Request.CanvasID, nodeID, update); err == nil {
|
||
successCount++
|
||
} else {
|
||
failCount++
|
||
}
|
||
}
|
||
|
||
case "delete":
|
||
for _, nodeID := range nodeIDs {
|
||
if err := r.service.repo.DeleteCanvasNode(userID, state.Request.CanvasID, nodeID, false); err == nil {
|
||
successCount++
|
||
} else {
|
||
failCount++
|
||
}
|
||
}
|
||
|
||
case "move":
|
||
var position *model.CanvasPosition
|
||
if pos, ok := params["position"].(map[string]any); ok {
|
||
x, _ := pos["x"].(float64)
|
||
y, _ := pos["y"].(float64)
|
||
position = &model.CanvasPosition{X: x, Y: y}
|
||
}
|
||
|
||
for i, nodeID := range nodeIDs {
|
||
update := model.CanvasNodeUpdate{
|
||
Position: position,
|
||
}
|
||
// 为每个节点稍微偏移位置
|
||
if position != nil && i > 0 {
|
||
offsetPos := &model.CanvasPosition{
|
||
X: position.X + float64(i*20),
|
||
Y: position.Y + float64(i*20),
|
||
}
|
||
update.Position = offsetPos
|
||
}
|
||
|
||
if _, err := r.service.repo.UpdateCanvasNode(userID, state.Request.CanvasID, nodeID, update); err == nil {
|
||
successCount++
|
||
} else {
|
||
failCount++
|
||
}
|
||
}
|
||
|
||
case "tag":
|
||
if tags, ok := params["tags"].([]interface{}); ok {
|
||
tagStrings := make([]string, 0, len(tags))
|
||
for _, tag := range tags {
|
||
if s, ok := tag.(string); ok {
|
||
tagStrings = append(tagStrings, s)
|
||
}
|
||
}
|
||
|
||
for _, nodeID := range nodeIDs {
|
||
node, err := r.service.repo.CanvasNode(userID, state.Request.CanvasID, nodeID)
|
||
if err != nil {
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
if node.Metadata == nil {
|
||
node.Metadata = make(map[string]interface{})
|
||
}
|
||
node.Metadata["tags"] = tagStrings
|
||
|
||
update := model.CanvasNodeUpdate{
|
||
Metadata: node.Metadata,
|
||
}
|
||
if _, err := r.service.repo.UpdateCanvasNode(userID, state.Request.CanvasID, nodeID, update); err == nil {
|
||
successCount++
|
||
} else {
|
||
failCount++
|
||
}
|
||
}
|
||
}
|
||
|
||
default:
|
||
return nil, fmt.Errorf("unsupported operation: %s", operation)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"operation": operation,
|
||
"successCount": successCount,
|
||
"failCount": failCount,
|
||
"message": fmt.Sprintf("✅ 批量%s完成:成功 %d,失败 %d(原因:%s)", operation, successCount, failCount, reason),
|
||
}, nil
|
||
}
|
||
|
||
// handleSnapshot 处理获取快照
|
||
func (r *CanvasToolRegistry) handleSnapshot(ctx context.Context, userID, runID string, args map[string]any) (any, error) {
|
||
includeIntelligence, _ := args["includeIntelligence"].(bool)
|
||
if _, ok := args["includeIntelligence"]; !ok {
|
||
includeIntelligence = true // 默认包含智能分析
|
||
}
|
||
|
||
var focusNodeIDs []string
|
||
if ids, ok := args["focusNodeIds"].([]interface{}); ok {
|
||
for _, id := range ids {
|
||
if s, ok := id.(string); ok {
|
||
focusNodeIDs = append(focusNodeIDs, s)
|
||
}
|
||
}
|
||
}
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decode state: %w", err)
|
||
}
|
||
|
||
if includeIntelligence {
|
||
// 使用完整的智能上下文
|
||
canvasIntelligence := r.service.NewCanvasIntelligence()
|
||
enhancedCanvas, err := canvasIntelligence.BuildEnhancedCanvasContext(ctx, userID, state.Request.CanvasID, focusNodeIDs)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("build intelligence: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"snapshot": enhancedCanvas.Snapshot,
|
||
"intelligence": enhancedCanvas.Intelligence,
|
||
"timestamp": time.Now().Unix(),
|
||
}, nil
|
||
}
|
||
|
||
// 简单快照(不含智能分析)
|
||
nodes, err := r.service.repo.CanvasNodes(userID, state.Request.CanvasID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get nodes: %w", err)
|
||
}
|
||
|
||
return map[string]any{
|
||
"success": true,
|
||
"nodeCount": len(nodes),
|
||
"nodes": nodes,
|
||
"timestamp": time.Now().Unix(),
|
||
}, nil
|
||
}
|
||
|
||
// 辅助方法
|
||
|
||
func (r *CanvasToolRegistry) validatePermissions(userID string, requiredPermissions []string) error {
|
||
// 实现真实的权限检查
|
||
if len(requiredPermissions) == 0 {
|
||
return nil
|
||
}
|
||
|
||
// 从用户服务获取用户权限
|
||
user, err := r.service.repo.User(userID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get user: %w", err)
|
||
}
|
||
|
||
if user.Role == model.UserRoleAdmin {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("permission denied: user role %s does not grant Agent canvas permissions", user.Role)
|
||
}
|
||
|
||
func (r *CanvasToolRegistry) validateParameters(tool *CanvasToolDefinition, args map[string]any) error {
|
||
// 实现参数验证(基于 JSON Schema)
|
||
if tool.Schema == nil {
|
||
return nil
|
||
}
|
||
|
||
// 检查必需参数
|
||
if required, ok := tool.Schema["required"].([]any); ok {
|
||
for _, req := range required {
|
||
if reqStr, ok := req.(string); ok {
|
||
if _, exists := args[reqStr]; !exists {
|
||
return fmt.Errorf("missing required parameter: %s", reqStr)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查参数类型
|
||
if properties, ok := tool.Schema["properties"].(map[string]any); ok {
|
||
for paramName, paramValue := range args {
|
||
if propSchema, exists := properties[paramName]; exists {
|
||
if schemaMap, ok := propSchema.(map[string]any); ok {
|
||
if err := r.validateParameterType(paramName, paramValue, schemaMap); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (r *CanvasToolRegistry) validateParameterType(name string, value any, schema map[string]any) error {
|
||
expectedType, _ := schema["type"].(string)
|
||
|
||
switch expectedType {
|
||
case "string":
|
||
if _, ok := value.(string); !ok {
|
||
return fmt.Errorf("parameter %s must be a string", name)
|
||
}
|
||
case "number":
|
||
switch value.(type) {
|
||
case float64, int, int64, float32:
|
||
// OK
|
||
default:
|
||
return fmt.Errorf("parameter %s must be a number", name)
|
||
}
|
||
case "boolean":
|
||
if _, ok := value.(bool); !ok {
|
||
return fmt.Errorf("parameter %s must be a boolean", name)
|
||
}
|
||
case "object":
|
||
if _, ok := value.(map[string]any); !ok {
|
||
return fmt.Errorf("parameter %s must be an object", name)
|
||
}
|
||
case "array":
|
||
if _, ok := value.([]any); !ok {
|
||
return fmt.Errorf("parameter %s must be an array", name)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (r *CanvasToolRegistry) checkApproval(userID, runID, toolName string, args map[string]any) (bool, error) {
|
||
// 实现审批检查逻辑
|
||
// 检查是否已经有批准记录
|
||
|
||
// 从数据库查询审批记录
|
||
approvalKey := fmt.Sprintf("approval:%s:%s:%s", runID, toolName, hashArgs(args))
|
||
|
||
run, err := r.service.repo.CloudAgent(userID, runID)
|
||
if err != nil {
|
||
return false, fmt.Errorf("get run: %w", err)
|
||
}
|
||
|
||
state, err := cloudAgentDecode(run)
|
||
if err != nil {
|
||
return false, fmt.Errorf("load state: %w", err)
|
||
}
|
||
if state.Decisions != nil && state.Decisions[approvalKey] == "approved" {
|
||
return true, nil
|
||
}
|
||
return false, nil
|
||
}
|
||
|
||
// hashArgs 生成参数的哈希值,用于审批记录的唯一标识
|
||
func hashArgs(args map[string]any) string {
|
||
data, _ := json.Marshal(args)
|
||
return fmt.Sprintf("%x", sha256.Sum256(data))[:16]
|
||
}
|