Update README and vector assets
This commit is contained in:
227
README.md
227
README.md
@@ -4,115 +4,138 @@
|
||||
|
||||
```
|
||||
LittleWhiteBox/
|
||||
├── index.js # 入口:初始化/注册所有模块
|
||||
├── manifest.json # 插件清单:版本/依赖/入口
|
||||
├── settings.html # 主设置页:模块开关/UI
|
||||
├── style.css # 全局样式
|
||||
├── README.md # 说明文档
|
||||
├── .eslintrc.cjs # ESLint 规则
|
||||
├── .eslintignore # ESLint 忽略
|
||||
├── .gitignore # Git 忽略
|
||||
├── package.json # 开发依赖/脚本
|
||||
├── package-lock.json # 依赖锁定
|
||||
├── jsconfig.json # 编辑器提示
|
||||
├── .editorconfig # 编辑器格式规范
|
||||
├── .eslintignore # ESLint 忽略规则
|
||||
├── .eslintrc.cjs # ESLint 配置
|
||||
├── .gitignore # Git 忽略规则
|
||||
├── index.js # 插件入口:初始化/注册所有模块
|
||||
├── jsconfig.json # JS/TS 编辑器提示
|
||||
├── manifest.json # 插件清单:版本/依赖/入口
|
||||
├── package-lock.json # 依赖锁定
|
||||
├── package.json # 开发依赖/脚本
|
||||
├── README.md # 说明文档
|
||||
├── settings.html # 主设置页:模块开关/UI
|
||||
├── style.css # 全局样式
|
||||
│
|
||||
├── core/ # 核心基础设施(不直接做功能UI)
|
||||
│ ├── constants.js # 常量/路径
|
||||
│ ├── event-manager.js # 统一事件管理
|
||||
│ ├── debug-core.js # 日志/缓存注册
|
||||
│ ├── slash-command.js # 斜杠命令封装
|
||||
│ ├── variable-path.js # 变量路径解析
|
||||
│ ├── server-storage.js # 服务器存储(防抖/重试)
|
||||
│ ├── wrapper-inline.js # iframe 内联脚本
|
||||
│ └── iframe-messaging.js # postMessage 封装与 origin 校验
|
||||
├── bridges/ # 外部桥接
|
||||
│ ├── call-generate-service.js # 调用生成服务桥接
|
||||
│ ├── worldbook-bridge.js # 世界书桥接
|
||||
│ └── wrapper-iframe.js # iframe 包装桥接
|
||||
│
|
||||
├── widgets/ # 通用UI组件(跨功能复用)
|
||||
│ ├── message-toolbar.js # 消息区工具条注册/管理
|
||||
│ └── button-collapse.js # 消息区按钮收纳
|
||||
├── core/ # 核心基础设施
|
||||
│ ├── constants.js # 常量/路径定义
|
||||
│ ├── debug-core.js # 日志/缓存注册
|
||||
│ ├── event-manager.js # 统一事件管理
|
||||
│ ├── iframe-messaging.js # postMessage 封装
|
||||
│ ├── server-storage.js # 服务器存储封装
|
||||
│ ├── slash-command.js # 斜杠命令封装
|
||||
│ ├── variable-path.js # 变量路径解析
|
||||
│ └── wrapper-inline.js # iframe 内联脚本
|
||||
│
|
||||
├── modules/ # 功能模块(每个功能自带UI)
|
||||
│ ├── control-audio.js # 音频权限控制
|
||||
│ ├── iframe-renderer.js # iframe 渲染
|
||||
│ ├── immersive-mode.js # 沉浸模式
|
||||
│ ├── message-preview.js # 消息预览/拦截
|
||||
│ ├── streaming-generation.js # 生成相关功能(xbgenraw)
|
||||
│ │
|
||||
│ ├── debug-panel/ # 调试面板
|
||||
│ │ ├── debug-panel.js # 悬浮窗控制
|
||||
│ │ └── debug-panel.html # UI
|
||||
│ │
|
||||
│ ├── fourth-wall/ # 四次元壁
|
||||
│ │ ├── fourth-wall.js # 逻辑
|
||||
│ │ ├── fourth-wall.html # UI
|
||||
│ │ ├── fw-image.js # 图像交互
|
||||
│ │ ├── fw-message-enhancer.js # 消息增强
|
||||
│ │ ├── fw-prompt.js # 提示词编辑
|
||||
│ │ └── fw-voice.js # 语音展示
|
||||
│ │
|
||||
│ ├── novel-draw/ # 画图
|
||||
│ │ ├── novel-draw.js # 主逻辑
|
||||
│ │ ├── novel-draw.html # UI
|
||||
│ │ ├── llm-service.js # LLM 分析
|
||||
│ │ ├── floating-panel.js # 悬浮面板
|
||||
│ │ ├── gallery-cache.js # 缓存
|
||||
│ │ ├── image-live-effect.js # Live 动效
|
||||
│ │ ├── cloud-presets.js # 云预设
|
||||
│ │ └── TAG编写指南.md # 文档
|
||||
│ │
|
||||
│ ├── tts/ # TTS
|
||||
│ │ ├── tts.js # 主逻辑
|
||||
│ │ ├── tts-auth-provider.js # 鉴权
|
||||
│ │ ├── tts-free-provider.js # 试用
|
||||
│ │ ├── tts-api.js # API
|
||||
│ │ ├── tts-text.js # 文本处理
|
||||
│ │ ├── tts-player.js # 播放器
|
||||
│ │ ├── tts-panel.js # 气泡UI
|
||||
│ │ ├── tts-cache.js # 缓存
|
||||
│ │ ├── tts-overlay.html # 设置UI
|
||||
│ │ ├── tts-voices.js # 音色数据
|
||||
│ │ ├── 开通管理.png # 说明图
|
||||
│ │ ├── 获取ID和KEY.png # 说明图
|
||||
│ │ └── 声音复刻.png # 说明图
|
||||
│ │
|
||||
│ ├── scheduled-tasks/ # 定时任务
|
||||
│ │ ├── scheduled-tasks.js # 调度
|
||||
│ │ ├── scheduled-tasks.html # UI
|
||||
│ │ └── embedded-tasks.html # 嵌入UI
|
||||
│ │
|
||||
│ ├── template-editor/ # 模板编辑器
|
||||
│ │ ├── template-editor.js # 逻辑
|
||||
│ │ └── template-editor.html # UI
|
||||
│ │
|
||||
│ ├── story-outline/ # 故事大纲
|
||||
│ │ ├── story-outline.js # 逻辑
|
||||
│ │ ├── story-outline.html # UI
|
||||
│ │ └── story-outline-prompt.js # 提示词
|
||||
│ │
|
||||
│ ├── story-summary/ # 剧情总结
|
||||
│ │ ├── story-summary.js # 逻辑
|
||||
│ │ ├── story-summary.html # UI
|
||||
│ │ └── llm-service.js # LLM 服务
|
||||
│ │
|
||||
│ └── variables/ # 变量系统
|
||||
│ ├── var-commands.js # 命令
|
||||
│ ├── varevent-editor.js # 编辑器
|
||||
│ ├── variables-core.js # 核心
|
||||
│ └── variables-panel.js # 面板
|
||||
├── docs/ # 文档与许可
|
||||
│ ├── COPYRIGHT # 版权声明
|
||||
│ ├── LICENSE.md # 许可协议
|
||||
│ └── NOTICE # 通知/第三方声明
|
||||
│
|
||||
├── bridges/ # 外部服务桥接
|
||||
│ ├── call-generate-service.js # ST 生成服务
|
||||
│ ├── worldbook-bridge.js # 世界书桥接
|
||||
│ └── wrapper-iframe.js # iframe 客户端脚本
|
||||
├── libs/ # 第三方库
|
||||
│ ├── dexie.mjs # IndexedDB 封装库
|
||||
│ ├── js-yaml.mjs # YAML 解析/序列化(ESM)
|
||||
│ ├── minisearch.mjs # 轻量搜索库
|
||||
│ ├── pixi.min.js # PixiJS 渲染库
|
||||
│ └── jieba-wasm/
|
||||
│ ├── jieba_rs_wasm.js # 结巴分词 WASM JS 包装
|
||||
│ ├── jieba_rs_wasm_bg.wasm # 结巴分词 WASM 二进制
|
||||
│ └── jieba_rs_wasm_bg.wasm.d.ts # WASM 类型声明
|
||||
│
|
||||
├── libs/ # 第三方库
|
||||
│ └── pixi.min.js # PixiJS
|
||||
├── modules/ # 功能模块
|
||||
│ ├── control-audio.js # 音频权限控制
|
||||
│ ├── iframe-renderer.js # iframe 渲染
|
||||
│ ├── immersive-mode.js # 沉浸模式
|
||||
│ ├── message-preview.js # 消息预览/拦截
|
||||
│ ├── streaming-generation.js # 生成相关功能
|
||||
│ │
|
||||
│ ├── debug-panel/ # 调试面板
|
||||
│ │ ├── debug-panel.html # 调试面板 UI
|
||||
│ │ └── debug-panel.js # 调试面板逻辑
|
||||
│ │
|
||||
│ ├── fourth-wall/ # 四次元壁
|
||||
│ │ ├── fourth-wall.html # UI
|
||||
│ │ ├── fourth-wall.js # 主逻辑
|
||||
│ │ ├── fw-image.js # 图像相关增强
|
||||
│ │ ├── fw-message-enhancer.js # 消息增强
|
||||
│ │ ├── fw-prompt.js # 提示词/注入
|
||||
│ │ └── fw-voice.js # 语音相关
|
||||
│ │
|
||||
│ ├── novel-draw/ # 画图模块
|
||||
│ │ ├── cloud-presets.js # 云端预设
|
||||
│ │ ├── floating-panel.js # 浮动面板
|
||||
│ │ ├── gallery-cache.js # 图库缓存
|
||||
│ │ ├── image-live-effect.js # 图像动态效果
|
||||
│ │ ├── llm-service.js # LLM 服务调用
|
||||
│ │ ├── novel-draw.html # UI
|
||||
│ │ ├── novel-draw.js # 主逻辑
|
||||
│ │ └── TAG编写指南.md # TAG 编写指南
|
||||
│ │
|
||||
│ ├── scheduled-tasks/ # 定时任务
|
||||
│ │ ├── embedded-tasks.html # 内嵌任务 UI
|
||||
│ │ ├── scheduled-tasks.html # 主 UI
|
||||
│ │ └── scheduled-tasks.js # 逻辑
|
||||
│ │
|
||||
│ ├── story-outline/ # 故事大纲
|
||||
│ │ ├── story-outline-prompt.js # Prompt 模板
|
||||
│ │ ├── story-outline.html # UI
|
||||
│ │ └── story-outline.js # 逻辑
|
||||
│ │
|
||||
│ ├── story-summary/ # 剧情总结 + 记忆系统
|
||||
│ │ ├── story-summary-ui.js # UI 逻辑
|
||||
│ │ ├── story-summary.css # 样式
|
||||
│ │ ├── story-summary.html # UI(含向量设置)
|
||||
│ │ ├── story-summary.js # 主入口:事件/UI/iframe 通讯
|
||||
│ │ ├── data/
|
||||
│ │ │ ├── config.js # 配置管理
|
||||
│ │ │ ├── db.js # 向量存储:L1/L2 Vectors (Dexie/IndexedDB)
|
||||
│ │ │ └── store.js # 核心存储:L2事件 + L3世界状态
|
||||
│ │ ├── generate/
|
||||
│ │ │ ├── generator.js # 调度器:调用 LLM -> 解析 -> 清洗 -> 合并
|
||||
│ │ │ ├── llm.js # LLM API 与 Prompt 定义
|
||||
│ │ │ └── prompt.js # 注入层:格式化 + 预算装配
|
||||
│ │ └── vector/
|
||||
│ │ ├── chunk-builder.js # L1 切分与构建
|
||||
│ │ ├── chunk-store.js # 向量 CRUD 操作
|
||||
│ │ ├── embedder.js # 向量化服务 (Local/Online)
|
||||
│ │ ├── embedder.worker.js # 本地模型 Worker
|
||||
│ │ ├── entity.js # 召回实体/辅助结构
|
||||
│ │ └── recall.js # 召回引擎:加权Query + 实体加分 + MMR去重
|
||||
│ │
|
||||
│ ├── template-editor/ # 模板编辑器
|
||||
│ │ ├── template-editor.html # UI
|
||||
│ │ └── template-editor.js # 逻辑
|
||||
│ │
|
||||
│ ├── tts/ # TTS
|
||||
│ │ ├── tts-api.js # API 适配
|
||||
│ │ ├── tts-auth-provider.js # 鉴权提供者
|
||||
│ │ ├── tts-cache.js # 缓存
|
||||
│ │ ├── tts-free-provider.js # 免费提供者
|
||||
│ │ ├── tts-overlay.html # Overlay UI
|
||||
│ │ ├── tts-panel.js # 面板逻辑
|
||||
│ │ ├── tts-player.js # 播放器
|
||||
│ │ ├── tts-text.js # 文本处理
|
||||
│ │ ├── tts-voices.js # 语音配置
|
||||
│ │ ├── tts.js # 主入口
|
||||
│ │ ├── 声音复刻.png # 说明图
|
||||
│ │ ├── 开通管理.png # 说明图
|
||||
│ │ └── 获取ID和KEY.png # 说明图
|
||||
│ │
|
||||
│ └── variables/ # 变量系统
|
||||
│ ├── var-commands.js # 变量命令/宏/路径解析
|
||||
│ ├── varevent-editor.js # 变量编辑器/注入处理
|
||||
│ ├── variables-core.js # 变量系统核心
|
||||
│ └── variables-panel.js # 变量面板 UI
|
||||
│
|
||||
└── docs/ # 许可/声明
|
||||
├── COPYRIGHT
|
||||
├── LICENSE.md
|
||||
└── NOTICE
|
||||
└── widgets/ # 通用 UI 组件
|
||||
├── button-collapse.js # 按钮收纳
|
||||
└── message-toolbar.js # 消息工具条
|
||||
|
||||
node_modules/ # 本地依赖(不提交)
|
||||
```
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
@@ -178,12 +178,11 @@ h1 span {
|
||||
}
|
||||
|
||||
#keep-visible-count {
|
||||
width: 3ch; /* 可稳定显示 3 位数字:0-50 足够 */
|
||||
min-width: 3ch;
|
||||
max-width: 4ch;
|
||||
font-variant-numeric: tabular-nums;
|
||||
padding: 2px 4px;
|
||||
margin: 0 2px;
|
||||
width: 3.5em;
|
||||
min-width: 3em;
|
||||
max-width: 4em;
|
||||
padding: 4px 6px;
|
||||
margin: 0 4px;
|
||||
background: var(--bg2);
|
||||
border: 1px solid var(--bdr);
|
||||
font-size: inherit;
|
||||
@@ -191,6 +190,17 @@ h1 span {
|
||||
color: var(--hl);
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
/* 禁用 number input 的 spinner(PC 上会挤掉数字) */
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
#keep-visible-count::-webkit-outer-spin-button,
|
||||
#keep-visible-count::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#keep-visible-count:focus {
|
||||
|
||||
@@ -464,18 +464,35 @@ export async function fetchOnlineModels(config) {
|
||||
/**
|
||||
* 使用在线服务生成向量
|
||||
*/
|
||||
async function embedOnline(texts, provider, config) {
|
||||
async function embedOnline(texts, provider, config, options = {}) {
|
||||
const { url, key, model } = config;
|
||||
const signal = options?.signal;
|
||||
|
||||
const providerConfig = ONLINE_PROVIDERS[provider];
|
||||
const baseUrl = (providerConfig?.baseUrl || url || '').replace(/\/+$/, '');
|
||||
|
||||
const reqId = Math.random().toString(36).slice(2, 6);
|
||||
const maxRetries = 3;
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
// 永远重试:指数退避 + 上限 + 抖动
|
||||
const BASE_WAIT_MS = 1200;
|
||||
const MAX_WAIT_MS = 15000;
|
||||
|
||||
const sleepAbortable = (ms) => new Promise((resolve, reject) => {
|
||||
if (signal?.aborted) return reject(new DOMException('Aborted', 'AbortError'));
|
||||
const t = setTimeout(resolve, ms);
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => {
|
||||
clearTimeout(t);
|
||||
reject(new DOMException('Aborted', 'AbortError'));
|
||||
}, { once: true });
|
||||
}
|
||||
});
|
||||
|
||||
let attempt = 0;
|
||||
while (true) {
|
||||
attempt++;
|
||||
const startTime = Date.now();
|
||||
console.log(`[embed ${reqId}] send ${texts.length} items${attempt > 1 ? ` (retry ${attempt}/${maxRetries})` : ''}`);
|
||||
console.log(`[embed ${reqId}] send ${texts.length} items (attempt ${attempt})`);
|
||||
|
||||
try {
|
||||
let response;
|
||||
@@ -492,6 +509,7 @@ async function embedOnline(texts, provider, config) {
|
||||
texts: texts,
|
||||
input_type: 'search_document',
|
||||
}),
|
||||
signal,
|
||||
});
|
||||
} else {
|
||||
response = await fetch(`${baseUrl}/v1/embeddings`, {
|
||||
@@ -504,40 +522,55 @@ async function embedOnline(texts, provider, config) {
|
||||
model: model,
|
||||
input: texts,
|
||||
}),
|
||||
signal,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`[embed ${reqId}] status=${response.status} time=${Date.now() - startTime}ms`);
|
||||
|
||||
// 需要“永远重试”的典型状态:
|
||||
// - 429:限流
|
||||
// - 403:配额/风控/未实名等(你提到的硅基未认证)
|
||||
// - 5xx:服务端错误
|
||||
const retryableStatus = (s) => s === 429 || s === 403 || (s >= 500 && s <= 599);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`API 返回 ${response.status}: ${error}`);
|
||||
const errorText = await response.text().catch(() => '');
|
||||
|
||||
if (retryableStatus(response.status)) {
|
||||
const exp = Math.min(MAX_WAIT_MS, BASE_WAIT_MS * Math.pow(2, Math.min(attempt, 6) - 1));
|
||||
const jitter = Math.floor(Math.random() * 350);
|
||||
const waitMs = exp + jitter;
|
||||
console.warn(`[embed ${reqId}] retryable error ${response.status}, wait ${waitMs}ms`);
|
||||
await sleepAbortable(waitMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 非可恢复错误:直接抛出(比如 400 参数错、401 key 错等)
|
||||
const err = new Error(`API 返回 ${response.status}: ${errorText}`);
|
||||
err.status = response.status;
|
||||
throw err;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (provider === 'cohere') {
|
||||
console.log(`[embed ${reqId}] done items=${data.embeddings?.length || 0} total=${Date.now() - startTime}ms`);
|
||||
return data.embeddings.map(e => Array.isArray(e) ? e : Array.from(e));
|
||||
return (data.embeddings || []).map(e => Array.isArray(e) ? e : Array.from(e));
|
||||
}
|
||||
|
||||
console.log(`[embed ${reqId}] done items=${data.data?.length || 0} total=${Date.now() - startTime}ms`);
|
||||
return data.data.map(item => {
|
||||
return (data.data || []).map(item => {
|
||||
const embedding = item.embedding;
|
||||
return Array.isArray(embedding) ? embedding : Array.from(embedding);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[embed ${reqId}] failed attempt=${attempt} time=${Date.now() - startTime}ms`, e.message);
|
||||
// 取消:必须立刻退出
|
||||
if (e?.name === 'AbortError') throw e;
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
const waitTime = Math.pow(2, attempt - 1) * 1000;
|
||||
console.log(`[embed ${reqId}] wait ${waitTime}ms then retry`);
|
||||
await new Promise(r => setTimeout(r, waitTime));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.error(`[embed ${reqId}] final failure`, e);
|
||||
throw e;
|
||||
// 网络错误:永远重试
|
||||
const exp = Math.min(MAX_WAIT_MS, BASE_WAIT_MS * Math.pow(2, Math.min(attempt, 6) - 1));
|
||||
const jitter = Math.floor(Math.random() * 350);
|
||||
const waitMs = exp + jitter;
|
||||
console.warn(`[embed ${reqId}] network/error, wait ${waitMs}ms then retry: ${e?.message || e}`);
|
||||
await sleepAbortable(waitMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,7 +586,7 @@ async function embedOnline(texts, provider, config) {
|
||||
* @param {Object} config - 配置
|
||||
* @returns {Promise<number[][]>}
|
||||
*/
|
||||
export async function embed(texts, config) {
|
||||
export async function embed(texts, config, options = {}) {
|
||||
if (!texts?.length) return [];
|
||||
|
||||
const { engine, local, online } = config;
|
||||
@@ -567,7 +600,7 @@ export async function embed(texts, config) {
|
||||
if (!online?.key || !online?.model) {
|
||||
throw new Error('在线服务配置不完整');
|
||||
}
|
||||
return await embedOnline(texts, provider, online);
|
||||
return await embedOnline(texts, provider, online, options);
|
||||
|
||||
} else {
|
||||
throw new Error(`未知的引擎类型: ${engine}`);
|
||||
|
||||
Reference in New Issue
Block a user