feat: add configurable client tools and photo input
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -31,6 +32,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
mcpServersApi,
|
||||
toolsApi,
|
||||
type ToolExecutionMode,
|
||||
type McpServer,
|
||||
type McpServerUpsert,
|
||||
type McpToolDefinition,
|
||||
@@ -364,6 +367,14 @@ export function McpServerDialog({
|
||||
tool={tool}
|
||||
expanded={expandedToolIds.has(tool.id)}
|
||||
onToggle={() => toggleTool(tool.id)}
|
||||
onUpdated={(updated) => {
|
||||
setServerTools((current) =>
|
||||
current.map((item) =>
|
||||
item.id === updated.id ? updated : item,
|
||||
),
|
||||
);
|
||||
void onChanged();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -390,12 +401,22 @@ function McpToolRow({
|
||||
tool,
|
||||
expanded,
|
||||
onToggle,
|
||||
onUpdated,
|
||||
}: {
|
||||
tool: Tool;
|
||||
expanded: boolean;
|
||||
onToggle: () => void;
|
||||
onUpdated: (tool: Tool) => void;
|
||||
}) {
|
||||
const definition = tool.definition as McpToolDefinition;
|
||||
const [allowInterruptions, setAllowInterruptions] = useState(
|
||||
definition.config.allowInterruptions ?? true,
|
||||
);
|
||||
const [executionMode, setExecutionMode] = useState<ToolExecutionMode>(
|
||||
definition.config.executionMode ?? "immediate",
|
||||
);
|
||||
const [savingPolicy, setSavingPolicy] = useState(false);
|
||||
const [policyError, setPolicyError] = useState<string | null>(null);
|
||||
const schema = definition.config.inputSchema ?? {};
|
||||
const properties = useMemo(
|
||||
() => Object.entries((schema.properties as Record<string, unknown> | undefined) ?? {}),
|
||||
@@ -403,6 +424,37 @@ function McpToolRow({
|
||||
);
|
||||
const required = new Set(Array.isArray(schema.required) ? schema.required.map(String) : []);
|
||||
|
||||
async function savePolicy() {
|
||||
if (savingPolicy) return;
|
||||
setSavingPolicy(true);
|
||||
setPolicyError(null);
|
||||
try {
|
||||
const updated = await toolsApi.update(tool.id, {
|
||||
name: tool.name,
|
||||
functionName: tool.functionName,
|
||||
description: tool.description,
|
||||
definition: {
|
||||
...definition,
|
||||
config: {
|
||||
...definition.config,
|
||||
allowInterruptions,
|
||||
executionMode,
|
||||
},
|
||||
},
|
||||
secrets: tool.secrets,
|
||||
status: tool.status,
|
||||
mcpServerId: tool.mcpServerId,
|
||||
});
|
||||
onUpdated(updated);
|
||||
} catch (error) {
|
||||
setPolicyError(
|
||||
error instanceof Error ? error.message : "保存工具运行策略失败",
|
||||
);
|
||||
} finally {
|
||||
setSavingPolicy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
@@ -425,6 +477,47 @@ function McpToolRow({
|
||||
{tool.description && (
|
||||
<p className="mb-4 text-sm leading-6 text-muted-foreground">{tool.description}</p>
|
||||
)}
|
||||
<div className="mb-4 grid gap-4 rounded-lg border border-hairline bg-card p-4 sm:grid-cols-2">
|
||||
<Field label="执行模式">
|
||||
<Select
|
||||
value={executionMode}
|
||||
onValueChange={(value: ToolExecutionMode) =>
|
||||
setExecutionMode(value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="immediate">立即执行 · 等待结果</SelectItem>
|
||||
<SelectItem value="async">异步执行 · 与对话并行</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<div className="flex items-center justify-between gap-4 rounded-lg border border-hairline px-3 py-2">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-foreground">允许用户打断</div>
|
||||
<div className="mt-0.5 text-xs text-muted-foreground">覆盖工具运行及随后播报</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={allowInterruptions}
|
||||
onCheckedChange={setAllowInterruptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 sm:col-span-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={savingPolicy}
|
||||
onClick={() => void savePolicy()}
|
||||
>
|
||||
{savingPolicy && <Loader2 size={14} className="animate-spin" />}
|
||||
保存运行策略
|
||||
</Button>
|
||||
{policyError && (
|
||||
<span className="text-xs text-destructive">{policyError}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{properties.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">此工具不需要参数。</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user