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>
|
||||
|
||||
Reference in New Issue
Block a user