feat: 独立对话(ChatAgent)也支持文件上传(方式1:落盘 + Read/Bash 读取)

补齐上次只做了任务主 Agent 的遗留:独立对话面此前只共用了 transcript 渲染,
后端注入 + Composer 上传缺失,现补上。

- 后端(pgSendConversationMessage):收 attachments;用 userActivityWithAttachments
  存 {text,attachments} JSON 供卡片渲染(无附件仍存全文 Detail 供懒加载);用
  composeAgentMessage 把【绝对路径】清单拼进发给 ChatAgent 的消息,baseDir=
  <workDir>/sessions/conv-<id>/,与 chatUpload 落盘、chat.go 的 sessionWorkDir 对齐;
  纯附件无文本也可发,首条会话名兜底"附件消息"。
- 前端(chat/page.tsx):Composer 加回形针上传按钮 + 附件 chip 预览(可移除);
  ChatView 上传到 scope=session&id=conv-<id> 并带附件发送;DraftChat 草稿态没有
  会话 id,点回形针先懒创建会话再上传,交接文本+附件给 ChatView 发送。
- api.sendConversationMessage 增加 attachments 参数。
This commit is contained in:
Autumn-27 committed 2026-08-18 07:55:18 -04:00
1 parent 071414063e
commit 683f5ee684
3 files changed
+162 -17

No files matched your search

+26 -5
View File
@@ -6,6 +6,7 @@ import (
"log"
"math"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
@@ -254,13 +255,16 @@ func (s *Server) pgSendConversationMessage(w http.ResponseWriter, r *http.Reques
if !ok {
return
}
var req struct{ Message string }
var req struct {
Message string `json:"message"`
Attachments []chatAttachment `json:"attachments,omitempty"` // 方式1 上传的文件(路径相对会话工作目录)
}
if err := decode(r, &req); err != nil {
writeErr(w, 400, err.Error())
return
}
msg := strings.TrimSpace(req.Message)
if msg == "" {
if msg == "" && len(req.Attachments) == 0 {
writeErr(w, 400, "消息不能为空")
return
}
@@ -283,15 +287,32 @@ func (s *Server) pgSendConversationMessage(w http.ResponseWriter, r *http.Reques
// persist + float the human turn; auto-title the thread from the first message.
// Worker is the agent key (not "user") so the transcript stays a single lane
// (no worker chips) — the kind='user' already right-aligns it as a human bubble.
if _, err := pg.AppendConvActivity(c.ID, db.Activity{Worker: c.AgentKey, Kind: "user", Summary: firstLine(msg, 200), Detail: msg}); err != nil {
// With attachments, Detail carries {text, attachments} JSON so the transcript
// renders attachment cards; without, Detail is the full text (Summary is truncated,
// so the transcript lazy-loads Detail to show the message untruncated).
ua := userActivityWithAttachments(c.AgentKey, msg, req.Attachments)
ua.Summary = firstLine(msg, 200)
if ua.Detail == "" {
ua.Detail = msg
}
if _, err := pg.AppendConvActivity(c.ID, ua); err != nil {
log.Printf("[conv %d] append user msg failed: %v", c.ID, err)
}
if c.Title == "" || c.Title == "新对话" {
_ = pg.RenameConversation(c.ID, firstLine(msg, 40))
title := firstLine(msg, 40)
if title == "" {
title = "附件消息"
}
_ = pg.RenameConversation(c.ID, title)
}
_ = pg.TouchConversation(c.ID)
s.runConversation(c, msg, busyKey)
// Append the uploaded files' ABSOLUTE paths so the ChatAgent opens them with its
// Read/Bash tools. baseDir = the agent's per-session CWD (<workDir>/sessions/
// conv-<id>/), matching chatUpload's landing dir and agent/chat.go's sessionWorkDir
// — busyKey == convBusyKey(c.ID) == "conv-<id>" == that session id.
baseDir := filepath.Join(s.m.dir, "sessions", busyKey)
s.runConversation(c, composeAgentMessage(msg, req.Attachments, baseDir), busyKey)
writeJSON(w, 202, map[string]any{"status": "accepted"})
}