Add files via upload
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* LittleWhiteBox 共享常量
|
||||
*/
|
||||
|
||||
export const EXT_ID = "LittleWhiteBox";
|
||||
export const EXT_NAME = "小白X";
|
||||
export const extensionFolderPath = `scripts/extensions/third-party/${EXT_ID}`;
|
||||
/**
|
||||
* LittleWhiteBox 共享常量
|
||||
*/
|
||||
|
||||
export const EXT_ID = "LittleWhiteBox";
|
||||
export const EXT_NAME = "小白X";
|
||||
export const extensionFolderPath = `scripts/extensions/third-party/${EXT_ID}`;
|
||||
|
||||
138
core/server-storage.js
Normal file
138
core/server-storage.js
Normal file
@@ -0,0 +1,138 @@
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// 服务器文件存储工具
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
import { getRequestHeaders } from '../../../../../script.js';
|
||||
import { debounce } from '../../../../utils.js';
|
||||
|
||||
const toBase64 = (text) => btoa(unescape(encodeURIComponent(text)));
|
||||
|
||||
class StorageFile {
|
||||
constructor(filename, opts = {}) {
|
||||
this.filename = filename;
|
||||
this.cache = null;
|
||||
this._loading = null;
|
||||
this._dirtyVersion = 0;
|
||||
this._savedVersion = 0;
|
||||
this._saving = false;
|
||||
this._pendingSave = false;
|
||||
this._retryCount = 0;
|
||||
this._retryTimer = null;
|
||||
this._maxRetries = Number.isFinite(opts.maxRetries) ? opts.maxRetries : 5;
|
||||
const debounceMs = Number.isFinite(opts.debounceMs) ? opts.debounceMs : 2000;
|
||||
this._saveDebounced = debounce(() => this.saveNow(), debounceMs);
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (this.cache !== null) return this.cache;
|
||||
if (this._loading) return this._loading;
|
||||
|
||||
this._loading = (async () => {
|
||||
try {
|
||||
const res = await fetch(`/user/files/${this.filename}`, {
|
||||
headers: getRequestHeaders(),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (!res.ok) {
|
||||
this.cache = {};
|
||||
return this.cache;
|
||||
}
|
||||
const text = await res.text();
|
||||
this.cache = text ? (JSON.parse(text) || {}) : {};
|
||||
} catch {
|
||||
this.cache = {};
|
||||
} finally {
|
||||
this._loading = null;
|
||||
}
|
||||
return this.cache;
|
||||
})();
|
||||
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
async get(key, defaultValue = null) {
|
||||
const data = await this.load();
|
||||
return data[key] ?? defaultValue;
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
const data = await this.load();
|
||||
data[key] = value;
|
||||
this._dirtyVersion++;
|
||||
this._saveDebounced();
|
||||
}
|
||||
|
||||
async delete(key) {
|
||||
const data = await this.load();
|
||||
if (key in data) {
|
||||
delete data[key];
|
||||
this._dirtyVersion++;
|
||||
this._saveDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
async saveNow() {
|
||||
if (this._saving) {
|
||||
this._pendingSave = true;
|
||||
return;
|
||||
}
|
||||
if (!this.cache || this._dirtyVersion === this._savedVersion) return;
|
||||
|
||||
this._saving = true;
|
||||
this._pendingSave = false;
|
||||
const versionToSave = this._dirtyVersion;
|
||||
|
||||
try {
|
||||
const json = JSON.stringify(this.cache);
|
||||
const base64 = toBase64(json);
|
||||
const res = await fetch('/api/files/upload', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name: this.filename, data: base64 }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
this._savedVersion = Math.max(this._savedVersion, versionToSave);
|
||||
this._retryCount = 0;
|
||||
if (this._retryTimer) {
|
||||
clearTimeout(this._retryTimer);
|
||||
this._retryTimer = null;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[ServerStorage] 保存失败:', err);
|
||||
this._retryCount++;
|
||||
const delay = Math.min(30000, 2000 * (2 ** Math.max(0, this._retryCount - 1)));
|
||||
if (!this._retryTimer && this._retryCount <= this._maxRetries) {
|
||||
this._retryTimer = setTimeout(() => {
|
||||
this._retryTimer = null;
|
||||
this.saveNow();
|
||||
}, delay);
|
||||
}
|
||||
} finally {
|
||||
this._saving = false;
|
||||
if (this._pendingSave || this._dirtyVersion > this._savedVersion) {
|
||||
this._saveDebounced();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cache = null;
|
||||
this._loading = null;
|
||||
}
|
||||
|
||||
getCacheSize() {
|
||||
if (!this.cache) return 0;
|
||||
return Object.keys(this.cache).length;
|
||||
}
|
||||
|
||||
getCacheBytes() {
|
||||
if (!this.cache) return 0;
|
||||
try {
|
||||
return JSON.stringify(this.cache).length * 2;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const TasksStorage = new StorageFile('LittleWhiteBox_Tasks.json');
|
||||
@@ -1,30 +1,30 @@
|
||||
import { getContext } from "../../../../extensions.js";
|
||||
|
||||
/**
|
||||
* 执行 SillyTavern 斜杠命令
|
||||
* @param {string} command - 要执行的命令
|
||||
* @returns {Promise<any>} 命令执行结果
|
||||
*/
|
||||
export async function executeSlashCommand(command) {
|
||||
try {
|
||||
if (!command) return { error: "命令为空" };
|
||||
if (!command.startsWith('/')) command = '/' + command;
|
||||
const { executeSlashCommands, substituteParams } = getContext();
|
||||
if (typeof executeSlashCommands !== 'function') throw new Error("executeSlashCommands 函数不可用");
|
||||
command = substituteParams(command);
|
||||
const result = await executeSlashCommands(command, true);
|
||||
if (result && typeof result === 'object' && result.pipe !== undefined) {
|
||||
const pipeValue = result.pipe;
|
||||
if (typeof pipeValue === 'string') {
|
||||
try { return JSON.parse(pipeValue); } catch { return pipeValue; }
|
||||
}
|
||||
return pipeValue;
|
||||
}
|
||||
if (typeof result === 'string' && result.trim()) {
|
||||
try { return JSON.parse(result); } catch { return result; }
|
||||
}
|
||||
return result === undefined ? "" : result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
import { getContext } from "../../../../extensions.js";
|
||||
|
||||
/**
|
||||
* 执行 SillyTavern 斜杠命令
|
||||
* @param {string} command - 要执行的命令
|
||||
* @returns {Promise<any>} 命令执行结果
|
||||
*/
|
||||
export async function executeSlashCommand(command) {
|
||||
try {
|
||||
if (!command) return { error: "命令为空" };
|
||||
if (!command.startsWith('/')) command = '/' + command;
|
||||
const { executeSlashCommands, substituteParams } = getContext();
|
||||
if (typeof executeSlashCommands !== 'function') throw new Error("executeSlashCommands 函数不可用");
|
||||
command = substituteParams(command);
|
||||
const result = await executeSlashCommands(command, true);
|
||||
if (result && typeof result === 'object' && result.pipe !== undefined) {
|
||||
const pipeValue = result.pipe;
|
||||
if (typeof pipeValue === 'string') {
|
||||
try { return JSON.parse(pipeValue); } catch { return pipeValue; }
|
||||
}
|
||||
return pipeValue;
|
||||
}
|
||||
if (typeof result === 'string' && result.trim()) {
|
||||
try { return JSON.parse(result); } catch { return result; }
|
||||
}
|
||||
return result === undefined ? "" : result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user