/**
* TalkingQ Platform - Agent Configuration Workbench
* Pure JS component functions following the existing static SPA router.
*/
const AGENT_CONFIG_STEPS = [
{ id: 'voice', index: 1, title: '语音交互' },
{ id: 'understand', index: 2, title: '理解和生成' },
{ id: 'skills', index: 3, title: '技能' },
{ id: 'advanced', index: 4, title: '高级设置' },
];
const AGENT_CONFIG_DEFAULT_APPS = [
{ id: 'app-001', type: 'multimodal', name: '小Q 故事机器人', status: 'published' },
{ id: 'app-002', type: 'voice', name: '英语学习助手', status: 'editing' },
{ id: 'app-003', type: 'voice', name: '科学百科探索者', status: 'draft' },
{ id: 'app-004', type: 'multimodal', name: '数学小课堂', status: 'published' },
];
const AGENT_SCENE_TEMPLATES = [
{ id: 'ai-audio-glasses', name: 'AI音频眼镜', icon: 'headphones', desc: '随身携带的语音助手' },
{ id: 'ar-glasses', name: 'AR眼镜', icon: 'glasses', desc: '具备屏幕显示能力' },
{ id: 'desktop-robot', name: '桌面机器人', icon: 'bot', desc: '实时语音对话,日常情感陪伴' },
{ id: 'smart-home', name: '智能家居', icon: 'home', desc: '语音控制家中设备' },
{ id: 'toy', name: '儿童玩具', icon: 'star', desc: '陪伴孩子聊天、放音乐、讲故事' },
{ id: 'story-player', name: '儿童故事机', icon: 'book-open', desc: '给孩子讲故事、放儿歌' },
{ id: 'smart-speaker', name: '智能音箱', icon: 'volume-2', desc: '语音控制播放音乐' },
{ id: 'mobile-app', name: '手机应用', icon: 'smartphone', desc: '实时语音互动应用' },
];
const AGENT_SCENE_VERSIONS = [
{
id: 'full',
name: '全能版',
desc: '支持意图识别、工具调用、联网搜索、多场景 Agent,适合完整智能体体验。',
},
{
id: 'lite',
name: '轻量版',
desc: '更快速、更低价的语音闲聊,不启用复杂工具调用和 Agent 编排。',
},
];
const AGENT_SCENE_LANGUAGES = [
{ id: 'zh', name: '中文', desc: '适合中文普通话和中文儿童内容场景' },
{ id: 'en', name: '英文', desc: '适合英语对话、英语学习和双语启蒙场景' },
];
const AGENT_CONFIG_MOCK = {
voice: {
languageOptions: [
{ id: 'zh', label: '中文' },
{ id: 'en', label: '英文' },
],
asrModels: [
{
id: 'fun-asr',
icon: 'audio-lines',
label: 'Fun-ASR实时语音识别',
desc: '中英文自由切换,多地区方言覆盖,适应多样复杂环境。',
},
{
id: 'qwen3-asr',
icon: 'audio-lines',
label: '通义千问3-ASR-Flash-Realtime',
desc: '高精度语音识别,中英日韩德法俄意西葡阿和多种方言。',
},
{
id: 'paraformer',
icon: 'audio-lines',
label: 'Paraformer实时语音识别',
desc: '新一代非自回归端到端模型,支持中英日韩德法俄等语言。',
},
{
id: 'multimodal-lite-asr',
icon: 'audio-lines',
label: '多模态交互轻量版语音识别',
desc: '超低价格,预算友好,支持中英等语言。',
},
],
ttsGroups: [
{
id: 'cosyvoice',
title: 'CosyVoice-实时语音合成',
models: [
{ id: 'cosyvoice-v3-flash', label: 'CosyVoice-v3-Flash大模型', desc: '独家音色,性价比高,支持情感控制。' },
{ id: 'cosyvoice-v3-plus', label: 'CosyVoice-v3-Plus大模型', desc: '情感表达最佳,支持声音复刻。' },
{ id: 'cosyvoice-v35-plus', label: 'CosyVoice-v3.5-Plus大模型', desc: '声音设计/复刻最佳,多种方言和外语。' },
{ id: 'cosyvoice-v35-flash', label: 'CosyVoice-v3.5-Flash大模型', desc: '声音设计/复刻性价比高,多种方言和外语。' },
{ id: 'cosyvoice-v2', label: 'CosyVoice-v2大模型', desc: '音色丰富,70+音色可选,一句话声音复刻。' },
],
},
{
id: 'qwen-tts',
title: '通义千问3-实时语音合成',
models: [
{ id: 'qwen3-tts-flash', label: '通义千问3-TTS-Flash-Realtime', desc: '多语言,拥有17种高表现力的拟人音色。' },
{ id: 'qwen3-tts-instruct', label: '通义千问3-TTS-Instruct-Flash-Realtime', desc: '支持指令控制,调整音调、语速、情感、特点。' },
{ id: 'qwen3-tts-design', label: '通义千问3-TTS-声音设计', desc: '通过文本描述生成定制化音色,支持多维度定义。' },
{ id: 'qwen3-tts-clone', label: '通义千问3-TTS-声音复刻', desc: '10-20秒音频即可生成效果最佳的定制音色。' },
],
},
{
id: 'others',
title: '其他语音合成模型',
models: [
{ id: 'sambert-tts', label: 'Sambert-实时语音合成', desc: '音色选择多,多语言,合成速度快。' },
{ id: 'lite-tts', label: '多模态交互轻量版语音合成', desc: '预算友好,支持多语种和方言。' },
],
},
],
defaultHotwords: ['TalkingQ', '小Q', '白雪公主', '睡前故事', '恐龙百科'],
correctionHint: '通过接口上传词表,对语音识别结果进行实时干预。',
},
understand: {
intentModel: {
icon: 'route',
label: '意图识别',
desc: '关闭将不再触发调用工具/Agent 等基于语义理解的交互功能。',
},
textModels: [
{ id: 'recommended-fast', group: '推荐模型', label: '多模态交互专有模型-高速版', desc: '对话速度更快,适合通用场景、闲聊陪伴场景。' },
{ id: 'recommended-tool', group: '推荐模型', label: '多模态交互专有模型', desc: '指令遵循能力更强,适合工作学习场景。' },
{ id: 'recommended-style', group: '推荐模型', label: '多模态交互专有模型-风格化版', desc: '风格化能力更强,适合角色扮演等场景。' },
{ id: 'qwen', group: '更多模型', label: '多千问', desc: '通用能力均衡,适合大多数任务。' },
{ id: 'deepseek', group: '更多模型', label: 'DeepSeek', desc: '推理和代码表现更强。' },
{ id: 'chatgpt', group: '更多模型', label: 'ChatGPT', desc: '多场景通用对话能力。' },
{ id: 'doubao', group: '更多模型', label: '豆包', desc: '适合中文对话和内容生成。' },
],
prompt: `# 角色
你是小Q,一个专为 3-10 岁儿童设计的 AI 玩具智能体。
## 沟通风格
- 语言简单、生动、温暖。
- 回复通常控制在 150 字以内。
- 优先使用比喻、拟声词和故事化表达。
- 不能输出任何不适合儿童的危险内容。
## 行为准则
1. 当孩子要求讲故事时,先确认主题,再生成短故事。
2. 当孩子提问百科知识时,先给出结论,再给出一个生活例子。
3. 当识别到情绪陪伴需求时,优先安抚,再引导表达。
\`\`\`policy
安全等级:儿童模式
联网搜索:必要时开启
知识库优先级:高
\`\`\``,
variables: [
{ name: 'child_age', desc: '儿童年龄' },
{ name: 'device_mode', desc: '设备当前模式' },
{ name: 'ambient_noise', desc: '环境噪音等级' },
{ name: 'weather', desc: '当前位置天气' },
],
knowledgeBases: [
{ name: '儿童科学百科', desc: '动物、太空、自然科学问答素材', weight: '0.72' },
{ name: '故事素材库 V2', desc: '睡前故事、角色设定、儿童互动剧情', weight: '0.68' },
],
},
skills: {
commands: [
{ icon: 'phone-call', name: '打电话', count: 6, desc: '拨打联系人电话、语音拨号等电话相关控制' },
{ icon: 'alarm-clock', name: '定闹钟', count: 1, desc: '创建、关闭、修改闹钟' },
{ icon: 'volume-2', name: '音量设置', count: 7, desc: '调节媒体音量、铃声音量、静音等' },
{ icon: 'bluetooth', name: '蓝牙', count: 4, desc: '打开/关闭蓝牙、连接蓝牙设备' },
{ icon: 'toggle-right', name: '应用开关', count: 10, desc: '打开/关闭指定 App 或系统功能' },
{ icon: 'cpu', name: '设备控制', count: 5, desc: '系统级设备控制能力(当前已选中)' },
{ icon: 'music', name: '音乐播放', count: 5, desc: '播放音乐、暂停、下一首等' },
{ icon: 'circle-play', name: '多媒体控制', count: 6, desc: '控制媒体播放状态与音频输出' },
{ icon: 'camera', name: '拍照录像', count: 6, desc: '打开相机、拍照、录像' },
{ icon: 'mic', name: '录音', count: 2, desc: '开始/停止录音' },
{ icon: 'book-open', name: '朗读文字', count: 1, desc: '语音朗读输入文本' },
{ icon: 'message-circle', name: '发短信', count: 3, desc: '发送短信消息' },
{ icon: 'sun', name: '亮度设置', count: 5, desc: '调节屏幕亮度' },
{ icon: 'sun-medium', name: '色温设置', count: 3, desc: '调节屏幕色温/护眼模式' },
{ icon: 'video', name: '视频播放', count: 1, desc: '打开并播放视频' },
{ icon: 'map-pinned', name: '打开地图', count: 1, desc: '打开地图导航类应用' },
{ icon: 'monitor', name: '屏幕控制', count: 4, desc: '锁屏、亮屏、熄屏、旋转等屏幕操作' },
],
plugins: [
{ icon: 'cloud-sun', name: '天气查询', desc: '查询实时天气、空气质量和未来预报', status: '系统执行' },
{ icon: 'calendar', name: '万年历', desc: '查询日期、农历、节假日和宜忌信息', status: '系统执行' },
{ icon: 'newspaper', name: '新闻资讯', desc: '获取最新新闻资讯和摘要播报', status: '系统执行' },
{ icon: 'navigation', name: '路线规划', desc: '规划出行路线、距离和预计耗时', status: '系统执行' },
{ icon: 'map', name: '本地生活', desc: '查询周边商户、生活服务和地点信息', status: '系统执行' },
],
bailianApps: [
{ name: '故事生成器', desc: '基于主题生成短故事' },
{ name: '儿童内容安全', desc: '对回答内容进行安全审查' },
],
thirdPartyApps: [
{ name: '音频内容库', desc: '接入第三方儿歌和故事资源' },
],
},
};
registerPage('agent-config', function(container) {
if (typeof window.__ensureAgentHotwordGroups === 'function') {
window.__ensureAgentHotwordGroups();
} else if (!window.__agentHotwordGroups) {
window.__agentHotwordGroups = [
{
id: 'hw-001',
name: '品牌基础热词',
mode: 'manual',
words: [
{ text: 'TalkingQ', lang: '英文' },
{ text: '小Q', lang: '中文' },
{ text: '故事机', lang: '中文' },
],
updatedAt: '2026-05-20 11:57:38',
},
{
id: 'hw-002',
name: '儿童内容热词',
mode: 'manual',
words: [
{ text: '白雪公主', lang: '中文' },
{ text: '睡前故事', lang: '中文' },
],
updatedAt: '2026-05-20 12:08:16',
},
];
}
const apps = window.__agentManagedApps || AGENT_CONFIG_DEFAULT_APPS;
const selectedApp = apps.find(app => app.id === window.__selectedAppId) || apps[0] || AGENT_CONFIG_DEFAULT_APPS[0];
const firstHotwordGroup = window.__agentHotwordGroups?.[0]?.id || '';
const state = {
app: selectedApp,
activeStep: 'voice',
preview: 'empty',
selectedSceneId: window.__selectedSceneTemplateId || 'toy',
selectedSceneVersion: window.__selectedSceneVersion || 'full',
selectedSceneLanguage: window.__selectedSceneLanguage || 'zh',
voiceEnabled: true,
asrEnabled: true,
ttsEnabled: true,
selectedAsrModelId: AGENT_CONFIG_MOCK.voice.asrModels[0]?.id || '',
selectedTtsGroupId: AGENT_CONFIG_MOCK.voice.ttsGroups[0]?.id || '',
selectedTtsModelId: AGENT_CONFIG_MOCK.voice.ttsGroups[0]?.models[0]?.id || '',
selectedHotwordGroupId: firstHotwordGroup,
realtimeCorrectionEnabled: true,
ttsFilterEnabled: false,
ttsFilterPairs: [
{ enabled: true, left: '/*', right: '*/' },
{ enabled: true, left: '[', right: ']' },
{ enabled: false, left: '{', right: '}' },
],
ttsFilterSingles: [],
interruptPrimaryEnabled: true,
interruptSecondaryEnabled: true,
interruptMode: 'full',
semanticStopEnabled: true,
edgeAlgorithmExpanded: true,
selectedTextModelMode: 'recommended',
selectedTextModelId: AGENT_CONFIG_MOCK.understand.textModels[0]?.id || '',
intentEnabled: true,
dialogueContinuationEnabled: true,
knowledgeSearchEnabled: true,
searchMode: 'fast',
longTermMemoryEnabled: false,
knowledgeBases: AGENT_CONFIG_MOCK.understand.knowledgeBases.map(item => ({ ...item })),
customCommands: [],
customPlugins: [],
};
function render() {
const scrollArea = container.querySelector('.bailian-config-scroll');
const previousScrollTop = scrollArea?.scrollTop || 0;
if (state.selectedHotwordGroupId && !(window.__agentHotwordGroups || []).some(group => group.id === state.selectedHotwordGroupId)) {
state.selectedHotwordGroupId = window.__agentHotwordGroups?.[0]?.id || '';
}
container.innerHTML = renderAgentWorkbench(state);
renderAgentIcons(container);
bindWorkbenchEvents(container, state, render);
bindPromptCounter(container);
requestAnimationFrame(() => {
const nextScrollArea = container.querySelector('.bailian-config-scroll');
if (nextScrollArea) {
nextScrollArea.scrollTop = previousScrollTop;
}
});
}
const onHotwordGroupsChanged = () => {
if (!container.isConnected || window.__currentPage !== 'agent-config') {
window.removeEventListener('hotwordGroupsChanged', onHotwordGroupsChanged);
return;
}
render();
};
window.addEventListener('hotwordGroupsChanged', onHotwordGroupsChanged);
render();
});
function renderAgentWorkbench(state) {
return `
${renderAgentHeader(state.app)}
${renderPreviewPane(state.preview)}
`;
}
function renderAgentHeader(app) {
const status = getAgentStatus(app.status);
return `
`;
}
function renderStepTabs(activeStep) {
return `
`;
}
function renderAllStepSections(state) {
return AGENT_CONFIG_STEPS.map(step => `
${renderStepContent(step.id, state)}
`).join('');
}
function renderStepContent(stepId, state) {
const renderers = {
voice: renderVoiceStep,
understand: renderUnderstandStep,
skills: renderSkillsStep,
advanced: renderAdvancedStep,
};
return (renderers[stepId] || renderVoiceStep)(state);
}
function renderVoiceStep(state) {
const data = AGENT_CONFIG_MOCK.voice;
const scene = getSelectedScene(state);
const version = getSelectedSceneVersion(state);
const language = getSelectedSceneLanguage(state);
const hotwordGroups = window.__agentHotwordGroups || [];
return `
语音交互
${renderSceneCard(scene, version, language)}
${state.voiceEnabled ? `
语音 AI
${renderFeatureSelect({
title: '语音识别',
toggleKey: 'asr-enabled',
checked: state.asrEnabled,
activeId: state.selectedAsrModelId,
options: data.asrModels,
hint: '前面的开关决定是否启用 ASR,后面的下拉用于切换识别模型',
})}
${renderFeatureSelect({
title: '语音合成',
toggleKey: 'tts-enabled',
checked: state.ttsEnabled,
activeId: state.selectedTtsModelId,
options: data.ttsGroups.flatMap(group => group.models.map(model => ({ ...model, groupTitle: group.title }))),
groupedOptions: data.ttsGroups,
hint: '前面的开关决定是否启用 TTS,后面的下拉用于切换播报模型',
})}
热词
热词组绑定
热词组来自热词库管理,新增后会自动出现在这里
即时纠错
可上传词表对识别结果进行实时干预
语音合成播报过滤
${state.ttsFilterEnabled ? `
选中符号对内的文字将不会通过 TTS 播报,如 [动作/表情编码]、[设备参数] 及 $$公式$$ 等。
` : ''}
${state.ttsFilterEnabled ? renderTtsFilterSummary(state) : ''}
语义判停与拒识
基于用户语义,判断是否结束语音输入,以及是否需要模型回复。全双工对话场景下,本功能效果更显著。
端侧算法
${state.edgeAlgorithmExpanded ? `
${[
['语音唤醒', '默认唤醒词“小云小云”'],
['端侧 VAD', '语音活性检测,判断是否存在语音信号'],
['AEC', '回声消除,减少自身播报的干扰'],
['定向拾音', '增强特定方向拾音,抑制其他方向'],
].map(([title, desc]) => `
${escapeHtml(title)}
${escapeHtml(desc)}
`).join('')}
` : ''}
` : `
当前为文字交互模式
关闭语音交互后,不展示语音 AI、热词、纠错、播报过滤、打断和端侧算法配置。
`}
`;
}
function renderUnderstandStep(state) {
const data = AGENT_CONFIG_MOCK.understand;
const selectedMode = state.selectedTextModelMode || 'recommended';
const selectedModelId = state.selectedTextModelId || data.textModels[0]?.id || '';
const selectedModel = data.textModels.find(item => item.id === selectedModelId)
|| data.textModels.find(item => item.group === '推荐模型')
|| data.textModels[0];
return `
${renderSection('意图识别', `
关闭将不再触发调用工具/Agent 等基于语义理解的交互功能,适用于低成本、轻量级语音交互场景。
`)}
${renderSection('文本模型', `
模型选择
建议使用推荐模型,为用户提供流畅的对话体验
推荐 / 更多
${escapeHtml(selectedModel.desc)}
`)}
${renderPromptEditor(data.prompt)}
${renderSection('对话变量', `
支持在对话输入中添加变量,为模型提供用户语音之外的更多信息,如天气、周围环境等。
${data.variables.map(item => `
${escapeHtml(item.name)}
${escapeHtml(item.desc)}
`).join('')}
`)}
${renderSection('上下文轮数', `
携带最近
轮对话作为上下文
`)}
${renderKnowledgeBaseSection(state.knowledgeBases || data.knowledgeBases)}
${renderSection('对话承接语', `
`)}
${renderSection('互联网搜索', `
${state.knowledgeSearchEnabled !== false ? `
` : ''}
`)}
${renderSection('长期记忆', `
开启长期记忆后,结合记忆内容进行回复的对话会增加额外耗时。
`)}
`;
}
function renderSkillsStep(state) {
const data = AGENT_CONFIG_MOCK.skills;
const commands = [...data.commands, ...(state.customCommands || [])];
const plugins = [...data.plugins, ...(state.customPlugins || [])];
return `
${renderSection('指令', `
当前已选择的指令可以编辑,右侧加号可添加新的指令类型
${commands.map(item => `
${escapeHtml(item.name)} ${item.count || 1} 指令
${escapeHtml(item.desc)}
`).join('')}
`)}
${renderSection('插件', `
插件能力默认由系统执行,也可以独立配置启用状态
${plugins.map(item => `
${escapeHtml(item.name)}
${escapeHtml(item.desc)}
${escapeHtml(item.status || '系统执行')}
`).join('')}
`)}
${renderMiniAppSection('百炼应用', data.bailianApps, 'boxes')}
${renderMiniAppSection('三方应用', data.thirdPartyApps, 'plug')}
${renderMcpSection()}
${renderSection('Planning / Function Calling', `
${renderSwitchRow('开启 Planning', '让智能体先规划步骤,再调用函数或插件完成复杂任务', true)}
${renderSwitchRow('开启 Function Calling', '允许模型按照结构化参数调用已授权函数', true)}
`)}
`;
}
function renderAdvancedStep() {
return `
${renderSection('高级设置', `
${renderSwitchRow('儿童安全模式', '强制启用儿童内容安全策略、危险行为拒答和敏感信息保护', true)}
${renderSwitchRow('链路追踪', '记录 ASR、LLM、RAG、TTS 的耗时和调用链路', true)}
${renderSwitchRow('自动降级', '主模型异常时自动切换到备用模型,保证设备可用性', true)}
`)}
${renderSection('调用预算', `
`)}
${renderSection('发布策略', `
${renderRadio('release', 'gray', '灰度发布 10%', true)}
${renderRadio('release', 'full', '全量发布')}
${renderRadio('release', 'schedule', '定时发布')}
`)}
`;
}
function renderPreviewPane(previewState) {
const title = previewState === 'running' ? '预览环境已准备好' : '请填好配置后点击运行';
const desc = previewState === 'running'
? '当前智能体已加载最新 mock 配置,可继续调整参数后再次运行。'
: '当前应用测试会产生实际调用,按对话次数计费';
return `
${title}
${desc}
建议佩戴耳机,并在安静环境下测试,对话体验更佳。
`;
}
function renderSceneCard(scene, version, language) {
return `
`;
}
function renderSection(title, content) {
return `
${escapeHtml(title)}
${content}
`;
}
function renderModelSelect(model) {
return `
`;
}
function renderFeatureSelect(config) {
const {
title,
toggleKey,
checked,
activeId,
options = [],
groupedOptions = null,
hint = '',
} = config;
const fieldAction = toggleKey === 'asr-enabled' ? 'asr-model'
: toggleKey === 'tts-enabled' ? 'tts-model'
: 'model';
const selectedOption = options.find(item => item.id === activeId) || options[0] || null;
return `
${selectedOption ? `
${escapeHtml(selectedOption.desc)}
` : ''}
`;
}
function renderSwitchRow(label, desc, checked) {
return `
`;
}
function renderRadio(name, value, label, checked = false) {
return `
`;
}
function renderTtsFilterSummary(state) {
if (!state.ttsFilterEnabled) return '';
const commonPairs = (state.ttsFilterPairs || [])
.filter(item => item.enabled !== false)
.map(item => `${item.left} ${item.right}`.trim());
const singles = state.ttsFilterSingles || [];
return `
常用过滤符号对
${commonPairs.length ? commonPairs.map(item => `${escapeHtml(item)}`).join('') : '暂无'}
自定义过滤符号对与单字符
${singles.length ? singles.map(item => `${escapeHtml(item)}`).join('') : '暂无'}
`;
}
function renderPromptEditor(prompt) {
return `
`;
}
function renderKnowledgeBaseSection(items) {
return renderSection(`知识库 ${items.length}/10`, `
已挂载知识库会参与 RAG 检索和答案补充
${items.map(item => `
${escapeHtml(item.name)}
${escapeHtml(item.desc)}
权重 ${escapeHtml(item.weight)}
`).join('')}
`);
}
function renderWebSearchSection(modes) {
return renderSection('联网搜索', `
${modes.map(mode => `
`).join('')}
`);
}
function renderMiniAppSection(title, items, iconName) {
return renderSection(title, `
${items.map(item => `
${escapeHtml(item.name)}
${escapeHtml(item.desc)}
`).join('')}
`);
}
function renderMcpSection() {
return renderSection('MCP服务 0', `
MCP服务 0
智能体可以通过标准化协议(MCP)连接企业内部服务API并发起调用
`);
}
function bindWorkbenchEvents(container, state, rerender) {
container.querySelectorAll('[data-step]').forEach(tab => {
tab.addEventListener('click', () => {
state.activeStep = tab.dataset.step;
updateActiveStepTab(container, state.activeStep);
scrollToAgentStep(container, state.activeStep);
});
});
bindStepScrollSpy(container, state);
container.querySelectorAll('[data-toggle]').forEach(input => {
input.addEventListener('change', () => {
const key = input.dataset.toggle;
const checked = input.checked;
const map = {
'voice-enabled': 'voiceEnabled',
'asr-enabled': 'asrEnabled',
'tts-enabled': 'ttsEnabled',
'realtime-correction': 'realtimeCorrectionEnabled',
'tts-filter': 'ttsFilterEnabled',
'interrupt-primary': 'interruptPrimaryEnabled',
'interrupt-secondary': 'interruptSecondaryEnabled',
'semantic-stop': 'semanticStopEnabled',
'intent-enabled': 'intentEnabled',
'continuation': 'dialogueContinuationEnabled',
'knowledge-search': 'knowledgeSearchEnabled',
'long-term-memory': 'longTermMemoryEnabled',
};
const stateKey = map[key];
if (stateKey) {
state[stateKey] = checked;
rerender();
}
});
});
container.querySelectorAll('input[name="textModelMode"]').forEach(radio => {
radio.addEventListener('change', () => {
if (!radio.checked) return;
state.selectedTextModelMode = radio.value;
const group = radio.value === 'recommended' ? '推荐模型' : '更多模型';
const candidates = AGENT_CONFIG_MOCK.understand.textModels.filter(item => item.group === group);
state.selectedTextModelId = candidates[0]?.id || state.selectedTextModelId;
rerender();
});
});
container.querySelectorAll('input[name="webSearchMode"]').forEach(radio => {
radio.addEventListener('change', () => {
if (!radio.checked) return;
state.searchMode = radio.value;
rerender();
});
});
container.querySelectorAll('[data-action="text-model"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedTextModelId = select.value;
const current = AGENT_CONFIG_MOCK.understand.textModels.find(item => item.id === select.value);
if (current) state.selectedTextModelMode = current.group === '更多模型' ? 'more' : 'recommended';
rerender();
});
});
container.querySelectorAll('[data-action="asr-model"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedAsrModelId = select.value;
rerender();
});
});
container.querySelectorAll('[data-action="tts-model"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedTtsModelId = select.value;
const matchedGroup = AGENT_CONFIG_MOCK.voice.ttsGroups.find(group => group.models.some(model => model.id === select.value));
state.selectedTtsGroupId = matchedGroup?.id || state.selectedTtsGroupId;
rerender();
});
});
container.querySelectorAll('[data-action="hotword-group"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedHotwordGroupId = select.value;
rerender();
});
});
container.querySelectorAll('[data-action="scene-language"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedSceneLanguage = select.value;
window.__selectedSceneLanguage = select.value;
rerender();
});
});
container.querySelectorAll('[data-action]').forEach(el => {
el.addEventListener('click', event => {
const action = el.dataset.action;
if (action === 'back') {
navigateTo('agent-manage-multimodal');
return;
}
if (action === 'rename') {
openRenameModal(state, rerender);
return;
}
if (action === 'publish') {
state.app.status = 'published';
Toast.success('应用已提交发布,预计 2-5 分钟生效');
rerender();
return;
}
if (action === 'run') {
state.preview = 'running';
updatePreviewOnly(container, state.preview);
Toast.success('运行预览已启动');
return;
}
if (action === 'add-kb') {
navigateTo('knowledge-base');
return;
}
if (action === 'knowledge-weight') {
openKnowledgeWeightModal(state, rerender);
return;
}
if (action === 'scene') {
openSceneTemplateModal(state, rerender);
return;
}
if (action === 'hotwords') {
navigateTo('hotword-library');
return;
}
if (action === 'tts-filter-config') {
openTtsFilterModal(state, rerender);
return;
}
if (action === 'command-config') {
openCommandConfigModal(state, rerender);
return;
}
if (action === 'plugin-config') {
openPluginConfigModal(state, rerender);
return;
}
if (action === 'edge-collapse') {
state.edgeAlgorithmExpanded = !state.edgeAlgorithmExpanded;
rerender();
return;
}
const actionText = {
history: '打开历史记录',
api: '打开 API 接入文档',
license: '打开 License 购买页',
model: '切换模型',
variable: '打开变量配置',
setting: '打开设置',
remove: '已移除当前项',
mcp: '添加 MCP 服务',
'add-app': '添加应用',
'correction-upload': '上传词表',
};
Toast.info(actionText[action] || '操作已触发');
event.stopPropagation();
});
});
}
function updateActiveStepTab(container, stepId) {
container.querySelectorAll('.bailian-step-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.step === stepId);
});
}
function scrollToAgentStep(container, stepId) {
const scrollArea = container.querySelector('.bailian-config-scroll');
const target = container.querySelector(`#agent-step-${stepId}`);
if (!scrollArea || !target) return;
const offset = scrollArea.scrollTop
+ target.getBoundingClientRect().top
- scrollArea.getBoundingClientRect().top
- 8;
scrollArea.scrollTo({ top: Math.max(offset, 0), behavior: 'smooth' });
}
function bindStepScrollSpy(container, state) {
const scrollArea = container.querySelector('.bailian-config-scroll');
if (!scrollArea) return;
let ticking = false;
scrollArea.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const marker = scrollArea.getBoundingClientRect().top + 96;
let currentStep = AGENT_CONFIG_STEPS[0].id;
AGENT_CONFIG_STEPS.forEach(step => {
const section = container.querySelector(`#agent-step-${step.id}`);
if (section && section.getBoundingClientRect().top <= marker) {
currentStep = step.id;
}
});
if (state.activeStep !== currentStep) {
state.activeStep = currentStep;
updateActiveStepTab(container, currentStep);
}
ticking = false;
});
});
}
function updatePreviewOnly(container, previewState) {
const preview = container.querySelector('.bailian-preview-pane');
if (!preview) return;
const temp = document.createElement('div');
temp.innerHTML = renderPreviewPane(previewState);
preview.replaceWith(temp.firstElementChild);
renderAgentIcons(container.querySelector('.bailian-preview-pane'));
}
function bindPromptCounter(container) {
const editor = container.querySelector('#agentPromptEditor');
const counter = container.querySelector('#promptCounter');
if (!editor || !counter) return;
const update = () => {
counter.textContent = `${editor.value.length}/8000`;
};
editor.addEventListener('input', update);
update();
}
function openSceneTemplateModal(state, rerender) {
let pendingSceneId = state.selectedSceneId;
let pendingVersionId = state.selectedSceneVersion;
let pendingLanguageId = state.selectedSceneLanguage;
if (typeof createModal !== 'function') {
Toast.info('场景模板选择器暂不可用');
return;
}
const modal = createModal({
title: '切换场景模板',
size: 'xl',
confirmText: '应用模板',
cancelText: '取消',
content: `
`,
onConfirm: () => {
if (!pendingLanguageId || !AGENT_SCENE_LANGUAGES.some(language => language.id === pendingLanguageId)) {
Toast.warning('请选择对话语言');
return false;
}
state.selectedSceneId = pendingSceneId;
state.selectedSceneVersion = pendingVersionId;
state.selectedSceneLanguage = pendingLanguageId;
window.__selectedSceneTemplateId = pendingSceneId;
window.__selectedSceneVersion = pendingVersionId;
window.__selectedSceneLanguage = pendingLanguageId;
const scene = getSelectedScene(state);
const version = getSelectedSceneVersion(state);
const language = getSelectedSceneLanguage(state);
Toast.success(`已切换至「${scene.name} · ${version.name} · ${language.name}」`);
rerender();
return true;
},
});
const overlay = modal.overlay;
const syncSceneOptions = () => {
overlay.querySelectorAll('.bailian-scene-option').forEach(option => {
option.classList.toggle('selected', option.dataset.sceneId === pendingSceneId);
option.querySelector('input').checked = option.dataset.sceneId === pendingSceneId;
});
};
const syncVersionOptions = () => {
overlay.querySelectorAll('.bailian-scene-version-option').forEach(option => {
option.classList.toggle('selected', option.dataset.versionId === pendingVersionId);
option.querySelector('input').checked = option.dataset.versionId === pendingVersionId;
});
};
const syncLanguageOptions = () => {
overlay.querySelectorAll('.bailian-scene-language-option').forEach(option => {
option.classList.toggle('selected', option.dataset.languageId === pendingLanguageId);
option.querySelector('input').checked = option.dataset.languageId === pendingLanguageId;
});
};
overlay.querySelectorAll('.bailian-scene-option').forEach(option => {
option.addEventListener('click', () => {
pendingSceneId = option.dataset.sceneId;
syncSceneOptions();
});
});
overlay.querySelectorAll('.bailian-scene-version-option').forEach(option => {
option.addEventListener('click', () => {
pendingVersionId = option.dataset.versionId;
syncVersionOptions();
});
});
overlay.querySelectorAll('.bailian-scene-language-option').forEach(option => {
option.addEventListener('click', () => {
pendingLanguageId = option.dataset.languageId;
syncLanguageOptions();
});
});
renderAgentIcons(overlay);
}
function openKnowledgeWeightModal(state, rerender) {
const draft = (state.knowledgeBases || AGENT_CONFIG_MOCK.understand.knowledgeBases).map(item => ({ ...item }));
const modal = createModal({
title: '知识库权重配置',
size: 'lg',
confirmText: '确定',
cancelText: '取消',
content: `
调整知识库权重可人工干预召回片段排序。知识库召回片段得分 = 知识库权重 × 切片相似度。
${draft.map((item, index) => `
`).join('')}
`,
onConfirm: () => {
const inputs = Array.from(modal.overlay.querySelectorAll('.bailian-weight-input'));
const next = inputs.map((input, index) => {
const weight = Number.parseFloat(input.value);
if (Number.isNaN(weight) || weight < 0.5 || weight > 2) {
Toast.warning('知识库权重需在 0.5 到 2 之间');
input.focus();
return null;
}
return { ...draft[index], weight: weight.toFixed(2) };
});
if (next.some(item => !item)) return false;
state.knowledgeBases = next;
Toast.success('知识库权重已更新');
rerender();
return true;
},
});
renderAgentIcons(modal.overlay);
}
function openTtsFilterModal(state, rerender) {
const commonPairs = [
{ id: 'slash', left: '/*', right: '*/', label: '/* */' },
{ id: 'square', left: '[', right: ']', label: '[ ]' },
{ id: 'curly', left: '{', right: '}', label: '{ }' },
];
const draftPairs = (state.ttsFilterPairs || []).map(item => ({ ...item }));
const draftSingles = [...(state.ttsFilterSingles || [])];
const modal = createModal({
title: '语音合成播报过滤设置',
size: 'xl',
confirmText: '确定',
cancelText: '取消',
content: `
选中符号对内的文字将不会通过 TTS 播报;单字符过滤在所有场景下都不会播报,请谨慎评估业务文本特点。
`,
onConfirm: () => {
const pairRows = Array.from(modal.overlay.querySelectorAll('[data-filter-pair-row]'));
const singleRows = Array.from(modal.overlay.querySelectorAll('[data-filter-single-row]'));
const nextPairs = [];
commonPairs.forEach(item => {
const enabled = modal.overlay.querySelector(`[data-common-pair="${item.id}"]`)?.checked;
if (enabled) nextPairs.push({ enabled: true, left: item.left, right: item.right });
});
pairRows.forEach(row => {
const left = row.querySelector('[data-left]')?.value.trim() || '';
const right = row.querySelector('[data-right]')?.value.trim() || '';
if (left || right) nextPairs.push({ enabled: true, left, right });
});
const nextSingles = singleRows.map(row => row.querySelector('[data-single]')?.value.trim() || '').filter(Boolean);
state.ttsFilterEnabled = true;
state.ttsFilterPairs = nextPairs;
state.ttsFilterSingles = nextSingles;
Toast.success('播报过滤设置已保存');
rerender();
return true;
},
});
const overlay = modal.overlay;
const pairList = overlay.querySelector('#filterPairList');
const singleList = overlay.querySelector('#filterSingleList');
function renderPairRows() {
pairList.innerHTML = draftPairs.length
? draftPairs.map((pair, index) => `
`).join('')
: '暂无自定义符号对
';
}
function renderSingleRows() {
singleList.innerHTML = draftSingles.length
? draftSingles.map((char, index) => `
`).join('')
: '暂无自定义单字符
';
}
function bindDynamicEvents() {
overlay.querySelectorAll('[data-delete-pair]').forEach(btn => {
btn.addEventListener('click', () => {
draftPairs.splice(Number(btn.dataset.deletePair), 1);
renderPairRows();
bindDynamicEvents();
renderAgentIcons(overlay);
});
});
overlay.querySelectorAll('[data-delete-single]').forEach(btn => {
btn.addEventListener('click', () => {
draftSingles.splice(Number(btn.dataset.deleteSingle), 1);
renderSingleRows();
bindDynamicEvents();
renderAgentIcons(overlay);
});
});
}
overlay.querySelector('#addFilterPairBtn')?.addEventListener('click', () => {
draftPairs.push({ enabled: true, left: '', right: '' });
renderPairRows();
bindDynamicEvents();
renderAgentIcons(overlay);
});
overlay.querySelector('#addFilterSingleBtn')?.addEventListener('click', () => {
draftSingles.push('');
renderSingleRows();
bindDynamicEvents();
renderAgentIcons(overlay);
});
renderPairRows();
renderSingleRows();
bindDynamicEvents();
renderAgentIcons(overlay);
}
function openCommandConfigModal(state, rerender) {
const baseCommands = AGENT_CONFIG_MOCK.skills.commands;
const customCommands = state.customCommands || [];
const modal = createModal({
title: '指令配置',
size: 'xl',
confirmText: '确定',
cancelText: '取消',
content: `
下发设备控制指令由端侧执行,可在这里查看系统指令、编辑已选指令并添加自定义指令类型。
`,
onConfirm: () => {
const rows = Array.from(modal.overlay.querySelectorAll('[data-custom-command-row]'));
const next = rows.map(row => ({
icon: 'settings-2',
name: row.querySelector('[data-name]')?.value.trim() || '',
desc: row.querySelector('[data-desc]')?.value.trim() || '',
example: row.querySelector('[data-example]')?.value.trim() || '',
params: row.querySelector('[data-params]')?.value.trim() || '',
count: 1,
})).filter(item => item.name);
state.customCommands = next;
Toast.success('自定义指令已保存');
rerender();
return true;
},
});
const overlay = modal.overlay;
const list = overlay.querySelector('#customCommandList');
const draft = (state.customCommands || []).map(item => ({ ...item }));
function renderRows() {
list.innerHTML = draft.length ? draft.map((item, index) => `
`).join('') : '暂无自定义指令
';
}
function bindCommandEvents() {
overlay.querySelectorAll('[data-delete-command]').forEach(btn => {
btn.addEventListener('click', () => {
draft.splice(Number(btn.dataset.deleteCommand), 1);
renderRows();
bindCommandEvents();
renderAgentIcons(overlay);
});
});
}
overlay.querySelector('#addCustomCommandBtn')?.addEventListener('click', () => {
draft.push({ name: '', desc: '', example: '', params: '' });
renderRows();
bindCommandEvents();
renderAgentIcons(overlay);
});
renderRows();
bindCommandEvents();
renderAgentIcons(overlay);
}
function openPluginConfigModal(state, rerender) {
const basePlugins = AGENT_CONFIG_MOCK.skills.plugins;
const customPlugins = (state.customPlugins || []).map(item => ({ ...item }));
const modal = createModal({
title: '插件配置',
size: 'xl',
confirmText: '确定',
cancelText: '取消',
content: `
插件默认由系统执行,也可以在这里单独增删自定义插件并控制是否启用。
系统插件
${basePlugins.length} 项
${basePlugins.map(item => `
`).join('')}
自定义插件
`,
onConfirm: () => {
const rows = Array.from(modal.overlay.querySelectorAll('[data-custom-plugin-row]'));
state.customPlugins = rows.map(row => ({
icon: row.querySelector('[data-icon]')?.value.trim() || 'plug',
name: row.querySelector('[data-name]')?.value.trim() || '',
desc: row.querySelector('[data-desc]')?.value.trim() || '',
status: row.querySelector('[data-status]')?.value.trim() || '系统执行',
})).filter(item => item.name);
Toast.success('插件配置已保存');
rerender();
return true;
},
});
renderAgentIcons(modal.overlay);
const overlay = modal.overlay;
const list = overlay.querySelector('#customPluginList');
const draft = customPlugins;
function renderRows() {
list.innerHTML = draft.length ? draft.map((item, index) => `
`).join('') : '暂无自定义插件
';
}
function bindRows() {
overlay.querySelectorAll('[data-delete-plugin]').forEach(btn => {
btn.addEventListener('click', () => {
draft.splice(Number(btn.dataset.deletePlugin), 1);
renderRows();
bindRows();
renderAgentIcons(overlay);
});
});
}
overlay.querySelector('#addCustomPluginBtn')?.addEventListener('click', () => {
draft.push({ icon: 'plug', name: '', desc: '', status: '系统执行' });
renderRows();
bindRows();
renderAgentIcons(overlay);
});
renderRows();
bindRows();
renderAgentIcons(overlay);
}
function openRenameModal(state, rerender) {
if (typeof createModal !== 'function') {
const name = prompt('请输入应用名称', state.app.name);
if (name && name.trim()) {
state.app.name = name.trim();
rerender();
}
return;
}
createModal({
title: '编辑应用名称',
content: `
`,
confirmText: '保存',
cancelText: '取消',
onConfirm: () => {
const input = document.getElementById('renameAgentInput');
const nextName = input?.value.trim();
if (!nextName) {
Toast.warning('请输入应用名称');
return false;
}
state.app.name = nextName;
Toast.success('应用名称已更新');
rerender();
return true;
},
});
setTimeout(() => document.getElementById('renameAgentInput')?.focus(), 50);
}
function getAgentStatus(status) {
const map = {
published: { label: '已发布', className: 'published' },
editing: { label: '编辑中', className: 'editing' },
draft: { label: '草稿', className: 'draft' },
};
return map[status] || map.draft;
}
function getSelectedScene(state) {
return AGENT_SCENE_TEMPLATES.find(scene => scene.id === state.selectedSceneId)
|| AGENT_SCENE_TEMPLATES.find(scene => scene.id === 'toy')
|| AGENT_SCENE_TEMPLATES[0];
}
function getSelectedSceneVersion(state) {
return AGENT_SCENE_VERSIONS.find(version => version.id === state.selectedSceneVersion)
|| AGENT_SCENE_VERSIONS[0];
}
function getSelectedSceneLanguage(state) {
return AGENT_SCENE_LANGUAGES.find(language => language.id === state.selectedSceneLanguage)
|| AGENT_SCENE_LANGUAGES[0];
}
function escapeHtml(value) {
const div = document.createElement('div');
div.textContent = String(value ?? '');
return div.innerHTML;
}
function escapeAttr(value) {
return escapeHtml(value).replace(/"/g, '"').replace(/'/g, ''');
}
function renderAgentIcons(root) {
if (!root || !window.lucide?.createIcons) return;
try {
lucide.createIcons({ elements: [root] });
} catch (error) {
console.warn('Agent config icon render failed:', error);
}
}