mirror of
https://github.com/Cola-Echo/memory-manager-concurrent.git
synced 2026-06-06 12:25:53 +00:00
feat: 优化进度条体验和修复一键全选功能
- 进度条改用检查点驱动模拟真实流式传输进度 (5→15→25→35→45→60→75→85→92→100) - 每个检查点间使用 ease-out 缓动平滑过渡 - 完成时从当前进度平滑动画到 100% - 修复一键全选按钮事件绑定问题 - 添加调试日志帮助诊断问题 - 修复 addSystemMessage 使用不存在容器的问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
9
package-lock.json
generated
9
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "memory-manager-concurrent",
|
"name": "memory-manager-concurrent",
|
||||||
"version": "0.4.2",
|
"version": "0.4.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "memory-manager-concurrent",
|
"name": "memory-manager-concurrent",
|
||||||
"version": "0.4.2",
|
"version": "0.4.7",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
@@ -351,6 +351,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -377,6 +378,7 @@
|
|||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -449,6 +451,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -1370,6 +1373,7 @@
|
|||||||
"integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
|
"integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.7",
|
"@types/eslint-scope": "^3.7.7",
|
||||||
"@types/estree": "^1.0.8",
|
"@types/estree": "^1.0.8",
|
||||||
@@ -1419,6 +1423,7 @@
|
|||||||
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
|
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discoveryjs/json-ext": "^0.5.0",
|
"@discoveryjs/json-ext": "^0.5.0",
|
||||||
"@webpack-cli/configtest": "^2.1.1",
|
"@webpack-cli/configtest": "^2.1.1",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* OpenAI API 提供商
|
* OpenAI API 提供商
|
||||||
* @module api/providers/openai
|
* @module api/providers/openai
|
||||||
@@ -5,7 +6,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 模拟流式进度管理器
|
* 模拟流式进度管理器
|
||||||
* 使用时间驱动的平滑进度增长,提供稳定的视觉体验
|
* 使用检查点驱动的进度增长,模拟真实的流式传输体验
|
||||||
*/
|
*/
|
||||||
class SimulatedProgressManager {
|
class SimulatedProgressManager {
|
||||||
constructor(taskId, progressTracker, config = {}) {
|
constructor(taskId, progressTracker, config = {}) {
|
||||||
@@ -15,17 +16,34 @@ class SimulatedProgressManager {
|
|||||||
this.currentProgress = 0;
|
this.currentProgress = 0;
|
||||||
this.intervalId = null;
|
this.intervalId = null;
|
||||||
this.isCompleted = false;
|
this.isCompleted = false;
|
||||||
|
this.completionIntervalId = null;
|
||||||
|
|
||||||
|
// 检查点配置:模拟真实的处理阶段
|
||||||
|
// [进度值, 到达该点的预估时间(ms), 停留时间(ms)]
|
||||||
|
this.checkpoints = config.checkpoints || [
|
||||||
|
{ progress: 5, time: 500, pause: 100 }, // 初始化
|
||||||
|
{ progress: 15, time: 2000, pause: 200 }, // 开始处理
|
||||||
|
{ progress: 25, time: 4000, pause: 150 }, // 解析请求
|
||||||
|
{ progress: 35, time: 7000, pause: 300 }, // 生成中...
|
||||||
|
{ progress: 45, time: 10000, pause: 200 }, // 持续生成
|
||||||
|
{ progress: 60, time: 15000, pause: 400 }, // 主要内容
|
||||||
|
{ progress: 75, time: 20000, pause: 300 }, // 接近完成
|
||||||
|
{ progress: 85, time: 25000, pause: 200 }, // 收尾阶段
|
||||||
|
{ progress: 92, time: 30000, pause: 0 }, // 等待完成
|
||||||
|
];
|
||||||
|
|
||||||
|
this.currentCheckpointIndex = 0;
|
||||||
|
this.lastCheckpointTime = Date.now();
|
||||||
|
this.isPaused = false;
|
||||||
|
this.pauseEndTime = 0;
|
||||||
|
|
||||||
// 配置参数
|
// 配置参数
|
||||||
this.maxProgress = config.maxProgress || 92; // 模拟进度最大值
|
this.updateInterval = config.updateInterval || 50; // 更新间隔(毫秒)
|
||||||
this.duration = config.duration || 30000; // 预估总时长(毫秒)
|
this.completionDuration = config.completionDuration || 300; // 完成动画时长
|
||||||
this.updateInterval = config.updateInterval || 100; // 更新间隔(毫秒)
|
|
||||||
|
|
||||||
// 使用缓动函数使进度更自然(开始快,后面慢)
|
// 流数据相关
|
||||||
this.easingFn = (t) => {
|
this.totalCharsReceived = 0;
|
||||||
// ease-out-cubic: 1 - (1 - t)^3
|
this.lastCharsReceived = 0;
|
||||||
return 1 - Math.pow(1 - t, 3);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,29 +58,103 @@ class SimulatedProgressManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsed = Date.now() - this.startTime;
|
this.tick();
|
||||||
const t = Math.min(elapsed / this.duration, 1);
|
|
||||||
const easedProgress = this.easingFn(t) * this.maxProgress;
|
|
||||||
|
|
||||||
// 确保进度只增不减
|
|
||||||
if (easedProgress > this.currentProgress) {
|
|
||||||
this.currentProgress = easedProgress;
|
|
||||||
this.updateProgress(this.currentProgress);
|
|
||||||
}
|
|
||||||
}, this.updateInterval);
|
}, this.updateInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每帧更新
|
||||||
|
*/
|
||||||
|
tick() {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// 如果在停顿中,等待停顿结束
|
||||||
|
if (this.isPaused) {
|
||||||
|
if (now >= this.pauseEndTime) {
|
||||||
|
this.isPaused = false;
|
||||||
|
this.lastCheckpointTime = now;
|
||||||
|
this.currentCheckpointIndex++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前和下一个检查点
|
||||||
|
if (this.currentCheckpointIndex >= this.checkpoints.length) {
|
||||||
|
return; // 已到达最后一个检查点
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentCp = this.currentCheckpointIndex > 0
|
||||||
|
? this.checkpoints[this.currentCheckpointIndex - 1]
|
||||||
|
: { progress: 0, time: 0, pause: 0 };
|
||||||
|
const nextCp = this.checkpoints[this.currentCheckpointIndex];
|
||||||
|
|
||||||
|
// 计算当前检查点段的进度
|
||||||
|
const segmentStartTime = this.currentCheckpointIndex === 0
|
||||||
|
? this.startTime
|
||||||
|
: this.lastCheckpointTime;
|
||||||
|
const segmentDuration = nextCp.time - (currentCp.time || 0);
|
||||||
|
const elapsed = now - segmentStartTime;
|
||||||
|
const t = Math.min(elapsed / segmentDuration, 1);
|
||||||
|
|
||||||
|
// 使用 ease-out 缓动在检查点之间过渡
|
||||||
|
const eased = 1 - Math.pow(1 - t, 2);
|
||||||
|
const progressInSegment = currentCp.progress + (nextCp.progress - currentCp.progress) * eased;
|
||||||
|
|
||||||
|
// 更新进度(只增不减)
|
||||||
|
if (progressInSegment > this.currentProgress) {
|
||||||
|
this.currentProgress = progressInSegment;
|
||||||
|
this.updateProgress(this.currentProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 到达检查点时触发停顿
|
||||||
|
if (t >= 1) {
|
||||||
|
this.currentProgress = nextCp.progress;
|
||||||
|
this.updateProgress(this.currentProgress);
|
||||||
|
|
||||||
|
if (nextCp.pause > 0) {
|
||||||
|
this.isPaused = true;
|
||||||
|
this.pauseEndTime = now + nextCp.pause;
|
||||||
|
} else {
|
||||||
|
this.lastCheckpointTime = now;
|
||||||
|
this.currentCheckpointIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收到流数据时调用,加速进度
|
* 接收到流数据时调用,加速进度
|
||||||
* @param {number} charsReceived 已接收字符数
|
* @param {number} charsReceived 已接收字符数
|
||||||
*/
|
*/
|
||||||
onStreamData(charsReceived) {
|
onStreamData(charsReceived) {
|
||||||
// 当收到流数据时,适度加速进度
|
this.totalCharsReceived = charsReceived;
|
||||||
// 每收到 100 字符,进度至少推进一点
|
const newChars = charsReceived - this.lastCharsReceived;
|
||||||
const minProgress = Math.min(this.maxProgress, 10 + charsReceived / 50);
|
this.lastCharsReceived = charsReceived;
|
||||||
if (minProgress > this.currentProgress) {
|
|
||||||
this.currentProgress = minProgress;
|
// 根据收到的字符数加速进度
|
||||||
this.updateProgress(this.currentProgress);
|
// 每收到数据,至少推进到当前检查点的 50%
|
||||||
|
if (newChars > 0 && this.currentCheckpointIndex < this.checkpoints.length) {
|
||||||
|
const currentCp = this.currentCheckpointIndex > 0
|
||||||
|
? this.checkpoints[this.currentCheckpointIndex - 1]
|
||||||
|
: { progress: 0 };
|
||||||
|
const nextCp = this.checkpoints[this.currentCheckpointIndex];
|
||||||
|
|
||||||
|
// 根据字符数推进进度
|
||||||
|
const charBasedProgress = Math.min(
|
||||||
|
nextCp.progress,
|
||||||
|
currentCp.progress + (charsReceived / 30) // 每30字符推进1%
|
||||||
|
);
|
||||||
|
|
||||||
|
if (charBasedProgress > this.currentProgress) {
|
||||||
|
this.currentProgress = charBasedProgress;
|
||||||
|
this.updateProgress(this.currentProgress);
|
||||||
|
|
||||||
|
// 如果超过当前检查点,跳到下一个
|
||||||
|
if (this.currentProgress >= nextCp.progress) {
|
||||||
|
this.currentCheckpointIndex++;
|
||||||
|
this.lastCheckpointTime = Date.now();
|
||||||
|
this.isPaused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,12 +169,42 @@ class SimulatedProgressManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完成进度
|
* 完成进度(带平滑动画从当前进度过渡到100%)
|
||||||
*/
|
*/
|
||||||
complete() {
|
complete() {
|
||||||
this.isCompleted = true;
|
this.isCompleted = true;
|
||||||
this.stop();
|
this.stop();
|
||||||
this.updateProgress(100);
|
|
||||||
|
// 平滑过渡到 100%
|
||||||
|
const startProgress = this.currentProgress;
|
||||||
|
const progressDiff = 100 - startProgress;
|
||||||
|
const startTime = Date.now();
|
||||||
|
const duration = this.completionDuration;
|
||||||
|
|
||||||
|
// 如果差距很小,直接完成
|
||||||
|
if (progressDiff <= 1) {
|
||||||
|
this.updateProgress(100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 setInterval 进行平滑动画
|
||||||
|
this.completionIntervalId = setInterval(() => {
|
||||||
|
const elapsed = Date.now() - startTime;
|
||||||
|
const t = Math.min(elapsed / duration, 1);
|
||||||
|
|
||||||
|
// 使用 ease-out 缓动
|
||||||
|
const eased = 1 - Math.pow(1 - t, 2);
|
||||||
|
const newProgress = startProgress + progressDiff * eased;
|
||||||
|
|
||||||
|
this.currentProgress = newProgress;
|
||||||
|
this.updateProgress(newProgress);
|
||||||
|
|
||||||
|
if (t >= 1) {
|
||||||
|
clearInterval(this.completionIntervalId);
|
||||||
|
this.completionIntervalId = null;
|
||||||
|
this.updateProgress(100);
|
||||||
|
}
|
||||||
|
}, 16); // ~60fps
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const defaultConfig = Object.freeze({
|
|||||||
sendIndexOnly: false,
|
sendIndexOnly: false,
|
||||||
showSummaryCheck: false,
|
showSummaryCheck: false,
|
||||||
enableRecentPlot: true, // 启用剧情末尾(截取并注入到汇总检查)
|
enableRecentPlot: true, // 启用剧情末尾(截取并注入到汇总检查)
|
||||||
|
recentPlotLength: 200, // 剧情末尾截取字数(10-300)
|
||||||
// 索引合并模式配置
|
// 索引合并模式配置
|
||||||
indexMergeEnabled: false, // 是否启用索引合并
|
indexMergeEnabled: false, // 是否启用索引合并
|
||||||
indexMergeConfig: {
|
indexMergeConfig: {
|
||||||
|
|||||||
@@ -90,11 +90,12 @@ const Logger = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 调试日志(受 showLogs 控制)
|
* 调试日志(受 showLogs 控制)
|
||||||
|
* 注意:使用 console.log 而非 console.debug,确保在所有浏览器设置下可见
|
||||||
* @param {...any} args 日志参数
|
* @param {...any} args 日志参数
|
||||||
*/
|
*/
|
||||||
debug: (...args) => {
|
debug: (...args) => {
|
||||||
if (Logger.shouldShowLogs()) {
|
if (Logger.shouldShowLogs()) {
|
||||||
console.debug(Logger.prefix, ...args);
|
console.log(Logger.prefix, "[DEBUG]", ...args);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ const Logger = {
|
|||||||
|
|
||||||
debug: (...args) => {
|
debug: (...args) => {
|
||||||
if (Logger.shouldShowLogs()) {
|
if (Logger.shouldShowLogs()) {
|
||||||
console.debug(modulePrefix, ...args);
|
console.log(modulePrefix, "[DEBUG]", ...args);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -503,10 +503,11 @@ export async function processMemoryForMessage(userMessage) {
|
|||||||
const context = getRecentContext(chat, contextRounds);
|
const context = getRecentContext(chat, contextRounds);
|
||||||
|
|
||||||
// 获取标签过滤配置(用于最近剧情截取)
|
// 获取标签过滤配置(用于最近剧情截取)
|
||||||
// [标签过滤调用点2] 用于处理最后一条助手消息的末尾200字
|
// [标签过滤调用点2] 用于处理最后一条助手消息的末尾
|
||||||
const tagFilterConfig = globalConfig.contextTagFilter;
|
const tagFilterConfig = globalConfig.contextTagFilter;
|
||||||
|
|
||||||
// 从最后一条助手消息中截取末尾200字
|
// 从最后一条助手消息中截取末尾(使用配置的字数,默认200)
|
||||||
|
const recentPlotLength = globalSettings.recentPlotLength ?? 200;
|
||||||
let latestContext = "";
|
let latestContext = "";
|
||||||
if (
|
if (
|
||||||
globalSettings.enableRecentPlot !== false &&
|
globalSettings.enableRecentPlot !== false &&
|
||||||
@@ -530,7 +531,7 @@ export async function processMemoryForMessage(userMessage) {
|
|||||||
// 使用 filterContentByRole 处理标签过滤(AI消息 = false)
|
// 使用 filterContentByRole 处理标签过滤(AI消息 = false)
|
||||||
content = filterContentByRole(content, tagFilterConfig, false);
|
content = filterContentByRole(content, tagFilterConfig, false);
|
||||||
|
|
||||||
latestContext = content.slice(-200).trim();
|
latestContext = content.slice(-recentPlotLength).trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1725,12 +1725,11 @@ async function generatePlotOptimize(userInput = "") {
|
|||||||
Logger.log("[剧情优化] 调用 progressTracker.addTask");
|
Logger.log("[剧情优化] 调用 progressTracker.addTask");
|
||||||
try {
|
try {
|
||||||
// 确保任务被正确添加,并且进度UI被显示
|
// 确保任务被正确添加,并且进度UI被显示
|
||||||
|
// addTask 已经设置了 status: "running",不需要再调用 startTask
|
||||||
progressTracker.addTask("plot_optimize", "剧情优化", "plot");
|
progressTracker.addTask("plot_optimize", "剧情优化", "plot");
|
||||||
Logger.log("[剧情优化] addTask 调用成功");
|
Logger.log("[剧情优化] addTask 调用成功");
|
||||||
// 立即更新进度为1%,确保进度条显示
|
// 立即更新进度为5%,确保进度条有初始显示
|
||||||
progressTracker.updateStreamProgress("plot_optimize", 1);
|
progressTracker.updateStreamProgress("plot_optimize", 5);
|
||||||
// 立即开始任务,确保状态正确
|
|
||||||
progressTracker.startTask("plot_optimize");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.error("[剧情优化] addTask 调用失败:", e);
|
Logger.error("[剧情优化] addTask 调用失败:", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
/**
|
/**
|
||||||
* 进度追踪器模块
|
* 进度追踪器模块
|
||||||
* @module ui/components/progress-tracker
|
* @module ui/components/progress-tracker
|
||||||
@@ -6,11 +7,12 @@
|
|||||||
import Logger from "@core/logger";
|
import Logger from "@core/logger";
|
||||||
|
|
||||||
// 消息进度面板引用(将在初始化时注入)
|
// 消息进度面板引用(将在初始化时注入)
|
||||||
|
/** @type {any} */
|
||||||
let messageProgressPanel = null;
|
let messageProgressPanel = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置消息进度面板引用
|
* 设置消息进度面板引用
|
||||||
* @param {object} panel 消息进度面板实例
|
* @param {any} panel 消息进度面板实例
|
||||||
*/
|
*/
|
||||||
export function setMessageProgressPanel(panel) {
|
export function setMessageProgressPanel(panel) {
|
||||||
messageProgressPanel = panel;
|
messageProgressPanel = panel;
|
||||||
@@ -182,6 +184,15 @@ export class ProgressTracker {
|
|||||||
type,
|
type,
|
||||||
);
|
);
|
||||||
Logger.log("[ProgressTracker] addTask 被调用:", taskId, name, type);
|
Logger.log("[ProgressTracker] addTask 被调用:", taskId, name, type);
|
||||||
|
|
||||||
|
// 先确保 messageProgressPanel 容器已创建(在添加任务数据之前)
|
||||||
|
if (messageProgressPanel && !messageProgressPanel.container) {
|
||||||
|
Logger.log("[ProgressTracker] 预先初始化 messageProgressPanel 容器");
|
||||||
|
messageProgressPanel.createDOM();
|
||||||
|
messageProgressPanel.bindEvents();
|
||||||
|
messageProgressPanel.loadPosition();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.tasks.has(taskId)) {
|
if (this.tasks.has(taskId)) {
|
||||||
const task = this.tasks.get(taskId);
|
const task = this.tasks.get(taskId);
|
||||||
task.status = "running";
|
task.status = "running";
|
||||||
@@ -213,15 +224,6 @@ export class ProgressTracker {
|
|||||||
!!messageProgressPanel,
|
!!messageProgressPanel,
|
||||||
);
|
);
|
||||||
if (messageProgressPanel) {
|
if (messageProgressPanel) {
|
||||||
// 确保 messageProgressPanel 已初始化(首次调用时需要创建 DOM)
|
|
||||||
Logger.log(
|
|
||||||
"[ProgressTracker] messageProgressPanel.container 状态:",
|
|
||||||
!!messageProgressPanel.container,
|
|
||||||
);
|
|
||||||
if (!messageProgressPanel.container) {
|
|
||||||
Logger.log("[ProgressTracker] 初始化 messageProgressPanel");
|
|
||||||
messageProgressPanel.init();
|
|
||||||
}
|
|
||||||
const activeTasks = new Map();
|
const activeTasks = new Map();
|
||||||
for (const [id, task] of this.tasks) {
|
for (const [id, task] of this.tasks) {
|
||||||
if (task.status !== "success" && task.status !== "error") {
|
if (task.status !== "success" && task.status !== "error") {
|
||||||
|
|||||||
@@ -85,6 +85,18 @@ export class MemorySearchPanel {
|
|||||||
this.toggleMinimize();
|
this.toggleMinimize();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 一键全选按钮
|
||||||
|
const injectAllBtn = document.getElementById("mm-search-inject-all");
|
||||||
|
if (injectAllBtn) {
|
||||||
|
injectAllBtn.addEventListener("click", () => {
|
||||||
|
Logger.debug("[一键全选] 按钮被点击");
|
||||||
|
this.selectAllUnrejected();
|
||||||
|
});
|
||||||
|
Logger.debug("[记忆搜索助手] 一键全选按钮事件已绑定");
|
||||||
|
} else {
|
||||||
|
Logger.warn("[记忆搜索助手] 一键全选按钮未找到,事件未绑定");
|
||||||
|
}
|
||||||
|
|
||||||
// 确认注入按钮
|
// 确认注入按钮
|
||||||
document
|
document
|
||||||
.getElementById("mm-search-confirm")
|
.getElementById("mm-search-confirm")
|
||||||
@@ -901,6 +913,68 @@ export class MemorySearchPanel {
|
|||||||
return historicalLines.join("\n");
|
return historicalLines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键全选所有未拒绝、未移除的记忆
|
||||||
|
* 将所有未被拒绝的搜索结果标记为已采纳,用户再点确认注入完成操作
|
||||||
|
*/
|
||||||
|
selectAllUnrejected() {
|
||||||
|
// 获取所有搜索结果项
|
||||||
|
const container = document.getElementById("mm-search-books-container");
|
||||||
|
if (!container) {
|
||||||
|
Logger.warn("[一键全选] 容器 mm-search-books-container 未找到");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allResultItems = container.querySelectorAll(".mm-search-result-item");
|
||||||
|
Logger.debug(`[一键全选] 找到 ${allResultItems.length} 个搜索结果项`);
|
||||||
|
|
||||||
|
if (allResultItems.length === 0) {
|
||||||
|
// 使用第一个世界书面板显示消息
|
||||||
|
if (this.summaryBooks.length > 0) {
|
||||||
|
this.addBookSystemMessage(this.summaryBooks[0].name, "没有可选择的搜索结果");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedCount = 0;
|
||||||
|
const beforeCount = this.selectedMemories.length;
|
||||||
|
|
||||||
|
for (const resultItem of allResultItems) {
|
||||||
|
// 跳过已拒绝的
|
||||||
|
if (resultItem.classList.contains("mm-rejected")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过已经采纳的(已在 selectedMemories 中)
|
||||||
|
if (resultItem.classList.contains("mm-adopted")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有 _memoryData
|
||||||
|
if (!resultItem._memoryData) {
|
||||||
|
Logger.warn("[一键全选] 搜索结果项缺少 _memoryData:", resultItem.dataset.resultId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过已有的 adoptMemory 方法采纳
|
||||||
|
this.adoptMemory(resultItem);
|
||||||
|
selectedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualAdopted = this.selectedMemories.length - beforeCount;
|
||||||
|
Logger.debug(`[一键全选] 尝试选择 ${selectedCount} 条,实际采纳 ${actualAdopted} 条`);
|
||||||
|
|
||||||
|
// 使用第一个世界书面板显示消息
|
||||||
|
const firstBookName = this.summaryBooks.length > 0 ? this.summaryBooks[0].name : null;
|
||||||
|
if (firstBookName) {
|
||||||
|
if (actualAdopted === 0) {
|
||||||
|
this.addBookSystemMessage(firstBookName, "没有新的条目可选择(可能都已采纳或拒绝)");
|
||||||
|
} else {
|
||||||
|
this.addBookSystemMessage(firstBookName, `已全选 ${actualAdopted} 条记忆,请点击「确认注入」完成操作`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确认选择
|
* 确认选择
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -641,11 +641,26 @@ function bindSettingsEvents() {
|
|||||||
?.addEventListener("change", (e) => {
|
?.addEventListener("change", (e) => {
|
||||||
const checked = e.target.checked;
|
const checked = e.target.checked;
|
||||||
updateGlobalSettings({ enableRecentPlot: checked });
|
updateGlobalSettings({ enableRecentPlot: checked });
|
||||||
|
// 显示/隐藏字数滑条
|
||||||
|
const lengthContainer = document.getElementById("mm-recent-plot-length-container");
|
||||||
|
if (lengthContainer) {
|
||||||
|
lengthContainer.style.display = checked ? "block" : "none";
|
||||||
|
}
|
||||||
if (typeof toastr !== 'undefined') {
|
if (typeof toastr !== 'undefined') {
|
||||||
toastr.success(`剧情末尾已${checked ? "启用" : "禁用"}`, "记忆管理并发系统");
|
toastr.success(`剧情末尾已${checked ? "启用" : "禁用"}`, "记忆管理并发系统");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 剧情末尾字数滑块
|
||||||
|
document
|
||||||
|
.getElementById("mm-recent-plot-length")
|
||||||
|
?.addEventListener("input", (e) => {
|
||||||
|
const value = parseInt(e.target.value) ?? 200;
|
||||||
|
const valueEl = document.getElementById("mm-recent-plot-length-value");
|
||||||
|
if (valueEl) valueEl.textContent = value;
|
||||||
|
updateGlobalSettings({ recentPlotLength: value });
|
||||||
|
});
|
||||||
|
|
||||||
// 上下文轮数滑块
|
// 上下文轮数滑块
|
||||||
document
|
document
|
||||||
.getElementById("mm-context-rounds")
|
.getElementById("mm-context-rounds")
|
||||||
@@ -1506,6 +1521,21 @@ export function loadGlobalSettingsUI() {
|
|||||||
recentPlotCheckbox.checked = settings.enableRecentPlot !== false;
|
recentPlotCheckbox.checked = settings.enableRecentPlot !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 剧情末尾字数滑条
|
||||||
|
const recentPlotLengthContainer = document.getElementById("mm-recent-plot-length-container");
|
||||||
|
const recentPlotLengthInput = document.getElementById("mm-recent-plot-length");
|
||||||
|
const recentPlotLengthValue = document.getElementById("mm-recent-plot-length-value");
|
||||||
|
if (recentPlotLengthContainer) {
|
||||||
|
// 根据启用状态显示/隐藏
|
||||||
|
recentPlotLengthContainer.style.display = settings.enableRecentPlot !== false ? "block" : "none";
|
||||||
|
}
|
||||||
|
if (recentPlotLengthInput) {
|
||||||
|
recentPlotLengthInput.value = settings.recentPlotLength ?? 200;
|
||||||
|
}
|
||||||
|
if (recentPlotLengthValue) {
|
||||||
|
recentPlotLengthValue.textContent = settings.recentPlotLength ?? 200;
|
||||||
|
}
|
||||||
|
|
||||||
// 上下文轮次
|
// 上下文轮次
|
||||||
const contextRoundsInput = document.getElementById("mm-context-rounds");
|
const contextRoundsInput = document.getElementById("mm-context-rounds");
|
||||||
const contextRoundsValue = document.getElementById("mm-context-rounds-value");
|
const contextRoundsValue = document.getElementById("mm-context-rounds-value");
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
|
|
||||||
<div class="mm-search-panel-footer">
|
<div class="mm-search-panel-footer">
|
||||||
<div class="mm-search-actions">
|
<div class="mm-search-actions">
|
||||||
|
<button id="mm-search-inject-all" class="mm-btn mm-btn-primary" title="全选所有未拒绝的条目">
|
||||||
|
<i class="fa-solid fa-bolt"></i> 一键全选
|
||||||
|
</button>
|
||||||
<button id="mm-search-confirm" class="mm-btn mm-btn-secondary" disabled>
|
<button id="mm-search-confirm" class="mm-btn mm-btn-secondary" disabled>
|
||||||
<i class="fa-solid fa-check"></i> 确认注入
|
<i class="fa-solid fa-check"></i> 确认注入
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -34,6 +34,23 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 剧情末尾字数设置(当启用剧情末尾时显示) -->
|
||||||
|
<div class="mm-setting-item" id="mm-recent-plot-length-container" style="display: none;">
|
||||||
|
<label>剧情末尾字数</label>
|
||||||
|
<div class="mm-slider-row">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="mm-recent-plot-length"
|
||||||
|
value="200"
|
||||||
|
min="10"
|
||||||
|
max="300"
|
||||||
|
step="10"
|
||||||
|
/>
|
||||||
|
<span id="mm-recent-plot-length-value">200</span>
|
||||||
|
</div>
|
||||||
|
<small class="mm-hint">截取最后一条AI消息末尾的字数(10-300)</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 功能开关折叠卡片 -->
|
<!-- 功能开关折叠卡片 -->
|
||||||
<div class="mm-collapse-card" id="mm-feature-switch-card">
|
<div class="mm-collapse-card" id="mm-feature-switch-card">
|
||||||
<div class="mm-collapse-header" id="mm-feature-switch-toggle">
|
<div class="mm-collapse-header" id="mm-feature-switch-toggle">
|
||||||
|
|||||||
Reference in New Issue
Block a user