diff --git a/cmd/gui/renderer/app.js b/cmd/gui/renderer/app.js
index 4e6a2ac..38011b0 100644
--- a/cmd/gui/renderer/app.js
+++ b/cmd/gui/renderer/app.js
@@ -371,6 +371,9 @@ function renderChat() {
});
}
var body = rc + tcs + '
' + c + '
';
+ if (m.source && m.source !== 'webui') {
+ body = '' + escHtml(__('通道','Channel')) + ': ' + escHtml(m.source) + '
' + body;
+ }
if (role === 'system') {
html += '';
} else {
diff --git a/cmd/gui/renderer/style.css b/cmd/gui/renderer/style.css
index 184bd38..fda4088 100644
--- a/cmd/gui/renderer/style.css
+++ b/cmd/gui/renderer/style.css
@@ -176,6 +176,7 @@ code { font-family:monospace; font-size:12px; color:var(--pre-color) }
.msg-system .msg-avatar { background:var(--msg-system-bg); color:var(--msg-system-color) }
.msg-content { min-width:0 }
.msg-bubble { padding:8px 12px; border-radius:10px; font-size:13px; line-height:1.5; word-break:break-word; position:relative }
+.msg-source { font-size:11px; opacity:.55; margin-bottom:4px; color:inherit }
.msg-user .msg-bubble { background:var(--msg-user-bg); color:var(--msg-user-color); border-bottom-right-radius:4px }
.msg-assistant .msg-bubble { background:var(--msg-assistant-bg); color:var(--msg-assistant-color); border-bottom-left-radius:4px }
.msg-system .msg-bubble { background:var(--msg-system-bg); color:var(--msg-system-color); text-align:center; font-size:12px }
diff --git a/internal/plugins/webui/handler.go b/internal/plugins/webui/handler.go
index 6ea8f48..17a8b93 100644
--- a/internal/plugins/webui/handler.go
+++ b/internal/plugins/webui/handler.go
@@ -65,6 +65,7 @@ type ChatMsg struct {
Role string `json:"role"`
Content string `json:"content"`
ReasoningContent string `json:"reasoning_content,omitempty"`
+ Source string `json:"source,omitempty"`
Time string `json:"time"`
}
@@ -136,6 +137,7 @@ func NewHandler(s *sdk.PluginSDK) *Handler {
h.loadChatHistory()
if s != nil {
go h.trackToolEvents()
+ h.subscribeChatEvents()
}
return h
}
@@ -170,6 +172,40 @@ func (h *Handler) trackToolEvents() {
})
}
+// subscribeChatEvents 捕获所有通道(cli/qq/webui 等)的对话轮次,
+// 与 handleChat 的注入一起构成完整的全通道对话历史。
+func (h *Handler) subscribeChatEvents() {
+ if h.sdk == nil {
+ return
+ }
+ h.sdk.Subscribe(sdk.EventRawInput, func(ev *sdk.Event) {
+ content, _ := ev.Payload["content"].(string)
+ source, _ := ev.Payload["source"].(string)
+ if content == "" {
+ return
+ }
+ h.addChatMsg(ChatMsg{
+ Role: "user",
+ Content: content,
+ Source: source,
+ Time: time.Unix(ev.Timestamp, 0).Format(time.RFC3339),
+ })
+ })
+ h.sdk.Subscribe(sdk.EventAgentOutput, func(ev *sdk.Event) {
+ content, _ := ev.Payload["content"].(string)
+ channel, _ := ev.Payload["channel"].(string)
+ if content == "" {
+ return
+ }
+ h.addChatMsg(ChatMsg{
+ Role: "assistant",
+ Content: content,
+ Source: channel,
+ Time: time.Unix(ev.Timestamp, 0).Format(time.RFC3339),
+ })
+ })
+}
+
func (h *Handler) handleToolEvent(ev *sdk.Event) {
payload := ev.Payload
tool, _ := payload["tool"].(string)
@@ -942,12 +978,11 @@ func (h *Handler) handleChat(w http.ResponseWriter, r *http.Request) {
return
}
- h.addChatMsg(ChatMsg{Role: "user", Content: body.Message, Time: time.Now().Format(time.RFC3339)})
if h.sdk == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "agent unavailable"})
return
}
- resp := h.sdk.InjectTextSync("cli", "cli", body.Message)
+ resp := h.sdk.InjectTextSync("webui", "webui", body.Message)
if resp == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "agent unavailable"})
return
@@ -960,7 +995,6 @@ func (h *Handler) handleChat(w http.ResponseWriter, r *http.Request) {
if reasoning != "" {
result["reasoning_content"] = reasoning
}
- h.addChatMsg(ChatMsg{Role: "assistant", Content: content, ReasoningContent: reasoning, Time: time.Now().Format(time.RFC3339)})
writeJSON(w, http.StatusOK, result)
}