feat(channels): 管理面板消息日志——chat_messages 加 adapter_id + 配置级/平台/总览三粒度端点
后端: - 迁移 0007:chat_messages 加 adapter_id 列 + 索引 - history.Message 加 AdapterID;persistToMain/persistIsolated 传适配器名(a.Name()) - channelbridge: 4 个持久化点 + registerSilenced 传 a;light.go registerSilenced 签名加 a - Store 新增 ListAdapterLog(?adapter=, 不排除主会话)/ListAllLog(adapter_id!='', 总览);ListPlatformLog 保留 - 新端点 GET /api/v1/admin/channels/messages:?adapter= 配置级 / ?platform= 平台 / 无参=总览,historyMessage 加 adapterId - 测试:ListAdapterLog/ListAllLog 过滤用例 + 端点粒度用例 + 桥接持久化断言 前端:渠道管理每配置(账号)「消息日志」按钮 + 页顶「适配器总览日志」入口 + ChannelLogPanel 弹窗(总览/配置级),Miuix 风格,build 绿。
This commit is contained in:
1 parent
c1bd740f7c
commit
2e044d1d19
15 files changed
+560
-41
No files matched your search
@@ -356,6 +356,18 @@ func basePlatform(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// adapterName returns the account-adapter registration name (e.g. "kook:3bef...")
|
||||
// for the adapter that received a message, or "" when a is nil. The single-account
|
||||
// convenience path (process) and a platform with no registered adapter can pass a nil
|
||||
// adapter, so the persist hooks tolerate it and store an empty adapter_id rather than
|
||||
// dereferencing a nil adapter.
|
||||
func adapterName(a adapter.PlatformAdapter) string {
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
return a.Name()
|
||||
}
|
||||
|
||||
// readLoop runs an adapter's blocking read path until the bridge stops or the
|
||||
// adapter's own context is cancelled (a hot reload). The emit sink is wired to
|
||||
// onMessage so poll-style adapters (Telegram getUpdates, weixin long poll)
|
||||
@@ -485,7 +497,7 @@ func (b *Bridge) processFrom(ctx context.Context, msg adapter.UnifiedMessage, a
|
||||
// owner-private chat and owner group @/high-value replies are unchanged.
|
||||
if !actor.IsOwner() {
|
||||
if ok, reason := b.shouldRespond(ctx, msg, actor); !ok {
|
||||
b.registerSilenced(ctx, msg, actor, sess)
|
||||
b.registerSilenced(ctx, msg, actor, sess, a)
|
||||
b.log.Debug("non-owner message silenced (minimal disturbance)",
|
||||
"platform", msg.Platform, "channel", msg.ChannelID,
|
||||
"session", sess.SessionID, "reason", reason)
|
||||
@@ -519,9 +531,9 @@ func (b *Bridge) processFrom(ctx context.Context, msg adapter.UnifiedMessage, a
|
||||
// are broadcast to the Web client; restricted messages land in their isolated
|
||||
// session (the adapter log page) and are NOT broadcast to the main session.
|
||||
if isOwnerPrivate {
|
||||
b.persistToMain(ctx, history.RoleUser, text, platform, deviceIDFor(msg))
|
||||
b.persistToMain(ctx, history.RoleUser, text, platform, deviceIDFor(msg), adapterName(a))
|
||||
} else {
|
||||
b.persistIsolated(ctx, history.RoleUser, text, deviceIDFor(msg), sess, msg.Platform, senderName)
|
||||
b.persistIsolated(ctx, history.RoleUser, text, deviceIDFor(msg), sess, msg.Platform, senderName, adapterName(a))
|
||||
}
|
||||
|
||||
res, err := b.run.Run(loop.WithSessionEnv(ctx, runEnv), sessID, loopMsgs)
|
||||
@@ -539,9 +551,9 @@ func (b *Bridge) processFrom(ctx context.Context, msg adapter.UnifiedMessage, a
|
||||
// Persist the assistant reply (owner-private → main session + broadcast;
|
||||
// restricted → isolated log) then deliver it back to the channel.
|
||||
if isOwnerPrivate {
|
||||
b.persistToMain(ctx, history.RoleAssistant, reply, platform, deviceIDFor(msg))
|
||||
b.persistToMain(ctx, history.RoleAssistant, reply, platform, deviceIDFor(msg), adapterName(a))
|
||||
} else {
|
||||
b.persistIsolated(ctx, history.RoleAssistant, reply, "", sess, msg.Platform, "")
|
||||
b.persistIsolated(ctx, history.RoleAssistant, reply, "", sess, msg.Platform, "", adapterName(a))
|
||||
}
|
||||
if err := b.sendReply(ctx, msg, a, reply); err != nil {
|
||||
if errors.Is(err, adapter.ErrUnsupported) {
|
||||
|
||||
Reference in new issue
Block a user