225 lines
8.2 KiB
TypeScript
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>
|
|
);
|
|
}
|