feat: add sku goods info query support
This commit is contained in:
parent
47df3b644c
commit
8e225e9ae1
@ -58,6 +58,10 @@ import {
|
||||
defaultAgentWorkflowMaterialApiConfig,
|
||||
normalizeAgentWorkflowMaterialApiConfig,
|
||||
} from "@/lib/agents/workflow-material-config";
|
||||
import {
|
||||
defaultAgentWorkflowGoodsInfoApiConfig,
|
||||
normalizeAgentWorkflowGoodsInfoApiConfig,
|
||||
} from "@/lib/agents/workflow-goods-info-config";
|
||||
import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
@ -76,6 +80,7 @@ import type {
|
||||
AgentPresetLibrarySection,
|
||||
AgentWorkflowBrandModelConfig,
|
||||
AgentWorkflowBrandStoreEntry,
|
||||
AgentWorkflowGoodsInfoApiConfig,
|
||||
AgentWorkflowMaterialApiConfig,
|
||||
AgentWorkflowOptionUiConfig,
|
||||
AgentWorkflowShowcaseConfig,
|
||||
@ -396,6 +401,7 @@ export function AgentWorkflowSettingsClient({
|
||||
agentPresetLibraryConfig?: AgentPresetLibraryConfig;
|
||||
agentWorkflowUiConfig?: AgentWorkflowUiConfig;
|
||||
agentWorkflowMaterialApiConfig?: AgentWorkflowMaterialApiConfig;
|
||||
agentWorkflowGoodsInfoApiConfig?: AgentWorkflowGoodsInfoApiConfig;
|
||||
agentWorkflowBrandModelConfig?: AgentWorkflowBrandModelConfig;
|
||||
agentWorkflowShowcaseConfig?: AgentWorkflowShowcaseConfig;
|
||||
}>("/api/site-settings", fetcher);
|
||||
@ -406,6 +412,9 @@ export function AgentWorkflowSettingsClient({
|
||||
const [materialApiConfig, setMaterialApiConfig] = useState<AgentWorkflowMaterialApiConfig>(
|
||||
defaultAgentWorkflowMaterialApiConfig
|
||||
);
|
||||
const [goodsInfoApiConfig, setGoodsInfoApiConfig] = useState<AgentWorkflowGoodsInfoApiConfig>(
|
||||
defaultAgentWorkflowGoodsInfoApiConfig
|
||||
);
|
||||
const [showcaseConfig, setShowcaseConfig] = useState<AgentWorkflowShowcaseConfig>(
|
||||
defaultAgentWorkflowShowcaseConfig
|
||||
);
|
||||
@ -438,6 +447,9 @@ export function AgentWorkflowSettingsClient({
|
||||
setMaterialApiConfig(
|
||||
normalizeAgentWorkflowMaterialApiConfig(data.agentWorkflowMaterialApiConfig)
|
||||
);
|
||||
setGoodsInfoApiConfig(
|
||||
normalizeAgentWorkflowGoodsInfoApiConfig(data.agentWorkflowGoodsInfoApiConfig)
|
||||
);
|
||||
setShowcaseConfig(
|
||||
normalizeAgentWorkflowShowcaseConfig(data.agentWorkflowShowcaseConfig)
|
||||
);
|
||||
@ -887,6 +899,7 @@ export function AgentWorkflowSettingsClient({
|
||||
agentPresetLibraryConfig: presetLibraryConfig,
|
||||
agentWorkflowUiConfig: config,
|
||||
agentWorkflowMaterialApiConfig: materialApiConfig,
|
||||
agentWorkflowGoodsInfoApiConfig: goodsInfoApiConfig,
|
||||
agentWorkflowBrandModelConfig: brandModelConfig,
|
||||
agentWorkflowShowcaseConfig: showcaseConfig,
|
||||
}),
|
||||
@ -2054,6 +2067,79 @@ export function AgentWorkflowSettingsClient({
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card>
|
||||
<CardHeader className="pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-cyan-500/10">
|
||||
<Package className="h-4 w-4 text-cyan-600" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base">SKU 商品信息接口</CardTitle>
|
||||
<CardDescription>配置货号 / 条码查询商品资料接口</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between rounded-lg border bg-muted/20 px-4 py-3">
|
||||
<div>
|
||||
<div className="text-sm font-medium">启用商品信息查询</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
开启后 SKU 查询结果会显示商品名称、尺寸、材质、设计师、采购负责人、板材类型
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={goodsInfoApiConfig.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setGoodsInfoApiConfig((prev) => ({ ...prev, enabled: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs">接口基地址</Label>
|
||||
<Input
|
||||
value={goodsInfoApiConfig.baseUrl}
|
||||
onChange={(e) =>
|
||||
setGoodsInfoApiConfig((prev) => ({ ...prev, baseUrl: e.target.value }))
|
||||
}
|
||||
placeholder="https://www.jiansun.vip:63211"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs">Client Key</Label>
|
||||
<Input
|
||||
value={goodsInfoApiConfig.clientKey}
|
||||
onChange={(e) =>
|
||||
setGoodsInfoApiConfig((prev) => ({
|
||||
...prev,
|
||||
clientKey: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="例如:yunyingb"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs">Secret</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={goodsInfoApiConfig.secret}
|
||||
onChange={(e) =>
|
||||
setGoodsInfoApiConfig((prev) => ({
|
||||
...prev,
|
||||
secret: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="输入 Secret"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card>
|
||||
<CardHeader className="pb-4">
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { generateText } from "ai";
|
||||
import { createCipheriv, createHash } from "node:crypto";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
@ -9,6 +10,10 @@ import {
|
||||
AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
||||
normalizeAgentWorkflowMaterialApiConfig,
|
||||
} from "@/lib/agents/workflow-material-config";
|
||||
import {
|
||||
AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||
normalizeAgentWorkflowGoodsInfoApiConfig,
|
||||
} from "@/lib/agents/workflow-goods-info-config";
|
||||
|
||||
export const maxDuration = 180;
|
||||
|
||||
@ -62,10 +67,114 @@ type ExternalSearchResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
type ExternalGoodsInfoResponse = {
|
||||
success?: boolean;
|
||||
errMsg?: string | null;
|
||||
data?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
function normalizeBaseUrl(value: string) {
|
||||
return String(value || "").trim().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function sanitizeGoodsInfoText(value: unknown, maxLength = 255) {
|
||||
return String(value || "").trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
function createGoodsInfoSignature(timespan: string, secret: string) {
|
||||
const secretBuffer = Buffer.from(String(secret || "").trim(), "utf8");
|
||||
if (secretBuffer.length !== 8) {
|
||||
throw new Error("商品信息接口 Secret 长度必须为 8 位");
|
||||
}
|
||||
|
||||
const cipher = createCipheriv("des-cbc", secretBuffer, secretBuffer);
|
||||
cipher.setAutoPadding(true);
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(String(timespan || "").trim(), "utf8"),
|
||||
cipher.final(),
|
||||
]);
|
||||
const encryptedHex = encrypted.toString("hex").toUpperCase();
|
||||
return createHash("md5").update(encryptedHex, "utf8").digest("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function buildGoodsInfoSummary(data: Record<string, unknown>) {
|
||||
const productName =
|
||||
sanitizeGoodsInfoText(data["商品名称"], 255) ||
|
||||
sanitizeGoodsInfoText(data["名称"], 255) ||
|
||||
null;
|
||||
const sizeInfoParts = [
|
||||
sanitizeGoodsInfoText(data["商品尺寸"], 128),
|
||||
[data["长"], data["宽"], data["高"]]
|
||||
.map((item) => sanitizeGoodsInfoText(item, 32))
|
||||
.filter(Boolean)
|
||||
.join(" x "),
|
||||
].filter(Boolean);
|
||||
const materialInfoParts = [
|
||||
sanitizeGoodsInfoText(data["材质"], 128),
|
||||
sanitizeGoodsInfoText(data["材质类型"], 128),
|
||||
].filter(Boolean);
|
||||
|
||||
return {
|
||||
productName,
|
||||
sizeInfo: sizeInfoParts.join(" · ") || null,
|
||||
materialInfo: materialInfoParts.join(" · ") || null,
|
||||
designer: sanitizeGoodsInfoText(data["设计师"], 128) || null,
|
||||
buyer: sanitizeGoodsInfoText(data["采购负责人"], 128) || null,
|
||||
boardType: sanitizeGoodsInfoText(data["板材类型"], 128) || null,
|
||||
matchedSpu:
|
||||
sanitizeGoodsInfoText(data["spu"], 64) ||
|
||||
sanitizeGoodsInfoText(data["SPU"], 64) ||
|
||||
null,
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchGoodsInfoByKeyword(params: {
|
||||
keyword: string;
|
||||
config: ReturnType<typeof normalizeAgentWorkflowGoodsInfoApiConfig>;
|
||||
}) {
|
||||
const baseUrl = normalizeBaseUrl(params.config.baseUrl);
|
||||
const clientKey = String(params.config.clientKey || "").trim();
|
||||
const secret = String(params.config.secret || "").trim();
|
||||
|
||||
if (!params.config.enabled || !baseUrl || !clientKey || !secret) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timespan = String(Math.floor(Date.now() / 1000));
|
||||
const sign = createGoodsInfoSignature(timespan, secret);
|
||||
const commonHeaders = {
|
||||
Key: clientKey,
|
||||
Timespan: timespan,
|
||||
Sign: sign,
|
||||
Accept: "application/json",
|
||||
};
|
||||
const requestPaths = [
|
||||
`/api/GoodsInfo/SearchByProcode?procode=${encodeURIComponent(params.keyword)}`,
|
||||
`/api/GoodsInfo/SearchBySkuoid?skuoid=${encodeURIComponent(params.keyword)}`,
|
||||
];
|
||||
|
||||
for (const requestPath of requestPaths) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}${requestPath}`, {
|
||||
method: "GET",
|
||||
headers: commonHeaders,
|
||||
cache: "no-store",
|
||||
});
|
||||
const result = (await response.json().catch(() => ({}))) as ExternalGoodsInfoResponse;
|
||||
if (response.ok && result.success === true && result.data && typeof result.data === "object") {
|
||||
return buildGoodsInfoSummary(result.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Agent Workflow Material Search] Goods info query failed:", {
|
||||
requestPath,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeImageIdentityText(...values: Array<string | null | undefined>) {
|
||||
return values
|
||||
.map((value) => String(value || "").trim().toLowerCase().replace(/\s+/g, ""))
|
||||
@ -564,6 +673,12 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const setting = await getSystemSetting({ key: AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY });
|
||||
const config = normalizeAgentWorkflowMaterialApiConfig(setting?.value);
|
||||
const goodsInfoSetting = await getSystemSetting({
|
||||
key: AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||
});
|
||||
const goodsInfoConfig = normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||
goodsInfoSetting?.value
|
||||
);
|
||||
if (!config.enabled) {
|
||||
return NextResponse.json(
|
||||
{ error: "素材搜索功能未开启,请先在系统设置中启用" },
|
||||
@ -643,12 +758,23 @@ export async function GET(request: NextRequest) {
|
||||
totalCount: skuOptions.length,
|
||||
images: [],
|
||||
skuOptions,
|
||||
goodsInfo: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const matchedSku = String(upstreamResult.data.sku || normalizedKeyword).trim() || normalizedKeyword;
|
||||
const productModel = String(upstreamResult.data.productModel || "").trim() || null;
|
||||
const goodsInfo =
|
||||
queryType === "sku"
|
||||
? await fetchGoodsInfoByKeyword({
|
||||
keyword: normalizedKeyword,
|
||||
config: goodsInfoConfig,
|
||||
}).catch((error) => {
|
||||
console.warn("[Agent Workflow Material Search] Goods info fetch failed:", error);
|
||||
return null;
|
||||
})
|
||||
: null;
|
||||
const requiredCount = Number.isFinite(Number(parsed.requiredCount))
|
||||
? Math.max(1, Math.min(4, Math.round(Number(parsed.requiredCount))))
|
||||
: null;
|
||||
@ -734,7 +860,7 @@ export async function GET(request: NextRequest) {
|
||||
query: normalizedKeyword,
|
||||
queryType: "sku",
|
||||
matchedSku,
|
||||
matchedSpu: productModel,
|
||||
matchedSpu: goodsInfo?.matchedSpu || productModel,
|
||||
materialId: String(upstreamResult.data.materialId || "").trim() || null,
|
||||
productModel,
|
||||
zipUrl: String(upstreamResult.data.downloadUrls?.zip || "").trim() || null,
|
||||
@ -744,6 +870,16 @@ export async function GET(request: NextRequest) {
|
||||
images: importedImages,
|
||||
recommendedImageIds,
|
||||
skuOptions: [],
|
||||
goodsInfo: goodsInfo
|
||||
? {
|
||||
productName: goodsInfo.productName,
|
||||
sizeInfo: goodsInfo.sizeInfo,
|
||||
materialInfo: goodsInfo.materialInfo,
|
||||
designer: goodsInfo.designer,
|
||||
buyer: goodsInfo.buyer,
|
||||
boardType: goodsInfo.boardType,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -46,6 +46,12 @@ import {
|
||||
normalizeAgentWorkflowMaterialApiConfig,
|
||||
normalizeAgentWorkflowMaterialApiConfigForPublic,
|
||||
} from "@/lib/agents/workflow-material-config";
|
||||
import {
|
||||
AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||
defaultAgentWorkflowGoodsInfoApiConfig,
|
||||
normalizeAgentWorkflowGoodsInfoApiConfig,
|
||||
normalizeAgentWorkflowGoodsInfoApiConfigForPublic,
|
||||
} from "@/lib/agents/workflow-goods-info-config";
|
||||
import {
|
||||
AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
||||
defaultAgentWorkflowShowcaseConfig,
|
||||
@ -245,6 +251,7 @@ export async function GET() {
|
||||
agentSecondaryTypeConfigSetting,
|
||||
agentWorkflowUiConfigSetting,
|
||||
agentWorkflowMaterialApiConfigSetting,
|
||||
agentWorkflowGoodsInfoApiConfigSetting,
|
||||
agentWorkflowBrandModelConfigSetting,
|
||||
agentWorkflowShowcaseConfigSetting,
|
||||
] = await Promise.all([
|
||||
@ -375,6 +382,12 @@ export async function GET() {
|
||||
.where(eq(systemSettings.key, AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY))
|
||||
.limit(1)
|
||||
.then(rows => rows[0]),
|
||||
db
|
||||
.select()
|
||||
.from(systemSettings)
|
||||
.where(eq(systemSettings.key, AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY))
|
||||
.limit(1)
|
||||
.then(rows => rows[0]),
|
||||
db
|
||||
.select()
|
||||
.from(systemSettings)
|
||||
@ -439,6 +452,11 @@ export async function GET() {
|
||||
: normalizeAgentWorkflowMaterialApiConfigForPublic(
|
||||
agentWorkflowMaterialApiConfigSetting?.value
|
||||
);
|
||||
const agentWorkflowGoodsInfoApiConfig = canManageAgentWorkflow
|
||||
? normalizeAgentWorkflowGoodsInfoApiConfig(agentWorkflowGoodsInfoApiConfigSetting?.value)
|
||||
: normalizeAgentWorkflowGoodsInfoApiConfigForPublic(
|
||||
agentWorkflowGoodsInfoApiConfigSetting?.value
|
||||
);
|
||||
const agentWorkflowBrandModelConfig = normalizeAgentWorkflowBrandModelConfig(
|
||||
agentWorkflowBrandModelConfigSetting?.value
|
||||
);
|
||||
@ -475,6 +493,7 @@ export async function GET() {
|
||||
agentSecondaryTypeConfig: defaultAgentSecondaryTypeConfig,
|
||||
agentWorkflowUiConfig: defaultAgentWorkflowUiConfig,
|
||||
agentWorkflowMaterialApiConfig: defaultAgentWorkflowMaterialApiConfig,
|
||||
agentWorkflowGoodsInfoApiConfig: defaultAgentWorkflowGoodsInfoApiConfig,
|
||||
agentWorkflowBrandModelConfig: defaultAgentWorkflowBrandModelConfig,
|
||||
agentWorkflowShowcaseConfig: defaultAgentWorkflowShowcaseConfig,
|
||||
realtimeCanvasEnabled: true,
|
||||
@ -524,6 +543,7 @@ export async function GET() {
|
||||
agentSecondaryTypeConfig,
|
||||
agentWorkflowUiConfig,
|
||||
agentWorkflowMaterialApiConfig,
|
||||
agentWorkflowGoodsInfoApiConfig,
|
||||
agentWorkflowBrandModelConfig,
|
||||
agentWorkflowShowcaseConfig,
|
||||
membershipConfig,
|
||||
@ -696,6 +716,14 @@ const updateSchema = z.object({
|
||||
apiKey: z.string().trim().max(500).optional(),
|
||||
})
|
||||
.optional(),
|
||||
agentWorkflowGoodsInfoApiConfig: z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
baseUrl: z.string().trim().max(255).optional(),
|
||||
clientKey: z.string().trim().max(255).optional(),
|
||||
secret: z.string().trim().max(255).optional(),
|
||||
})
|
||||
.optional(),
|
||||
agentWorkflowBrandModelConfig: z
|
||||
.object({
|
||||
entries: z
|
||||
@ -807,6 +835,7 @@ export async function PUT(request: NextRequest) {
|
||||
"agentPresetLibraryConfig",
|
||||
"agentWorkflowUiConfig",
|
||||
"agentWorkflowMaterialApiConfig",
|
||||
"agentWorkflowGoodsInfoApiConfig",
|
||||
"agentWorkflowBrandModelConfig",
|
||||
"agentWorkflowShowcaseConfig",
|
||||
]);
|
||||
@ -829,6 +858,7 @@ export async function PUT(request: NextRequest) {
|
||||
agentSecondaryTypeConfig,
|
||||
agentWorkflowUiConfig,
|
||||
agentWorkflowMaterialApiConfig,
|
||||
agentWorkflowGoodsInfoApiConfig,
|
||||
agentWorkflowBrandModelConfig,
|
||||
agentWorkflowShowcaseConfig,
|
||||
membershipConfig,
|
||||
@ -1097,6 +1127,28 @@ export async function PUT(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
if (agentWorkflowGoodsInfoApiConfig !== undefined) {
|
||||
const normalized = normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||
agentWorkflowGoodsInfoApiConfig
|
||||
);
|
||||
await db
|
||||
.insert(systemSettings)
|
||||
.values({
|
||||
key: AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||
value: normalized,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: session.user.id,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: systemSettings.key,
|
||||
set: {
|
||||
value: normalized,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: session.user.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (agentWorkflowBrandModelConfig !== undefined) {
|
||||
const normalized = normalizeAgentWorkflowBrandModelConfig(
|
||||
agentWorkflowBrandModelConfig
|
||||
@ -1323,6 +1375,10 @@ export async function PUT(request: NextRequest) {
|
||||
agentWorkflowMaterialApiConfig !== undefined
|
||||
? normalizeAgentWorkflowMaterialApiConfig(agentWorkflowMaterialApiConfig)
|
||||
: defaultAgentWorkflowMaterialApiConfig,
|
||||
agentWorkflowGoodsInfoApiConfig:
|
||||
agentWorkflowGoodsInfoApiConfig !== undefined
|
||||
? normalizeAgentWorkflowGoodsInfoApiConfig(agentWorkflowGoodsInfoApiConfig)
|
||||
: defaultAgentWorkflowGoodsInfoApiConfig,
|
||||
agentWorkflowBrandModelConfig:
|
||||
agentWorkflowBrandModelConfig !== undefined
|
||||
? normalizeAgentWorkflowBrandModelConfig(agentWorkflowBrandModelConfig)
|
||||
@ -1868,6 +1924,10 @@ export async function PUT(request: NextRequest) {
|
||||
agentWorkflowMaterialApiConfig !== undefined
|
||||
? normalizeAgentWorkflowMaterialApiConfig(agentWorkflowMaterialApiConfig)
|
||||
: defaultAgentWorkflowMaterialApiConfig,
|
||||
agentWorkflowGoodsInfoApiConfig:
|
||||
agentWorkflowGoodsInfoApiConfig !== undefined
|
||||
? normalizeAgentWorkflowGoodsInfoApiConfig(agentWorkflowGoodsInfoApiConfig)
|
||||
: defaultAgentWorkflowGoodsInfoApiConfig,
|
||||
agentWorkflowBrandModelConfig:
|
||||
agentWorkflowBrandModelConfig !== undefined
|
||||
? normalizeAgentWorkflowBrandModelConfig(agentWorkflowBrandModelConfig)
|
||||
|
||||
@ -73,6 +73,33 @@ export function AgentWorkflowMaterialPicker({
|
||||
const hasSelectableImages = Boolean(
|
||||
result?.images?.some((item) => Boolean(String(item.imageUrl || "").trim()))
|
||||
);
|
||||
const goodsInfoItems = [
|
||||
{
|
||||
label: "商品名称",
|
||||
value: result?.goodsInfo?.productName || null,
|
||||
fullWidth: true,
|
||||
},
|
||||
{
|
||||
label: "尺寸信息",
|
||||
value: result?.goodsInfo?.sizeInfo || null,
|
||||
},
|
||||
{
|
||||
label: "材质信息",
|
||||
value: result?.goodsInfo?.materialInfo || null,
|
||||
},
|
||||
{
|
||||
label: "设计师",
|
||||
value: result?.goodsInfo?.designer || null,
|
||||
},
|
||||
{
|
||||
label: "采购负责人",
|
||||
value: result?.goodsInfo?.buyer || null,
|
||||
},
|
||||
{
|
||||
label: "板材类型",
|
||||
value: result?.goodsInfo?.boardType || null,
|
||||
},
|
||||
].filter((item) => Boolean(item.value));
|
||||
const remainingSelectableCount =
|
||||
resolvedSelectionLimit === null ? null : Math.max(resolvedSelectionLimit - selectedCount, 0);
|
||||
|
||||
@ -309,6 +336,36 @@ export function AgentWorkflowMaterialPicker({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{goodsInfoItems.length > 0 ? (
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-2 rounded-[24px] border p-3",
|
||||
isDark
|
||||
? "border-white/12 bg-white/[0.03]"
|
||||
: "border-border bg-background/70",
|
||||
"grid-cols-1 md:grid-cols-2 xl:grid-cols-3"
|
||||
)}
|
||||
>
|
||||
{goodsInfoItems.map((item) => (
|
||||
<div
|
||||
key={`goods-info-${item.label}`}
|
||||
className={cn(
|
||||
"rounded-[18px] border px-3 py-2",
|
||||
isDark ? "border-white/8 bg-black/20" : "border-border bg-muted/30",
|
||||
item.fullWidth ? "md:col-span-2 xl:col-span-3" : ""
|
||||
)}
|
||||
>
|
||||
<div className={cn("text-[11px]", isDark ? "text-white/45" : "text-muted-foreground")}>
|
||||
{item.label}
|
||||
</div>
|
||||
<div className={cn("mt-1 text-sm font-medium leading-6", isDark ? "text-white/90" : "text-foreground")}>
|
||||
{item.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{result.skuOptions?.length ? (
|
||||
<div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
|
||||
{result.skuOptions.map((option) => (
|
||||
|
||||
44
lib/agents/workflow-goods-info-config.ts
Normal file
44
lib/agents/workflow-goods-info-config.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { AgentWorkflowGoodsInfoApiConfig } from "@/types/agent";
|
||||
|
||||
export const AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY =
|
||||
"agent_workflow_goods_info_api_config";
|
||||
|
||||
export const defaultAgentWorkflowGoodsInfoApiConfig: AgentWorkflowGoodsInfoApiConfig = {
|
||||
enabled: false,
|
||||
baseUrl: "https://www.jiansun.vip:63211",
|
||||
clientKey: "",
|
||||
secret: "",
|
||||
};
|
||||
|
||||
function sanitizeText(value: unknown, maxLength: number) {
|
||||
return String(value || "").trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
export function normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||
value: unknown
|
||||
): AgentWorkflowGoodsInfoApiConfig {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return defaultAgentWorkflowGoodsInfoApiConfig;
|
||||
}
|
||||
|
||||
const raw = value as Partial<AgentWorkflowGoodsInfoApiConfig>;
|
||||
|
||||
return {
|
||||
enabled: raw.enabled === true,
|
||||
baseUrl:
|
||||
sanitizeText(raw.baseUrl, 255) || defaultAgentWorkflowGoodsInfoApiConfig.baseUrl,
|
||||
clientKey: sanitizeText(raw.clientKey, 255),
|
||||
secret: sanitizeText(raw.secret, 255),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeAgentWorkflowGoodsInfoApiConfigForPublic(
|
||||
value: unknown
|
||||
): AgentWorkflowGoodsInfoApiConfig {
|
||||
const config = normalizeAgentWorkflowGoodsInfoApiConfig(value);
|
||||
return {
|
||||
...config,
|
||||
clientKey: "",
|
||||
secret: "",
|
||||
};
|
||||
}
|
||||
@ -121,6 +121,17 @@ function normalizeMaterialSearchResult(value: unknown): AgentWorkflowMaterialSea
|
||||
)
|
||||
: [],
|
||||
skuOptions,
|
||||
goodsInfo:
|
||||
raw.goodsInfo && typeof raw.goodsInfo === "object" && !Array.isArray(raw.goodsInfo)
|
||||
? {
|
||||
productName: sanitizeText((raw.goodsInfo as Record<string, unknown>).productName, 255) || null,
|
||||
sizeInfo: sanitizeText((raw.goodsInfo as Record<string, unknown>).sizeInfo, 255) || null,
|
||||
materialInfo: sanitizeText((raw.goodsInfo as Record<string, unknown>).materialInfo, 255) || null,
|
||||
designer: sanitizeText((raw.goodsInfo as Record<string, unknown>).designer, 128) || null,
|
||||
buyer: sanitizeText((raw.goodsInfo as Record<string, unknown>).buyer, 128) || null,
|
||||
boardType: sanitizeText((raw.goodsInfo as Record<string, unknown>).boardType, 128) || null,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -118,6 +118,13 @@ export interface AgentWorkflowMaterialApiConfig {
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
export interface AgentWorkflowGoodsInfoApiConfig {
|
||||
enabled: boolean;
|
||||
baseUrl: string;
|
||||
clientKey: string;
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export type AgentWorkflowMaterialQueryType = "sku" | "spu";
|
||||
|
||||
export interface AgentWorkflowBrandModelImage {
|
||||
@ -190,6 +197,14 @@ export interface AgentWorkflowMaterialSearchResult {
|
||||
images: AgentWorkflowMaterialImage[];
|
||||
recommendedImageIds?: string[];
|
||||
skuOptions?: AgentWorkflowMaterialSkuOption[];
|
||||
goodsInfo?: {
|
||||
productName?: string | null;
|
||||
sizeInfo?: string | null;
|
||||
materialInfo?: string | null;
|
||||
designer?: string | null;
|
||||
buyer?: string | null;
|
||||
boardType?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface AgentWorkflowMaterialSelectionState {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user