From 0fa739d438b1acfaf971f7b2320fdcf3b4815240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:00:23 +0800 Subject: [PATCH] Add files via upload --- internal/database/conversation.go | 151 +++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/internal/database/conversation.go b/internal/database/conversation.go index 5dbb4a64..631431f7 100644 --- a/internal/database/conversation.go +++ b/internal/database/conversation.go @@ -1234,9 +1234,20 @@ func (db *DB) GetProcessDetailByID(id string) (*ProcessDetail, error) { // ProcessDetailsSummary 过程详情摘要(用于折叠态展示,避免全量加载)。 type ProcessDetailsSummary struct { - Total int `json:"total"` - IterationCount int `json:"iterationCount"` - MaxIteration int `json:"maxIteration"` + Total int `json:"total"` + IterationCount int `json:"iterationCount"` + MaxIteration int `json:"maxIteration"` + ToolCount int `json:"toolCount"` + ToolExecutions []ProcessDetailsToolExecution `json:"toolExecutions,omitempty"` + MCPExecutionIDs []string `json:"mcpExecutionIds,omitempty"` +} + +type ProcessDetailsToolExecution struct { + ProcessDetailID string `json:"processDetailId,omitempty"` + ToolName string `json:"toolName,omitempty"` + ToolCallID string `json:"toolCallId,omitempty"` + ExecutionID string `json:"executionId,omitempty"` + Status string `json:"status,omitempty"` } // GetProcessDetailsSummary 统计消息的过程详情数量与迭代轮次。 @@ -1254,6 +1265,110 @@ func (db *DB) GetProcessDetailsSummary(messageID string) (*ProcessDetailsSummary return summary, nil } + if err := db.QueryRow( + "SELECT COUNT(*) FROM process_details WHERE message_id = ? AND event_type = 'tool_call'", + messageID, + ).Scan(&summary.ToolCount); err != nil { + return nil, fmt.Errorf("统计工具调用详情失败: %w", err) + } + + execRows, err := db.Query( + "SELECT id, event_type, data FROM process_details WHERE message_id = ? AND event_type IN ('tool_call', 'tool_result') ORDER BY created_at ASC, rowid ASC", + messageID, + ) + if err != nil { + return nil, fmt.Errorf("查询工具执行摘要失败: %w", err) + } + seenExecIDs := make(map[string]bool) + toolIndexByCallID := make(map[string]int) + unmatchedToolIdx := 0 + for execRows.Next() { + var detailID string + var eventType string + var dataJSON string + if err := execRows.Scan(&detailID, &eventType, &dataJSON); err != nil { + execRows.Close() + return nil, fmt.Errorf("扫描工具执行摘要失败: %w", err) + } + if dataJSON == "" { + continue + } + var payload map[string]interface{} + if err := json.Unmarshal([]byte(dataJSON), &payload); err != nil { + continue + } + toolName, _ := payload["toolName"].(string) + toolName = strings.TrimSpace(toolName) + toolCallID, _ := payload["toolCallId"].(string) + toolCallID = strings.TrimSpace(toolCallID) + execID, _ := payload["executionId"].(string) + execID = strings.TrimSpace(execID) + status := "" + if eventType == "tool_result" { + if success, ok := payload["success"].(bool); ok { + if success { + status = "completed" + } else { + status = "failed" + } + } else if isErr, ok := payload["isError"].(bool); ok && isErr { + status = "failed" + } + } + if eventType == "tool_call" { + summary.ToolExecutions = append(summary.ToolExecutions, ProcessDetailsToolExecution{ + ProcessDetailID: strings.TrimSpace(detailID), + ToolName: toolName, + ToolCallID: toolCallID, + Status: "running", + }) + if toolCallID != "" { + toolIndexByCallID[toolCallID] = len(summary.ToolExecutions) - 1 + } + } + if eventType == "tool_result" { + idx := -1 + if toolCallID != "" { + if found, ok := toolIndexByCallID[toolCallID]; ok { + idx = found + } + } + if idx < 0 && unmatchedToolIdx < len(summary.ToolExecutions) { + idx = unmatchedToolIdx + unmatchedToolIdx++ + } + if idx >= 0 && idx < len(summary.ToolExecutions) { + if summary.ToolExecutions[idx].ToolName == "" { + summary.ToolExecutions[idx].ToolName = toolName + } + if summary.ToolExecutions[idx].ToolCallID == "" { + summary.ToolExecutions[idx].ToolCallID = toolCallID + } + summary.ToolExecutions[idx].ExecutionID = execID + if status != "" { + summary.ToolExecutions[idx].Status = status + } + } else { + summary.ToolExecutions = append(summary.ToolExecutions, ProcessDetailsToolExecution{ + ProcessDetailID: strings.TrimSpace(detailID), + ToolName: toolName, + ToolCallID: toolCallID, + ExecutionID: execID, + Status: status, + }) + } + } + if execID != "" && !seenExecIDs[execID] { + seenExecIDs[execID] = true + summary.MCPExecutionIDs = append(summary.MCPExecutionIDs, execID) + } + } + if err := execRows.Err(); err != nil { + execRows.Close() + return nil, fmt.Errorf("遍历工具执行摘要失败: %w", err) + } + execRows.Close() + rows, err := db.Query( "SELECT data FROM process_details WHERE message_id = ? AND event_type = 'iteration' ORDER BY created_at ASC, rowid ASC", messageID, @@ -1333,6 +1448,36 @@ func (db *DB) GetProcessDetailsPage(messageID string, limit, offset int) ([]Proc return details, total, nil } +// GetProcessDetailOffset 返回某条过程详情在所属消息详情流中的零基 offset。 +func (db *DB) GetProcessDetailOffset(messageID, detailID string) (int, error) { + messageID = strings.TrimSpace(messageID) + detailID = strings.TrimSpace(detailID) + if messageID == "" || detailID == "" { + return 0, fmt.Errorf("messageID and detailID are required") + } + var createdAt string + var rowID int64 + if err := db.QueryRow( + "SELECT created_at, rowid FROM process_details WHERE message_id = ? AND id = ?", + messageID, detailID, + ).Scan(&createdAt, &rowID); err != nil { + if err == sql.ErrNoRows { + return 0, fmt.Errorf("过程详情不存在") + } + return 0, fmt.Errorf("查询过程详情锚点失败: %w", err) + } + var offset int + if err := db.QueryRow( + `SELECT COUNT(*) FROM process_details + WHERE message_id = ? + AND (created_at < ? OR (created_at = ? AND rowid < ?))`, + messageID, createdAt, createdAt, rowID, + ).Scan(&offset); err != nil { + return 0, fmt.Errorf("计算过程详情锚点位置失败: %w", err) + } + return offset, nil +} + // GetProcessDetailsByConversation 获取对话的所有过程详情(按消息分组) func (db *DB) GetProcessDetailsByConversation(conversationID string) (map[string][]ProcessDetail, error) { rows, err := db.Query(