mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-14 15:10:20 +02:00
优化项目对话、刷新续流、实时滚动与工具状态恢复 (#245)
* feat(chat): add project-based conversation sidebar * feat(chat): refine Codex-style conversation UI * feat(chat): add Codex-style conversation workflow * feat(ui): 优化对话框与项目侧边栏交互 * fix(chat): 修复暗色输入框圆角填色 * fix(chat): 恢复输入区分层错位布局 * fix(hitl): isolate reviewer state per conversation * feat(hitl): 增加双入口审批与倒计时进度 * fix(hitl): 汇总项目审批并隔离对话状态 * fix(ui): 修复审批状态与无项目新任务 * fix(ui): 优化审批状态与对话切换性能 * fix(ui): 修复中断任务审批仍计时 * fix(ui): 修复多对话并发切换卡顿 * fix(hitl): 主动同步审批并关闭中断状态 * fix(ui): 固定项目审批汇总为绿色 * feat(ui): 同步系统模型与推理强度 * feat(ui): 完善项目侧栏预览与新建入口 * fix(ui): 防止无项目文件夹误展开 * fix(ui): 防止长历史对话滚动误触审批 * fix(ui): 修复对话操作并补充项目置顶 * fix(ui): 移除对话分组并调整项目置顶排序 * feat(ui): 优化迭代导航与审批交互 * fix(hitl): 将 write_file 加入内置免审批工具 * fix(chat): 支持回车发送与 Shift 回车换行 * fix(chat): 优化对话刷新与 Codex 风格交互 * fix(ui): 显示对话具体更新时间 * fix(ui): 优化对话刷新与项目加载 * fix(ui): 修复 Agent 审查文字裁切 * fix(chat): 修复刷新续流与多标签页同步 * fix(chat): 修复滚动跟随与中断任务终态 * fix(ui): 修复流式滚动跳动与工具状态恢复 * fix(ui): 修复刷新后流式输出停止粘底
This commit is contained in:
@@ -1353,6 +1353,39 @@ func (db *DB) AddProcessDetailWithID(messageID, conversationID, eventType, messa
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// UpdateProcessDetailContent 更新流式聚合详情的正文与元数据。使用固定记录 ID,
|
||||
// 避免每个 token 新增一行,同时让页面刷新能读取到尚未结束的规划输出。
|
||||
func (db *DB) UpdateProcessDetailContent(id, message string, data interface{}) error {
|
||||
var dataJSON string
|
||||
if data != nil {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化过程详情数据失败: %w", err)
|
||||
}
|
||||
dataJSON = string(jsonData)
|
||||
}
|
||||
result, err := db.Exec(
|
||||
"UPDATE process_details SET message = ?, data = ? WHERE id = ?",
|
||||
message, dataJSON, strings.TrimSpace(id),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新过程详情失败: %w", err)
|
||||
}
|
||||
if affected, affectedErr := result.RowsAffected(); affectedErr == nil && affected == 0 {
|
||||
return fmt.Errorf("过程详情不存在: %s", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProcessDetail 删除被判定为工具结果回显的临时规划记录。
|
||||
func (db *DB) DeleteProcessDetail(id string) error {
|
||||
_, err := db.Exec("DELETE FROM process_details WHERE id = ?", strings.TrimSpace(id))
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除过程详情失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProcessDetails 获取消息的过程详情
|
||||
func (db *DB) GetProcessDetails(messageID string) ([]ProcessDetail, error) {
|
||||
rows, err := db.Query(
|
||||
@@ -1420,6 +1453,10 @@ type ProcessDetailsSummary struct {
|
||||
ToolCount int `json:"toolCount"`
|
||||
ToolExecutions []ProcessDetailsToolExecution `json:"toolExecutions,omitempty"`
|
||||
MCPExecutionIDs []string `json:"mcpExecutionIds,omitempty"`
|
||||
StartedAt *time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt *time.Time `json:"completedAt,omitempty"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessDetailsToolExecution struct {
|
||||
@@ -1442,6 +1479,54 @@ func (db *DB) GetProcessDetailsSummary(messageID string) (*ProcessDetailsSummary
|
||||
}
|
||||
|
||||
summary := &ProcessDetailsSummary{Total: total}
|
||||
var messageCreatedAt, messageUpdatedAt sql.NullString
|
||||
var messageContent string
|
||||
if err := db.QueryRow(
|
||||
"SELECT created_at, updated_at, content FROM messages WHERE id = ?",
|
||||
messageID,
|
||||
).Scan(&messageCreatedAt, &messageUpdatedAt, &messageContent); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("查询过程详情耗时失败: %w", err)
|
||||
}
|
||||
if messageCreatedAt.Valid {
|
||||
if startedAt := parseDBTime(messageCreatedAt.String); !startedAt.IsZero() {
|
||||
summary.StartedAt = &startedAt
|
||||
}
|
||||
}
|
||||
var terminalEvent, terminalCreatedAt string
|
||||
terminalErr := db.QueryRow(`
|
||||
SELECT event_type, created_at
|
||||
FROM process_details
|
||||
WHERE message_id = ? AND event_type IN ('cancelled', 'timeout', 'error')
|
||||
ORDER BY created_at DESC, rowid DESC
|
||||
LIMIT 1`, messageID).Scan(&terminalEvent, &terminalCreatedAt)
|
||||
if terminalErr != nil && !errors.Is(terminalErr, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("查询过程详情终态失败: %w", terminalErr)
|
||||
}
|
||||
if terminalEvent != "" {
|
||||
switch terminalEvent {
|
||||
case "cancelled":
|
||||
summary.Status = "cancelled"
|
||||
case "timeout":
|
||||
summary.Status = "timeout"
|
||||
default:
|
||||
summary.Status = "failed"
|
||||
}
|
||||
if completedAt := parseDBTime(terminalCreatedAt); !completedAt.IsZero() {
|
||||
summary.CompletedAt = &completedAt
|
||||
}
|
||||
} else if strings.TrimSpace(messageContent) == "处理中..." || strings.TrimSpace(messageContent) == "Processing..." {
|
||||
summary.Status = "running"
|
||||
} else {
|
||||
summary.Status = "completed"
|
||||
if messageUpdatedAt.Valid {
|
||||
if completedAt := parseDBTime(messageUpdatedAt.String); !completedAt.IsZero() {
|
||||
summary.CompletedAt = &completedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
if summary.StartedAt != nil && summary.CompletedAt != nil && !summary.CompletedAt.Before(*summary.StartedAt) {
|
||||
summary.DurationMs = summary.CompletedAt.Sub(*summary.StartedAt).Milliseconds()
|
||||
}
|
||||
if total == 0 {
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user