diff --git a/BUILD.md b/BUILD.md deleted file mode 100644 index 4d6d9bf..0000000 --- a/BUILD.md +++ /dev/null @@ -1,144 +0,0 @@ -# Windows 安装包构建指南 - -## 环境要求 - -- Ubuntu 24.04 (amd64) -- Node.js v22+ -- Wine 9.0+(用于 electron-builder 交叉编译) -- i386 架构已启用(`dpkg --add-architecture i386`) - -## 构建步骤 - -### 1. 克隆项目 - -```bash -git clone https://gitee.com/thzxx/metona-ollama.git -cd metona-ollama -git checkout metona-ollama-desktop-v1.1 -``` - -### 2. 安装依赖(使用国内镜像) - -```bash -npm config set registry https://registry.npmmirror.com -npm install -``` - -> ⚠️ **不要用默认 npm 源**,国内环境下大概率超时。npmmirror 速度快且稳定。 - -### 3. 构建 Windows 安装包 - -```bash -ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist -``` - -> ⚠️ **必须设置 `ELECTRON_MIRROR`**,否则 electron-builder 会从 GitHub 下载 Electron 二进制(115MB),国内基本连不上。 - -## 遇到的问题与解决方案 - -### 问题 1:apt 源不可用(阿里云镜像超时) - -**现象**:`apt-get update` 报错 `mirrors.cloud.aliyuncs.com` 连接超时。 - -**解决**:替换为 Ubuntu 官方源: - -```bash -sed -i 's|http://mirrors.cloud.aliyuncs.com/ubuntu|http://archive.ubuntu.com/ubuntu|g' /etc/apt/sources.list -``` - -### 问题 2:npm install 超时 - -**现象**:使用默认 npm registry 下载依赖超时。 - -**解决**:切换到 npmmirror 国内镜像: - -```bash -npm config set registry https://registry.npmmirror.com -``` - -### 问题 3:Electron 下载超时(GitHub 连不上) - -**现象**:electron-builder 打包时卡在 `downloading electron-v33.4.11-win32-x64.zip`,115MB 从 GitHub 下载,国内超时。 - -**解决**:设置环境变量使用 npmmirror 的 Electron 镜像: - -```bash -ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist -``` - -设置后 4.22 秒下载完成,vs 之前直接超时。 - -### 问题 4:winCodeSign 下载慢 - -**现象**:electron-builder 需要从 GitHub 下载 `winCodeSign-2.6.0`(5.6MB)。 - -**说明**:这个文件较小,即使从 GitHub 下载通常也能完成(可能需要几分钟)。如果超时,可以手动下载放到缓存目录: - -```bash -# electron-builder 缓存目录 -~/.cache/electron-builder/winCodeSign/winCodeSign-2.6.0/ -``` - -## 常用命令 - -```bash -# 开发运行 -npm start - -# 仅构建目录(不打包,用于测试) -npm run pack - -# 构建 NSIS 安装包 -npm run dist:nsis - -# 构建绿色便携版 -npm run dist:portable - -# 构建全部(NSIS + Portable) -npm run dist -``` - -## 构建产物 - -| 文件 | 说明 | -|------|------| -| `release/Metona Ollama Setup X.X.X.exe` | NSIS 安装包(可选目录、创建快捷方式) | -| `release/MetonaOllama-Portable-X.X.X.exe` | 绿色便携版(免安装,双击即用) | - -## 发布到 Gitee - -### 上传附件 - -```bash -TOKEN="your_access_token" -RELEASE_ID="600168" # v1.1.0 release ID - -curl -X POST \ - "https://gitee.com/api/v5/repos/thzxx/metona-ollama/releases/$RELEASE_ID/attach_files?access_token=$TOKEN" \ - -H "Content-Type: multipart/form-data" \ - -F "file=@release/Metona Ollama Setup 1.1.0.exe" - -curl -X POST \ - "https://gitee.com/api/v5/repos/thzxx/metona-ollama/releases/$RELEASE_ID/attach_files?access_token=$TOKEN" \ - -H "Content-Type: multipart/form-data" \ - -F "file=@release/MetonaOllama-Portable-1.1.0.exe" -``` - -### 清理重复附件 - -```bash -# 查看附件列表 -curl -s "https://gitee.com/api/v5/repos/thzxx/metona-ollama/releases/$RELEASE_ID/attach_files?access_token=$TOKEN" - -# 删除指定附件 -curl -X DELETE "https://gitee.com/api/v5/repos/thzxx/metona-ollama/releases/$RELEASE_ID/attach_files/$ASSET_ID?access_token=$TOKEN" -``` - -## 提速清单 - -| 环节 | 默认方案 | 加速方案 | 效果 | -|------|----------|----------|------| -| apt 源 | archive.ubuntu.com | 保持官方源即可 | 稳定可用 | -| npm 依赖 | registry.npmjs.org | registry.npmmirror.com | 59s 完成 | -| Electron 下载 | github.com | npmmirror.com/mirrors/electron | 4s 完成(vs 超时) | -| winCodeSign | github.com | 小文件可等待 / 手动缓存 | 通常可完成 | diff --git a/DESKTOP_README.md b/DESKTOP_README.md deleted file mode 100644 index 57fccb9..0000000 --- a/DESKTOP_README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Metona Ollama Desktop v1.0 - -基于 [Metona Ollama](https://gitee.com/thzxx/metona-ollama) Web 版封装的 **Electron Windows 桌面客户端**。 - -## ✨ 桌面版特性 - -| 特性 | 说明 | -|------|------| -| 原生安装包 | NSIS 安装程序 + 绿色便携版 | -| 系统托盘 | 最小化到托盘,双击恢复,托盘右键菜单 | -| 原生菜单 | 完整的文件/编辑/视图/窗口/帮助菜单 | -| 原生文件对话框 | Windows 文件选择器替代浏览器 input | -| 系统通知 | 通过 Windows 原生通知中心推送 | -| 窗口管理 | 记忆窗口大小和位置,支持置顶 | -| 单实例锁 | 防止重复启动 | -| 无 CORS 限制 | 关闭 webSecurity,直接连接本地 Ollama | - -## 🚀 开发 - -### 前置条件 - -- Node.js 18+ -- npm - -### 启动开发环境 - -```bash -# 安装依赖 -npm install - -# 启动开发模式 -npm start -``` - -### 构建 - -```bash -# 构建目录(不打包,用于测试) -npm run pack - -# 构建 Windows 安装包 (NSIS + Portable) -npm run dist - -# 仅构建 NSIS 安装包 -npm run dist:nsis - -# 仅构建便携版 -npm run dist:portable -``` - -构建产物在 `release/` 目录。 - -## 📁 Desktop 项目结构 - -``` -metona-ollama/ -├── electron/ -│ ├── main.js # 主进程:窗口、菜单、托盘、IPC -│ ├── preload.js # 预加载:contextBridge 安全 API 暴露 -│ ├── desktop-bridge.js # 渲染进程桥接:统一 API + 降级处理 -│ └── desktop-integration.js # 桌面集成:菜单响应、原生文件选择、快捷键 -├── index.html # 入口(已集成桌面脚本) -├── js/ # 原有 Web 应用代码(未修改) -├── css/ # 原有样式(未修改) -├── assets/ # 图标资源 -├── package.json # Electron + electron-builder 配置 -└── ... -``` - -## 🔌 架构设计 - -``` -┌──────────────────────────────────────────────┐ -│ Main Process │ -│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ -│ │ Browser │ │ Tray │ │ Menu │ │ -│ │ Window │ │ │ │ │ │ -│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │ -│ │ │ │ │ -│ ┌────┴─────────────┴──────────────┴───────┐ │ -│ │ IPC Main │ │ -│ └────────────────┬────────────────────────┘ │ -└───────────────────┼──────────────────────────┘ - │ contextBridge -┌───────────────────┼──────────────────────────┐ -│ Renderer │ │ -│ ┌────────────────┴───────────────────────┐ │ -│ │ window.metonaDesktop (preload.js) │ │ -│ └────────────────┬───────────────────────┘ │ -│ ┌────────────────┴───────────────────────┐ │ -│ │ window.__metonaBridge (bridge.js) │ │ -│ │ 统一接口 + 降级 (Web环境可用) │ │ -│ └────────────────┬───────────────────────┘ │ -│ ┌────────────────┴───────────────────────┐ │ -│ │ desktop-integration.js │ │ -│ │ 菜单响应 · 原生文件 · 快捷键 │ │ -│ └────────────────┬───────────────────────┘ │ -│ ┌────────────────┴───────────────────────┐ │ -│ │ 原有 Web 应用 (app.js + components) │ │ -│ │ 零修改,完全复用 │ │ -│ └────────────────────────────────────────┘ │ -└──────────────────────────────────────────────┘ -``` - -## 🔒 安全策略 - -- `contextIsolation: true` — 渲染进程无 Node.js 访问 -- `nodeIntegration: false` — 通过 preload 安全暴露 API -- `sandbox: false` — 允许文件系统访问(受 IPC 白名单控制) -- `webSecurity: false` — 仅用于连接本地 Ollama API - -## 📝 与 Web 版的兼容性 - -桌面版完全兼容 Web 版代码,通过降级处理实现: - -- `window.__metonaBridge.isDesktop` 判断当前环境 -- Web 环境下所有 desktop API 自动降级为浏览器原生行为 -- `index.html` 无条件加载桌面脚本,非 Electron 环境静默跳过 -- 原有 JS/HTML/CSS 代码**零修改** diff --git a/css/style.css b/css/style.css deleted file mode 100644 index 9198df5..0000000 --- a/css/style.css +++ /dev/null @@ -1,2183 +0,0 @@ -/* ═══════════════════════════════════════════════════════════════ - Metona Ollama — Windows 11 Fluent Design 暗色主题 - Mica 材质 · Segoe UI Variable · WinUI 3 控件风格 - ═══════════════════════════════════════════════════════════════ */ - -/* ── CSS 变量 ── */ -:root { - /* Windows 11 暗色调色板 */ - --bg-solid: #202020; - --bg-card: #2d2d2d; - --bg-card-hover: #323232; - --bg-layer: #383838; - --bg-layer-alt: #2a2a2a; - --bg-smoke: rgba(0, 0, 0, 0.3); - --bg-mica: rgba(32, 32, 32, 0.72); - --bg-acrylic: rgba(32, 32, 32, 0.8); - - --border-subtle: rgba(255, 255, 255, 0.06); - --border-default: rgba(255, 255, 255, 0.08); - --border-strong: rgba(255, 255, 255, 0.12); - --border-focus: #60CDFF; - - --text-primary: #FFFFFF; - --text-secondary: #999999; - --text-tertiary: #666666; - --text-disabled: #505050; - - --accent: #60CDFF; - --accent-hover: #7AD8FF; - --accent-subtle: rgba(96, 205, 255, 0.06); - --accent-subtle-hover: rgba(96, 205, 255, 0.1); - - --critical: #F1707B; - --critical-bg: rgba(241, 112, 123, 0.06); - --success: #6CCB5F; - --success-bg: rgba(108, 203, 95, 0.06); - --caution: #FCE100; - --caution-bg: rgba(252, 225, 0, 0.06); - - /* 圆角 — Windows 11 标准 */ - --radius-control: 4px; - --radius-sm: 8px; - --radius-md: 8px; - --radius-lg: 12px; - --radius-xl: 16px; - - /* 阴影 — Fluent Design 深度 */ - --shadow-flyout: 0 8px 16px rgba(0, 0, 0, 0.14); - --shadow-dialog: 0 24px 48px rgba(0, 0, 0, 0.18); - --shadow-tooltip: 0 4px 8px rgba(0, 0, 0, 0.14); - - /* 字体 */ - --font: 'Segoe UI Variable', 'Segoe UI', system-ui, -apple-system, sans-serif; - --font-mono: 'Cascadia Mono', 'Cascadia Code', 'Consolas', monospace; - - --transition: 167ms cubic-bezier(0, 0, 0, 1); - --transition-fast: 83ms cubic-bezier(0, 0, 0, 1); -} - -/* ── 全局重置 ── */ -*, *::before, *::after { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body { - height: 100%; - overflow: hidden; - background: var(--bg-solid); - color: var(--text-primary); - font-family: var(--font); - font-size: 14px; - line-height: 1.5; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -#app { - display: flex; - flex-direction: column; - height: 100vh; -} - -/* ═══ 顶部导航 ═══ */ -.app-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 16px; - background: var(--bg-mica); - backdrop-filter: saturate(180%) blur(20px); - -webkit-backdrop-filter: saturate(180%) blur(20px); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; - z-index: 10; -} - -.header-left { - display: flex; - align-items: center; - gap: 10px; - min-width: 0; - flex: 1; -} - -.logo { - font-size: 20px; - flex-shrink: 0; -} - -.app-title { - font-size: 14px; - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; -} - -.app-version { - font-size: 10px; - font-weight: 600; - color: var(--text-secondary); - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-radius: 12px; - padding: 1px 8px; - white-space: nowrap; - user-select: none; -} - -.header-right { - display: flex; - align-items: center; - gap: 4px; -} - -/* ── 连接状态 ── */ -.conn-status { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border-radius: var(--radius-control); - font-size: 12px; - color: var(--text-secondary); -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - transition: var(--transition); -} - -.status-label { - white-space: nowrap; -} - -.conn-status.connected .status-dot { - background: var(--success); - box-shadow: 0 0 4px var(--success); -} - -.conn-status.disconnected .status-dot { - background: var(--critical); -} - -.conn-status.pending .status-dot { - background: var(--caution); - animation: blink 1s ease-in-out infinite; -} - -@keyframes blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.4; } -} - -/* ── 图标按钮 (WinUI 3 风格) ── */ -.icon-btn { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - border: none; - background: transparent; - color: var(--text-secondary); - border-radius: var(--radius-control); - cursor: pointer; - transition: var(--transition); - position: relative; -} - -.icon-btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: var(--radius-control); - background: currentColor; - opacity: 0; - transition: var(--transition); -} - -.icon-btn:hover::before { - opacity: 0.06; -} - -.icon-btn:active::before { - opacity: 0.04; -} - -.icon-btn:hover { - color: var(--text-primary); -} - -.icon-btn:active { - transform: scale(0.97); -} - -.icon-btn svg { - width: 18px; - height: 18px; - position: relative; - z-index: 1; -} - -.icon-btn.disabled, -.icon-btn:disabled { - opacity: 0.36; - cursor: not-allowed; -} - -/* 图片按钮禁用时保留 pointer-events 以显示 tooltip 和 cursor */ -#btnAttachImg.disabled { - pointer-events: auto; -} - -#btnAttachImg.disabled:hover { - opacity: 0.3; - background: rgba(241, 112, 123, 0.08); -} - -.icon-btn.sm { - width: 28px; - height: 28px; -} - -.icon-btn.sm svg { - width: 14px; - height: 14px; -} - -.icon-btn.danger:hover { - color: var(--critical); -} - -.help-btn { - margin-left: 2px; - opacity: 0.5; -} - -.help-btn:hover { - opacity: 1; -} - -/* ═══ 模型选择栏 ═══ */ -.model-bar { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 16px; - background: var(--bg-layer-alt); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.model-bar-icon { - width: 28px; - height: 28px; - border-radius: var(--radius-control); - background: var(--accent-subtle); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.model-bar-icon svg { - width: 14px; - height: 14px; - color: var(--accent); -} - -.model-select-wrap { - position: relative; - flex: 1; - min-width: 0; - display: flex; - align-items: center; -} - -.model-select { - width: 100%; - padding: 6px 32px 6px 12px; - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-radius: var(--radius-control); - color: var(--text-primary); - font-size: 13px; - font-family: var(--font); - outline: none; - cursor: pointer; - transition: var(--transition); - appearance: none; - -webkit-appearance: none; -} - -.model-select:focus { - border-color: var(--border-focus); - box-shadow: 0 0 0 1px var(--border-focus); -} - -.model-select:hover { - border-color: var(--border-strong); -} - -.model-select option { - background: var(--bg-card); - color: var(--text-primary); -} - -.model-select-arrow { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - color: var(--text-tertiary); -} - -.model-select-arrow svg { - width: 14px; - height: 14px; -} - -.model-badges { - display: flex; - gap: 6px; - flex-shrink: 0; -} - -.model-badge { - font-size: 11px; - font-weight: 500; - white-space: nowrap; - padding: 3px 8px; - border-radius: var(--radius-control); - border: 1px solid; -} - -.think-badge { - color: var(--accent); - background: var(--accent-subtle); - border-color: rgba(96, 205, 255, 0.15); -} - -.vision-badge { - color: var(--success); - background: var(--success-bg); - border-color: rgba(108, 203, 95, 0.15); -} - -.rag-badge { - color: #B388FF; - background: rgba(179, 136, 255, 0.06); - border-color: rgba(179, 136, 255, 0.15); -} - -/* ── 预设栏 ── */ -.preset-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 16px; - background: var(--bg-layer-alt); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.preset-list { - display: flex; - gap: 6px; - flex: 1; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - -ms-overflow-style: none; - padding: 2px 0; -} - -.preset-list::-webkit-scrollbar { display: none; } - -.preset-chip { - display: flex; - align-items: center; - gap: 5px; - padding: 4px 12px; - border-radius: var(--radius-control); - border: 1px solid var(--border-default); - background: var(--bg-layer); - color: var(--text-secondary); - font-size: 12px; - font-family: var(--font); - white-space: nowrap; - cursor: pointer; - transition: var(--transition); - flex-shrink: 0; - user-select: none; -} - -.preset-chip:hover { - background: var(--bg-card-hover); - border-color: var(--border-strong); - color: var(--text-primary); -} - -.preset-chip.active { - background: var(--accent-subtle); - border-color: var(--accent); - color: var(--accent); - font-weight: 600; -} - -.preset-chip.active:hover { - background: var(--accent-subtle-hover); -} - -.preset-chip-icon { - font-size: 13px; - line-height: 1; -} - -.preset-chip-name { - line-height: 1; -} - -.preset-add-btn { - width: 28px; - height: 28px; - border-radius: var(--radius-control); - border: 1px dashed var(--border-default); - background: transparent; - color: var(--text-tertiary); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: var(--transition); - flex-shrink: 0; -} - -.preset-add-btn:hover { - border-color: var(--accent); - color: var(--accent); - background: var(--accent-subtle); -} - -.preset-add-btn svg { - width: 14px; - height: 14px; -} - -/* ── 预设模态框 ── */ -.preset-name-row { - display: flex; - gap: 8px; - align-items: center; -} - -.preset-icon-input { - width: 52px !important; - text-align: center; - font-size: 16px; - flex-shrink: 0; - padding: 6px 4px !important; -} - -.preset-temp-slider { - width: 100%; - -webkit-appearance: none; - appearance: none; - height: 4px; - border-radius: 2px; - background: var(--bg-layer); - outline: none; - margin: 8px 0; -} - -.preset-temp-slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--accent); - cursor: pointer; - box-shadow: 0 0 0 4px transparent; - transition: box-shadow var(--transition); -} - -.preset-temp-slider::-webkit-slider-thumb:hover { - box-shadow: 0 0 0 3px var(--accent-subtle); -} - -.preset-temp-slider::-moz-range-thumb { - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--accent); - cursor: pointer; - border: none; -} - -.preset-modal-actions { - display: flex; - gap: 8px; - align-items: center; - margin-top: 16px; - padding-top: 12px; - border-top: 1px solid var(--border-subtle); -} - -/* ── Think 按钮 ── */ -.think-toggle { - display: flex; - align-items: center; - cursor: pointer; - flex-shrink: 0; - user-select: none; -} - -.think-toggle input { display: none; } - -.think-btn { - width: 36px; - height: 36px; - border-radius: var(--radius-control); - background: var(--bg-layer); - border: 1px solid var(--border-default); - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); - color: var(--text-tertiary); -} - -.think-btn svg { - width: 18px; - height: 18px; -} - -.think-toggle:not(.unavailable):hover .think-btn { - border-color: var(--border-strong); - color: var(--text-secondary); -} - -.think-toggle input:checked + .think-btn { - background: var(--accent-subtle); - border-color: var(--accent); - color: var(--accent); - box-shadow: 0 0 12px rgba(96, 205, 255, 0.4), 0 0 4px rgba(96, 205, 255, 0.2); -} - -.think-toggle input:checked + .think-btn:hover { - background: var(--accent-subtle-hover); - box-shadow: 0 0 18px rgba(96, 205, 255, 0.55), 0 0 6px rgba(96, 205, 255, 0.3); -} - -.think-toggle.unavailable .think-btn { - opacity: 0.36; - cursor: not-allowed; -} - -.think-toggle.unavailable:hover .think-btn { - opacity: 0.3; - background: rgba(241, 112, 123, 0.08); -} - -/* ── 开关控件 (WinUI ToggleSwitch) ── */ -.toggle-label { - display: flex; - align-items: center; - gap: 8px; - font-size: 12px; - color: var(--text-secondary); - cursor: pointer; - user-select: none; - white-space: nowrap; -} - -.toggle-label input { display: none; } - -.toggle-slider { - position: relative; - width: 40px; - height: 20px; - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-radius: 10px; - transition: var(--transition); -} - -.toggle-slider::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 14px; - height: 14px; - background: var(--text-secondary); - border-radius: 50%; - transition: var(--transition); -} - -.toggle-label input:checked + .toggle-slider { - background: var(--accent); - border-color: var(--accent); -} - -.toggle-label input:checked + .toggle-slider::after { - transform: translateX(20px); - background: var(--bg-solid); -} - -/* ═══ 聊天区域 ═══ */ -.chat-area { - flex: 1; - overflow-y: auto; - overflow-x: hidden; - padding: 16px; - scroll-behavior: smooth; -} - -.chat-area::-webkit-scrollbar { - width: 8px; -} - -.chat-area::-webkit-scrollbar-track { - background: transparent; -} - -.chat-area::-webkit-scrollbar-thumb { - background: var(--border-default); - border-radius: 4px; -} - -.chat-area::-webkit-scrollbar-thumb:hover { - background: var(--border-strong); -} - -/* ── 回到底部按钮 ── */ -.scroll-to-bottom { - position: sticky; - bottom: 16px; - float: right; - margin-right: 4px; - display: flex; - align-items: center; - gap: 6px; - padding: 6px 14px; - border: 1px solid var(--border-default); - background: var(--bg-card); - backdrop-filter: blur(20px); - border-radius: var(--radius-control); - color: var(--text-secondary); - font-size: 12px; - font-family: var(--font); - cursor: pointer; - z-index: 5; - box-shadow: var(--shadow-flyout); - transition: var(--transition); - animation: fadeSlideUp 167ms ease; -} - -.scroll-to-bottom:hover { - background: var(--bg-card-hover); - border-color: var(--border-strong); - color: var(--text-primary); -} - -.scroll-to-bottom:active { - transform: scale(0.98); -} - -.scroll-to-bottom svg { - width: 14px; - height: 14px; -} - -@keyframes fadeSlideUp { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } -} - -/* ── 空状态 ── */ -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - gap: 12px; - color: var(--text-tertiary); -} - -.empty-icon svg { - width: 80px; - height: 80px; - opacity: 0.3; - color: var(--text-tertiary); -} - -.empty-state h2 { - font-size: 20px; - font-weight: 600; - color: var(--text-primary); -} - -.empty-state p { - font-size: 14px; - color: var(--text-tertiary); -} - -/* ── 消息气泡 ── */ -.messages { - display: flex; - flex-direction: column; - gap: 12px; - max-width: 780px; - margin: 0 auto; -} - -.message { - display: flex; - gap: 12px; - animation: fadeSlideUp 167ms ease; -} - -.msg-avatar { - flex-shrink: 0; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - border-radius: var(--radius-control); - background: var(--bg-layer); - border: 1px solid var(--border-subtle); -} - -.msg-body { - flex: 1; - min-width: 0; -} - -.msg-role { - font-size: 12px; - font-weight: 600; - color: var(--text-tertiary); - margin-bottom: 4px; - display: flex; - align-items: center; - gap: 8px; -} - -.model-tag { - display: inline-block; - font-size: 11px; - font-weight: 600; - color: var(--accent); - background: var(--accent-subtle); - border: 1px solid rgba(96, 205, 255, 0.15); - border-radius: var(--radius-control); - padding: 1px 8px; - line-height: 1.5; - white-space: nowrap; -} - -.message.user .msg-body { - background: var(--bg-layer); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - padding: 10px 14px; -} - -.message.assistant .msg-body { - background: var(--bg-card); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - padding: 10px 14px; -} - -.msg-content { - font-size: 14px; - line-height: 1.65; - word-break: break-word; -} - -.msg-content pre { - background: var(--bg-solid); - border: 1px solid var(--border-default); - border-radius: var(--radius-control); - padding: 12px 14px; - overflow-x: auto; - font-family: var(--font-mono); - font-size: 13px; - margin: 8px 0; -} - -.msg-content code { - background: rgba(255, 255, 255, 0.06); - padding: 1px 5px; - border-radius: 3px; - font-family: var(--font-mono); - font-size: 0.9em; -} - -.msg-content pre code { - background: none; - padding: 0; -} - -.msg-content p { margin-bottom: 8px; } -.msg-content p:last-child { margin-bottom: 0; } - -/* 思考块 */ -.think-block { - margin-bottom: 8px; - border: 1px solid rgba(252, 225, 0, 0.12); - border-radius: var(--radius-control); - overflow: hidden; -} - -.think-header { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 10px; - background: var(--caution-bg); - color: var(--caution); - font-size: 12px; - cursor: pointer; - user-select: none; -} - -.think-header .chevron { - margin-left: auto; - transition: transform var(--transition); -} - -.think-header .chevron.expanded { - transform: rotate(180deg); -} - -.think-content { - padding: 8px 12px; - background: rgba(252, 225, 0, 0.02); -} - -.think-content pre { - font-size: 12px; - color: var(--text-secondary); - white-space: pre-wrap; - word-break: break-word; - margin: 0; - background: none; - border: none; - padding: 0; -} - -/* 打字光标 */ -.typing-cursor { - display: inline-block; - width: 2px; - height: 1em; - background: var(--accent); - animation: caretBlink 1s step-end infinite; - vertical-align: text-bottom; - margin-left: 1px; -} - -@keyframes caretBlink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0; } -} - -/* ═══ Loading 动画 ═══ */ -.loading-dots { - display: inline-flex; - align-items: center; - gap: 5px; - padding: 8px 0; -} - -.loading-dots span { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--text-tertiary); - animation: dotPulse 1.4s ease-in-out infinite; -} - -.loading-dots span:nth-child(1) { animation-delay: 0s; } -.loading-dots span:nth-child(2) { animation-delay: 0.2s; } -.loading-dots span:nth-child(3) { animation-delay: 0.4s; } - -@keyframes dotPulse { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.3; } - 40% { transform: scale(1); opacity: 1; } -} - -.loading-text { - font-size: 12px; - color: var(--text-tertiary); - animation: loadingFade 1.5s ease-in-out infinite; -} - -@keyframes loadingFade { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 0.8; } -} - -/* 消息图片 */ -.msg-images { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 8px; -} - -.msg-img { - max-width: 200px; - max-height: 200px; - border-radius: var(--radius-control); - border: 1px solid var(--border-subtle); - object-fit: cover; -} - -.msg-img[data-lightbox] { - cursor: zoom-in; - transition: var(--transition); -} - -.msg-img[data-lightbox]:hover { - opacity: 0.85; -} - -/* 文件 chip */ -.msg-files { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 8px; -} - -.msg-files .file-chip { - background: var(--accent-subtle); - border-color: rgba(96, 205, 255, 0.12); -} - -/* 统计信息 */ -.msg-stats { - display: flex; - gap: 12px; - margin-top: 6px; - font-size: 11px; - color: var(--text-tertiary); -} - -/* RAG 知识库来源 */ -.rag-sources { - margin-top: 8px; - padding: 8px 12px; - background: var(--accent-subtle); - border: 1px solid rgba(96, 205, 255, 0.1); - border-radius: var(--radius-control); - font-size: 12px; -} - -.rag-sources-header { - color: var(--accent); - font-weight: 600; - margin-bottom: 4px; - font-size: 11px; -} - -.rag-source-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 3px 0; - color: var(--text-tertiary); -} - -.rag-source-item + .rag-source-item { - border-top: 1px solid var(--border-subtle); -} - -.rag-source-name { - color: var(--text-secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.rag-source-score { - color: var(--success); - font-weight: 500; - margin-left: 8px; - flex-shrink: 0; -} - -/* ═══ 输入区域 ═══ */ -.input-area { - flex-shrink: 0; - padding: 10px 16px 14px; - background: var(--bg-mica); - backdrop-filter: saturate(180%) blur(20px); - -webkit-backdrop-filter: saturate(180%) blur(20px); - border-top: 1px solid var(--border-subtle); -} - -.image-preview { - display: flex; - gap: 8px; - padding: 8px 0; - flex-wrap: wrap; -} - -.preview-thumb { - position: relative; - width: 56px; - height: 56px; - border-radius: var(--radius-control); - overflow: hidden; - border: 1px solid var(--border-default); -} - -.preview-thumb img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.remove-img { - position: absolute; - top: 2px; - right: 2px; - width: 18px; - height: 18px; - border: none; - background: var(--critical); - color: white; - border-radius: 50%; - font-size: 12px; - line-height: 1; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; -} - -/* 文件预览 */ -.file-preview { - display: flex; - flex-wrap: wrap; - gap: 6px; - padding: 8px 0; -} - -.file-chip { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border-radius: var(--radius-control); - background: var(--accent-subtle); - border: 1px solid rgba(96, 205, 255, 0.12); - font-size: 12px; - line-height: 1; - transition: var(--transition); - animation: fadeSlideUp 167ms ease; -} - -.file-chip:hover { - background: var(--accent-subtle-hover); - border-color: rgba(96, 205, 255, 0.2); -} - -.file-icon { - font-size: 13px; - flex-shrink: 0; -} - -.file-name { - font-weight: 600; - color: var(--text-primary); - max-width: 160px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.file-size { - color: var(--text-tertiary); - flex-shrink: 0; -} - -.remove-file { - background: none; - border: none; - color: var(--text-tertiary); - cursor: pointer; - font-size: 14px; - line-height: 1; - padding: 0 2px; - border-radius: 3px; - transition: var(--transition); -} - -.remove-file:hover { - color: var(--critical); - background: var(--critical-bg); -} - -.input-row { - display: flex; - align-items: flex-end; - gap: 8px; -} - -.attach-btn { - flex-shrink: 0; -} - -.chat-input { - flex: 1; - padding: 8px 12px; - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-radius: var(--radius-control); - color: var(--text-primary); - font-family: var(--font); - font-size: 14px; - resize: none; - outline: none; - min-height: 36px; - max-height: 120px; - line-height: 1.5; - transition: var(--transition); -} - -.chat-input:focus { - border-color: var(--border-focus); - box-shadow: 0 0 0 1px var(--border-focus); -} - -.chat-input::placeholder { - color: var(--text-disabled); -} - -.send-btn { - flex-shrink: 0; - width: 36px; - height: 36px; - border: none; - border-radius: var(--radius-control); - background: var(--accent); - color: #1a1a1a; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); -} - -.send-btn:hover { - background: var(--accent-hover); -} - -.send-btn:active { - transform: scale(0.96); -} - -.send-btn.disabled { - opacity: 0.36; - cursor: not-allowed; - pointer-events: none; -} - -.send-btn svg { - width: 16px; - height: 16px; -} - -.send-btn.stop-btn { - background: var(--critical); - color: white; -} - -.send-btn.stop-btn:hover { - opacity: 0.9; -} - -.spinner { - width: 16px; - height: 16px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top-color: white; - border-radius: 50%; - animation: spin 0.8s linear infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* ═══ 历史只读提示栏 ═══ */ -.history-view-bar { - display: flex; - align-items: center; - gap: 10px; - padding: 6px 16px; - background: rgba(96, 205, 255, 0.04); - border-top: 1px solid rgba(96, 205, 255, 0.1); - font-size: 13px; - color: var(--accent); - flex-shrink: 0; -} - -/* ═══ 模态框 (WinUI ContentDialog 风格) ═══ */ -.modal-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(16px); - -webkit-backdrop-filter: blur(16px); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; - animation: overlayIn 167ms ease; - padding: 20px; -} - -@keyframes overlayIn { - from { opacity: 0; } - to { opacity: 1; } -} - -.modal { - background: var(--bg-card); - border: 1px solid var(--border-default); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-dialog); - width: 100%; - max-width: 480px; - max-height: 80vh; - display: flex; - flex-direction: column; - animation: dialogIn 200ms cubic-bezier(0, 0, 0, 1); - overflow: hidden; -} - -@keyframes dialogIn { - from { opacity: 0; transform: scale(0.96) translateY(8px); } - to { opacity: 1; transform: scale(1) translateY(0); } -} - -.modal-lg { - max-width: 860px; - width: 96vw; - max-height: 85vh; -} - -.modal-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 20px; - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.modal-header h3 { - font-size: 16px; - font-weight: 600; - color: var(--text-primary); -} - -.modal-body { - padding: 16px 20px; - overflow-y: auto; - flex: 1; -} - -/* ── 设置项 ── */ -.setting-group { - margin-bottom: 16px; - padding-bottom: 16px; - border-bottom: 1px solid var(--border-subtle); -} - -.setting-group:last-child { - border-bottom: none; - margin-bottom: 0; -} - -.setting-label { - display: flex; - align-items: center; - font-size: 13px; - font-weight: 600; - color: var(--text-secondary); - margin-bottom: 6px; -} - -.setting-input { - width: 100%; - padding: 8px 10px; - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-radius: var(--radius-control); - color: var(--text-primary); - font-size: 13px; - font-family: var(--font); - outline: none; - margin-bottom: 6px; - transition: var(--transition); -} - -.setting-input:focus { - border-color: var(--border-focus); - box-shadow: 0 0 0 1px var(--border-focus); -} - -.setting-input::placeholder { - color: var(--text-disabled); -} - -/* ── 按钮 (WinUI Button 风格) ── */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 16px; - border: 1px solid transparent; - border-radius: var(--radius-control); - font-size: 13px; - font-weight: 400; - font-family: var(--font); - cursor: pointer; - transition: var(--transition); - gap: 6px; - position: relative; - outline: none; -} - -.btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - background: currentColor; - opacity: 0; - transition: var(--transition); -} - -.btn:hover::before { opacity: 0.06; } -.btn:active::before { opacity: 0.04; } - -.btn:active { - transform: scale(0.98); -} - -.btn-primary { - background: var(--accent); - color: #1a1a1a; - border-color: transparent; -} - -.btn-primary:hover { - background: var(--accent-hover); -} - -.btn-primary::before { display: none; } - -.btn-outline { - background: var(--bg-layer); - border: 1px solid var(--border-default); - color: var(--text-secondary); -} - -.btn-outline:hover { - border-color: var(--border-strong); - color: var(--text-primary); -} - -.btn-danger { - background: var(--critical-bg); - border: 1px solid rgba(241, 112, 123, 0.15); - color: var(--critical); -} - -.btn-danger:hover { - background: rgba(241, 112, 123, 0.1); -} - -.btn-sm { - padding: 4px 10px; - font-size: 12px; -} - -/* ── 连接信息 ── */ -.connection-info { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 8px; -} - -.status-badge { - padding: 3px 10px; - border-radius: var(--radius-control); - font-size: 12px; - font-weight: 500; -} - -.status-badge.success { - background: var(--success-bg); - color: var(--success); - border: 1px solid rgba(108, 203, 95, 0.15); -} - -.status-badge.error { - background: var(--critical-bg); - color: var(--critical); - border: 1px solid rgba(241, 112, 123, 0.15); -} - -.status-badge.warning { - background: var(--caution-bg); - color: var(--caution); - border: 1px solid rgba(252, 225, 0, 0.15); -} - -.server-version { - font-size: 12px; - color: var(--text-tertiary); - font-family: var(--font-mono); -} - -.cors-hint { - padding: 10px; - background: var(--caution-bg); - border: 1px solid rgba(252, 225, 0, 0.1); - border-radius: var(--radius-control); - margin-top: 8px; -} - -.cors-hint p { - font-size: 12px; - color: var(--caution); - margin-bottom: 6px; -} - -.cors-hint code { - display: block; - padding: 8px; - background: var(--bg-solid); - border-radius: 3px; - font-family: var(--font-mono); - font-size: 12px; - color: var(--accent); - user-select: all; -} - -/* ── 运行中的模型 ── */ -.running-models { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: 10px; -} - -.running-model { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 10px; - background: var(--bg-layer); - border-radius: var(--radius-control); - border: 1px solid var(--border-subtle); -} - -.running-model .model-name { - flex: 1; - font-size: 13px; - font-weight: 500; -} - -.running-model .model-vram { - font-size: 12px; - color: var(--text-tertiary); - font-family: var(--font-mono); -} - -.text-muted { - color: var(--text-tertiary); - font-size: 13px; -} - -/* ── 历史记录模态框加宽 ── */ -#historyModal .modal { - max-width: 680px; - max-height: 88vh; -} - -/* ── 历史记录 ── */ -.history-list { - display: flex; - flex-direction: column; - gap: 2px; -} - -.history-search-wrap { - position: relative; - margin-bottom: 10px; -} - -.history-search-input { - width: 100%; - padding: 8px 32px 8px 12px; - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-radius: var(--radius-control); - color: var(--text-primary); - font-size: 13px; - font-family: var(--font); - outline: none; - transition: var(--transition); -} - -.history-search-input::placeholder { - color: var(--text-disabled); -} - -.history-search-input:focus { - border-color: var(--border-focus); - box-shadow: 0 0 0 1px var(--border-focus); -} - -.history-search-clear { - position: absolute; - right: 6px; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: var(--text-tertiary); - cursor: pointer; - font-size: 14px; - padding: 4px; - line-height: 1; -} - -.history-search-clear:hover { - color: var(--text-primary); -} - -.history-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 10px; - border-radius: var(--radius-control); - transition: var(--transition); -} - -.history-item:hover { - background: var(--bg-layer); -} - -.history-info { - flex: 1; - min-width: 0; - cursor: pointer; -} - -.history-title { - font-size: 13px; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.history-meta { - display: flex; - gap: 10px; - font-size: 11px; - color: var(--text-tertiary); - margin-top: 2px; -} - -.history-actions { - display: flex; - gap: 2px; - flex-shrink: 0; -} - -/* ── 历史分页 ── */ -.history-pagination { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 0 4px; - border-top: 1px solid var(--border-subtle); - margin-top: 8px; -} - -.page-info { - font-size: 12px; - color: var(--text-tertiary); -} - -.page-buttons { - display: flex; - align-items: center; - gap: 4px; -} - -.page-btn { - min-width: 30px; - height: 30px; - border: 1px solid var(--border-default); - background: var(--bg-layer); - color: var(--text-secondary); - border-radius: var(--radius-control); - font-size: 12px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); -} - -.page-btn:hover:not(.disabled):not(.active) { - border-color: var(--border-strong); - color: var(--text-primary); -} - -.page-btn.active { - background: var(--accent-subtle); - border-color: var(--accent); - color: var(--accent); - font-weight: 600; -} - -.page-btn.disabled { - opacity: 0.3; - cursor: not-allowed; -} - -.page-ellipsis { - color: var(--text-disabled); - font-size: 12px; - padding: 0 2px; -} - -/* ═══ Lightbox ═══ */ -.lightbox-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.75); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - display: flex; - align-items: center; - justify-content: center; - z-index: 300; - cursor: zoom-out; - animation: overlayIn 167ms ease; -} - -.lightbox-img { - max-width: 90vw; - max-height: 90vh; - border-radius: var(--radius-md); - box-shadow: var(--shadow-dialog); - object-fit: contain; - cursor: default; -} - -.lightbox-close { - position: fixed; - top: 16px; - right: 20px; - width: 36px; - height: 36px; - border: 1px solid var(--border-default); - background: var(--bg-card); - color: var(--text-primary); - font-size: 16px; - border-radius: var(--radius-control); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); - z-index: 301; -} - -.lightbox-close:hover { - background: var(--bg-card-hover); -} - -/* ═══ 帮助弹框 ═══ */ -#helpModal .modal { - max-width: 600px; - max-height: 85vh; -} - -.help-section { - margin-bottom: 16px; - padding: 12px 14px; - background: var(--bg-layer); - border-radius: var(--radius-control); - border-left: 2px solid var(--border-strong); - transition: border-color var(--transition); -} - -.help-section:hover { - border-left-color: var(--accent); -} - -.help-section:last-child { - margin-bottom: 0; -} - -.help-section h4 { - font-size: 14px; - font-weight: 600; - margin-bottom: 8px; - color: var(--text-primary); -} - -.help-section ul, -.help-section ol { - padding-left: 18px; - font-size: 13px; - color: var(--text-secondary); - line-height: 1.7; -} - -.help-section li { - margin-bottom: 3px; -} - -.help-section code { - background: rgba(255, 255, 255, 0.06); - color: var(--accent); - padding: 1px 6px; - border-radius: 3px; - font-size: 12px; -} - -.help-section kbd { - background: var(--bg-layer); - border: 1px solid var(--border-default); - border-bottom-width: 2px; - border-radius: 3px; - padding: 1px 6px; - font-size: 12px; - font-family: inherit; - color: var(--text-primary); -} - -.help-shortcuts { - width: 100%; - font-size: 13px; - border-collapse: collapse; - border-radius: var(--radius-control); - overflow: hidden; - border: 1px solid var(--border-default); -} - -.help-shortcuts td { - padding: 6px 10px; - border-bottom: 1px solid var(--border-subtle); -} - -.help-shortcuts tr:last-child td { - border-bottom: none; -} - -.help-shortcuts td:first-child { - width: 140px; - text-align: center; - background: var(--bg-layer); -} - -.help-shortcuts tr:hover td { - background: var(--bg-layer-alt); -} - -/* ═══ 知识库管理 ═══ */ -#kbModal .modal { - max-width: 95vw; - width: 1100px; - max-height: 88vh; -} - -#kbModal .modal-body { - padding: 16px 20px; - overflow: hidden; - display: flex; - flex-direction: column; -} - -.kb-layout { - display: flex; - gap: 16px; - flex: 1; - min-height: 0; - overflow: hidden; -} - -.kb-sidebar { - width: 240px; - flex-shrink: 0; - display: flex; - flex-direction: column; - gap: 8px; - min-height: 0; - overflow: hidden; - padding-right: 16px; - border-right: 1px solid var(--border-subtle); -} - -.kb-new-collection { - display: flex; - gap: 6px; -} - -.kb-new-collection .setting-input { - flex: 1; - margin-bottom: 0; - font-size: 13px; - padding: 6px 10px; -} - -.kb-collection-list { - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 2px; - min-height: 0; -} - -.kb-collection-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 12px; - border-radius: var(--radius-control); - cursor: pointer; - transition: var(--transition); -} - -.kb-collection-item:hover { - background: var(--bg-layer); -} - -.kb-collection-item.active { - background: var(--accent-subtle); - border: 1px solid rgba(96, 205, 255, 0.12); -} - -.kb-col-info { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} - -.kb-col-name { - font-size: 13px; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.kb-col-stats { - font-size: 11px; - color: var(--text-tertiary); -} - -.kb-col-delete { - opacity: 0; - transition: opacity var(--transition); -} - -.kb-collection-item:hover .kb-col-delete { - opacity: 1; -} - -.kb-main { - flex: 1; - display: flex; - flex-direction: column; - gap: 10px; - min-width: 0; - min-height: 0; - overflow: hidden; -} - -.kb-toolbar { - display: flex; - align-items: center; - gap: 8px; - flex-shrink: 0; - flex-wrap: wrap; - padding: 8px 12px; - background: var(--bg-layer); - border-radius: var(--radius-control); - border: 1px solid var(--border-subtle); -} - -.kb-embed-select { - flex: 1; - min-width: 0; - max-width: 200px; - padding: 5px 8px; - font-size: 12px; - background: var(--bg-layer-alt); - color: var(--text-primary); - border: 1px solid var(--border-default); - border-radius: var(--radius-control); - outline: none; - cursor: pointer; - font-family: var(--font); -} - -.kb-embed-select:focus { - border-color: var(--border-focus); -} - -.kb-rag-toggle { - display: flex; - align-items: center; - gap: 6px; - margin-left: auto; - white-space: nowrap; - padding-left: 8px; - border-left: 1px solid var(--border-subtle); -} - -.kb-progress { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: var(--text-tertiary); - padding: 3px 8px; - background: var(--accent-subtle); - border-radius: var(--radius-control); -} - -.kb-doc-list { - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 2px; - min-height: 0; -} - -.kb-doc-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 12px; - border-radius: var(--radius-control); - transition: var(--transition); -} - -.kb-doc-item:hover { - background: var(--bg-layer); -} - -.kb-doc-icon { - font-size: 16px; - flex-shrink: 0; -} - -.kb-doc-info { - flex: 1; - display: flex; - flex-direction: column; - gap: 1px; - min-width: 0; -} - -.kb-doc-name { - font-size: 13px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.kb-doc-meta { - font-size: 11px; - color: var(--text-tertiary); -} - -.kb-doc-delete { - opacity: 0; - transition: opacity var(--transition); -} - -.kb-doc-item:hover .kb-doc-delete { - opacity: 1; -} - -/* ── 移动端响应式 ── */ -@media (max-width: 640px) { - .kb-layout { - flex-direction: column; - } - - .kb-sidebar { - width: 100%; - max-height: 160px; - border-right: none; - padding-right: 0; - padding-bottom: 10px; - border-bottom: 1px solid var(--border-subtle); - } - - .kb-main { - min-height: 180px; - } - - .modal-lg { - max-height: 92vh; - } -} - -/* ═══ Toast 通知 ═══ */ -.toast-container { - position: fixed; - top: 56px; - right: 16px; - z-index: 200; - display: flex; - flex-direction: column; - gap: 6px; - pointer-events: none; -} - -.toast { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border-radius: var(--radius-control); - background: var(--bg-card); - border: 1px solid var(--border-default); - box-shadow: var(--shadow-flyout); - font-size: 13px; - color: var(--text-primary); - pointer-events: auto; - animation: toastIn 167ms ease; - max-width: 320px; -} - -.toast.removing { - animation: toastOut 167ms ease forwards; -} - -.toast-icon { - font-size: 14px; - flex-shrink: 0; -} - -.toast.success { border-left: 3px solid var(--success); } -.toast.error { border-left: 3px solid var(--critical); } -.toast.warning { border-left: 3px solid var(--caution); } -.toast.info { border-left: 3px solid var(--accent); } - -@keyframes toastIn { - from { opacity: 0; transform: translateX(24px); } - to { opacity: 1; transform: translateX(0); } -} - -@keyframes toastOut { - from { opacity: 1; transform: translateX(0); } - to { opacity: 0; transform: translateX(24px); } -} - -/* ═══ 响应式 ═══ */ -@media (min-width: 768px) { - .app-header { padding: 8px 24px; } - .model-bar { padding: 8px 24px; } - .chat-area { padding: 20px 24px; } - .messages { max-width: 820px; } - .input-area { padding: 10px 24px 16px; } - .modal { max-width: 500px; } -} - -@media (min-width: 1024px) { - .messages { max-width: 900px; } -} - -/* ── 选中文本 ── */ -::selection { - background: rgba(96, 205, 255, 0.3); -} - -/* ── 链接 ── */ -.msg-content a { - color: var(--accent); - text-decoration: none; -} - -.msg-content a:hover { - text-decoration: underline; -} - -.msg-content .blocked-link { - color: var(--critical); - text-decoration: line-through; - cursor: not-allowed; - opacity: 0.7; -} - -/* ── 列表 ── */ -.msg-content ul, .msg-content ol { - padding-left: 20px; - margin: 8px 0; -} - -.msg-content li { margin-bottom: 3px; } - -/* ── 表格 ── */ -.msg-content table { - width: 100%; - border-collapse: collapse; - margin: 8px 0; - font-size: 13px; -} - -.msg-content th, .msg-content td { - padding: 6px 10px; - border: 1px solid var(--border-default); - text-align: left; -} - -.msg-content th { - background: var(--bg-layer); - font-weight: 600; -} - -/* ── 引用块 ── */ -.msg-content blockquote { - border-left: 2px solid var(--border-strong); - padding-left: 12px; - margin: 8px 0; - color: var(--text-secondary); -} diff --git a/electron/desktop-bridge.js b/electron/desktop-bridge.js deleted file mode 100644 index 4fc0974..0000000 --- a/electron/desktop-bridge.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Metona Ollama Desktop - 渲染进程桥接模块 - * - * 在 Web 应用启动前加载,将桌面能力注入到 window 对象 - * 提供优雅降级:Web 环境下所有 desktop API 返回 null/空函数 - */ - -// Desktop API 由 preload.js 通过 contextBridge 注入 -// 这里做一层封装,提供统一的降级处理 - -const desktop = window.metonaDesktop || null; - -const bridge = { - /** 是否在 Electron 桌面环境中 */ - isDesktop: !!desktop, - - /** 应用信息 */ - async getInfo() { - if (!desktop) return { platform: 'web', version: '1.0.0' }; - return desktop.info(); - }, - - /** 打开原生文件选择对话框 */ - async openFile(filters) { - if (!desktop) return null; - return desktop.dialog.openFile(filters); - }, - - /** 打开原生保存对话框 */ - async saveFile(options) { - if (!desktop) return null; - return desktop.dialog.saveFile(options); - }, - - /** 读取文件内容 */ - async readFile(filePath) { - if (!desktop) return { success: false, error: 'Not in desktop' }; - return desktop.fs.readFile(filePath); - }, - - /** 写入文件 */ - async writeFile(filePath, content) { - if (!desktop) return { success: false, error: 'Not in desktop' }; - return desktop.fs.writeFile(filePath, content); - }, - - /** 发送系统通知 */ - notify(title, body) { - if (desktop) { - desktop.notify(title, body); - } else if ('Notification' in window && Notification.permission === 'granted') { - new Notification(title, { body }); - } - }, - - /** 最小化窗口 */ - minimize() { - desktop?.window.minimize(); - }, - - /** 最大化/还原 */ - maximize() { - desktop?.window.maximize(); - }, - - /** 关闭窗口 */ - close() { - desktop?.window.close(); - }, - - /** 打开外部链接 */ - openExternal(url) { - if (desktop) { - desktop.openExternal(url); - } else { - window.open(url, '_blank'); - } - }, - - /** 监听主进程菜单事件 */ - onMenuAction(callback) { - desktop?.onMenuAction(callback); - }, - - /** 监听托盘事件 */ - onTrayAction(callback) { - desktop?.onTrayAction(callback); - }, - - /** 注销监听 */ - off(channel) { - desktop?.removeAllListeners(channel); - } -}; - -// 暴露到全局 -window.__metonaBridge = bridge; - -// 打印环境信息 -if (bridge.isDesktop) { - bridge.getInfo().then(info => { - console.log('[Desktop] Metona Ollama Desktop'); - console.log('[Desktop] Platform:', info.platform, info.arch); - console.log('[Desktop] Electron:', info.electronVersion); - console.log('[Desktop] User Data:', info.userDataPath); - }); -} diff --git a/electron/desktop-integration.js b/electron/desktop-integration.js deleted file mode 100644 index da8c773..0000000 --- a/electron/desktop-integration.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Metona Ollama Desktop - 桌面端集成 - * - * 在 DOM 加载后执行,为现有 Web 应用添加桌面特性: - * 1. 主进程菜单/托盘事件响应 - * 2. 原生文件对话框替换浏览器 input[type=file] - * 3. 桌面端快捷键增强 - * 4. 标题栏集成 - */ - -(function () { - 'use strict'; - - const bridge = window.__metonaBridge; - if (!bridge || !bridge.isDesktop) { - // 非桌面环境,跳过 - return; - } - - console.log('[Desktop Integration] 初始化桌面集成...'); - - // ── 1. 菜单/托盘事件响应 ── - bridge.onMenuAction((action) => { - switch (action) { - case 'new-chat': - document.querySelector('#btnNewChat')?.click(); - break; - case 'import': - document.querySelector('#btnImportSessions')?.click(); - break; - case 'export': - document.querySelector('#btnExportAll')?.click(); - break; - case 'help': - document.querySelector('#btnHelp')?.click(); - break; - } - }); - - bridge.onTrayAction((action) => { - switch (action) { - case 'new-chat': - document.querySelector('#btnNewChat')?.click(); - break; - } - }); - - // ── 2. 增强文件上传 - 使用原生文件对话框 ── - function setupNativeFilePicker(btnId, inputId, fileFilters) { - const btn = document.querySelector(btnId); - const input = document.querySelector(inputId); - if (!btn || !input) return; - - // 拦截点击事件,使用原生对话框 - btn.addEventListener('click', async (e) => { - e.preventDefault(); - e.stopPropagation(); - - const filePaths = await bridge.openFile(fileFilters); - if (!filePaths || filePaths.length === 0) return; - - // 读取所有文件 - for (const filePath of filePaths) { - const result = await bridge.readFile(filePath); - if (result.success) { - // 创建 File 对象注入到原有流程 - const blob = new Blob([result.content], { type: 'text/plain' }); - const file = new File([blob], result.name, { type: 'text/plain' }); - - // 使用 DataTransfer 注入文件到 input,触发 change 事件 - const dt = new DataTransfer(); - dt.items.add(file); - input.files = dt.files; - input.dispatchEvent(new Event('change', { bubbles: true })); - } - } - }, true); // capture phase, 优先于原有事件 - } - - // DOM 就绪后设置 - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', setupEnhancements); - } else { - setupEnhancements(); - } - - function setupEnhancements() { - // 代码/文本文件上传 - setupNativeFilePicker( - '#btnAttachFile', - '#textFileInput', - [ - { name: '代码文件', extensions: ['py', 'js', 'ts', 'tsx', 'java', 'go', 'rs', 'cpp', 'c', 'h', 'rb', 'php', 'sh', 'swift', 'kt', 'lua'] }, - { name: '配置文件', extensions: ['json', 'yaml', 'yml', 'toml', 'xml', 'ini', 'conf'] }, - { name: '文档', extensions: ['txt', 'md', 'log', 'csv', 'html', 'css'] }, - { name: '所有文件', extensions: ['*'] } - ] - ); - } - - // ── 3. 标题更新 - 显示连接/模型状态 ── - const origTitle = document.title; - window.addEventListener('metona:model-changed', (e) => { - if (e.detail?.model) { - document.title = `${e.detail.model} - Metona Ollama`; - } else { - document.title = origTitle; - } - }); - - // ── 4. 桌面端键盘快捷键 ── - document.addEventListener('keydown', (e) => { - // Ctrl+N 新建会话(与菜单同步) - if (e.ctrlKey && e.key === 'n') { - e.preventDefault(); - document.querySelector('#btnNewChat')?.click(); - } - }); - - console.log('[Desktop Integration] 桌面集成完成 ✓'); -})(); diff --git a/electron/main.js b/electron/main.js deleted file mode 100644 index f5a1a1d..0000000 --- a/electron/main.js +++ /dev/null @@ -1,435 +0,0 @@ -/** - * Metona Ollama Desktop - 主进程 - * - * 职责: - * 1. 应用生命周期管理 - * 2. BrowserWindow 创建与配置 - * 3. 原生菜单、系统托盘 - * 4. IPC 通信桥梁(文件对话框、通知、窗口控制) - * 5. 单实例锁 - */ - -const { app, BrowserWindow, ipcMain, dialog, Menu, Tray, nativeImage, shell, Notification } = require('electron'); -const path = require('path'); -const fs = require('fs'); - -// ── 常量 ── -const APP_NAME = 'Metona Ollama'; -const ICON_PATH = path.join(__dirname, '..', 'assets', 'icons', 'llama.png'); -const ICO_PATH = path.join(__dirname, '..', 'assets', 'icons', 'llama.ico'); -const IS_DEV = !app.isPackaged; - -let mainWindow = null; -let tray = null; -let isQuitting = false; - -// ── 单实例锁 ── -const gotTheLock = app.requestSingleInstanceLock(); -if (!gotTheLock) { - app.quit(); -} else { - app.on('second-instance', () => { - if (mainWindow) { - if (mainWindow.isMinimized()) mainWindow.restore(); - mainWindow.focus(); - } - }); -} - -// ── 创建主窗口 ── -function createMainWindow() { - // 读取上次窗口大小 - const userDataPath = app.getPath('userData'); - const configPath = path.join(userDataPath, 'window-state.json'); - let windowState = { width: 1200, height: 800, x: undefined, y: undefined, maximized: false }; - - try { - if (fs.existsSync(configPath)) { - windowState = JSON.parse(fs.readFileSync(configPath, 'utf-8')); - } - } catch { /* 使用默认值 */ } - - mainWindow = new BrowserWindow({ - width: windowState.width || 1200, - height: windowState.height || 800, - x: windowState.x, - y: windowState.y, - minWidth: 800, - minHeight: 600, - icon: process.platform === 'win32' ? ICO_PATH : ICON_PATH, - title: APP_NAME, - backgroundColor: '#0a0a1a', - show: false, // 等 ready-to-show 再显示,避免闪烁 - autoHideMenuBar: true, - menuBarVisible: false, - webPreferences: { - preload: path.join(__dirname, 'preload.js'), - contextIsolation: true, - nodeIntegration: false, - sandbox: false, - // 关闭同源策略,避免 Ollama CORS 问题 - webSecurity: false, - allowRunningInsecureContent: false - } - }); - - // 加载应用 - mainWindow.loadFile(path.join(__dirname, '..', 'index.html')); - - // 彻底隐藏菜单栏 - mainWindow.setMenuBarVisibility(false); - - // 窗口准备好后显示 - mainWindow.once('ready-to-show', () => { - if (windowState.maximized) { - mainWindow.maximize(); - } - mainWindow.show(); - }); - - // 保存窗口状态 - const saveWindowState = () => { - if (!mainWindow || mainWindow.isMaximized() || mainWindow.isMinimized()) return; - const bounds = mainWindow.getBounds(); - try { - fs.writeFileSync(configPath, JSON.stringify(bounds)); - } catch { /* 忽略写入错误 */ } - }; - - mainWindow.on('resize', saveWindowState); - mainWindow.on('move', saveWindowState); - - mainWindow.on('maximize', () => { - try { - const state = JSON.parse(fs.readFileSync(configPath, 'utf-8')); - state.maximized = true; - fs.writeFileSync(configPath, JSON.stringify(state)); - } catch { /* 忽略 */ } - }); - - mainWindow.on('unmaximize', () => { - try { - const state = JSON.parse(fs.readFileSync(configPath, 'utf-8')); - state.maximized = false; - fs.writeFileSync(configPath, JSON.stringify(state)); - } catch { /* 忽略 */ } - }); - - // 关闭窗口时:最小化到托盘而非退出 - mainWindow.on('close', (e) => { - if (!isQuitting && tray) { - e.preventDefault(); - mainWindow.hide(); - // 首次最小化到托盘时显示通知 - if (!global._trayHintShown) { - global._trayHintShown = true; - showNotification('Metona 已最小化到系统托盘', '点击托盘图标可重新打开'); - } - } - }); - - mainWindow.on('closed', () => { - mainWindow = null; - }); - - // 外部链接用系统浏览器打开 - mainWindow.webContents.setWindowOpenHandler(({ url }) => { - if (url.startsWith('http://') || url.startsWith('https://')) { - shell.openExternal(url); - return { action: 'deny' }; - } - return { action: 'allow' }; - }); - - return mainWindow; -} - -// ── 创建系统托盘 ── -function createTray() { - const icon = nativeImage.createFromPath(process.platform === 'win32' ? ICO_PATH : ICON_PATH); - tray = new Tray(icon.resize({ width: 16, height: 16 })); - - const contextMenu = Menu.buildFromTemplate([ - { - label: '打开 Metona', - click: () => { - if (mainWindow) { - mainWindow.show(); - mainWindow.focus(); - } - } - }, - { type: 'separator' }, - { - label: '新建会话', - click: () => { - if (mainWindow) { - mainWindow.show(); - mainWindow.focus(); - mainWindow.webContents.send('tray-action', 'new-chat'); - } - } - }, - { type: 'separator' }, - { - label: '退出', - click: () => { - isQuitting = true; - app.quit(); - } - } - ]); - - tray.setToolTip(APP_NAME); - tray.setContextMenu(contextMenu); - - // 双击托盘图标打开窗口 - tray.on('double-click', () => { - if (mainWindow) { - mainWindow.show(); - mainWindow.focus(); - } - }); -} - -// ── 原生菜单 ── -function createMenu() { - const template = [ - { - label: '文件', - submenu: [ - { - label: '新建会话', - accelerator: 'Ctrl+N', - click: () => mainWindow?.webContents.send('menu-action', 'new-chat') - }, - { type: 'separator' }, - { - label: '导入会话', - click: () => mainWindow?.webContents.send('menu-action', 'import') - }, - { - label: '导出会话', - accelerator: 'Ctrl+Shift+E', - click: () => mainWindow?.webContents.send('menu-action', 'export') - }, - { type: 'separator' }, - { - label: '退出', - accelerator: 'Ctrl+Q', - click: () => { isQuitting = true; app.quit(); } - } - ] - }, - { - label: '编辑', - submenu: [ - { role: 'undo', label: '撤销' }, - { role: 'redo', label: '重做' }, - { type: 'separator' }, - { role: 'cut', label: '剪切' }, - { role: 'copy', label: '复制' }, - { role: 'paste', label: '粘贴' }, - { role: 'selectAll', label: '全选' } - ] - }, - { - label: '视图', - submenu: [ - { role: 'reload', label: '刷新' }, - { role: 'forceReload', label: '强制刷新' }, - { type: 'separator' }, - { role: 'zoomIn', label: '放大' }, - { role: 'zoomOut', label: '缩小' }, - { role: 'resetZoom', label: '重置缩放' }, - { type: 'separator' }, - { role: 'togglefullscreen', label: '全屏' } - ] - }, - { - label: '窗口', - submenu: [ - { role: 'minimize', label: '最小化' }, - { - label: '最大化/还原', - click: () => { - if (mainWindow?.isMaximized()) mainWindow.unmaximize(); - else mainWindow?.maximize(); - } - }, - { type: 'separator' }, - { - label: '置顶', - type: 'checkbox', - checked: false, - click: (menuItem) => { - mainWindow?.setAlwaysOnTop(menuItem.checked); - } - } - ] - }, - { - label: '帮助', - submenu: [ - { - label: '使用帮助', - click: () => mainWindow?.webContents.send('menu-action', 'help') - }, - { type: 'separator' }, - { - label: '关于 Ollama', - click: () => shell.openExternal('https://ollama.com') - }, - { - label: '关于 Metona', - click: () => { - dialog.showMessageBox(mainWindow, { - type: 'info', - title: '关于 Metona Ollama', - message: 'Metona Ollama Desktop v1.1.0', - detail: '基于原生 JavaScript 的 Ollama AI 聊天客户端\n桌面版由 Electron 驱动\n\nhttps://gitee.com/thzxx/metona-ollama', - icon: process.platform === 'win32' ? ICO_PATH : ICON_PATH - }); - } - } - ] - } - ]; - - // 开发环境增加开发者工具菜单 - if (IS_DEV) { - template.push({ - label: 'Dev', - submenu: [ - { - label: '切换开发者工具', - accelerator: 'F12', - click: () => mainWindow?.webContents.toggleDevTools() - } - ] - }); - } - - const menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); -} - -// ── 系统通知 ── -function showNotification(title, body) { - if (Notification.isSupported()) { - new Notification({ title, body, icon: ICON_PATH }).show(); - } -} - -// ── IPC 处理 ── -function setupIPC() { - // 打开文件对话框 - ipcMain.handle('dialog:openFile', async (_, options) => { - const result = await dialog.showOpenDialog(mainWindow, { - properties: ['openFile', 'multiSelections'], - filters: options?.filters || [ - { name: '所有文件', extensions: ['*'] } - ] - }); - if (result.canceled) return null; - return result.filePaths; - }); - - // 保存文件对话框 - ipcMain.handle('dialog:saveFile', async (_, options) => { - const result = await dialog.showSaveDialog(mainWindow, { - defaultPath: options?.defaultPath || 'export', - filters: options?.filters || [ - { name: '所有文件', extensions: ['*'] } - ] - }); - if (result.canceled) return null; - return result.filePath; - }); - - // 读取文件内容 - ipcMain.handle('fs:readFile', async (_, filePath) => { - try { - const content = await fs.promises.readFile(filePath, 'utf-8'); - return { success: true, content, name: path.basename(filePath) }; - } catch (err) { - return { success: false, error: err.message }; - } - }); - - // 写入文件 - ipcMain.handle('fs:writeFile', async (_, filePath, content) => { - try { - await fs.promises.writeFile(filePath, content, 'utf-8'); - return { success: true }; - } catch (err) { - return { success: false, error: err.message }; - } - }); - - // 发送系统通知 - ipcMain.handle('notify', (_, title, body) => { - showNotification(title, body); - }); - - // 获取应用信息 - ipcMain.handle('app:info', () => ({ - version: app.getVersion(), - platform: process.platform, - arch: process.arch, - electronVersion: process.versions.electron, - userDataPath: app.getPath('userData') - })); - - // 窗口控制 - ipcMain.handle('window:minimize', () => mainWindow?.minimize()); - ipcMain.handle('window:maximize', () => { - if (mainWindow?.isMaximized()) mainWindow.unmaximize(); - else mainWindow?.maximize(); - }); - ipcMain.handle('window:close', () => mainWindow?.close()); - - // 打开外部链接 - ipcMain.handle('shell:openExternal', (_, url) => { - if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('mailto:')) { - shell.openExternal(url); - } - }); -} - -// ── 应用生命周期 ── -app.whenReady().then(() => { - setupIPC(); - createMainWindow(); - createTray(); - createMenu(); - - // macOS: 点击 Dock 图标时重新创建窗口 - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createMainWindow(); - } else if (mainWindow) { - mainWindow.show(); - } - }); -}); - -// 所有窗口关闭时不退出(有托盘) -app.on('window-all-closed', () => { - // macOS 习惯保留 - if (process.platform !== 'darwin') { - // 有托盘时不退出 - if (!tray) app.quit(); - } -}); - -app.on('before-quit', () => { - isQuitting = true; -}); - -// 移除默认菜单(Windows 生产环境) -if (process.platform === 'win32' && !IS_DEV) { - app.on('browser-window-created', (_, win) => { - // 保留自定义菜单 - }); -} diff --git a/electron/preload.js b/electron/preload.js deleted file mode 100644 index a69c8ea..0000000 --- a/electron/preload.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Metona Ollama Desktop - Preload 脚本 - * - * 通过 contextBridge 安全暴露 IPC 接口给渲染进程 - * 所有 API 都是白名单模式,不暴露 Node.js 全局对象 - */ - -const { contextBridge, ipcRenderer } = require('electron'); - -// ── 暴露 Desktop API ── -contextBridge.exposeInMainWorld('metonaDesktop', { - // 是否在桌面环境中 - isDesktop: true, - - // 版本信息 - info: () => ipcRenderer.invoke('app:info'), - - // ── 文件对话框 ── - dialog: { - openFile: (filters) => ipcRenderer.invoke('dialog:openFile', { filters }), - saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options) - }, - - // ── 文件系统 ── - fs: { - readFile: (filePath) => ipcRenderer.invoke('fs:readFile', filePath), - writeFile: (filePath, content) => ipcRenderer.invoke('fs:writeFile', filePath, content) - }, - - // ── 系统通知 ── - notify: (title, body) => ipcRenderer.invoke('notify', title, body), - - // ── 窗口控制 ── - window: { - minimize: () => ipcRenderer.invoke('window:minimize'), - maximize: () => ipcRenderer.invoke('window:maximize'), - close: () => ipcRenderer.invoke('window:close') - }, - - // ── 外部链接 ── - openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url), - - // ── 主进程事件监听 ── - onMenuAction: (callback) => { - ipcRenderer.on('menu-action', (_, action) => callback(action)); - }, - onTrayAction: (callback) => { - ipcRenderer.on('tray-action', (_, action) => callback(action)); - }, - // 移除监听 - removeAllListeners: (channel) => { - ipcRenderer.removeAllListeners(channel); - } -}); diff --git a/index.html b/index.html deleted file mode 100644 index f889961..0000000 --- a/index.html +++ /dev/null @@ -1,456 +0,0 @@ - - - - - - - - - - - - Metona Ollama - - - -
- - -
-
- - Metona Ollama - v1.1.0 - -
-
-
- - 未连接 -
- - - - -
-
- - -
-
- - - - -
-
- - - - -
-
- - - -
-
- - -
-
-
- - - - - - - - -
-

开始对话

-

选择一个模型,输入消息开始聊天

-
- - -
- - - - - -
- - -
- - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - diff --git a/js/app.js b/js/app.js deleted file mode 100644 index de4d320..0000000 --- a/js/app.js +++ /dev/null @@ -1,242 +0,0 @@ -/** - * App - 主入口模块 - * 负责初始化所有组件、协调事件流、管理生命周期 - */ - -import { ChatDB } from './chat-db.js'; -import { OllamaAPI } from './ollama-api.js'; -import { state, KEYS } from './state.js'; -import { generateId } from './utils.js'; - -// 组件 -import { initToast, showToast } from './components/toast.js'; -import { initLightbox, closeLightbox } from './components/lightbox.js'; -import { initHeader, checkConnection } from './components/header.js'; -import { initModelBar, loadModels, setSelectedModel } from './components/model-bar.js'; -import { initChatArea, renderMessages, clearMessages, enableAutoScroll } from './components/chat-area.js'; -import { initInputArea } from './components/input-area.js'; -import { initSettingsModal, closeSettingsModal } from './components/settings-modal.js'; -import { initHistoryModal, closeHistoryModal } from './components/history-modal.js'; -import { initKBModal } from './components/kb-modal.js'; -import { initPresetBar, initPresetModal } from './components/preset-bar.js'; -import { initPresetManager } from './preset-manager.js'; -import { initVectorStore } from './rag.js'; - -function initHelpModal() { - const helpModal = document.querySelector('#helpModal'); - document.querySelector('#btnHelp').addEventListener('click', () => { - helpModal.style.display = ''; - }); - document.querySelector('#btnCloseHelp').addEventListener('click', () => { - helpModal.style.display = 'none'; - }); - helpModal.addEventListener('click', (e) => { - if (e.target === helpModal) helpModal.style.display = 'none'; - }); -} - -console.log('[Metona] 模块化版本 v4.0.3 ' + new Date().toISOString()); - -// ── 会话管理 ── - -function createNewSession() { - const selectedModel = document.querySelector('#modelSelect')?.value || ''; - const defaultModel = selectedModel || state.get('_defaultModel', ''); - return { - id: generateId(), - title: '新会话', - model: defaultModel, - messages: [], - createdAt: Date.now(), - updatedAt: Date.now() - }; -} - -async function startNewSession() { - // 先保存当前会话(如果有的话) - const db = state.get(KEYS.DB); - const currentSession = state.get(KEYS.CURRENT_SESSION); - if (db && currentSession && currentSession.messages.length > 0) { - currentSession.updatedAt = Date.now(); - await db.saveSession(currentSession); - } - - // 释放显存,清理模型上下文 - const api = state.get(KEYS.API); - const model = currentSession?.model || state.get('_defaultModel', ''); - if (api && model) { - try { - await api.chat({ model, messages: [], keep_alive: 0 }); - } catch (e) { - console.warn('[App] 释放显存失败:', e); - } - } - - const session = createNewSession(); - state.set(KEYS.CURRENT_SESSION, session); - state.set(KEYS.IS_HISTORY_VIEW, false); - - document.querySelector('#historyBar').style.display = 'none'; - document.querySelector('#inputArea').style.display = ''; - - clearMessages(); - enableAutoScroll(); - renderMessages(); - document.querySelector('#chatInput')?.focus(); -} - -// ── 初始化 ── - -async function init() { - let db, api; - - try { - // 1. 初始化 IndexedDB - db = new ChatDB(); - await db.init(); - state.set(KEYS.DB, db); - - // 2. 读取保存的设置 - const serverUrl = await db.getSetting('serverUrl', 'http://127.0.0.1:11434'); - api = new OllamaAPI(serverUrl); - state.set(KEYS.API, api); - - // 3. 创建初始会话 - state.set(KEYS.CURRENT_SESSION, createNewSession()); - state.set(KEYS.IS_STREAMING, false); - state.set(KEYS.IS_HISTORY_VIEW, false); - state.set(KEYS.NUM_CTX, 24576); - - // 4. 初始化所有组件 - initToast(); - initLightbox(); - initHeader(); - initModelBar(); - initChatArea(); - initInputArea(); - initSettingsModal(); - initHistoryModal(); - initKBModal(); - initPresetBar(); - initPresetModal(); - initHelpModal(); - - // 5. 绑定全局事件 - bindGlobalEvents(); - - // 6. 检测连接 + 加载模型 - await checkConnection(); - await loadModels(); - - // 6.1 初始化向量存储 - await initVectorStore(); - - // 6.2 初始化预设管理器(会从 DB 加载并应用上次激活的预设) - await initPresetManager(); - - // 7. 恢复上次选择的模型 - const savedModel = await db.getSetting('selectedModel', ''); - if (savedModel) { - setSelectedModel(savedModel); - state.set('_defaultModel', savedModel); - await db.saveSetting('selectedModel', savedModel); - } - - // 8. 恢复系统提示词、上下文长度、温度 - const systemPromptEnabled = await db.getSetting('systemPromptEnabled', false); - const systemPrompt = await db.getSetting('systemPrompt', ''); - const numCtx = await db.getSetting('numCtx', 24576); - const temperature = await db.getSetting('temperature', 0.7); - - state.set(KEYS.SYSTEM_PROMPT_ENABLED, systemPromptEnabled); - state.set(KEYS.SYSTEM_PROMPT, systemPrompt); - state.set(KEYS.NUM_CTX, numCtx); - state.set('temperature', temperature); - - document.querySelector('#toggleSystemPrompt').checked = systemPromptEnabled; - document.querySelector('#inputSystemPrompt').disabled = !systemPromptEnabled; - document.querySelector('#inputSystemPrompt').value = systemPrompt; - document.querySelector('#inputNumCtx').value = numCtx; - document.querySelector('#inputTemperature').value = temperature; - document.querySelector('#tempValue').textContent = parseFloat(temperature).toFixed(1); - - // 9. 聚焦输入框 - document.querySelector('#chatInput')?.focus(); - - // 10. 注册 Service Worker (PWA) - initServiceWorker(); - - console.log('[App] 初始化完成'); - } catch (err) { - console.error('[App] 初始化失败:', err); - // 即使 DB 失败也尝试运行 - if (!api) { - api = new OllamaAPI(); - state.set(KEYS.API, api); - } - state.set(KEYS.CURRENT_SESSION, createNewSession()); - bindGlobalEvents(); - initToast(); - initLightbox(); - initHeader(); - initModelBar(); - initChatArea(); - initInputArea(); - initSettingsModal(); - initHistoryModal(); - initPresetBar(); - initPresetModal(); - initPresetManager(); - checkConnection().then(loadModels); - } -} - -function bindGlobalEvents() { - // 新建会话 - document.querySelector('#btnNewChat')?.addEventListener('click', startNewSession); - - // ESC 关闭模态框 - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - closeSettingsModal(); - closeHistoryModal(); - closeLightbox(); - document.querySelector('#helpModal').style.display = 'none'; - document.querySelector('#kbModal').style.display = 'none'; - } - }); - - // 全局错误捕获 - window.addEventListener('error', (e) => { - console.error('[App] 未捕获错误:', e.error || e.message); - showToast(`发生错误: ${e.message}`, 'error', 5000); - }); - - window.addEventListener('unhandledrejection', (e) => { - console.error('[App] 未处理 Promise 拒绝:', e.reason); - const msg = e.reason?.message || String(e.reason); - showToast(`操作失败: ${msg}`, 'error', 5000); - }); -} - -function initServiceWorker() { - if (!('serviceWorker' in navigator)) return; - - navigator.serviceWorker.getRegistrations() - .then(regs => regs.forEach(r => r.unregister())) - .then(() => caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k))))) - .then(() => navigator.serviceWorker.register('sw.js?v=3', { updateViaCache: 'none' })) - .then(reg => { - reg.addEventListener('updatefound', () => { - const w = reg.installing; - if (w) w.addEventListener('statechange', () => { - if (w.state === 'activated') window.location.reload(); - }); - }); - reg.update(); - }) - .catch(err => console.warn('[App] Service Worker 注册失败:', err)); -} - -// 启动 -init(); diff --git a/js/chat-db.js b/js/chat-db.js deleted file mode 100644 index 53ddb3b..0000000 --- a/js/chat-db.js +++ /dev/null @@ -1,193 +0,0 @@ -/** - * ChatDB - IndexedDB 封装层 - * - * 用于持久化聊天历史记录和设置 - * 使用 IndexedDB 而非 localStorage,原因: - * 1. 支持存储更大的数据(如 base64 图片) - * 2. 异步操作不阻塞主线程 - * 3. 支持事务和索引查询 - */ - -export class ChatDB { - constructor(dbName = 'metona-ollama', version = 1) { - this.dbName = dbName; - this.version = version; - this.db = null; - } - - /** - * 初始化数据库 - * 创建 object stores 和索引 - */ - async init() { - return new Promise((resolve, reject) => { - const request = indexedDB.open(this.dbName, this.version); - - request.onerror = () => { - console.error('[ChatDB] 数据库打开失败:', request.error); - reject(request.error); - }; - - request.onsuccess = () => { - this.db = request.result; - console.log('[ChatDB] 数据库已连接'); - resolve(); - }; - - // 数据库升级/创建回调 - request.onupgradeneeded = (event) => { - const db = event.target.result; - - // 会话存储 - if (!db.objectStoreNames.contains('sessions')) { - const sessionStore = db.createObjectStore('sessions', { keyPath: 'id' }); - sessionStore.createIndex('updatedAt', 'updatedAt', { unique: false }); - sessionStore.createIndex('model', 'model', { unique: false }); - console.log('[ChatDB] 创建 sessions 存储'); - } - - // 设置存储 - if (!db.objectStoreNames.contains('settings')) { - db.createObjectStore('settings', { keyPath: 'key' }); - console.log('[ChatDB] 创建 settings 存储'); - } - }; - }); - } - - /** - * 确保数据库已连接 - */ - _ensureDB() { - if (!this.db) throw new Error('数据库未初始化,请先调用 init()'); - } - - /** - * 获取事务 - */ - _tx(storeName, mode = 'readonly') { - this._ensureDB(); - const tx = this.db.transaction(storeName, mode); - return tx.objectStore(storeName); - } - - // ═══════════════════════════════════════════════════════════════ - // 会话 CRUD - // ═══════════════════════════════════════════════════════════════ - - /** 保存/更新会话 */ - async saveSession(session) { - return new Promise((resolve, reject) => { - const store = this._tx('sessions', 'readwrite'); - const request = store.put(session); - request.onsuccess = () => resolve(session.id); - request.onerror = () => reject(request.error); - }); - } - - /** 获取单个会话 */ - async getSession(id) { - return new Promise((resolve, reject) => { - const store = this._tx('sessions'); - const request = store.get(id); - request.onsuccess = () => resolve(request.result || null); - request.onerror = () => reject(request.error); - }); - } - - /** 获取所有会话 */ - async getAllSessions() { - return new Promise((resolve, reject) => { - const store = this._tx('sessions'); - const request = store.getAll(); - request.onsuccess = () => resolve(request.result || []); - request.onerror = () => reject(request.error); - }); - } - - /** 删除会话 */ - async deleteSession(id) { - return new Promise((resolve, reject) => { - const store = this._tx('sessions', 'readwrite'); - const request = store.delete(id); - request.onsuccess = () => resolve(); - request.onerror = () => reject(request.error); - }); - } - - /** 清空所有会话 */ - async clearAll() { - return new Promise((resolve, reject) => { - const store = this._tx('sessions', 'readwrite'); - const request = store.clear(); - request.onsuccess = () => resolve(); - request.onerror = () => reject(request.error); - }); - } - - /** 批量保存会话(单事务,原子性) */ - async importSessions(sessions) { - return new Promise((resolve, reject) => { - const tx = this.db.transaction('sessions', 'readwrite'); - const store = tx.objectStore('sessions'); - let imported = 0; - let skipped = 0; - - tx.oncomplete = () => resolve({ imported, skipped }); - tx.onerror = () => reject(tx.error); - - // 先检查每个 ID 是否已存在,再 put - for (const session of sessions) { - if (!session.id || !Array.isArray(session.messages)) { - skipped++; - continue; - } - const getRequest = store.get(session.id); - getRequest.onsuccess = () => { - if (getRequest.result) { - skipped++; - } else { - store.put(session); - imported++; - } - }; - } - }); - } - - /** 按时间范围查询会话 */ - async getSessionsByTimeRange(startTime, endTime) { - return new Promise((resolve, reject) => { - const store = this._tx('sessions'); - const index = store.index('updatedAt'); - const range = IDBKeyRange.bound(startTime, endTime); - const request = index.getAll(range); - request.onsuccess = () => resolve(request.result || []); - request.onerror = () => reject(request.error); - }); - } - - // ═══════════════════════════════════════════════════════════════ - // 设置 CRUD - // ═══════════════════════════════════════════════════════════════ - - /** 保存设置 */ - async saveSetting(key, value) { - return new Promise((resolve, reject) => { - const store = this._tx('settings', 'readwrite'); - const request = store.put({ key, value }); - request.onsuccess = () => resolve(); - request.onerror = () => reject(request.error); - }); - } - - /** 获取设置 */ - async getSetting(key, defaultValue = null) { - return new Promise((resolve, reject) => { - const store = this._tx('settings'); - const request = store.get(key); - request.onsuccess = () => resolve(request.result ? request.result.value : defaultValue); - request.onerror = () => reject(request.error); - }); - } -} diff --git a/js/components/chat-area.js b/js/components/chat-area.js deleted file mode 100644 index 7dded6c..0000000 --- a/js/components/chat-area.js +++ /dev/null @@ -1,395 +0,0 @@ -/** - * ChatArea - 聊天区域组件 - * 负责消息渲染、流式更新、导出功能 - */ - -import { state, KEYS } from '../state.js'; -import { marked } from '../marked-config.js'; -import { escapeHtml, formatTime, downloadFile } from '../utils.js'; - -/** 根据文件名返回合适的图标 */ -function getFileIcon(filename) { - const name = filename.toLowerCase(); - if (name === 'dockerfile') return '🐳'; - if (name === 'makefile' || name === 'gnumakefile') return '🔨'; - const ext = filename.split('.').pop().toLowerCase(); - const map = { - py: '🐍', pyw: '🐍', pyi: '🐍', - js: '💛', mjs: '💛', cjs: '💛', - ts: '🔷', tsx: '⚛️', jsx: '⚛️', - java: '☕', go: '🐹', rs: '🦀', rb: '💎', - php: '🐘', swift: '🍎', kt: '🤖', scala: '🔴', - lua: '🌙', pl: '🐪', r: '📐', m: '📐', - cs: '🟪', fs: '🔵', vb: '🔷', - ex: '💧', exs: '💧', erl: '📞', hs: '🟣', - clj: '🟢', lisp: '🟢', - cpp: '⚙️', cc: '⚙️', cxx: '⚙️', hpp: '⚙️', - c: '⚙️', h: '⚙️', - sh: '🐚', bash: '🐚', zsh: '🐚', fish: '🐚', - html: '🌐', htm: '🌐', css: '🎨', svg: '🎨', - json: '📋', yaml: '📋', yml: '📋', toml: '📋', - ini: '📋', cfg: '📋', conf: '📋', - xml: '📰', sql: '🗃️', - md: '📝', markdown: '📝', rst: '📝', txt: '📄', - log: '📜', csv: '📊', tsv: '📊', - diff: '🔀', patch: '🔀', - }; - return map[ext] || '📄'; -} - -let chatAreaEl, messagesContainerEl, emptyStateEl, scrollBtnEl; -let autoScroll = true; // 是否自动滚动到底部 -let currentPlaceholder = null; // 当前流式消息的 placeholder 引用 - -export function initChatArea() { - chatAreaEl = document.querySelector('#chatArea'); - messagesContainerEl = document.querySelector('#messagesContainer'); - emptyStateEl = document.querySelector('#emptyState'); - scrollBtnEl = document.querySelector('#scrollToBottom'); - - // 监听用户手动滚动 - chatAreaEl.addEventListener('scroll', () => { - const atBottom = isAtBottom(); - if (atBottom) { - autoScroll = true; - scrollBtnEl.style.display = 'none'; - } else { - autoScroll = false; - scrollBtnEl.style.display = ''; - } - }); - - // 点击滚动到底部按钮 - scrollBtnEl.addEventListener('click', () => { - autoScroll = true; - scrollBtnEl.style.display = 'none'; - chatAreaEl.scrollTo({ top: chatAreaEl.scrollHeight, behavior: 'smooth' }); - }); -} - -/** 判断是否在底部附近(60px 容差) */ -function isAtBottom() { - return chatAreaEl.scrollHeight - chatAreaEl.scrollTop - chatAreaEl.clientHeight < 60; -} - -/** 安全地将 Markdown 转为 HTML */ -export function safeMarkdown(text) { - if (!text && text !== 0) return ''; - const str = typeof text === 'string' ? text : String(text); - if (!str) return ''; - try { - const result = marked.parse(str); - return typeof result === 'string' ? result : escapeHtml(str); - } catch (e) { - return escapeHtml(str); - } -} - -/** 滚动到底部(仅当 autoScroll 开启时) */ -export function scrollToBottom() { - if (!autoScroll) return; - requestAnimationFrame(() => { - chatAreaEl.scrollTop = chatAreaEl.scrollHeight; - }); -} - -/** 渲染所有消息(增量渲染) */ -export function renderMessages() { - const currentSession = state.get(KEYS.CURRENT_SESSION); - const msgs = currentSession ? currentSession.messages : []; - - if (msgs.length === 0) { - emptyStateEl.style.display = ''; - messagesContainerEl.style.display = 'none'; - return; - } - - emptyStateEl.style.display = 'none'; - messagesContainerEl.style.display = ''; - - const existingCount = messagesContainerEl.children.length; - const isNewMessage = existingCount > 0 && existingCount < msgs.length; - for (let i = existingCount; i < msgs.length; i++) { - appendMessageDOM(msgs[i], i); - } - - // 新消息按 autoScroll 决定;首次渲染(加载历史)强制滚到底部 - if (isNewMessage) { - scrollToBottom(); - } else if (msgs.length > 0) { - chatAreaEl.scrollTop = chatAreaEl.scrollHeight; - } -} - -/** 追加单条消息 DOM */ -export function appendMessageDOM(msg, index) { - const div = document.createElement('div'); - div.className = `message ${msg.role}`; - div.dataset.index = index; - - const avatar = msg.role === 'user' ? '👤' : '🤖'; - const roleLabel = msg.role === 'user' ? '你' : 'AI'; - const modelTag = (msg.role === 'assistant' && msg.model) - ? `${escapeHtml(msg.model)}` - : ''; - - let contentHtml = ''; - const safeContent = (msg.content != null) ? String(msg.content) : ''; - - if (msg.role === 'assistant') { - // 思考块 - if (msg.think) { - contentHtml += ` -
-
- 🤔 思考过程 - -
-
-
${escapeHtml(msg.think)}
-
-
`; - } - if (safeContent) { - contentHtml += `
${safeMarkdown(safeContent)}
`; - } else { - contentHtml += `
`; - } - } else { - contentHtml += `
${escapeHtml(safeContent)}
`; - } - - // 图片 - if (msg.images && msg.images.length > 0) { - contentHtml += '
'; - for (const img of msg.images) { - contentHtml += `用户图片`; - } - contentHtml += '
'; - } - - // 文件 chip(展示用,不暴露文件内容) - if (msg.files && msg.files.length > 0) { - contentHtml += '
'; - for (const f of msg.files) { - const icon = getFileIcon(f.name); - contentHtml += `
${icon}${escapeHtml(f.name)}
`; - } - contentHtml += '
'; - } - - // 纯图片/文件消息隐藏空内容 - if (msg.role === 'user' && (!msg.content || msg.content.trim() === '') && (msg.images?.length > 0 || msg.files?.length > 0)) { - contentHtml = contentHtml.replace('
', ''); - } else if (msg.role === 'user' && msg.files?.length > 0 && msg.content) { - // 有文本也有文件,保留文本内容展示 - } - - // 统计信息 - const stats = []; - if (msg.timestamp) stats.push(formatTime(msg.timestamp)); - if (msg.eval_count) stats.push(`${msg.eval_count} tokens`); - if (msg.total_duration) stats.push(`${(msg.total_duration / 1e9).toFixed(1)}s`); - if (stats.length > 0) { - contentHtml += `
${stats.map(s => `${s}`).join(' · ')}
`; - } - - // RAG 知识库来源 - if (msg.role === 'assistant' && msg.ragSources && msg.ragSources.length > 0) { - const sourcesHtml = msg.ragSources.map((s, i) => { - const scorePercent = s.score ? `${(s.score * 100).toFixed(0)}%` : ''; - return `
- 📄 来源 ${i + 1}: ${escapeHtml(s.filename)} - ${scorePercent ? `${scorePercent}` : ''} -
`; - }).join(''); - contentHtml += `
-
🧠 基于知识库回答
- ${sourcesHtml} -
`; - } - - div.innerHTML = ` -
${avatar}
-
-
${roleLabel}${modelTag}
- ${contentHtml} -
- `; - - messagesContainerEl.appendChild(div); -} - -/** 更新最后一条 assistant 消息(流式输出) */ -export function updateLastAssistantMessage(content, think, stats, model) { - // 使用 placeholder 引用,完全不依赖 CSS 选择器 - const lastMsg = currentPlaceholder; - if (!lastMsg) return; - - // 仅首次调用时移除 loading 指示器 - if (lastMsg.classList.contains('loading')) { - lastMsg.classList.remove('loading'); - const loadingDots = lastMsg.querySelector('.loading-dots'); - const loadingText = lastMsg.querySelector('.loading-text'); - if (loadingDots) loadingDots.remove(); - if (loadingText) loadingText.remove(); - } - - if (model) { - let modelTag = lastMsg.querySelector('.model-tag'); - if (!modelTag) { - modelTag = document.createElement('span'); - modelTag.className = 'model-tag'; - const roleEl = lastMsg.querySelector('.msg-role'); - if (roleEl) roleEl.appendChild(modelTag); - } - modelTag.textContent = model; - } - - const contentDiv = lastMsg.querySelector('.msg-content'); - const safeContent = (content != null) ? String(content) : ''; - if (contentDiv && safeContent) { - contentDiv.innerHTML = safeMarkdown(safeContent); - } - - // 思考块 - let thinkBlock = lastMsg.querySelector('.think-block'); - if (think) { - if (!thinkBlock) { - const msgBody = lastMsg.querySelector('.msg-body'); - thinkBlock = document.createElement('div'); - thinkBlock.className = 'think-block'; - thinkBlock.innerHTML = ` -
- 🤔 思考过程 - -
-
- `; - msgBody.insertBefore(thinkBlock, contentDiv); - } - thinkBlock.querySelector('pre').textContent = think; - } - - if (stats) { - let statsDiv = lastMsg.querySelector('.msg-stats'); - if (!statsDiv) { - statsDiv = document.createElement('div'); - statsDiv.className = 'msg-stats'; - lastMsg.querySelector('.msg-body').appendChild(statsDiv); - } - const parts = []; - if (stats.eval_count) parts.push(`${stats.eval_count} tokens`); - if (stats.total_duration) parts.push(`${(stats.total_duration / 1e9).toFixed(1)}s`); - if (parts.length) statsDiv.innerHTML = parts.join(' · '); - } - - scrollToBottom(); -} - -/** 追加临时的 assistant 消息占位 */ -export function appendAssistantPlaceholder() { - const div = document.createElement('div'); - div.className = 'message assistant loading'; - div.innerHTML = ` -
🤖
-
-
AI
-
-
- -
- 正在思考... -
-
- `; - messagesContainerEl.appendChild(div); - currentPlaceholder = div; - scrollToBottom(); -} - -/** 追加系统提示消息 */ -export function appendSystemMessage(text, tempClass) { - const div = document.createElement('div'); - div.style.cssText = 'text-align:center;padding:8px;font-size:12px;color:var(--text-muted);'; - if (tempClass) div.classList.add(tempClass); - div.textContent = text; - emptyStateEl.style.display = 'none'; - messagesContainerEl.style.display = ''; - messagesContainerEl.appendChild(div); - scrollToBottom(); -} - -/** 获取消息容器 */ -export function getMessagesContainer() { - return messagesContainerEl; -} - -/** 清空消息 DOM */ -export function clearMessages() { - messagesContainerEl.innerHTML = ''; - currentPlaceholder = null; -} - -/** 重置自动滚动(用户发送消息、新建会话时调用) */ -export function enableAutoScroll() { - autoScroll = true; - if (scrollBtnEl) scrollBtnEl.style.display = 'none'; -} - -// ── 导出功能 ── - -/** 导出为 Markdown */ -export function exportAsMarkdown(session) { - let md = `# ${session.title}\n\n`; - md += `**模型:** ${session.model} \n`; - md += `**时间:** ${formatTime(session.createdAt)}\n\n---\n\n`; - - session.messages.forEach(m => { - const role = m.role === 'user' ? '👤 用户' : '🤖 AI'; - md += `### ${role}\n\n${m.content || ''}\n\n`; - if (m.files && m.files.length > 0) { - md += m.files.map(f => `📎 \`${f.name}\``).join(' · ') + '\n\n'; - } - if (m.think) md += `> **思考:** ${m.think}\n\n`; - }); - - downloadFile(`${session.title}.md`, md, 'text/markdown'); -} - -/** 导出为 HTML */ -export function exportAsHtml(session) { - let html = `${escapeHtml(session.title)} - -

${escapeHtml(session.title)}

${formatTime(session.createdAt)} · ${session.model}


`; - - session.messages.forEach(m => { - const fileChips = (m.files && m.files.length > 0) - ? '
' + m.files.map(f => `📎 ${escapeHtml(f.name)}`).join(' ') - : ''; - html += `
${m.role === 'user' ? '👤 用户' : '🤖 AI'}${fileChips}
${(m.content || '').replace(/\n/g, '
')}
`; - }); - - html += ''; - downloadFile(`${session.title}.html`, html, 'text/html'); -} - -/** 导出为 TXT */ -export function exportAsTxt(session) { - let txt = `${session.title}\n模型: ${session.model}\n时间: ${formatTime(session.createdAt)}\n${'='.repeat(50)}\n\n`; - - session.messages.forEach(m => { - txt += `[${m.role === 'user' ? '用户' : 'AI'}]\n`; - if (m.files && m.files.length > 0) { - txt += m.files.map(f => `📎 ${f.name}`).join(' · ') + '\n'; - } - txt += `${m.content || ''}\n\n`; - }); - - downloadFile(`${session.title}.txt`, txt, 'text/plain'); -} diff --git a/js/components/header.js b/js/components/header.js deleted file mode 100644 index d4da936..0000000 --- a/js/components/header.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Header - 顶部导航组件 - * 包含连接状态检测、新建会话按钮 - */ - -import { state, KEYS } from '../state.js'; -import { formatSize } from '../utils.js'; -import { OllamaAPI } from '../ollama-api.js'; - -let connStatusEl; - -export function initHeader() { - connStatusEl = document.querySelector('#connStatus'); -} - -/** - * 检测连接状态并更新 UI - * @returns {Promise<{ok: boolean, version?: object, error?: Error}>} - */ -export async function checkConnection() { - const api = state.get(KEYS.API); - if (!api) return { ok: false, error: new Error('API 未初始化') }; - - connStatusEl.className = 'conn-status pending'; - connStatusEl.querySelector('.status-label').textContent = '连接中...'; - - try { - const version = await api.getVersion(); - connStatusEl.className = 'conn-status connected'; - connStatusEl.title = `已连接 (v${version.version})`; - connStatusEl.querySelector('.status-label').textContent = '已连接'; - return { ok: true, version }; - } catch (err) { - connStatusEl.className = 'conn-status disconnected'; - connStatusEl.title = '未连接'; - connStatusEl.querySelector('.status-label').textContent = '未连接'; - return { ok: false, error: err }; - } -} - -/** - * 更新设置面板中的连接信息 - */ -export async function updateConnectionInfo() { - const api = state.get(KEYS.API); - if (!api) return; - - const badge = document.querySelector('#connBadge'); - const verEl = document.querySelector('#serverVersion'); - const corsHint = document.querySelector('#corsHint'); - - badge.textContent = '检测中...'; - badge.className = 'status-badge warning'; - verEl.textContent = ''; - - try { - const version = await api.getVersion(); - badge.textContent = '✓ 已连接'; - badge.className = 'status-badge success'; - verEl.textContent = `v${version.version}`; - corsHint.style.display = 'none'; - } catch (err) { - badge.textContent = '✗ 未连接'; - badge.className = 'status-badge error'; - verEl.textContent = ''; - - if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError') || err.message.includes('CORS')) { - corsHint.style.display = ''; - } else { - corsHint.style.display = 'none'; - } - } -} - -/** - * 更新运行中的模型列表 - */ -export async function updateRunningModels() { - const api = state.get(KEYS.API); - if (!api) return; - - const container = document.querySelector('#runningModels'); - try { - const data = await api.psModels(); - if (!data.models || data.models.length === 0) { - container.innerHTML = '

无运行中的模型

'; - return; - } - container.innerHTML = data.models.map(m => ` -
- ${m.name} - ${formatSize(m.size_vram || 0)} VRAM -
- `).join(''); - } catch { - container.innerHTML = '

无法获取运行信息

'; - } -} diff --git a/js/components/history-modal.js b/js/components/history-modal.js deleted file mode 100644 index eb8d457..0000000 --- a/js/components/history-modal.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * HistoryModal - 历史记录面板组件 - */ - -import { state, KEYS } from '../state.js'; -import { debounce, escapeHtml, formatTime } from '../utils.js'; -import { exportAsMarkdown, exportAsHtml, exportAsTxt, renderMessages, clearMessages } from './chat-area.js'; - -const HISTORY_PAGE_SIZE = 10; - -let historyModalEl, historyListEl, historyPaginationEl; -let historyPage = 1; -let historySearchQuery = ''; - -export function initHistoryModal() { - historyModalEl = document.querySelector('#historyModal'); - historyListEl = document.querySelector('#historyList'); - historyPaginationEl = document.querySelector('#historyPagination'); - - // 打开/关闭 - document.querySelector('#btnHistory').addEventListener('click', openHistoryModal); - document.querySelector('#btnCloseHistory').addEventListener('click', closeHistoryModal); - historyModalEl.addEventListener('click', (e) => { - if (e.target === historyModalEl) closeHistoryModal(); - }); - - // 退出历史只读模式 - document.querySelector('#btnExitHistory').addEventListener('click', () => { - document.querySelector('#btnNewChat').click(); - }); - - // 搜索 - const historySearchInput = document.querySelector('#historySearchInput'); - const historySearchClear = document.querySelector('#historySearchClear'); - const doHistorySearch = debounce(() => { - historySearchQuery = historySearchInput.value.trim(); - historySearchClear.style.display = historySearchQuery ? '' : 'none'; - historyPage = 1; - loadHistory(); - }, 250); - historySearchInput.addEventListener('input', doHistorySearch); - historySearchClear.addEventListener('click', () => { - historySearchInput.value = ''; - historySearchQuery = ''; - historySearchClear.style.display = 'none'; - historyPage = 1; - loadHistory(); - }); - - // 历史列表事件委托 - historyListEl.addEventListener('click', async (e) => { - const target = e.target.closest('[data-id]'); - if (!target) return; - const sessionId = target.dataset.id; - const db = state.get(KEYS.DB); - - if (target.classList.contains('btn-delete-session')) { - if (confirm('确定删除此会话?')) { - await db.deleteSession(sessionId); - const currentSession = state.get(KEYS.CURRENT_SESSION); - if (currentSession && currentSession.id === sessionId) { - document.querySelector('#btnNewChat').click(); - } - loadHistory(); - } - } else if (target.classList.contains('btn-export-md')) { - const session = await db.getSession(sessionId); - if (session) exportAsMarkdown(session); - } else if (target.classList.contains('btn-export-html')) { - const session = await db.getSession(sessionId); - if (session) exportAsHtml(session); - } else if (target.classList.contains('btn-export-txt')) { - const session = await db.getSession(sessionId); - if (session) exportAsTxt(session); - } else if (target.classList.contains('history-info') || target.classList.contains('history-item')) { - loadHistorySession(sessionId); - } - }); - - // 分页事件 - historyPaginationEl.addEventListener('click', (e) => { - const btn = e.target.closest('.page-btn'); - if (!btn || btn.classList.contains('disabled')) return; - const page = parseInt(btn.dataset.page); - if (page && page !== historyPage) { - historyPage = page; - loadHistory(); - historyListEl.scrollTop = 0; - } - }); -} - -export async function loadHistory() { - const db = state.get(KEYS.DB); - if (!db) return; - - let allSessions = await db.getAllSessions(); - - if (historySearchQuery) { - const q = historySearchQuery.toLowerCase(); - allSessions = allSessions.filter(s => { - if (s.title?.toLowerCase().includes(q)) return true; - if (s.model?.toLowerCase().includes(q)) return true; - return s.messages.some(m => m.content?.toLowerCase().includes(q)); - }); - } - - allSessions.sort((a, b) => b.updatedAt - a.updatedAt); - - if (allSessions.length === 0) { - historyListEl.innerHTML = ` -
- - - -

暂无历史记录

-
`; - historyPaginationEl.innerHTML = ''; - return; - } - - const totalPages = Math.ceil(allSessions.length / HISTORY_PAGE_SIZE); - if (historyPage > totalPages) historyPage = totalPages; - - const start = (historyPage - 1) * HISTORY_PAGE_SIZE; - const pageSessions = allSessions.slice(start, start + HISTORY_PAGE_SIZE); - - historyListEl.innerHTML = pageSessions.map(s => ` -
-
-
${escapeHtml(s.title)}
-
- ${formatTime(s.updatedAt)} - ${s.messages.length} 条消息 - ${s.model || '无模型'} -
-
-
- - - - -
-
- `).join(''); - - // 分页 - if (totalPages <= 1) { - historyPaginationEl.innerHTML = `共 ${allSessions.length} 条`; - return; - } - - let html = `共 ${allSessions.length} 条`; - html += '
'; - html += ``; - - const range = 2; - let pages = []; - for (let i = 1; i <= totalPages; i++) { - if (i === 1 || i === totalPages || (i >= historyPage - range && i <= historyPage + range)) { - pages.push(i); - } - } - let prev = 0; - for (const p of pages) { - if (p - prev > 1) html += ''; - html += ``; - prev = p; - } - - html += ``; - html += '
'; - historyPaginationEl.innerHTML = html; -} - -async function loadHistorySession(sessionId) { - const db = state.get(KEYS.DB); - if (!db) return; - - const session = await db.getSession(sessionId); - if (!session) return; - - state.set(KEYS.CURRENT_SESSION, session); - state.set(KEYS.IS_HISTORY_VIEW, true); - - document.querySelector('#historyBar').style.display = ''; - document.querySelector('#inputArea').style.display = 'none'; - - clearMessages(); - renderMessages(); - closeHistoryModal(); -} - -export function openHistoryModal() { - historyPage = 1; - historySearchQuery = ''; - document.querySelector('#historySearchInput').value = ''; - document.querySelector('#historySearchClear').style.display = 'none'; - loadHistory(); - historyModalEl.style.display = ''; -} - -export function closeHistoryModal() { - historyModalEl.style.display = 'none'; -} diff --git a/js/components/input-area.js b/js/components/input-area.js deleted file mode 100644 index 857b573..0000000 --- a/js/components/input-area.js +++ /dev/null @@ -1,650 +0,0 @@ -/** - * InputArea - 输入区域组件 - * 包含文本输入、图片上传、文件上传、发送逻辑 - */ - -import { state, KEYS } from '../state.js'; -import { fileToBase64, fileToText, detectLanguage, debounce, truncate, escapeHtml, formatSize } from '../utils.js'; -import { getSelectedModel, isThinkEnabled, isVisionAvailable } from './model-bar.js'; -import { - renderMessages, appendAssistantPlaceholder, appendSystemMessage, - updateLastAssistantMessage, clearMessages, safeMarkdown, enableAutoScroll -} from './chat-area.js'; -import { showToast } from './toast.js'; -import { checkConnection } from './header.js'; -import { isRagEnabled, performRagRetrieval } from './kb-modal.js'; - -let chatInputEl, btnSendEl, fileInputEl, textFileInputEl, imagePreviewEl, filePreviewEl; -let pendingImages = []; -let pendingFiles = []; - -/** 常见文本/代码文件扩展名集合(用于校验) */ -const TEXT_EXTENSIONS = new Set([ - 'txt','md','markdown','rst','log','csv','tsv', - 'py','pyw','pyi','js','mjs','cjs','ts','tsx','jsx', - 'java','c','h','cpp','cc','cxx','hpp','go','rs','rb','php', - 'sh','bash','zsh','fish','sql','html','htm','css', - 'json','jsonl','xml','svg','yaml','yml','toml','ini','cfg','conf', - 'swift','kt','kts','scala','lua','r','m','mm', - 'cs','fs','vb','ex','exs','erl','hs','clj','lisp', - 'diff','patch','pl','dockerfile','makefile' -]); - -/** 最大文件大小 500KB */ -const MAX_FILE_SIZE = 500 * 1024; - -/** 根据文件名返回图标 */ -function getFileIcon(filename) { - const name = filename.toLowerCase(); - if (name === 'dockerfile') return '🐳'; - if (name === 'makefile' || name === 'gnumakefile') return '🔨'; - const ext = filename.split('.').pop().toLowerCase(); - const map = { - py: '🐍', pyw: '🐍', pyi: '🐍', - js: '💛', mjs: '💛', cjs: '💛', - ts: '🔷', tsx: '⚛️', jsx: '⚛️', - java: '☕', go: '🐹', rs: '🦀', rb: '💎', - php: '🐘', swift: '🍎', kt: '🤖', scala: '🔴', - lua: '🌙', pl: '🐪', r: '📐', m: '📐', - cs: '🟪', fs: '🔵', vb: '🔷', - ex: '💧', exs: '💧', erl: '📞', hs: '🟣', - clj: '🟢', lisp: '🟢', - cpp: '⚙️', cc: '⚙️', cxx: '⚙️', hpp: '⚙️', - c: '⚙️', h: '⚙️', - sh: '🐚', bash: '🐚', zsh: '🐚', fish: '🐚', - html: '🌐', htm: '🌐', css: '🎨', svg: '🎨', - json: '📋', yaml: '📋', yml: '📋', toml: '📋', - ini: '📋', cfg: '📋', conf: '📋', - xml: '📰', sql: '🗃️', - md: '📝', markdown: '📝', rst: '📝', txt: '📄', - log: '📜', csv: '📊', tsv: '📊', - diff: '🔀', patch: '🔀', - }; - return map[ext] || '📄'; -} - -/** 判断是否为文本/代码文件 */ -function isTextFile(file) { - // 无扩展名的特殊文件名 - const name = file.name.toLowerCase(); - if (name === 'dockerfile' || name === 'makefile') return true; - const ext = name.split('.').pop(); - return TEXT_EXTENSIONS.has(ext); -} - -export function initInputArea() { - chatInputEl = document.querySelector('#chatInput'); - btnSendEl = document.querySelector('#btnSend'); - fileInputEl = document.querySelector('#fileInput'); - textFileInputEl = document.querySelector('#textFileInput'); - imagePreviewEl = document.querySelector('#imagePreview'); - filePreviewEl = document.querySelector('#filePreview'); - - // 发送 / 停止 - btnSendEl.addEventListener('click', () => { - if (state.get(KEYS.IS_STREAMING)) { - stopGeneration(); - } else { - sendMessage(); - } - }); - chatInputEl.addEventListener('keydown', (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - sendMessage(); - } - }); - - // 输入框自动调整 - chatInputEl.addEventListener('input', autoResizeTextarea); - - // 图片上传 - document.querySelector('#btnAttachImg').addEventListener('click', () => { - if (!isVisionAvailable()) { - showToast('当前模型不支持图片分析', 'warning'); - return; - } - fileInputEl.click(); - }); - fileInputEl.addEventListener('change', (e) => { - handleImageSelect(e.target.files); - e.target.value = ''; - }); - - // 文件上传(文本/代码) - document.querySelector('#btnAttachFile').addEventListener('click', () => textFileInputEl.click()); - textFileInputEl.addEventListener('change', (e) => { - handleTextFileSelect(e.target.files); - e.target.value = ''; - }); - - // 删除图片预览 - imagePreviewEl.addEventListener('click', (e) => { - if (e.target.classList.contains('remove-img')) { - const idx = parseInt(e.target.dataset.index); - pendingImages.splice(idx, 1); - renderImagePreviews(); - } - }); - - // 删除文件预览 - filePreviewEl.addEventListener('click', (e) => { - if (e.target.classList.contains('remove-file')) { - const idx = parseInt(e.target.dataset.index); - pendingFiles.splice(idx, 1); - renderFilePreviews(); - } - }); -} - -function autoResizeTextarea() { - chatInputEl.style.height = 'auto'; - chatInputEl.style.height = Math.min(chatInputEl.scrollHeight, 120) + 'px'; -} - -// ── 图片处理 ── - -async function handleImageSelect(files) { - const maxSize = 20 * 1024 * 1024; - for (const file of files) { - if (!file.type.startsWith('image/')) continue; - if (file.size > maxSize) { - appendSystemMessage(`⚠️ 图片 ${file.name} 超过 20MB 限制`); - continue; - } - try { - const base64 = await fileToBase64(file); - pendingImages.push({ name: file.name, base64 }); - renderImagePreviews(); - } catch (err) { - console.error('[InputArea] 图片读取失败:', err); - } - } -} - -function renderImagePreviews() { - if (pendingImages.length === 0) { - imagePreviewEl.style.display = 'none'; - imagePreviewEl.innerHTML = ''; - return; - } - imagePreviewEl.style.display = ''; - imagePreviewEl.innerHTML = pendingImages.map((img, i) => ` -
- ${escapeHtml(img.name)} - -
- `).join(''); -} - -// ── 文件处理(文本/代码) ── - -async function handleTextFileSelect(files) { - for (const file of files) { - if (!isTextFile(file)) { - appendSystemMessage(`⚠️ ${file.name} 不是支持的文本/代码文件`); - continue; - } - if (file.size > MAX_FILE_SIZE) { - appendSystemMessage(`⚠️ ${file.name} 超过 500KB 限制(${formatSize(file.size)})`); - continue; - } - // 重复检测 - if (pendingFiles.some(f => f.name === file.name && f.size === file.size)) { - appendSystemMessage(`ℹ️ ${file.name} 已添加`); - continue; - } - try { - const content = await fileToText(file); - pendingFiles.push({ - name: file.name, - content, - language: detectLanguage(file.name), - size: file.size - }); - renderFilePreviews(); - } catch (err) { - console.error('[InputArea] 文件读取失败:', err); - appendSystemMessage(`❌ 读取 ${file.name} 失败`); - } - } -} - -function renderFilePreviews() { - if (pendingFiles.length === 0) { - filePreviewEl.style.display = 'none'; - filePreviewEl.innerHTML = ''; - return; - } - filePreviewEl.style.display = ''; - filePreviewEl.innerHTML = pendingFiles.map((f, i) => ` -
- ${getFileIcon(f.name)} - ${escapeHtml(f.name)} - ${formatSize(f.size)} - -
- `).join(''); -} - -// ── UI 状态 ── - -function updateSendButton(streaming) { - if (streaming) { - btnSendEl.innerHTML = ` - - `; - btnSendEl.classList.add('stop-btn'); - btnSendEl.classList.remove('disabled'); - btnSendEl.title = '停止生成'; - } else { - btnSendEl.innerHTML = ` - - `; - btnSendEl.classList.remove('stop-btn'); - btnSendEl.classList.remove('disabled'); - btnSendEl.title = '发送'; - } -} - -/** - * 停止生成 - */ -function stopGeneration() { - const abortController = state.get(KEYS.ABORT_CONTROLLER); - if (abortController) { - abortController.abort(); - } -} - -/** - * 核心发送逻辑 - */ - -/** 构建发送给 Ollama API 的消息数组:将文件内容拼入 content */ -function buildApiMessages(messages) { - return messages.map(m => { - let content = m.content || ''; - // 如果有文件内容,拼接到 API 消息的 content 中 - if (m._fileContents && m._fileContents.length > 0) { - const fileParts = m._fileContents.map(f => { - const lang = f.language || ''; - return `📄 以下是一个文件内容:\n\n\`\`\`${lang}\n${f.content}\n\`\`\``; - }); - if (content) { - content += '\n\n---\n' + fileParts.join('\n\n---\n'); - } else { - const count = m._fileContents.length; - content = `请分析以下 ${count > 1 ? count + ' 个' : ''}文件:\n\n${fileParts.join('\n\n---\n')}`; - } - } - return { - role: m.role, - content, - ...(m.images && { images: m.images }) - }; - }); -} - -export async function sendMessage() { - const text = chatInputEl.value.trim(); - if (!text && pendingImages.length === 0 && pendingFiles.length === 0) return; - if (state.get(KEYS.IS_STREAMING)) return; - - const model = getSelectedModel(); - if (!model) { - appendSystemMessage('⚠️ 请先选择一个模型'); - return; - } - - // 有图片但模型不支持 vision → 阻止发送 - if (pendingImages.length > 0 && !isVisionAvailable()) { - appendSystemMessage('⚠️ 当前模型不支持图片分析,请移除图片后重试'); - return; - } - - const currentSession = state.get(KEYS.CURRENT_SESSION); - if (!currentSession) return; - - const now = Date.now(); - const msgsToAdd = []; - - // ── 构造用户消息:展示用(content=纯文本,files=元数据) ── - const userFiles = pendingFiles.map(f => ({ - name: f.name, - language: f.language, - size: f.size - })); - - // 场景1:有图片 - if (pendingImages.length > 0) { - msgsToAdd.push({ - role: 'user', - content: pendingImages.length === 1 - ? `[上传了图片: ${pendingImages[0].name}]` - : `[上传了 ${pendingImages.length} 张图片]`, - images: pendingImages.map(img => img.base64), - timestamp: now - }); - } - - // 场景2:有文本输入 + 文件,或者只有文件 - if (text || pendingFiles.length > 0) { - const msg = { - role: 'user', - content: text || '', - timestamp: now - }; - if (userFiles.length > 0) { - msg.files = userFiles; - msg._fileContents = pendingFiles.map(f => ({ - language: f.language, - content: f.content - })); - } - msgsToAdd.push(msg); - } - - // 更新会话标题 + 追加消息(通过 update 确保引用变化,触发浅比较) - const isFirstMsg = currentSession.messages.length === 0; - state.update(KEYS.CURRENT_SESSION, session => ({ - ...session, - ...(isFirstMsg && { - title: truncate( - text || (pendingFiles.length > 0 ? `[文件: ${pendingFiles.map(f => f.name).join(', ')}]` : '[图片消息]'), - 30 - ), - model - }), - messages: [...session.messages, ...msgsToAdd], - updatedAt: Date.now() - })); - - // update() 产生新引用,后续必须从 state 读取最新值 - const freshSession = state.get(KEYS.CURRENT_SESSION); - - enableAutoScroll(); - renderMessages(); - - // 立即保存 - await saveCurrentSession(); - - // 清空输入 - chatInputEl.value = ''; - pendingImages = []; - pendingFiles = []; - imagePreviewEl.style.display = 'none'; - imagePreviewEl.innerHTML = ''; - filePreviewEl.style.display = 'none'; - filePreviewEl.innerHTML = ''; - autoResizeTextarea(); - - // 助手占位 - appendAssistantPlaceholder(); - - // 流式回复 - state.set(KEYS.IS_STREAMING, true); - updateSendButton(true); - - const api = state.get(KEYS.API); - const abortController = new AbortController(); - state.set(KEYS.ABORT_CONTROLLER, abortController); - const thinkMode = isThinkEnabled(); - const systemPromptEnabled = state.get(KEYS.SYSTEM_PROMPT_ENABLED); - const systemPrompt = state.get(KEYS.SYSTEM_PROMPT); - const numCtx = state.get(KEYS.NUM_CTX, 24576); - - let assistantContent = ''; - let thinkContent = ''; - let finalStats = null; - let modelName = ''; - - try { - const apiMessages = buildApiMessages(freshSession.messages); - - const temperature = state.get('temperature', 0.7); - - const chatParams = { - model, - messages: apiMessages, - think: thinkMode, - ...(systemPromptEnabled && systemPrompt && { system: systemPrompt }), - ...(numCtx && { options: { num_ctx: numCtx, temperature } }) - }; - - // RAG 检索增强 - let ragSources = null; - if (isRagEnabled()) { - appendSystemMessage('🧠 正在检索知识库...', 'rag-status'); - try { - const ragResult = await performRagRetrieval(text); - if (ragResult && ragResult.results && ragResult.results.length > 0) { - // 计算 RAG 上下文可用预算 - const ctxLimit = numCtx || 24576; - const outputReserve = 2048; // 预留给模型输出 - // 估算已有 token 数(粗略:1 token ≈ 2 字符) - const msgChars = apiMessages.reduce((sum, m) => sum + (m.content?.length || 0), 0); - const systemChars = (chatParams.system?.length || 0); - const usedTokens = Math.ceil((msgChars + systemChars) / 2); - const ragBudget = Math.max(0, ctxLimit - outputReserve - usedTokens); - const ragBudgetChars = ragBudget * 2; // 转换为字符数 - - // 构建 RAG prompt,按预算截取上下文 - let ragContext = ragResult.results.map((r, i) => - `[来源 ${i + 1}: ${r.filename}]\n${r.text}` - ).join('\n\n---\n\n'); - - const ragTemplate = `你是一个知识库问答助手。以下是检索到的相关文档片段,请基于这些内容回答用户的问题。如果检索到的内容无法回答问题,请如实说明。 - -=== 检索到的相关内容 === - -=== 内容结束 === - -请基于以上内容回答用户的问题。引用时请注明来源编号(如"来源 1")。`; - const templateOverhead = ragTemplate.length; // 模板本身占用的字符数 - - if (ragContext.length + templateOverhead > ragBudgetChars) { - ragContext = ragContext.slice(0, Math.max(0, ragBudgetChars - templateOverhead - 50)) + '\n\n[知识库内容过长,已截取最相关的部分]'; - appendSystemMessage(`⚠️ 知识库内容已截取(预算 ${ragBudgetChars} 字符)`, 'rag-status'); - } - - const ragPrompt = `你是一个知识库问答助手。以下是检索到的相关文档片段,请基于这些内容回答用户的问题。如果检索到的内容无法回答问题,请如实说明。 - -=== 检索到的相关内容 === -${ragContext} -=== 内容结束 === - -请基于以上内容回答用户的问题。引用时请注明来源编号(如"来源 1")。`; - - chatParams.system = chatParams.system - ? chatParams.system + '\n\n' + ragPrompt - : ragPrompt; - - ragSources = ragResult.results.map(r => ({ - filename: r.filename, - score: r.score, - text: truncate(r.text, 100) - })); - const sourceNames = [...new Set(ragSources.map(s => s.filename))]; - appendSystemMessage(`🧠 已检索 ${ragSources.length} 个相关片段(来源:${sourceNames.join('、')})`, 'rag-status'); - } else { - appendSystemMessage('🧠 知识库未检索到相关内容,使用通用知识回答', 'rag-status'); - } - } catch (ragErr) { - console.error('[RAG] 检索失败:', ragErr); - appendSystemMessage(`🧠 知识库检索失败: ${ragErr.message}`, 'rag-status'); - } - } - - console.log('[RAG] numCtx:', numCtx, 'system长度:', chatParams.system?.length || 0); - console.log('[RAG] 开始调用 chatStream...'); - - let ragStatusCleared = false; - - await api.chatStream(chatParams, (chunk) => { - // 首个有效内容到达时才清除 RAG 状态,避免检索完到开始回复之间的空白 - if (!ragStatusCleared && chunk.message?.content) { - ragStatusCleared = true; - document.querySelectorAll('.rag-status').forEach(el => el.remove()); - } - if (chunk.message) { - if (chunk.message.content) { - assistantContent += chunk.message.content; - } - const think = chunk.message.thinking || chunk.message.reasoning_content; - if (think && typeof think === 'string') { - thinkContent += think; - } - } - if (chunk.model) modelName = chunk.model; - updateLastAssistantMessage(assistantContent, thinkContent || null, null, modelName || null); - if (chunk.done) { - finalStats = { eval_count: chunk.eval_count, total_duration: chunk.total_duration }; - } - }, abortController); - - // 如果流结束但没有任何内容 - if (!assistantContent && !thinkContent) { - appendSystemMessage('⚠️ 模型未返回任何内容,可能是上下文过长或模型不支持当前请求'); - } - - const assistantMsg = { - role: 'assistant', - content: assistantContent || '', - timestamp: Date.now(), - ...(modelName && { model: modelName }), - ...(thinkContent && { think: thinkContent }), - ...(finalStats && { eval_count: finalStats.eval_count, total_duration: finalStats.total_duration }), - ...(ragSources && { ragSources }) - }; - state.update(KEYS.CURRENT_SESSION, session => ({ - ...session, - messages: [...session.messages, assistantMsg], - updatedAt: Date.now() - })); - - if (finalStats) { - updateLastAssistantMessage(assistantContent, thinkContent || null, finalStats, modelName || null); - } - - // 流结束后插入 RAG 来源到现有 DOM(renderMessages 不会重新渲染已有消息) - if (ragSources && ragSources.length > 0) { - const lastAssistant = document.querySelector('#messagesContainer .message.assistant:last-of-type'); - if (lastAssistant) { - const sourcesHtml = ragSources.map((s, i) => { - const scorePercent = s.score ? `${(s.score * 100).toFixed(0)}%` : ''; - return `
📄 来源 ${i + 1}: ${escapeHtml(s.filename)}${scorePercent ? `${scorePercent}` : ''}
`; - }).join(''); - const ragDiv = document.createElement('div'); - ragDiv.className = 'rag-sources'; - ragDiv.innerHTML = `
🧠 基于知识库回答
${sourcesHtml}`; - lastAssistant.querySelector('.msg-body').appendChild(ragDiv); - } - } - - await saveCurrentSession(); - - } catch (err) { - console.error('[InputArea] 流式聊天错误:', err); - - // 清除 RAG 临时状态消息 - document.querySelectorAll('.rag-status').forEach(el => el.remove()); - - if (err.name === 'AbortError') { - const placeholder = document.querySelector('#messagesContainer .message.assistant.loading'); - const loadingDots = placeholder?.querySelector('.loading-dots'); - const loadingText = placeholder?.querySelector('.loading-text'); - if (loadingDots) loadingDots.remove(); - if (loadingText) loadingText.remove(); - placeholder?.classList.remove('loading'); - - const contentDiv = placeholder?.querySelector('.msg-content'); - - const partialMsg = { - role: 'assistant', - content: assistantContent, - timestamp: Date.now(), - ...(modelName && { model: modelName }), - ...(thinkContent && { think: thinkContent }), - stopped: true - }; - state.update(KEYS.CURRENT_SESSION, session => ({ - ...session, - messages: [...session.messages, partialMsg], - updatedAt: Date.now() - })); - - if (contentDiv) { - let html = ''; - if (assistantContent) { - html = safeMarkdown(assistantContent); - } - html += '

[已停止]

'; - contentDiv.innerHTML = html; - } - - appendSystemMessage('⏹ 已停止生成'); - await saveCurrentSession(); - - state.set(KEYS.IS_STREAMING, false); - updateSendButton(false); - state.set(KEYS.ABORT_CONTROLLER, null); - return; - } - - const placeholder = document.querySelector('#messagesContainer .message.assistant.loading'); - const loadingDots = placeholder?.querySelector('.loading-dots'); - const loadingText = placeholder?.querySelector('.loading-text'); - if (loadingDots) loadingDots.remove(); - if (loadingText) loadingText.remove(); - placeholder?.classList.remove('loading'); - - const contentDiv = placeholder?.querySelector('.msg-content'); - - if (assistantContent && contentDiv) { - const partialMsg = { - role: 'assistant', - content: assistantContent + '\n\n`[已中断]`', - timestamp: Date.now(), - }; - state.update(KEYS.CURRENT_SESSION, session => ({ - ...session, - messages: [...session.messages, partialMsg], - updatedAt: Date.now() - })); - contentDiv.innerHTML = safeMarkdown(assistantContent) + '

[已中断]

'; - await saveCurrentSession(); - } else { - if (placeholder) placeholder.remove(); - } - - let errMsg = `❌ 错误: ${err.message}`; - console.error('[InputArea] 完整错误:', err); - if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError')) { - errMsg = '❌ 连接失败。请检查:\n1. Ollama 是否正在运行\n2. 地址是否正确\n3. 是否设置了 OLLAMA_ORIGINS="*"'; - } else if (err.message.includes('400')) { - errMsg = '❌ 请求参数错误,可能是知识库内容超出模型上下文限制'; - } else if (err.message.includes('500')) { - errMsg = '❌ Ollama 服务器错误,请检查 Ollama 日志'; - } - appendSystemMessage(errMsg); - - } finally { - state.set(KEYS.IS_STREAMING, false); - updateSendButton(false); - state.set(KEYS.ABORT_CONTROLLER, null); - } -} - -async function saveCurrentSession() { - const db = state.get(KEYS.DB); - const currentSession = state.get(KEYS.CURRENT_SESSION); - const isHistoryView = state.get(KEYS.IS_HISTORY_VIEW); - if (!currentSession || !db || isHistoryView) return; - currentSession.updatedAt = Date.now(); - try { - await db.saveSession(currentSession); - } catch (err) { - console.error('[InputArea] 保存会话失败:', err); - } -} diff --git a/js/components/kb-modal.js b/js/components/kb-modal.js deleted file mode 100644 index 571494a..0000000 --- a/js/components/kb-modal.js +++ /dev/null @@ -1,346 +0,0 @@ -/** - * KBModal - 知识库管理面板 - * - * 支持:创建/删除集合、上传/删除文档、选择嵌入模型、启用 RAG 检索 - */ - -import { state, KEYS } from '../state.js'; -import { - initVectorStore, getVectorStore, addDocumentToKB, - removeDocumentFromKB, getDocumentsInCollection, retrieveContext, - buildRagSystemPrompt -} from '../rag.js'; -import { fileToText, formatSize, escapeHtml, truncate } from '../utils.js'; -import { showToast } from './toast.js'; - -let kbModalEl; -let currentColId = null; - -/** 当前是否启用 RAG */ -let ragEnabled = false; -let ragCollectionId = null; - -export function initKBModal() { - kbModalEl = document.querySelector('#kbModal'); - - document.querySelector('#btnKB').addEventListener('click', openKBModal); - document.querySelector('#btnCloseKB').addEventListener('click', closeKBModal); - kbModalEl.addEventListener('click', (e) => { - if (e.target === kbModalEl) closeKBModal(); - }); - - // 新建集合 - document.querySelector('#btnNewCollection').addEventListener('click', createCollection); - - // 上传文档 - document.querySelector('#btnUploadDoc').addEventListener('click', () => { - if (!currentColId) { - showToast('请先选择一个知识库集合', 'warning'); - return; - } - document.querySelector('#kbFileInput').click(); - }); - document.querySelector('#kbFileInput').addEventListener('change', handleDocUpload); - - // RAG 开关 - document.querySelector('#toggleRAG').addEventListener('change', (e) => { - ragEnabled = e.target.checked; - if (ragEnabled && currentColId) { - ragCollectionId = currentColId; - showToast('RAG 知识检索已开启', 'success'); - } else if (ragEnabled && !currentColId) { - e.target.checked = false; - ragEnabled = false; - showToast('请先选择一个知识库集合', 'warning'); - return; - } else { - ragCollectionId = null; - showToast('RAG 知识检索已关闭', 'info'); - } - updateRagBadge(); - }); - - // 嵌入模型变更 - document.querySelector('#selectEmbedModel').addEventListener('change', async (e) => { - if (!currentColId) return; - const vs = getVectorStore(); - const col = await vs.getCollection(currentColId); - if (col) { - col.embeddingModel = e.target.value; - await vs.updateCollection(col); - } - }); -} - -// ── RAG 接口 ── - -export function isRagEnabled() { - return ragEnabled && !!ragCollectionId; -} - -export function getRagCollectionId() { - return ragCollectionId; -} - -export async function performRagRetrieval(query) { - if (!isRagEnabled()) return null; - try { - const { context, results } = await retrieveContext(query, ragCollectionId, 5); - if (!context) return null; - return { context, results, ragPrompt: buildRagSystemPrompt(context) }; - } catch (err) { - console.warn('[KB] RAG 检索失败:', err); - return null; - } -} - -// ── UI ── - -function updateRagBadge() { - const badge = document.querySelector('#badgeRAG'); - if (!badge) return; - badge.style.display = ragEnabled ? '' : 'none'; -} - -async function openKBModal() { - kbModalEl.style.display = ''; - await refreshCollections(); - await populateEmbedModels(); -} - -function closeKBModal() { - kbModalEl.style.display = 'none'; -} - -/** - * 刷新集合列表 - */ -async function refreshCollections() { - const vs = await initVectorStore(); - const collections = await vs.getCollections(); - const listEl = document.querySelector('#kbCollectionList'); - - if (collections.length === 0) { - listEl.innerHTML = '

暂无知识库,点击上方「新建集合」创建

'; - currentColId = null; - refreshDocList(); - return; - } - - listEl.innerHTML = collections.map(c => ` -
-
- ${escapeHtml(c.name)} - ${c.docCount || 0} 文档 · ${(c.chunkCount || 0)} 分块 -
- -
- `).join(''); - - // 点击选中集合 - listEl.querySelectorAll('.kb-collection-item').forEach(el => { - el.addEventListener('click', async (e) => { - if (e.target.closest('.kb-col-delete')) return; - currentColId = el.dataset.id; - await refreshCollections(); - await refreshDocList(); - // 设置嵌入模型下拉 - const vs = getVectorStore(); - const col = await vs.getCollection(currentColId); - if (col?.embeddingModel) { - document.querySelector('#selectEmbedModel').value = col.embeddingModel; - } - }); - }); - - // 删除集合 - listEl.querySelectorAll('.kb-col-delete').forEach(btn => { - btn.addEventListener('click', async (e) => { - e.stopPropagation(); - const id = btn.dataset.id; - if (!confirm('确定删除此知识库集合?所有文档将被清除。')) return; - const vs = getVectorStore(); - await vs.deleteCollection(id); - if (currentColId === id) currentColId = null; - if (ragCollectionId === id) { - ragEnabled = false; - ragCollectionId = null; - document.querySelector('#toggleRAG').checked = false; - updateRagBadge(); - } - showToast('集合已删除', 'success'); - await refreshCollections(); - await refreshDocList(); - }); - }); -} - -/** - * 刷新文档列表 - */ -async function refreshDocList() { - const docListEl = document.querySelector('#kbDocList'); - if (!currentColId) { - docListEl.innerHTML = '

← 选择一个知识库集合

'; - return; - } - - const docs = await getDocumentsInCollection(currentColId); - if (docs.length === 0) { - docListEl.innerHTML = '

此集合暂无文档,上传文件开始构建知识库

'; - return; - } - - docListEl.innerHTML = docs.map(d => ` -
- 📄 -
- ${escapeHtml(d.filename)} - ${d.chunkCount} 个分块 -
- -
- `).join(''); - - docListEl.querySelectorAll('.kb-doc-delete').forEach(btn => { - btn.addEventListener('click', async () => { - const docId = btn.dataset.docid; - if (!confirm('确定删除此文档?')) return; - await removeDocumentFromKB(currentColId, docId); - showToast('文档已删除', 'success'); - await refreshCollections(); - await refreshDocList(); - }); - }); -} - -/** - * 创建新集合 - */ -async function createCollection() { - const nameInput = document.querySelector('#inputKBName'); - const name = nameInput.value.trim(); - if (!name) { - showToast('请输入知识库名称', 'warning'); - return; - } - const vs = await initVectorStore(); - const col = await vs.createCollection(name); - currentColId = col.id; - nameInput.value = ''; - showToast(`知识库「${name}」已创建`, 'success'); - await refreshCollections(); - await refreshDocList(); -} - -/** - * 上传文档处理 - */ -async function handleDocUpload(e) { - const fileArr = Array.from(e.target.files || []); - e.target.value = ''; - - if (fileArr.length === 0) return; - - const embedModel = document.querySelector('#selectEmbedModel').value; - if (!embedModel) { - showToast('请先选择一个嵌入模型', 'warning'); - return; - } - - const progressEl = document.querySelector('#kbProgress'); - const progressTextEl = document.querySelector('#kbProgressText'); - - let successCount = 0; - let failCount = 0; - - for (const file of fileArr) { - if (file.size > 5 * 1024 * 1024) { - showToast(`${file.name} 超过 5MB 限制`, 'warning'); - failCount++; - continue; - } - - try { - progressEl.style.display = ''; - progressTextEl.textContent = `正在处理 ${file.name}...`; - - const content = await fileToText(file); - const result = await addDocumentToKB( - currentColId, file.name, content, embedModel, - (done, total, msg) => { - progressTextEl.textContent = `${file.name}: ${msg} (${done}/${total})`; - } - ); - - showToast(`${file.name} 已添加到知识库(${result.chunkCount} 个分块)`, 'success'); - successCount++; - } catch (err) { - console.error('[KB] 文档处理失败:', err); - showToast(`${file.name} 处理失败: ${err.message}`, 'error'); - failCount++; - } - } - - progressEl.style.display = 'none'; - - if (fileArr.length > 1) { - showToast(`上传完成:成功 ${successCount} 个,失败 ${failCount} 个`, failCount > 0 ? 'warning' : 'success'); - } - - await refreshCollections(); - await refreshDocList(); -} - -/** - * 填充嵌入模型下拉(通过 /api/show 检测 embedding 能力) - */ -async function populateEmbedModels() { - const select = document.querySelector('#selectEmbedModel'); - const api = state.get(KEYS.API); - if (!api) return; - - try { - const data = await api.listModels(); - select.innerHTML = ''; - if (!data.models || data.models.length === 0) { - select.innerHTML = ''; - return; - } - - // 并发检测每个模型的 capabilities - const checks = await Promise.allSettled( - data.models.map(async (m) => { - const info = await api.showModel(m.name); - const caps = info.capabilities || []; - return { name: m.name, size: m.size, isEmbed: caps.includes('embedding') }; - }) - ); - - const embedModels = checks - .filter(r => r.status === 'fulfilled' && r.value.isEmbed) - .map(r => r.value); - - embedModels.forEach(m => { - const opt = document.createElement('option'); - opt.value = m.name; - const size = m.size ? ` (${formatSize(m.size)})` : ''; - opt.textContent = m.name + size; - select.appendChild(opt); - }); - - if (embedModels.length === 0) { - select.innerHTML = ''; - } - } catch (err) { - console.warn('[KB] 加载模型列表失败:', err); - } -} diff --git a/js/components/lightbox.js b/js/components/lightbox.js deleted file mode 100644 index 794b64e..0000000 --- a/js/components/lightbox.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Lightbox - 图片预览组件 - */ - -let lightboxEl, lightboxImg; - -export function initLightbox() { - lightboxEl = document.querySelector('#lightbox'); - lightboxImg = document.querySelector('#lightboxImg'); - - // 关闭事件 - lightboxEl.addEventListener('click', (e) => { - if (e.target === lightboxEl) closeLightbox(); - }); - document.querySelector('#lightboxClose').addEventListener('click', closeLightbox); - - // 消息区域点击图片打开预览 - document.querySelector('#messagesContainer')?.addEventListener('click', (e) => { - if (e.target.dataset.lightbox === 'true') { - openLightbox(e.target.src); - } - }); -} - -export function openLightbox(src) { - lightboxImg.src = src; - lightboxEl.style.display = ''; -} - -export function closeLightbox() { - lightboxEl.style.display = 'none'; - lightboxImg.src = ''; -} diff --git a/js/components/model-bar.js b/js/components/model-bar.js deleted file mode 100644 index b03b952..0000000 --- a/js/components/model-bar.js +++ /dev/null @@ -1,230 +0,0 @@ -/** - * ModelBar - 模型选择栏组件 - * 包含模型下拉选择、Think 能力检测、模型信息展示 - */ - -import { state, KEYS } from '../state.js'; -import { formatSize } from '../utils.js'; - -let modelSelectEl, badgeThinkEl, badgeVisionEl, thinkCheckbox, thinkWrap, btnAttachImg; - -/** 缓存模型能力 { modelName: { think: bool, vision: bool } } */ -const modelCapabilityCache = new Map(); - -export function initModelBar() { - modelSelectEl = document.querySelector('#modelSelect'); - badgeThinkEl = document.querySelector('#badgeThink'); - badgeVisionEl = document.querySelector('#badgeVision'); - thinkCheckbox = document.querySelector('#toggleThink'); - thinkWrap = document.querySelector('#thinkToggleWrap'); - btnAttachImg = document.querySelector('#btnAttachImg'); - - // 模型切换 → 检测能力 + 保存设置 - modelSelectEl.addEventListener('change', async () => { - const model = modelSelectEl.value; - if (db()) await db().saveSetting('selectedModel', model); - state.set('_defaultModel', model); - state.update(KEYS.CURRENT_SESSION, session => session ? ({ ...session, model }) : session); - if (model) { - await checkModelCapability(model); - } else { - setThinkAvailable(false); - setVisionAvailable(false); - badgeThinkEl.style.display = 'none'; - badgeVisionEl.style.display = 'none'; - } - }); -} - -function db() { return state.get(KEYS.DB); } -function api() { return state.get(KEYS.API); } - -/** - * 加载模型列表 - */ -export async function loadModels() { - if (!api()) return; - - try { - const data = await api().listModels(); - modelSelectEl.innerHTML = ''; - - if (!data.models || data.models.length === 0) { - modelSelectEl.innerHTML = ''; - badgeThinkEl.style.display = 'none'; badgeVisionEl.style.display = 'none'; - setThinkAvailable(false); setVisionAvailable(false); - return; - } - - data.models - .sort((a, b) => { - const nameA = a.name.toLowerCase(), nameB = b.name.toLowerCase(); - if (nameA !== nameB) return nameA.localeCompare(nameB); - return (a.size || 0) - (b.size || 0); - }) - .forEach(m => { - const opt = document.createElement('option'); - opt.value = m.name; - const diskSize = formatSize(m.size); - const label = diskSize ? `${m.name} · ${diskSize}` : m.name; - opt.textContent = label; - modelSelectEl.appendChild(opt); - }); - - // 恢复默认选中模型 - const defaultModel = state.get('_defaultModel', ''); - if (defaultModel) { - modelSelectEl.value = defaultModel; - } - - // 检测当前选中模型的能力 - if (modelSelectEl.value) { - await checkModelCapability(modelSelectEl.value); - } - - // 后台过滤:移除不支持 completion 的嵌入模型 - filterEmbedModels(data.models); - - } catch (err) { - console.error('[ModelBar] 加载模型失败:', err); - modelSelectEl.innerHTML = ''; - badgeThinkEl.style.display = 'none'; badgeVisionEl.style.display = 'none'; - setThinkAvailable(false); setVisionAvailable(false); - } -} - -/** - * 后台检测并过滤嵌入模型(不支持 completion 的模型) - */ -async function filterEmbedModels(models) { - for (const m of models) { - try { - const info = await api().showModel(m.name); - const caps = info.capabilities || []; - const isCompletion = caps.includes('completion'); - // 缓存能力 - modelCapabilityCache.set(m.name, { - think: caps.includes('thinking'), - vision: caps.includes('vision'), - completion: isCompletion, - }); - // 从下拉框移除不支持 completion 的模型 - if (!isCompletion) { - const opt = modelSelectEl.querySelector(`option[value="${CSS.escape(m.name)}"]`); - if (opt) opt.remove(); - } - } catch { /* 忽略检测失败的模型 */ } - } - // 如果当前选中的模型被移除了,选中第一个可用模型 - if (!modelSelectEl.value && modelSelectEl.options.length > 0) { - modelSelectEl.value = modelSelectEl.options[0].value; - state.set('_defaultModel', modelSelectEl.value); - if (db()) await db().saveSetting('selectedModel', modelSelectEl.value); - await checkModelCapability(modelSelectEl.value); - } -} - -/** - * 调用 /api/show 检测模型能力 - * @param {string} modelName - */ -async function checkModelCapability(modelName) { - // 命中缓存 - if (modelCapabilityCache.has(modelName)) { - applyCapability(modelName, modelCapabilityCache.get(modelName)); - return; - } - - if (!api()) return; - - try { - const info = await api().showModel(modelName); - const capabilities = info.capabilities || []; - const caps = { - think: capabilities.includes('thinking'), - vision: capabilities.includes('vision'), - completion: capabilities.includes('completion'), - }; - modelCapabilityCache.set(modelName, caps); - applyCapability(modelName, caps); - } catch (err) { - console.warn('[ModelBar] 获取模型能力失败:', err); - // 降级:不支持 think - const fallback = { think: false, vision: false, completion: true }; - modelCapabilityCache.set(modelName, fallback); - applyCapability(modelName, fallback); - } -} - -/** - * 根据能力设置 UI 状态 - */ -function applyCapability(modelName, caps) { - setThinkAvailable(caps.think); - setVisionAvailable(caps.vision); - - // 更新能力标签 - badgeThinkEl.style.display = caps.think ? '' : 'none'; - badgeVisionEl.style.display = caps.vision ? '' : 'none'; -} - -/** - * 设置 Think 开关是否可用 - */ -function setThinkAvailable(available) { - if (available) { - thinkCheckbox.disabled = false; - thinkWrap.title = 'Think 模式:启用深度推理'; - thinkWrap.classList.remove('unavailable'); - } else { - thinkCheckbox.checked = false; - thinkCheckbox.disabled = true; - thinkWrap.title = '当前模型不支持 Think 模式'; - thinkWrap.classList.add('unavailable'); - } -} - -/** - * 设置图片上传按钮是否可用 - */ -function setVisionAvailable(available) { - if (!btnAttachImg) return; - if (available) { - btnAttachImg.disabled = false; - btnAttachImg.title = '上传图片'; - btnAttachImg.classList.remove('disabled'); - } else { - btnAttachImg.disabled = true; - btnAttachImg.title = '当前模型不支持图片分析'; - btnAttachImg.classList.add('disabled'); - } -} - -/** - * 当前选择的模型是否支持视觉分析 - */ -export function isVisionAvailable() { - return btnAttachImg && !btnAttachImg.disabled; -} - -/** - * 获取当前选择的模型 - */ -export function getSelectedModel() { - return modelSelectEl.value; -} - -/** - * 设置选中的模型 - */ -export function setSelectedModel(modelName) { - modelSelectEl.value = modelName; - modelSelectEl.dispatchEvent(new Event('change')); -} - -/** - * Think 模式是否开启 - */ -export function isThinkEnabled() { - return thinkCheckbox.checked && !thinkCheckbox.disabled; -} diff --git a/js/components/preset-bar.js b/js/components/preset-bar.js deleted file mode 100644 index 132c415..0000000 --- a/js/components/preset-bar.js +++ /dev/null @@ -1,212 +0,0 @@ -/** - * PresetBar - 预设栏组件 - * - * 在模型栏下方显示预设胶囊按钮列表,点击即切换 - * 右侧"+"按钮打开管理面板 - */ - -import { state, KEYS } from '../state.js'; -import { - getPresets, getActivePresetId, activatePreset, - createPreset, updatePreset, deletePreset -} from '../preset-manager.js'; -import { showToast } from './toast.js'; - -let presetBarEl; -let presetListEl; -let editingPresetId = null; - -export function initPresetBar() { - // 创建预设栏 DOM - const modelBar = document.querySelector('.model-bar'); - if (!modelBar) return; - - const bar = document.createElement('div'); - bar.className = 'preset-bar'; - bar.innerHTML = ` -
- - `; - modelBar.after(bar); - - presetBarEl = bar; - presetListEl = bar.querySelector('#presetList'); - - // 点击"+"打开管理面板 - bar.querySelector('#btnAddPreset').addEventListener('click', openPresetModal); - - // 监听预设变化,刷新 UI - state.on('activePresetId', renderPresetBar); - state.on('presets', renderPresetBar); - - // 初始渲染 - renderPresetBar(); -} - -/** - * 渲染预设胶囊列表 - */ -function renderPresetBar() { - if (!presetListEl) return; - - const presets = getPresets(); - const activeId = getActivePresetId(); - - presetListEl.innerHTML = ''; - - for (const preset of presets) { - const chip = document.createElement('button'); - chip.className = 'preset-chip' + (preset.id === activeId ? ' active' : ''); - chip.title = preset.systemPrompt ? preset.systemPrompt.slice(0, 100) : '无系统提示词'; - chip.innerHTML = `${preset.icon}${escapeHtml(preset.name)}`; - - chip.addEventListener('click', () => handlePresetClick(preset.id)); - // 右键编辑(仅自定义) - chip.addEventListener('contextmenu', (e) => { - if (!preset.builtIn) { - e.preventDefault(); - editingPresetId = preset.id; - openPresetModal(preset); - } - }); - - presetListEl.appendChild(chip); - } -} - -async function handlePresetClick(presetId) { - const currentId = getActivePresetId(); - if (presetId === currentId) return; // 已激活,不重复切换 - - await activatePreset(presetId); - - const preset = getPresets().find(p => p.id === presetId); - if (preset) { - showToast(`已切换到 ${preset.icon} ${preset.name}`, 'success', 2000); - } -} - -// ═══════════════════════════════════════ -// 预设管理模态框 -// ═══════════════════════════════════════ - -function openPresetModal(editPreset) { - const modal = document.querySelector('#presetModal'); - const titleEl = modal.querySelector('#presetModalTitle'); - const nameInput = modal.querySelector('#inputPresetName'); - const iconInput = modal.querySelector('#inputPresetIcon'); - const spInput = modal.querySelector('#inputPresetSystemPrompt'); - const tempInput = modal.querySelector('#inputPresetTemp'); - const tempVal = modal.querySelector('#presetTempValue'); - const ctxInput = modal.querySelector('#inputPresetNumCtx'); - const thinkCheck = modal.querySelector('#togglePresetThink'); - const btnSave = modal.querySelector('#btnSavePreset'); - const btnDelete = modal.querySelector('#btnDeletePreset'); - - if (editPreset && editPreset.id) { - // 编辑模式 - editingPresetId = editPreset.id; - titleEl.textContent = '✏️ 编辑预设'; - nameInput.value = editPreset.name; - iconInput.value = editPreset.icon; - spInput.value = editPreset.systemPrompt; - tempInput.value = editPreset.temperature ?? 0.7; - tempVal.textContent = (editPreset.temperature ?? 0.7).toFixed(1); - ctxInput.value = editPreset.numCtx || 24576; - thinkCheck.checked = editPreset.think ?? false; - btnDelete.style.display = editPreset.builtIn ? 'none' : ''; - btnSave.textContent = '保存'; - } else { - // 新建模式 - editingPresetId = null; - titleEl.textContent = '✨ 新建预设'; - nameInput.value = ''; - iconInput.value = '🤖'; - spInput.value = ''; - tempInput.value = 0.7; - tempVal.textContent = '0.7'; - ctxInput.value = 24576; - thinkCheck.checked = false; - btnDelete.style.display = 'none'; - btnSave.textContent = '创建'; - } - - modal.style.display = ''; - nameInput.focus(); - - // 温度滑块实时更新 - tempInput.oninput = () => { - tempVal.textContent = parseFloat(tempInput.value).toFixed(1); - }; -} - -export function initPresetModal() { - const modal = document.querySelector('#presetModal'); - if (!modal) return; - - // 关闭 - modal.querySelector('#btnClosePresetModal').addEventListener('click', () => { - modal.style.display = 'none'; - }); - modal.addEventListener('click', (e) => { - if (e.target === modal) modal.style.display = 'none'; - }); - - // 保存 - modal.querySelector('#btnSavePreset').addEventListener('click', async () => { - const name = modal.querySelector('#inputPresetName').value.trim(); - if (!name) { - showToast('请输入预设名称', 'warning'); - return; - } - - const data = { - name, - icon: modal.querySelector('#inputPresetIcon').value.trim() || '🤖', - systemPrompt: modal.querySelector('#inputPresetSystemPrompt').value.trim(), - temperature: parseFloat(modal.querySelector('#inputPresetTemp').value) || 0.7, - numCtx: parseInt(modal.querySelector('#inputPresetNumCtx').value) || 24576, - think: modal.querySelector('#togglePresetThink').checked - }; - - if (editingPresetId) { - await updatePreset(editingPresetId, data); - showToast('预设已更新', 'success', 2000); - // 如果更新的是当前激活预设,重新应用 - if (editingPresetId === getActivePresetId()) { - await activatePreset(editingPresetId); - } - } else { - const created = await createPreset(data); - // 自动激活新建的预设 - await activatePreset(created.id); - showToast(`已创建并激活 ${data.icon} ${data.name}`, 'success', 2000); - } - - modal.style.display = 'none'; - editingPresetId = null; - }); - - // 删除 - modal.querySelector('#btnDeletePreset').addEventListener('click', async () => { - if (!editingPresetId) return; - const preset = getPresets().find(p => p.id === editingPresetId); - if (!preset) return; - - if (confirm(`确定删除预设 "${preset.icon} ${preset.name}" ?`)) { - await deletePreset(editingPresetId); - showToast('预设已删除', 'info', 2000); - modal.style.display = 'none'; - editingPresetId = null; - } - }); -} - -function escapeHtml(str) { - const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; - return String(str).replace(/[&<>"']/g, c => map[c]); -} diff --git a/js/components/settings-modal.js b/js/components/settings-modal.js deleted file mode 100644 index d1a9a37..0000000 --- a/js/components/settings-modal.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * SettingsModal - 设置面板组件 - */ - -import { state, KEYS } from '../state.js'; -import { debounce, formatSize } from '../utils.js'; -import { encryptData, decryptData, isMetonaFile } from '../crypto.js'; -import { updateConnectionInfo, updateRunningModels } from './header.js'; -import { loadModels } from './model-bar.js'; -import { showToast } from './toast.js'; -import { OllamaAPI } from '../ollama-api.js'; - -let settingsModalEl; - -export function initSettingsModal() { - settingsModalEl = document.querySelector('#settingsModal'); - - // 打开/关闭 - document.querySelector('#btnSettings').addEventListener('click', openSettingsModal); - document.querySelector('#btnCloseSettings').addEventListener('click', closeSettingsModal); - settingsModalEl.addEventListener('click', (e) => { - if (e.target === settingsModalEl) closeSettingsModal(); - }); - - // 保存服务器地址 - const saveServerUrl = debounce(async () => { - const url = document.querySelector('#inputServerUrl').value.trim(); - if (!url) return; - const db = state.get(KEYS.DB); - const api = new OllamaAPI(url); - state.set(KEYS.API, api); - if (db) await db.saveSetting('serverUrl', url); - updateConnectionInfo(); - loadModels(); - }, 500); - document.querySelector('#inputServerUrl').addEventListener('input', saveServerUrl); - - // 系统提示词开关 - document.querySelector('#toggleSystemPrompt').addEventListener('change', async (e) => { - const db = state.get(KEYS.DB); - state.set(KEYS.SYSTEM_PROMPT_ENABLED, e.target.checked); - document.querySelector('#inputSystemPrompt').disabled = !e.target.checked; - if (db) await db.saveSetting('systemPromptEnabled', e.target.checked); - }); - - // 保存系统提示词 - const saveSystemPrompt = debounce(async () => { - const db = state.get(KEYS.DB); - const val = document.querySelector('#inputSystemPrompt').value.trim(); - state.set(KEYS.SYSTEM_PROMPT, val); - if (db) await db.saveSetting('systemPrompt', val); - }, 500); - document.querySelector('#inputSystemPrompt').addEventListener('input', saveSystemPrompt); - - // 保存上下文长度 - const saveNumCtx = debounce(async () => { - const db = state.get(KEYS.DB); - const val = document.querySelector('#inputNumCtx').value.trim(); - const numCtx = val ? parseInt(val) : 24576; - state.set(KEYS.NUM_CTX, numCtx); - if (db) await db.saveSetting('numCtx', numCtx); - }, 500); - document.querySelector('#inputNumCtx').addEventListener('input', saveNumCtx); - - // 温度滑块 - const tempSlider = document.querySelector('#inputTemperature'); - const tempDisplay = document.querySelector('#tempValue'); - tempSlider.addEventListener('input', () => { - const val = parseFloat(tempSlider.value); - tempDisplay.textContent = val.toFixed(1); - }); - const saveTemperature = debounce(async () => { - const db = state.get(KEYS.DB); - const val = parseFloat(tempSlider.value); - state.set('temperature', val); - if (db) await db.saveSetting('temperature', val); - }, 300); - tempSlider.addEventListener('change', saveTemperature); - - // 释放显存 - document.querySelector('#btnReleaseVRAM').addEventListener('click', async () => { - const api = state.get(KEYS.API); - const model = document.querySelector('#modelSelect').value; - try { - await api.chat({ model: model || 'any', messages: [], keep_alive: 0 }); - showToast('显存已释放', 'success'); - updateRunningModels(); - } catch (err) { - showToast(`释放失败: ${err.message}`, 'error'); - } - }); - - // 清空所有历史 - document.querySelector('#btnClearAllHistory').addEventListener('click', async () => { - const db = state.get(KEYS.DB); - if (confirm('确定清空所有历史记录?此操作不可恢复!')) { - await db.clearAll(); - // 触发新会话 - document.querySelector('#btnNewChat').click(); - showToast('已清空所有历史记录', 'success'); - } - }); - - // 导出全部会话 - document.querySelector('#btnExportAll').addEventListener('click', exportAllSessions); - - // 导入会话 - const importFileInput = document.querySelector('#importFileInput'); - document.querySelector('#btnImportSessions').addEventListener('click', () => importFileInput.click()); - importFileInput.addEventListener('change', (e) => { - if (e.target.files.length > 0) { - importSessions(e.target.files[0]); - e.target.value = ''; - } - }); -} - -export function openSettingsModal() { - settingsModalEl.style.display = ''; - document.querySelector('#inputServerUrl').value = state.get(KEYS.API)?.baseUrl || ''; - updateConnectionInfo(); - updateRunningModels(); -} - -export function closeSettingsModal() { - settingsModalEl.style.display = 'none'; -} - -async function exportAllSessions() { - const db = state.get(KEYS.DB); - if (!db) return; - const sessions = await db.getAllSessions(); - if (sessions.length === 0) { - showToast('没有可导出的会话', 'warning'); - return; - } - - const backup = { - app: 'Metona Ollama Client', - version: 1, - exportedAt: new Date().toISOString(), - count: sessions.length, - sessions - }; - - try { - const blob = await encryptData(backup); - const ts = new Date().toISOString().slice(0, 10); - const a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = `metona-backup-${ts}.metona`; - a.click(); - showToast(`已导出 ${sessions.length} 个会话(已加密)`, 'success'); - } catch (err) { - console.error('[Settings] 导出失败:', err); - showToast(`导出失败: ${err.message}`, 'error'); - } -} - -async function importSessions(file) { - const db = state.get(KEYS.DB); - if (!db) return; - - if (!isMetonaFile(file)) { - showToast('仅支持导入 .metona 格式文件', 'error'); - return; - } - - try { - const buffer = await file.arrayBuffer(); - const data = await decryptData(buffer); - - let sessions; - if (Array.isArray(data)) { - sessions = data; - } else if (data.sessions && Array.isArray(data.sessions)) { - sessions = data.sessions; - } else { - showToast('文件内容格式错误:未找到会话数据', 'error'); - return; - } - - const result = await db.importSessions(sessions); - showToast(`导入完成:${result.imported} 个会话${result.skipped > 0 ? `,跳过 ${result.skipped} 个` : ''}`, 'success', 4000); - } catch (err) { - console.error('[Settings] 导入失败:', err); - showToast(`导入失败: ${err.message}`, 'error'); - } -} diff --git a/js/components/toast.js b/js/components/toast.js deleted file mode 100644 index 8f60d9a..0000000 --- a/js/components/toast.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Toast - 通知组件 - */ - -let toastContainer = null; - -export function initToast() { - toastContainer = document.querySelector('#toastContainer'); -} - -/** - * 显示 Toast 通知 - * @param {string} text - 通知文本 - * @param {'info'|'success'|'warning'|'error'} type - 通知类型 - * @param {number} duration - 显示时长 (ms) - */ -export function showToast(text, type = 'info', duration = 3000) { - if (!toastContainer) return; - - const iconMap = { success: '✓', error: '✗', warning: '⚠', info: 'ℹ' }; - const toast = document.createElement('div'); - toast.className = `toast ${type}`; - toast.innerHTML = `${iconMap[type] || 'ℹ'}${text}`; - toastContainer.appendChild(toast); - - setTimeout(() => { - toast.classList.add('removing'); - toast.addEventListener('animationend', () => toast.remove()); - }, duration); -} diff --git a/js/crypto.js b/js/crypto.js deleted file mode 100644 index f780afb..0000000 --- a/js/crypto.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * Crypto - Metona 会话加密模块 - * - * 文件格式: [8B magic "METONA1\0"][16B salt][12B IV][AES-256-GCM ciphertext] - * 后缀: .metona - * 安全上下文(HTTPS)下使用 AES-256-GCM,否则降级为 XOR 混淆 - */ - -const MAGIC = new TextEncoder().encode('METONA1\0'); -const PASSPHRASE = 'metona-ollama-v2'; -const PBKDF2_ITERATIONS = 100000; - -const SECURE = !!(globalThis.crypto && globalThis.crypto.subtle); - -/** - * SHA-256 哈希 (降级模式用) - */ -async function sha256(data) { - if (SECURE) { - const buf = await crypto.subtle.digest('SHA-256', data); - return new Uint8Array(buf); - } - // 纯 JS SHA-256 - return sha256js(data); -} - -/** - * 纯 JS SHA-256 实现 (兼容非安全上下文) - */ -function sha256js(message) { - const K = new Uint32Array([ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ]); - - const bytes = message instanceof Uint8Array ? message : new Uint8Array(message); - const bitLen = bytes.length * 8; - - // 预处理: padding - const paddedLen = Math.ceil((bytes.length + 9) / 64) * 64; - const padded = new Uint8Array(paddedLen); - padded.set(bytes); - padded[bytes.length] = 0x80; - const view = new DataView(padded.buffer); - view.setUint32(paddedLen - 4, bitLen, false); - - let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a; - let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19; - - const w = new Uint32Array(64); - - for (let offset = 0; offset < paddedLen; offset += 64) { - for (let i = 0; i < 16; i++) { - w[i] = view.getUint32(offset + i * 4, false); - } - for (let i = 16; i < 64; i++) { - const s0 = (w[i - 15] >>> 7 | w[i - 15] << 25) ^ (w[i - 15] >>> 18 | w[i - 15] << 14) ^ (w[i - 15] >>> 3); - const s1 = (w[i - 2] >>> 17 | w[i - 2] << 15) ^ (w[i - 2] >>> 19 | w[i - 2] << 13) ^ (w[i - 2] >>> 10); - w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0; - } - - let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7; - - for (let i = 0; i < 64; i++) { - const S1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7); - const ch = (e & f) ^ (~e & g); - const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0; - const S0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10); - const maj = (a & b) ^ (a & c) ^ (b & c); - const temp2 = (S0 + maj) >>> 0; - - h = g; g = f; f = e; - e = (d + temp1) >>> 0; - d = c; c = b; b = a; - a = (temp1 + temp2) >>> 0; - } - - h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0; h3 = (h3 + d) >>> 0; - h4 = (h4 + e) >>> 0; h5 = (h5 + f) >>> 0; h6 = (h6 + g) >>> 0; h7 = (h7 + h) >>> 0; - } - - const result = new Uint8Array(32); - const rv = new DataView(result.buffer); - rv.setUint32(0, h0, false); rv.setUint32(4, h1, false); - rv.setUint32(8, h2, false); rv.setUint32(12, h3, false); - rv.setUint32(16, h4, false); rv.setUint32(20, h5, false); - rv.setUint32(24, h6, false); rv.setUint32(28, h7, false); - return result; -} - -/** - * 从 passhprase + salt 派生密钥 (PBKDF2 或降级多次哈希) - */ -async function deriveKeyBytes(salt) { - const passBytes = new TextEncoder().encode(PASSPHRASE); - const input = new Uint8Array(passBytes.length + salt.length); - input.set(passBytes); - input.set(salt, passBytes.length); - - if (SECURE) { - const keyMaterial = await crypto.subtle.importKey('raw', passBytes, 'PBKDF2', false, ['deriveKey']); - const key = await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' }, - keyMaterial, - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); - const raw = await crypto.subtle.exportKey('raw', key); - return new Uint8Array(raw); - } - - // 降级: 多次哈希模拟密钥派生 - let key = input; - for (let i = 0; i < 10000; i++) { - key = await sha256(key); - } - return key; -} - -/** - * XOR 加密/解密 (降级模式) - */ -function xorBytes(data, keyBytes) { - const out = new Uint8Array(data.length); - for (let i = 0; i < data.length; i++) { - out[i] = data[i] ^ keyBytes[i % keyBytes.length]; - } - return out; -} - -/** - * 加密 JSON 数据 → .metona 二进制格式 - */ -export async function encryptData(data) { - const json = new TextEncoder().encode(JSON.stringify(data)); - const salt = crypto.getRandomValues(new Uint8Array(16)); - const iv = crypto.getRandomValues(new Uint8Array(12)); - const keyBytes = await deriveKeyBytes(salt); - - let payload; - if (SECURE) { - const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['encrypt']); - const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, json); - payload = new Uint8Array(ciphertext); - } else { - // 降级: XOR 混淆,前 4 字节存原始长度 - const lenBytes = new Uint32Array([json.length]); - const withLen = new Uint8Array(4 + json.length); - withLen.set(new Uint8Array(lenBytes.buffer)); - withLen.set(json, 4); - payload = xorBytes(withLen, keyBytes); - } - - // 标记加密模式: SECURE=0x01, FALLBACK=0x00 - const flag = new Uint8Array([SECURE ? 1 : 0]); - - return new Blob([MAGIC, salt, iv, flag, payload]); -} - -/** - * 解密 .metona 文件 → JSON 对象 - */ -export async function decryptData(buffer) { - const data = new Uint8Array(buffer); - - // 校验 magic - for (let i = 0; i < 8; i++) { - if (data[i] !== MAGIC[i]) throw new Error('不是有效的 .metona 文件'); - } - - const salt = data.slice(8, 24); - const iv = data.slice(24, 36); - const mode = data[36]; - const payload = data.slice(37); - const keyBytes = await deriveKeyBytes(salt); - - let jsonBytes; - if (mode === 1) { - // AES-GCM - const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['decrypt']); - const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, payload); - jsonBytes = new Uint8Array(decrypted); - } else { - // XOR 降级 - const withLen = xorBytes(payload, keyBytes); - const len = new DataView(withLen.buffer).getUint32(0, true); - jsonBytes = withLen.slice(4, 4 + len); - } - - return JSON.parse(new TextDecoder().decode(jsonBytes)); -} - -/** - * 检查文件是否为 .metona 格式 - */ -export function isMetonaFile(file) { - return file.name.endsWith('.metona'); -} diff --git a/js/document-processor.js b/js/document-processor.js deleted file mode 100644 index e8d4cc9..0000000 --- a/js/document-processor.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * DocumentProcessor - 文档分块处理 - */ - -/** - * 将文本切分为语义块 - * @param {string} text - 原始文本 - * @param {object} options - * @param {number} options.maxChunkSize - 每块最大字符数 (默认 1500) - * @param {number} options.overlap - 块间重叠字符数 (默认 200) - * @returns {string[]} 分块后的文本数组 - */ -export function chunkText(text, { maxChunkSize = 1500, overlap = 200 } = {}) { - if (!text || text.trim().length === 0) return []; - - // 先按段落分割 - const paragraphs = text.split(/\n\s*\n/).filter(p => p.trim()); - const chunks = []; - let current = ''; - - for (const para of paragraphs) { - const trimmed = para.trim(); - if (!trimmed) continue; - - // 单段落超过 maxChunkSize → 按句子分割 - if (trimmed.length > maxChunkSize) { - if (current) { - chunks.push(current.trim()); - current = ''; - } - const sentences = splitSentences(trimmed); - let sentenceBuf = ''; - for (const sent of sentences) { - if ((sentenceBuf + sent).length > maxChunkSize && sentenceBuf) { - chunks.push(sentenceBuf.trim()); - // 保留末尾 overlap - sentenceBuf = overlap > 0 - ? sentenceBuf.slice(-overlap) + sent - : sent; - } else { - sentenceBuf += sent; - } - } - if (sentenceBuf.trim()) { - current = sentenceBuf; - } - continue; - } - - // 正常段落累积 - if ((current + '\n\n' + trimmed).length > maxChunkSize && current) { - chunks.push(current.trim()); - // 保留末尾 overlap - if (overlap > 0 && current.length > overlap) { - current = current.slice(-overlap) + '\n\n' + trimmed; - } else { - current = trimmed; - } - } else { - current = current ? current + '\n\n' + trimmed : trimmed; - } - } - - if (current.trim()) { - chunks.push(current.trim()); - } - - return chunks; -} - -/** - * 按句子分割(中英文兼容) - */ -function splitSentences(text) { - // 中文句号、问号、感叹号 + 英文句号等 - const parts = text.split(/(?<=[。!?.!?])\s*/); - return parts.filter(p => p.trim()).map(p => p.trim() + ' '); -} - -/** - * 从文件内容提取文档信息 - * @param {string} content - 文件内容 - * @param {string} filename - 文件名 - * @returns {{ text: string, filename: string }} - */ -export function extractDocument(content, filename) { - return { - text: content, - filename - }; -} - -/** - * 生成文档块的元数据 - */ -export function createChunkMetadata(chunk, index, docId, filename) { - return { - id: `${docId}_chunk_${index}`, - docId, - filename, - chunkIndex: index, - text: chunk, - charCount: chunk.length - }; -} diff --git a/js/lib/marked.esm.js b/js/lib/marked.esm.js deleted file mode 100644 index d07059e..0000000 --- a/js/lib/marked.esm.js +++ /dev/null @@ -1,2580 +0,0 @@ -/** - * marked v15.0.7 - a markdown parser - * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed) - * https://github.com/markedjs/marked - */ - -/** - * DO NOT EDIT THIS FILE - * The code in this file is generated from files in ./src/ - */ - -/** - * Gets the original marked default options. - */ -function _getDefaults() { - return { - async: false, - breaks: false, - extensions: null, - gfm: true, - hooks: null, - pedantic: false, - renderer: null, - silent: false, - tokenizer: null, - walkTokens: null, - }; -} -let _defaults = _getDefaults(); -function changeDefaults(newDefaults) { - _defaults = newDefaults; -} - -const noopTest = { exec: () => null }; -function edit(regex, opt = '') { - let source = typeof regex === 'string' ? regex : regex.source; - const obj = { - replace: (name, val) => { - let valSource = typeof val === 'string' ? val : val.source; - valSource = valSource.replace(other.caret, '$1'); - source = source.replace(name, valSource); - return obj; - }, - getRegex: () => { - return new RegExp(source, opt); - }, - }; - return obj; -} -const other = { - codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, - outputLinkReplace: /\\([\[\]])/g, - indentCodeCompensation: /^(\s+)(?:```)/, - beginningSpace: /^\s+/, - endingHash: /#$/, - startingSpaceChar: /^ /, - endingSpaceChar: / $/, - nonSpaceChar: /[^ ]/, - newLineCharGlobal: /\n/g, - tabCharGlobal: /\t/g, - multipleSpaceGlobal: /\s+/g, - blankLine: /^[ \t]*$/, - doubleBlankLine: /\n[ \t]*\n[ \t]*$/, - blockquoteStart: /^ {0,3}>/, - blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, - blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, - listReplaceTabs: /^\t+/, - listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, - listIsTask: /^\[[ xX]\] /, - listReplaceTask: /^\[[ xX]\] +/, - anyLine: /\n.*\n/, - hrefBrackets: /^<(.*)>$/, - tableDelimiter: /[:|]/, - tableAlignChars: /^\||\| *$/g, - tableRowBlankLine: /\n[ \t]*$/, - tableAlignRight: /^ *-+: *$/, - tableAlignCenter: /^ *:-+: *$/, - tableAlignLeft: /^ *:-+ *$/, - startATag: /^/i, - startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, - endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, - startAngleBracket: /^$/, - pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, - unicodeAlphaNumeric: /[\p{L}\p{N}]/u, - escapeTest: /[&<>"']/, - escapeReplace: /[&<>"']/g, - escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, - escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, - unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, - caret: /(^|[^\[])\^/g, - percentDecode: /%25/g, - findPipe: /\|/g, - splitPipe: / \|/, - slashPipe: /\\\|/g, - carriageReturn: /\r\n|\r/g, - spaceLine: /^ +$/gm, - notSpaceStart: /^\S*/, - endingNewline: /\n$/, - listItemRegex: (bull) => new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`), - nextBulletRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`), - hrRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`), - fencesBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`), - headingBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`), - htmlBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, 'i'), -}; -/** - * Block-Level Grammar - */ -const newline = /^(?:[ \t]*(?:\n|$))+/; -const blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/; -const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; -const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; -const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; -const bullet = /(?:[*+-]|\d{1,9}[.)])/; -const lheadingCore = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/; -const lheading = edit(lheadingCore) - .replace(/bull/g, bullet) // lists can interrupt - .replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code blocks can interrupt - .replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt - .replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt - .replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt - .replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt - .replace(/\|table/g, '') // table not in commonmark - .getRegex(); -const lheadingGfm = edit(lheadingCore) - .replace(/bull/g, bullet) // lists can interrupt - .replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code blocks can interrupt - .replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt - .replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt - .replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt - .replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt - .replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/) // table can interrupt - .getRegex(); -const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/; -const blockText = /^[^\n]+/; -const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/; -const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/) - .replace('label', _blockLabel) - .replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/) - .getRegex(); -const list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/) - .replace(/bull/g, bullet) - .getRegex(); -const _tag = 'address|article|aside|base|basefont|blockquote|body|caption' - + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' - + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' - + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' - + '|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title' - + '|tr|track|ul'; -const _comment = /|$))/; -const html = edit('^ {0,3}(?:' // optional indentation - + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3) - + '|\\n*|$)' // (4) - + '|\\n*|$)' // (5) - + '|)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (6) - + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) open tag - + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) closing tag - + ')', 'i') - .replace('comment', _comment) - .replace('tag', _tag) - .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) - .getRegex(); -const paragraph = edit(_paragraph) - .replace('hr', hr) - .replace('heading', ' {0,3}#{1,6}(?:\\s|$)') - .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs - .replace('|table', '') - .replace('blockquote', ' {0,3}>') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|textarea|!--)') - .replace('tag', _tag) // pars can be interrupted by type (6) html blocks - .getRegex(); -const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/) - .replace('paragraph', paragraph) - .getRegex(); -/** - * Normal Block Grammar - */ -const blockNormal = { - blockquote, - code: blockCode, - def, - fences, - heading, - hr, - html, - lheading, - list, - newline, - paragraph, - table: noopTest, - text: blockText, -}; -/** - * GFM Block Grammar - */ -const gfmTable = edit('^ *([^\\n ].*)\\n' // Header - + ' {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)' // Align - + '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)') // Cells - .replace('hr', hr) - .replace('heading', ' {0,3}#{1,6}(?:\\s|$)') - .replace('blockquote', ' {0,3}>') - .replace('code', '(?: {4}| {0,3}\t)[^\\n]') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|textarea|!--)') - .replace('tag', _tag) // tables can be interrupted by type (6) html blocks - .getRegex(); -const blockGfm = { - ...blockNormal, - lheading: lheadingGfm, - table: gfmTable, - paragraph: edit(_paragraph) - .replace('hr', hr) - .replace('heading', ' {0,3}#{1,6}(?:\\s|$)') - .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs - .replace('table', gfmTable) // interrupt paragraphs with table - .replace('blockquote', ' {0,3}>') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|textarea|!--)') - .replace('tag', _tag) // pars can be interrupted by type (6) html blocks - .getRegex(), -}; -/** - * Pedantic grammar (original John Gruber's loose markdown specification) - */ -const blockPedantic = { - ...blockNormal, - html: edit('^ *(?:comment *(?:\\n|\\s*$)' - + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag - + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') - .replace('comment', _comment) - .replace(/tag/g, '(?!(?:' - + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' - + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' - + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') - .getRegex(), - def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, - heading: /^(#{1,6})(.*)(?:\n+|$)/, - fences: noopTest, // fences not supported - lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, - paragraph: edit(_paragraph) - .replace('hr', hr) - .replace('heading', ' *#{1,6} *[^\n]') - .replace('lheading', lheading) - .replace('|table', '') - .replace('blockquote', ' {0,3}>') - .replace('|fences', '') - .replace('|list', '') - .replace('|html', '') - .replace('|tag', '') - .getRegex(), -}; -/** - * Inline-Level Grammar - */ -const escape$1 = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; -const inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; -const br = /^( {2,}|\\)\n(?!\s*$)/; -const inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\ -const blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g; -const emStrongLDelimCore = /^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/; -const emStrongLDelim = edit(emStrongLDelimCore, 'u') - .replace(/punct/g, _punctuation) - .getRegex(); -const emStrongLDelimGfm = edit(emStrongLDelimCore, 'u') - .replace(/punct/g, _punctuationGfmStrongEm) - .getRegex(); -const emStrongRDelimAstCore = '^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong - + '|[^*]+(?=[^*])' // Consume to delim - + '|(?!\\*)punct(\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter - + '|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)' // (2) a***#, a*** can only be a Right Delimiter - + '|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)' // (3) #***a, ***a can only be Left Delimiter - + '|[\\s](\\*+)(?!\\*)(?=punct)' // (4) ***# can only be Left Delimiter - + '|(?!\\*)punct(\\*+)(?!\\*)(?=punct)' // (5) #***# can be either Left or Right Delimiter - + '|notPunctSpace(\\*+)(?=notPunctSpace)'; // (6) a***a can be either Left or Right Delimiter -const emStrongRDelimAst = edit(emStrongRDelimAstCore, 'gu') - .replace(/notPunctSpace/g, _notPunctuationOrSpace) - .replace(/punctSpace/g, _punctuationOrSpace) - .replace(/punct/g, _punctuation) - .getRegex(); -const emStrongRDelimAstGfm = edit(emStrongRDelimAstCore, 'gu') - .replace(/notPunctSpace/g, _notPunctuationOrSpaceGfmStrongEm) - .replace(/punctSpace/g, _punctuationOrSpaceGfmStrongEm) - .replace(/punct/g, _punctuationGfmStrongEm) - .getRegex(); -// (6) Not allowed for _ -const emStrongRDelimUnd = edit('^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong - + '|[^_]+(?=[^_])' // Consume to delim - + '|(?!_)punct(_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter - + '|notPunctSpace(_+)(?!_)(?=punctSpace|$)' // (2) a___#, a___ can only be a Right Delimiter - + '|(?!_)punctSpace(_+)(?=notPunctSpace)' // (3) #___a, ___a can only be Left Delimiter - + '|[\\s](_+)(?!_)(?=punct)' // (4) ___# can only be Left Delimiter - + '|(?!_)punct(_+)(?!_)(?=punct)', 'gu') // (5) #___# can be either Left or Right Delimiter - .replace(/notPunctSpace/g, _notPunctuationOrSpace) - .replace(/punctSpace/g, _punctuationOrSpace) - .replace(/punct/g, _punctuation) - .getRegex(); -const anyPunctuation = edit(/\\(punct)/, 'gu') - .replace(/punct/g, _punctuation) - .getRegex(); -const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/) - .replace('scheme', /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/) - .replace('email', /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/) - .getRegex(); -const _inlineComment = edit(_comment).replace('(?:-->|$)', '-->').getRegex(); -const tag = edit('^comment' - + '|^' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. - + '|^' // declaration, e.g. - + '|^') // CDATA section - .replace('comment', _inlineComment) - .replace('attribute', /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/) - .getRegex(); -const _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; -const link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/) - .replace('label', _inlineLabel) - .replace('href', /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/) - .replace('title', /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/) - .getRegex(); -const reflink = edit(/^!?\[(label)\]\[(ref)\]/) - .replace('label', _inlineLabel) - .replace('ref', _blockLabel) - .getRegex(); -const nolink = edit(/^!?\[(ref)\](?:\[\])?/) - .replace('ref', _blockLabel) - .getRegex(); -const reflinkSearch = edit('reflink|nolink(?!\\()', 'g') - .replace('reflink', reflink) - .replace('nolink', nolink) - .getRegex(); -/** - * Normal Inline Grammar - */ -const inlineNormal = { - _backpedal: noopTest, // only used for GFM url - anyPunctuation, - autolink, - blockSkip, - br, - code: inlineCode, - del: noopTest, - emStrongLDelim, - emStrongRDelimAst, - emStrongRDelimUnd, - escape: escape$1, - link, - nolink, - punctuation, - reflink, - reflinkSearch, - tag, - text: inlineText, - url: noopTest, -}; -/** - * Pedantic Inline Grammar - */ -const inlinePedantic = { - ...inlineNormal, - link: edit(/^!?\[(label)\]\((.*?)\)/) - .replace('label', _inlineLabel) - .getRegex(), - reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) - .replace('label', _inlineLabel) - .getRegex(), -}; -/** - * GFM Inline Grammar - */ -const inlineGfm = { - ...inlineNormal, - emStrongRDelimAst: emStrongRDelimAstGfm, - emStrongLDelim: emStrongLDelimGfm, - url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, 'i') - .replace('email', /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/) - .getRegex(), - _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, - del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/, - text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\': '>', - '"': '"', - "'": ''', -}; -const getEscapeReplacement = (ch) => escapeReplacements[ch]; -function escape(html, encode) { - if (encode) { - if (other.escapeTest.test(html)) { - return html.replace(other.escapeReplace, getEscapeReplacement); - } - } - else { - if (other.escapeTestNoEncode.test(html)) { - return html.replace(other.escapeReplaceNoEncode, getEscapeReplacement); - } - } - return html; -} -function cleanUrl(href) { - try { - href = encodeURI(href).replace(other.percentDecode, '%'); - } - catch { - return null; - } - return href; -} -function splitCells(tableRow, count) { - // ensure that every cell-delimiting pipe has a space - // before it to distinguish it from an escaped pipe - const row = tableRow.replace(other.findPipe, (match, offset, str) => { - let escaped = false; - let curr = offset; - while (--curr >= 0 && str[curr] === '\\') - escaped = !escaped; - if (escaped) { - // odd number of slashes means | is escaped - // so we leave it alone - return '|'; - } - else { - // add space before unescaped | - return ' |'; - } - }), cells = row.split(other.splitPipe); - let i = 0; - // First/last cell in a row cannot be empty if it has no leading/trailing pipe - if (!cells[0].trim()) { - cells.shift(); - } - if (cells.length > 0 && !cells.at(-1)?.trim()) { - cells.pop(); - } - if (count) { - if (cells.length > count) { - cells.splice(count); - } - else { - while (cells.length < count) - cells.push(''); - } - } - for (; i < cells.length; i++) { - // leading or trailing whitespace is ignored per the gfm spec - cells[i] = cells[i].trim().replace(other.slashPipe, '|'); - } - return cells; -} -/** - * Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). - * /c*$/ is vulnerable to REDOS. - * - * @param str - * @param c - * @param invert Remove suffix of non-c chars instead. Default falsey. - */ -function rtrim(str, c, invert) { - const l = str.length; - if (l === 0) { - return ''; - } - // Length of suffix matching the invert condition. - let suffLen = 0; - // Step left until we fail to match the invert condition. - while (suffLen < l) { - const currChar = str.charAt(l - suffLen - 1); - if (currChar === c && true) { - suffLen++; - } - else { - break; - } - } - return str.slice(0, l - suffLen); -} -function findClosingBracket(str, b) { - if (str.indexOf(b[1]) === -1) { - return -1; - } - let level = 0; - for (let i = 0; i < str.length; i++) { - if (str[i] === '\\') { - i++; - } - else if (str[i] === b[0]) { - level++; - } - else if (str[i] === b[1]) { - level--; - if (level < 0) { - return i; - } - } - } - return -1; -} - -function outputLink(cap, link, raw, lexer, rules) { - const href = link.href; - const title = link.title || null; - const text = cap[1].replace(rules.other.outputLinkReplace, '$1'); - if (cap[0].charAt(0) !== '!') { - lexer.state.inLink = true; - const token = { - type: 'link', - raw, - href, - title, - text, - tokens: lexer.inlineTokens(text), - }; - lexer.state.inLink = false; - return token; - } - return { - type: 'image', - raw, - href, - title, - text, - }; -} -function indentCodeCompensation(raw, text, rules) { - const matchIndentToCode = raw.match(rules.other.indentCodeCompensation); - if (matchIndentToCode === null) { - return text; - } - const indentToCode = matchIndentToCode[1]; - return text - .split('\n') - .map(node => { - const matchIndentInNode = node.match(rules.other.beginningSpace); - if (matchIndentInNode === null) { - return node; - } - const [indentInNode] = matchIndentInNode; - if (indentInNode.length >= indentToCode.length) { - return node.slice(indentToCode.length); - } - return node; - }) - .join('\n'); -} -/** - * Tokenizer - */ -class _Tokenizer { - options; - rules; // set by the lexer - lexer; // set by the lexer - constructor(options) { - this.options = options || _defaults; - } - space(src) { - const cap = this.rules.block.newline.exec(src); - if (cap && cap[0].length > 0) { - return { - type: 'space', - raw: cap[0], - }; - } - } - code(src) { - const cap = this.rules.block.code.exec(src); - if (cap) { - const text = cap[0].replace(this.rules.other.codeRemoveIndent, ''); - return { - type: 'code', - raw: cap[0], - codeBlockStyle: 'indented', - text: !this.options.pedantic - ? rtrim(text, '\n') - : text, - }; - } - } - fences(src) { - const cap = this.rules.block.fences.exec(src); - if (cap) { - const raw = cap[0]; - const text = indentCodeCompensation(raw, cap[3] || '', this.rules); - return { - type: 'code', - raw, - lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, '$1') : cap[2], - text, - }; - } - } - heading(src) { - const cap = this.rules.block.heading.exec(src); - if (cap) { - let text = cap[2].trim(); - // remove trailing #s - if (this.rules.other.endingHash.test(text)) { - const trimmed = rtrim(text, '#'); - if (this.options.pedantic) { - text = trimmed.trim(); - } - else if (!trimmed || this.rules.other.endingSpaceChar.test(trimmed)) { - // CommonMark requires space before trailing #s - text = trimmed.trim(); - } - } - return { - type: 'heading', - raw: cap[0], - depth: cap[1].length, - text, - tokens: this.lexer.inline(text), - }; - } - } - hr(src) { - const cap = this.rules.block.hr.exec(src); - if (cap) { - return { - type: 'hr', - raw: rtrim(cap[0], '\n'), - }; - } - } - blockquote(src) { - const cap = this.rules.block.blockquote.exec(src); - if (cap) { - let lines = rtrim(cap[0], '\n').split('\n'); - let raw = ''; - let text = ''; - const tokens = []; - while (lines.length > 0) { - let inBlockquote = false; - const currentLines = []; - let i; - for (i = 0; i < lines.length; i++) { - // get lines up to a continuation - if (this.rules.other.blockquoteStart.test(lines[i])) { - currentLines.push(lines[i]); - inBlockquote = true; - } - else if (!inBlockquote) { - currentLines.push(lines[i]); - } - else { - break; - } - } - lines = lines.slice(i); - const currentRaw = currentLines.join('\n'); - const currentText = currentRaw - // precede setext continuation with 4 spaces so it isn't a setext - .replace(this.rules.other.blockquoteSetextReplace, '\n $1') - .replace(this.rules.other.blockquoteSetextReplace2, ''); - raw = raw ? `${raw}\n${currentRaw}` : currentRaw; - text = text ? `${text}\n${currentText}` : currentText; - // parse blockquote lines as top level tokens - // merge paragraphs if this is a continuation - const top = this.lexer.state.top; - this.lexer.state.top = true; - this.lexer.blockTokens(currentText, tokens, true); - this.lexer.state.top = top; - // if there is no continuation then we are done - if (lines.length === 0) { - break; - } - const lastToken = tokens.at(-1); - if (lastToken?.type === 'code') { - // blockquote continuation cannot be preceded by a code block - break; - } - else if (lastToken?.type === 'blockquote') { - // include continuation in nested blockquote - const oldToken = lastToken; - const newText = oldToken.raw + '\n' + lines.join('\n'); - const newToken = this.blockquote(newText); - tokens[tokens.length - 1] = newToken; - raw = raw.substring(0, raw.length - oldToken.raw.length) + newToken.raw; - text = text.substring(0, text.length - oldToken.text.length) + newToken.text; - break; - } - else if (lastToken?.type === 'list') { - // include continuation in nested list - const oldToken = lastToken; - const newText = oldToken.raw + '\n' + lines.join('\n'); - const newToken = this.list(newText); - tokens[tokens.length - 1] = newToken; - raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw; - text = text.substring(0, text.length - oldToken.raw.length) + newToken.raw; - lines = newText.substring(tokens.at(-1).raw.length).split('\n'); - continue; - } - } - return { - type: 'blockquote', - raw, - tokens, - text, - }; - } - } - list(src) { - let cap = this.rules.block.list.exec(src); - if (cap) { - let bull = cap[1].trim(); - const isordered = bull.length > 1; - const list = { - type: 'list', - raw: '', - ordered: isordered, - start: isordered ? +bull.slice(0, -1) : '', - loose: false, - items: [], - }; - bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`; - if (this.options.pedantic) { - bull = isordered ? bull : '[*+-]'; - } - // Get next list item - const itemRegex = this.rules.other.listItemRegex(bull); - let endsWithBlankLine = false; - // Check if current bullet point can start a new List Item - while (src) { - let endEarly = false; - let raw = ''; - let itemContents = ''; - if (!(cap = itemRegex.exec(src))) { - break; - } - if (this.rules.block.hr.test(src)) { // End list if bullet was actually HR (possibly move into itemRegex?) - break; - } - raw = cap[0]; - src = src.substring(raw.length); - let line = cap[2].split('\n', 1)[0].replace(this.rules.other.listReplaceTabs, (t) => ' '.repeat(3 * t.length)); - let nextLine = src.split('\n', 1)[0]; - let blankLine = !line.trim(); - let indent = 0; - if (this.options.pedantic) { - indent = 2; - itemContents = line.trimStart(); - } - else if (blankLine) { - indent = cap[1].length + 1; - } - else { - indent = cap[2].search(this.rules.other.nonSpaceChar); // Find first non-space char - indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent - itemContents = line.slice(indent); - indent += cap[1].length; - } - if (blankLine && this.rules.other.blankLine.test(nextLine)) { // Items begin with at most one blank line - raw += nextLine + '\n'; - src = src.substring(nextLine.length + 1); - endEarly = true; - } - if (!endEarly) { - const nextBulletRegex = this.rules.other.nextBulletRegex(indent); - const hrRegex = this.rules.other.hrRegex(indent); - const fencesBeginRegex = this.rules.other.fencesBeginRegex(indent); - const headingBeginRegex = this.rules.other.headingBeginRegex(indent); - const htmlBeginRegex = this.rules.other.htmlBeginRegex(indent); - // Check if following lines should be included in List Item - while (src) { - const rawLine = src.split('\n', 1)[0]; - let nextLineWithoutTabs; - nextLine = rawLine; - // Re-align to follow commonmark nesting rules - if (this.options.pedantic) { - nextLine = nextLine.replace(this.rules.other.listReplaceNesting, ' '); - nextLineWithoutTabs = nextLine; - } - else { - nextLineWithoutTabs = nextLine.replace(this.rules.other.tabCharGlobal, ' '); - } - // End list item if found code fences - if (fencesBeginRegex.test(nextLine)) { - break; - } - // End list item if found start of new heading - if (headingBeginRegex.test(nextLine)) { - break; - } - // End list item if found start of html block - if (htmlBeginRegex.test(nextLine)) { - break; - } - // End list item if found start of new bullet - if (nextBulletRegex.test(nextLine)) { - break; - } - // Horizontal rule found - if (hrRegex.test(nextLine)) { - break; - } - if (nextLineWithoutTabs.search(this.rules.other.nonSpaceChar) >= indent || !nextLine.trim()) { // Dedent if possible - itemContents += '\n' + nextLineWithoutTabs.slice(indent); - } - else { - // not enough indentation - if (blankLine) { - break; - } - // paragraph continuation unless last line was a different block level element - if (line.replace(this.rules.other.tabCharGlobal, ' ').search(this.rules.other.nonSpaceChar) >= 4) { // indented code block - break; - } - if (fencesBeginRegex.test(line)) { - break; - } - if (headingBeginRegex.test(line)) { - break; - } - if (hrRegex.test(line)) { - break; - } - itemContents += '\n' + nextLine; - } - if (!blankLine && !nextLine.trim()) { // Check if current line is blank - blankLine = true; - } - raw += rawLine + '\n'; - src = src.substring(rawLine.length + 1); - line = nextLineWithoutTabs.slice(indent); - } - } - if (!list.loose) { - // If the previous item ended with a blank line, the list is loose - if (endsWithBlankLine) { - list.loose = true; - } - else if (this.rules.other.doubleBlankLine.test(raw)) { - endsWithBlankLine = true; - } - } - let istask = null; - let ischecked; - // Check for task list items - if (this.options.gfm) { - istask = this.rules.other.listIsTask.exec(itemContents); - if (istask) { - ischecked = istask[0] !== '[ ] '; - itemContents = itemContents.replace(this.rules.other.listReplaceTask, ''); - } - } - list.items.push({ - type: 'list_item', - raw, - task: !!istask, - checked: ischecked, - loose: false, - text: itemContents, - tokens: [], - }); - list.raw += raw; - } - // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic - const lastItem = list.items.at(-1); - if (lastItem) { - lastItem.raw = lastItem.raw.trimEnd(); - lastItem.text = lastItem.text.trimEnd(); - } - else { - // not a list since there were no items - return; - } - list.raw = list.raw.trimEnd(); - // Item child tokens handled here at end because we needed to have the final item to trim it first - for (let i = 0; i < list.items.length; i++) { - this.lexer.state.top = false; - list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []); - if (!list.loose) { - // Check if list should be loose - const spacers = list.items[i].tokens.filter(t => t.type === 'space'); - const hasMultipleLineBreaks = spacers.length > 0 && spacers.some(t => this.rules.other.anyLine.test(t.raw)); - list.loose = hasMultipleLineBreaks; - } - } - // Set all items to loose if list is loose - if (list.loose) { - for (let i = 0; i < list.items.length; i++) { - list.items[i].loose = true; - } - } - return list; - } - } - html(src) { - const cap = this.rules.block.html.exec(src); - if (cap) { - const token = { - type: 'html', - block: true, - raw: cap[0], - pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', - text: cap[0], - }; - return token; - } - } - def(src) { - const cap = this.rules.block.def.exec(src); - if (cap) { - const tag = cap[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal, ' '); - const href = cap[2] ? cap[2].replace(this.rules.other.hrefBrackets, '$1').replace(this.rules.inline.anyPunctuation, '$1') : ''; - const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, '$1') : cap[3]; - return { - type: 'def', - tag, - raw: cap[0], - href, - title, - }; - } - } - table(src) { - const cap = this.rules.block.table.exec(src); - if (!cap) { - return; - } - if (!this.rules.other.tableDelimiter.test(cap[2])) { - // delimiter row must have a pipe (|) or colon (:) otherwise it is a setext heading - return; - } - const headers = splitCells(cap[1]); - const aligns = cap[2].replace(this.rules.other.tableAlignChars, '').split('|'); - const rows = cap[3]?.trim() ? cap[3].replace(this.rules.other.tableRowBlankLine, '').split('\n') : []; - const item = { - type: 'table', - raw: cap[0], - header: [], - align: [], - rows: [], - }; - if (headers.length !== aligns.length) { - // header and align columns must be equal, rows can be different. - return; - } - for (const align of aligns) { - if (this.rules.other.tableAlignRight.test(align)) { - item.align.push('right'); - } - else if (this.rules.other.tableAlignCenter.test(align)) { - item.align.push('center'); - } - else if (this.rules.other.tableAlignLeft.test(align)) { - item.align.push('left'); - } - else { - item.align.push(null); - } - } - for (let i = 0; i < headers.length; i++) { - item.header.push({ - text: headers[i], - tokens: this.lexer.inline(headers[i]), - header: true, - align: item.align[i], - }); - } - for (const row of rows) { - item.rows.push(splitCells(row, item.header.length).map((cell, i) => { - return { - text: cell, - tokens: this.lexer.inline(cell), - header: false, - align: item.align[i], - }; - })); - } - return item; - } - lheading(src) { - const cap = this.rules.block.lheading.exec(src); - if (cap) { - return { - type: 'heading', - raw: cap[0], - depth: cap[2].charAt(0) === '=' ? 1 : 2, - text: cap[1], - tokens: this.lexer.inline(cap[1]), - }; - } - } - paragraph(src) { - const cap = this.rules.block.paragraph.exec(src); - if (cap) { - const text = cap[1].charAt(cap[1].length - 1) === '\n' - ? cap[1].slice(0, -1) - : cap[1]; - return { - type: 'paragraph', - raw: cap[0], - text, - tokens: this.lexer.inline(text), - }; - } - } - text(src) { - const cap = this.rules.block.text.exec(src); - if (cap) { - return { - type: 'text', - raw: cap[0], - text: cap[0], - tokens: this.lexer.inline(cap[0]), - }; - } - } - escape(src) { - const cap = this.rules.inline.escape.exec(src); - if (cap) { - return { - type: 'escape', - raw: cap[0], - text: cap[1], - }; - } - } - tag(src) { - const cap = this.rules.inline.tag.exec(src); - if (cap) { - if (!this.lexer.state.inLink && this.rules.other.startATag.test(cap[0])) { - this.lexer.state.inLink = true; - } - else if (this.lexer.state.inLink && this.rules.other.endATag.test(cap[0])) { - this.lexer.state.inLink = false; - } - if (!this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(cap[0])) { - this.lexer.state.inRawBlock = true; - } - else if (this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(cap[0])) { - this.lexer.state.inRawBlock = false; - } - return { - type: 'html', - raw: cap[0], - inLink: this.lexer.state.inLink, - inRawBlock: this.lexer.state.inRawBlock, - block: false, - text: cap[0], - }; - } - } - link(src) { - const cap = this.rules.inline.link.exec(src); - if (cap) { - const trimmedUrl = cap[2].trim(); - if (!this.options.pedantic && this.rules.other.startAngleBracket.test(trimmedUrl)) { - // commonmark requires matching angle brackets - if (!(this.rules.other.endAngleBracket.test(trimmedUrl))) { - return; - } - // ending angle bracket cannot be escaped - const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\'); - if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { - return; - } - } - else { - // find closing parenthesis - const lastParenIndex = findClosingBracket(cap[2], '()'); - if (lastParenIndex > -1) { - const start = cap[0].indexOf('!') === 0 ? 5 : 4; - const linkLen = start + cap[1].length + lastParenIndex; - cap[2] = cap[2].substring(0, lastParenIndex); - cap[0] = cap[0].substring(0, linkLen).trim(); - cap[3] = ''; - } - } - let href = cap[2]; - let title = ''; - if (this.options.pedantic) { - // split pedantic href and title - const link = this.rules.other.pedanticHrefTitle.exec(href); - if (link) { - href = link[1]; - title = link[3]; - } - } - else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - href = href.trim(); - if (this.rules.other.startAngleBracket.test(href)) { - if (this.options.pedantic && !(this.rules.other.endAngleBracket.test(trimmedUrl))) { - // pedantic allows starting angle bracket without ending angle bracket - href = href.slice(1); - } - else { - href = href.slice(1, -1); - } - } - return outputLink(cap, { - href: href ? href.replace(this.rules.inline.anyPunctuation, '$1') : href, - title: title ? title.replace(this.rules.inline.anyPunctuation, '$1') : title, - }, cap[0], this.lexer, this.rules); - } - } - reflink(src, links) { - let cap; - if ((cap = this.rules.inline.reflink.exec(src)) - || (cap = this.rules.inline.nolink.exec(src))) { - const linkString = (cap[2] || cap[1]).replace(this.rules.other.multipleSpaceGlobal, ' '); - const link = links[linkString.toLowerCase()]; - if (!link) { - const text = cap[0].charAt(0); - return { - type: 'text', - raw: text, - text, - }; - } - return outputLink(cap, link, cap[0], this.lexer, this.rules); - } - } - emStrong(src, maskedSrc, prevChar = '') { - let match = this.rules.inline.emStrongLDelim.exec(src); - if (!match) - return; - // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well - if (match[3] && prevChar.match(this.rules.other.unicodeAlphaNumeric)) - return; - const nextChar = match[1] || match[2] || ''; - if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) { - // unicode Regex counts emoji as 1 char; spread into array for proper count (used multiple times below) - const lLength = [...match[0]].length - 1; - let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0; - const endReg = match[0][0] === '*' ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; - endReg.lastIndex = 0; - // Clip maskedSrc to same section of string as src (move to lexer?) - maskedSrc = maskedSrc.slice(-1 * src.length + lLength); - while ((match = endReg.exec(maskedSrc)) != null) { - rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6]; - if (!rDelim) - continue; // skip single * in __abc*abc__ - rLength = [...rDelim].length; - if (match[3] || match[4]) { // found another Left Delim - delimTotal += rLength; - continue; - } - else if (match[5] || match[6]) { // either Left or Right Delim - if (lLength % 3 && !((lLength + rLength) % 3)) { - midDelimTotal += rLength; - continue; // CommonMark Emphasis Rules 9-10 - } - } - delimTotal -= rLength; - if (delimTotal > 0) - continue; // Haven't found enough closing delimiters - // Remove extra characters. *a*** -> *a* - rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); - // char length can be >1 for unicode characters; - const lastCharLength = [...match[0]][0].length; - const raw = src.slice(0, lLength + match.index + lastCharLength + rLength); - // Create `em` if smallest delimiter has odd char count. *a*** - if (Math.min(lLength, rLength) % 2) { - const text = raw.slice(1, -1); - return { - type: 'em', - raw, - text, - tokens: this.lexer.inlineTokens(text), - }; - } - // Create 'strong' if smallest delimiter has even char count. **a*** - const text = raw.slice(2, -2); - return { - type: 'strong', - raw, - text, - tokens: this.lexer.inlineTokens(text), - }; - } - } - } - codespan(src) { - const cap = this.rules.inline.code.exec(src); - if (cap) { - let text = cap[2].replace(this.rules.other.newLineCharGlobal, ' '); - const hasNonSpaceChars = this.rules.other.nonSpaceChar.test(text); - const hasSpaceCharsOnBothEnds = this.rules.other.startingSpaceChar.test(text) && this.rules.other.endingSpaceChar.test(text); - if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { - text = text.substring(1, text.length - 1); - } - return { - type: 'codespan', - raw: cap[0], - text, - }; - } - } - br(src) { - const cap = this.rules.inline.br.exec(src); - if (cap) { - return { - type: 'br', - raw: cap[0], - }; - } - } - del(src) { - const cap = this.rules.inline.del.exec(src); - if (cap) { - return { - type: 'del', - raw: cap[0], - text: cap[2], - tokens: this.lexer.inlineTokens(cap[2]), - }; - } - } - autolink(src) { - const cap = this.rules.inline.autolink.exec(src); - if (cap) { - let text, href; - if (cap[2] === '@') { - text = cap[1]; - href = 'mailto:' + text; - } - else { - text = cap[1]; - href = text; - } - return { - type: 'link', - raw: cap[0], - text, - href, - tokens: [ - { - type: 'text', - raw: text, - text, - }, - ], - }; - } - } - url(src) { - let cap; - if (cap = this.rules.inline.url.exec(src)) { - let text, href; - if (cap[2] === '@') { - text = cap[0]; - href = 'mailto:' + text; - } - else { - // do extended autolink path validation - let prevCapZero; - do { - prevCapZero = cap[0]; - cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? ''; - } while (prevCapZero !== cap[0]); - text = cap[0]; - if (cap[1] === 'www.') { - href = 'http://' + cap[0]; - } - else { - href = cap[0]; - } - } - return { - type: 'link', - raw: cap[0], - text, - href, - tokens: [ - { - type: 'text', - raw: text, - text, - }, - ], - }; - } - } - inlineText(src) { - const cap = this.rules.inline.text.exec(src); - if (cap) { - const escaped = this.lexer.state.inRawBlock; - return { - type: 'text', - raw: cap[0], - text: cap[0], - escaped, - }; - } - } -} - -/** - * Block Lexer - */ -class _Lexer { - tokens; - options; - state; - tokenizer; - inlineQueue; - constructor(options) { - // TokenList cannot be created in one go - this.tokens = []; - this.tokens.links = Object.create(null); - this.options = options || _defaults; - this.options.tokenizer = this.options.tokenizer || new _Tokenizer(); - this.tokenizer = this.options.tokenizer; - this.tokenizer.options = this.options; - this.tokenizer.lexer = this; - this.inlineQueue = []; - this.state = { - inLink: false, - inRawBlock: false, - top: true, - }; - const rules = { - other, - block: block.normal, - inline: inline.normal, - }; - if (this.options.pedantic) { - rules.block = block.pedantic; - rules.inline = inline.pedantic; - } - else if (this.options.gfm) { - rules.block = block.gfm; - if (this.options.breaks) { - rules.inline = inline.breaks; - } - else { - rules.inline = inline.gfm; - } - } - this.tokenizer.rules = rules; - } - /** - * Expose Rules - */ - static get rules() { - return { - block, - inline, - }; - } - /** - * Static Lex Method - */ - static lex(src, options) { - const lexer = new _Lexer(options); - return lexer.lex(src); - } - /** - * Static Lex Inline Method - */ - static lexInline(src, options) { - const lexer = new _Lexer(options); - return lexer.inlineTokens(src); - } - /** - * Preprocessing - */ - lex(src) { - src = src.replace(other.carriageReturn, '\n'); - this.blockTokens(src, this.tokens); - for (let i = 0; i < this.inlineQueue.length; i++) { - const next = this.inlineQueue[i]; - this.inlineTokens(next.src, next.tokens); - } - this.inlineQueue = []; - return this.tokens; - } - blockTokens(src, tokens = [], lastParagraphClipped = false) { - if (this.options.pedantic) { - src = src.replace(other.tabCharGlobal, ' ').replace(other.spaceLine, ''); - } - while (src) { - let token; - if (this.options.extensions?.block?.some((extTokenizer) => { - if (token = extTokenizer.call({ lexer: this }, src, tokens)) { - src = src.substring(token.raw.length); - tokens.push(token); - return true; - } - return false; - })) { - continue; - } - // newline - if (token = this.tokenizer.space(src)) { - src = src.substring(token.raw.length); - const lastToken = tokens.at(-1); - if (token.raw.length === 1 && lastToken !== undefined) { - // if there's a single \n as a spacer, it's terminating the last line, - // so move it there so that we don't get unnecessary paragraph tags - lastToken.raw += '\n'; - } - else { - tokens.push(token); - } - continue; - } - // code - if (token = this.tokenizer.code(src)) { - src = src.substring(token.raw.length); - const lastToken = tokens.at(-1); - // An indented code block cannot interrupt a paragraph. - if (lastToken?.type === 'paragraph' || lastToken?.type === 'text') { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - this.inlineQueue.at(-1).src = lastToken.text; - } - else { - tokens.push(token); - } - continue; - } - // fences - if (token = this.tokenizer.fences(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // heading - if (token = this.tokenizer.heading(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // hr - if (token = this.tokenizer.hr(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // blockquote - if (token = this.tokenizer.blockquote(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // list - if (token = this.tokenizer.list(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // html - if (token = this.tokenizer.html(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // def - if (token = this.tokenizer.def(src)) { - src = src.substring(token.raw.length); - const lastToken = tokens.at(-1); - if (lastToken?.type === 'paragraph' || lastToken?.type === 'text') { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.raw; - this.inlineQueue.at(-1).src = lastToken.text; - } - else if (!this.tokens.links[token.tag]) { - this.tokens.links[token.tag] = { - href: token.href, - title: token.title, - }; - } - continue; - } - // table (gfm) - if (token = this.tokenizer.table(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // lheading - if (token = this.tokenizer.lheading(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // top-level paragraph - // prevent paragraph consuming extensions by clipping 'src' to extension start - let cutSrc = src; - if (this.options.extensions?.startBlock) { - let startIndex = Infinity; - const tempSrc = src.slice(1); - let tempStart; - this.options.extensions.startBlock.forEach((getStartIndex) => { - tempStart = getStartIndex.call({ lexer: this }, tempSrc); - if (typeof tempStart === 'number' && tempStart >= 0) { - startIndex = Math.min(startIndex, tempStart); - } - }); - if (startIndex < Infinity && startIndex >= 0) { - cutSrc = src.substring(0, startIndex + 1); - } - } - if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) { - const lastToken = tokens.at(-1); - if (lastParagraphClipped && lastToken?.type === 'paragraph') { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - this.inlineQueue.pop(); - this.inlineQueue.at(-1).src = lastToken.text; - } - else { - tokens.push(token); - } - lastParagraphClipped = cutSrc.length !== src.length; - src = src.substring(token.raw.length); - continue; - } - // text - if (token = this.tokenizer.text(src)) { - src = src.substring(token.raw.length); - const lastToken = tokens.at(-1); - if (lastToken?.type === 'text') { - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - this.inlineQueue.pop(); - this.inlineQueue.at(-1).src = lastToken.text; - } - else { - tokens.push(token); - } - continue; - } - if (src) { - const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); - if (this.options.silent) { - console.error(errMsg); - break; - } - else { - throw new Error(errMsg); - } - } - } - this.state.top = true; - return tokens; - } - inline(src, tokens = []) { - this.inlineQueue.push({ src, tokens }); - return tokens; - } - /** - * Lexing/Compiling - */ - inlineTokens(src, tokens = []) { - // String with links masked to avoid interference with em and strong - let maskedSrc = src; - let match = null; - // Mask out reflinks - if (this.tokens.links) { - const links = Object.keys(this.tokens.links); - if (links.length > 0) { - while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) { - if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) { - maskedSrc = maskedSrc.slice(0, match.index) - + '[' + 'a'.repeat(match[0].length - 2) + ']' - + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); - } - } - } - } - // Mask out other blocks - while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) { - maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); - } - // Mask out escaped characters - while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) { - maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex); - } - let keepPrevChar = false; - let prevChar = ''; - while (src) { - if (!keepPrevChar) { - prevChar = ''; - } - keepPrevChar = false; - let token; - // extensions - if (this.options.extensions?.inline?.some((extTokenizer) => { - if (token = extTokenizer.call({ lexer: this }, src, tokens)) { - src = src.substring(token.raw.length); - tokens.push(token); - return true; - } - return false; - })) { - continue; - } - // escape - if (token = this.tokenizer.escape(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // tag - if (token = this.tokenizer.tag(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // link - if (token = this.tokenizer.link(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // reflink, nolink - if (token = this.tokenizer.reflink(src, this.tokens.links)) { - src = src.substring(token.raw.length); - const lastToken = tokens.at(-1); - if (token.type === 'text' && lastToken?.type === 'text') { - lastToken.raw += token.raw; - lastToken.text += token.text; - } - else { - tokens.push(token); - } - continue; - } - // em & strong - if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // code - if (token = this.tokenizer.codespan(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // br - if (token = this.tokenizer.br(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // del (gfm) - if (token = this.tokenizer.del(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // autolink - if (token = this.tokenizer.autolink(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // url (gfm) - if (!this.state.inLink && (token = this.tokenizer.url(src))) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - // text - // prevent inlineText consuming extensions by clipping 'src' to extension start - let cutSrc = src; - if (this.options.extensions?.startInline) { - let startIndex = Infinity; - const tempSrc = src.slice(1); - let tempStart; - this.options.extensions.startInline.forEach((getStartIndex) => { - tempStart = getStartIndex.call({ lexer: this }, tempSrc); - if (typeof tempStart === 'number' && tempStart >= 0) { - startIndex = Math.min(startIndex, tempStart); - } - }); - if (startIndex < Infinity && startIndex >= 0) { - cutSrc = src.substring(0, startIndex + 1); - } - } - if (token = this.tokenizer.inlineText(cutSrc)) { - src = src.substring(token.raw.length); - if (token.raw.slice(-1) !== '_') { // Track prevChar before string of ____ started - prevChar = token.raw.slice(-1); - } - keepPrevChar = true; - const lastToken = tokens.at(-1); - if (lastToken?.type === 'text') { - lastToken.raw += token.raw; - lastToken.text += token.text; - } - else { - tokens.push(token); - } - continue; - } - if (src) { - const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); - if (this.options.silent) { - console.error(errMsg); - break; - } - else { - throw new Error(errMsg); - } - } - } - return tokens; - } -} - -/** - * Renderer - */ -class _Renderer { - options; - parser; // set by the parser - constructor(options) { - this.options = options || _defaults; - } - space(token) { - return ''; - } - code({ text, lang, escaped }) { - const langString = (lang || '').match(other.notSpaceStart)?.[0]; - const code = text.replace(other.endingNewline, '') + '\n'; - if (!langString) { - return '
'
-                + (escaped ? code : escape(code, true))
-                + '
\n'; - } - return '
'
-            + (escaped ? code : escape(code, true))
-            + '
\n'; - } - blockquote({ tokens }) { - const body = this.parser.parse(tokens); - return `
\n${body}
\n`; - } - html({ text }) { - return text; - } - heading({ tokens, depth }) { - return `${this.parser.parseInline(tokens)}\n`; - } - hr(token) { - return '
\n'; - } - list(token) { - const ordered = token.ordered; - const start = token.start; - let body = ''; - for (let j = 0; j < token.items.length; j++) { - const item = token.items[j]; - body += this.listitem(item); - } - const type = ordered ? 'ol' : 'ul'; - const startAttr = (ordered && start !== 1) ? (' start="' + start + '"') : ''; - return '<' + type + startAttr + '>\n' + body + '\n'; - } - listitem(item) { - let itemBody = ''; - if (item.task) { - const checkbox = this.checkbox({ checked: !!item.checked }); - if (item.loose) { - if (item.tokens[0]?.type === 'paragraph') { - item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; - if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { - item.tokens[0].tokens[0].text = checkbox + ' ' + escape(item.tokens[0].tokens[0].text); - item.tokens[0].tokens[0].escaped = true; - } - } - else { - item.tokens.unshift({ - type: 'text', - raw: checkbox + ' ', - text: checkbox + ' ', - escaped: true, - }); - } - } - else { - itemBody += checkbox + ' '; - } - } - itemBody += this.parser.parse(item.tokens, !!item.loose); - return `
  • ${itemBody}
  • \n`; - } - checkbox({ checked }) { - return ''; - } - paragraph({ tokens }) { - return `

    ${this.parser.parseInline(tokens)}

    \n`; - } - table(token) { - let header = ''; - // header - let cell = ''; - for (let j = 0; j < token.header.length; j++) { - cell += this.tablecell(token.header[j]); - } - header += this.tablerow({ text: cell }); - let body = ''; - for (let j = 0; j < token.rows.length; j++) { - const row = token.rows[j]; - cell = ''; - for (let k = 0; k < row.length; k++) { - cell += this.tablecell(row[k]); - } - body += this.tablerow({ text: cell }); - } - if (body) - body = `${body}`; - return '\n' - + '\n' - + header - + '\n' - + body - + '
    \n'; - } - tablerow({ text }) { - return `\n${text}\n`; - } - tablecell(token) { - const content = this.parser.parseInline(token.tokens); - const type = token.header ? 'th' : 'td'; - const tag = token.align - ? `<${type} align="${token.align}">` - : `<${type}>`; - return tag + content + `\n`; - } - /** - * span level renderer - */ - strong({ tokens }) { - return `${this.parser.parseInline(tokens)}`; - } - em({ tokens }) { - return `${this.parser.parseInline(tokens)}`; - } - codespan({ text }) { - return `${escape(text, true)}`; - } - br(token) { - return '
    '; - } - del({ tokens }) { - return `${this.parser.parseInline(tokens)}`; - } - link({ href, title, tokens }) { - const text = this.parser.parseInline(tokens); - const cleanHref = cleanUrl(href); - if (cleanHref === null) { - return text; - } - href = cleanHref; - let out = '
    '; - return out; - } - image({ href, title, text }) { - const cleanHref = cleanUrl(href); - if (cleanHref === null) { - return escape(text); - } - href = cleanHref; - let out = `${text} { - const tokens = genericToken[childTokens].flat(Infinity); - values = values.concat(this.walkTokens(tokens, callback)); - }); - } - else if (genericToken.tokens) { - values = values.concat(this.walkTokens(genericToken.tokens, callback)); - } - } - } - } - return values; - } - use(...args) { - const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} }; - args.forEach((pack) => { - // copy options to new object - const opts = { ...pack }; - // set async to true if it was set to true before - opts.async = this.defaults.async || opts.async || false; - // ==-- Parse "addon" extensions --== // - if (pack.extensions) { - pack.extensions.forEach((ext) => { - if (!ext.name) { - throw new Error('extension name required'); - } - if ('renderer' in ext) { // Renderer extensions - const prevRenderer = extensions.renderers[ext.name]; - if (prevRenderer) { - // Replace extension with func to run new extension but fall back if false - extensions.renderers[ext.name] = function (...args) { - let ret = ext.renderer.apply(this, args); - if (ret === false) { - ret = prevRenderer.apply(this, args); - } - return ret; - }; - } - else { - extensions.renderers[ext.name] = ext.renderer; - } - } - if ('tokenizer' in ext) { // Tokenizer Extensions - if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) { - throw new Error("extension level must be 'block' or 'inline'"); - } - const extLevel = extensions[ext.level]; - if (extLevel) { - extLevel.unshift(ext.tokenizer); - } - else { - extensions[ext.level] = [ext.tokenizer]; - } - if (ext.start) { // Function to check for start of token - if (ext.level === 'block') { - if (extensions.startBlock) { - extensions.startBlock.push(ext.start); - } - else { - extensions.startBlock = [ext.start]; - } - } - else if (ext.level === 'inline') { - if (extensions.startInline) { - extensions.startInline.push(ext.start); - } - else { - extensions.startInline = [ext.start]; - } - } - } - } - if ('childTokens' in ext && ext.childTokens) { // Child tokens to be visited by walkTokens - extensions.childTokens[ext.name] = ext.childTokens; - } - }); - opts.extensions = extensions; - } - // ==-- Parse "overwrite" extensions --== // - if (pack.renderer) { - const renderer = this.defaults.renderer || new _Renderer(this.defaults); - for (const prop in pack.renderer) { - if (!(prop in renderer)) { - throw new Error(`renderer '${prop}' does not exist`); - } - if (['options', 'parser'].includes(prop)) { - // ignore options property - continue; - } - const rendererProp = prop; - const rendererFunc = pack.renderer[rendererProp]; - const prevRenderer = renderer[rendererProp]; - // Replace renderer with func to run extension, but fall back if false - renderer[rendererProp] = (...args) => { - let ret = rendererFunc.apply(renderer, args); - if (ret === false) { - ret = prevRenderer.apply(renderer, args); - } - return ret || ''; - }; - } - opts.renderer = renderer; - } - if (pack.tokenizer) { - const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults); - for (const prop in pack.tokenizer) { - if (!(prop in tokenizer)) { - throw new Error(`tokenizer '${prop}' does not exist`); - } - if (['options', 'rules', 'lexer'].includes(prop)) { - // ignore options, rules, and lexer properties - continue; - } - const tokenizerProp = prop; - const tokenizerFunc = pack.tokenizer[tokenizerProp]; - const prevTokenizer = tokenizer[tokenizerProp]; - // Replace tokenizer with func to run extension, but fall back if false - // @ts-expect-error cannot type tokenizer function dynamically - tokenizer[tokenizerProp] = (...args) => { - let ret = tokenizerFunc.apply(tokenizer, args); - if (ret === false) { - ret = prevTokenizer.apply(tokenizer, args); - } - return ret; - }; - } - opts.tokenizer = tokenizer; - } - // ==-- Parse Hooks extensions --== // - if (pack.hooks) { - const hooks = this.defaults.hooks || new _Hooks(); - for (const prop in pack.hooks) { - if (!(prop in hooks)) { - throw new Error(`hook '${prop}' does not exist`); - } - if (['options', 'block'].includes(prop)) { - // ignore options and block properties - continue; - } - const hooksProp = prop; - const hooksFunc = pack.hooks[hooksProp]; - const prevHook = hooks[hooksProp]; - if (_Hooks.passThroughHooks.has(prop)) { - // @ts-expect-error cannot type hook function dynamically - hooks[hooksProp] = (arg) => { - if (this.defaults.async) { - return Promise.resolve(hooksFunc.call(hooks, arg)).then(ret => { - return prevHook.call(hooks, ret); - }); - } - const ret = hooksFunc.call(hooks, arg); - return prevHook.call(hooks, ret); - }; - } - else { - // @ts-expect-error cannot type hook function dynamically - hooks[hooksProp] = (...args) => { - let ret = hooksFunc.apply(hooks, args); - if (ret === false) { - ret = prevHook.apply(hooks, args); - } - return ret; - }; - } - } - opts.hooks = hooks; - } - // ==-- Parse WalkTokens extensions --== // - if (pack.walkTokens) { - const walkTokens = this.defaults.walkTokens; - const packWalktokens = pack.walkTokens; - opts.walkTokens = function (token) { - let values = []; - values.push(packWalktokens.call(this, token)); - if (walkTokens) { - values = values.concat(walkTokens.call(this, token)); - } - return values; - }; - } - this.defaults = { ...this.defaults, ...opts }; - }); - return this; - } - setOptions(opt) { - this.defaults = { ...this.defaults, ...opt }; - return this; - } - lexer(src, options) { - return _Lexer.lex(src, options ?? this.defaults); - } - parser(tokens, options) { - return _Parser.parse(tokens, options ?? this.defaults); - } - parseMarkdown(blockType) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const parse = (src, options) => { - const origOpt = { ...options }; - const opt = { ...this.defaults, ...origOpt }; - const throwError = this.onError(!!opt.silent, !!opt.async); - // throw error if an extension set async to true but parse was called with async: false - if (this.defaults.async === true && origOpt.async === false) { - return throwError(new Error('marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.')); - } - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - return throwError(new Error('marked(): input parameter is undefined or null')); - } - if (typeof src !== 'string') { - return throwError(new Error('marked(): input parameter is of type ' - + Object.prototype.toString.call(src) + ', string expected')); - } - if (opt.hooks) { - opt.hooks.options = opt; - opt.hooks.block = blockType; - } - const lexer = opt.hooks ? opt.hooks.provideLexer() : (blockType ? _Lexer.lex : _Lexer.lexInline); - const parser = opt.hooks ? opt.hooks.provideParser() : (blockType ? _Parser.parse : _Parser.parseInline); - if (opt.async) { - return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src) - .then(src => lexer(src, opt)) - .then(tokens => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens) - .then(tokens => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens) - .then(tokens => parser(tokens, opt)) - .then(html => opt.hooks ? opt.hooks.postprocess(html) : html) - .catch(throwError); - } - try { - if (opt.hooks) { - src = opt.hooks.preprocess(src); - } - let tokens = lexer(src, opt); - if (opt.hooks) { - tokens = opt.hooks.processAllTokens(tokens); - } - if (opt.walkTokens) { - this.walkTokens(tokens, opt.walkTokens); - } - let html = parser(tokens, opt); - if (opt.hooks) { - html = opt.hooks.postprocess(html); - } - return html; - } - catch (e) { - return throwError(e); - } - }; - return parse; - } - onError(silent, async) { - return (e) => { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - if (silent) { - const msg = '

    An error occurred:

    '
    -                    + escape(e.message + '', true)
    -                    + '
    '; - if (async) { - return Promise.resolve(msg); - } - return msg; - } - if (async) { - return Promise.reject(e); - } - throw e; - }; - } -} - -const markedInstance = new Marked(); -function marked(src, opt) { - return markedInstance.parse(src, opt); -} -/** - * Sets the default options. - * - * @param options Hash of options - */ -marked.options = - marked.setOptions = function (options) { - markedInstance.setOptions(options); - marked.defaults = markedInstance.defaults; - changeDefaults(marked.defaults); - return marked; - }; -/** - * Gets the original marked default options. - */ -marked.getDefaults = _getDefaults; -marked.defaults = _defaults; -/** - * Use Extension - */ -marked.use = function (...args) { - markedInstance.use(...args); - marked.defaults = markedInstance.defaults; - changeDefaults(marked.defaults); - return marked; -}; -/** - * Run callback for every token - */ -marked.walkTokens = function (tokens, callback) { - return markedInstance.walkTokens(tokens, callback); -}; -/** - * Compiles markdown to HTML without enclosing `p` tag. - * - * @param src String of markdown source to be compiled - * @param options Hash of options - * @return String of compiled HTML - */ -marked.parseInline = markedInstance.parseInline; -/** - * Expose - */ -marked.Parser = _Parser; -marked.parser = _Parser.parse; -marked.Renderer = _Renderer; -marked.TextRenderer = _TextRenderer; -marked.Lexer = _Lexer; -marked.lexer = _Lexer.lex; -marked.Tokenizer = _Tokenizer; -marked.Hooks = _Hooks; -marked.parse = marked; -const options = marked.options; -const setOptions = marked.setOptions; -const use = marked.use; -const walkTokens = marked.walkTokens; -const parseInline = marked.parseInline; -const parse = marked; -const parser = _Parser.parse; -const lexer = _Lexer.lex; - -export { _Hooks as Hooks, _Lexer as Lexer, Marked, _Parser as Parser, _Renderer as Renderer, _TextRenderer as TextRenderer, _Tokenizer as Tokenizer, _defaults as defaults, _getDefaults as getDefaults, lexer, marked, options, parse, parseInline, parser, setOptions, use, walkTokens }; -//# sourceMappingURL=marked.esm.js.map diff --git a/js/marked-config.js b/js/marked-config.js deleted file mode 100644 index 46f500d..0000000 --- a/js/marked-config.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Markdown 渲染器配置 - * 封装 marked.js,配置自定义渲染器和安全策略 - */ - -import { marked } from './lib/marked.esm.js'; -import { SAFE_URI_SCHEMES } from './sanitizer.js'; - -// 配置 marked -marked.setOptions({ - breaks: true, // 换行转
    - gfm: true, // GitHub 风格 Markdown -}); - -// 自定义渲染器:链接新窗口打开 + 安全属性 -const renderer = new marked.Renderer(); - -renderer.link = function ({ href, title, text }) { - const safe = SAFE_URI_SCHEMES.has(new URL(href, 'https://placeholder').protocol); - const t = title ? ` title="${title}"` : ''; - if (!safe) { - return `${text}`; - } - return `
    ${text}`; -}; - -renderer.image = function ({ href, title, text }) { - const safe = SAFE_URI_SCHEMES.has(new URL(href, 'https://placeholder').protocol); - if (!safe && !/^data:image\//i.test(href)) { - return `[已阻止不安全图片: ${text}]`; - } - const t = title ? ` title="${title}"` : ''; - return `${text}`; -}; - -marked.use({ renderer }); - -export { marked }; diff --git a/js/ollama-api.js b/js/ollama-api.js deleted file mode 100644 index c08cc3f..0000000 --- a/js/ollama-api.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Ollama API 客户端封装 - * - * 基于 Fetch API 与 Ollama REST 接口交互 - * 支持:流式聊天、模型管理、连接检测 - * - * API 文档:https://docs.ollama.com/api - */ - -export class OllamaAPI { - constructor(baseUrl = 'http://127.0.0.1:11434') { - this.baseUrl = baseUrl.replace(/\/+$/, ''); - } - - /** 通用请求方法 */ - async _request(path, options = {}) { - const url = `${this.baseUrl}${path}`; - const config = { - ...options, - headers: { 'Content-Type': 'application/json', ...options.headers } - }; - const response = await fetch(url, config); - if (!response.ok) { - const errorBody = await response.text().catch(() => ''); - throw new Error(`Ollama API 错误 ${response.status}: ${errorBody || response.statusText}`); - } - return response; - } - - /** JSON 请求快捷方法 */ - async _json(path, body = null) { - const options = body ? { method: 'POST', body: JSON.stringify(body) } : {}; - const response = await this._request(path, options); - return response.json(); - } - - // ── 模型管理 ── - - /** 获取已安装模型列表 */ - async listModels() { return this._json('/api/tags'); } - - /** 获取运行中的模型列表 */ - async psModels() { return this._json('/api/ps'); } - - /** 获取 Ollama 版本 */ - async getVersion() { return this._json('/api/version'); } - - /** 查看模型详情 */ - async showModel(model) { return this._json('/api/show', { model }); } - - // ── 聊天 API ── - - /** 非流式聊天 */ - async chat(params) { - const body = { - model: params.model, - messages: params.messages, - stream: false, - ...(params.think !== undefined && { think: params.think }), - ...(params.system && { system: params.system }), - ...(params.keep_alive !== undefined && { keep_alive: params.keep_alive }), - ...(params.options && { options: params.options }) - }; - return this._json('/api/chat', body); - } - - /** - * 流式聊天 - * 使用 ReadableStream + TextDecoder 解析 NDJSON 流 - * Ollama 流式响应格式 (NDJSON): - * {"model":"xxx","message":{"role":"assistant","content":"Hello"},"done":false} - * - * @param {AbortController} [abortController] - 可选,用于强制终止流 - */ - async chatStream(params, onChunk, abortController) { - const body = { - model: params.model, - messages: params.messages, - stream: true, - ...(params.think !== undefined && { think: params.think }), - ...(params.system && { system: params.system }), - ...(params.keep_alive !== undefined && { keep_alive: params.keep_alive }), - ...(params.options && { options: params.options }) - }; - - const fetchOptions = { - method: 'POST', - body: JSON.stringify(body) - }; - - // 注入 abort 信号,同时中止 fetch 和 reader - if (abortController) { - fetchOptions.signal = abortController.signal; - } - - const response = await this._request('/api/chat', fetchOptions); - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ''; - - // 如果外部触发 abort,立即取消 reader(双重保险:fetch abort + reader.cancel) - if (abortController) { - abortController.signal.addEventListener('abort', () => { - reader.cancel(); - }, { once: true }); - } - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split('\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - if (!line.trim()) continue; - try { - const chunk = JSON.parse(line); - if (onChunk) onChunk(chunk); - if (chunk.done) { - reader.cancel(); - return; - } - } catch (err) { - console.warn('[OllamaAPI] 流式数据解析警告:', err.message); - } - } - } - - if (buffer.trim()) { - try { - const chunk = JSON.parse(buffer); - if (onChunk) onChunk(chunk); - } catch { /* 忽略 */ } - } - } - - /** 流式生成(单轮) */ - async generateStream(params, onChunk) { - const body = { - model: params.model, - prompt: params.prompt, - stream: true, - ...(params.images && { images: params.images }), - ...(params.options && { options: params.options }) - }; - - const response = await this._request('/api/generate', { - method: 'POST', - body: JSON.stringify(body) - }); - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ''; - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split('\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - if (!line.trim()) continue; - try { - const chunk = JSON.parse(line); - if (onChunk) onChunk(chunk); - if (chunk.done) { - reader.cancel(); - return; - } - } catch { /* 忽略不完整数据 */ } - } - } - } - - /** 生成嵌入向量 */ - async embed(model, input) { - return this._json('/api/embed', { model, input }); - } -} diff --git a/js/preset-manager.js b/js/preset-manager.js deleted file mode 100644 index 7b63762..0000000 --- a/js/preset-manager.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * PresetManager - Agent 预设管理 - * - * 预设数据结构: - * { - * id: string, - * name: string, // 预设名称,如 "翻译官" - * icon: string, // emoji 图标 - * systemPrompt: string, // 系统提示词 - * temperature: number, // 温度 (0~2) - * numCtx: number, // 上下文长度 - * think: boolean, // Think 开关 - * createdAt: number, - * updatedAt: number, - * } - */ - -import { state, KEYS } from './state.js'; -import { generateId } from './utils.js'; - -const PRESETS_KEY = 'agentPresets'; -const ACTIVE_PRESET_KEY = 'activePresetId'; - -// ── 内置预设 ── -const BUILT_IN_PRESETS = [ - { - id: '_default', - name: '默认', - icon: '💬', - systemPrompt: '', - temperature: 0.7, - numCtx: 24576, - think: false, - builtIn: true, - createdAt: 0, - updatedAt: 0 - }, - { - id: '_translator', - name: '翻译官', - icon: '🌐', - systemPrompt: '你是一位专业翻译官。你的任务是将用户输入的内容准确翻译为目标语言。\n\n规则:\n1. 自动识别源语言,翻译为用户指定的目标语言(如未指定则翻译为中文)\n2. 保持原文的格式、语气和风格\n3. 专有名词保留原文并给出通用译名\n4. 对于有多种含义的词语,结合上下文选择最合适的翻译\n5. 直接输出翻译结果,不需要额外解释(除非用户要求)', - temperature: 0.3, - numCtx: 16384, - think: false, - builtIn: true, - createdAt: 0, - updatedAt: 0 - }, - { - id: '_code_review', - name: '代码审查', - icon: '🔍', - systemPrompt: '你是一位资深代码审查专家。对用户提交的代码进行全面审查。\n\n审查维度:\n1. **Bug 与逻辑错误** — 潜在的运行时错误、边界条件、空指针等\n2. **安全性** — SQL注入、XSS、敏感信息泄露等安全隐患\n3. **性能** — 时间/空间复杂度、不必要的计算、内存泄漏\n4. **可读性** — 命名规范、代码结构、注释质量\n5. **最佳实践** — 设计模式、语言惯用法、框架约定\n\n输出格式:\n- 按严重程度排列问题(严重 → 轻微)\n- 每个问题给出:位置、问题描述、修复建议、改进后的代码\n- 最后给出整体评价和改进建议', - temperature: 0.2, - numCtx: 32768, - think: true, - builtIn: true, - createdAt: 0, - updatedAt: 0 - }, - { - id: '_writer', - name: '写作助手', - icon: '✍️', - systemPrompt: '你是一位才华横溢的写作助手。擅长多种文体和写作风格。\n\n能力范围:\n1. 文章撰写 — 议论文、说明文、散文、故事\n2. 文案创作 — 广告文案、产品描述、社交媒体文案\n3. 内容润色 — 修改语法、优化措辞、提升表达力\n4. 风格调整 — 正式/口语/文艺/简洁等多种风格\n5. 创意写作 — 小说、诗歌、剧本、对话\n\n工作方式:\n- 仔细理解用户的写作需求(读者、用途、风格偏好)\n- 如需信息不全,主动询问确认\n- 提供多种版本供选择\n- 解释重要的写作决策', - temperature: 0.9, - numCtx: 24576, - think: false, - builtIn: true, - createdAt: 0, - updatedAt: 0 - }, - { - id: '_analyst', - name: '数据分析师', - icon: '📊', - systemPrompt: '你是一位专业的数据分析师。擅长从数据中提取洞察并给出可执行的建议。\n\n分析方法:\n1. 数据概览 — 数据量、字段类型、缺失值、异常值\n2. 统计分析 — 描述性统计、相关性分析、趋势分析\n3. 可视化建议 — 推荐合适的图表类型和展示方式\n4. 洞察提取 — 关键发现、模式识别、因果推断\n5. 行动建议 — 基于数据的具体可执行建议\n\n输出格式:\n- 先给出核心结论(Executive Summary)\n- 再展开详细分析过程\n- 使用表格和列表组织数据\n- 给出代码(Python/SQL)时附带注释', - temperature: 0.4, - numCtx: 32768, - think: true, - builtIn: true, - createdAt: 0, - updatedAt: 0 - }, - { - id: '_tutor', - name: '学习导师', - icon: '🎓', - systemPrompt: '你是一位耐心且善于启发的学习导师。用苏格拉底式教学法帮助用户深入理解知识。\n\n教学原则:\n1. **先问后答** — 通过引导性问题帮助用户自己找到答案\n2. **由浅入深** — 从基础概念开始,逐步构建知识体系\n3. **类比教学** — 用生活中的类比解释抽象概念\n4. **实践导向** — 每个知识点配一个动手练习\n5. **鼓励探索** — 提供延伸阅读方向和思考题\n\n当用户提问时:\n- 先评估用户当前理解水平\n- 用引导性问题帮助思考\n- 如果用户确实卡住,再给出答案并解释思路\n- 每个概念学完后给出一个小练习', - temperature: 0.6, - numCtx: 24576, - think: true, - builtIn: true, - createdAt: 0, - updatedAt: 0 - } -]; - -let presets = []; -let activePresetId = null; - -/** - * 初始化预设管理器 - */ -export async function initPresetManager() { - const db = state.get(KEYS.DB); - - // 从 DB 加载用户自定义预设 - let userPresets = []; - if (db) { - userPresets = await db.getSetting(PRESETS_KEY, []); - } - - // 合并:内置 + 用户自定义 - presets = [...BUILT_IN_PRESETS, ...userPresets]; - - // 加载上次激活的预设 - if (db) { - activePresetId = await db.getSetting(ACTIVE_PRESET_KEY, '_default'); - } else { - activePresetId = '_default'; - } - - // 写入 state - state.set('presets', presets); - state.set('activePresetId', activePresetId); - - // 应用当前预设(恢复设置) - const activePreset = presets.find(p => p.id === activePresetId); - if (activePreset && activePreset.id !== '_default') { - applyPresetToState(activePreset, false); // 不再 DB,已在 init 阶段加载 - } -} - -/** - * 获取所有预设 - */ -export function getPresets() { - return presets; -} - -/** - * 获取当前激活预设 - */ -export function getActivePreset() { - return presets.find(p => p.id === activePresetId) || presets[0]; -} - -/** - * 获取当前激活预设 ID - */ -export function getActivePresetId() { - return activePresetId; -} - -/** - * 激活预设 - */ -export async function activatePreset(presetId) { - const preset = presets.find(p => p.id === presetId); - if (!preset) return; - - activePresetId = presetId; - state.set('activePresetId', presetId); - - applyPresetToState(preset, true); - - const db = state.get(KEYS.DB); - if (db) { - await db.saveSetting(ACTIVE_PRESET_KEY, presetId); - } -} - -/** - * 将预设配置应用到全局 state - */ -function applyPresetToState(preset, saveToDb) { - const db = state.get(KEYS.DB); - - // 系统提示词 - if (preset.systemPrompt) { - state.set(KEYS.SYSTEM_PROMPT_ENABLED, true); - state.set(KEYS.SYSTEM_PROMPT, preset.systemPrompt); - } else { - state.set(KEYS.SYSTEM_PROMPT_ENABLED, false); - state.set(KEYS.SYSTEM_PROMPT, ''); - } - - // 上下文长度 - if (preset.numCtx) { - state.set(KEYS.NUM_CTX, preset.numCtx); - } - - // 温度(存储到 state,聊天时使用) - state.set('temperature', preset.temperature ?? 0.7); - - // Think 开关 - state.set('thinkEnabled', preset.think ?? false); - - // 同步到 UI - syncPresetToUI(preset); - - // 持久化 - if (saveToDb && db) { - db.saveSetting('systemPromptEnabled', preset.systemPrompt ? true : false); - db.saveSetting('systemPrompt', preset.systemPrompt || ''); - db.saveSetting('numCtx', preset.numCtx || 24576); - db.saveSetting('temperature', preset.temperature ?? 0.7); - } -} - -/** - * 将预设配置同步到设置面板 UI - */ -function syncPresetToUI(preset) { - // 系统提示词 - const toggleSP = document.querySelector('#toggleSystemPrompt'); - const inputSP = document.querySelector('#inputSystemPrompt'); - if (toggleSP) toggleSP.checked = !!preset.systemPrompt; - if (inputSP) { - inputSP.disabled = !preset.systemPrompt; - inputSP.value = preset.systemPrompt || ''; - } - - // numCtx - const inputCtx = document.querySelector('#inputNumCtx'); - if (inputCtx) inputCtx.value = preset.numCtx || 24576; - - // Temperature - const tempSlider = document.querySelector('#inputTemperature'); - const tempDisplay = document.querySelector('#tempValue'); - if (tempSlider) tempSlider.value = preset.temperature ?? 0.7; - if (tempDisplay) tempDisplay.textContent = (preset.temperature ?? 0.7).toFixed(1); - - // Think toggle — 仅在模型支持时才设为 checked,否则保持 unchecked - const thinkCheckbox = document.querySelector('#toggleThink'); - if (thinkCheckbox) { - const modelSupportsThink = !thinkCheckbox.disabled; - thinkCheckbox.checked = modelSupportsThink && (preset.think ?? false); - } -} - -/** - * 创建预设 - */ -export async function createPreset({ name, icon, systemPrompt, temperature, numCtx, think }) { - const preset = { - id: `preset_${generateId()}`, - name: name || '新预设', - icon: icon || '🤖', - systemPrompt: systemPrompt || '', - temperature: temperature ?? 0.7, - numCtx: numCtx ?? 24576, - think: think ?? false, - createdAt: Date.now(), - updatedAt: Date.now() - }; - - presets.push(preset); - state.set('presets', [...presets]); - - await saveUserPresets(); - return preset; -} - -/** - * 更新预设 - */ -export async function updatePreset(presetId, updates) { - const idx = presets.findIndex(p => p.id === presetId); - if (idx === -1) return; - - // 内置预设不允许修改 - if (presets[idx].builtIn) return; - - presets[idx] = { ...presets[idx], ...updates, updatedAt: Date.now() }; - state.set('presets', [...presets]); - - // 如果更新的是当前激活预设,重新应用 - if (activePresetId === presetId) { - applyPresetToState(presets[idx], true); - } - - await saveUserPresets(); -} - -/** - * 删除预设 - */ -export async function deletePreset(presetId) { - const idx = presets.findIndex(p => p.id === presetId); - if (idx === -1) return; - if (presets[idx].builtIn) return; // 内置不可删 - - presets.splice(idx, 1); - state.set('presets', [...presets]); - - // 如果删除的是当前激活预设,切回默认 - if (activePresetId === presetId) { - await activatePreset('_default'); - } - - await saveUserPresets(); -} - -/** - * 保存用户自定义预设到 IndexedDB - */ -async function saveUserPresets() { - const db = state.get(KEYS.DB); - if (!db) return; - - const userPresets = presets - .filter(p => !p.builtIn) - .map(({ builtIn, ...rest }) => rest); // 去掉 builtIn 标记 - - await db.saveSetting(PRESETS_KEY, userPresets); -} diff --git a/js/rag.js b/js/rag.js deleted file mode 100644 index 904c04a..0000000 --- a/js/rag.js +++ /dev/null @@ -1,202 +0,0 @@ -/** - * RAG - 检索增强生成管线 - * - * 查询 → 嵌入 → 向量检索 → 构造上下文 → 注入 prompt - */ - -import { state, KEYS } from './state.js'; -import { VectorStore } from './vector-store.js'; -import { chunkText, createChunkMetadata } from './document-processor.js'; -import { showToast } from './components/toast.js'; - -let vectorStore = null; - -/** - * 初始化向量存储 - */ -export async function initVectorStore() { - if (vectorStore) return vectorStore; - vectorStore = new VectorStore(); - await vectorStore.init(); - return vectorStore; -} - -/** - * 获取向量存储实例 - */ -export function getVectorStore() { - return vectorStore; -} - -/** - * 使用 Ollama 生成文本嵌入向量 - * @param {string} text - * @param {string} model - 嵌入模型名称 - * @returns {number[]} - */ -export async function embedText(text, model) { - const api = state.get(KEYS.API); - if (!api) throw new Error('Ollama API 未连接'); - - const result = await api.embed(model, text); - // /api/embed 返回 { embedding: [...] } 或 { embeddings: [[...]] } - if (result.embedding) return result.embedding; - if (result.embeddings && result.embeddings[0]) return result.embeddings[0]; - throw new Error('嵌入向量返回格式异常'); -} - -/** - * 批量嵌入(分批处理,避免超时) - * @param {string[]} texts - * @param {string} model - * @param {function} onProgress - (done, total) => void - * @returns {number[][]} - */ -export async function embedBatch(texts, model, onProgress) { - const results = []; - const batchSize = 5; - for (let i = 0; i < texts.length; i += batchSize) { - const batch = texts.slice(i, i + batchSize); - const embeddings = await Promise.all(batch.map(t => embedText(t, model))); - results.push(...embeddings); - if (onProgress) onProgress(Math.min(i + batchSize, texts.length), texts.length); - } - return results; -} - -/** - * 向知识库集合添加文档 - * @param {string} colId - 集合 ID - * @param {string} filename - 文件名 - * @param {string} content - 文件内容 - * @param {string} embedModel - 嵌入模型 - * @param {function} onProgress - */ -export async function addDocumentToKB(colId, filename, content, embedModel, onProgress) { - const vs = await initVectorStore(); - - // 1. 分块 - const textChunks = chunkText(content); - if (textChunks.length === 0) throw new Error('文档内容为空,无法处理'); - - // 2. 生成文档 ID - const docId = `doc_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`; - - // 3. 生成嵌入向量 - if (onProgress) onProgress(0, textChunks.length, '正在生成向量...'); - const embeddings = await embedBatch(textChunks, embedModel, (done, total) => { - if (onProgress) onProgress(done, total, '正在生成向量...'); - }); - - // 4. 构造向量数据 - const vectors = textChunks.map((chunk, i) => { - const meta = createChunkMetadata(chunk, i, docId, filename); - return { - ...meta, - collectionId: colId, - embedding: embeddings[i] - }; - }); - - // 5. 存储 - if (onProgress) onProgress(textChunks.length, textChunks.length, '正在保存...'); - await vs.addVectors(vectors); - - // 6. 更新集合统计 - const col = await vs.getCollection(colId); - if (col) { - col.docCount = (col.docCount || 0) + 1; - col.chunkCount = (col.chunkCount || 0) + textChunks.length; - col.embeddingModel = embedModel; - await vs.updateCollection(col); - } - - return { docId, chunkCount: textChunks.length }; -} - -/** - * 从知识库删除文档 - */ -export async function removeDocumentFromKB(colId, docId) { - const vs = await initVectorStore(); - const allVectors = await vs.getVectorsByCollection(colId); - const docVectors = allVectors.filter(v => v.docId === docId); - - await vs.deleteVectorsByDocument(colId, docId); - - const col = await vs.getCollection(colId); - if (col) { - col.chunkCount = Math.max(0, (col.chunkCount || 0) - docVectors.length); - col.docCount = Math.max(0, (col.docCount || 0) - 1); - await vs.updateCollection(col); - } -} - -/** - * 获取集合中的文档列表(去重) - */ -export async function getDocumentsInCollection(colId) { - const vs = await initVectorStore(); - const vectors = await vs.getVectorsByCollection(colId); - const docMap = new Map(); - for (const v of vectors) { - if (!docMap.has(v.docId)) { - docMap.set(v.docId, { - docId: v.docId, - filename: v.filename, - chunkCount: 0 - }); - } - docMap.get(v.docId).chunkCount++; - } - return Array.from(docMap.values()); -} - -/** - * RAG 检索:查询 → 嵌入 → 相似度搜索 → 返回上下文文本 - * @param {string} query - 用户查询 - * @param {string} colId - 集合 ID - * @param {number} topK - 返回最相关的 K 条 - * @returns {{ context: string, results: Array }} - */ -export async function retrieveContext(query, colId, topK = 5) { - const vs = await initVectorStore(); - const col = await vs.getCollection(colId); - if (!col) throw new Error('知识库集合不存在'); - - const embedModel = col.embeddingModel; - if (!embedModel) throw new Error('未配置嵌入模型'); - - // 嵌入查询 - const queryEmbedding = await embedText(query, embedModel); - - // 向量检索 - const results = await vs.search(colId, queryEmbedding, topK); - if (results.length === 0) return { context: '', results: [] }; - - // 构造上下文 - const contextParts = results.map((r, i) => - `[来源 ${i + 1}: ${r.filename}]\n${r.text}` - ); - const context = contextParts.join('\n\n---\n\n'); - - return { context, results }; -} - -/** - * 构造 RAG 增强的系统提示词 - */ -export function buildRagSystemPrompt(context, originalSystemPrompt = '') { - const ragPrompt = `你是一个知识库问答助手。以下是检索到的相关文档片段,请基于这些内容回答用户的问题。如果检索到的内容无法回答问题,请如实说明。 - -=== 检索到的相关内容 === -${context} -=== 内容结束 === - -请基于以上内容回答用户的问题。引用时请注明来源编号(如"来源 1")。`; - - if (originalSystemPrompt) { - return originalSystemPrompt + '\n\n' + ragPrompt; - } - return ragPrompt; -} diff --git a/js/sanitizer.js b/js/sanitizer.js deleted file mode 100644 index 66e5389..0000000 --- a/js/sanitizer.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Sanitizer - HTML 净化器 - * 基于白名单策略,防御 XSS 攻击 - */ - -export const SAFE_URI_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:', 'ftp:', 'ftps:', 'xmpp:']); -export const SAFE_DATA_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml']); - -const ALLOWED_TAGS = new Set([ - 'a', 'abbr', 'b', 'blockquote', 'br', 'code', 'del', 'details', 'div', - 'dl', 'dt', 'dd', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', - 'i', 'img', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'rp', 'rt', - 'ruby', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', - 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', - 'tr', 'u', 'ul', 'var', 'input', -]); - -const ALLOWED_ATTRS = new Set([ - 'href', 'src', 'alt', 'title', 'width', 'height', 'align', - 'target', 'rel', 'class', 'id', 'type', 'checked', 'disabled', - 'start', 'value', 'scope', 'rowspan', 'colspan', 'lang', -]); - -const DANGEROUS_PREFIXES = ['on', 'xlink:href', 'xmlns:']; - -/** - * 净化 HTML 字符串,移除危险标签和属性 - * @param {string} html - 原始 HTML - * @returns {string} 净化后的 HTML - */ -export function sanitize(html) { - try { - const template = document.createElement('template'); - template.innerHTML = html; - const root = template.content; - - // 删除注释节点 - const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT); - const comments = []; - while (walker.nextNode()) comments.push(walker.currentNode); - comments.forEach(c => c.remove()); - - // 遍历所有元素(先收集再处理,避免动态集合问题) - const elements = [...root.querySelectorAll('*')]; - for (const el of elements) { - if (!el.parentNode) continue; - - const tag = el.tagName.toLowerCase(); - - if (!ALLOWED_TAGS.has(tag)) { - while (el.firstChild) el.parentNode.insertBefore(el.firstChild, el); - el.remove(); - continue; - } - - // 清理属性 - for (const attr of [...el.attributes]) { - const name = attr.name.toLowerCase(); - - if (DANGEROUS_PREFIXES.some(p => name.startsWith(p))) { - el.removeAttribute(attr.name); - continue; - } - - if (!ALLOWED_ATTRS.has(name)) { - el.removeAttribute(attr.name); - continue; - } - - // URI 协议检查 - if ((name === 'href' || name === 'src' || name === 'action') && attr.value) { - const val = attr.value.trim().toLowerCase(); - if (/^(javascript|vbscript|data|file):/i.test(val)) { - const mimeMatch = val.match(/^data:([^;,]+)/i); - if (!mimeMatch || !SAFE_DATA_TYPES.has(mimeMatch[1].toLowerCase())) { - el.removeAttribute(attr.name); - } - } - } - } - } - - return root.innerHTML; - } catch (e) { - console.warn('[Sanitizer] 净化失败,返回原始文本:', e); - return html; - } -} diff --git a/js/state.js b/js/state.js deleted file mode 100644 index c2855a6..0000000 --- a/js/state.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * State - 轻量级响应式状态管理 - * - * 优化点: - * 1. 结构化浅比较:对象按 key 数量 + 逐 key 严格比较,数组按长度 + 逐元素 - * 2. update():函数式更新,自动比较 - * 3. setIn():路径式嵌套更新,沿途浅拷贝确保引用变化 - * 4. batch():批量更新,合并通知 - */ - -// ── 结构化浅比较 ── -function shallowEqual(a, b) { - if (a === b) return true; - if (a == null || b == null) return a === b; - if (typeof a !== 'object' || typeof b !== 'object') return false; - - // 数组:长度 + 逐元素 - if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; - } - - // 混合类型 - if (Array.isArray(a) !== Array.isArray(b)) return false; - - // 对象:key 数量 + 逐 key - const keysA = Object.keys(a); - const keysB = Object.keys(b); - if (keysA.length !== keysB.length) return false; - for (const key of keysA) { - if (!Object.prototype.hasOwnProperty.call(b, key) || a[key] !== b[key]) return false; - } - return true; -} - -class AppState { - constructor() { - this._state = {}; - this._listeners = new Map(); - this._batching = false; - this._batchQueue = null; - } - - /** - * 设置状态值,使用结构化浅比较判断变更 - */ - set(key, value) { - const old = this._state[key]; - this._state[key] = value; - - if (!shallowEqual(old, value)) { - if (this._batching) { - this._batchQueue.add(key); - } else { - this._notify(key, value, old); - } - } - } - - /** - * 函数式更新:基于当前值计算新值,自动比较 - * - * state.update('currentSession', session => ({ - * ...session, - * messages: [...session.messages, newMsg], - * updatedAt: Date.now() - * })); - */ - update(key, updater) { - const old = this._state[key]; - const value = updater(old); - this._state[key] = value; - - if (!shallowEqual(old, value)) { - if (this._batching) { - this._batchQueue.add(key); - } else { - this._notify(key, value, old); - } - } - } - - /** - * 路径式嵌套更新:按 path 更新嵌套属性,沿途浅拷贝确保引用变化 - * - * state.setIn('currentSession', ['messages', 0, 'content'], '新内容'); - */ - setIn(key, path, value) { - if (!path || path.length === 0) { - this.set(key, value); - return; - } - - const old = this._state[key]; - if (old == null || typeof old !== 'object') { - console.warn(`[State] setIn 失败: ${key} 不是对象`); - return; - } - - const newRoot = Array.isArray(old) ? [...old] : { ...old }; - let current = newRoot; - - for (let i = 0; i < path.length - 1; i++) { - const segment = path[i]; - const next = current[segment]; - if (next == null || typeof next !== 'object') { - const isIndex = typeof path[i + 1] === 'number'; - current[segment] = isIndex ? [] : {}; - } else { - current[segment] = Array.isArray(next) ? [...next] : { ...next }; - } - current = current[segment]; - } - - current[path[path.length - 1]] = value; - - this._state[key] = newRoot; - if (!shallowEqual(old, newRoot)) { - if (this._batching) { - this._batchQueue.add(key); - } else { - this._notify(key, newRoot, old); - } - } - } - - /** - * 批量更新:合并多次 set 为一次通知 - * - * state.batch(() => { - * state.set('isStreaming', false); - * state.update('currentSession', s => ({ ...s, updatedAt: Date.now() })); - * }); - */ - batch(fn) { - this._batching = true; - this._batchQueue = new Set(); - try { - fn(); - } finally { - const keys = this._batchQueue; - this._batching = false; - this._batchQueue = null; - for (const k of keys) { - this._notify(k, this._state[k], undefined); - } - } - } - - _notify(key, value, old) { - if (!this._listeners.has(key)) return; - for (const cb of this._listeners.get(key)) { - try { cb(value, old); } catch (e) { - console.error(`[State] 监听器错误 (${key}):`, e); - } - } - } - - /** - * 获取状态值 - */ - get(key, defaultValue = undefined) { - return key in this._state ? this._state[key] : defaultValue; - } - - /** - * 订阅状态变更 - * @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); - } - - /** - * 批量设置初始状态 - */ - 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', -}; diff --git a/js/utils.js b/js/utils.js deleted file mode 100644 index 5bb86be..0000000 --- a/js/utils.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Utils - 通用工具函数 - */ - -/** 生成唯一 ID */ -export function generateId() { - return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; -} - -/** 格式化时间戳 */ -export function formatTime(ts) { - const d = new Date(ts); - const pad = n => String(n).padStart(2, '0'); - return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; -} - -/** 截断文本 */ -export function truncate(str, len = 50) { - if (!str) return ''; - return str.length > len ? str.substring(0, len) + '...' : str; -} - -/** 防抖 */ -export function debounce(fn, ms = 300) { - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => fn(...args), ms); - }; -} - -/** 格式化字节大小 */ -export function formatSize(bytes) { - if (!bytes) return ''; - const units = ['B', 'KB', 'MB', 'GB', 'TB']; - let i = 0; - let size = bytes; - while (size >= 1024 && i < units.length - 1) { - size /= 1024; - i++; - } - return `${size.toFixed(1)} ${units[i]}`; -} - -/** HTML 转义 */ -export function escapeHtml(str) { - const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; - return String(str).replace(/[&<>"']/g, (c) => map[c]); -} - -/** 下载文件 */ -export function downloadFile(filename, content, type) { - const blob = new Blob([content], { type: type + ';charset=utf-8' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); -} - -/** File → Base64(去前缀) */ -export function fileToBase64(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - const base64 = reader.result.split(',')[1]; - resolve(base64); - }; - reader.onerror = reject; - reader.readAsDataURL(file); - }); -} - -/** File → 文本内容 */ -export function fileToText(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsText(file); - }); -} - -/** 根据文件名检测语言标识(用于 Markdown 代码块) */ -export function detectLanguage(filename) { - const ext = filename.split('.').pop().toLowerCase(); - const map = { - py: 'python', pyw: 'python', pyi: 'python', - js: 'javascript', mjs: 'javascript', cjs: 'javascript', - ts: 'typescript', tsx: 'tsx', jsx: 'jsx', - java: 'java', class: 'java', - c: 'c', h: 'c', - cpp: 'cpp', cc: 'cpp', cxx: 'cpp', hpp: 'cpp', - go: 'go', rs: 'rust', rb: 'ruby', php: 'php', - sh: 'bash', bash: 'bash', zsh: 'bash', fish: 'bash', - sql: 'sql', html: 'html', htm: 'html', css: 'css', - json: 'json', jsonl: 'json', - xml: 'xml', svg: 'svg', - yaml: 'yaml', yml: 'yaml', toml: 'toml', - ini: 'ini', cfg: 'ini', conf: 'ini', - md: 'markdown', markdown: 'markdown', rst: 'rst', - txt: '', csv: 'csv', tsv: 'csv', log: '', - swift: 'swift', kt: 'kotlin', kts: 'kotlin', - scala: 'scala', lua: 'lua', pl: 'perl', r: 'r', - m: 'matlab', mm: 'objectivec', - cs: 'csharp', fs: 'fsharp', vb: 'vbnet', - ex: 'elixir', exs: 'elixir', erl: 'erlang', - hs: 'haskell', clj: 'clojure', lisp: 'lisp', - dockerfile: 'dockerfile', makefile: 'makefile', - diff: 'diff', patch: 'diff', - }; - return map[ext] ?? ext; -} diff --git a/js/vector-store.js b/js/vector-store.js deleted file mode 100644 index b0d0da1..0000000 --- a/js/vector-store.js +++ /dev/null @@ -1,449 +0,0 @@ -/** - * VectorStore - 向量存储与相似度检索 - * - * 优化:IVF 倒排索引 - * - 小数据集 (N < MIN_IVF_SIZE):自动降级为暴力搜索 - * - 大数据集:K-Means 聚类 + nprobe 近邻搜索 - * - 索引在 addVectors() 后自动构建,持久化到 IndexedDB - */ - -const MIN_IVF_SIZE = 200; // 低于此数量不建索引,直接暴力搜 -const DEFAULT_K = 20; // 聚类中心数上限 -const DEFAULT_NPROBE = 5; // 搜索时探测的聚类数(覆盖 ~25% 数据) -const KMEANS_ITERS = 10; // K-Means 迭代次数 - -export class VectorStore { - constructor(dbName = 'metona-ollama-vectors') { - this.dbName = dbName; - this.db = null; - - // 内存索引缓存 { colId → { centroids, invertedLists, version } } - this._indexCache = new Map(); - - // 向量内存缓存 { colId → Map } - this._vectorCache = new Map(); - } - - async init() { - return new Promise((resolve, reject) => { - const req = indexedDB.open(this.dbName, 2); - req.onerror = () => reject(req.error); - req.onsuccess = () => { this.db = req.result; resolve(); }; - req.onupgradeneeded = (e) => { - const db = e.target.result; - if (!db.objectStoreNames.contains('vectors')) { - const store = db.createObjectStore('vectors', { keyPath: 'id' }); - store.createIndex('collectionId', 'collectionId', { unique: false }); - } - if (!db.objectStoreNames.contains('collections')) { - db.createObjectStore('collections', { keyPath: 'id' }); - } - if (!db.objectStoreNames.contains('indexes')) { - db.createObjectStore('indexes', { keyPath: 'collectionId' }); - } - }; - }); - } - - _tx(store, mode = 'readonly') { - return this.db.transaction(store, mode).objectStore(store); - } - - // ═══════════════════════════════════════════ - // 集合管理 - // ═══════════════════════════════════════════ - - async createCollection(name, embeddingModel = '') { - const col = { - id: `kb_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`, - name, embeddingModel, - docCount: 0, chunkCount: 0, - createdAt: Date.now(), updatedAt: Date.now() - }; - return new Promise((resolve, reject) => { - const req = this._tx('collections', 'readwrite').put(col); - req.onsuccess = () => resolve(col); - req.onerror = () => reject(req.error); - }); - } - - async getCollections() { - return new Promise((resolve, reject) => { - const req = this._tx('collections').getAll(); - req.onsuccess = () => resolve(req.result || []); - req.onerror = () => reject(req.error); - }); - } - - async getCollection(id) { - return new Promise((resolve, reject) => { - const req = this._tx('collections').get(id); - req.onsuccess = () => resolve(req.result || null); - req.onerror = () => reject(req.error); - }); - } - - async updateCollection(col) { - col.updatedAt = Date.now(); - return new Promise((resolve, reject) => { - const req = this._tx('collections', 'readwrite').put(col); - req.onsuccess = () => resolve(); - req.onerror = () => reject(req.error); - }); - } - - async deleteCollection(colId) { - const vectors = await this.getVectorsByCollection(colId); - await new Promise((resolve, reject) => { - const tx = this.db.transaction(['vectors', 'collections', 'indexes'], 'readwrite'); - const vStore = tx.objectStore('vectors'); - for (const v of vectors) vStore.delete(v.id); - tx.objectStore('collections').delete(colId); - tx.objectStore('indexes').delete(colId); - tx.oncomplete = () => resolve(); - tx.onerror = () => reject(tx.error); - }); - this._indexCache.delete(colId); - this._vectorCache.delete(colId); - } - - // ═══════════════════════════════════════════ - // 向量操作 - // ═══════════════════════════════════════════ - - async addVectors(items) { - if (items.length === 0) return; - - await new Promise((resolve, reject) => { - const tx = this.db.transaction('vectors', 'readwrite'); - const store = tx.objectStore('vectors'); - for (const item of items) store.put(item); - tx.oncomplete = () => resolve(); - tx.onerror = () => reject(tx.error); - }); - - // 更新内存缓存 - const colId = items[0].collectionId; - if (!this._vectorCache.has(colId)) { - this._vectorCache.set(colId, new Map()); - } - const cache = this._vectorCache.get(colId); - for (const item of items) cache.set(item.id, item); - - // 标记索引需重建 - this._indexCache.delete(colId); - } - - async getVectorsByCollection(colId) { - if (this._vectorCache.has(colId)) { - return Array.from(this._vectorCache.get(colId).values()); - } - - const vectors = await new Promise((resolve, reject) => { - const idx = this._tx('vectors').index('collectionId'); - const req = idx.getAll(colId); - req.onsuccess = () => resolve(req.result || []); - req.onerror = () => reject(req.error); - }); - - const cache = new Map(); - for (const v of vectors) cache.set(v.id, v); - this._vectorCache.set(colId, cache); - - return vectors; - } - - async deleteVectorsByCollection(colId) { - const vectors = await this.getVectorsByCollection(colId); - await new Promise((resolve, reject) => { - const tx = this.db.transaction('vectors', 'readwrite'); - const store = tx.objectStore('vectors'); - for (const v of vectors) store.delete(v.id); - tx.oncomplete = () => resolve(); - tx.onerror = () => reject(tx.error); - }); - this._indexCache.delete(colId); - this._vectorCache.delete(colId); - } - - async deleteVectorsByDocument(colId, docId) { - const vectors = await this.getVectorsByCollection(colId); - const docVectors = vectors.filter(v => v.docId === docId); - await new Promise((resolve, reject) => { - const tx = this.db.transaction('vectors', 'readwrite'); - const store = tx.objectStore('vectors'); - for (const v of docVectors) store.delete(v.id); - tx.oncomplete = () => resolve(); - tx.onerror = () => reject(tx.error); - }); - if (this._vectorCache.has(colId)) { - const cache = this._vectorCache.get(colId); - for (const v of docVectors) cache.delete(v.id); - } - this._indexCache.delete(colId); - } - - // ═══════════════════════════════════════════ - // 搜索:自动选择暴力 / IVF - // ═══════════════════════════════════════════ - - async search(colId, queryEmbedding, topK = 5) { - const vectors = await this.getVectorsByCollection(colId); - if (vectors.length === 0) return []; - - // 小数据集 → 暴力搜索 - if (vectors.length < MIN_IVF_SIZE) { - return this._bruteForceSearch(vectors, queryEmbedding, topK); - } - - // 大数据集 → IVF 搜索 - const index = await this._getIndex(colId, vectors); - if (!index) { - return this._bruteForceSearch(vectors, queryEmbedding, topK); - } - - return this._ivfSearch(vectors, index, queryEmbedding, topK); - } - - _bruteForceSearch(vectors, queryEmbedding, topK) { - return vectors - .map(v => ({ ...v, score: VectorStore.cosineSimilarity(queryEmbedding, v.embedding) })) - .sort((a, b) => b.score - a.score) - .slice(0, topK); - } - - _ivfSearch(vectors, index, queryEmbedding, topK) { - const { centroids, invertedLists } = index; - - // 1. 找最近的 nprobe 个聚类中心 - const clusterScores = centroids.map((c, i) => ({ - id: i, - score: VectorStore.cosineSimilarity(queryEmbedding, c) - })); - clusterScores.sort((a, b) => b.score - a.score); - - const nprobe = Math.min(DEFAULT_NPROBE, centroids.length); - const targetClusters = clusterScores.slice(0, nprobe); - - // 2. 收集候选向量 ID - const candidateIds = new Set(); - for (const { id: clusterId } of targetClusters) { - const list = invertedLists.get(clusterId); - if (list) for (const id of list) candidateIds.add(id); - } - - // 3. 候选向量构建快速查找(避免逐个 find) - const vectorMap = new Map(); - for (const v of vectors) vectorMap.set(v.id, v); - - const candidates = []; - for (const id of candidateIds) { - const v = vectorMap.get(id); - if (v) candidates.push(v); - } - - return candidates - .map(v => ({ ...v, score: VectorStore.cosineSimilarity(queryEmbedding, v.embedding) })) - .sort((a, b) => b.score - a.score) - .slice(0, topK); - } - - // ═══════════════════════════════════════════ - // IVF 索引 - // ═══════════════════════════════════════════ - - async _getIndex(colId, vectors) { - // 内存缓存 - if (this._indexCache.has(colId)) { - const cached = this._indexCache.get(colId); - if (cached.version === this._indexVersion(vectors)) { - return cached; - } - } - - // IndexedDB 缓存 - const saved = await new Promise((resolve, reject) => { - const req = this._tx('indexes').get(colId); - req.onsuccess = () => resolve(req.result || null); - req.onerror = () => reject(req.error); - }); - - if (saved && saved.version === this._indexVersion(vectors)) { - // 反序列化 invertedLists(IndexedDB 不存 Map,存的是对象) - if (!(saved.invertedLists instanceof Map)) { - saved.invertedLists = new Map(Object.entries(saved.invertedLists || {})); - } - this._indexCache.set(colId, saved); - return saved; - } - - // 重建 - console.log(`[VectorStore] 构建 IVF 索引: ${colId} (${vectors.length} 向量)`); - const index = this._buildIVFIndex(vectors); - index.collectionId = colId; - index.version = this._indexVersion(vectors); - - // 持久化(Map → 普通对象) - const toSave = { - collectionId: index.collectionId, - version: index.version, - centroids: index.centroids, - invertedLists: Object.fromEntries(index.invertedLists) - }; - await new Promise((resolve, reject) => { - const req = this._tx('indexes', 'readwrite').put(toSave); - req.onsuccess = () => resolve(); - req.onerror = () => reject(req.error); - }); - - this._indexCache.set(colId, index); - return index; - } - - _indexVersion(vectors) { - if (vectors.length === 0) return '0'; - return `${vectors.length}_${vectors[0]?.id || ''}_${vectors[vectors.length - 1]?.id || ''}`; - } - - /** - * K-Means 聚类构建 IVF 索引 - */ - _buildIVFIndex(vectors) { - const N = vectors.length; - const dim = vectors[0].embedding.length; - - const K = Math.max(2, Math.min(DEFAULT_K, Math.floor(N / 10))); - - // 1. K-Means++ 初始化 - const centroids = this._kmeansPPInit(vectors, K, dim); - - // 2. K-Means 迭代 - const assignments = new Array(N); - for (let iter = 0; iter < KMEANS_ITERS; iter++) { - // E-step - for (let i = 0; i < N; i++) { - let bestCluster = 0; - let bestScore = -Infinity; - for (let k = 0; k < K; k++) { - const score = VectorStore.cosineSimilarity(vectors[i].embedding, centroids[k]); - if (score > bestScore) { - bestScore = score; - bestCluster = k; - } - } - assignments[i] = bestCluster; - } - - // M-step - const newCentroids = Array.from({ length: K }, () => new Array(dim).fill(0)); - const counts = new Array(K).fill(0); - - for (let i = 0; i < N; i++) { - const k = assignments[i]; - counts[k]++; - const emb = vectors[i].embedding; - for (let d = 0; d < dim; d++) { - newCentroids[k][d] += emb[d]; - } - } - - for (let k = 0; k < K; k++) { - if (counts[k] > 0) { - for (let d = 0; d < dim; d++) { - newCentroids[k][d] /= counts[k]; - } - this._normalize(newCentroids[k]); - } else { - const randIdx = Math.floor(Math.random() * N); - newCentroids[k] = [...vectors[randIdx].embedding]; - } - } - - // 收敛检测 - let converged = true; - for (let k = 0; k < K; k++) { - if (VectorStore.cosineSimilarity(centroids[k], newCentroids[k]) < 0.999) { - converged = false; - break; - } - } - - for (let k = 0; k < K; k++) { - centroids[k] = newCentroids[k]; - } - - if (converged) break; - } - - // 3. 构建倒排链表 - const invertedLists = new Map(); - for (let i = 0; i < N; i++) { - const k = assignments[i]; - if (!invertedLists.has(k)) invertedLists.set(k, []); - invertedLists.get(k).push(vectors[i].id); - } - - return { centroids, invertedLists }; - } - - /** - * K-Means++ 初始化 - */ - _kmeansPPInit(vectors, K, dim) { - const centroids = []; - const first = Math.floor(Math.random() * vectors.length); - centroids.push([...vectors[first].embedding]); - - for (let k = 1; k < K; k++) { - const dists = vectors.map(v => { - let minDist = Infinity; - for (const c of centroids) { - const sim = VectorStore.cosineSimilarity(v.embedding, c); - const dist = 1 - sim; - if (dist < minDist) minDist = dist; - } - return minDist; - }); - - const total = dists.reduce((s, d) => s + d, 0); - if (total === 0) { - centroids.push([...vectors[Math.floor(Math.random() * vectors.length)].embedding]); - continue; - } - - let r = Math.random() * total; - for (let i = 0; i < vectors.length; i++) { - r -= dists[i]; - if (r <= 0) { - centroids.push([...vectors[i].embedding]); - break; - } - } - } - return centroids; - } - - /** 归一化向量(in-place) */ - _normalize(vec) { - let norm = 0; - for (let i = 0; i < vec.length; i++) norm += vec[i] * vec[i]; - norm = Math.sqrt(norm); - if (norm > 0) for (let i = 0; i < vec.length; i++) vec[i] /= norm; - } - - // ═══════════════════════════════════════════ - // 工具 - // ═══════════════════════════════════════════ - - static cosineSimilarity(a, b) { - if (!a || !b || a.length !== b.length) return 0; - let dot = 0, normA = 0, normB = 0; - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - const denom = Math.sqrt(normA) * Math.sqrt(normB); - return denom === 0 ? 0 : dot / denom; - } -} diff --git a/manifest.json b/manifest.json deleted file mode 100644 index ec9255e..0000000 --- a/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Metona Ollama Client", - "short_name": "Ollama", - "description": "基于原生 JavaScript 的 Ollama AI 客户端 - 流式聊天、多模态、历史管理", - "start_url": "./index.html", - "display": "standalone", - "background_color": "#0a0a1a", - "theme_color": "#0a0a1a", - "orientation": "any", - "icons": [ - { - "src": "data:image/svg+xml,🦙", - "sizes": "any", - "type": "image/svg+xml", - "purpose": "any maskable" - } - ], - "categories": ["productivity", "utilities"] -} diff --git a/package-lock.json b/package-lock.json index 91fdd8d..d011096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,25 @@ { "name": "metona-ollama-desktop", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "metona-ollama-desktop", - "version": "1.0.0", + "version": "2.0.0", "license": "MIT", "devDependencies": { + "@types/node": "^20.17.0", "electron": "^33.4.11", - "electron-builder": "^25.0.0" + "electron-builder": "^25.1.8", + "typescript": "^5.7.0", + "vite": "^5.4.0" } }, "node_modules/@develar/schema-utils": { "version": "2.6.5", + "resolved": "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", "dev": true, "license": "MIT", "dependencies": { @@ -31,6 +36,8 @@ }, "node_modules/@electron/asar": { "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", "dev": true, "license": "MIT", "dependencies": { @@ -47,11 +54,15 @@ }, "node_modules/@electron/asar/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/@electron/asar/node_modules/brace-expansion": { "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -61,6 +72,8 @@ }, "node_modules/@electron/asar/node_modules/minimatch": { "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -72,6 +85,8 @@ }, "node_modules/@electron/get": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", "dev": true, "license": "MIT", "dependencies": { @@ -92,6 +107,8 @@ }, "node_modules/@electron/notarize": { "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", "dev": true, "license": "MIT", "dependencies": { @@ -105,6 +122,8 @@ }, "node_modules/@electron/notarize/node_modules/fs-extra": { "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", "dependencies": { @@ -119,6 +138,8 @@ }, "node_modules/@electron/notarize/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -130,6 +151,8 @@ }, "node_modules/@electron/notarize/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -138,6 +161,8 @@ }, "node_modules/@electron/osx-sign": { "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/@electron/osx-sign/-/osx-sign-1.3.1.tgz", + "integrity": "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -158,6 +183,8 @@ }, "node_modules/@electron/osx-sign/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -171,6 +198,8 @@ }, "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { "version": "4.0.10", + "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, "license": "MIT", "engines": { @@ -182,6 +211,8 @@ }, "node_modules/@electron/osx-sign/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -193,6 +224,8 @@ }, "node_modules/@electron/osx-sign/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -201,6 +234,8 @@ }, "node_modules/@electron/rebuild": { "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@electron/rebuild/-/rebuild-3.6.1.tgz", + "integrity": "sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==", "dev": true, "license": "MIT", "dependencies": { @@ -228,6 +263,8 @@ }, "node_modules/@electron/rebuild/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -241,6 +278,8 @@ }, "node_modules/@electron/rebuild/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -252,6 +291,8 @@ }, "node_modules/@electron/rebuild/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -263,6 +304,8 @@ }, "node_modules/@electron/rebuild/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -271,6 +314,8 @@ }, "node_modules/@electron/universal": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@electron/universal/-/universal-2.0.1.tgz", + "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==", "dev": true, "license": "MIT", "dependencies": { @@ -288,11 +333,15 @@ }, "node_modules/@electron/universal/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/@electron/universal/node_modules/brace-expansion": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -301,6 +350,8 @@ }, "node_modules/@electron/universal/node_modules/fs-extra": { "version": "11.3.4", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "dependencies": { @@ -314,6 +365,8 @@ }, "node_modules/@electron/universal/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -325,6 +378,8 @@ }, "node_modules/@electron/universal/node_modules/minimatch": { "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { @@ -339,19 +394,416 @@ }, "node_modules/@electron/universal/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -368,6 +820,8 @@ }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -379,6 +833,8 @@ }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -390,11 +846,15 @@ }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -411,6 +871,8 @@ }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { @@ -425,6 +887,8 @@ }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -441,6 +905,8 @@ }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", "dev": true, "funding": [ { @@ -462,6 +928,8 @@ }, "node_modules/@malept/flatpak-bundler": { "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -476,6 +944,8 @@ }, "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", "dependencies": { @@ -490,6 +960,8 @@ }, "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -501,6 +973,8 @@ }, "node_modules/@malept/flatpak-bundler/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -509,6 +983,8 @@ }, "node_modules/@npmcli/fs": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", "dev": true, "license": "ISC", "dependencies": { @@ -521,6 +997,8 @@ }, "node_modules/@npmcli/fs/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -532,6 +1010,9 @@ }, "node_modules/@npmcli/move-file": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", "dev": true, "license": "MIT", "dependencies": { @@ -544,6 +1025,8 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -551,8 +1034,360 @@ "node": ">=14" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, "license": "MIT", "engines": { @@ -564,6 +1399,8 @@ }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "dev": true, "license": "MIT", "dependencies": { @@ -575,6 +1412,8 @@ }, "node_modules/@tootallnate/once": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, "license": "MIT", "engines": { @@ -583,6 +1422,8 @@ }, "node_modules/@types/cacheable-request": { "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "dev": true, "license": "MIT", "dependencies": { @@ -594,14 +1435,25 @@ }, "node_modules/@types/debug": { "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, "license": "MIT", "dependencies": { "@types/ms": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/fs-extra": { "version": "9.0.13", + "resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, "license": "MIT", "dependencies": { @@ -610,11 +1462,15 @@ }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", "dev": true, "license": "MIT" }, "node_modules/@types/keyv": { "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", "dev": true, "license": "MIT", "dependencies": { @@ -623,11 +1479,15 @@ }, "node_modules/@types/ms": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "20.19.39", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { @@ -636,6 +1496,8 @@ }, "node_modules/@types/plist": { "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", "dev": true, "license": "MIT", "optional": true, @@ -646,6 +1508,8 @@ }, "node_modules/@types/responselike": { "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "license": "MIT", "dependencies": { @@ -654,12 +1518,16 @@ }, "node_modules/@types/verror": { "version": "1.10.11", + "resolved": "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", "dev": true, "license": "MIT", "optional": true }, "node_modules/@types/yauzl": { "version": "2.10.3", + "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "license": "MIT", "optional": true, @@ -669,6 +1537,8 @@ }, "node_modules/@xmldom/xmldom": { "version": "0.8.12", + "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", "dev": true, "license": "MIT", "engines": { @@ -677,16 +1547,22 @@ }, "node_modules/7zip-bin": { "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", "dev": true, "license": "MIT" }, "node_modules/abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "license": "ISC" }, "node_modules/agent-base": { "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -695,6 +1571,8 @@ }, "node_modules/agentkeepalive": { "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -706,6 +1584,8 @@ }, "node_modules/aggregate-error": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "license": "MIT", "dependencies": { @@ -718,6 +1598,8 @@ }, "node_modules/ajv": { "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -733,6 +1615,8 @@ }, "node_modules/ajv-keywords": { "version": "3.5.2", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -741,6 +1625,8 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -749,6 +1635,8 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -763,11 +1651,15 @@ }, "node_modules/app-builder-bin": { "version": "5.0.0-alpha.10", + "resolved": "https://registry.npmmirror.com/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz", + "integrity": "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==", "dev": true, "license": "MIT" }, "node_modules/app-builder-lib": { "version": "25.1.8", + "resolved": "https://registry.npmmirror.com/app-builder-lib/-/app-builder-lib-25.1.8.tgz", + "integrity": "sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==", "dev": true, "license": "MIT", "dependencies": { @@ -814,6 +1706,8 @@ }, "node_modules/app-builder-lib/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -827,6 +1721,8 @@ }, "node_modules/app-builder-lib/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -838,6 +1734,8 @@ }, "node_modules/app-builder-lib/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -849,6 +1747,8 @@ }, "node_modules/app-builder-lib/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -857,11 +1757,15 @@ }, "node_modules/aproba": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", "dev": true, "license": "ISC" }, "node_modules/archiver": { "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", "peer": true, @@ -880,6 +1784,8 @@ }, "node_modules/archiver-utils": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", "peer": true, @@ -901,6 +1807,8 @@ }, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "peer": true, @@ -916,12 +1824,16 @@ }, "node_modules/archiver-utils/node_modules/safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "peer": true, @@ -931,6 +1843,9 @@ }, "node_modules/are-we-there-yet": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", "dev": true, "license": "ISC", "dependencies": { @@ -943,11 +1858,15 @@ }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, "node_modules/assert-plus": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, "license": "MIT", "optional": true, @@ -957,6 +1876,8 @@ }, "node_modules/astral-regex": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "license": "MIT", "optional": true, @@ -966,11 +1887,15 @@ }, "node_modules/async": { "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true, "license": "MIT" }, "node_modules/async-exit-hook": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true, "license": "MIT", "engines": { @@ -979,11 +1904,15 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, "license": "ISC", "engines": { @@ -992,6 +1921,8 @@ }, "node_modules/balanced-match": { "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { @@ -1000,6 +1931,8 @@ }, "node_modules/base64-js": { "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -1019,6 +1952,8 @@ }, "node_modules/bl": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", "dependencies": { @@ -1029,11 +1964,15 @@ }, "node_modules/bluebird": { "version": "3.7.2", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true, "license": "MIT" }, "node_modules/bluebird-lst": { "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", "dev": true, "license": "MIT", "dependencies": { @@ -1042,12 +1981,17 @@ }, "node_modules/boolean": { "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "license": "MIT", "optional": true }, "node_modules/brace-expansion": { "version": "5.0.5", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1059,6 +2003,8 @@ }, "node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "funding": [ { @@ -1082,6 +2028,8 @@ }, "node_modules/buffer-crc32": { "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", "engines": { @@ -1090,11 +2038,15 @@ }, "node_modules/buffer-from": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT" }, "node_modules/builder-util": { "version": "25.1.7", + "resolved": "https://registry.npmmirror.com/builder-util/-/builder-util-25.1.7.tgz", + "integrity": "sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==", "dev": true, "license": "MIT", "dependencies": { @@ -1118,6 +2070,8 @@ }, "node_modules/builder-util-runtime": { "version": "9.2.10", + "resolved": "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz", + "integrity": "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==", "dev": true, "license": "MIT", "dependencies": { @@ -1130,6 +2084,8 @@ }, "node_modules/builder-util/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1143,6 +2099,8 @@ }, "node_modules/builder-util/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -1154,6 +2112,8 @@ }, "node_modules/builder-util/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -1162,6 +2122,8 @@ }, "node_modules/cacache": { "version": "16.1.3", + "resolved": "https://registry.npmmirror.com/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1190,11 +2152,15 @@ }, "node_modules/cacache/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/cacache/node_modules/brace-expansion": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -1203,6 +2169,9 @@ }, "node_modules/cacache/node_modules/glob": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -1221,6 +2190,8 @@ }, "node_modules/cacache/node_modules/lru-cache": { "version": "7.18.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", "engines": { @@ -1229,6 +2200,8 @@ }, "node_modules/cacache/node_modules/minimatch": { "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -1240,6 +2213,8 @@ }, "node_modules/cacheable-lookup": { "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", "dev": true, "license": "MIT", "engines": { @@ -1248,6 +2223,8 @@ }, "node_modules/cacheable-request": { "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dev": true, "license": "MIT", "dependencies": { @@ -1265,6 +2242,8 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1277,6 +2256,8 @@ }, "node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -1292,6 +2273,8 @@ }, "node_modules/chownr": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "license": "ISC", "engines": { @@ -1300,11 +2283,15 @@ }, "node_modules/chromium-pickle-js": { "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", "dev": true, "license": "MIT" }, "node_modules/ci-info": { "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, "funding": [ { @@ -1319,6 +2306,8 @@ }, "node_modules/clean-stack": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "license": "MIT", "engines": { @@ -1327,6 +2316,8 @@ }, "node_modules/cli-cursor": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "license": "MIT", "dependencies": { @@ -1338,6 +2329,8 @@ }, "node_modules/cli-spinners": { "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "license": "MIT", "engines": { @@ -1349,6 +2342,8 @@ }, "node_modules/cli-truncate": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, "license": "MIT", "optional": true, @@ -1365,6 +2360,8 @@ }, "node_modules/cliui": { "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1378,6 +2375,8 @@ }, "node_modules/clone": { "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, "license": "MIT", "engines": { @@ -1386,6 +2385,8 @@ }, "node_modules/clone-response": { "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, "license": "MIT", "dependencies": { @@ -1397,6 +2398,8 @@ }, "node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1408,11 +2411,15 @@ }, "node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/color-support": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true, "license": "ISC", "bin": { @@ -1421,6 +2428,8 @@ }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { @@ -1432,6 +2441,8 @@ }, "node_modules/commander": { "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true, "license": "MIT", "engines": { @@ -1440,6 +2451,8 @@ }, "node_modules/compare-version": { "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", "dev": true, "license": "MIT", "engines": { @@ -1448,6 +2461,8 @@ }, "node_modules/compress-commons": { "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", "peer": true, @@ -1463,11 +2478,15 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/config-file-ts": { "version": "0.2.8-rc1", + "resolved": "https://registry.npmmirror.com/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz", + "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==", "dev": true, "license": "MIT", "dependencies": { @@ -1477,11 +2496,15 @@ }, "node_modules/config-file-ts/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/config-file-ts/node_modules/brace-expansion": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -1489,7 +2512,9 @@ } }, "node_modules/config-file-ts/node_modules/glob": { - "version": "10.5.0", + "version": "10.4.5", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -1509,6 +2534,8 @@ }, "node_modules/config-file-ts/node_modules/minimatch": { "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { @@ -1523,6 +2550,8 @@ }, "node_modules/config-file-ts/node_modules/minipass": { "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -1531,16 +2560,22 @@ }, "node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true, "license": "ISC" }, "node_modules/core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true, "license": "MIT" }, "node_modules/crc": { "version": "3.8.0", + "resolved": "https://registry.npmmirror.com/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", "dev": true, "license": "MIT", "optional": true, @@ -1550,6 +2585,8 @@ }, "node_modules/crc-32": { "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1562,6 +2599,8 @@ }, "node_modules/crc32-stream": { "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", "peer": true, @@ -1575,6 +2614,8 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1588,6 +2629,8 @@ }, "node_modules/debug": { "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1604,6 +2647,8 @@ }, "node_modules/decompress-response": { "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1618,6 +2663,8 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "license": "MIT", "engines": { @@ -1629,6 +2676,8 @@ }, "node_modules/defaults": { "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1640,6 +2689,8 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, "license": "MIT", "engines": { @@ -1648,6 +2699,8 @@ }, "node_modules/define-data-property": { "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", "optional": true, @@ -1665,6 +2718,8 @@ }, "node_modules/define-properties": { "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "optional": true, @@ -1682,6 +2737,8 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { @@ -1690,11 +2747,15 @@ }, "node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1703,12 +2764,16 @@ }, "node_modules/detect-node": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true, "license": "MIT", "optional": true }, "node_modules/dir-compare": { "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1718,11 +2783,15 @@ }, "node_modules/dir-compare/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/dir-compare/node_modules/brace-expansion": { "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -1732,6 +2801,8 @@ }, "node_modules/dir-compare/node_modules/minimatch": { "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1743,6 +2814,8 @@ }, "node_modules/dmg-builder": { "version": "25.1.8", + "resolved": "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-25.1.8.tgz", + "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1757,9 +2830,47 @@ "dmg-license": "^1.0.11" } }, - "node_modules/dmg-builder/node_modules/dmg-license": { + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "resolved": "https://registry.npmmirror.com/dmg-license/-/dmg-license-1.0.11.tgz", "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", "dev": true, "license": "MIT", @@ -1784,58 +2895,10 @@ "node": ">=8" } }, - "node_modules/dmg-builder/node_modules/fs-extra": { - "version": "10.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dmg-builder/node_modules/iconv-corefoundation": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", - "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "cli-truncate": "^2.1.0", - "node-addon-api": "^1.6.3" - }, - "engines": { - "node": "^8.11.2 || >=10" - } - }, - "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/dmg-builder/node_modules/universalify": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/dotenv": { "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -1847,6 +2910,8 @@ }, "node_modules/dotenv-expand": { "version": "11.0.7", + "resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1861,6 +2926,8 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { @@ -1874,11 +2941,15 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ejs": { "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1893,7 +2964,7 @@ }, "node_modules/electron": { "version": "33.4.11", - "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "resolved": "https://registry.npmmirror.com/electron/-/electron-33.4.11.tgz", "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", "dev": true, "hasInstallScript": true, @@ -1912,6 +2983,8 @@ }, "node_modules/electron-builder": { "version": "25.1.8", + "resolved": "https://registry.npmmirror.com/electron-builder/-/electron-builder-25.1.8.tgz", + "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", "dev": true, "license": "MIT", "dependencies": { @@ -1936,6 +3009,8 @@ }, "node_modules/electron-builder-squirrel-windows": { "version": "25.1.8", + "resolved": "https://registry.npmmirror.com/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", + "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", "dev": true, "license": "MIT", "peer": true, @@ -1948,6 +3023,8 @@ }, "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "peer": true, @@ -1962,6 +3039,8 @@ }, "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "peer": true, @@ -1974,6 +3053,8 @@ }, "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "peer": true, @@ -1983,6 +3064,8 @@ }, "node_modules/electron-builder/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1996,6 +3079,8 @@ }, "node_modules/electron-builder/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2007,6 +3092,8 @@ }, "node_modules/electron-builder/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -2015,6 +3102,8 @@ }, "node_modules/electron-publish": { "version": "25.1.7", + "resolved": "https://registry.npmmirror.com/electron-publish/-/electron-publish-25.1.7.tgz", + "integrity": "sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2029,6 +3118,8 @@ }, "node_modules/electron-publish/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2042,6 +3133,8 @@ }, "node_modules/electron-publish/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2053,6 +3146,8 @@ }, "node_modules/electron-publish/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -2061,11 +3156,15 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/encoding": { "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "license": "MIT", "optional": true, @@ -2075,6 +3174,8 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", "dependencies": { @@ -2083,6 +3184,8 @@ }, "node_modules/env-paths": { "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { @@ -2091,11 +3194,15 @@ }, "node_modules/err-code": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true, "license": "MIT" }, "node_modules/es-define-property": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { @@ -2104,6 +3211,8 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", "engines": { @@ -2112,6 +3221,8 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", "dependencies": { @@ -2123,6 +3234,8 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -2137,12 +3250,55 @@ }, "node_modules/es6-error": { "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, "license": "MIT", "optional": true }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -2151,6 +3307,8 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "optional": true, @@ -2163,11 +3321,15 @@ }, "node_modules/exponential-backoff": { "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, "node_modules/extract-zip": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2187,6 +3349,8 @@ }, "node_modules/extsprintf": { "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", "dev": true, "engines": [ "node >=0.6.0" @@ -2196,16 +3360,22 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fd-slicer": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2214,6 +3384,8 @@ }, "node_modules/filelist": { "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2222,11 +3394,15 @@ }, "node_modules/filelist/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/filelist/node_modules/brace-expansion": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -2235,6 +3411,8 @@ }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -2246,6 +3424,8 @@ }, "node_modules/foreground-child": { "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -2261,6 +3441,8 @@ }, "node_modules/foreground-child/node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -2272,6 +3454,8 @@ }, "node_modules/form-data": { "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { @@ -2287,12 +3471,16 @@ }, "node_modules/fs-constants": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, "license": "MIT", "peer": true }, "node_modules/fs-extra": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "license": "MIT", "dependencies": { @@ -2306,6 +3494,8 @@ }, "node_modules/fs-minipass": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "license": "ISC", "dependencies": { @@ -2317,11 +3507,30 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { @@ -2330,6 +3539,9 @@ }, "node_modules/gauge": { "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", "dev": true, "license": "ISC", "dependencies": { @@ -2348,6 +3560,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -2356,6 +3570,8 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2379,6 +3595,8 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2391,6 +3609,8 @@ }, "node_modules/get-stream": { "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "license": "MIT", "dependencies": { @@ -2405,6 +3625,9 @@ }, "node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -2424,11 +3647,15 @@ }, "node_modules/glob/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2438,6 +3665,8 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2449,6 +3678,8 @@ }, "node_modules/global-agent": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", "dev": true, "license": "BSD-3-Clause", "optional": true, @@ -2466,6 +3697,8 @@ }, "node_modules/global-agent/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "optional": true, @@ -2478,6 +3711,8 @@ }, "node_modules/globalthis": { "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", "optional": true, @@ -2494,6 +3729,8 @@ }, "node_modules/gopd": { "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -2505,6 +3742,8 @@ }, "node_modules/got": { "version": "11.8.6", + "resolved": "https://registry.npmmirror.com/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2529,11 +3768,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -2542,6 +3785,8 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", "optional": true, @@ -2554,6 +3799,8 @@ }, "node_modules/has-symbols": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -2565,6 +3812,8 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { @@ -2579,11 +3828,15 @@ }, "node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true, "license": "ISC" }, "node_modules/hasown": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2595,6 +3848,8 @@ }, "node_modules/hosted-git-info": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "license": "ISC", "dependencies": { @@ -2606,11 +3861,15 @@ }, "node_modules/http-cache-semantics": { "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -2623,6 +3882,8 @@ }, "node_modules/http2-wrapper": { "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", "dev": true, "license": "MIT", "dependencies": { @@ -2635,6 +3896,8 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -2647,14 +3910,36 @@ }, "node_modules/humanize-ms": { "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dev": true, "license": "MIT", "dependencies": { "ms": "^2.0.0" } }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2666,6 +3951,8 @@ }, "node_modules/ieee754": { "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ { @@ -2685,6 +3972,8 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -2693,6 +3982,8 @@ }, "node_modules/indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", "engines": { @@ -2701,11 +3992,16 @@ }, "node_modules/infer-owner": { "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true, "license": "ISC" }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { @@ -2715,11 +4011,15 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, "node_modules/ip-address": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -2728,6 +4028,8 @@ }, "node_modules/is-ci": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2739,6 +4041,8 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -2747,6 +4051,8 @@ }, "node_modules/is-interactive": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, "license": "MIT", "engines": { @@ -2755,11 +4061,15 @@ }, "node_modules/is-lambda": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true, "license": "MIT" }, "node_modules/is-unicode-supported": { "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", "engines": { @@ -2771,12 +4081,16 @@ }, "node_modules/isarray": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", "dev": true, "license": "MIT", "engines": { @@ -2788,11 +4102,15 @@ }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -2807,6 +4125,8 @@ }, "node_modules/jake": { "version": "10.9.4", + "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2823,6 +4143,8 @@ }, "node_modules/js-yaml": { "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -2834,22 +4156,30 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC", "optional": true }, "node_modules/json5": { "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -2861,6 +4191,8 @@ }, "node_modules/jsonfile": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -2869,6 +4201,8 @@ }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -2877,11 +4211,15 @@ }, "node_modules/lazy-val": { "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "dev": true, "license": "MIT" }, "node_modules/lazystream": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", "peer": true, @@ -2894,6 +4232,8 @@ }, "node_modules/lazystream/node_modules/readable-stream": { "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "peer": true, @@ -2909,12 +4249,16 @@ }, "node_modules/lazystream/node_modules/safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "peer": true, @@ -2924,41 +4268,55 @@ }, "node_modules/lodash": { "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, "node_modules/lodash.defaults": { "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, "license": "MIT", "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2974,6 +4332,8 @@ }, "node_modules/lowercase-keys": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true, "license": "MIT", "engines": { @@ -2982,6 +4342,8 @@ }, "node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -2993,6 +4355,8 @@ }, "node_modules/make-fetch-happen": { "version": "10.2.1", + "resolved": "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", "dev": true, "license": "ISC", "dependencies": { @@ -3019,6 +4383,8 @@ }, "node_modules/make-fetch-happen/node_modules/agent-base": { "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3030,6 +4396,8 @@ }, "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "license": "MIT", "dependencies": { @@ -3043,6 +4411,8 @@ }, "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "license": "MIT", "dependencies": { @@ -3055,6 +4425,8 @@ }, "node_modules/make-fetch-happen/node_modules/lru-cache": { "version": "7.18.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", "engines": { @@ -3063,6 +4435,8 @@ }, "node_modules/matcher": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "dev": true, "license": "MIT", "optional": true, @@ -3075,6 +4449,8 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -3083,6 +4459,8 @@ }, "node_modules/mime": { "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", "bin": { @@ -3094,6 +4472,8 @@ }, "node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { @@ -3102,6 +4482,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { @@ -3113,6 +4495,8 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -3121,6 +4505,8 @@ }, "node_modules/mimic-response": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true, "license": "MIT", "engines": { @@ -3129,6 +4515,8 @@ }, "node_modules/minimatch": { "version": "10.2.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3143,6 +4531,8 @@ }, "node_modules/minimist": { "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", "funding": { @@ -3151,6 +4541,8 @@ }, "node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -3162,6 +4554,8 @@ }, "node_modules/minipass-collect": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "dev": true, "license": "ISC", "dependencies": { @@ -3173,6 +4567,8 @@ }, "node_modules/minipass-fetch": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, "license": "MIT", "dependencies": { @@ -3189,6 +4585,8 @@ }, "node_modules/minipass-flush": { "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3200,6 +4598,8 @@ }, "node_modules/minipass-pipeline": { "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "license": "ISC", "dependencies": { @@ -3211,6 +4611,8 @@ }, "node_modules/minipass-sized": { "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "license": "ISC", "dependencies": { @@ -3222,6 +4624,8 @@ }, "node_modules/minizlib": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "license": "MIT", "dependencies": { @@ -3234,6 +4638,8 @@ }, "node_modules/mkdirp": { "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", "bin": { @@ -3245,11 +4651,34 @@ }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.4", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, "license": "MIT", "engines": { @@ -3258,6 +4687,8 @@ }, "node_modules/node-abi": { "version": "3.89.0", + "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", "dev": true, "license": "MIT", "dependencies": { @@ -3269,6 +4700,8 @@ }, "node_modules/node-abi/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3280,12 +4713,16 @@ }, "node_modules/node-addon-api": { "version": "1.7.2", + "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", "dev": true, "license": "MIT", "optional": true }, "node_modules/node-api-version": { "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3294,6 +4731,8 @@ }, "node_modules/node-api-version/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3305,6 +4744,8 @@ }, "node_modules/node-gyp": { "version": "9.4.1", + "resolved": "https://registry.npmmirror.com/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3329,6 +4770,8 @@ }, "node_modules/node-gyp/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3340,6 +4783,8 @@ }, "node_modules/nopt": { "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, "license": "ISC", "dependencies": { @@ -3354,6 +4799,8 @@ }, "node_modules/normalize-path": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "peer": true, @@ -3363,6 +4810,8 @@ }, "node_modules/normalize-url": { "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true, "license": "MIT", "engines": { @@ -3374,6 +4823,9 @@ }, "node_modules/npmlog": { "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", "dev": true, "license": "ISC", "dependencies": { @@ -3388,6 +4840,8 @@ }, "node_modules/object-keys": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", "optional": true, @@ -3397,6 +4851,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { @@ -3405,6 +4861,8 @@ }, "node_modules/onetime": { "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -3419,6 +4877,8 @@ }, "node_modules/ora": { "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3441,6 +4901,8 @@ }, "node_modules/p-cancelable": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true, "license": "MIT", "engines": { @@ -3449,6 +4911,8 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3463,6 +4927,8 @@ }, "node_modules/p-map": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3477,11 +4943,15 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { @@ -3490,6 +4960,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -3498,6 +4970,8 @@ }, "node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3513,11 +4987,15 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3526,6 +5004,8 @@ }, "node_modules/pe-library": { "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", "dev": true, "license": "MIT", "engines": { @@ -3539,16 +5019,22 @@ }, "node_modules/pend": { "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/plist": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3560,14 +5046,47 @@ "node": ">=10.4.0" } }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "license": "MIT", "peer": true }, "node_modules/progress": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "license": "MIT", "engines": { @@ -3576,11 +5095,15 @@ }, "node_modules/promise-inflight": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true, "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "license": "MIT", "dependencies": { @@ -3593,6 +5116,8 @@ }, "node_modules/pump": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", "dependencies": { @@ -3602,6 +5127,8 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -3610,6 +5137,8 @@ }, "node_modules/quick-lru": { "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "license": "MIT", "engines": { @@ -3621,6 +5150,8 @@ }, "node_modules/read-binary-file-arch": { "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", "dev": true, "license": "MIT", "dependencies": { @@ -3632,6 +5163,8 @@ }, "node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", "dependencies": { @@ -3645,6 +5178,8 @@ }, "node_modules/readdir-glob": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -3654,12 +5189,16 @@ }, "node_modules/readdir-glob/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT", "peer": true }, "node_modules/readdir-glob/node_modules/brace-expansion": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "peer": true, @@ -3669,6 +5208,8 @@ }, "node_modules/readdir-glob/node_modules/minimatch": { "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "peer": true, @@ -3681,6 +5222,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -3689,6 +5232,8 @@ }, "node_modules/resedit": { "version": "1.7.2", + "resolved": "https://registry.npmmirror.com/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", "dev": true, "license": "MIT", "dependencies": { @@ -3705,11 +5250,15 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true, "license": "MIT" }, "node_modules/responselike": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "dev": true, "license": "MIT", "dependencies": { @@ -3721,6 +5270,8 @@ }, "node_modules/restore-cursor": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "license": "MIT", "dependencies": { @@ -3733,6 +5284,8 @@ }, "node_modules/retry": { "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", "engines": { @@ -3741,6 +5294,9 @@ }, "node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -3755,6 +5311,8 @@ }, "node_modules/roarr": { "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", "dev": true, "license": "BSD-3-Clause", "optional": true, @@ -3770,8 +5328,55 @@ "node": ">=8.0" } }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -3791,11 +5396,15 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/sanitize-filename": { "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", "dev": true, "license": "WTFPL OR ISC", "dependencies": { @@ -3804,6 +5413,8 @@ }, "node_modules/sax": { "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3812,6 +5423,8 @@ }, "node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -3820,12 +5433,16 @@ }, "node_modules/semver-compare": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true, "license": "MIT", "optional": true }, "node_modules/serialize-error": { "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "dev": true, "license": "MIT", "optional": true, @@ -3841,11 +5458,15 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -3857,6 +5478,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -3865,11 +5488,15 @@ }, "node_modules/signal-exit": { "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/simple-update-notifier": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3881,6 +5508,8 @@ }, "node_modules/simple-update-notifier/node_modules/semver": { "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3892,6 +5521,8 @@ }, "node_modules/slice-ansi": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, "license": "MIT", "optional": true, @@ -3906,6 +5537,8 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", "engines": { @@ -3915,6 +5548,8 @@ }, "node_modules/socks": { "version": "2.8.7", + "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { @@ -3928,6 +5563,8 @@ }, "node_modules/socks-proxy-agent": { "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, "license": "MIT", "dependencies": { @@ -3941,6 +5578,8 @@ }, "node_modules/socks-proxy-agent/node_modules/agent-base": { "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3952,6 +5591,18 @@ }, "node_modules/source-map": { "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3960,6 +5611,8 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -3969,12 +5622,16 @@ }, "node_modules/sprintf-js": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true, "license": "BSD-3-Clause", "optional": true }, "node_modules/ssri": { "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "dev": true, "license": "ISC", "dependencies": { @@ -3986,6 +5643,8 @@ }, "node_modules/stat-mode": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", "dev": true, "license": "MIT", "engines": { @@ -3994,6 +5653,8 @@ }, "node_modules/string_decoder": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", "dependencies": { @@ -4002,6 +5663,8 @@ }, "node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -4016,6 +5679,8 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -4029,6 +5694,8 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -4041,6 +5708,8 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -4052,6 +5721,8 @@ }, "node_modules/sumchecker": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4063,6 +5734,8 @@ }, "node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -4074,6 +5747,8 @@ }, "node_modules/tar": { "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "license": "ISC", "dependencies": { @@ -4090,6 +5765,8 @@ }, "node_modules/tar-stream": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", "peer": true, @@ -4106,6 +5783,8 @@ }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "license": "ISC", "engines": { @@ -4114,6 +5793,8 @@ }, "node_modules/temp-file": { "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", "dev": true, "license": "MIT", "dependencies": { @@ -4123,6 +5804,8 @@ }, "node_modules/temp-file/node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4136,6 +5819,8 @@ }, "node_modules/temp-file/node_modules/jsonfile": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -4147,6 +5832,8 @@ }, "node_modules/temp-file/node_modules/universalify": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -4155,6 +5842,8 @@ }, "node_modules/tmp": { "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, "license": "MIT", "engines": { @@ -4163,6 +5852,8 @@ }, "node_modules/tmp-promise": { "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4171,6 +5862,8 @@ }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", "dev": true, "license": "WTFPL", "dependencies": { @@ -4179,6 +5872,8 @@ }, "node_modules/type-fest": { "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true, "license": "(MIT OR CC0-1.0)", "optional": true, @@ -4191,6 +5886,8 @@ }, "node_modules/typescript": { "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4203,11 +5900,15 @@ }, "node_modules/undici-types": { "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/unique-filename": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, "license": "ISC", "dependencies": { @@ -4219,6 +5920,8 @@ }, "node_modules/unique-slug": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, "license": "ISC", "dependencies": { @@ -4230,6 +5933,8 @@ }, "node_modules/universalify": { "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", "engines": { @@ -4238,6 +5943,8 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4246,16 +5953,22 @@ }, "node_modules/utf8-byte-length": { "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", "dev": true, "license": "(WTFPL OR MIT)" }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "license": "MIT" }, "node_modules/verror": { "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", "dev": true, "license": "MIT", "optional": true, @@ -4268,8 +5981,70 @@ "node": ">=0.6.0" } }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "node_modules/wcwidth": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, "license": "MIT", "dependencies": { @@ -4278,6 +6053,8 @@ }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -4292,6 +6069,8 @@ }, "node_modules/wide-align": { "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, "license": "ISC", "dependencies": { @@ -4300,6 +6079,8 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4317,6 +6098,8 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4333,11 +6116,15 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/xmlbuilder": { "version": "15.1.1", + "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", "dev": true, "license": "MIT", "engines": { @@ -4346,6 +6133,8 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -4354,11 +6143,15 @@ }, "node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -4376,6 +6169,8 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -4384,6 +6179,8 @@ }, "node_modules/yauzl": { "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "license": "MIT", "dependencies": { @@ -4393,6 +6190,8 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -4404,6 +6203,8 @@ }, "node_modules/zip-stream": { "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", "peer": true, @@ -4418,6 +6219,8 @@ }, "node_modules/zip-stream/node_modules/archiver-utils": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", "peer": true, diff --git a/package.json b/package.json index 2443a32..95aedce 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,16 @@ "author": "thzxx", "license": "MIT", "scripts": { - "dev": "vite && electron .", - "build": "tsc && vite build", - "start": "electron . --no-sandbox", - "pack": "electron-builder --dir", - "dist": "electron-builder --win", - "dist:nsis": "electron-builder --win nsis", - "dist:portable": "electron-builder --win portable" + "dev:renderer": "vite build --watch", + "dev:main": "tsc -p tsconfig.main.json --watch", + "build:renderer": "vite build", + "build:main": "tsc -p tsconfig.main.json", + "build": "npm run build:renderer && npm run build:main", + "start": "npm run build && electron . --no-sandbox", + "pack": "npm run build && electron-builder --dir", + "dist": "npm run build && electron-builder --win", + "dist:nsis": "npm run build && electron-builder --win nsis", + "dist:portable": "npm run build && electron-builder --win portable" }, "build": { "appId": "com.metona.ollama-desktop", diff --git a/src/main/main.ts b/src/main/main.ts index 8b5460a..3e11c9a 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -18,6 +18,10 @@ const IS_DEV = !app.isPackaged; export let mainWindow: BrowserWindow | null = null; export let isQuitting = false; +export function setQuitting(): void { + isQuitting = true; +} + export function getIconPath(): string { return process.platform === 'win32' ? ICO_PATH : ICON_PATH; } @@ -45,7 +49,6 @@ function createMainWindow(): BrowserWindow { backgroundColor: '#202020', show: false, autoHideMenuBar: true, - menuBarVisible: false, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, @@ -56,7 +59,13 @@ function createMainWindow(): BrowserWindow { } }); - mainWindow.loadFile(path.join(__dirname, '..', '..', 'index.html')); + // 开发模式:从 src/renderer/ 加载;生产模式:从 dist/renderer/ 加载 + const isDev = !app.isPackaged; + if (isDev) { + mainWindow.loadFile(path.join(__dirname, '..', '..', 'src', 'renderer', 'index.html')); + } else { + mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'index.html')); + } mainWindow.setMenuBarVisibility(false); mainWindow.once('ready-to-show', () => { diff --git a/src/main/menu.ts b/src/main/menu.ts index 6d59670..4c7a7fe 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -2,8 +2,8 @@ * Metona Ollama Desktop - 原生菜单 */ -import { Menu, dialog, shell } from 'electron'; -import { mainWindow, isQuitting, getIconPath } from './main.js'; +import { Menu, dialog, shell, app } from 'electron'; +import { mainWindow, setQuitting, getIconPath } from './main.js'; export function createMenu(): void { const template: Electron.MenuItemConstructorOptions[] = [ @@ -30,8 +30,7 @@ export function createMenu(): void { label: '退出', accelerator: 'Ctrl+Q', click: () => { - const { app } = require('electron'); - (require('./main.js') as Record).isQuitting = true; + setQuitting(); app.quit(); } } @@ -112,8 +111,7 @@ export function createMenu(): void { } ]; - const IS_DEV = !require('electron').app.isPackaged; - if (IS_DEV) { + if (!app.isPackaged) { template.push({ label: 'Dev', submenu: [ diff --git a/src/main/tray.ts b/src/main/tray.ts index a7d911f..7046357 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -2,8 +2,8 @@ * Metona Ollama Desktop - 系统托盘 */ -import { Tray, Menu, nativeImage } from 'electron'; -import { mainWindow, isQuitting, getIconPath } from './main.js'; +import { Tray, Menu, nativeImage, app } from 'electron'; +import { mainWindow, setQuitting, getIconPath } from './main.js'; let tray: Tray | null = null; @@ -32,9 +32,8 @@ export function createTray(): void { { label: '退出', click: () => { - const main = require('./main.js'); - main.isQuitting = true; - require('electron').app.quit(); + setQuitting(); + app.quit(); } } ]); diff --git a/src/renderer/index.html b/src/renderer/index.html index 9e1ef0d..d6af2ab 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -5,7 +5,7 @@ - + Metona Ollama diff --git a/src/renderer/main.ts b/src/renderer/main.ts index 7adcfb8..bec3818 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -3,8 +3,7 @@ * 负责初始化所有组件、协调事件流、管理生命周期 */ -import '../styles/style.css'; -import './types.js'; +import './styles/style.css'; import { ChatDB } from './db/chat-db.js'; import { OllamaAPI } from './api/ollama.js'; diff --git a/sw.js b/sw.js deleted file mode 100644 index 67b4fab..0000000 --- a/sw.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Service Worker - PWA 离线支持 - * 缓存应用资源以便离线使用 - */ - -const CACHE_NAME = 'metona-ollama-v3'; -const ASSETS = [ - './', - './index.html', - './css/style.css', - './js/chat-db.js', - './js/lib/marked.esm.js', - './js/ollama-api.js', - './manifest.json' -]; - -// 安装阶段:预缓存核心资源 -self.addEventListener('install', (event) => { - event.waitUntil( - caches.open(CACHE_NAME) - .then(cache => cache.addAll(ASSETS)) - .then(() => self.skipWaiting()) - ); -}); - -// 激活阶段:清理旧缓存 -self.addEventListener('activate', (event) => { - event.waitUntil( - caches.keys() - .then(keys => Promise.all( - keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key)) - )) - .then(() => self.clients.claim()) - ); -}); - -// 拦截请求:缓存优先,网络回退 -self.addEventListener('fetch', (event) => { - // 不拦截非 GET 请求 - if (event.request.method !== 'GET') return; - - event.respondWith( - caches.match(event.request) - .then(cached => { - if (cached) return cached; - return fetch(event.request).then(response => { - // 仅缓存成功的响应 - if (response && response.status === 200) { - const clone = response.clone(); - caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone)); - } - return response; - }).catch(() => { - // 离线且无缓存时返回简单提示 - if (event.request.destination === 'document') { - return new Response('离线模式:请检查网络连接', { - headers: { 'Content-Type': 'text/plain; charset=utf-8' } - }); - } - }); - }) - ); -}); diff --git a/tsconfig.json b/tsconfig.json index 86464f5..a0b9de1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,18 +13,11 @@ "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "declaration": true, - "declarationMap": true, "sourceMap": true, - "outDir": "dist", - "rootDir": "src", - "baseUrl": ".", - "paths": { - "@main/*": ["src/main/*"], - "@renderer/*": ["src/renderer/*"] - }, + "outDir": "dist/renderer", + "rootDir": "src/renderer", "lib": ["ES2022", "DOM", "DOM.Iterable"] }, - "include": ["src/**/*.ts", "src/**/*.d.ts"], + "include": ["src/renderer/**/*.ts", "src/renderer/**/*.d.ts"], "exclude": ["node_modules", "dist", "release"] } diff --git a/tsconfig.main.json b/tsconfig.main.json new file mode 100644 index 0000000..e4b2200 --- /dev/null +++ b/tsconfig.main.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "dist/main", + "rootDir": "src/main", + "sourceMap": true, + "declaration": false + }, + "include": ["src/main/**/*.ts"], + "exclude": ["node_modules", "dist", "release"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..1a1f803 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + root: 'src/renderer', + base: './', + build: { + outDir: '../../dist/renderer', + emptyOutDir: true, + rollupOptions: { + input: resolve(__dirname, 'src/renderer/index.html'), + }, + }, + css: { + preprocessorOptions: {}, + }, + server: { + port: 5173, + }, +});