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

281 lines
6.2 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 (
"crypto/sha256"
"encoding/hex"
"fmt"
"log/slog"
"infinite-canvas/backend/internal/model"
)
// Skills Configuration Builders
// buildSkillManifests 构建 Skills 清单(传递给 Pi)
func (s *Service) buildSkillManifests(skills []cloudAgentSkill) []map[string]any {
if len(skills) == 0 {
return []map[string]any{}
}
manifests := make([]map[string]any, 0, len(skills))
for _, skill := range skills {
manifest := map[string]any{
"name": skill.Name,
"enabled": true,
"description": skill.Description,
"version": skill.Version,
"hash": skill.Hash,
"instruction": skill.Instruction,
"files": skill.Files,
}
manifests = append(manifests, manifest)
}
return manifests
}
// buildCompactionStrategy 构建压缩策略
func (s *Service) buildCompactionStrategy(userID, canvasID string, budgets ...cloudAgentContextBudget) map[string]any {
budget := defaultCloudAgentContextBudget()
if len(budgets) > 0 {
budget = budgets[0]
}
compactAt := budget.CompactAtTokens
if compactAt <= 0 || compactAt >= budget.ContextWindowTokens {
compactAt = budget.ContextWindowTokens * 80 / 100
}
keepRecentTokens := min(20_000, max(1_024, budget.ContextWindowTokens/6))
keepRecentTokens = min(keepRecentTokens, max(512, compactAt/2))
return map[string]any{
"enabled": true,
"reserveTokens": budget.ContextWindowTokens - compactAt,
"keepRecentTokens": keepRecentTokens,
}
}
// Utility Functions
// hashContentSHA256 使用 SHA256 计算内容哈希
func hashContentSHA256(content string) string {
hash := sha256.Sum256([]byte(content))
return hex.EncodeToString(hash[:])
}
// truncateString 截断字符串
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}
// mergeMetadata 合并元数据
func mergeMetadata(base, override map[string]interface{}) map[string]interface{} {
if base == nil {
base = make(map[string]interface{})
}
if override == nil {
return base
}
result := make(map[string]interface{})
for k, v := range base {
result[k] = v
}
for k, v := range override {
result[k] = v
}
return result
}
// extractNodeIDs 从节点列表提取 ID
func extractNodeIDs(nodes []model.CanvasNode) []string {
ids := make([]string, 0, len(nodes))
for _, node := range nodes {
ids = append(ids, node.ID)
}
return ids
}
// filterNodesByType 按类型过滤节点
func filterNodesByType(nodes []model.CanvasNode, nodeType string) []model.CanvasNode {
filtered := make([]model.CanvasNode, 0)
for _, node := range nodes {
if node.Type == nodeType {
filtered = append(filtered, node)
}
}
return filtered
}
// findNodeByID 通过 ID 查找节点
func findNodeByID(nodes []model.CanvasNode, nodeID string) *model.CanvasNode {
for _, node := range nodes {
if node.ID == nodeID {
return &node
}
}
return nil
}
// Validation Functions
// validateNodeType 验证节点类型
func validateNodeType(nodeType string) bool {
validTypes := map[string]bool{
"text": true,
"markdown": true,
"code": true,
"image": true,
"video": true,
"audio": true,
"link": true,
"diagram": true,
"mindmap": true,
"flowchart": true,
"task": true,
"note": true,
}
return validTypes[nodeType]
}
// validateRelationshipType 验证关系类型
func validateRelationshipType(relType string) bool {
validTypes := map[string]bool{
"parent": true,
"reference": true,
"dependency": true,
"flow": true,
"semantic": true,
"custom": true,
}
return validTypes[relType]
}
// Format Functions
// formatBytes 格式化字节数
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// formatDuration 格式化时长
func formatDuration(ms int64) string {
if ms < 1000 {
return fmt.Sprintf("%dms", ms)
}
if ms < 60000 {
return fmt.Sprintf("%.1fs", float64(ms)/1000)
}
return fmt.Sprintf("%.1fm", float64(ms)/60000)
}
// Debug and Logging Helpers
// logPiAgentStep 记录 Pi Agent 步骤
func logPiAgentStep(runID, step, message string) {
slog.Debug("agent step", "run", runID, "step", step, "message", message)
}
// logPiAgentError 记录 Pi Agent 错误
func logPiAgentError(runID string, err error) {
slog.Warn("agent error", "run", runID, "error", err)
}
// logPiAgentMetric 记录 Pi Agent 指标
func logPiAgentMetric(runID, metric string, value interface{}) {
slog.Debug("agent metric", "run", runID, "metric", metric, "value", value)
}
// Type Conversions
// toStringSlice 转换为字符串切片
func toStringSlice(input interface{}) []string {
if input == nil {
return []string{}
}
switch v := input.(type) {
case []string:
return v
case []interface{}:
result := make([]string, 0, len(v))
for _, item := range v {
if s, ok := item.(string); ok {
result = append(result, s)
}
}
return result
default:
return []string{}
}
}
// toMapStringAny 转换为 map[string]any
func toMapStringAny(input interface{}) map[string]any {
if input == nil {
return make(map[string]any)
}
if m, ok := input.(map[string]any); ok {
return m
}
if m, ok := input.(map[string]interface{}); ok {
return m
}
return make(map[string]any)
}
// Safe Accessors
// safeGetString 安全获取字符串
func safeGetString(m map[string]any, key string, defaultValue string) string {
if v, ok := m[key].(string); ok {
return v
}
return defaultValue
}
// safeGetInt 安全获取整数
func safeGetInt(m map[string]any, key string, defaultValue int) int {
if v, ok := m[key].(int); ok {
return v
}
if v, ok := m[key].(float64); ok {
return int(v)
}
return defaultValue
}
// safeGetBool 安全获取布尔值
func safeGetBool(m map[string]any, key string, defaultValue bool) bool {
if v, ok := m[key].(bool); ok {
return v
}
return defaultValue
}
// safeGetFloat 安全获取浮点数
func safeGetFloat(m map[string]any, key string, defaultValue float64) float64 {
if v, ok := m[key].(float64); ok {
return v
}
if v, ok := m[key].(int); ok {
return float64(v)
}
return defaultValue
}