feat(desktop): add channel management, paired devices, and multiple fixes

Channel Management:
- Add Channels settings tab with full CRUD for Telegram/Discord (Lite: max 1 each)
- Channel detail panel with tabs: General, Credentials, Managers
- Advanced settings dialog (network, limits, streaming, behavior, access control)
- Schema-driven field renderer with Combobox selects, Switch toggles
- Paired Devices section: approve/deny pending, revoke paired, WS event auto-refresh
- Pairing notification badge in sidebar footer with pending count
- i18n support (en/vi/zh) for all channel strings

Bug Fixes:
- Fix Vietnamese slug generation (NFD normalize + đ/Đ handling) in lib/slug.ts
- Make agent key field editable instead of read-only
- Fix trace detail input/output: use MarkdownRenderer with copy button instead of forced-dark CodePreview
- Fix API error parsing to handle both {error: "string"} and {error: {message}} formats
- Add Accept-Language header to desktop API client for i18n error messages
- Wrap raw err.Error() with i18n messages in HTTP handlers (agents, channel_instances)
- Increase SQLite MaxOpenConns from 2 to 4 to reduce SQLITE_BUSY contention
- Add retryOnBusy wrapper for context file seeding writes
- Update EditionCompareModal: channels moved from "false" to "1 Telegram + 1 Discord"
This commit is contained in:
viettranx committed 2026-03-27 19:52:28 +07:00
1 parent 2bc0f5f14a
commit 6ea9b4d762
40 files changed
+2346 -64

No files matched your search

+2 -1
View File
@@ -190,7 +190,8 @@ func (h *AgentsHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "23505") {
writeJSON(w, http.StatusConflict, map[string]string{"error": i18n.T(locale, i18n.MsgAlreadyExists, "agent", req.AgentKey)})
} else {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
slog.Error("agents.create", "agent_key", req.AgentKey, "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToCreate, "agent", "internal error")})
}
return
}
+3 -3
View File
@@ -168,7 +168,7 @@ func (h *ChannelInstancesHandler) handleCreate(w http.ResponseWriter, r *http.Re
if err := h.store.Create(r.Context(), inst); err != nil {
slog.Error("channel_instances.create", "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToCreate, "channel instance", "internal error")})
return
}
@@ -213,7 +213,7 @@ func (h *ChannelInstancesHandler) handleUpdate(w http.ResponseWriter, r *http.Re
if err := h.store.Update(r.Context(), id, updates); err != nil {
slog.Error("channel_instances.update", "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToUpdate, "channel instance", "internal error")})
return
}
@@ -243,7 +243,7 @@ func (h *ChannelInstancesHandler) handleDelete(w http.ResponseWriter, r *http.Re
if err := h.store.Delete(r.Context(), id); err != nil {
slog.Error("channel_instances.delete", "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToDelete, "channel instance", "internal error")})
return
}