backend/frontend/runtime: 训练面板 / session 隔离 / agent runtime 调整
- backend/api/server.py:训练 step-detail / pipeline 卡片排序逻辑 - frontend:thread-list / thread / training-pipeline-panel / step-detail-sheet 配套调整,新增 metrics-chart-panel - src/agent_*:tool spec / runtime / prompting 配套 - .gitignore 加 .logs/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,12 @@
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
FileIcon,
|
||||
Maximize2Icon,
|
||||
Minimize2Icon,
|
||||
RefreshCwIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -52,6 +54,7 @@ type StepDetail = {
|
||||
type Props = {
|
||||
sessionId: string | null;
|
||||
stepKey: string | null;
|
||||
runId?: string | null;
|
||||
stepTitle?: string;
|
||||
stepStatus?: string;
|
||||
open: boolean;
|
||||
@@ -61,6 +64,7 @@ type Props = {
|
||||
export function StepDetailSheet({
|
||||
sessionId,
|
||||
stepKey,
|
||||
runId,
|
||||
stepTitle,
|
||||
stepStatus,
|
||||
open,
|
||||
@@ -69,13 +73,41 @@ export function StepDetailSheet({
|
||||
const [detail, setDetail] = useState<StepDetail | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [reloadCount, setReloadCount] = useState(0);
|
||||
const [width, setWidth] = useState(672);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const dragRef = useRef<{ startX: number; startW: number } | null>(null);
|
||||
|
||||
const onResizePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (isFullscreen) return;
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
dragRef.current = { startX: e.clientX, startW: width };
|
||||
};
|
||||
const onResizePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragRef.current) return;
|
||||
const dx = dragRef.current.startX - e.clientX;
|
||||
const max =
|
||||
typeof window !== "undefined"
|
||||
? Math.max(360, window.innerWidth - 32)
|
||||
: 1200;
|
||||
setWidth(Math.max(360, Math.min(max, dragRef.current.startW + dx)));
|
||||
};
|
||||
const onResizePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragRef.current) return;
|
||||
dragRef.current = null;
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !sessionId || !stepKey) return;
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setDetail(null);
|
||||
const url = `/api/claw/training/step-detail?session_id=${encodeURIComponent(sessionId)}&step=${encodeURIComponent(stepKey)}`;
|
||||
const params = new URLSearchParams({
|
||||
session_id: sessionId,
|
||||
step: stepKey,
|
||||
});
|
||||
if (runId) params.set("run_id", runId);
|
||||
const url = `/api/claw/training/step-detail?${params.toString()}`;
|
||||
fetch(url, { cache: "no-store" })
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((payload) => {
|
||||
@@ -89,15 +121,39 @@ export function StepDetailSheet({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, sessionId, stepKey, reloadCount]);
|
||||
}, [open, sessionId, stepKey, runId, reloadCount]);
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent
|
||||
side="right"
|
||||
hideClose
|
||||
className="flex w-[min(50rem,calc(100vw-2rem))] flex-col gap-0 p-0 sm:max-w-2xl"
|
||||
onEscapeKeyDown={(e) => {
|
||||
if (isFullscreen) {
|
||||
e.preventDefault();
|
||||
setIsFullscreen(false);
|
||||
}
|
||||
}}
|
||||
className="flex max-w-none flex-col gap-0 p-0 sm:max-w-none"
|
||||
style={
|
||||
isFullscreen
|
||||
? { width: "100vw", maxWidth: "100vw" }
|
||||
: { width: `${width}px`, maxWidth: "none" }
|
||||
}
|
||||
>
|
||||
{!isFullscreen ? (
|
||||
<div
|
||||
onPointerDown={onResizePointerDown}
|
||||
onPointerMove={onResizePointerMove}
|
||||
onPointerUp={onResizePointerUp}
|
||||
onPointerCancel={onResizePointerUp}
|
||||
className="group absolute inset-y-0 left-0 z-50 w-1.5 cursor-ew-resize hover:bg-sky-500/40 active:bg-sky-500/60"
|
||||
title="拖动调整宽度"
|
||||
aria-label="拖动调整宽度"
|
||||
>
|
||||
<div className="-translate-y-1/2 absolute top-1/2 left-[2px] h-10 w-[2px] rounded-full bg-muted-foreground/30 group-hover:bg-sky-400" />
|
||||
</div>
|
||||
) : null}
|
||||
<header className="flex shrink-0 items-start justify-between gap-3 border-b px-5 py-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -119,6 +175,21 @@ export function StepDetailSheet({
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-0.5 flex shrink-0 items-center gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-7"
|
||||
onClick={() => setIsFullscreen((v) => !v)}
|
||||
title={isFullscreen ? "退出全屏 (Esc)" : "全屏"}
|
||||
aria-label={isFullscreen ? "退出全屏" : "全屏"}
|
||||
>
|
||||
{isFullscreen ? (
|
||||
<Minimize2Icon className="size-3.5" />
|
||||
) : (
|
||||
<Maximize2Icon className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
||||
Reference in New Issue
Block a user