Files
deerflow-code/frontend-web/src/components/workspace/agents/agent-chat-link-fields.tsx
T

225 lines
8.2 KiB
TypeScript

"use client";
import { PlusIcon, XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type { AgentChatLink, AgentChatRouteParam } from "@/core/agents/types";
import { emptyAgentChatLink } from "@/core/agents/types";
import {
APP_THEME_COLORS,
LOGIN_SOURCE_OPTIONS,
parseThemeMap,
stringifyThemeMap,
} from "@/strategy-components/lib/light-app";
function newParam(): AgentChatRouteParam {
return {
key: "",
paramType: "login",
source: "access_token",
customValue: "",
themeValue: null,
};
}
export function AgentChatLinkFields({
value,
onChange,
}: {
value: AgentChatLink;
onChange: (next: AgentChatLink) => void;
}) {
const link = value ?? emptyAgentChatLink();
function patch(partial: Partial<AgentChatLink>) {
onChange({ ...link, ...partial });
}
function patchParam(index: number, partial: Partial<AgentChatRouteParam>) {
patch({
route_params: link.route_params.map((param, i) =>
i === index ? { ...param, ...partial } : param,
),
});
}
return (
<div className="space-y-3 rounded-md border p-3">
<div>
<p className="text-sm font-medium">对话跳转</p>
<p className="text-muted-foreground text-xs">
配置后,智能体管理里的「对话」按钮会打开这个地址。留空则仍进入本系统问答。
</p>
</div>
<Input
value={link.url}
onChange={(event) => patch({ url: event.currentTarget.value })}
placeholder="https://... ,或以 / 开头的站内路径"
/>
<div className="space-y-1">
<p className="text-muted-foreground text-xs">打开方式</p>
<Select
value={link.open_mode}
onValueChange={(next) => patch({ open_mode: next === "self" ? "self" : "blank" })}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="blank">新窗口</SelectItem>
<SelectItem value="self">当前窗口</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<div>
<p className="text-sm font-medium">路由参数</p>
<p className="text-muted-foreground text-xs">
点击「对话」时拼到地址后面。登录参数取当前登录信息,主题参数跟随当前主题。
</p>
</div>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => patch({ route_params: [...link.route_params, newParam()] })}
>
<PlusIcon className="h-3.5 w-3.5" />
添加
</Button>
</div>
{link.route_params.length === 0 ? (
<p className="text-muted-foreground rounded-md border border-dashed px-3 py-3 text-center text-xs">
暂无路由参数
</p>
) : (
<div className="space-y-2">
{link.route_params.map((param, index) => {
const themeMap = param.paramType === "theme" ? parseThemeMap(param.themeValue) : {};
return (
<div key={index} className="space-y-2 rounded-md border p-2">
<div className="flex items-center gap-2">
<Input
value={param.key}
onChange={(event) => patchParam(index, { key: event.currentTarget.value })}
placeholder="参数名"
className="h-8 font-mono text-xs"
/>
<Select
value={param.paramType}
onValueChange={(next) =>
patchParam(index, {
paramType: next === "theme" ? "theme" : "login",
source: next === "login" ? param.source || "access_token" : null,
themeValue:
next === "theme"
? param.themeValue || stringifyThemeMap({})
: null,
})
}
>
<SelectTrigger className="h-8 w-[120px] shrink-0">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="login">登录参数</SelectItem>
<SelectItem value="theme">主题参数</SelectItem>
</SelectContent>
</Select>
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8 shrink-0"
onClick={() =>
patch({
route_params: link.route_params.filter((_, i) => i !== index),
})
}
title="移除参数"
>
<XIcon className="h-4 w-4" />
</Button>
</div>
{param.paramType === "login" && (
<div className="flex items-center gap-2">
<Select
value={param.source || "access_token"}
onValueChange={(next) => patchParam(index, { source: next })}
>
<SelectTrigger className="h-8 min-w-0 flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
{LOGIN_SOURCE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
{param.source === "other" && (
<Input
value={param.customValue ?? ""}
onChange={(event) =>
patchParam(index, { customValue: event.currentTarget.value })
}
placeholder="自定义值"
className="h-8 w-[140px] text-xs"
/>
)}
</div>
)}
{param.paramType === "theme" && (
<div className="space-y-1.5">
<p className="text-muted-foreground text-[11px]">
各系统主题色跳转时传给目标地址的值
</p>
{APP_THEME_COLORS.map((color) => (
<div key={color.key} className="flex items-center gap-2">
<span className="flex w-[72px] shrink-0 items-center gap-1.5 text-xs">
<span
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ backgroundColor: color.dot }}
/>
{color.label}
</span>
<Input
value={themeMap[color.key] ?? color.key}
onChange={(event) =>
patchParam(index, {
themeValue: stringifyThemeMap({
...themeMap,
[color.key]: event.currentTarget.value,
}),
})
}
className="h-7 font-mono text-xs"
/>
</div>
))}
</div>
)}
</div>
);
})}
</div>
)}
</div>
</div>
);
}