mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 04:16:06 +00:00
feat(ui): add agent and channel dropdown filters to traces page
This commit is contained in:
1 parent
050de16aa2
commit
225bd3d3b6
8 files changed
+73
-31
No files matched your search
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
},
|
||||
"emptyDescription": "暂无追踪记录。当Agent处理请求时将自动记录追踪。",
|
||||
"emptyTitle": "暂无追踪",
|
||||
"allAgents": "所有Agent",
|
||||
"allChannels": "所有渠道",
|
||||
"filter": "筛选",
|
||||
"filterPlaceholder": "按Agent ID 筛选...",
|
||||
"span": {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<string>();
|
||||
const [channelFilter, setChannelFilter] = useState<string>();
|
||||
const [selectedTraceId, setSelectedTraceId] = useState<string | null>(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 (
|
||||
<div className="p-4 sm:p-6">
|
||||
<PageHeader
|
||||
@@ -51,20 +57,39 @@ export function TracesPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<form onSubmit={handleFilterSubmit} className="mt-4 flex gap-2">
|
||||
<div className="relative max-w-sm flex-1">
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
value={agentFilter}
|
||||
onChange={(e) => setAgentFilter(e.target.value)}
|
||||
placeholder={t("filterPlaceholder")}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="outline" size="sm">
|
||||
{t("filter")}
|
||||
</Button>
|
||||
</form>
|
||||
<div className="mt-4 flex flex-wrap items-center gap-2">
|
||||
{/* Agent filter */}
|
||||
<Select
|
||||
value={agentFilter ?? "__all__"}
|
||||
onValueChange={(v) => { setAgentFilter(v === "__all__" ? undefined : v); setPage(1); }}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-44 text-xs">
|
||||
<SelectValue placeholder={t("allAgents")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">{t("allAgents")}</SelectItem>
|
||||
{agents.map((a) => (
|
||||
<SelectItem key={a.id} value={a.id}>{a.display_name || a.agent_key || a.id}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* Channel filter */}
|
||||
<Select
|
||||
value={channelFilter ?? "__all__"}
|
||||
onValueChange={(v) => { setChannelFilter(v === "__all__" ? undefined : v); setPage(1); }}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-44 text-xs">
|
||||
<SelectValue placeholder={t("allChannels")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">{t("allChannels")}</SelectItem>
|
||||
{channels.map((ch) => (
|
||||
<SelectItem key={ch.id} value={ch.name}>{ch.display_name || ch.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
{showSkeleton ? (
|
||||
|
||||
Reference in new issue
Block a user