657 lines
24 KiB
JavaScript
657 lines
24 KiB
JavaScript
/**
|
|
* 历史记录管理器
|
|
* 负责管理历史记录的显示和操作
|
|
*/
|
|
|
|
import { app } from "../../../../scripts/app.js";
|
|
import { logger } from '../utils/logger.js';
|
|
import { HistoryCacheService } from "../services/cache.js";
|
|
import { PopupManager } from "../utils/popupManager.js";
|
|
import { EventManager } from "../utils/eventManager.js";
|
|
import { UIToolkit } from "../utils/UIToolkit.js";
|
|
import { ResourceManager } from "../utils/resourceManager.js";
|
|
|
|
/**
|
|
* 历史记录管理器类
|
|
* 管理历史记录弹窗和历史记录选择
|
|
*/
|
|
class HistoryManager {
|
|
static popupInstance = null;
|
|
static onCloseCallback = null;
|
|
static currentNodeId = null; // 当前节点ID
|
|
static currentInputId = null; // 当前输入框ID
|
|
static currentWidgetKey = null; // 当前widgetKey
|
|
static eventCleanups = []; // 事件清理函数数组
|
|
static activeTooltip = null; // 当前活动的 tooltip
|
|
|
|
/**
|
|
* 显示历史记录弹窗
|
|
*/
|
|
static async showHistoryPopup(params) {
|
|
const { anchorButton, nodeId, inputId, onClose } = params;
|
|
|
|
try {
|
|
// 保存数据
|
|
this.currentNodeId = nodeId;
|
|
this.currentInputId = inputId;
|
|
this.currentWidgetKey = params.widgetKey || null;
|
|
|
|
// logger.debug(`历史弹窗 | 触发显示 | 节点:${nodeId} | 输入框:${inputId}`);
|
|
|
|
// 清理现有事件监听
|
|
this._cleanupEvents();
|
|
|
|
// 获取历史数据
|
|
const historyList = HistoryCacheService.getHistoryList({
|
|
nodeId: nodeId,
|
|
limit: 100, // 增加限制以确保有足够的历史数据
|
|
workflowId: app.graph?._workflow_id
|
|
});
|
|
|
|
// 创建新弹窗
|
|
const popup = this._createHistoryPopup({ historyList, nodeId });
|
|
|
|
// 使用PopupManager显示弹窗
|
|
await PopupManager.showPopup({
|
|
popup: popup,
|
|
anchorButton: anchorButton,
|
|
buttonInfo: params.buttonInfo,
|
|
onClose: () => {
|
|
// 清理事件监听
|
|
this._cleanupEvents();
|
|
// 执行传入的关闭回调
|
|
if (typeof onClose === 'function') {
|
|
onClose();
|
|
}
|
|
}
|
|
});
|
|
|
|
// logger.debug(`历史弹窗 | 结果:显示成功 | 节点:${nodeId}`);
|
|
} catch (error) {
|
|
logger.error(`历史弹窗 | 结果:失败 | 错误:${error.message}`);
|
|
this._cleanupAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 隐藏历史记录弹窗
|
|
*/
|
|
static hideHistoryPopup() {
|
|
// 清理事件监听
|
|
this._cleanupEvents();
|
|
|
|
// 使用PopupManager关闭所有弹窗
|
|
PopupManager.closeAllPopups();
|
|
}
|
|
|
|
/**
|
|
* 清理所有事件监听
|
|
*/
|
|
static _cleanupEvents() {
|
|
// 执行并清空所有事件清理函数
|
|
if (this.eventCleanups.length > 0) {
|
|
this.eventCleanups.forEach(cleanup => {
|
|
if (typeof cleanup === 'function') {
|
|
cleanup();
|
|
}
|
|
});
|
|
this.eventCleanups = [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 强制清理所有相关资源
|
|
*/
|
|
static _cleanupAll() {
|
|
// 清理事件监听
|
|
this._cleanupEvents();
|
|
|
|
// 使用PopupManager关闭所有弹窗
|
|
PopupManager.closeAllPopups();
|
|
}
|
|
|
|
/**
|
|
* 格式化历史内容,进行适当的截断和处理
|
|
*/
|
|
static _formatHistoryContent(content, operationType) {
|
|
// 不再添加操作类型前缀,直接返回内容
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* 创建并显示tooltip
|
|
*/
|
|
static _showTooltip(target, text) {
|
|
// 移除已存在的tooltip
|
|
this._hideTooltip();
|
|
|
|
// 创建tooltip元素
|
|
const tooltip = document.createElement('div');
|
|
tooltip.className = 'tag_tooltip';
|
|
tooltip.textContent = text;
|
|
document.body.appendChild(tooltip);
|
|
|
|
// 获取目标元素的位置和尺寸
|
|
const rect = target.getBoundingClientRect();
|
|
|
|
// 计算tooltip位置
|
|
const tooltipRect = tooltip.getBoundingClientRect();
|
|
const left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
const top = rect.top - tooltipRect.height - 8; // 8px的间距
|
|
|
|
// 设置tooltip位置
|
|
tooltip.style.left = `${left}px`;
|
|
tooltip.style.top = `${top}px`;
|
|
|
|
// 保存当前tooltip引用
|
|
this.activeTooltip = tooltip;
|
|
}
|
|
|
|
/**
|
|
* 隐藏tooltip
|
|
*/
|
|
static _hideTooltip() {
|
|
if (this.activeTooltip) {
|
|
this.activeTooltip.remove();
|
|
this.activeTooltip = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建历史记录弹窗
|
|
*/
|
|
static _createHistoryPopup({ historyList, nodeId }) {
|
|
const popup = document.createElement('div');
|
|
popup.className = 'popup_container';
|
|
|
|
// 创建标题栏
|
|
const titleBar = document.createElement('div');
|
|
titleBar.className = 'popup_title_bar';
|
|
|
|
const title = document.createElement('div');
|
|
title.className = 'popup_title';
|
|
title.style.display = 'flex';
|
|
title.style.alignItems = 'center';
|
|
|
|
// 添加图标
|
|
const iconContainer = ResourceManager.getIcon('icon-history.svg');
|
|
if (iconContainer) {
|
|
iconContainer.style.width = '18px';
|
|
iconContainer.style.height = '18px';
|
|
iconContainer.style.color = 'var(--p-dialog-color)';
|
|
iconContainer.style.marginRight = '8px';
|
|
title.appendChild(iconContainer);
|
|
}
|
|
|
|
title.appendChild(document.createTextNode('历史记录'));
|
|
|
|
const actions = document.createElement('div');
|
|
actions.className = 'popup_actions';
|
|
|
|
// 创建清除当前按钮
|
|
const clearCurrentBtn = document.createElement('button');
|
|
clearCurrentBtn.className = 'popup_action_btn';
|
|
clearCurrentBtn.textContent = '清除当前';
|
|
|
|
// 使用EventManager添加点击事件
|
|
const clearCurrentCleanup = EventManager.addDOMListener(clearCurrentBtn, 'click', async (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.hideHistoryPopup();
|
|
try {
|
|
// 获取当前输入框内容
|
|
const mapping = UIToolkit._findMapping(nodeId, this.currentInputId, this.currentWidgetKey);
|
|
const inputEl = mapping?.inputEl;
|
|
const currentContent = inputEl?.value || '';
|
|
|
|
// 清除当前节点的历史
|
|
await HistoryCacheService.clearNodeHistory(nodeId);
|
|
|
|
// 如果输入框有内容,添加到历史记录
|
|
if (currentContent.trim()) {
|
|
HistoryCacheService.addHistory({
|
|
workflow_id: '',
|
|
node_id: nodeId,
|
|
input_id: this.currentInputId,
|
|
content: currentContent,
|
|
operation_type: 'input',
|
|
timestamp: Date.now()
|
|
});
|
|
logger.debug(`历史记录 | 清除后保存当前内容 | 节点:${nodeId} | 输入框:${this.currentInputId}`);
|
|
}
|
|
|
|
// 更新按钮状态
|
|
if (mapping?.widget) {
|
|
const widget = mapping.widget;
|
|
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
|
|
}
|
|
|
|
logger.debug(`历史记录 | 清除当前节点历史 | 节点:${nodeId}`);
|
|
} catch (error) {
|
|
logger.error(`历史记录 | 清除失败 | 错误:${error.message}`);
|
|
}
|
|
});
|
|
this.eventCleanups.push(clearCurrentCleanup);
|
|
|
|
// 创建清除所有按钮
|
|
const clearAllBtn = document.createElement('button');
|
|
clearAllBtn.className = 'popup_action_btn danger';
|
|
clearAllBtn.textContent = '清除所有';
|
|
|
|
// 使用EventManager添加点击事件
|
|
const clearAllCleanup = EventManager.addDOMListener(clearAllBtn, 'click', async (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.hideHistoryPopup();
|
|
try {
|
|
// 获取当前输入框内容
|
|
const mapping = UIToolkit._findMapping(nodeId, this.currentInputId, this.currentWidgetKey);
|
|
const inputEl = mapping?.inputEl;
|
|
const currentContent = inputEl?.value || '';
|
|
|
|
// 清除所有历史
|
|
await HistoryCacheService.clearAllHistory();
|
|
|
|
// 如果输入框有内容,添加到历史记录
|
|
if (currentContent.trim()) {
|
|
HistoryCacheService.addHistory({
|
|
workflow_id: '',
|
|
node_id: nodeId,
|
|
input_id: this.currentInputId,
|
|
content: currentContent,
|
|
operation_type: 'input',
|
|
timestamp: Date.now()
|
|
});
|
|
logger.debug(`历史记录 | 清除后保存当前内容 | 节点:${nodeId} | 输入框:${this.currentInputId}`);
|
|
}
|
|
|
|
// 更新按钮状态
|
|
if (mapping?.widget) {
|
|
const widget = mapping.widget;
|
|
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
|
|
}
|
|
|
|
logger.debug('历史记录 | 清除所有历史');
|
|
} catch (error) {
|
|
logger.error(`历史记录 | 清除失败 | 错误:${error.message}`);
|
|
}
|
|
});
|
|
this.eventCleanups.push(clearAllCleanup);
|
|
|
|
const closeBtn = document.createElement('button');
|
|
closeBtn.className = 'popup_btn';
|
|
UIToolkit.addIconToButton(closeBtn, 'pi-times', '关闭');
|
|
|
|
// 使用EventManager添加点击事件
|
|
const closeCleanup = EventManager.addDOMListener(closeBtn, 'click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
this.hideHistoryPopup();
|
|
});
|
|
this.eventCleanups.push(closeCleanup);
|
|
|
|
// 添加按钮到操作区域
|
|
actions.appendChild(clearCurrentBtn);
|
|
actions.appendChild(clearAllBtn);
|
|
actions.appendChild(closeBtn);
|
|
titleBar.appendChild(title);
|
|
titleBar.appendChild(actions);
|
|
|
|
// 将历史记录按节点分组并排序
|
|
const { orderedNodeIds, nodeGroups } = this._groupAndSortHistoryByNode(historyList, nodeId);
|
|
|
|
// 创建tabs容器
|
|
const tabsContainer = document.createElement('div');
|
|
tabsContainer.className = 'popup_tabs_container';
|
|
|
|
// 创建可滚动区域
|
|
const tabsScroll = document.createElement('div');
|
|
tabsScroll.className = 'popup_tabs_scroll';
|
|
|
|
// 创建tabs
|
|
const tabs = document.createElement('div');
|
|
tabs.className = 'popup_tabs';
|
|
|
|
// 创建左右滚动指示器
|
|
const leftIndicator = document.createElement('div');
|
|
leftIndicator.className = 'tabs_scroll_indicator left';
|
|
|
|
// 添加图标
|
|
const leftIcon = ResourceManager.getIcon('icon-movedown.svg');
|
|
if (leftIcon) {
|
|
leftIcon.classList.add('rotate_left', 'scroll_indicator_icon');
|
|
leftIndicator.appendChild(leftIcon);
|
|
}
|
|
leftIndicator.style.display = 'none'; // 初始隐藏
|
|
|
|
const rightIndicator = document.createElement('div');
|
|
rightIndicator.className = 'tabs_scroll_indicator right';
|
|
|
|
// 添加图标
|
|
const rightIcon = ResourceManager.getIcon('icon-movedown.svg');
|
|
if (rightIcon) {
|
|
rightIcon.classList.add('rotate_right', 'scroll_indicator_icon');
|
|
rightIndicator.appendChild(rightIcon);
|
|
}
|
|
rightIndicator.style.display = 'none'; // 初始隐藏
|
|
|
|
// 添加指示器点击事件 - 改进滚动逻辑
|
|
const leftScrollCleanup = EventManager.addDOMListener(leftIndicator, 'click', () => {
|
|
// 获取可视区域宽度
|
|
const visibleWidth = tabsScroll.clientWidth;
|
|
|
|
// 计算滚动距离,PrimeVue风格是滚动一个较大的距离
|
|
const scrollDistance = visibleWidth * 0.75;
|
|
|
|
// 平滑滚动
|
|
tabsScroll.scrollBy({
|
|
left: -scrollDistance,
|
|
behavior: 'smooth'
|
|
});
|
|
});
|
|
this.eventCleanups.push(leftScrollCleanup);
|
|
|
|
const rightScrollCleanup = EventManager.addDOMListener(rightIndicator, 'click', () => {
|
|
// 获取可视区域宽度
|
|
const visibleWidth = tabsScroll.clientWidth;
|
|
|
|
// 计算滚动距离,PrimeVue风格是滚动一个较大的距离
|
|
const scrollDistance = visibleWidth * 0.75;
|
|
|
|
// 平滑滚动
|
|
tabsScroll.scrollBy({
|
|
left: scrollDistance,
|
|
behavior: 'smooth'
|
|
});
|
|
});
|
|
this.eventCleanups.push(rightScrollCleanup);
|
|
|
|
// 监听滚动事件,显示/隐藏滚动指示器
|
|
const scrollCleanup = EventManager.addDOMListener(tabsScroll, 'scroll', () => {
|
|
// 检查是否需要滚动
|
|
const canScroll = tabsScroll.scrollWidth > tabsScroll.clientWidth;
|
|
|
|
if (!canScroll) {
|
|
// 如果不需要滚动,隐藏两个指示器
|
|
leftIndicator.style.display = 'none';
|
|
rightIndicator.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
// 显示/隐藏左右滚动指示器
|
|
leftIndicator.style.display = tabsScroll.scrollLeft > 0 ? 'flex' : 'none';
|
|
rightIndicator.style.display =
|
|
tabsScroll.scrollLeft < (tabsScroll.scrollWidth - tabsScroll.clientWidth - 2) ? 'flex' : 'none';
|
|
});
|
|
this.eventCleanups.push(scrollCleanup);
|
|
|
|
// 初始检测是否需要滚动指示器
|
|
setTimeout(() => {
|
|
// 检查是否需要滚动
|
|
const canScroll = tabsScroll.scrollWidth > tabsScroll.clientWidth;
|
|
|
|
if (canScroll) {
|
|
// 如果需要滚动,显示右侧指示器
|
|
rightIndicator.style.display = 'flex';
|
|
}
|
|
}, 100);
|
|
|
|
// 创建内容区域
|
|
const tabContentsContainer = document.createElement('div');
|
|
tabContentsContainer.className = 'popup_content';
|
|
|
|
// 记录tab和内容元素的映射
|
|
const tabContentPairs = [];
|
|
|
|
// 遍历已排序的节点ID数组,确保当前节点在最前面,其他节点按序号排序
|
|
orderedNodeIds.forEach((groupNodeId) => {
|
|
const nodeItems = nodeGroups[groupNodeId];
|
|
|
|
// 如果没有数据,跳过
|
|
if (!nodeItems || nodeItems.length === 0) return;
|
|
|
|
// 创建tab
|
|
const tab = document.createElement('div');
|
|
tab.className = 'popup_tab';
|
|
|
|
// 设置tab标题
|
|
if (groupNodeId === nodeId) {
|
|
tab.textContent = '当前节点';
|
|
tab.classList.add('current_node');
|
|
|
|
// 当前节点tab默认选中并激活
|
|
tab.classList.add('active');
|
|
} else {
|
|
// 提取节点编号(假设格式为数字+可能的字母)
|
|
const nodeNumMatch = String(groupNodeId).match(/\d+/);
|
|
const nodeNum = nodeNumMatch ? nodeNumMatch[0] : groupNodeId;
|
|
tab.textContent = `节点 ${nodeNum}`;
|
|
}
|
|
|
|
// 创建内容容器
|
|
const tabContent = document.createElement('div');
|
|
tabContent.className = 'popup_tab_content';
|
|
|
|
// 如果该tab默认选中,显示其内容
|
|
if (tab.classList.contains('active')) {
|
|
tabContent.classList.add('active');
|
|
}
|
|
|
|
// 渲染该节点的历史记录
|
|
if (nodeItems.length === 0) {
|
|
const empty = document.createElement('div');
|
|
empty.className = 'popup_empty';
|
|
empty.textContent = '暂无历史记录';
|
|
tabContent.appendChild(empty);
|
|
} else {
|
|
nodeItems.forEach((item) => {
|
|
const itemDiv = document.createElement('div');
|
|
itemDiv.className = 'popup_list_item';
|
|
|
|
// 添加元数据容器
|
|
const metaDiv = document.createElement('div');
|
|
metaDiv.className = 'history_meta';
|
|
|
|
// 添加输入框ID(始终显示)
|
|
const inputIdSpan = document.createElement('span');
|
|
inputIdSpan.className = 'input_id';
|
|
inputIdSpan.textContent = `${item.input_id}`;
|
|
metaDiv.appendChild(inputIdSpan);
|
|
|
|
// 根据操作类型添加对应标签
|
|
if (item.operation_type) {
|
|
const operationSpan = document.createElement('span');
|
|
|
|
switch (item.operation_type) {
|
|
case 'translate':
|
|
operationSpan.className = 'history_operation translated';
|
|
operationSpan.textContent = '翻译';
|
|
break;
|
|
case 'expand':
|
|
operationSpan.className = 'history_operation expanded';
|
|
operationSpan.textContent = '提示词优化';
|
|
break;
|
|
case 'caption':
|
|
operationSpan.className = 'history_operation caption';
|
|
operationSpan.textContent = '提示词反推';
|
|
break;
|
|
// 其他操作类型可以在这里添加
|
|
}
|
|
|
|
if (operationSpan.textContent) {
|
|
metaDiv.appendChild(operationSpan);
|
|
}
|
|
}
|
|
|
|
// 创建内容容器
|
|
const contentDiv = document.createElement('div');
|
|
contentDiv.className = 'history_content';
|
|
|
|
// 设置显示内容 - 使用格式化方法
|
|
const displayContent = this._formatHistoryContent(item.content, item.operation_type);
|
|
contentDiv.textContent = displayContent;
|
|
|
|
// 组装历史记录项
|
|
itemDiv.appendChild(metaDiv);
|
|
itemDiv.appendChild(contentDiv);
|
|
|
|
// 添加鼠标悬浮事件,显示完整内容的tooltip
|
|
const mouseEnterCleanup = EventManager.addDOMListener(itemDiv, 'mouseenter', () => {
|
|
this._showTooltip(itemDiv, displayContent);
|
|
});
|
|
|
|
const mouseLeaveCleanup = EventManager.addDOMListener(itemDiv, 'mouseleave', () => {
|
|
this._hideTooltip();
|
|
});
|
|
|
|
this.eventCleanups.push(mouseEnterCleanup, mouseLeaveCleanup);
|
|
|
|
// 使用EventManager添加点击事件
|
|
const itemCleanup = EventManager.addDOMListener(itemDiv, 'click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// 主动关闭 tooltip
|
|
this._hideTooltip();
|
|
|
|
// 使用UIToolkit写入内容到输入框
|
|
const success = UIToolkit.writeToInput(item.content, this.currentNodeId, this.currentInputId, {
|
|
highlight: true,
|
|
focus: true,
|
|
widgetKey: this.currentWidgetKey
|
|
});
|
|
|
|
if (success) {
|
|
// 如果内容来自其他输入框,使用新方法添加历史并更新撤销状态
|
|
if (item.node_id !== this.currentNodeId || item.input_id !== this.currentInputId) {
|
|
HistoryCacheService.addHistoryAndUpdateUndoState(
|
|
this.currentNodeId,
|
|
this.currentInputId,
|
|
item.content,
|
|
'input'
|
|
);
|
|
}
|
|
|
|
// 隐藏历史弹窗
|
|
this.hideHistoryPopup();
|
|
}
|
|
});
|
|
this.eventCleanups.push(itemCleanup);
|
|
|
|
tabContent.appendChild(itemDiv);
|
|
});
|
|
}
|
|
|
|
// 添加到容器
|
|
tabs.appendChild(tab);
|
|
tabContentsContainer.appendChild(tabContent);
|
|
|
|
// 保存tab和内容的映射关系,用于点击tab时切换内容
|
|
tabContentPairs.push({ tab, content: tabContent });
|
|
|
|
// 添加tab点击事件
|
|
const tabCleanup = EventManager.addDOMListener(tab, 'click', () => {
|
|
// 获取当前激活的标签
|
|
const currentActiveTab = tabs.querySelector('.popup_tab.active');
|
|
|
|
// 如果点击的是当前激活的标签,不做任何处理
|
|
if (currentActiveTab === tab) return;
|
|
|
|
// 为当前激活的标签添加退出动画
|
|
if (currentActiveTab) {
|
|
currentActiveTab.classList.add('exiting');
|
|
// 监听动画结束
|
|
const animationEndHandler = () => {
|
|
currentActiveTab.classList.remove('active', 'exiting');
|
|
currentActiveTab.removeEventListener('transitionend', animationEndHandler);
|
|
};
|
|
currentActiveTab.addEventListener('transitionend', animationEndHandler);
|
|
}
|
|
|
|
// 移除所有内容的active类
|
|
tabContentPairs.forEach(pair => {
|
|
pair.content.classList.remove('active');
|
|
});
|
|
|
|
// 激活当前点击的tab
|
|
tab.classList.add('active');
|
|
tabContent.classList.add('active');
|
|
|
|
// 滚动到可见区域
|
|
tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
|
});
|
|
this.eventCleanups.push(tabCleanup);
|
|
});
|
|
|
|
// 组装tabs容器
|
|
tabsScroll.appendChild(tabs);
|
|
tabsContainer.appendChild(tabsScroll);
|
|
tabsContainer.appendChild(leftIndicator);
|
|
tabsContainer.appendChild(rightIndicator);
|
|
|
|
// 组装弹窗
|
|
popup.appendChild(titleBar);
|
|
popup.appendChild(tabsContainer);
|
|
popup.appendChild(tabContentsContainer);
|
|
|
|
return popup;
|
|
}
|
|
|
|
/**
|
|
* 按节点分组并排序历史记录
|
|
*/
|
|
static _groupAndSortHistoryByNode(historyList, currentNodeId) {
|
|
// 按节点ID分组
|
|
const groups = {};
|
|
|
|
// 收集所有节点ID,用于后续排序
|
|
const nodeIds = new Set();
|
|
|
|
// 分组历史记录
|
|
historyList.forEach(item => {
|
|
const nodeId = item.node_id;
|
|
if (!groups[nodeId]) {
|
|
groups[nodeId] = [];
|
|
nodeIds.add(nodeId);
|
|
}
|
|
groups[nodeId].push(item);
|
|
});
|
|
|
|
// 确保当前节点ID存在于节点列表中
|
|
if (currentNodeId && !nodeIds.has(currentNodeId)) {
|
|
nodeIds.add(currentNodeId);
|
|
groups[currentNodeId] = [];
|
|
}
|
|
|
|
// 将节点ID转换为数组并排序
|
|
// 排序规则:1. 当前节点最前 2. 其他节点按数字序号排序
|
|
const sortedNodeIds = Array.from(nodeIds).sort((a, b) => {
|
|
// 如果a是当前节点,排在最前面
|
|
if (a === currentNodeId) return -1;
|
|
// 如果b是当前节点,排在最前面
|
|
if (b === currentNodeId) return 1;
|
|
|
|
// 提取节点ID中的数字部分
|
|
const getNodeNumber = (id) => {
|
|
const match = String(id).match(/\d+/);
|
|
return match ? parseInt(match[0]) : 0;
|
|
};
|
|
|
|
// 按数字大小排序
|
|
return getNodeNumber(a) - getNodeNumber(b);
|
|
});
|
|
|
|
// 对每个组内的历史记录按时间戳排序(最新的在前)
|
|
Object.keys(groups).forEach(nodeId => {
|
|
groups[nodeId].sort((a, b) => b.timestamp - a.timestamp);
|
|
});
|
|
|
|
// 返回排序后的节点ID数组和分组后的历史记录
|
|
return {
|
|
orderedNodeIds: sortedNodeIds,
|
|
nodeGroups: groups
|
|
};
|
|
}
|
|
}
|
|
|
|
export { HistoryManager }; |