You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
9.1 KiB
196 lines
9.1 KiB
/**
|
|
* 配置解析模块 - 参数校验 + 默认值填充
|
|
*
|
|
* 参数映射:
|
|
* integrateId → 后端 roleId(客服角色 ID)
|
|
* userId → 后端 accountId(客户账号 ID)
|
|
* chatId → 自动管理(不在用户配置中暴露)
|
|
*/
|
|
import { SDKConfig, ResolvedConfig } from './types';
|
|
import { logger } from './logger';
|
|
|
|
/** 默认悬浮按钮 SVG 图标(赛博机器人:对话气泡 + 机器人面孔 + 天线能量灯 + 眨眼动效) */
|
|
const DEFAULT_LAUNCHER_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 32 32" fill="none">
|
|
<defs>
|
|
<linearGradient id="csk-ico-grad" x1="6" y1="8" x2="26" y2="28" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0%" stop-color="#fff" stop-opacity="1"/>
|
|
<stop offset="100%" stop-color="#fff" stop-opacity="0.82"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect x="5" y="8" width="22" height="16" rx="4" fill="url(#csk-ico-grad)"/>
|
|
<path d="M9 24L6 28.5l6-4.5z" fill="url(#csk-ico-grad)"/>
|
|
<line x1="16" y1="8" x2="16" y2="4" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/>
|
|
<circle cx="16" cy="3" r="1.5" fill="#fff" class="csk-ico-antenna"/>
|
|
<rect x="9.5" y="13" width="4" height="3.5" rx="1.2" fill="rgba(99,102,241,0.55)" class="csk-ico-eye"/>
|
|
<rect x="18.5" y="13" width="4" height="3.5" rx="1.2" fill="rgba(99,102,241,0.55)" class="csk-ico-eye"/>
|
|
<circle cx="11" cy="14.2" r="0.7" fill="#fff" opacity="0.9"/>
|
|
<circle cx="20" cy="14.2" r="0.7" fill="#fff" opacity="0.9"/>
|
|
<path d="M12.5 20 Q16 22.5 19.5 20" stroke="rgba(99,102,241,0.4)" stroke-width="1.2" fill="none" stroke-linecap="round"/>
|
|
</svg>`;
|
|
|
|
// ==================== 马卡龙色系玻璃质感图标 ====================
|
|
|
|
/**
|
|
* 生成玻璃质感机器人 SVG 图标
|
|
* 包含:圆润机器人头部 + 眨眼动效 + 跳动气泡 + 高光反射层
|
|
*/
|
|
function buildGlassIcon(eyeColor: string, smileColor: string, bubbleColor: string): string {
|
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 40 40" fill="none">
|
|
<defs>
|
|
<!-- 高光渐变:模拟玻璃反射 -->
|
|
<linearGradient id="csk-glass-hi" x1="0" y1="0" x2="1" y2="1">
|
|
<stop offset="0%" stop-color="#fff" stop-opacity="0.72"/>
|
|
<stop offset="100%" stop-color="#fff" stop-opacity="0.18"/>
|
|
</linearGradient>
|
|
<!-- 机器人身体渐变 -->
|
|
<linearGradient id="csk-bot-body" x1="8" y1="10" x2="32" y2="34" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0%" stop-color="#fff" stop-opacity="0.95"/>
|
|
<stop offset="100%" stop-color="#fff" stop-opacity="0.78"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<!-- 机器人主体(圆角矩形 + 对话气泡尾巴) -->
|
|
<rect x="7" y="10" width="26" height="18" rx="5" fill="url(#csk-bot-body)" stroke="rgba(255,255,255,0.6)" stroke-width="0.8"/>
|
|
<path d="M12 28L8 33.5l7-5.5z" fill="url(#csk-bot-body)" stroke="rgba(255,255,255,0.5)" stroke-width="0.5"/>
|
|
<!-- 高光反射层(左上角弧形高光) -->
|
|
<ellipse cx="16" cy="14" rx="9" ry="4.5" fill="url(#csk-glass-hi)" opacity="0.55"/>
|
|
<!-- 天线 + 能量球 -->
|
|
<line x1="20" y1="10" x2="20" y2="5" stroke="#fff" stroke-width="1.6" stroke-linecap="round" opacity="0.85"/>
|
|
<circle cx="20" cy="4" r="2" fill="#fff" opacity="0.9" class="csk-ico-antenna"/>
|
|
<!-- 眼睛(会眨) -->
|
|
<rect x="12" y="16" width="4.5" height="4" rx="1.5" fill="${eyeColor}" class="csk-ico-eye"/>
|
|
<rect x="23.5" y="16" width="4.5" height="4" rx="1.5" fill="${eyeColor}" class="csk-ico-eye"/>
|
|
<!-- 眼睛高光点 -->
|
|
<circle cx="13.5" cy="17" r="0.9" fill="#fff" opacity="0.95"/>
|
|
<circle cx="25" cy="17" r="0.9" fill="#fff" opacity="0.95"/>
|
|
<!-- 微笑 -->
|
|
<path d="M16 23 Q20 25.8 24 23" stroke="${smileColor}" stroke-width="1.4" fill="none" stroke-linecap="round"/>
|
|
<!-- 跳动气泡装饰(3 个小圆点) -->
|
|
<circle cx="31" cy="8" r="2.2" fill="${bubbleColor}" class="csk-ico-bubble csk-ico-bubble--1"/>
|
|
<circle cx="35" cy="5" r="1.6" fill="${bubbleColor}" class="csk-ico-bubble csk-ico-bubble--2" opacity="0.7"/>
|
|
<circle cx="28" cy="4.5" r="1.2" fill="${bubbleColor}" class="csk-ico-bubble csk-ico-bubble--3" opacity="0.5"/>
|
|
</svg>`;
|
|
}
|
|
|
|
/** 梦幻紫粉主题图标 */
|
|
const GLASS_ICON_DREAM_PURPLE = buildGlassIcon(
|
|
'rgba(139,92,246,0.6)', // 眼睛:紫色
|
|
'rgba(139,92,246,0.45)', // 微笑
|
|
'rgba(240,171,252,0.85)' // 气泡:粉紫
|
|
);
|
|
|
|
/** 薄荷青绿主题图标 */
|
|
const GLASS_ICON_MINT_TECH = buildGlassIcon(
|
|
'rgba(16,185,129,0.6)', // 眼睛:翠绿
|
|
'rgba(16,185,129,0.45)', // 微笑
|
|
'rgba(110,231,183,0.85)' // 气泡:薄荷
|
|
);
|
|
|
|
/** 珊瑚蜜桃主题图标 */
|
|
const GLASS_ICON_CORAL_PEACH = buildGlassIcon(
|
|
'rgba(244,63,94,0.55)', // 眼睛:珊瑚红
|
|
'rgba(244,63,94,0.4)', // 微笑
|
|
'rgba(253,186,116,0.85)' // 气泡:蜜桃
|
|
);
|
|
|
|
/** 天空蓝紫主题图标 */
|
|
const GLASS_ICON_SKY_BLUE = buildGlassIcon(
|
|
'rgba(56,189,248,0.6)', // 眼睛:天蓝
|
|
'rgba(56,189,248,0.45)', // 微笑
|
|
'rgba(167,139,250,0.85)' // 气泡:淡紫
|
|
);
|
|
|
|
/** 主题 → 默认主色映射(当用户未指定 primaryColor 时自动使用) */
|
|
const THEME_PRIMARY_COLORS: Record<string, string> = {
|
|
'dream-purple': '#A78BFA',
|
|
'mint-tech': '#34D399',
|
|
'coral-peach': '#FB7185',
|
|
'sky-blue': '#7DD3FC',
|
|
};
|
|
|
|
/**
|
|
* 解析并校验用户传入的配置,填充默认值
|
|
*/
|
|
export function parseConfig(raw: SDKConfig): ResolvedConfig | null {
|
|
// 校验必传参数:integrateId(对应后端 roleId)
|
|
if (!raw.integrateId || (typeof raw.integrateId !== 'string' && typeof raw.integrateId !== 'number')
|
|
|| (typeof raw.integrateId === 'string' && raw.integrateId.trim() === '')) {
|
|
logger.error('integrateId 是必传参数(对应后端 roleId 客服角色 ID),请检查 init() 调用。示例:ChatbotSDK.init({ integrateId: 1, requestDomain: "https://api.example.com" })');
|
|
return null;
|
|
}
|
|
|
|
// 校验必传参数:requestDomain
|
|
if (!raw.requestDomain || typeof raw.requestDomain !== 'string' || raw.requestDomain.trim() === '') {
|
|
logger.error('requestDomain 是必传参数,请检查 init() 调用。示例:ChatbotSDK.init({ integrateId: 1, requestDomain: "https://api.example.com" })');
|
|
return null;
|
|
}
|
|
|
|
// 校验 requestDomain 是否为合法 URL 格式
|
|
try {
|
|
new URL(raw.requestDomain);
|
|
} catch {
|
|
logger.error(`requestDomain 不是合法的 URL 格式:${raw.requestDomain}。请提供完整的域名,如 https://api.example.com`);
|
|
return null;
|
|
}
|
|
|
|
// integrateId 统一转为字符串(后端 roleId 为 Long,但 query param 传字符串也可接收)
|
|
const integrateIdStr = String(raw.integrateId).trim();
|
|
|
|
// 解析 launcherTheme:根据主题自动选择图标和默认主色
|
|
const launcherTheme = raw.launcherTheme || undefined;
|
|
let resolvedLauncherIcon = raw.launcherIcon || DEFAULT_LAUNCHER_ICON;
|
|
let resolvedPrimaryColor = raw.primaryColor || '#4F46E5';
|
|
|
|
if (launcherTheme && !raw.launcherIcon) {
|
|
// 有主题且未自定义图标 → 使用主题专属玻璃质感图标
|
|
const themeIconsMap: Record<string, string> = {
|
|
'dream-purple': GLASS_ICON_DREAM_PURPLE,
|
|
'mint-tech': GLASS_ICON_MINT_TECH,
|
|
'coral-peach': GLASS_ICON_CORAL_PEACH,
|
|
'sky-blue': GLASS_ICON_SKY_BLUE,
|
|
};
|
|
resolvedLauncherIcon = themeIconsMap[launcherTheme] || DEFAULT_LAUNCHER_ICON;
|
|
|
|
// 若未自定义主色 → 使用主题默认色
|
|
if (!raw.primaryColor) {
|
|
resolvedPrimaryColor = THEME_PRIMARY_COLORS[launcherTheme] || '#4F46E5';
|
|
}
|
|
}
|
|
|
|
// 填充默认值
|
|
const config: ResolvedConfig = {
|
|
integrateId: integrateIdStr,
|
|
requestDomain: raw.requestDomain.replace(/\/+$/, ''), // 去掉末尾斜杠
|
|
userId: raw.userId,
|
|
categoryId: raw.categoryId,
|
|
showCategorySwitch: raw.showCategorySwitch ?? false,
|
|
title: raw.title || 'AI 智能助手',
|
|
width: raw.width ?? 380,
|
|
height: Math.max(300, raw.height ?? 520),
|
|
position: raw.position === 'left-bottom' ? 'left-bottom' : 'right-bottom',
|
|
primaryColor: resolvedPrimaryColor,
|
|
launcherTheme: launcherTheme,
|
|
launcherIcon: resolvedLauncherIcon,
|
|
showClear: raw.showClear ?? true,
|
|
showAdminPanel: raw.showAdminPanel ?? false,
|
|
quickReplies: Array.isArray(raw.quickReplies)
|
|
? raw.quickReplies.map(s => String(s).trim()).filter(Boolean)
|
|
: [],
|
|
theme: raw.theme === 'dark' ? 'dark' : 'light',
|
|
showTeaser: raw.showTeaser ?? true,
|
|
teaserText: (typeof raw.teaserText === 'string' && raw.teaserText.trim()) || '',
|
|
streaming: raw.streaming ?? true,
|
|
enableRag: raw.enableRag ?? true,
|
|
rewriteStrategy: raw.rewriteStrategy || 'REWRITE',
|
|
locale: raw.locale || 'zh-CN',
|
|
debug: raw.debug ?? true,
|
|
sound: raw.sound ?? false,
|
|
notification: raw.notification ?? false,
|
|
onError: typeof raw.onError === 'function' ? raw.onError : undefined,
|
|
onReady: typeof raw.onReady === 'function' ? raw.onReady : undefined,
|
|
onMessage: typeof raw.onMessage === 'function' ? raw.onMessage : undefined,
|
|
chatId: '', // 初始为空,由 chatId 初始化流程填充
|
|
};
|
|
|
|
logger.info(`配置解析完成 integrateId(=roleId)=${config.integrateId} userId(=accountId)=${config.userId || '(未设置)'} requestDomain=${config.requestDomain}`);
|
|
return config;
|
|
}
|