Files
iAs/web/src/pages/ToolsPage.tsx
T
wunianxiao 640ba9e7d2 feat: add React UI and AI page sharing tools
Move HTTP page rendering to the React app, fix multi-tool-call message handling, add API docs lookup, web page lookup, and Markdown preview page sharing.
2026-07-07 22:27:14 +08:00

187 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import { apiGet } from "../lib/api";
interface ToolParam {
name: string;
param_type: string;
required: boolean;
description: string;
}
interface ToolItem {
name: string;
description: string;
source: string;
path?: string;
params: ToolParam[];
required_env: string[];
}
interface ToolsResponse {
success: boolean;
total: number;
tools_path: string;
builtin_tools: ToolItem[];
capability_tools: ToolItem[];
warnings: string[];
}
export function ToolsPage() {
const [data, setData] = useState<ToolsResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
apiGet<ToolsResponse>("/v1/tools")
.then(setData)
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) {
return (
<div className="text-center py-16 text-gray-400">
<div className="animate-pulse">...</div>
</div>
);
}
if (error) {
return (
<div className="text-center py-16 text-red-500">
<p>{error}</p>
</div>
);
}
if (!data) return null;
const renderToolCard = (item: ToolItem) => (
<article
key={item.name}
className="bg-white rounded-lg border border-gray-200 p-4 md:p-5"
>
<div className="flex items-start justify-between gap-3 mb-2">
<div className="min-w-0">
<h3 className="text-base font-semibold text-gray-900 break-words">
{item.name}
</h3>
<p className="mt-1 text-sm text-gray-500 leading-relaxed">
{item.description}
</p>
</div>
<span className="shrink-0 inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-semibold bg-blue-50 text-blue-700">
{item.source}
</span>
</div>
{item.path && (
<div className="flex flex-wrap gap-2 mb-3">
<span className="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium bg-gray-100 text-gray-500">
{item.path}
</span>
</div>
)}
{item.params.length > 0 ? (
<div className="mt-3">
{item.params.map((param) => (
<div
key={param.name}
className="py-1.5 border-t border-gray-100 first:border-t-0"
>
<div className="flex flex-wrap items-center gap-2">
<code className="text-xs bg-gray-100 text-gray-700 px-1.5 py-0.5 rounded">
{param.name}
</code>
<span className="text-xs text-gray-400">{param.param_type}</span>
{param.required ? (
<span className="text-[11px] font-medium text-red-600 bg-red-50 px-1.5 py-0.5 rounded">
</span>
) : (
<span className="text-[11px] font-medium text-gray-400 bg-gray-100 px-1.5 py-0.5 rounded">
</span>
)}
</div>
<p className="mt-1 text-sm text-gray-500">{param.description}</p>
</div>
))}
</div>
) : (
<p className="text-sm text-gray-400"></p>
)}
{item.required_env.length > 0 && (
<div className="mt-3 text-xs text-gray-500">
{" "}
{item.required_env.map((env) => (
<code
key={env}
className="bg-gray-100 px-1.5 py-0.5 rounded mr-1"
>
{env}
</code>
))}
</div>
)}
</article>
);
return (
<div>
<div className="mb-7">
<h1 className="text-2xl md:text-3xl font-bold tracking-tight"></h1>
<p className="mt-1.5 text-sm text-gray-500">
{data.total} YAML
<code className="text-xs bg-gray-200 text-gray-700 px-1.5 py-0.5 rounded">
{data.tools_path}
</code>
</p>
</div>
{data.warnings.length > 0 && (
<section className="bg-yellow-50 border border-yellow-200 text-yellow-800 rounded-lg p-4 mb-5 text-sm">
<div className="font-semibold mb-1"></div>
<ul>
{data.warnings.map((w, i) => (
<li key={i} className="ml-4 list-disc">
{w}
</li>
))}
</ul>
</section>
)}
<section className="mb-8">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold"></h2>
<span className="text-sm text-gray-400">{data.builtin_tools.length} </span>
</div>
<div className="flex flex-col gap-3">
{data.builtin_tools.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-5 text-sm text-gray-400">
</div>
) : (
data.builtin_tools.map(renderToolCard)
)}
</div>
</section>
<section className="mb-8">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold">YAML </h2>
<span className="text-sm text-gray-400">{data.capability_tools.length} </span>
</div>
<div className="flex flex-col gap-3">
{data.capability_tools.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-5 text-sm text-gray-400">
</div>
) : (
data.capability_tools.map(renderToolCard)
)}
</div>
</section>
</div>
);
}