755 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { Save } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL,
DETAIL_AGENT_LOCAL_MOONSHOT_DISPLAY_NAME,
DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID,
} from "@/lib/ai/detail-agent-model-config";
import {
PRODUCT_PLANNING_AI_MODEL_DEFAULT_BASE_URL,
PRODUCT_PLANNING_AI_MODEL_DEFAULT_MODEL_ID,
} from "@/lib/product-planning/model-config";
import type { LocalAiPlanningSectionsProps } from "../../_lib/types";
import {
LocalAiGroupHeading,
LocalAiSectionShell,
LocalAiSectionSummary,
} from "../local-ai-section-shell";
export function LocalAiPlanningSections({
siteSettings,
setSiteSettings,
productPlanningApiConfig,
setProductPlanningApiConfig,
productPlanningAiModelConfig,
setProductPlanningAiModelConfig,
moonshotDetailAgentConfig,
setMoonshotDetailAgentConfig,
isLocalAiSectionExpanded,
toggleLocalAiSection,
handleSaveProductPlanningApiConfig,
handleSaveProductPlanningAiModelConfig,
handleSaveMoonshotDetailAgentConfig,
}: LocalAiPlanningSectionsProps) {
return (
<>
<LocalAiGroupHeading
description="把电商数据源、商品策划专用模型、详情 Agent 和业务数据库放在同一组,减少跨页面查找。"
eyebrow="Planning"
title="商品策划与数据集成"
/>
<LocalAiSectionShell
description={
<>
配置商品策划工具页面使用的淘宝 / 京东
Onebound、问大家和销量接口。保存后商品策划相关接口会优先读取这里的配置,未填写时才回退到环境变量。
</>
}
expanded={isLocalAiSectionExpanded("product-planning-api")}
id="local-ai-section-product-planning-api"
onToggle={() => toggleLocalAiSection("product-planning-api")}
summary={
<LocalAiSectionSummary
items={[
{
label: siteSettings.productPlanningEnabled
? "功能已启用"
: "功能已关闭",
tone: siteSettings.productPlanningEnabled
? "enabled"
: "disabled",
},
{
label: productPlanningApiConfig.oneboundBaseUrl
? "Onebound 已配置"
: "Onebound 待配置",
tone: productPlanningApiConfig.oneboundBaseUrl
? "enabled"
: "warning",
},
{
label: productPlanningApiConfig.planningDbHost
? "回写库已配置"
: "回写库待配置",
tone: productPlanningApiConfig.planningDbHost
? "enabled"
: "neutral",
},
]}
/>
}
title="商品策划工具 API 配置"
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="flex-1 space-y-1">
<Label className="text-base">启用商品策划工具</Label>
<div className="text-muted-foreground text-sm">
关闭后左侧导航入口会隐藏,商品策划页面和相关接口也会一并禁用。
</div>
</div>
<Switch
checked={siteSettings.productPlanningEnabled}
onCheckedChange={(checked) =>
setSiteSettings({
...siteSettings,
productPlanningEnabled: checked,
})
}
/>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="productPlanningOneboundBaseUrl">
Onebound API 地址
</Label>
<Input
id="productPlanningOneboundBaseUrl"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
oneboundBaseUrl: e.target.value,
}))
}
placeholder="https://api-gw.onebound.cn"
value={productPlanningApiConfig.oneboundBaseUrl}
/>
<div className="text-muted-foreground text-xs">
用于淘宝评价、商品详情、关键词洞察以及京东商品详情。
</div>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDetailPlusBaseUrl">
详情增强接口地址(公网)
</Label>
<Input
id="productPlanningDetailPlusBaseUrl"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
detailPlusBaseUrl: e.target.value,
}))
}
placeholder="http://ltai5tdb2ogyj59fquyedh.yydd.ai:18082/api/public/taobao-detail-plus"
value={productPlanningApiConfig.detailPlusBaseUrl}
/>
<div className="text-muted-foreground text-xs">
竞品分析会优先用这个接口查询商品详情,查不到再走 Onebound 旧接口。
</div>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDetailPlusInternalBaseUrl">
详情增强接口地址(内网,可选)
</Label>
<Input
id="productPlanningDetailPlusInternalBaseUrl"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
detailPlusInternalBaseUrl: e.target.value,
}))
}
placeholder="http://127.0.0.1:4000/api/public/taobao-detail-plus"
value={productPlanningApiConfig.detailPlusInternalBaseUrl}
/>
<div className="text-muted-foreground text-xs">
服务器同机部署时可填内网地址,系统会先尝试内网再尝试公网。
</div>
</div>
<div className="space-y-2 lg:col-span-2">
<Label htmlFor="productPlanningDetailPlusApiKey">
详情增强接口 API Key
</Label>
<Input
id="productPlanningDetailPlusApiKey"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
detailPlusApiKey: e.target.value,
}))
}
placeholder="留空保存时会保留原 API Key"
type="password"
value={productPlanningApiConfig.detailPlusApiKey}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningQaApiUrl">问大家接口地址</Label>
<Input
id="productPlanningQaApiUrl"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
qaApiUrl: e.target.value,
}))
}
placeholder="http://119.91.237.2:5432/taobao_social_feed_aggregate"
value={productPlanningApiConfig.qaApiUrl}
/>
<div className="text-muted-foreground text-xs">
用于商品策划工具中的问大家抓取。
</div>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningTaobaoApiKey">
淘宝 / 京东 API Key
</Label>
<Input
id="productPlanningTaobaoApiKey"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
taobaoApiKey: e.target.value,
}))
}
placeholder="请输入 Onebound API Key"
type="password"
value={productPlanningApiConfig.taobaoApiKey}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningTaobaoApiSecret">
淘宝 / 京东 API Secret
</Label>
<Input
id="productPlanningTaobaoApiSecret"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
taobaoApiSecret: e.target.value,
}))
}
placeholder="请输入 Onebound API Secret"
type="password"
value={productPlanningApiConfig.taobaoApiSecret}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningQaToken">问大家 Token</Label>
<Input
id="productPlanningQaToken"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
qaToken: e.target.value,
}))
}
placeholder="请输入问大家抓取 Token"
type="password"
value={productPlanningApiConfig.qaToken}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningSaleApiUrl">销量接口地址</Label>
<Input
id="productPlanningSaleApiUrl"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
saleApiUrl: e.target.value,
}))
}
placeholder="http://119.91.237.2:5432/taobao_item_month_sale"
value={productPlanningApiConfig.saleApiUrl}
/>
<div className="text-muted-foreground text-xs">
用于商品策划工具中的销量查询。
</div>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningSaleToken">销量 Token</Label>
<Input
id="productPlanningSaleToken"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
saleToken: e.target.value,
}))
}
placeholder="请输入销量查询 Token"
type="password"
value={productPlanningApiConfig.saleToken}
/>
</div>
<div className="rounded-lg border bg-muted/30 p-4 lg:col-span-2">
<div className="mb-4 flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div>
<div className="font-medium">企划数据库回写配置</div>
<div className="text-muted-foreground text-xs">
旧接口兜底查到的淘宝详情会同步写入新的企划详情表,供后续新接口命中。
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground">SSL</span>
<Switch
checked={productPlanningApiConfig.planningDbSsl}
onCheckedChange={(checked) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbSsl: checked,
}))
}
/>
</div>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="productPlanningDbHost">数据库 Host</Label>
<Input
id="productPlanningDbHost"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbHost: e.target.value,
}))
}
placeholder="156.225.27.52"
value={productPlanningApiConfig.planningDbHost}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDbPort">数据库端口</Label>
<Input
id="productPlanningDbPort"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbPort: e.target.value,
}))
}
placeholder="60023"
value={productPlanningApiConfig.planningDbPort}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDbDatabase">数据库名</Label>
<Input
id="productPlanningDbDatabase"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbDatabase: e.target.value,
}))
}
placeholder="planning_database"
value={productPlanningApiConfig.planningDbDatabase}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDbUser">数据库账号</Label>
<Input
id="productPlanningDbUser"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbUser: e.target.value,
}))
}
placeholder="planning_user"
value={productPlanningApiConfig.planningDbUser}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDbPassword">数据库密码</Label>
<Input
id="productPlanningDbPassword"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbPassword: e.target.value,
}))
}
placeholder="留空保存时会保留原密码"
type="password"
value={productPlanningApiConfig.planningDbPassword}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningDbCharset">字符集</Label>
<Input
id="productPlanningDbCharset"
onChange={(e) =>
setProductPlanningApiConfig((prev) => ({
...prev,
planningDbCharset: e.target.value,
}))
}
placeholder="utf8mb4"
value={productPlanningApiConfig.planningDbCharset}
/>
</div>
</div>
</div>
</div>
<div className="space-y-1 rounded-lg border border-blue-200 bg-blue-50 p-4 text-blue-800 text-xs">
<p>
• 当前功能状态:
{siteSettings.productPlanningEnabled
? "已启用,左侧导航和接口开放中。"
: "已关闭,入口、页面和接口都会被禁用。"}
</p>
<p>
•
竞品分析会优先读取详情增强接口;未命中时自动走旧接口,并将旧结果回写到企划详情表。
</p>
<p>
•
若某个字段留空,系统会继续尝试读取旧环境变量,避免你切换配置期间功能直接中断。
</p>
</div>
<div className="flex justify-end">
<Button onClick={handleSaveProductPlanningApiConfig}>
<Save className="mr-2 h-4 w-4" />
保存商品策划工具设置
</Button>
</div>
</LocalAiSectionShell>
<LocalAiSectionShell
description={
<>
为商品策划工具单独绑定一套分析模型。开启后,评价分析、问大家分析、竞品图片分析和
SKU 卖点分析会优先走这里配置的 OpenAI
兼容模型,不再依赖站点默认模型。
</>
}
expanded={isLocalAiSectionExpanded("product-planning-ai-model")}
id="local-ai-section-product-planning-ai-model"
onToggle={() => toggleLocalAiSection("product-planning-ai-model")}
summary={
<LocalAiSectionSummary
items={[
{
label: productPlanningAiModelConfig.enabled
? "专用模型已启用"
: "回退默认模型",
tone: productPlanningAiModelConfig.enabled
? "enabled"
: "neutral",
},
{
label: productPlanningAiModelConfig.modelId || "模型 ID 待配置",
tone: productPlanningAiModelConfig.modelId
? "neutral"
: "warning",
},
]}
/>
}
title="商品策划专用 AI 模型"
>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="flex-1 space-y-1">
<Label className="text-base">启用专用模型</Label>
<div className="text-muted-foreground text-sm">
推荐在商品策划需要固定分析口径时开启。关闭后会自动回退到站点当前可用的默认模型。
</div>
</div>
<Switch
checked={productPlanningAiModelConfig.enabled}
onCheckedChange={(checked) =>
setProductPlanningAiModelConfig((prev) => ({
...prev,
enabled: checked,
}))
}
/>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="productPlanningAiProviderName">提供商名称</Label>
<Input
id="productPlanningAiProviderName"
onChange={(e) =>
setProductPlanningAiModelConfig((prev) => ({
...prev,
providerName: e.target.value,
}))
}
placeholder="Product Planning Qwen3-VL"
value={productPlanningAiModelConfig.providerName}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningAiDisplayName">显示名称</Label>
<Input
id="productPlanningAiDisplayName"
onChange={(e) =>
setProductPlanningAiModelConfig((prev) => ({
...prev,
displayName: e.target.value,
}))
}
placeholder="商品策划专用 Qwen3-VL"
value={productPlanningAiModelConfig.displayName}
/>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningAiBaseUrl">
OpenAI 兼容 API 地址
</Label>
<Input
id="productPlanningAiBaseUrl"
onChange={(e) =>
setProductPlanningAiModelConfig((prev) => ({
...prev,
baseUrl: e.target.value,
}))
}
placeholder={PRODUCT_PLANNING_AI_MODEL_DEFAULT_BASE_URL}
value={productPlanningAiModelConfig.baseUrl}
/>
<div className="text-muted-foreground text-xs">
支持填写服务根地址,例如
`http://103.118.245.6:6081`。系统会自动兼容补全 `/v1` 路径。
</div>
</div>
<div className="space-y-2">
<Label htmlFor="productPlanningAiModelId">模型 ID</Label>
<Input
id="productPlanningAiModelId"
onChange={(e) =>
setProductPlanningAiModelConfig((prev) => ({
...prev,
modelId: e.target.value,
}))
}
placeholder={PRODUCT_PLANNING_AI_MODEL_DEFAULT_MODEL_ID}
value={productPlanningAiModelConfig.modelId}
/>
<div className="text-muted-foreground text-xs">
你的 Qwen3-VL OpenAI 兼容接口若使用默认模型名,可直接保留
`Qwen3-VL-4B-Instruct`。
</div>
</div>
<div className="space-y-2 lg:col-span-2">
<Label htmlFor="productPlanningAiApiKey">API Key</Label>
<Input
id="productPlanningAiApiKey"
onChange={(e) =>
setProductPlanningAiModelConfig((prev) => ({
...prev,
apiKey: e.target.value,
}))
}
placeholder="请输入商品策划专用模型 API Key"
type="password"
value={productPlanningAiModelConfig.apiKey}
/>
</div>
</div>
<div className="space-y-1 rounded-lg border border-blue-200 bg-blue-50 p-4 text-blue-800 text-xs">
<p>
•
开启后,商品策划页的文本分析和图片分析都会优先使用这里配置的独立模型。
</p>
<p>
•
关闭后,系统会继续回退到站点默认文本/视觉模型,不会影响其他页面的模型设置。
</p>
</div>
<div className="flex justify-end">
<Button onClick={handleSaveProductPlanningAiModelConfig}>
<Save className="mr-2 h-4 w-4" />
保存商品策划专用模型
</Button>
</div>
</LocalAiSectionShell>
<LocalAiSectionShell
description={
<>
为 AI详情生成 单独配置 Kimi Code 编程套餐
模型。保存后会自动绑定到“AI详情生成 - Agent专用模型”。
</>
}
expanded={isLocalAiSectionExpanded("moonshot-detail-agent")}
id="local-ai-section-moonshot-detail-agent"
onToggle={() => toggleLocalAiSection("moonshot-detail-agent")}
summary={
<LocalAiSectionSummary
items={[
{
label: moonshotDetailAgentConfig.isEnabled
? "模型已启用"
: "模型已关闭",
tone: moonshotDetailAgentConfig.isEnabled
? "enabled"
: "disabled",
},
{
label: moonshotDetailAgentConfig.modelId || "模型 ID 待配置",
tone: moonshotDetailAgentConfig.modelId ? "neutral" : "warning",
},
]}
/>
}
title={<>Kimi Code 编程套餐配置</>}
>
<div className="space-y-2">
<Label htmlFor="moonshotProviderName">提供商名称</Label>
<Input
id="moonshotProviderName"
onChange={(e) =>
setMoonshotDetailAgentConfig((prev) => ({
...prev,
providerName: e.target.value,
}))
}
placeholder="Kimi Code 编程套餐"
value={moonshotDetailAgentConfig.providerName}
/>
</div>
<div className="space-y-2">
<Label htmlFor="moonshotBaseUrl">编程套餐专用 API 地址</Label>
<Input
id="moonshotBaseUrl"
onChange={(e) =>
setMoonshotDetailAgentConfig((prev) => ({
...prev,
baseUrl: e.target.value,
}))
}
placeholder={DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL}
value={moonshotDetailAgentConfig.baseUrl}
/>
<div className="text-muted-foreground text-xs">
根据 Kimi Code 文档,编程套餐 OpenAI 兼容接口地址为 `
{DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL}`
</div>
</div>
<div className="space-y-2">
<Label htmlFor="moonshotApiKey">
API Key{" "}
{moonshotDetailAgentConfig.providerId ? "(留空则不修改)" : "*"}
</Label>
<Input
id="moonshotApiKey"
onChange={(e) =>
setMoonshotDetailAgentConfig((prev) => ({
...prev,
apiKey: e.target.value,
}))
}
placeholder={
moonshotDetailAgentConfig.providerId
? "留空则不修改当前 Key"
: "请输入 Kimi Code 控制台 API Key"
}
type="password"
value={moonshotDetailAgentConfig.apiKey}
/>
</div>
<div className="space-y-2">
<Label htmlFor="moonshotModelId">Kimi Code API 模型 ID *</Label>
<Input
id="moonshotModelId"
onChange={(e) =>
setMoonshotDetailAgentConfig((prev) => ({
...prev,
modelId: e.target.value,
}))
}
placeholder={DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID}
value={moonshotDetailAgentConfig.modelId}
/>
<div className="text-muted-foreground text-xs">
Kimi Code 编程套餐专用接口固定使用
`kimi-for-coding`;保存后系统会自动创建/更新对应模型记录。
</div>
</div>
<div className="space-y-2">
<Label htmlFor="moonshotDisplayName">模型显示名称</Label>
<Input
id="moonshotDisplayName"
onChange={(e) =>
setMoonshotDetailAgentConfig((prev) => ({
...prev,
displayName: e.target.value,
}))
}
placeholder={DETAIL_AGENT_LOCAL_MOONSHOT_DISPLAY_NAME}
value={moonshotDetailAgentConfig.displayName}
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<Label className="text-sm">启用该详情 Agent 模型</Label>
<div className="text-muted-foreground text-xs">
关闭后仍会保留记录,但不会被 AI详情生成 选作专用 Agent 模型。
</div>
</div>
<div className="flex items-center gap-2">
<Switch
checked={moonshotDetailAgentConfig.isEnabled}
onCheckedChange={(checked) =>
setMoonshotDetailAgentConfig((prev) => ({
...prev,
isEnabled: checked,
}))
}
/>
<span className="min-w-[3rem] font-medium text-muted-foreground text-sm">
{moonshotDetailAgentConfig.isEnabled ? "已开启" : "已关闭"}
</span>
</div>
</div>
<div className="space-y-1 rounded-lg border border-blue-200 bg-blue-50 p-4 text-blue-800 text-xs">
<p>
• 专用于 AI详情生成 的分镜规划与排版复审,不影响普通 Agent 对话。
</p>
<p>
• 系统会按多模态 Agent
模型写入:支持视觉、支持结构化规划、支持协议复审。
</p>
<p>• 保存成功后会自动写入“AI详情生成 - Agent专用模型”。</p>
</div>
<div className="flex justify-end">
<Button onClick={handleSaveMoonshotDetailAgentConfig}>
<Save className="mr-2 h-4 w-4" />
保存 Kimi Code 编程套餐配置
</Button>
</div>
</LocalAiSectionShell>
</>
);
}