feat: collapse local ai settings by default
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-04 23:03:05 +08:00
parent 67602b0af7
commit 033c4f2fa4

View File

@ -4,7 +4,7 @@ import Link from "next/link";
import { useState, useEffect, useMemo, type ChangeEvent, type DragEvent } from "react";
import useSWR from "swr";
import { toast } from "sonner";
import { formatBeijingTime } from "@/lib/utils";
import { cn, formatBeijingTime } from "@/lib/utils";
import {
Card,
CardContent,
@ -1214,6 +1214,9 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
...defaultThirdPartySqlServerConfig,
});
const [isTestingThirdPartySqlServer, setIsTestingThirdPartySqlServer] = useState(false);
const [isLocalAiSettingsExpanded, setIsLocalAiSettingsExpanded] = useState(false);
const [isThirdPartySqlServerFieldsVisible, setIsThirdPartySqlServerFieldsVisible] =
useState(false);
const [productPlanningApiConfig, setProductPlanningApiConfig] =
useState<ProductPlanningApiConfig>(defaultProductPlanningApiConfig());
const [productPlanningAiModelConfig, setProductPlanningAiModelConfig] =
@ -1459,6 +1462,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
}
}, [thirdPartySqlServerConfigData]);
useEffect(() => {
if (!isLocalAiSettingsExpanded) {
setIsThirdPartySqlServerFieldsVisible(false);
}
}, [isLocalAiSettingsExpanded]);
// 加载实时画布API配置
const { data: realtimeCanvasConfigData } = useSWR(
"/api/admin/settings?key=realtime_canvas_config",
@ -4587,6 +4596,13 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
</div>
);
const hasThirdPartySqlServerConfiguredValue = Boolean(
thirdPartySqlServerConfig.host.trim() ||
thirdPartySqlServerConfig.database.trim() ||
thirdPartySqlServerConfig.username.trim() ||
thirdPartySqlServerConfig.password.trim(),
);
return (
<div className="container mx-auto p-6">
<Tabs value={activeTab} onValueChange={setActiveTab}>
@ -9742,6 +9758,28 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
{/* 本地AI设置 */}
<TabsContent value="rmbg" className="mt-6 space-y-6">
<div className="flex flex-col gap-3 rounded-xl border bg-slate-50/70 p-4 md:flex-row md:items-center md:justify-between">
<div className="space-y-1">
<div className="text-sm font-medium">本地 AI 设置概览模式</div>
<div className="text-sm text-muted-foreground">
默认先收起各项配置,避免打开页签时一次性展示过多参数。数据库连接信息会额外保持隐藏,需手动显示后才会回显。
</div>
</div>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setIsLocalAiSettingsExpanded((prev) => !prev)}
>
{isLocalAiSettingsExpanded ? (
<EyeOff className="mr-2 h-4 w-4" />
) : (
<Eye className="mr-2 h-4 w-4" />
)}
{isLocalAiSettingsExpanded ? "收起全部配置项" : "展开全部配置项"}
</Button>
</div>
<Card>
<CardHeader>
<CardTitle className="text-xl">商品策划工具 API 配置</CardTitle>
@ -9749,7 +9787,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置商品策划工具页面使用的淘宝 / 京东 Onebound、问大家和销量接口。保存后商品策划相关接口会优先读取这里的配置,未填写时才回退到环境变量。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="flex-1 space-y-1">
<Label className="text-base">启用商品策划工具</Label>
@ -9910,7 +9953,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
为商品策划工具单独绑定一套分析模型。开启后,评价分析、问大家分析、竞品图片分析和 SKU 卖点分析会优先走这里配置的 OpenAI 兼容模型,不再依赖站点默认模型。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="flex-1 space-y-1">
<Label className="text-base">启用专用模型</Label>
@ -10034,7 +10082,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
为 AI详情生成 单独配置 Moonshot 官方 Kimi K2.5 模型。保存后会自动绑定到“AI详情生成 - Agent专用模型”。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="space-y-2">
<Label htmlFor="moonshotProviderName">提供商名称</Label>
<Input
@ -10168,7 +10221,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置无限画布 3D 生成功能使用的 Tripo3D 接口,保存后写入数据库 AI 提供商配置。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="space-y-2">
<Label htmlFor="tripo3dName">提供商名称</Label>
<Input
@ -10242,7 +10300,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置无限画布 3D 世界模式使用的 World Labs 接口,保存后写入数据库 AI 提供商配置。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="space-y-2">
<Label htmlFor="worldlabs3dName">提供商名称</Label>
<Input
@ -10331,7 +10394,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置背景移除API,用于画图标注功能中的抠图处理。支持自部署RMBG、PhotoRoom和AOS云服务。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
{/* 服务提供商选择 */}
<div className="space-y-3">
<Label>抠图服务提供商</Label>
@ -10560,7 +10628,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置图片变清晰API,用于画图标注功能中的图片增强处理。支持主API和备用API切换。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
{/* API切换开关 */}
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
@ -10759,7 +10832,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置 Krea Topaz Generative,用于无限画布中的“图像增强 Pro”能力。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<Label className="text-base">启用图像增强 Pro</Label>
@ -11156,7 +11234,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置阿里云Dashscope图像拓展API,用于画图标注功能中的图像智能扩展处理
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="space-y-2">
<Label htmlFor="extendApiKey">API密钥 *</Label>
<Input
@ -11235,7 +11318,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置PicWish去水印API,用于自动去除图片中的水印
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="space-y-2">
<Label htmlFor="watermarkRemoveApiKey">API密钥 *</Label>
<Input
@ -11275,7 +11363,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置 fal.ai 多角度图像编辑 API,用于对同一图像生成不同视角的变体
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
{/* 启用开关 */}
<div className="flex items-center justify-between">
<div className="space-y-0.5">
@ -11438,7 +11531,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置 Replicate 上的 Qwen-Image-Layered 模型,用于将单张图片拆分为多个可编辑图层并回写到无限画布。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>启用图层拆分功能</Label>
@ -11652,7 +11750,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置后可供后续业务功能直接访问外部 SQL Server 数据库。当前仅提供配置保存与连通性测试。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<Label className="text-base">启用第三方数据库连接</Label>
@ -11673,151 +11776,201 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
</div>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="sqlserverHost">主机地址</Label>
<Input
id="sqlserverHost"
value={thirdPartySqlServerConfig.host}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({ ...prev, host: e.target.value }))
}
placeholder="218.93.191.16"
/>
<div className="flex flex-col gap-3 rounded-lg border border-amber-200 bg-amber-50 p-4 md:flex-row md:items-center md:justify-between">
<div className="space-y-1">
<div className="text-sm font-medium text-amber-900">数据库连接信息默认隐藏</div>
<div className="text-xs text-amber-800">
{hasThirdPartySqlServerConfiguredValue
? "已保存的主机地址、数据库、账号和密码不会默认显示,只有主动点击后才会回显并允许编辑。"
: "当前未展开连接字段。需要新增或编辑配置时,再手动显示连接信息。"}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverPort">端口</Label>
<Input
id="sqlserverPort"
type="number"
min={1}
max={65535}
value={thirdPartySqlServerConfig.port}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
port: Math.max(1, Math.min(65535, Number(e.target.value) || 1433)),
}))
}
placeholder="1433"
/>
</div>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="sqlserverDatabase">数据库名称</Label>
<Input
id="sqlserverDatabase"
value={thirdPartySqlServerConfig.database}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({ ...prev, database: e.target.value }))
}
placeholder="JSDataMiddlePlatform"
/>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverUsername">账号</Label>
<Input
id="sqlserverUsername"
value={thirdPartySqlServerConfig.username}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({ ...prev, username: e.target.value }))
}
placeholder="nanting"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverPassword">密码</Label>
<Input
id="sqlserverPassword"
type="password"
value={thirdPartySqlServerConfig.password}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({ ...prev, password: e.target.value }))
<Button
type="button"
variant="outline"
onClick={() =>
setIsThirdPartySqlServerFieldsVisible((prev) => !prev)
}
placeholder="请输入 SQL Server 密码"
/>
>
{isThirdPartySqlServerFieldsVisible ? (
<EyeOff className="mr-2 h-4 w-4" />
) : (
<Eye className="mr-2 h-4 w-4" />
)}
{isThirdPartySqlServerFieldsVisible ? "隐藏连接信息" : "显示并编辑连接信息"}
</Button>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="sqlserverConnectionTimeout">连接超时(毫秒)</Label>
<Input
id="sqlserverConnectionTimeout"
type="number"
min={1000}
max={120000}
step={1000}
value={thirdPartySqlServerConfig.connectionTimeoutMs}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
connectionTimeoutMs: Math.max(
1000,
Math.min(120000, Number(e.target.value) || 15000)
),
}))
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverRequestTimeout">请求超时(毫秒)</Label>
<Input
id="sqlserverRequestTimeout"
type="number"
min={1000}
max={120000}
step={1000}
value={thirdPartySqlServerConfig.requestTimeoutMs}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
requestTimeoutMs: Math.max(
1000,
Math.min(120000, Number(e.target.value) || 30000)
),
}))
}
/>
</div>
</div>
{isThirdPartySqlServerFieldsVisible ? (
<>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="sqlserverHost">主机地址</Label>
<Input
id="sqlserverHost"
value={thirdPartySqlServerConfig.host}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
host: e.target.value,
}))
}
placeholder="例如:db.example.com 或 10.0.0.8"
/>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverPort">端口</Label>
<Input
id="sqlserverPort"
type="number"
min={1}
max={65535}
value={thirdPartySqlServerConfig.port}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
port: Math.max(1, Math.min(65535, Number(e.target.value) || 1433)),
}))
}
placeholder="1433"
/>
</div>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label className="text-sm">启用加密</Label>
<div className="text-xs text-muted-foreground">
现代云数据库通常需要开启;传统内网 SQL Server 可关闭。
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="sqlserverDatabase">数据库名称</Label>
<Input
id="sqlserverDatabase"
value={thirdPartySqlServerConfig.database}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
database: e.target.value,
}))
}
placeholder="请输入数据库名称"
/>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverUsername">账号</Label>
<Input
id="sqlserverUsername"
value={thirdPartySqlServerConfig.username}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
username: e.target.value,
}))
}
placeholder="请输入数据库账号"
/>
</div>
</div>
<Switch
checked={thirdPartySqlServerConfig.encrypt}
onCheckedChange={(checked) =>
setThirdPartySqlServerConfig((prev) => ({ ...prev, encrypt: checked }))
}
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label className="text-sm">信任服务器证书</Label>
<div className="text-xs text-muted-foreground">
对自签名证书或传统 SQL Server 更友好。
<div className="space-y-2">
<Label htmlFor="sqlserverPassword">密码</Label>
<Input
id="sqlserverPassword"
type="password"
value={thirdPartySqlServerConfig.password}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
password: e.target.value,
}))
}
placeholder="请输入 SQL Server 密码"
/>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="sqlserverConnectionTimeout">连接超时(毫秒)</Label>
<Input
id="sqlserverConnectionTimeout"
type="number"
min={1000}
max={120000}
step={1000}
value={thirdPartySqlServerConfig.connectionTimeoutMs}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
connectionTimeoutMs: Math.max(
1000,
Math.min(120000, Number(e.target.value) || 15000),
),
}))
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="sqlserverRequestTimeout">请求超时(毫秒)</Label>
<Input
id="sqlserverRequestTimeout"
type="number"
min={1000}
max={120000}
step={1000}
value={thirdPartySqlServerConfig.requestTimeoutMs}
onChange={(e) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
requestTimeoutMs: Math.max(
1000,
Math.min(120000, Number(e.target.value) || 30000),
),
}))
}
/>
</div>
</div>
<Switch
checked={thirdPartySqlServerConfig.trustServerCertificate}
onCheckedChange={(checked) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
trustServerCertificate: checked,
}))
}
/>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label className="text-sm">启用加密</Label>
<div className="text-xs text-muted-foreground">
现代云数据库通常需要开启;传统内网 SQL Server 可关闭。
</div>
</div>
<Switch
checked={thirdPartySqlServerConfig.encrypt}
onCheckedChange={(checked) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
encrypt: checked,
}))
}
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label className="text-sm">信任服务器证书</Label>
<div className="text-xs text-muted-foreground">
对自签名证书或传统 SQL Server 更友好。
</div>
</div>
<Switch
checked={thirdPartySqlServerConfig.trustServerCertificate}
onCheckedChange={(checked) =>
setThirdPartySqlServerConfig((prev) => ({
...prev,
trustServerCertificate: checked,
}))
}
/>
</div>
</div>
</>
) : (
<div className="rounded-lg border border-dashed p-4 text-sm text-muted-foreground">
{hasThirdPartySqlServerConfiguredValue
? "当前已保存的 SQL Server 连接字段处于隐藏状态。点击上方按钮后才会展示主机、数据库、账号和密码。"
: "当前未展示 SQL Server 连接字段。点击上方按钮开始填写连接信息。"}
</div>
</div>
)}
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4 space-y-1 text-xs text-blue-800">
<p>• 当前支持 SQL Server 标准 TCP 连接。</p>
@ -11852,7 +12005,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置实时画布功能的供应商、模型与 API Key。当前已接入 BFL / Replicate / FAL 供应商;FAL 的 WebSocket 真流式实时属于第二阶段,不是这次上线内容。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<Label className="text-base">启用实时画布服务</Label>
@ -12450,7 +12608,12 @@ const [realtimeCanvasConfig, setRealtimeCanvasConfig] = useState({
配置批量处理功能的默认提示词和参数。批量处理可将产品图批量融合到多个场景中。
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 pt-6">
<CardContent
className={cn(
"local-ai-config-card-content space-y-6 pt-6",
!isLocalAiSettingsExpanded && "hidden",
)}
>
{/* 功能开关 */}
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">