Make server tool http based
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -102,6 +102,10 @@ const mapTool = (raw: AnyRecord): Tool => ({
|
||||
description: readField(raw, ['description'], ''),
|
||||
category: readField(raw, ['category'], 'system') as 'system' | 'query',
|
||||
icon: readField(raw, ['icon'], 'Wrench'),
|
||||
httpMethod: readField(raw, ['httpMethod', 'http_method'], 'GET') as Tool['httpMethod'],
|
||||
httpUrl: readField(raw, ['httpUrl', 'http_url'], ''),
|
||||
httpHeaders: readField(raw, ['httpHeaders', 'http_headers'], {}),
|
||||
httpTimeoutMs: Number(readField(raw, ['httpTimeoutMs', 'http_timeout_ms'], 10000)),
|
||||
isSystem: Boolean(readField(raw, ['isSystem', 'is_system'], false)),
|
||||
enabled: Boolean(readField(raw, ['enabled'], true)),
|
||||
isCustom: !Boolean(readField(raw, ['isSystem', 'is_system'], false)),
|
||||
@@ -500,6 +504,10 @@ export const createTool = async (data: Partial<Tool>): Promise<Tool> => {
|
||||
description: data.description || '',
|
||||
category: data.category || 'system',
|
||||
icon: data.icon || (data.category === 'query' ? 'Globe' : 'Terminal'),
|
||||
http_method: data.httpMethod || 'GET',
|
||||
http_url: data.httpUrl || null,
|
||||
http_headers: data.httpHeaders || {},
|
||||
http_timeout_ms: data.httpTimeoutMs ?? 10000,
|
||||
enabled: data.enabled ?? true,
|
||||
};
|
||||
const response = await apiRequest<AnyRecord>('/tools/resources', { method: 'POST', body: payload });
|
||||
@@ -512,6 +520,10 @@ export const updateTool = async (id: string, data: Partial<Tool>): Promise<Tool>
|
||||
description: data.description,
|
||||
category: data.category,
|
||||
icon: data.icon,
|
||||
http_method: data.httpMethod,
|
||||
http_url: data.httpUrl,
|
||||
http_headers: data.httpHeaders,
|
||||
http_timeout_ms: data.httpTimeoutMs,
|
||||
enabled: data.enabled,
|
||||
};
|
||||
const response = await apiRequest<AnyRecord>(`/tools/resources/${id}`, { method: 'PUT', body: payload });
|
||||
|
||||
@@ -186,6 +186,10 @@ export interface Tool {
|
||||
description: string;
|
||||
category: 'system' | 'query';
|
||||
icon: string;
|
||||
httpMethod?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
httpUrl?: string;
|
||||
httpHeaders?: Record<string, string>;
|
||||
httpTimeoutMs?: number;
|
||||
isCustom?: boolean;
|
||||
isSystem?: boolean;
|
||||
enabled?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user