UNPKG

ziwei-mcp

Version:

紫微斗数 MCP (Ziwei Doushu Model Context Protocol) - 基于 MCP 协议的紫微斗数命理分析工具

961 lines (830 loc) 22.6 kB
/** * SVG增量更新管理器 * 负责检测变化、生成差异补丁、应用增量更新 */ class SVGIncrementalUpdater { constructor() { this.snapshots = new Map(); // 存储SVG快照 this.patches = new Map(); // 存储补丁历史 this.watchers = new Map(); // 数据监听器 // 配置 this.config = { enableDiffing: true, maxSnapshots: 50, maxPatches: 100, diffGranularity: "element", // 'element' | 'attribute' | 'text' enableOptimization: true, }; // 性能统计 this.stats = { totalUpdates: 0, incrementalUpdates: 0, fullRebuilds: 0, averageUpdateTime: 0, bytesTransferred: 0, }; } /** * 创建SVG快照 */ createSnapshot(svgId, svgContent, metadata = {}) { const snapshot = { id: svgId, content: svgContent, metadata: metadata, timestamp: Date.now(), hash: this.generateHash(svgContent), structure: this.parseStructure(svgContent), }; // 存储快照 this.snapshots.set(svgId, snapshot); // 清理旧快照 this.cleanupSnapshots(); return snapshot; } /** * 检测变化并生成补丁 */ detectChanges(svgId, newData, oldData) { const startTime = performance.now(); try { // 获取旧快照 const oldSnapshot = this.snapshots.get(svgId); if (!oldSnapshot) { return { type: "full_rebuild", reason: "no_previous_snapshot", patch: null, }; } // 比较数据 const dataDiff = this.compareData(oldData, newData); if (dataDiff.changes.length === 0) { return { type: "no_change", reason: "data_identical", patch: null, }; } // 生成增量补丁 const patch = this.generatePatch(svgId, dataDiff, oldSnapshot); // 评估补丁效率 const efficiency = this.evaluatePatchEfficiency( patch, oldSnapshot.content, ); if (efficiency.shouldUseIncremental) { this.stats.incrementalUpdates++; return { type: "incremental", reason: "efficient_patch", patch: patch, efficiency: efficiency, }; } else { this.stats.fullRebuilds++; return { type: "full_rebuild", reason: efficiency.reason, patch: null, }; } } finally { const endTime = performance.now(); this.updatePerformanceStats(endTime - startTime); } } /** * 比较数据 */ compareData(oldData, newData) { const changes = []; const visited = new Set(); // 递归比较对象 this.deepCompare(oldData, newData, "", changes, visited); return { changes: changes, summary: this.summarizeChanges(changes), }; } /** * 深度比较 */ deepCompare(oldObj, newObj, path, changes, visited) { // 防止循环引用 const objKey = `${path}:${typeof oldObj}:${typeof newObj}`; if (visited.has(objKey)) return; visited.add(objKey); // 类型变化 if (typeof oldObj !== typeof newObj) { changes.push({ type: "type_change", path: path, oldValue: oldObj, newValue: newObj, impact: this.assessImpact(path), }); return; } // 基本类型比较 if (typeof oldObj !== "object" || oldObj === null || newObj === null) { if (oldObj !== newObj) { changes.push({ type: "value_change", path: path, oldValue: oldObj, newValue: newObj, impact: this.assessImpact(path), }); } return; } // 数组比较 if (Array.isArray(oldObj) && Array.isArray(newObj)) { this.compareArrays(oldObj, newObj, path, changes, visited); return; } // 对象比较 if (Array.isArray(oldObj) !== Array.isArray(newObj)) { changes.push({ type: "structure_change", path: path, oldValue: oldObj, newValue: newObj, impact: "high", }); return; } // 比较对象属性 const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]); allKeys.forEach((key) => { const newPath = path ? `${path}.${key}` : key; const oldValue = oldObj[key]; const newValue = newObj[key]; if (!(key in oldObj)) { changes.push({ type: "property_added", path: newPath, newValue: newValue, impact: this.assessImpact(newPath), }); } else if (!(key in newObj)) { changes.push({ type: "property_removed", path: newPath, oldValue: oldValue, impact: this.assessImpact(newPath), }); } else { this.deepCompare(oldValue, newValue, newPath, changes, visited); } }); } /** * 比较数组 */ compareArrays(oldArray, newArray, path, changes, visited) { const maxLength = Math.max(oldArray.length, newArray.length); // 长度变化 if (oldArray.length !== newArray.length) { changes.push({ type: "array_length_change", path: path, oldLength: oldArray.length, newLength: newArray.length, impact: this.assessImpact(path), }); } // 元素比较 for (let i = 0; i < maxLength; i++) { const newPath = `${path}[${i}]`; if (i >= oldArray.length) { changes.push({ type: "array_item_added", path: newPath, newValue: newArray[i], impact: this.assessImpact(newPath), }); } else if (i >= newArray.length) { changes.push({ type: "array_item_removed", path: newPath, oldValue: oldArray[i], impact: this.assessImpact(newPath), }); } else { this.deepCompare(oldArray[i], newArray[i], newPath, changes, visited); } } } /** * 评估变化影响 */ assessImpact(path) { // 高影响路径 const highImpactPaths = [ "structure", "width", "height", "viewBox", "theme.background", "theme.borderColor", ]; // 中等影响路径 const mediumImpactPaths = ["palaces", "stars", "theme"]; // 检查高影响 if (highImpactPaths.some((p) => path.startsWith(p))) { return "high"; } // 检查中等影响 if (mediumImpactPaths.some((p) => path.startsWith(p))) { return "medium"; } return "low"; } /** * 总结变化 */ summarizeChanges(changes) { const summary = { total: changes.length, byType: {}, byImpact: { high: 0, medium: 0, low: 0 }, affectedPaths: new Set(), }; changes.forEach((change) => { // 按类型统计 summary.byType[change.type] = (summary.byType[change.type] || 0) + 1; // 按影响统计 summary.byImpact[change.impact]++; // 受影响路径 summary.affectedPaths.add(change.path.split(".")[0]); }); summary.affectedPaths = Array.from(summary.affectedPaths); return summary; } /** * 生成补丁 */ generatePatch(svgId, dataDiff, oldSnapshot) { const patch = { id: this.generatePatchId(), svgId: svgId, timestamp: Date.now(), changes: dataDiff.changes, operations: [], metadata: { changeCount: dataDiff.changes.length, summary: dataDiff.summary, }, }; // 根据变化生成操作 dataDiff.changes.forEach((change) => { const operations = this.generateOperationsForChange(change, oldSnapshot); patch.operations.push(...operations); }); // 优化操作 if (this.config.enableOptimization) { patch.operations = this.optimizeOperations(patch.operations); } // 存储补丁 this.patches.set(patch.id, patch); this.cleanupPatches(); return patch; } /** * 为变化生成操作 */ generateOperationsForChange(change, oldSnapshot) { const operations = []; switch (change.type) { case "value_change": operations.push( ...this.generateValueChangeOperations(change, oldSnapshot), ); break; case "property_added": operations.push( ...this.generatePropertyAddOperations(change, oldSnapshot), ); break; case "property_removed": operations.push( ...this.generatePropertyRemoveOperations(change, oldSnapshot), ); break; case "array_item_added": operations.push( ...this.generateArrayAddOperations(change, oldSnapshot), ); break; case "array_item_removed": operations.push( ...this.generateArrayRemoveOperations(change, oldSnapshot), ); break; case "structure_change": operations.push( ...this.generateStructureChangeOperations(change, oldSnapshot), ); break; } return operations; } /** * 生成值变化操作 */ generateValueChangeOperations(change, oldSnapshot) { const operations = []; // 根据路径确定SVG元素和属性 const mapping = this.mapPathToSVGElement(change.path, oldSnapshot); if (mapping) { operations.push({ type: "update_attribute", selector: mapping.selector, attribute: mapping.attribute, oldValue: change.oldValue, newValue: change.newValue, priority: this.getOperationPriority(change.impact), }); } return operations; } /** * 生成属性添加操作 */ generatePropertyAddOperations(change, oldSnapshot) { const operations = []; const mapping = this.mapPathToSVGElement(change.path, oldSnapshot); if (mapping) { operations.push({ type: "add_attribute", selector: mapping.selector, attribute: mapping.attribute, value: change.newValue, priority: this.getOperationPriority(change.impact), }); } return operations; } /** * 生成属性移除操作 */ generatePropertyRemoveOperations(change, oldSnapshot) { const operations = []; const mapping = this.mapPathToSVGElement(change.path, oldSnapshot); if (mapping) { operations.push({ type: "remove_attribute", selector: mapping.selector, attribute: mapping.attribute, priority: this.getOperationPriority(change.impact), }); } return operations; } /** * 生成数组添加操作 */ generateArrayAddOperations(change, oldSnapshot) { const operations = []; // 如果是星曜数组变化 if (change.path.includes("stars")) { operations.push({ type: "add_element", parentSelector: this.getParentSelector(change.path, oldSnapshot), element: this.generateStarElement(change.newValue), position: this.extractArrayIndex(change.path), priority: this.getOperationPriority(change.impact), }); } return operations; } /** * 生成数组移除操作 */ generateArrayRemoveOperations(change, oldSnapshot) { const operations = []; operations.push({ type: "remove_element", selector: this.mapPathToElementSelector(change.path, oldSnapshot), priority: this.getOperationPriority(change.impact), }); return operations; } /** * 生成结构变化操作 */ generateStructureChangeOperations(change, oldSnapshot) { // 结构变化通常需要重建 return [ { type: "rebuild_section", section: this.identifySection(change.path), priority: 1, // 最高优先级 }, ]; } /** * 映射路径到SVG元素 */ mapPathToSVGElement(path, oldSnapshot) { // 路径映射规则 const mappingRules = { width: { selector: "svg", attribute: "width" }, height: { selector: "svg", attribute: "height" }, viewBox: { selector: "svg", attribute: "viewBox" }, "theme.background": { selector: ".chart-background", attribute: "fill" }, "theme.borderColor": { selector: ".palace-divider", attribute: "stroke" }, "theme.textColor": { selector: ".palace-name", attribute: "fill" }, }; // 直接匹配 if (mappingRules[path]) { return mappingRules[path]; } // 模式匹配 for (const [pattern, mapping] of Object.entries(mappingRules)) { if (path.startsWith(pattern)) { return { selector: mapping.selector, attribute: mapping.attribute, }; } } // 动态映射 return this.dynamicPathMapping(path, oldSnapshot); } /** * 动态路径映射 */ dynamicPathMapping(path, oldSnapshot) { // 宫位相关 const palaceMatch = path.match(/palaces\[(\d+)\]\.(.+)/); if (palaceMatch) { const palaceIndex = palaceMatch[1]; const property = palaceMatch[2]; return { selector: `.palace-${palaceIndex}`, attribute: this.mapPropertyToAttribute(property), }; } // 星曜相关 const starMatch = path.match(/palaces\[(\d+)\]\.stars\[(\d+)\]\.(.+)/); if (starMatch) { const palaceIndex = starMatch[1]; const starIndex = starMatch[2]; const property = starMatch[3]; return { selector: `.palace-${palaceIndex} .star:nth-child(${parseInt(starIndex) + 1})`, attribute: this.mapPropertyToAttribute(property), }; } return null; } /** * 映射属性到SVG属性 */ mapPropertyToAttribute(property) { const mapping = { name: "textContent", color: "fill", backgroundColor: "fill", borderColor: "stroke", fontSize: "font-size", x: "x", y: "y", width: "width", height: "height", }; return mapping[property] || property; } /** * 优化操作 */ optimizeOperations(operations) { // 按优先级排序 operations.sort((a, b) => a.priority - b.priority); // 合并相同元素的操作 const merged = this.mergeOperations(operations); // 移除冗余操作 const deduplicated = this.deduplicateOperations(merged); return deduplicated; } /** * 合并操作 */ mergeOperations(operations) { const grouped = new Map(); operations.forEach((op) => { const key = `${op.selector}:${op.type}`; if (!grouped.has(key)) { grouped.set(key, []); } grouped.get(key).push(op); }); const merged = []; grouped.forEach((ops) => { if (ops.length === 1) { merged.push(ops[0]); } else { // 合并多个操作 merged.push(this.combineOperations(ops)); } }); return merged; } /** * 组合操作 */ combineOperations(operations) { const first = operations[0]; if (first.type === "update_attribute") { // 合并属性更新 const attributes = {}; operations.forEach((op) => { attributes[op.attribute] = op.newValue; }); return { type: "update_attributes", selector: first.selector, attributes: attributes, priority: Math.min(...operations.map((op) => op.priority)), }; } return first; } /** * 去重操作 */ deduplicateOperations(operations) { const seen = new Set(); return operations.filter((op) => { const key = this.getOperationKey(op); if (seen.has(key)) { return false; } seen.add(key); return true; }); } /** * 获取操作键 */ getOperationKey(operation) { return `${operation.type}:${operation.selector}:${JSON.stringify(operation.attributes || operation.attribute)}`; } /** * 评估补丁效率 */ evaluatePatchEfficiency(patch, originalContent) { const originalSize = originalContent.length; const patchSize = this.estimatePatchSize(patch); const operationCount = patch.operations.length; // 效率指标 const sizeRatio = patchSize / originalSize; const complexityScore = this.calculateComplexityScore(patch); // 决策逻辑 const shouldUseIncremental = sizeRatio < 0.3 && // 补丁大小小于原内容30% operationCount < 20 && // 操作数量少于20个 complexityScore < 0.7; // 复杂度分数低于0.7 return { shouldUseIncremental: shouldUseIncremental, sizeRatio: sizeRatio, operationCount: operationCount, complexityScore: complexityScore, reason: shouldUseIncremental ? "efficient" : "too_complex", }; } /** * 估算补丁大小 */ estimatePatchSize(patch) { let size = 0; patch.operations.forEach((op) => { switch (op.type) { case "update_attribute": size += 50; // 估算属性更新大小 break; case "add_element": size += op.element ? op.element.length : 200; // 估算元素大小 break; case "remove_element": size += 20; // 移除操作很小 break; case "rebuild_section": size += 1000; // 重建操作较大 break; default: size += 100; } }); return size; } /** * 计算复杂度分数 */ calculateComplexityScore(patch) { let score = 0; const weights = { update_attribute: 0.1, add_element: 0.3, remove_element: 0.2, rebuild_section: 1.0, }; patch.operations.forEach((op) => { score += weights[op.type] || 0.5; }); return Math.min(score / patch.operations.length, 1.0); } /** * 应用补丁 */ applyPatch(svgElement, patch) { const startTime = performance.now(); try { patch.operations.forEach((operation) => { this.executeOperation(svgElement, operation); }); return { success: true, operationsApplied: patch.operations.length, duration: performance.now() - startTime, }; } catch (error) { console.error("补丁应用失败:", error); return { success: false, error: error.message, duration: performance.now() - startTime, }; } } /** * 执行操作 */ executeOperation(svgElement, operation) { const elements = svgElement.querySelectorAll(operation.selector); elements.forEach((element) => { switch (operation.type) { case "update_attribute": this.updateAttribute( element, operation.attribute, operation.newValue, ); break; case "update_attributes": Object.entries(operation.attributes).forEach(([attr, value]) => { this.updateAttribute(element, attr, value); }); break; case "add_attribute": this.updateAttribute(element, operation.attribute, operation.value); break; case "remove_attribute": element.removeAttribute(operation.attribute); break; case "add_element": this.addElement(element, operation.element, operation.position); break; case "remove_element": element.remove(); break; } }); } /** * 更新属性 */ updateAttribute(element, attribute, value) { if (attribute === "textContent") { element.textContent = value; } else { element.setAttribute(attribute, value); } } /** * 添加元素 */ addElement(parentElement, elementHTML, position) { const tempDiv = document.createElement("div"); tempDiv.innerHTML = elementHTML; const newElement = tempDiv.firstElementChild; if (position !== undefined && position < parentElement.children.length) { parentElement.insertBefore(newElement, parentElement.children[position]); } else { parentElement.appendChild(newElement); } } /** * 解析SVG结构 */ parseStructure(svgContent) { // 简化的结构解析 return { elementCount: (svgContent.match(/<[^/][^>]*>/g) || []).length, hasAnimations: svgContent.includes("<animate"), hasGradients: svgContent.includes("<gradient"), hasFilters: svgContent.includes("<filter"), }; } /** * 生成哈希 */ generateHash(content) { let hash = 0; for (let i = 0; i < content.length; i++) { const char = content.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return hash.toString(36); } /** * 生成补丁ID */ generatePatchId() { return `patch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * 获取操作优先级 */ getOperationPriority(impact) { const priorities = { high: 1, medium: 2, low: 3, }; return priorities[impact] || 2; } /** * 更新性能统计 */ updatePerformanceStats(duration) { this.stats.totalUpdates++; this.stats.averageUpdateTime = (this.stats.averageUpdateTime * (this.stats.totalUpdates - 1) + duration) / this.stats.totalUpdates; } /** * 清理快照 */ cleanupSnapshots() { if (this.snapshots.size > this.config.maxSnapshots) { const entries = Array.from(this.snapshots.entries()).sort( (a, b) => a[1].timestamp - b[1].timestamp, ); const toDelete = entries.slice( 0, entries.length - this.config.maxSnapshots, ); toDelete.forEach(([id]) => this.snapshots.delete(id)); } } /** * 清理补丁 */ cleanupPatches() { if (this.patches.size > this.config.maxPatches) { const entries = Array.from(this.patches.entries()).sort( (a, b) => a[1].timestamp - b[1].timestamp, ); const toDelete = entries.slice( 0, entries.length - this.config.maxPatches, ); toDelete.forEach(([id]) => this.patches.delete(id)); } } /** * 获取统计信息 */ getStats() { return { ...this.stats, snapshotCount: this.snapshots.size, patchCount: this.patches.size, incrementalRatio: this.stats.totalUpdates > 0 ? this.stats.incrementalUpdates / this.stats.totalUpdates : 0, }; } /** * 重置统计 */ resetStats() { this.stats = { totalUpdates: 0, incrementalUpdates: 0, fullRebuilds: 0, averageUpdateTime: 0, bytesTransferred: 0, }; } } module.exports = { SVGIncrementalUpdater, };