From 225bd3d3b632f1aae1fb7b3e003d3ecf47b1e26c Mon Sep 17 00:00:00 2001 From: viettranx Date: Thu, 12 Mar 2026 10:38:20 +0700 Subject: [PATCH] feat(ui): add agent and channel dropdown filters to traces page --- internal/http/traces.go | 3 + internal/store/pg/tracing.go | 5 ++ internal/store/tracing_store.go | 13 ++-- ui/web/src/i18n/locales/en/traces.json | 2 + ui/web/src/i18n/locales/vi/traces.json | 2 + ui/web/src/i18n/locales/zh/traces.json | 2 + ui/web/src/pages/traces/hooks/use-traces.ts | 2 + ui/web/src/pages/traces/traces-page.tsx | 75 ++++++++++++++------- 8 files changed, 73 insertions(+), 31 deletions(-) diff --git a/internal/http/traces.go b/internal/http/traces.go index a8707d5c..ce65fff9 100644 --- a/internal/http/traces.go +++ b/internal/http/traces.go @@ -71,6 +71,9 @@ func (h *TracesHandler) handleList(w http.ResponseWriter, r *http.Request) { if v := r.URL.Query().Get("status"); v != "" { opts.Status = v } + if v := r.URL.Query().Get("channel"); v != "" { + opts.Channel = v + } if v := r.URL.Query().Get("limit"); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 { opts.Limit = n diff --git a/internal/store/pg/tracing.go b/internal/store/pg/tracing.go index 05ac9075..2fa06a3f 100644 --- a/internal/store/pg/tracing.go +++ b/internal/store/pg/tracing.go @@ -115,6 +115,11 @@ func buildTraceWhere(opts store.TraceListOpts) (string, []any) { args = append(args, opts.Status) argIdx++ } + if opts.Channel != "" { + conditions = append(conditions, fmt.Sprintf("channel = $%d", argIdx)) + args = append(args, opts.Channel) + argIdx++ + } where := "" if len(conditions) > 0 { diff --git a/internal/store/tracing_store.go b/internal/store/tracing_store.go index 636e70c3..1dd800f6 100644 --- a/internal/store/tracing_store.go +++ b/internal/store/tracing_store.go @@ -96,12 +96,13 @@ type SpanData struct { // TraceListOpts configures trace listing. type TraceListOpts struct { - AgentID *uuid.UUID - UserID string - SessionKey string - Status string - Limit int - Offset int + AgentID *uuid.UUID + UserID string + SessionKey string + Status string + Channel string + Limit int + Offset int } // CostSummaryOpts configures cost aggregation queries. diff --git a/ui/web/src/i18n/locales/en/traces.json b/ui/web/src/i18n/locales/en/traces.json index 98a7834c..6db86b81 100644 --- a/ui/web/src/i18n/locales/en/traces.json +++ b/ui/web/src/i18n/locales/en/traces.json @@ -3,6 +3,8 @@ "description": "LLM call traces and performance data", "filterPlaceholder": "Filter by agent ID...", "filter": "Filter", + "allAgents": "All agents", + "allChannels": "All channels", "emptyTitle": "No traces", "emptyDescription": "No traces found. Traces are recorded when agents process requests.", "columns": { diff --git a/ui/web/src/i18n/locales/vi/traces.json b/ui/web/src/i18n/locales/vi/traces.json index c7ca86c8..b843fc56 100644 --- a/ui/web/src/i18n/locales/vi/traces.json +++ b/ui/web/src/i18n/locales/vi/traces.json @@ -3,6 +3,8 @@ "description": "Theo dõi lệnh gọi LLM và dữ liệu hiệu suất", "filterPlaceholder": "Lọc theo agent ID...", "filter": "Lọc", + "allAgents": "Tất cả agent", + "allChannels": "Tất cả kênh", "emptyTitle": "Không có dữ liệu theo dõi", "emptyDescription": "Không tìm thấy dữ liệu theo dõi. Theo dõi được ghi lại khi agent xử lý yêu cầu.", "columns": { diff --git a/ui/web/src/i18n/locales/zh/traces.json b/ui/web/src/i18n/locales/zh/traces.json index 773e97a1..a9635f26 100644 --- a/ui/web/src/i18n/locales/zh/traces.json +++ b/ui/web/src/i18n/locales/zh/traces.json @@ -30,6 +30,8 @@ }, "emptyDescription": "暂无追踪记录。当Agent处理请求时将自动记录追踪。", "emptyTitle": "暂无追踪", + "allAgents": "所有Agent", + "allChannels": "所有渠道", "filter": "筛选", "filterPlaceholder": "按Agent ID 筛选...", "span": { diff --git a/ui/web/src/pages/traces/hooks/use-traces.ts b/ui/web/src/pages/traces/hooks/use-traces.ts index c170444e..d7914d29 100644 --- a/ui/web/src/pages/traces/hooks/use-traces.ts +++ b/ui/web/src/pages/traces/hooks/use-traces.ts @@ -10,6 +10,7 @@ export interface TraceFilters { agentId?: string; userId?: string; status?: string; + channel?: string; limit?: number; offset?: number; } @@ -27,6 +28,7 @@ export function useTraces(filters: TraceFilters = {}) { if (filters.agentId) params.agent_id = filters.agentId; if (filters.userId) params.user_id = filters.userId; if (filters.status) params.status = filters.status; + if (filters.channel) params.channel = filters.channel; if (filters.limit) params.limit = String(filters.limit); if (filters.offset !== undefined) params.offset = String(filters.offset); diff --git a/ui/web/src/pages/traces/traces-page.tsx b/ui/web/src/pages/traces/traces-page.tsx index 9cc1a73a..591b4189 100644 --- a/ui/web/src/pages/traces/traces-page.tsx +++ b/ui/web/src/pages/traces/traces-page.tsx @@ -1,9 +1,15 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Activity, GitFork, RefreshCw, Search } from "lucide-react"; +import { Activity, GitFork, RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { PageHeader } from "@/components/shared/page-header"; import { EmptyState } from "@/components/shared/empty-state"; import { Pagination } from "@/components/shared/pagination"; @@ -13,18 +19,24 @@ import { useTraces, type TraceData } from "./hooks/use-traces"; import { TraceDetailDialog } from "./trace-detail-dialog"; import { useMinLoading } from "@/hooks/use-min-loading"; import { useDeferredLoading } from "@/hooks/use-deferred-loading"; +import { useAgents } from "@/pages/agents/hooks/use-agents"; +import { useChannelInstances } from "@/pages/channels/hooks/use-channel-instances"; export function TracesPage() { const { t } = useTranslation("traces"); const { t: tc } = useTranslation("common"); - const [agentFilter, setAgentFilter] = useState(""); - const [appliedAgentFilter, setAppliedAgentFilter] = useState(""); + const [agentFilter, setAgentFilter] = useState(); + const [channelFilter, setChannelFilter] = useState(); const [selectedTraceId, setSelectedTraceId] = useState(null); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); + const { agents } = useAgents(); + const { instances: channels } = useChannelInstances(); + const { traces, total, loading, fetching, refresh, getTrace } = useTraces({ - agentId: appliedAgentFilter || undefined, + agentId: agentFilter, + channel: channelFilter, limit: pageSize, offset: (page - 1) * pageSize, }); @@ -33,12 +45,6 @@ export function TracesPage() { const totalPages = Math.max(1, Math.ceil(total / pageSize)); - const handleFilterSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setAppliedAgentFilter(agentFilter); - setPage(1); - }; - return (
-
-
- - setAgentFilter(e.target.value)} - placeholder={t("filterPlaceholder")} - className="pl-9" - /> -
- -
+
+ {/* Agent filter */} + + + {/* Channel filter */} + +
{showSkeleton ? (