Files
metona-ollama-desktop/js/state.js
T
thzxx 38edaee0f0 refactor: 模块化架构重构
将单文件巨型 IIFE (1608行) 拆分为 14 个 ES Module:

核心层:
- js/state.js          - 轻量级响应式状态管理 (发布-订阅)
- js/utils.js          - 通用工具函数
- js/sanitizer.js      - HTML XSS 净化器
- js/marked-config.js  - Markdown 渲染器配置

组件层:
- js/components/chat-area.js      - 消息渲染/流式更新/导出
- js/components/input-area.js     - 文本输入/图片上传/发送
- js/components/history-modal.js  - 历史记录面板/搜索/分页
- js/components/settings-modal.js - 设置面板/数据管理
- js/components/header.js         - 顶部导航/连接状态
- js/components/model-bar.js      - 模型选择栏
- js/components/toast.js          - Toast 通知
- js/components/lightbox.js       - 图片预览

入口层:
- js/app.js            - 主入口/初始化协调

index.html: 1608行 → 204行纯模板
2026-04-03 11:34:24 +08:00

78 lines
2.1 KiB
JavaScript

/**
* State - 轻量级响应式状态管理
* 使用发布-订阅模式,状态变更时通知所有订阅者
*/
class AppState {
constructor() {
this._state = {};
this._listeners = new Map(); // key → Set<callback>
}
/**
* 设置状态值并通知订阅者
* @param {string} key - 状态键名
* @param {*} value - 状态值
*/
set(key, value) {
const old = this._state[key];
this._state[key] = value;
if (old !== value && this._listeners.has(key)) {
for (const cb of this._listeners.get(key)) {
try { cb(value, old); } catch (e) { console.error(`[State] 监听器错误 (${key}):`, e); }
}
}
}
/**
* 获取状态值
* @param {string} key - 状态键名
* @param {*} [defaultValue] - 默认值
* @returns {*}
*/
get(key, defaultValue = undefined) {
return key in this._state ? this._state[key] : defaultValue;
}
/**
* 订阅状态变更
* @param {string} key - 状态键名
* @param {function} callback - 回调函数 (newValue, oldValue) => void
* @returns {function} 取消订阅函数
*/
on(key, callback) {
if (!this._listeners.has(key)) {
this._listeners.set(key, new Set());
}
this._listeners.get(key).add(callback);
return () => this._listeners.get(key)?.delete(callback);
}
/**
* 批量设置初始状态
* @param {Object} initial - 初始状态对象
*/
init(initial) {
Object.assign(this._state, initial);
}
}
// 单例
export const state = new AppState();
// ── 状态键名常量 ──
export const KEYS = {
DB: 'db',
API: 'api',
CURRENT_SESSION: 'currentSession',
IS_STREAMING: 'isStreaming',
IS_HISTORY_VIEW: 'isHistoryView',
PENDING_IMAGES: 'pendingImages',
ABORT_CONTROLLER: 'abortController',
SYSTEM_PROMPT: 'systemPrompt',
SYSTEM_PROMPT_ENABLED: 'systemPromptEnabled',
NUM_CTX: 'numCtx',
HISTORY_PAGE: 'historyPage',
HISTORY_SEARCH: 'historySearchQuery',
};