// @ts-nocheck import * as mcp from '../utils/mcp-client.js'; import * as http from './http-client.js'; import { loadModelSource, saveModelSource, loadOllamaConfig, loadAnthropicConfig, loadOmlxConfig, getCurrentModelInfo, CONFIG, loadCrossModuleEnabled, saveCrossModuleEnabled, } from '../utils/config.js'; import { shortName as shortModelName } from '../utils/metrics-client.js'; const STORAGE_KEY = 'ccsparkle_enabled_tools'; const CUSTOM_PRESETS_KEY = 'ccsparkle_custom_presets'; const PRESETS_INIT_KEY = 'ccsparkle_custom_presets_v4_initialized'; const DEFAULT_CUSTOM_PRESETS = [ { id: 'default_prod', name: '生产主链路', tools: [ 'metro_load_timetable_full', 'metro_confirm_debug_train_delete', 'metro_setup_rest_rules', 'metro_setup_handover_rules', 'metro_get_station_list', 'metro_get_app_state', 'metro_get_rest_rules', 'metro_get_handover_rules', 'metro_validate_all_tasks', 'metro_query_driver_segments', 'metro_health_check', ], createdAt: 0, isDefault: true, }, ]; let SUBSYSTEMS = [ { id: 'assignment', name: 'Assignment', descPrefix: '司机任务排班', healthUrl: 'http://127.0.0.1:7777/health' }, { id: 'profile', name: 'Profile', descPrefix: '员工能力画像', healthUrl: 'http://127.0.0.1:7778/health' }, { id: 'profile+', name: 'Profile+', descPrefix: '乘务派班系统', healthUrl: null }, { id: 'center', name: 'Center', descPrefix: '任务中心', healthUrl: null }, { id: 'git', name: 'Git', descPrefix: '代码托管平台', healthUrl: null }, { id: 'inspection', name: 'Inspection', descPrefix: '安全分析系统', healthUrl: null }, ]; async function refreshSubsystemsFromRegistry() { try { const r = await fetch(`${CONFIG.ZHONGTAI_BASE}/subsystems`); if (!r.ok) return; const data = await r.json(); if (!Array.isArray(data.subsystems) || data.subsystems.length === 0) return; const remoteById = new Map(data.subsystems.map(s => [s.id, s])); const localIds = new Set(SUBSYSTEMS.map(s => s.id)); const merged = SUBSYSTEMS.map(local => { const remote = remoteById.get(local.id); if (!remote) return local; return { ...local, ...remote }; }); for (const remote of data.subsystems) { if (!localIds.has(remote.id)) merged.push(remote); } SUBSYSTEMS = merged; } catch (e) { } } let _allTools = []; let _enabled = new Set(); let _expandedGroups = new Set(); let _collapsedSubsystems = new Set(); const COLLAPSE_STORAGE_KEY = 'ccsparkle_collapse_state'; function loadCollapseState() { try { const s = localStorage.getItem(COLLAPSE_STORAGE_KEY); if (s) { const d = JSON.parse(s); _collapsedSubsystems = new Set(d.subsystems || ['assignment', 'profile', 'profile+', 'center', 'git', 'inspection']); _expandedGroups = new Set(d.groups || []); } else { _collapsedSubsystems = new Set(['assignment', 'profile', 'profile+', 'center', 'git', 'inspection']); } } catch (e) { _collapsedSubsystems = new Set(['assignment', 'profile', 'profile+', 'center', 'git', 'inspection']); } } function saveCollapseState() { try { localStorage.setItem(COLLAPSE_STORAGE_KEY, JSON.stringify({ subsystems: [..._collapsedSubsystems], groups: [..._expandedGroups], })); } catch (e) {} } loadCollapseState(); let _subsystemStates = {}; let _customPresets = []; let _sidebarEl = null; let _subsystemsListEl = null; let _enabledCountEl = null; let _totalCountEl = null; let _sourceInfoEl = null; let _subsystemSummaryEl = null; let _onToolsChange = null; let _monitorTimer = null; let _registryTimer = null; function groupOf(name) { if (name.startsWith('profile_')) { if (name === 'profile_health_check' || name === 'profile_get_app_state') return '元工具'; if (name.includes('employee')) return '员工'; if (name.includes('exam')) return '考试'; if (name.includes('kilometer')) return '公里数'; if (name.includes('dashboard')) return '仪表盘'; return '其他'; } if (name === 'metro_list_tools' || name === 'metro_health_check' || name === 'metro_get_app_state') return '元工具'; if (name.includes('setup')) return '高阶工作流'; if (name.includes('timetable') || name.includes('debug_train')) return '加载时刻表'; if (name.includes('big_station')) return '大站点'; if (name.includes('rest')) return '轮乘规则'; if (name.includes('handover')) return '交接规则'; if (name.includes('segment') || name.includes('driver')) return '司机段'; if (name.includes('station')) return '站点'; if (name.includes('validate')) return '校验'; if (name.includes('export') || name.includes('backup') || name.includes('workspace') || name.includes('import')) return 'IO/备份'; if (name.includes('stat') || name.includes('radar') || name.includes('workload') || name.includes('depot')) return '统计'; if (name.includes('apply')) return '应用规则'; return '其他'; } const TOOL_NAME_LABELS = { metro_list_tools: '列工具', metro_health_check: '健康检查', metro_get_app_state: '应用状态', metro_load_timetable_full: '加载运营图', metro_setup_rest_rules: '设置轮乘规则', metro_setup_handover_rules: '设置交接规则', metro_search_tools: '搜索工具', metro_get_tool_detail: '工具详情', metro_list_timetable_files: '列时刻表文件', metro_load_timetable: '加载时刻表', metro_confirm_debug_train_delete: '确认删调试车', metro_import_timetable_file: '导入时刻表', metro_delete_timetable: '删除时刻表', metro_open_timetable_folder: '打开时刻表目录', metro_add_debug_train: '添加调试车', metro_remove_debug_train: '移除调试车', metro_add_big_station: '添加大站点', metro_edit_big_station: '编辑大站点', metro_delete_big_station: '删除大站点', metro_get_big_station_counts: '大站点统计', metro_get_big_stations: '大站点列表', metro_add_rest_rule: '添加轮乘规则', metro_edit_rest_rule: '编辑轮乘规则', metro_delete_rest_rule: '删除轮乘规则', metro_save_rest_config_as: '另存轮乘配置', metro_load_rest_config: '加载轮乘配置', metro_get_rest_configs: '轮乘配置列表', metro_get_rest_rules_by_station: '按站点查轮乘', metro_validate_rest_rule: '预检轮乘冲突', metro_add_handover_rule: '添加交接规则', metro_edit_handover_rule: '编辑交接规则', metro_delete_handover_rule: '删除交接规则', metro_get_handover_rule: '查询单条交接', metro_get_handover_rules: '交接规则列表', metro_apply_rest_rules: '应用轮乘规则', metro_apply_handover_rules: '应用交接规则', metro_apply_flexible_rules: '应用灵活规则', metro_apply_special_shifts: '应用特殊班次', metro_apply_circle_rules: '应用圈环规则', metro_auto_sort: '自动排序匹配表', metro_add_flexible_entry: '添加灵活规则', metro_remove_flexible_entry: '删除灵活规则', metro_add_overnight_train: '添加过夜车', metro_add_circle_history: '加入圈环历史', metro_delete_circle_history: '删除圈环历史', metro_merge_segments: '合并分段', metro_swap_driver_ids: '交换司机编号', metro_update_driver_shift: '修改司机班制', metro_update_segment_reason: '修改分段原因', metro_split_segment_by_time: '按时间拆分段', metro_split_segment_by_station: '按站点拆分段', metro_delete_segments: '批量删除段', metro_assign_driver_to_section: '给未分配段加司机', metro_get_station_list: '站点列表', metro_get_station_name_by_id: 'ID 查站名', metro_get_station_id_by_name: '站名查 ID', metro_get_station_order: '站点排序索引', metro_find_path_between_stations: '查站点路径', metro_validate_all_tasks: '校验所有任务', metro_query_complete_data: '完整分段数据', metro_query_unassigned: '未分配段', metro_query_duplicates: '重复分段', metro_query_station_workload: '站点 workload', metro_get_timetable_summary: '运营图速看', metro_calculate_segment_distances: '计算分段距离', metro_calculate_driver_work_time: '司机工时', metro_get_depot_counts: '出回库统计', metro_analyze_rest_points: '分析休息点', metro_export_workspace: '导出工作区', metro_export_csv: '导出 CSV', metro_list_backups: '列出备份', metro_save_backup: '保存备份', metro_load_backup: '加载备份', metro_delete_backup: '删除备份', profile_health_check: '画像健康检查', profile_get_app_state: '画像应用状态', profile_query_employees: '查员工列表', profile_get_employee: '查单个员工', profile_query_online_exams: '查在线考试', profile_query_practical_exams: '查实操考试', profile_get_practical_exam_stats: '实操考试统计', profile_query_kilometers: '查公里数', profile_get_kilometers_stats: '公里数统计', profile_get_kilometers_by_month: '按月查公里数', profile_get_dashboard: '画像仪表盘', }; const _toolLabels = new Map(); function refreshToolLabels(tools) { _toolLabels.clear(); for (const t of tools || []) { if (t?.name && typeof t.label === 'string' && t.label.trim()) { _toolLabels.set(t.name, t.label.trim()); } } } export function getToolNameLabel(name) { if (!name) return ''; return _toolLabels.get(name) || TOOL_NAME_LABELS[name] || name; } export function getSubsystemDisplayName(id) { const sys = SUBSYSTEMS.find((s) => s.id === id); return String(sys?.name || '').trim(); } export function resolveAppNamesFromToolNames(toolNames) { const names = []; const seen = new Set(); for (const tool of toolNames || []) { const id = mcp.getToolSubsystemId(tool) || subsysIdOfTool(tool); const label = getSubsystemDisplayName(id); if (!label || seen.has(label)) continue; seen.add(label); names.push(label); } return names; } function subsysIdOfTool(name) { if (name.startsWith('metro_')) return 'assignment'; if (name.startsWith('profile_')) return 'profile'; return 'assignment'; } function getToolLabel(t) { if (typeof t.label === 'string' && t.label.trim()) { return t.label.trim(); } if (_toolLabels.has(t.name)) return _toolLabels.get(t.name); if (TOOL_NAME_LABELS[t.name]) return TOOL_NAME_LABELS[t.name]; let d = (t.description || '').trim(); if (d.startsWith('【')) { const idx = d.indexOf('】'); if (idx > 0) d = d.slice(idx + 1).trim(); } const m = d.match(/^([^。,!!\n,;]+)/); if (m) d = m[1].trim(); if (d.length > 35) d = d.slice(0, 35) + '...'; return d || t.name; } export async function loadTools(mode, force = false) { if (_allTools.length > 0 && !force) return _allTools; if (mode === 'http') { _allTools = await http.listTools(); } else { _allTools = await mcp.listTools(); } refreshToolLabels(_allTools); const stored = loadStored(); _enabled = new Set(stored.filter(n => _allTools.some(t => t.name === n))); if (_enabled.size === 0) { const prod = DEFAULT_CUSTOM_PRESETS.find(p => p.id === 'default_prod'); const fallbackNames = (prod?.tools || []).filter(n => _allTools.some(t => t.name === n)); if (fallbackNames.length > 0) { fallbackNames.forEach(n => _enabled.add(n)); } else if (_allTools.length <= 30) { _allTools.forEach(t => _enabled.add(t.name)); } saveStored(); } renderSubsystems(); return _allTools; } export function clearTools() { _allTools = []; _toolLabels.clear(); _enabled = new Set(); renderSubsystems(); } export function getEnabledTools() { return _allTools.filter(t => _enabled.has(t.name)); } export function applyPreset(preset, subsystId = null) { const targetTools = subsystId ? _allTools.filter(t => subsysIdOfTool(t.name) === subsystId) : _allTools; if (preset === 'all') { targetTools.forEach(t => _enabled.add(t.name)); } else if (preset === 'none') { targetTools.forEach(t => _enabled.delete(t.name)); } saveStored(); renderSubsystems(); } function toggleTool(name, enabled) { if (enabled) _enabled.add(name); else _enabled.delete(name); saveStored(); renderSubsystems(); } function loadCustomPresets() { const initialized = localStorage.getItem(PRESETS_INIT_KEY); if (!initialized) { _customPresets = DEFAULT_CUSTOM_PRESETS.map(p => ({ ...p, tools: [...p.tools] })); saveCustomPresets(); localStorage.setItem(PRESETS_INIT_KEY, '1'); return; } try { const s = localStorage.getItem(CUSTOM_PRESETS_KEY); _customPresets = s ? JSON.parse(s) : []; } catch (e) { _customPresets = []; } } export function restoreDefaultPresets() { const existingIds = new Set(_customPresets.map(p => p.id)); let added = 0; for (const def of DEFAULT_CUSTOM_PRESETS) { if (!existingIds.has(def.id)) { _customPresets.push({ ...def, tools: [...def.tools] }); added++; } } saveCustomPresets(); renderCustomPresets(); return added; } function saveCustomPresets() { try { localStorage.setItem(CUSTOM_PRESETS_KEY, JSON.stringify(_customPresets)); } catch (e) {} } export function saveCurrentAsPreset() { const enabledTools = getEnabledTools(); if (enabledTools.length === 0) { alert('请先勾选工具,再保存为预设'); return; } let name = null; try { name = prompt(`保存当前 ${enabledTools.length} 个工具为预设,请输入名称:`); } catch (e) { alert('当前环境( Electron 打包版)不支持 prompt,请在「配」中保存预设'); return; } if (!name || !name.trim()) return; _customPresets.push({ id: `preset_${Date.now()}`, name: name.trim(), tools: enabledTools.map(t => t.name), createdAt: Date.now(), }); saveCustomPresets(); renderCustomPresets(); } export function deleteCustomPreset(id) { _customPresets = _customPresets.filter(p => p.id !== id); saveCustomPresets(); renderCustomPresets(); } export function applyCustomPreset(id) { const p = _customPresets.find(x => x.id === id); if (!p) return; _enabled.clear(); p.tools.forEach(name => { if (_allTools.some(t => t.name === name)) _enabled.add(name); }); saveStored(); renderSubsystems(); } function renderCustomPresets() { const container = _sidebarEl?.querySelector('#custom-presets'); if (!container) return; if (_customPresets.length === 0) { container.innerHTML = '