Make server tool http based

This commit is contained in:
Xin Wang
2026-02-11 11:39:45 +08:00
parent 80e1d24443
commit 4c46793169
9 changed files with 281 additions and 17 deletions

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Search, Filter, Plus, Wrench, Terminal, Globe, Camera, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Trash2, Edit2, Box } from 'lucide-react';
import { Search, Filter, Plus, Wrench, Terminal, Globe, Camera, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Trash2, Edit2, Box, Volume2 } from 'lucide-react';
import { Button, Input, Badge, Dialog } from '../components/UI';
import { Tool } from '../types';
import { createTool, deleteTool, fetchTools, updateTool } from '../services/backendApi';
@@ -17,6 +17,7 @@ const iconMap: Record<string, React.ReactNode> = {
Globe: <Globe className="w-5 h-5" />,
Wrench: <Wrench className="w-5 h-5" />,
Box: <Box className="w-5 h-5" />,
Volume2: <Volume2 className="w-5 h-5" />,
};
export const ToolLibraryPage: React.FC = () => {
@@ -32,6 +33,10 @@ export const ToolLibraryPage: React.FC = () => {
const [toolCategory, setToolCategory] = useState<'system' | 'query'>('system');
const [toolIcon, setToolIcon] = useState('Wrench');
const [toolEnabled, setToolEnabled] = useState(true);
const [toolHttpMethod, setToolHttpMethod] = useState<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'>('GET');
const [toolHttpUrl, setToolHttpUrl] = useState('');
const [toolHttpHeadersText, setToolHttpHeadersText] = useState('{}');
const [toolHttpTimeoutMs, setToolHttpTimeoutMs] = useState(10000);
const [saving, setSaving] = useState(false);
const loadTools = async () => {
@@ -57,6 +62,10 @@ export const ToolLibraryPage: React.FC = () => {
setToolCategory('system');
setToolIcon('Wrench');
setToolEnabled(true);
setToolHttpMethod('GET');
setToolHttpUrl('');
setToolHttpHeadersText('{}');
setToolHttpTimeoutMs(10000);
setIsToolModalOpen(true);
};
@@ -67,6 +76,10 @@ export const ToolLibraryPage: React.FC = () => {
setToolCategory(tool.category);
setToolIcon(tool.icon || 'Wrench');
setToolEnabled(tool.enabled ?? true);
setToolHttpMethod((tool.httpMethod || 'GET') as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE');
setToolHttpUrl(tool.httpUrl || '');
setToolHttpHeadersText(JSON.stringify(tool.httpHeaders || {}, null, 2));
setToolHttpTimeoutMs(tool.httpTimeoutMs || 10000);
setIsToolModalOpen(true);
};
@@ -88,12 +101,37 @@ export const ToolLibraryPage: React.FC = () => {
try {
setSaving(true);
let parsedHeaders: Record<string, string> = {};
if (toolCategory === 'query') {
if (!toolHttpUrl.trim() && editingTool?.id !== 'calculator' && editingTool?.id !== 'code_interpreter') {
alert('信息查询工具请填写 HTTP URL');
setSaving(false);
return;
}
try {
const parsed = JSON.parse(toolHttpHeadersText || '{}');
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
parsedHeaders = parsed as Record<string, string>;
} else {
throw new Error('headers must be object');
}
} catch {
alert('HTTP Headers 必须是合法 JSON 对象');
setSaving(false);
return;
}
}
if (editingTool) {
const updated = await updateTool(editingTool.id, {
name: toolName.trim(),
description: toolDesc,
category: toolCategory,
icon: toolIcon,
httpMethod: toolHttpMethod,
httpUrl: toolHttpUrl.trim(),
httpHeaders: parsedHeaders,
httpTimeoutMs: toolHttpTimeoutMs,
enabled: toolEnabled,
});
setTools((prev) => prev.map((item) => (item.id === updated.id ? updated : item)));
@@ -103,6 +141,10 @@ export const ToolLibraryPage: React.FC = () => {
description: toolDesc,
category: toolCategory,
icon: toolIcon,
httpMethod: toolHttpMethod,
httpUrl: toolHttpUrl.trim(),
httpHeaders: parsedHeaders,
httpTimeoutMs: toolHttpTimeoutMs,
enabled: toolEnabled,
});
setTools((prev) => [created, ...prev]);
@@ -294,6 +336,53 @@ export const ToolLibraryPage: React.FC = () => {
/>
</div>
{toolCategory === 'query' && (
<div className="space-y-4 rounded-md border border-blue-500/20 bg-blue-500/5 p-3">
<div className="text-[10px] font-black uppercase tracking-widest text-blue-300">HTTP Request Config</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block">Method</label>
<select
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground"
value={toolHttpMethod}
onChange={(e) => setToolHttpMethod(e.target.value as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE')}
>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="PATCH">PATCH</option>
<option value="DELETE">DELETE</option>
</select>
</div>
<div className="space-y-1.5 md:col-span-2">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block">URL</label>
<Input value={toolHttpUrl} onChange={(e) => setToolHttpUrl(e.target.value)} placeholder="https://api.example.com/endpoint" />
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block">Headers (JSON)</label>
<textarea
className="flex min-h-[90px] w-full rounded-md border border-white/10 bg-white/5 px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 text-white font-mono"
value={toolHttpHeadersText}
onChange={(e) => setToolHttpHeadersText(e.target.value)}
placeholder='{"Authorization":"Bearer ..."}'
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block">Timeout (ms)</label>
<Input
type="number"
min={1000}
value={toolHttpTimeoutMs}
onChange={(e) => setToolHttpTimeoutMs(Math.max(1000, Number(e.target.value || 10000)))}
/>
</div>
<p className="text-[11px] text-muted-foreground">
Query tools send model arguments as request params for GET/DELETE, and JSON body for POST/PUT/PATCH.
</p>
</div>
)}
<label className="flex items-center space-x-2 text-xs text-muted-foreground">
<input type="checkbox" checked={toolEnabled} onChange={(e) => setToolEnabled(e.target.checked)} />
<span></span>