mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-11 12:18:59 +00:00
feat(ui): add stop button for running traces (#481)
* feat(ui): add stop button for running traces on traces page Allows admins to abort running agent runs directly from the traces page, useful for stopping channel-originated runs (Telegram, Discord, etc.) without needing access to the chat page. * style(ui): match stop button style to chat destructive button * fix(ui): fix abort response handling and variable declaration order - Check aborted field from chat.abort response for accurate feedback - Move useTraces() before handleAbortRun to fix block-scoped variable error - Add abortNotFound i18n key for when run already finished * fix(ui): remove invalid pending status check, add cursor-pointer
This commit is contained in:
1 parent
4d31fe0dda
commit
709d86c701
5 files changed
+77
-12
No files matched your search
@@ -16,6 +16,7 @@
|
||||
"time": "Time"
|
||||
},
|
||||
"unnamed": "Unnamed",
|
||||
"stopRun": "Stop run",
|
||||
"cached": "cached",
|
||||
"detail": {
|
||||
"title": "Trace Detail",
|
||||
@@ -36,7 +37,8 @@
|
||||
"spansCount": "Spans ({{count}})",
|
||||
"llmCalls": "LLM",
|
||||
"toolCalls": "tool",
|
||||
"export": "Export trace"
|
||||
"export": "Export trace",
|
||||
"stopRun": "Stop run"
|
||||
},
|
||||
"span": {
|
||||
"model": "Model:",
|
||||
@@ -53,6 +55,9 @@
|
||||
"cached": "cached"
|
||||
},
|
||||
"toast": {
|
||||
"failedLoad": "Failed to load traces"
|
||||
"failedLoad": "Failed to load traces",
|
||||
"abortSent": "Run stopped",
|
||||
"abortFailed": "Failed to stop run",
|
||||
"abortNotFound": "Run already finished or not found"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
"time": "Thời gian"
|
||||
},
|
||||
"unnamed": "Không tên",
|
||||
"stopRun": "Dừng chạy",
|
||||
"cached": "đã cache",
|
||||
"detail": {
|
||||
"title": "Chi tiết theo dõi",
|
||||
@@ -36,7 +37,8 @@
|
||||
"spansCount": "Khoảng ({{count}})",
|
||||
"llmCalls": "LLM",
|
||||
"toolCalls": "công cụ",
|
||||
"export": "Xuất trace"
|
||||
"export": "Xuất trace",
|
||||
"stopRun": "Dừng chạy"
|
||||
},
|
||||
"span": {
|
||||
"model": "Model:",
|
||||
@@ -53,6 +55,9 @@
|
||||
"cached": "đã cache"
|
||||
},
|
||||
"toast": {
|
||||
"failedLoad": "Không thể tải dữ liệu theo dõi"
|
||||
"failedLoad": "Không thể tải dữ liệu theo dõi",
|
||||
"abortSent": "Đã dừng chạy",
|
||||
"abortFailed": "Không thể dừng chạy",
|
||||
"abortNotFound": "Lệnh đã hoàn tất hoặc không tìm thấy"
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,8 @@
|
||||
"title": "追踪详情",
|
||||
"tokens": "令牌:",
|
||||
"toolCalls": "工具",
|
||||
"export": "导出追踪"
|
||||
"export": "导出追踪",
|
||||
"stopRun": "停止运行"
|
||||
},
|
||||
"emptyDescription": "暂无追踪记录。当Agent处理请求时将自动记录追踪。",
|
||||
"emptyTitle": "暂无追踪",
|
||||
@@ -51,7 +52,11 @@
|
||||
},
|
||||
"title": "追踪",
|
||||
"toast": {
|
||||
"failedLoad": "加载追踪失败"
|
||||
"failedLoad": "加载追踪失败",
|
||||
"abortSent": "已停止运行",
|
||||
"abortFailed": "停止运行失败",
|
||||
"abortNotFound": "运行已完成或未找到"
|
||||
},
|
||||
"unnamed": "未命名"
|
||||
"unnamed": "未命名",
|
||||
"stopRun": "停止运行"
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ChevronRight, ChevronDown, Copy, Check, Download, CircleCheck, CircleX, Loader, CircleMinus } from "lucide-react";
|
||||
import { ChevronRight, ChevronDown, Copy, Check, Download, CircleCheck, CircleX, Loader, CircleMinus, Square } from "lucide-react";
|
||||
import { useClipboard } from "@/hooks/use-clipboard";
|
||||
import { useHttp } from "@/hooks/use-ws";
|
||||
import { useWsEvent } from "@/hooks/use-ws-event";
|
||||
@@ -53,9 +53,10 @@ interface TraceDetailDialogProps {
|
||||
onClose: () => void;
|
||||
getTrace: (id: string) => Promise<{ trace: TraceData; spans: SpanData[] } | null>;
|
||||
onNavigateTrace?: (traceId: string) => void;
|
||||
onAbortRun?: (trace: TraceData, e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export function TraceDetailDialog({ traceId, onClose, getTrace, onNavigateTrace }: TraceDetailDialogProps) {
|
||||
export function TraceDetailDialog({ traceId, onClose, getTrace, onNavigateTrace, onAbortRun }: TraceDetailDialogProps) {
|
||||
const { t } = useTranslation("traces");
|
||||
const tz = useUiStore((s) => s.timezone);
|
||||
const http = useHttp();
|
||||
@@ -160,6 +161,16 @@ export function TraceDetailDialog({ traceId, onClose, getTrace, onNavigateTrace
|
||||
)}
|
||||
{t("detail.export")}
|
||||
</button>
|
||||
{trace && (trace.status === "running") && onAbortRun && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => onAbortRun(trace, e)}
|
||||
className="flex cursor-pointer items-center gap-1 rounded-md bg-destructive px-2 py-1 text-xs text-destructive-foreground transition-colors hover:bg-destructive/90"
|
||||
>
|
||||
<Square className="h-3.5 w-3.5" />
|
||||
{t("detail.stopRun")}
|
||||
</button>
|
||||
)}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Activity, GitFork, RefreshCw } from "lucide-react";
|
||||
import { Activity, GitFork, RefreshCw, Square } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
@@ -22,6 +22,9 @@ import { useDeferredLoading } from "@/hooks/use-deferred-loading";
|
||||
import { useUiStore } from "@/stores/use-ui-store";
|
||||
import { useAgents } from "@/pages/agents/hooks/use-agents";
|
||||
import { useChannelInstances } from "@/pages/channels/hooks/use-channel-instances";
|
||||
import { useWs } from "@/hooks/use-ws";
|
||||
import { Methods } from "@/api/protocol";
|
||||
import { toast } from "@/stores/use-toast-store";
|
||||
|
||||
export function TracesPage() {
|
||||
const { t } = useTranslation("traces");
|
||||
@@ -33,6 +36,7 @@ export function TracesPage() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
|
||||
const ws = useWs();
|
||||
const { agents } = useAgents();
|
||||
const { instances: channels } = useChannelInstances();
|
||||
|
||||
@@ -42,6 +46,28 @@ export function TracesPage() {
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
});
|
||||
|
||||
const handleAbortRun = useCallback(
|
||||
async (trace: TraceData, e: React.MouseEvent) => {
|
||||
e.stopPropagation(); // Don't open trace detail
|
||||
if (!ws.isConnected) return;
|
||||
try {
|
||||
const res = await ws.call(Methods.CHAT_ABORT, {
|
||||
sessionKey: trace.session_key,
|
||||
runId: trace.run_id,
|
||||
}) as { aborted?: boolean };
|
||||
if (res?.aborted) {
|
||||
toast.success(t("toast.abortSent"));
|
||||
refresh();
|
||||
} else {
|
||||
toast.info(t("toast.abortNotFound"));
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("toast.abortFailed"));
|
||||
}
|
||||
},
|
||||
[ws, t, refresh],
|
||||
);
|
||||
const spinning = useMinLoading(fetching);
|
||||
const showSkeleton = useDeferredLoading(loading && traces.length === 0);
|
||||
|
||||
@@ -134,7 +160,19 @@ export function TracesPage() {
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<StatusBadge status={trace.status} />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<StatusBadge status={trace.status} />
|
||||
{(trace.status === "running") && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="icon-xs"
|
||||
onClick={(e) => handleAbortRun(trace, e)}
|
||||
title={t("stopRun")}
|
||||
>
|
||||
<Square className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
{formatDuration(trace.duration_ms || computeDurationMs(trace.start_time, trace.end_time))}
|
||||
@@ -175,6 +213,7 @@ export function TracesPage() {
|
||||
onClose={() => setSelectedTraceId(null)}
|
||||
getTrace={getTrace}
|
||||
onNavigateTrace={setSelectedTraceId}
|
||||
onAbortRun={handleAbortRun}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user