feat: add skill evaluation workbench

This commit is contained in:
wuyang6
2026-07-23 19:38:25 +08:00
parent 9067914ecc
commit 5be9434ba3
8 changed files with 5232 additions and 1 deletions
@@ -0,0 +1,78 @@
import { getCurrentAccount } from "@/lib/claw-auth";
const CLAW_API_URL = process.env.CLAW_API_URL ?? "http://127.0.0.1:8765";
type RouteContext = {
params: Promise<{ path: string[] }>;
};
export async function GET(request: Request, context: RouteContext) {
return proxyEvaluationRequest("GET", request, context);
}
export async function POST(request: Request, context: RouteContext) {
return proxyEvaluationRequest("POST", request, context);
}
export async function PATCH(request: Request, context: RouteContext) {
return proxyEvaluationRequest("PATCH", request, context);
}
async function proxyEvaluationRequest(
method: "GET" | "POST" | "PATCH",
request: Request,
context: RouteContext,
) {
const account = await getCurrentAccount();
if (!account)
return Response.json({ detail: "unauthorized" }, { status: 401 });
const { path } = await context.params;
const incomingUrl = new URL(request.url);
const targetUrl = new URL(
`${CLAW_API_URL}/api/evaluations/${path.map(encodeURIComponent).join("/")}`,
);
for (const [key, value] of incomingUrl.searchParams.entries()) {
targetUrl.searchParams.append(key, value);
}
const headers: Record<string, string> = {};
let body: string | undefined;
if (method === "GET") {
targetUrl.searchParams.set("account_id", account.id);
} else {
const payload = (await request.json()) as Record<string, unknown>;
headers["content-type"] = "application/json";
body = JSON.stringify({ ...payload, account_id: account.id });
}
try {
const response = await fetch(targetUrl, {
method,
headers,
body,
cache: "no-store",
});
const responseHeaders = new Headers();
responseHeaders.set(
"content-type",
response.headers.get("content-type") ?? "application/json",
);
const disposition = response.headers.get("content-disposition");
if (disposition) responseHeaders.set("content-disposition", disposition);
return new Response(await response.arrayBuffer(), {
status: response.status,
headers: responseHeaders,
});
} catch (error) {
return Response.json(
{
detail:
error instanceof Error
? `Unable to reach Claw backend: ${error.message}`
: "Unable to reach Claw backend",
},
{ status: 502 },
);
}
}
File diff suppressed because it is too large Load Diff
@@ -6,6 +6,7 @@ import {
import { ThreadListPrimitive } from "@assistant-ui/react";
import type { UIMessage } from "ai";
import {
FlaskConicalIcon,
Loader2Icon,
MoreHorizontalIcon,
PencilIcon,
@@ -148,6 +149,7 @@ export const ThreadList: FC = () => {
return (
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex min-h-0 flex-1 flex-col gap-1">
<ThreadListNew />
<ThreadListEvaluation />
<JupyterWorkspaceList />
<ClawSessionList />
</ThreadListPrimitive.Root>
@@ -174,6 +176,21 @@ const ThreadListNew: FC = () => {
);
};
const ThreadListEvaluation: FC = () => {
return (
<Button
variant="ghost"
className="h-9 w-full justify-start gap-2 rounded-lg px-3 text-sm"
asChild
>
<a href="/evaluations">
<FlaskConicalIcon className="size-4" />
Skill
</a>
</Button>
);
};
type JupyterWorkspaceEntry = {
id: string;
label?: string;
@@ -1220,7 +1237,7 @@ function resolvePendingPrompt(
: "";
if (!pendingPrompt) return null;
for (const message of messages) {
if (!message || message.role !== "user") continue;
if (message?.role !== "user") continue;
const content = cleanStoredContent(message.content ?? "").trim();
if (!content || content.trimStart().startsWith("<system-reminder>"))
continue;
@@ -5,6 +5,7 @@ import {
BrainIcon,
ChevronDownIcon,
Clock3Icon,
FlaskConicalIcon,
LogOutIcon,
PanelLeftOpenIcon,
SaveIcon,
@@ -215,6 +216,12 @@ function CollapsedSidebarRail({
>
<SquarePenIcon className="size-4" />
</CollapsedIconButton>
<CollapsedIconButton
label="Skill 评测"
onClick={() => window.location.assign("/evaluations")}
>
<FlaskConicalIcon className="size-4" />
</CollapsedIconButton>
<CollapsedSessionSearch />
<CollapsedRecentSessions />
</div>