+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+ .
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1ed37e2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,141 @@
+**English** | [简体中文](README.zh.md)
+
+# ✍️ Author — AI-Powered Creative Writing Platform
+
+> An AI-powered writing studio for novelists, screenwriters, and storytellers.
+
+**Author** is an AI-assisted creative writing tool designed for fiction writers. It brings together a professional rich text editor, an intelligent AI writing assistant, and a complete worldbuilding management system — all in one seamless experience.
+
+---
+
+## 💬 Why I Built This
+
+I've been using AI for a while now — from the early days of ChatGPT 3.5, to Gemini 2.0 Exp Thinking, and eventually settling on Gemini 2.5 Pro Thinking after the ChatGPT o1 era.
+
+As a novelist, I care deeply about AI's ability to handle language. Novels are long, so I need models with strong context windows and high recall. But what truly moved me about Gemini was its characters — there were moments when the words on screen made me want to cry. That's emotional resonance. I need writing that embraces the full complexity of being human.
+
+Then the coding-focused trend took over. Every company started optimizing for code. I thought it was a good thing — until Gemini 3.1 Pro started describing its characters in biological and psychological terminology. Code-optimized models had begun deconstructing humans into biological components. Claude Opus 4.6 was even worse: every character spoke with peak efficiency — concise, economical, not like a human, but like a machine wearing a human mask.
+
+**I could no longer see the models understanding human complexity. They didn't care about what humans *do* — only what humans *are*. They stopped showing personality through behavior and emotion, and instead slapped simple definitions onto human beings.**
+
+I watched the versatility of these models being gutted. I don't want us to live in a cold world of code. I built this project so that AI can preserve **our own language** — beyond the mechanical operators.
+
+> To all the authors, screenwriters, hobbyists, readers, and players who use this project: I hope you can bring out the best of your craft, create works with a human touch, and keep the flame of our language alive. 🔥
+
+---
+
+## ✨ Features
+
+### 📝 Professional Editor
+- Rich text editor powered by **Tiptap** — bold, italic, headings, lists, code blocks, and more
+- **Word-style pagination** with WYSIWYG layout
+- **KaTeX** math formula support
+- Customizable fonts, font size, line height, and colors
+- Real-time word / character / paragraph count
+
+### 🤖 AI Writing Assistant
+- **Multi-provider support**: ZhipuAI GLM-4 / DeepSeek / OpenAI / Google Gemini
+- **Continue / Rewrite / Polish / Expand** — one-click generation
+- **Ghost Text** streaming preview — see AI output in real-time like Cursor, with accept/reject
+- **Free chat mode** — discuss plot, characters, and settings with AI
+- **Context engine** — AI automatically reads your character profiles, worldbuilding, and previous chapters to maintain story consistency
+
+### 📚 Worldbuilding Manager
+- **Tree-structured** management for characters, locations, items, outlines, and writing rules
+- Three writing modes: **Web Novel** / **Literary Fiction** / **Screenplay**, each with dedicated fields
+- Color-coded categories with glassmorphism design
+- Settings automatically injected into AI context
+
+### 💾 Data Management
+- **Local-first** — all data stored in browser IndexedDB, never uploaded to servers
+- **Snapshot system** — manual/auto versioning with one-click rollback
+- **Project import/export** — full project JSON backup
+- **Markdown export** — single chapter or entire book
+
+### 🌐 Internationalization
+- 🇨🇳 简体中文 / 🇺🇸 English / 🇷🇺 Русский
+
+### 🎨 User Experience
+- Eye-comfort warm tones / dark mode toggle
+- Interactive onboarding tour
+- Help panel with keyboard shortcuts
+
+---
+
+## 🚀 Getting Started
+
+### Requirements
+- **Node.js** 18+
+- **npm** 9+
+
+### Installation
+
+```bash
+# Clone the repository
+git clone https://github.com/YuanShiJiLoong/author.git
+cd author
+
+# Install dependencies
+npm install
+
+# Configure environment variables (optional)
+cp .env.example .env.local
+# Edit .env.local with your API keys
+# You can also configure them in the app's Settings panel
+```
+
+### Development Server
+
+```bash
+npm run dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) to start writing.
+
+### Production Build
+
+```bash
+npm run build
+npm start
+```
+
+### Deploy to Vercel
+
+[](https://vercel.com/new/clone?repository-url=https://github.com/YuanShiJiLoong/author)
+
+---
+
+## ⚙️ AI Configuration
+
+Author supports multiple AI providers. Configure via **environment variables** or **in-app settings**:
+
+| Provider | Env Variable | Get API Key |
+|----------|-------------|-------------|
+| ZhipuAI (GLM-4) | `ZHIPU_API_KEY` | [open.bigmodel.cn](https://open.bigmodel.cn/) |
+| Google Gemini | `GEMINI_API_KEY` | [aistudio.google.com](https://aistudio.google.com/apikey) |
+| DeepSeek | In-app config | [platform.deepseek.com](https://platform.deepseek.com/) |
+| OpenAI / Compatible | In-app config | [platform.openai.com](https://platform.openai.com/) |
+
+> 💡 **No API key required** for most editing features. AI features need at least one provider configured.
+
+---
+
+## 📄 License
+
+This project is licensed under [AGPL-3.0](LICENSE).
+
+**In short**:
+- ✅ Free to use, modify, and distribute
+- ✅ Commercial use allowed
+- ⚠️ Modified versions must also be open-sourced (including network services)
+- ⚠️ Original copyright notice must be preserved
+
+---
+
+## 🙏 Acknowledgments
+
+- [Google Antigravity](https://antigravity.google/) — AI programming partner
+- [Tiptap](https://tiptap.dev/) — Editor framework
+- [Next.js](https://nextjs.org/) — React full-stack framework
+- [Zustand](https://zustand-demo.pmnd.rs/) — State management
+- [KaTeX](https://katex.org/) — Math rendering
diff --git a/README.zh.md b/README.zh.md
new file mode 100644
index 0000000..84e9c3a
--- /dev/null
+++ b/README.zh.md
@@ -0,0 +1,141 @@
+[English](README.md) | **简体中文**
+
+# ✍️ Author — AI-Powered Creative Writing Platform
+
+> AI 驱动的网文创作平台 | AI-Powered Web Novel Writing Studio
+
+**Author** 是一款面向小说作者的 AI 辅助创作工具。它将专业的富文本编辑器、智能 AI 写作助手和完整的世界观管理系统整合在一起,为创作者提供一站式写作体验。
+
+---
+
+## 💬 为什么做这个项目
+
+我使用 AI 已经有一段日子了,各家公司的模型基本上都用过——从最开始的 ChatGPT 3.5,到 Gemini 2.0 Exp Thinking,接着从 ChatGPT o1 时代之后,彻底转入 Gemini 2.5 Pro Thinking。
+
+我本人是写小说的,对 AI 的文字能力比较看重。小说的文本很长,因此我对模型的上下文以及召回率有很高的要求。当然,Gemini 最触动我的点还是祂笔下的人物——总会有那么一瞬间,让我有种想要落泪的冲动。这是情感共鸣。我需要这种接受了人类本身复杂性的文字。
+
+然而,随着代码趋势的兴起,所有公司都开始往这个方向死磕。我本来觉得是好事,但当 Gemini 3.1 Pro 第一次将笔下的人物描述成生物学和心理学的术语时,我发现我错了——代码方向的模型将人类本身解构成一堆生物学零件。特别是 Claude Opus 4.6,这个模型笔下的所有人物都在某种心理学定义的性格里达成了极致的效率:说话言简意赅、惜字如金,不像人类,像个人机。
+
+**我看不到模型对人类本身复杂性的理解。模型不在乎人类做了什么,只在乎人类是什么。模型不从人的行为去体现人的性格和情感,反而直接对人类本身下一个很简单的定义。**
+
+我看到模型的通用性在被阉割。我不希望我们活在冰冷的代码世界。建立这个项目,是为了让 AI 能够在那些机械算符之外,**保留我们人类自己的语言**。
+
+> 希望所有使用该项目的作者、编剧、爱好者,甚至读者、玩家,能够发挥自己的长处,创建出有人味儿的作品,保住我们自己语言的火种。🔥
+
+---
+
+## ✨ 核心功能
+
+### 📝 专业编辑器
+- 基于 **Tiptap** 的富文本编辑器,支持加粗、斜体、标题、列表、代码块等
+- **Word 风格分页**排版,所见即所得
+- **KaTeX** 数学公式支持
+- 字体、字号、行距、颜色自定义
+- 实时字数/字符/段落统计
+
+### 🤖 AI 写作助手
+- **多 AI 供应商**:智谱 GLM-4 / DeepSeek / OpenAI / Google Gemini
+- **续写 / 改写 / 润色 / 扩写**,一键生成
+- **Ghost Text** 流式预览 — 像 Cursor 一样实时显示 AI 生成内容,支持接受/拒绝
+- **自由对话模式** — 与 AI 讨论剧情、角色、设定
+- **上下文引擎** — AI 自动感知你的角色设定、世界观、前文,保持剧情连贯
+
+### 📚 设定集管理
+- **树形结构**管理角色、地点、物品、大纲、写作规则
+- 三种写作模式:**网文** / **传统文学** / **剧本**,每种模式有专属字段
+- 分类配色 + glassmorphism 视觉风格
+- 设定内容自动注入 AI 上下文
+
+### 💾 数据管理
+- **本地优先** — 所有数据存储在浏览器 IndexedDB,不上传服务器
+- **快照系统** — 手动/自动版本存档,支持一键回滚
+- **项目导入导出** — 完整项目 JSON 备份
+- **Markdown 导出** — 单章或全部导出
+
+### 🌐 国际化
+- 🇨🇳 简体中文 / 🇺🇸 English / 🇷🇺 Русский
+
+### 🎨 界面体验
+- 护眼暖色调 / 深色模式切换
+- 新手引导教程
+- 帮助面板 + 快捷键说明
+
+---
+
+## 🚀 快速开始
+
+### 环境要求
+- **Node.js** 18+
+- **npm** 9+
+
+### 安装
+
+```bash
+# 克隆仓库
+git clone https://github.com/YuanShiJiLoong/author.git
+cd author
+
+# 安装依赖
+npm install
+
+# 配置环境变量(可选)
+cp .env.example .env.local
+# 编辑 .env.local 填入你的 API Key
+# 也可以在应用内「设置」面板中配置
+```
+
+### 启动开发服务器
+
+```bash
+npm run dev
+```
+
+打开 [http://localhost:3000](http://localhost:3000) 即可使用。
+
+### 生产构建
+
+```bash
+npm run build
+npm start
+```
+
+### 部署到 Vercel
+
+[](https://vercel.com/new/clone?repository-url=https://github.com/YuanShiJiLoong/author)
+
+---
+
+## ⚙️ AI 配置
+
+Author 支持多种 AI 供应商,你可以通过 **环境变量** 或 **应用内设置** 来配置:
+
+| 供应商 | 环境变量 | 获取 API Key |
+|--------|---------|-------------|
+| 智谱 AI (GLM-4) | `ZHIPU_API_KEY` | [open.bigmodel.cn](https://open.bigmodel.cn/) |
+| Google Gemini | `GEMINI_API_KEY` | [aistudio.google.com](https://aistudio.google.com/apikey) |
+| DeepSeek | 应用内配置 | [platform.deepseek.com](https://platform.deepseek.com/) |
+| OpenAI / 兼容 | 应用内配置 | [platform.openai.com](https://platform.openai.com/) |
+
+> 💡 **无需 API Key 也能使用**大部分编辑功能。AI 功能需要至少配置一个供应商。
+
+---
+
+## 开源协议
+
+本项目采用 [AGPL-3.0](LICENSE) 协议开源。
+
+**简单说**:
+- ✅ 你可以自由使用、修改、分发
+- ✅ 你可以用于个人/商业用途
+- ⚠️ 修改后的版本也必须开源(包括网络服务)
+- ⚠️ 必须保留原始版权声明
+
+---
+
+## 🙏 致谢
+
+- [Google Antigravity](https://antigravity.google/) — AI 编程伙伴
+- [Tiptap](https://tiptap.dev/) — 编辑器框架
+- [Next.js](https://nextjs.org/) — React 全栈框架
+- [Zustand](https://zustand-demo.pmnd.rs/) — 状态管理
+- [KaTeX](https://katex.org/) — 数学公式渲染
diff --git a/app/api/ai/gemini/models/route.js b/app/api/ai/gemini/models/route.js
new file mode 100644
index 0000000..2d7019a
--- /dev/null
+++ b/app/api/ai/gemini/models/route.js
@@ -0,0 +1,62 @@
+import { NextResponse } from 'next/server';
+
+// 拉取 Gemini 可用模型列表
+export async function POST(request) {
+ try {
+ const { apiKey, baseUrl } = await request.json();
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: '请填入 API Key' },
+ { status: 400 }
+ );
+ }
+
+ const base = (baseUrl || 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
+ const url = `${base}/models?key=${apiKey}`;
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: { 'Content-Type': 'application/json' },
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('拉取模型列表失败:', response.status, errorText);
+
+ if (response.status === 401 || response.status === 403) {
+ return NextResponse.json(
+ { error: 'API Key 无效或无权限' },
+ { status: 401 }
+ );
+ }
+ return NextResponse.json(
+ { error: `拉取失败(${response.status})` },
+ { status: response.status }
+ );
+ }
+
+ const data = await response.json();
+
+ // 过滤出支持 generateContent 的模型
+ const models = (data.models || [])
+ .filter(m => m.supportedGenerationMethods?.includes('generateContent'))
+ .map(m => ({
+ id: m.name?.replace('models/', '') || m.name,
+ displayName: m.displayName || m.name,
+ description: m.description || '',
+ inputTokenLimit: m.inputTokenLimit,
+ outputTokenLimit: m.outputTokenLimit,
+ }))
+ .sort((a, b) => a.id.localeCompare(b.id));
+
+ return NextResponse.json({ models });
+
+ } catch (error) {
+ console.error('拉取模型列表错误:', error);
+ return NextResponse.json(
+ { error: '网络连接失败' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/ai/gemini/route.js b/app/api/ai/gemini/route.js
new file mode 100644
index 0000000..0f27371
--- /dev/null
+++ b/app/api/ai/gemini/route.js
@@ -0,0 +1,158 @@
+// Gemini 原生 API — SSE 流式转发(Edge Runtime 确保流式不被缓冲)
+// 使用 streamGenerateContent 端点
+
+export const runtime = 'edge';
+
+export async function POST(request) {
+ try {
+ const { systemPrompt, userPrompt, apiConfig, maxTokens } = await request.json();
+
+ const apiKey = apiConfig?.apiKey || process.env.GEMINI_API_KEY;
+ let rawBaseUrl = apiConfig?.baseUrl;
+ if (!rawBaseUrl || rawBaseUrl.includes('open.bigmodel.cn')) {
+ rawBaseUrl = 'https://generativelanguage.googleapis.com/v1beta';
+ }
+ const baseUrl = rawBaseUrl.replace(/\/$/, '');
+ const model = apiConfig?.model || 'gemini-2.0-flash';
+
+ if (!apiKey) {
+ return new Response(
+ JSON.stringify({ error: '请先配置 API Key。点击左下角 ⚙️ → API配置,填入你的 Gemini Key' }),
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ // 使用 streamGenerateContent 端点 + alt=sse
+ const url = `${baseUrl}/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`;
+
+ const requestBody = {
+ system_instruction: {
+ parts: [{ text: systemPrompt }]
+ },
+ contents: [
+ {
+ role: 'user',
+ parts: [{ text: userPrompt }]
+ }
+ ],
+ generationConfig: {
+ temperature: 0.8,
+ topP: 0.9,
+ maxOutputTokens: maxTokens || 4000,
+ },
+ };
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(requestBody),
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('Gemini API错误:', response.status, errorText);
+
+ const errorHandlers = {
+ 400: () => {
+ try {
+ const errObj = JSON.parse(errorText);
+ return `Gemini 请求错误:${errObj?.error?.message || errorText}`;
+ } catch {
+ return `Gemini 请求错误(400):${errorText}`;
+ }
+ },
+ 401: () => 'API Key 无效或无权限,请检查你的 Gemini API Key',
+ 403: () => 'API Key 无效或无权限,请检查你的 Gemini API Key',
+ 429: () => '请求频率过高或配额不足,请稍后再试',
+ };
+ const errMsg = errorHandlers[response.status]?.()
+ || `Gemini 服务返回错误(${response.status}),请检查 API 配置`;
+
+ return new Response(
+ JSON.stringify({ error: errMsg }),
+ { status: response.status, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ // 将 Gemini SSE 流转换为统一格式转发给前端
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ const stream = new ReadableStream({
+ async start(controller) {
+ const reader = response.body.getReader();
+ let buffer = '';
+
+ try {
+ 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) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith(':')) continue;
+
+ if (trimmed.startsWith('data: ')) {
+ try {
+ const json = JSON.parse(trimmed.slice(6));
+ const candidate = json.candidates?.[0];
+ const parts = candidate?.content?.parts || [];
+
+ for (const part of parts) {
+ if (part.thought === true && part.text) {
+ // Gemini 思维链
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ thinking: part.text })}\n\n`));
+ } else if (part.text) {
+ // 正文内容
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: part.text })}\n\n`));
+ }
+ }
+
+ // 提取 usage(通常在最后一个 chunk)
+ const usageMeta = json.usageMetadata;
+ if (usageMeta?.totalTokenCount) {
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({
+ usage: {
+ promptTokens: usageMeta.promptTokenCount || 0,
+ completionTokens: usageMeta.candidatesTokenCount || 0,
+ totalTokens: usageMeta.totalTokenCount || 0,
+ }
+ })}\n\n`));
+ }
+ } catch {
+ // 解析失败跳过
+ }
+ }
+ }
+ }
+ // 发送结束信号
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
+ } catch (err) {
+ console.error('Gemini Stream 读取错误:', err.message);
+ } finally {
+ controller.close();
+ reader.releaseLock();
+ }
+ }
+ });
+
+ return new Response(stream, {
+ headers: {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ },
+ });
+
+ } catch (error) {
+ console.error('Gemini 接口错误:', error);
+ return new Response(
+ JSON.stringify({ error: '网络连接失败,请检查 API 地址是否正确' }),
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+}
diff --git a/app/api/ai/models/route.js b/app/api/ai/models/route.js
new file mode 100644
index 0000000..9921dbd
--- /dev/null
+++ b/app/api/ai/models/route.js
@@ -0,0 +1,111 @@
+import { NextResponse } from 'next/server';
+
+// 通用模型列表拉取 — 支持 OpenAI 兼容格式和 Gemini 原生格式
+export async function POST(request) {
+ try {
+ const { apiKey, baseUrl, provider } = await request.json();
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: '请先填入 API Key' },
+ { status: 400 }
+ );
+ }
+
+ // Gemini 原生格式
+ if (provider === 'gemini-native') {
+ return await fetchGeminiModels(apiKey, baseUrl);
+ }
+
+ // OpenAI 兼容格式(适用于所有其他供应商)
+ return await fetchOpenAIModels(apiKey, baseUrl);
+
+ } catch (error) {
+ console.error('拉取模型列表错误:', error);
+ return NextResponse.json(
+ { error: '网络连接失败,请检查 API 地址' },
+ { status: 500 }
+ );
+ }
+}
+
+// Gemini 原生格式拉取模型
+async function fetchGeminiModels(apiKey, baseUrl) {
+ const base = (baseUrl || 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
+ const url = `${base}/models?key=${apiKey}`;
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: { 'Content-Type': 'application/json' },
+ });
+
+ if (!response.ok) {
+ return handleFetchError(response);
+ }
+
+ const data = await response.json();
+ const models = (data.models || [])
+ .filter(m => m.supportedGenerationMethods?.includes('generateContent') || m.supportedGenerationMethods?.includes('embedContent'))
+ .map(m => ({
+ id: m.name?.replace('models/', '') || m.name,
+ displayName: m.displayName || m.name,
+ }))
+ .sort((a, b) => a.id.localeCompare(b.id));
+
+ return NextResponse.json({ models });
+}
+
+// OpenAI 兼容格式拉取模型(/v1/models)
+async function fetchOpenAIModels(apiKey, baseUrl) {
+ const base = (baseUrl || '').replace(/\/$/, '');
+ if (!base) {
+ return NextResponse.json(
+ { error: '请先填写 API 地址' },
+ { status: 400 }
+ );
+ }
+
+ const url = `${base}/models`;
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${apiKey}`,
+ },
+ });
+
+ if (!response.ok) {
+ return handleFetchError(response);
+ }
+
+ const data = await response.json();
+ // OpenAI 格式返回 { data: [{id, ...}, ...] }
+ const models = (data.data || [])
+ .map(m => ({
+ id: m.id,
+ displayName: m.id,
+ }))
+ .sort((a, b) => a.id.localeCompare(b.id));
+
+ return NextResponse.json({ models });
+}
+
+async function handleFetchError(response) {
+ const errorText = await response.text();
+ if (response.status === 401 || response.status === 403) {
+ return NextResponse.json(
+ { error: 'API Key 无效或无权限' },
+ { status: 401 }
+ );
+ }
+ let errMsg = `拉取失败(${response.status})`;
+ try {
+ const errObj = JSON.parse(errorText);
+ errMsg = errObj?.error?.message || errMsg;
+ } catch { /* ignore */ }
+ return NextResponse.json(
+ { error: errMsg },
+ { status: response.status }
+ );
+}
diff --git a/app/api/ai/route.js b/app/api/ai/route.js
new file mode 100644
index 0000000..73aed98
--- /dev/null
+++ b/app/api/ai/route.js
@@ -0,0 +1,145 @@
+// OpenAI 兼容 API — SSE 流式转发(Edge Runtime 确保流式不被缓冲)
+// 支持智谱、DeepSeek、OpenAI、Moonshot 等所有兼容 OpenAI 格式的 API
+
+export const runtime = 'edge';
+
+export async function POST(request) {
+ try {
+ const { systemPrompt, userPrompt, apiConfig, maxTokens } = await request.json();
+
+ const apiKey = apiConfig?.apiKey || process.env.ZHIPU_API_KEY;
+ const baseUrl = apiConfig?.baseUrl || 'https://open.bigmodel.cn/api/paas/v4';
+ const model = apiConfig?.model || 'glm-4-flash';
+
+ if (!apiKey) {
+ return new Response(
+ JSON.stringify({ error: '请先配置 API Key。点击左下角 ⚙️ → API配置,填入你的 Key' }),
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
+
+ // 请求上游 API,开启 stream 模式
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify({
+ model,
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: userPrompt }
+ ],
+ temperature: 0.8,
+ max_tokens: maxTokens || 4096,
+ stream: true,
+ }),
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('API错误:', response.status, errorText);
+
+ const errorMessages = {
+ 401: 'API Key 无效或已过期,请检查后重新填写',
+ 429: '请求频率过高或额度不足,请稍后再试',
+ };
+ const errMsg = errorMessages[response.status]
+ || `AI服务返回错误(${response.status}),请检查 API 配置`;
+
+ return new Response(
+ JSON.stringify({ error: errMsg }),
+ { status: response.status, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ // 将上游 SSE 流透传给前端
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ const stream = new ReadableStream({
+ async start(controller) {
+ const reader = response.body.getReader();
+ let buffer = '';
+
+ try {
+ 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) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith(':')) continue;
+
+ if (trimmed === 'data: [DONE]') {
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
+ continue;
+ }
+
+ if (trimmed.startsWith('data: ')) {
+ try {
+ const json = JSON.parse(trimmed.slice(6));
+ const delta = json.choices?.[0]?.delta;
+
+ // 转发思维链内容(DeepSeek reasoning_content)
+ const reasoning = delta?.reasoning_content;
+ if (reasoning) {
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ thinking: reasoning })}\n\n`));
+ }
+
+ // 转发文本 delta
+ const content = delta?.content;
+ if (content) {
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: content })}\n\n`));
+ }
+
+ // 检查是否包含 usage 信息(某些 API 在最后一个事件中返回)
+ if (json.usage) {
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({
+ usage: {
+ promptTokens: json.usage.prompt_tokens || 0,
+ completionTokens: json.usage.completion_tokens || 0,
+ totalTokens: json.usage.total_tokens || 0,
+ }
+ })}\n\n`));
+ }
+ } catch {
+ // 解析失败的行直接跳过
+ }
+ }
+ }
+ }
+ } catch (err) {
+ // 客户端断开等情况
+ console.error('Stream 读取错误:', err.message);
+ } finally {
+ controller.close();
+ reader.releaseLock();
+ }
+ }
+ });
+
+ return new Response(stream, {
+ headers: {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ },
+ });
+
+ } catch (error) {
+ console.error('AI接口错误:', error);
+ return new Response(
+ JSON.stringify({ error: '网络连接失败,请检查 API 地址是否正确' }),
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+}
diff --git a/app/api/ai/test/route.js b/app/api/ai/test/route.js
new file mode 100644
index 0000000..66cdeda
--- /dev/null
+++ b/app/api/ai/test/route.js
@@ -0,0 +1,105 @@
+import { NextResponse } from 'next/server';
+
+// 测试 API 连接(通用 — 支持 OpenAI 兼容格式和 Gemini 原生格式)
+export async function POST(request) {
+ try {
+ const { apiConfig } = await request.json();
+ const { apiKey, baseUrl, model, provider } = apiConfig || {};
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { success: false, error: '请先填入 API Key' },
+ { status: 400 }
+ );
+ }
+
+ // Gemini 原生格式的测试
+ if (provider === 'gemini-native') {
+ return await testGeminiNative(apiKey, baseUrl, model);
+ }
+
+ // OpenAI 兼容格式的测试
+ return await testOpenAICompat(apiKey, baseUrl, model);
+
+ } catch (error) {
+ console.error('API测试错误:', error);
+ return NextResponse.json(
+ { success: false, error: '网络连接失败,请检查 API 地址' },
+ { status: 500 }
+ );
+ }
+}
+
+async function testGeminiNative(apiKey, baseUrl, model) {
+ const base = (baseUrl || 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
+ const m = model || 'gemini-2.0-flash';
+ const url = `${base}/models/${m}:generateContent?key=${apiKey}`;
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ contents: [{ role: 'user', parts: [{ text: '说"连接成功"' }] }],
+ generationConfig: { maxOutputTokens: 20 },
+ }),
+ });
+
+ if (!response.ok) {
+ const errText = await response.text();
+ let errMsg = `连接失败(${response.status})`;
+ try {
+ const errObj = JSON.parse(errText);
+ errMsg = errObj?.error?.message || errMsg;
+ } catch { /* ignore parse error */ }
+ return NextResponse.json({ success: false, error: errMsg });
+ }
+
+ const data = await response.json();
+ const reply = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
+
+ return NextResponse.json({
+ success: true,
+ message: `✅ Gemini 原生 API 连接成功!`,
+ model: m,
+ reply: reply.trim(),
+ });
+}
+
+async function testOpenAICompat(apiKey, baseUrl, model) {
+ const base = (baseUrl || 'https://open.bigmodel.cn/api/paas/v4').replace(/\/$/, '');
+ const m = model || 'gpt-4o-mini';
+ const url = `${base}/chat/completions`;
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify({
+ model: m,
+ messages: [{ role: 'user', content: '说"连接成功"' }],
+ max_tokens: 20,
+ }),
+ });
+
+ if (!response.ok) {
+ const errText = await response.text();
+ let errMsg = `连接失败(${response.status})`;
+ try {
+ const errObj = JSON.parse(errText);
+ errMsg = errObj?.error?.message || errMsg;
+ } catch { /* ignore parse error */ }
+ return NextResponse.json({ success: false, error: errMsg });
+ }
+
+ const data = await response.json();
+ const reply = data.choices?.[0]?.message?.content || '';
+
+ return NextResponse.json({
+ success: true,
+ message: `✅ API 连接成功!`,
+ model: m,
+ reply: reply.trim(),
+ });
+}
diff --git a/app/api/embed/route.js b/app/api/embed/route.js
new file mode 100644
index 0000000..55185d9
--- /dev/null
+++ b/app/api/embed/route.js
@@ -0,0 +1,100 @@
+// Gemini 原生 API & OpenAI 兼容 API — 文本向量化 (Text Embeddings)
+
+export const runtime = 'edge';
+
+export async function POST(request) {
+ try {
+ const { text, apiConfig } = await request.json();
+
+ const isCustomEmbed = apiConfig?.useCustomEmbed;
+ const provider = isCustomEmbed ? apiConfig.embedProvider : (apiConfig?.provider || 'zhipu');
+ const apiKey = isCustomEmbed ? (apiConfig.embedApiKey || apiConfig?.apiKey) : apiConfig?.apiKey;
+
+ // 自动识别默认填写或遗留的智谱URL并矫正为对应官方URL
+ let defaultBaseUrl = provider === 'gemini-native' ? 'https://generativelanguage.googleapis.com/v1beta' : 'https://open.bigmodel.cn/api/paas/v4';
+
+ let rawBaseUrl;
+ if (isCustomEmbed) {
+ rawBaseUrl = apiConfig.embedBaseUrl;
+ } else {
+ // 如果是自定义提供商且没开独立Embed,默认继承对聊的baseUrl
+ rawBaseUrl = apiConfig?.baseUrl || defaultBaseUrl;
+ }
+
+ if (!rawBaseUrl || (provider === 'gemini-native' && rawBaseUrl.includes('open.bigmodel.cn'))) {
+ rawBaseUrl = defaultBaseUrl;
+ }
+
+ const baseUrl = rawBaseUrl.replace(/\/$/, '');
+
+ let embedModelName;
+ if (isCustomEmbed) {
+ embedModelName = apiConfig.embedModel || 'embedding-3';
+ } else if (provider === 'custom') {
+ // 如果没开独立embed,但选了custom,默认用text-embedding-v3-small或用户主模型
+ embedModelName = 'text-embedding-v3-small';
+ } else {
+ embedModelName = provider === 'zhipu' ? 'embedding-3' : 'text-embedding-v3-small';
+ }
+
+ if (!apiKey) {
+ return new Response(JSON.stringify({ error: isCustomEmbed ? '请在API配置中填写独立的 Embedding API Key' : '请先配置 API Key' }), { status: 400 });
+ }
+
+ if (!text || typeof text !== 'string') {
+ return new Response(JSON.stringify({ error: '无效的文本输入' }), { status: 400 });
+ }
+
+ let embeddings = [];
+
+ if (provider === 'gemini-native') {
+ const geminiModel = embedModelName || 'text-embedding-004';
+ const url = `${baseUrl}/models/${geminiModel}:embedContent?key=${apiKey}`;
+ console.log('Fetching Gemini Embeddings:', url);
+ const res = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ model: `models/${geminiModel}`,
+ content: { parts: [{ text }] }
+ })
+ });
+
+ if (!res.ok) {
+ const errText = await res.text();
+ throw new Error(`Gemini Embedding Error: ${errText}`);
+ }
+ const data = await res.json();
+ embeddings = data.embedding.values;
+ } else {
+ // OpenAI 兼容格式
+ const url = `${baseUrl}/embeddings`;
+
+ const res = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${apiKey}`
+ },
+ body: JSON.stringify({
+ input: text,
+ model: embedModelName
+ })
+ });
+
+ if (!res.ok) {
+ const errText = await res.text();
+ throw new Error(`Embedding API Error: ${errText}`);
+ }
+ const data = await res.json();
+ embeddings = data.data[0].embedding;
+ }
+
+ return new Response(JSON.stringify({ embedding: embeddings }), {
+ headers: { 'Content-Type': 'application/json' }
+ });
+ } catch (err) {
+ console.error('Embedding API Error:', err);
+ return new Response(JSON.stringify({ error: err.message }), { status: 500 });
+ }
+}
diff --git a/app/components/AiSidebar.js b/app/components/AiSidebar.js
new file mode 100644
index 0000000..eac151a
--- /dev/null
+++ b/app/components/AiSidebar.js
@@ -0,0 +1,1089 @@
+'use client';
+
+import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
+import { INPUT_TOKEN_BUDGET, buildContext, compileSystemPrompt } from '../lib/context-engine';
+import {
+ saveSessionStore, createSession, deleteSession as deleteSessionFn,
+ renameSession, switchSession, getActiveSession, addMessage, editMessage as editMsgFn,
+ deleteMessage as deleteMsgFn, createBranch, addVariant, switchVariant, replaceMessages
+} from '../lib/chat-sessions';
+import { getProjectSettings, getActiveWorkId, getSettingsNodes, addSettingsNode, updateSettingsNode, deleteSettingsNode } from '../lib/settings';
+import { useAppStore } from '../store/useAppStore';
+import ChatMarkdown from './ChatMarkdown';
+import { useI18n } from '../lib/useI18n';
+
+// 解析消息中的 [SETTINGS_ACTION] 块
+function parseSettingsActions(content) {
+ if (!content) return { parts: [content || ''], actions: [] };
+ const regex = /\[SETTINGS_ACTION\]\s*([\s\S]*?)\s*\[\/SETTINGS_ACTION\]/g;
+ const parts = [];
+ const actions = [];
+ let lastIndex = 0;
+ let match;
+ while ((match = regex.exec(content)) !== null) {
+ if (match.index > lastIndex) parts.push(content.slice(lastIndex, match.index));
+ try {
+ const action = JSON.parse(match[1].trim());
+ actions.push(action);
+ parts.push({ _action: true, index: actions.length - 1 });
+ } catch {
+ parts.push(match[0]); // parse failed, show raw
+ }
+ lastIndex = regex.lastIndex;
+ }
+ if (lastIndex < content.length) parts.push(content.slice(lastIndex));
+ return { parts, actions };
+}
+
+// Removed static label maps in favor of i18n
+
+// ==================== AI 对话侧栏 ====================
+export default function AiSidebar({ onInsertText }) {
+ const {
+ aiSidebarOpen: open, setAiSidebarOpen, setShowSettings,
+ activeChapterId,
+ sessionStore, setSessionStore,
+ chatStreaming, setChatStreaming,
+ generationArchive,
+ contextItems, contextSelection, setContextSelection,
+ showToast
+ } = useAppStore();
+ const { t } = useI18n();
+
+ const onClose = useCallback(() => setAiSidebarOpen(false), [setAiSidebarOpen]);
+ const onOpenSettings = useCallback(() => { setAiSidebarOpen(false); setShowSettings(true); }, [setAiSidebarOpen, setShowSettings]);
+
+ // 派生状态
+ const activeSession = useMemo(() => getActiveSession(sessionStore), [sessionStore]);
+ const chatHistory = useMemo(() => activeSession?.messages || [], [activeSession]);
+
+ // 会话管理回调
+ const setChatHistory = useCallback((newMessages) => setSessionStore(prev => replaceMessages(prev, newMessages)), [setSessionStore]);
+ const onNewSession = useCallback(() => setSessionStore(prev => createSession(prev)), [setSessionStore]);
+ const onDeleteSession = useCallback((id) => setSessionStore(prev => deleteSessionFn(prev, id)), [setSessionStore]);
+ const onRenameSession = useCallback((id, title) => setSessionStore(prev => renameSession(prev, id, title)), [setSessionStore]);
+ const onSwitchSession = useCallback((id) => setSessionStore(prev => switchSession(prev, id)), [setSessionStore]);
+ const onEditMessage = useCallback((msgId, newContent) => setSessionStore(prev => editMsgFn(prev, msgId, newContent)), [setSessionStore]);
+ const onDeleteMessage = useCallback((msgId) => setSessionStore(prev => deleteMsgFn(prev, msgId)), [setSessionStore]);
+ const onBranch = useCallback((msgId) => setSessionStore(prev => createBranch(prev, msgId)), [setSessionStore]);
+ const onSwitchVariant = useCallback((msgId, variantIndex) => setSessionStore(prev => switchVariant(prev, msgId, variantIndex)), [setSessionStore]);
+ const [activeTab, setActiveTab] = useState('chat');
+ const [inputText, setInputText] = useState('');
+ const [archiveSearch, setArchiveSearch] = useState('');
+ const [expandedArchive, setExpandedArchive] = useState(null);
+ // 对话历史勾选状态
+ const [checkedHistory, setCheckedHistory] = useState(new Set());
+ const [slidingWindow, setSlidingWindow] = useState(false);
+ const [slidingWindowSize, setSlidingWindowSize] = useState(8);
+ // 总结编辑
+ const [summaryDraft, setSummaryDraft] = useState(null);
+ // 参考 Tab 状态
+ const [contextSearch, setContextSearch] = useState('');
+ const [collapsedGroups, setCollapsedGroups] = useState(new Set());
+ // 消息编辑状态
+ const [editingMsgId, setEditingMsgId] = useState(null);
+ const [editingContent, setEditingContent] = useState('');
+ // 会话重命名
+ const [renamingSessionId, setRenamingSessionId] = useState(null);
+ const [renameTitle, setRenameTitle] = useState('');
+ // 显示会话列表
+ const [showSessionList, setShowSessionList] = useState(false);
+ // 设定操作卡片展开状态
+ const [expandedActions, setExpandedActions] = useState(new Set());
+
+ const chatEndRef = useRef(null);
+ const chatContainerRef = useRef(null);
+ const inputRef = useRef(null);
+
+ // 新消息时只在用户已滚动到底部时才自动滚动(不劫持用户滚动)
+ useEffect(() => {
+ const container = chatContainerRef.current;
+ if (!container) {
+ chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ return;
+ }
+ const threshold = 80;
+ const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
+ if (isNearBottom) {
+ chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, [chatHistory]);
+
+ // 切到聊天 Tab 时聚焦输入框
+ useEffect(() => {
+ if (activeTab === 'chat' && open) {
+ setTimeout(() => inputRef.current?.focus(), 100);
+ }
+ }, [activeTab, open]);
+
+ // 新消息自动加入 checkedHistory(仅追加,不全量重置)
+ useEffect(() => {
+ if (chatHistory.length === 0) {
+ setCheckedHistory(new Set());
+ return;
+ }
+ setCheckedHistory(prev => {
+ const next = new Set(prev);
+ for (const m of chatHistory) {
+ if (!next.has(m.id)) next.add(m.id);
+ }
+ // 清理已删除的消息 ID
+ const currentIds = new Set(chatHistory.map(m => m.id));
+ for (const id of next) {
+ if (!currentIds.has(id)) next.delete(id);
+ }
+ return next;
+ });
+ }, [chatHistory]);
+
+ // 滑动窗口联动
+ useEffect(() => {
+ if (slidingWindow && chatHistory.length > 0) {
+ const recent = chatHistory.slice(-slidingWindowSize);
+ setCheckedHistory(new Set(recent.map(m => m.id)));
+ }
+ }, [slidingWindow, slidingWindowSize, chatHistory.length]);
+
+ // --- 通用 SSE 流式读取,支持 text+thinking ---
+ const streamResponse = useCallback(async (apiEndpoint, systemPrompt, userPrompt, apiConfig, onUpdate, onDone) => {
+ const res = await fetch(apiEndpoint, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ systemPrompt, userPrompt, apiConfig, maxTokens: 2000 }),
+ });
+
+ const contentType = res.headers.get('content-type') || '';
+ if (contentType.includes('application/json')) {
+ const data = await res.json();
+ throw new Error(data.error || '请求失败');
+ }
+
+ const reader = res.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+ let fullText = '';
+ let fullThinking = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ buffer += decoder.decode(value, { stream: true });
+ const events = buffer.split('\n\n');
+ buffer = events.pop() || '';
+ let hasUpdate = false;
+ for (const event of events) {
+ const trimmed = event.trim();
+ if (!trimmed || trimmed === 'data: [DONE]') continue;
+ if (trimmed.startsWith('data: ')) {
+ try {
+ const json = JSON.parse(trimmed.slice(6));
+ if (json.thinking) { fullThinking += json.thinking; hasUpdate = true; }
+ if (json.text) { fullText += json.text; hasUpdate = true; }
+ } catch { }
+ }
+ }
+ if (hasUpdate) onUpdate(fullText, fullThinking);
+ }
+ onDone(fullText, fullThinking);
+ }, []);
+
+ const onChatMessage = useCallback(async (text, selectedHistory) => {
+ const userMsg = { id: `msg-${Date.now()}-u`, role: 'user', content: text, timestamp: Date.now() };
+ setSessionStore(prev => addMessage(prev, userMsg));
+ setChatStreaming(true);
+ const aiMsgId = `msg-${Date.now()}-a`;
+
+ try {
+ const { apiConfig } = getProjectSettings();
+ const apiEndpoint = apiConfig?.provider === 'gemini-native' ? '/api/ai/gemini' : '/api/ai';
+
+ const context = await buildContext(activeChapterId, '', contextSelection.size > 0 ? contextSelection : null);
+ const systemPrompt = compileSystemPrompt(context, 'chat');
+ const historyForApi = selectedHistory.map(m => `${m.role === 'user' ? t('aiSidebar.roleYou') : t('aiSidebar.roleAi')}: ${m.content}`).join('\n');
+ const userPrompt = historyForApi ? `${historyForApi}\n${t('aiSidebar.roleYou')}: ${text}` : text;
+
+ const aiPlaceholder = { id: aiMsgId, role: 'assistant', content: '', thinking: '', timestamp: Date.now() };
+ setSessionStore(prev => addMessage(prev, aiPlaceholder));
+
+ await streamResponse(apiEndpoint, systemPrompt, userPrompt, apiConfig,
+ (snapText, snapThinking) => {
+ setSessionStore(prev => ({
+ ...prev, sessions: prev.sessions.map(s => {
+ if (s.id !== prev.activeSessionId) return s;
+ return { ...s, messages: s.messages.map(m => m.id === aiMsgId ? { ...m, content: snapText, thinking: snapThinking } : m) };
+ }),
+ }));
+ },
+ (finalText, finalThinking) => {
+ setSessionStore(prev => {
+ const finalStore = {
+ ...prev, sessions: prev.sessions.map(s => {
+ if (s.id !== prev.activeSessionId) return s;
+ return {
+ ...s, messages: s.messages.map(m => m.id === aiMsgId ? { ...m, content: finalText || '(AI 未返回内容)', thinking: finalThinking } : m),
+ updatedAt: Date.now(),
+ };
+ }),
+ };
+ saveSessionStore(finalStore);
+ return finalStore;
+ });
+ }
+ );
+ } catch (err) {
+ const errorMsg = { id: `msg-${Date.now()}-e`, role: 'assistant', content: `❌ ${err.message}`, timestamp: Date.now() };
+ setSessionStore(prev => addMessage(prev, errorMsg));
+ } finally {
+ setChatStreaming(false);
+ }
+ }, [activeChapterId, contextSelection, streamResponse, setSessionStore, setChatStreaming]);
+
+ const onRegenerate = useCallback(async (aiMsgId) => {
+ if (chatStreaming) return;
+
+ const msgs = chatHistory;
+ const aiIdx = msgs.findIndex(m => m.id === aiMsgId);
+ if (aiIdx < 0) return;
+
+ let userMsgIdx = -1;
+ for (let i = aiIdx - 1; i >= 0; i--) {
+ if (msgs[i].role === 'user') { userMsgIdx = i; break; }
+ }
+ if (userMsgIdx < 0) return;
+
+ const userMsg = msgs[userMsgIdx];
+ const priorHistory = msgs.slice(0, userMsgIdx);
+ setChatStreaming(true);
+
+ try {
+ const { apiConfig } = getProjectSettings();
+ const apiEndpoint = apiConfig?.provider === 'gemini-native' ? '/api/ai/gemini' : '/api/ai';
+
+ const context = await buildContext(activeChapterId, '', contextSelection.size > 0 ? contextSelection : null);
+ const systemPrompt = compileSystemPrompt(context, 'chat');
+ const historyForApi = priorHistory
+ .filter(m => m.role === 'user' || m.role === 'assistant')
+ .map(m => `${m.role === 'user' ? t('aiSidebar.roleYou') : t('aiSidebar.roleAi')}: ${m.content}`).join('\n');
+ const userPrompt = historyForApi ? `${historyForApi}\n${t('aiSidebar.roleYou')}: ${userMsg.content}` : userMsg.content;
+
+ setSessionStore(prev => ({
+ ...prev, sessions: prev.sessions.map(s => {
+ if (s.id !== prev.activeSessionId) return s;
+ return {
+ ...s, messages: s.messages.map(m => {
+ if (m.id !== aiMsgId) return m;
+ const variants = m.variants || [{ content: m.content, thinking: m.thinking || '', timestamp: m.timestamp }];
+ return { ...m, variants, content: '', thinking: '' };
+ }),
+ };
+ }),
+ }));
+
+ await streamResponse(apiEndpoint, systemPrompt, userPrompt, apiConfig,
+ (snapText, snapThinking) => {
+ setSessionStore(prev => ({
+ ...prev, sessions: prev.sessions.map(s => {
+ if (s.id !== prev.activeSessionId) return s;
+ return { ...s, messages: s.messages.map(m => m.id === aiMsgId ? { ...m, content: snapText, thinking: snapThinking } : m) };
+ }),
+ }));
+ },
+ (finalText, finalThinking) => {
+ setSessionStore(prev => {
+ const newStore = addVariant(prev, aiMsgId, { content: finalText || '(AI 未返回内容)', thinking: finalThinking, timestamp: Date.now() });
+ saveSessionStore(newStore);
+ return newStore;
+ });
+ }
+ );
+ } catch (err) {
+ setSessionStore(prev => ({
+ ...prev, sessions: prev.sessions.map(s => {
+ if (s.id !== prev.activeSessionId) return s;
+ return { ...s, messages: s.messages.map(m => m.id === aiMsgId ? { ...m, content: `❌ ${err.message}` } : m) };
+ }),
+ }));
+ } finally {
+ setChatStreaming(false);
+ }
+ }, [chatHistory, chatStreaming, activeChapterId, contextSelection, streamResponse, setSessionStore, setChatStreaming]);
+
+ const onApplySettingsAction = useCallback(async (action, actionKey) => {
+ try {
+ const nodes = await getSettingsNodes();
+ const workId = getActiveWorkId() || 'work-default';
+ const catToSuffix = { character: 'characters', world: 'world', location: 'locations', object: 'objects', plot: 'plot', rules: 'rules', custom: 'rules' };
+ const suffix = catToSuffix[action.category] || 'rules';
+ let parentId = `${workId}-${suffix}`;
+ const parentNode = nodes.find(n => n.id === parentId);
+ if (!parentNode) parentId = nodes.find(n => n.parentId === workId && n.category === action.category)?.id || parentId;
+
+ const resolveNode = () => {
+ if (action.nodeId) return nodes.find(n => n.id === action.nodeId);
+ if (action.name) return nodes.find(n => n.name === action.name && n.category === action.category && n.type === 'item');
+ return null;
+ };
+
+ if (action.action === 'add') {
+ const existing = resolveNode();
+ if (existing) {
+ const mergedContent = { ...(existing.content || {}), ...(action.content || {}) };
+ await updateSettingsNode(existing.id, { name: action.name || existing.name, content: mergedContent });
+ } else {
+ await addSettingsNode({ name: action.name || '新条目', type: 'item', category: action.category || 'custom', parentId, content: action.content || {} });
+ }
+ } else if (action.action === 'update') {
+ const target = resolveNode();
+ if (target) {
+ const updates = {};
+ if (action.name) updates.name = action.name;
+ if (action.content) updates.content = { ...(target.content || {}), ...action.content };
+ await updateSettingsNode(target.id, updates);
+ } else {
+ await addSettingsNode({ name: action.name || '新条目', type: 'item', category: action.category || 'custom', parentId, content: action.content || {} });
+ }
+ } else if (action.action === 'delete') {
+ const target = resolveNode();
+ if (target) await deleteSettingsNode(target.id);
+ }
+
+ const msgIdFromKey = actionKey.split('-action-')[0].replace(/-v\d+$/, '');
+ setSessionStore(prev => {
+ const newStore = {
+ ...prev, sessions: prev.sessions.map(s => {
+ if (s.id !== prev.activeSessionId) return s;
+ return { ...s, messages: s.messages.map(m => m.id === msgIdFromKey ? { ...m, _appliedActions: [...(m._appliedActions || []), actionKey] } : m), updatedAt: Date.now() };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+ });
+ showToast('应用设定成功', 'success');
+ } catch (err) {
+ console.error('Settings action failed:', err);
+ showToast('应用操作失败:' + err.message, 'error');
+ }
+ }, [setSessionStore, showToast]);
+
+ // 发送消息
+ const handleSend = useCallback(() => {
+ const text = inputText.trim();
+ if (!text || chatStreaming) return;
+
+ const selectedHistory = chatHistory.filter(m => checkedHistory.has(m.id));
+ onChatMessage?.(text, selectedHistory);
+ setInputText('');
+ }, [inputText, chatStreaming, chatHistory, checkedHistory, onChatMessage]);
+
+ // 重新发送某条用户消息
+ const handleResend = useCallback((msgId) => {
+ const msg = chatHistory.find(m => m.id === msgId);
+ if (!msg || msg.role !== 'user' || chatStreaming) return;
+ const selectedHistory = chatHistory.filter(m => checkedHistory.has(m.id) && m.timestamp < msg.timestamp);
+ onChatMessage?.(msg.content, selectedHistory);
+ }, [chatHistory, checkedHistory, chatStreaming, onChatMessage]);
+
+ // 思维链折叠状态
+ const [expandedThinking, setExpandedThinking] = useState(new Set());
+ const toggleThinking = useCallback((msgId) => {
+ setExpandedThinking(prev => {
+ const next = new Set(prev);
+ if (next.has(msgId)) next.delete(msgId);
+ else next.add(msgId);
+ return next;
+ });
+ }, []);
+
+ // 开始编辑消息
+ const startEdit = useCallback((msg) => {
+ setEditingMsgId(msg.id);
+ setEditingContent(msg.content);
+ }, []);
+
+ // 确认编辑
+ const confirmEdit = useCallback(() => {
+ if (editingMsgId && editingContent.trim()) {
+ onEditMessage?.(editingMsgId, editingContent.trim());
+ }
+ setEditingMsgId(null);
+ setEditingContent('');
+ }, [editingMsgId, editingContent, onEditMessage]);
+
+ // 取消编辑
+ const cancelEdit = useCallback(() => {
+ setEditingMsgId(null);
+ setEditingContent('');
+ }, []);
+
+ // 切换单条历史勾选
+ const toggleCheck = (id) => {
+ setCheckedHistory(prev => {
+ const next = new Set(prev);
+ if (next.has(id)) next.delete(id);
+ else next.add(id);
+ return next;
+ });
+ };
+
+ // 总结历史
+ const handleSummarize = useCallback(() => {
+ const checked = chatHistory.filter(m => checkedHistory.has(m.id));
+ if (checked.length < 2) return;
+ const summaryLines = checked.map(m =>
+ `${m.role === 'user' ? t('aiSidebar.roleYou') : t('aiSidebar.roleAi')}: ${m.content.slice(0, 80)}${m.content.length > 80 ? '...' : ''}`
+ );
+ setSummaryDraft(summaryLines.join('\n'));
+ }, [chatHistory, checkedHistory, t]);
+
+ // 确认总结
+ const confirmSummary = useCallback(() => {
+ if (!summaryDraft) return;
+ const checkedIds = new Set(checkedHistory);
+ const unchecked = chatHistory.filter(m => !checkedIds.has(m.id));
+ const summaryMsg = {
+ id: `summary-${Date.now()}`,
+ role: 'system',
+ content: `[对话摘要]\n${summaryDraft}`,
+ timestamp: Date.now(),
+ isSummary: true,
+ };
+ setChatHistory?.([...unchecked, summaryMsg]);
+ setSummaryDraft(null);
+ }, [summaryDraft, checkedHistory, chatHistory, setChatHistory]);
+
+ // 清空对话
+ const handleClearChat = () => {
+ setChatHistory?.([]);
+ setCheckedHistory(new Set());
+ };
+
+ // 存档过滤
+ const filteredArchive = archiveSearch
+ ? generationArchive.filter(a =>
+ a.text?.includes(archiveSearch) || a.mode?.includes(archiveSearch)
+ )
+ : generationArchive;
+
+ // 参考 Tab 分组
+ const groupedItems = useMemo(() => {
+ const groups = {};
+ const filteredItems = contextSearch
+ ? contextItems.filter(it => it.name.toLowerCase().includes(contextSearch.toLowerCase()))
+ : contextItems;
+ for (const item of filteredItems) {
+ if (item._empty) continue;
+ // 不显示没有创建条目的空分类
+ if (item.tokens === 0 && item.name === '(暂无条目)') continue;
+ const g = item.group || '其他';
+ if (!groups[g]) groups[g] = [];
+ groups[g].push(item);
+ }
+ return groups;
+ }, [contextItems, contextSearch]);
+
+ // Token 统计
+ const totalSelectedTokens = useMemo(() => {
+ return contextItems
+ .filter(it => contextSelection?.has(it.id))
+ .reduce((sum, it) => sum + (it.tokens || 0), 0);
+ }, [contextItems, contextSelection]);
+
+ // 参考条目切换
+ const toggleContextItem = useCallback((itemId) => {
+ setContextSelection(prev => {
+ const next = new Set(prev);
+ if (next.has(itemId)) next.delete(itemId);
+ else next.add(itemId);
+ return next;
+ });
+ }, [setContextSelection]);
+
+ const toggleGroup = useCallback((groupName) => {
+ const items = groupedItems[groupName] || [];
+ setContextSelection(prev => {
+ const next = new Set(prev);
+ const allChecked = items.every(it => prev.has(it.id));
+ items.forEach(it => {
+ if (allChecked) next.delete(it.id);
+ else next.add(it.id);
+ });
+ return next;
+ });
+ }, [groupedItems, contextSelection, setContextSelection]);
+
+ const toggleCollapse = useCallback((groupName) => {
+ setCollapsedGroups(prev => {
+ const next = new Set(prev);
+ if (next.has(groupName)) next.delete(groupName);
+ else next.add(groupName);
+ return next;
+ });
+ }, []);
+
+ const selectAll = useCallback(() => {
+ if (!contextItems) return;
+ setContextSelection(new Set(contextItems.map(it => it.id)));
+ }, [contextItems, setContextSelection]);
+
+ const selectNone = useCallback(() => {
+ setContextSelection(new Set());
+ }, [setContextSelection]);
+
+ const resetSelection = useCallback(() => {
+ if (!contextItems) return;
+ setContextSelection(new Set(contextItems.filter(it => it.enabled).map(it => it.id)));
+ }, [contextItems, setContextSelection]);
+
+ // Token 预算
+ const budgetPercent = Math.min(100, (totalSelectedTokens / INPUT_TOKEN_BUDGET) * 100);
+ const isOverBudget = totalSelectedTokens > INPUT_TOKEN_BUDGET;
+
+ const tabs = [
+ { key: 'chat', label: t('aiSidebar.tabChat') },
+ { key: 'archive', label: t('aiSidebar.tabArchive') },
+ { key: 'reference', label: t('aiSidebar.tabReference') },
+ ];
+
+ const MODE_LABELS = {
+ continue: '续写',
+ rewrite: '改写',
+ expand: '扩写',
+ condense: '精简',
+ dialogue: '对话',
+ chat: '对话',
+ };
+
+ const STATUS_LABELS = {
+ accepted: '✓ 已接受',
+ rejected: '✗ 已拒绝',
+ pending: '⏳ 待确认',
+ };
+
+ // 会话列表
+ const sessions = sessionStore?.sessions || [];
+ const activeSessionId = sessionStore?.activeSessionId;
+
+ if (!open) return null;
+
+ return (
+
+ {/* 标题栏 */}
+
+
{t('aiSidebar.title')}
+
+ setShowSessionList(!showSessionList)}
+ title={t('aiSidebar.btnSessionList')}
+ >📂
+ +
+ ✕
+
+
+
+ {/* 会话列表面板 */}
+ {showSessionList && (
+
+
+ {t('aiSidebar.historyCount').replace('{count}', sessions.length)}
+
+
+ {[...sessions].reverse().map(s => (
+
{ onSwitchSession?.(s.id); setShowSessionList(false); }}
+ >
+ {renamingSessionId === s.id ? (
+
setRenameTitle(e.target.value)}
+ onKeyDown={e => {
+ if (e.key === 'Enter') {
+ onRenameSession?.(s.id, renameTitle.trim() || s.title);
+ setRenamingSessionId(null);
+ } else if (e.key === 'Escape') {
+ setRenamingSessionId(null);
+ }
+ }}
+ onBlur={() => {
+ onRenameSession?.(s.id, renameTitle.trim() || s.title);
+ setRenamingSessionId(null);
+ }}
+ onClick={e => e.stopPropagation()}
+ autoFocus
+ />
+ ) : (
+ <>
+
+ {s.title}
+
+ {s.messages?.length || 0} 条 · {new Date(s.updatedAt || s.createdAt).toLocaleDateString('zh-CN')}
+
+
+
e.stopPropagation()}>
+ { setRenamingSessionId(s.id); setRenameTitle(s.title); }}
+ title={t('aiSidebar.rename')}
+ >✎
+ {sessions.length > 1 && (
+ onDeleteSession?.(s.id)}
+ title={t('aiSidebar.delete')}
+ >🗑
+ )}
+
+ >
+ )}
+
+ ))}
+
+
+ )}
+
+ {/* Tab 切换 */}
+
+ {tabs.map(t => (
+ setActiveTab(t.key)}
+ >
+ {t.label}
+
+ ))}
+
+
+ {/* ==================== 💬 对话 Tab ==================== */}
+ {activeTab === 'chat' && (
+
+ {/* 对话控制栏 */}
+
+
+ {summaryDraft !== null && (
+
+
{t('aiSidebar.editSummary')}
+
+ )}
+
+ {/* 对话消息列表 */}
+
+ {chatHistory.length === 0 && (
+
+
{t('aiSidebar.emptyChatIcon')}
+
{t('aiSidebar.emptyChatTitle')}
+
{t('aiSidebar.emptyChatHint')}
+
+ )}
+ {chatHistory.map(msg => {
+ const isStreaming = chatStreaming && msg.role === 'assistant' && msg === chatHistory[chatHistory.length - 1];
+ const hasVariants = msg.variants && msg.variants.length > 1;
+ const variantIdx = msg.activeVariant ?? 0;
+ const variantTotal = msg.variants?.length || 1;
+
+ return (
+
+
+
toggleCheck(msg.id)}
+ className="chat-check"
+ // TODO: Make this tooltip translate string and optional
+ // title="勾选以包含在下次请求中"
+ />
+
{msg.role === 'user' ? t('aiSidebar.roleYou') : msg.isSummary ? t('aiSidebar.roleSummary') : t('aiSidebar.roleAi')}
+
+ {new Date(msg.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
+
+ {msg.editedAt &&
{t('aiSidebar.edited')} }
+
+ startEdit(msg)}
+ // title="编辑"
+ >{t('aiSidebar.btnEdit')}
+ {msg.role === 'user' && (
+ handleResend(msg.id)}
+ // title="重新发送"
+ disabled={chatStreaming}
+ >{t('aiSidebar.btnResend')}
+ )}
+ {msg.role === 'assistant' && (
+ onRegenerate?.(msg.id)}
+ // title="重新生成"
+ disabled={chatStreaming}
+ >{t('aiSidebar.btnRegenerate')}
+ )}
+ onBranch?.(msg.id)}
+ // title="从此创建分支"
+ >{t('aiSidebar.btnBranch')}
+ onDeleteMessage?.(msg.id)}
+ title={t('aiSidebar.delete')}
+ >{t('aiSidebar.clearChat')}
+
+
+
+ {/* 思维链折叠显示 */}
+ {msg.thinking && (
+
+
toggleThinking(msg.id)}
+ >
+ ▶
+ {t('aiSidebar.thinkingChain')}
+ {!expandedThinking.has(msg.id) && (
+
+ {msg.thinking.slice(0, 40)}{msg.thinking.length > 40 ? '…' : ''}
+
+ )}
+
+ {expandedThinking.has(msg.id) && (
+
+ {msg.thinking}
+
+ )}
+
+ )}
+
+ {/* 消息内容 / 编辑模式 */}
+ {editingMsgId === msg.id ? (
+
+ ) : (
+
+ {(() => {
+ const { parts, actions } = parseSettingsActions(msg.content || '正在思考…');
+ return parts.map((part, pi) => {
+ if (typeof part === 'object' && part._action) {
+ const action = actions[part.index];
+ const actionKey = `${msg.id}-v${msg.activeVariant || 0}-action-${part.index}`;
+ return (
+
+
setExpandedActions(prev => {
+ const next = new Set(prev);
+ next.has(actionKey) ? next.delete(actionKey) : next.add(actionKey);
+ return next;
+ })}
+ style={{ cursor: 'pointer' }}
+ >
+ {t(`aiSidebar.actions.${action.action}`) || action.action}
+ {t(`aiSidebar.categories.${action.category}`) || action.category || ''}
+ {action.name || action.nodeId || ''}
+ {expandedActions.has(actionKey) ? '▲...' : '▼...'}
+
+ {action.content && expandedActions.has(actionKey) && (
+
+ {Object.entries(action.content).map(([k, v]) => (
+
+ {k}:
+ {String(v)}
+
+ ))}
+
+ )}
+
onApplySettingsAction?.(action, actionKey)}
+ disabled={msg._appliedActions?.includes(actionKey)}
+ >
+ {msg._appliedActions?.includes(actionKey) ? t('aiSidebar.actionsApplied') : t('aiSidebar.actionsApply')}
+
+
+ );
+ }
+ return
;
+ });
+ })()}
+
+ )}
+
+ {/* 变体导航 < 1/3 > */}
+ {hasVariants && !isStreaming && (
+
+ onSwitchVariant?.(msg.id, variantIdx - 1)}
+ disabled={variantIdx <= 0}
+ >◀
+ {variantIdx + 1} / {variantTotal}
+ onSwitchVariant?.(msg.id, variantIdx + 1)}
+ disabled={variantIdx >= variantTotal - 1}
+ >▶
+
+ )}
+
+ );
+ })}
+
+
+
+ {/* 输入框 */}
+
+
+
+ )}
+
+ {/* ==================== 📋 存档 Tab ==================== */}
+ {activeTab === 'archive' && (
+
+
+ setArchiveSearch(e.target.value)}
+ />
+
+
+ {filteredArchive.length === 0 && (
+
+
{t('aiSidebar.emptyArchiveIcon')}
+
{t('aiSidebar.emptyArchiveTitle')}
+
{t('aiSidebar.emptyArchiveHint')}
+
+ )}
+ {[...filteredArchive].reverse().map(item => (
+
setExpandedArchive(expandedArchive === item.id ? null : item.id)}
+ >
+
+
+ {t(`aiSidebar.statuses.${item.status}`) || item.status}
+
+ {t(`aiSidebar.modes.${item.mode}`) || item.mode}
+
+ {new Date(item.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
+
+
+
+ {item.text?.slice(0, 60)}…
+
+ {expandedArchive === item.id && (
+
+
{item.text}
+
+ { e.stopPropagation(); onInsertText?.(item.text); }}>
+ {t('aiSidebar.insertEditor')}
+
+ { e.stopPropagation(); navigator.clipboard.writeText(item.text); }}>
+ {t('aiSidebar.copy')}
+
+
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {/* ==================== 📚 参考 Tab ==================== */}
+ {activeTab === 'reference' && (
+
+ {/* Token 预算进度条 */}
+
+
+ {t('aiSidebar.tokenUsage')}
+
+ {totalSelectedTokens.toLocaleString()} / {(INPUT_TOKEN_BUDGET / 1000).toFixed(0)}k
+
+
+
+
+
+ {/* 搜索框 */}
+
+ setContextSearch(e.target.value)}
+ />
+
+
+ {/* 分组列表 */}
+
+ {Object.entries(groupedItems).length === 0 && (
+
+
{t('aiSidebar.emptyContextIcon')}
+
{t('aiSidebar.emptyContextTitle')}
+
+ {contextSearch ? t('aiSidebar.emptyContextHint1') : t('aiSidebar.emptyContextHint2')}
+
+
+ )}
+ {Object.entries(groupedItems).map(([groupName, items]) => {
+ const isCollapsed = collapsedGroups.has(groupName);
+ const checkedCount = items.filter(it => contextSelection?.has(it.id)).length;
+ const groupTokens = items
+ .filter(it => contextSelection?.has(it.id))
+ .reduce((sum, it) => sum + it.tokens, 0);
+ const allGroupChecked = checkedCount === items.length;
+
+ return (
+
+ );
+ })}
+
+
+ {/* 批量操作 */}
+
+ {t('aiSidebar.selectAll')}
+ {t('aiSidebar.selectNone')}
+ {t('aiSidebar.reset')}
+ {t('aiSidebar.settings')}
+
+
+ )}
+
+ );
+}
diff --git a/app/components/ChatMarkdown.js b/app/components/ChatMarkdown.js
new file mode 100644
index 0000000..34bdd94
--- /dev/null
+++ b/app/components/ChatMarkdown.js
@@ -0,0 +1,94 @@
+'use client';
+
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+import { memo, useState, useCallback } from 'react';
+
+/**
+ * 聊天消息 Markdown 渲染组件
+ * 支持:GFM (表格/删除线/任务列表) + 数学公式 (KaTeX) + 代码块
+ */
+function ChatMarkdownInner({ content }) {
+ if (!content) return null;
+
+ return (
+ {children};
+ }
+ const lang = className?.replace('language-', '') || '';
+ return ;
+ },
+ // 链接:新窗口打开
+ a({ href, children, ...props }) {
+ return (
+
+ {children}
+
+ );
+ },
+ // 表格样式
+ table({ children, ...props }) {
+ return (
+
+ );
+ },
+ // 段落
+ p({ children, ...props }) {
+ return {children}
;
+ },
+ // 列表
+ ul({ children, ...props }) {
+ return ;
+ },
+ ol({ children, ...props }) {
+ return {children} ;
+ },
+ // 引用
+ blockquote({ children, ...props }) {
+ return {children} ;
+ },
+ // 标题
+ h1({ children, ...props }) { return {children} ; },
+ h2({ children, ...props }) { return {children} ; },
+ h3({ children, ...props }) { return {children} ; },
+ }}
+ >
+ {content}
+
+ );
+}
+
+// 代码块子组件(带复制按钮)
+function CodeBlock({ lang, code }) {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = useCallback(() => {
+ navigator.clipboard.writeText(code).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ });
+ }, [code]);
+
+ return (
+
+
+ {lang || 'text'}
+
+ {copied ? '✓ 已复制' : '📋 复制'}
+
+
+
{code}
+
+ );
+}
+
+const ChatMarkdown = memo(ChatMarkdownInner);
+export default ChatMarkdown;
diff --git a/app/components/Editor.js b/app/components/Editor.js
new file mode 100644
index 0000000..617bcde
--- /dev/null
+++ b/app/components/Editor.js
@@ -0,0 +1,1168 @@
+'use client';
+
+import { useEditor, EditorContent } from '@tiptap/react';
+import StarterKit from '@tiptap/starter-kit';
+import Placeholder from '@tiptap/extension-placeholder';
+import CharacterCount from '@tiptap/extension-character-count';
+import Highlight from '@tiptap/extension-highlight';
+import Underline from '@tiptap/extension-underline';
+import { TextStyle, Color, FontFamily } from '@tiptap/extension-text-style';
+import TextAlign from '@tiptap/extension-text-align';
+import Subscript from '@tiptap/extension-subscript';
+import Superscript from '@tiptap/extension-superscript';
+import TaskList from '@tiptap/extension-task-list';
+import TaskItem from '@tiptap/extension-task-item';
+import { Markdown } from 'tiptap-markdown';
+import { MathInline, MathBlock, openMathEditor } from './MathExtension';
+import { PageBreakExtension } from './PageBreakExtension';
+import GhostMark from './GhostMark';
+import { useEffect, useCallback, useRef, useState, useMemo, useId, forwardRef, useImperativeHandle } from 'react';
+
+// ==================== AI 模式配置 ====================
+const AI_MODES = [
+ { key: 'continue', label: '✦ 续写', desc: '从光标处自然续写', needsSelection: false },
+ { key: 'rewrite', label: '✎ 润色', desc: '提升选中文字质量', needsSelection: true },
+ { key: 'expand', label: '⊕ 扩写', desc: '丰富细节与描写', needsSelection: true },
+ { key: 'condense', label: '⊖ 精简', desc: '浓缩核心内容', needsSelection: true },
+];
+
+// ==================== 虚拟分页常量 ====================
+const PAGE_HEIGHT = 1056; // A4 纸 @ 96dpi
+const PAGE_GAP = 24; // 页间灰色间隙
+
+
+const Editor = forwardRef(function Editor({ content, onUpdate, editable = true, onAiRequest, onArchiveGeneration, contextItems, contextSelection, setContextSelection }, ref) {
+ const clipPathId = useId();
+ const debounceRef = useRef(null);
+ const contentRef = useRef(null);
+
+ // 页数状态
+ const [pageCount, setPageCount] = useState(1);
+
+ // 页边距状态(从 localStorage 读取)
+ const [margins, setMargins] = useState(() => {
+ if (typeof window !== 'undefined') {
+ try {
+ const saved = JSON.parse(localStorage.getItem('author-margins'));
+ if (saved) return { x: saved.x ?? 96, y: saved.y ?? 96 };
+ } catch { }
+ }
+ return { x: 96, y: 96 };
+ });
+
+ // 边距变更自动保存
+ useEffect(() => {
+ localStorage.setItem('author-margins', JSON.stringify(margins));
+ }, [margins]);
+
+ const editor = useEditor({
+ immediatelyRender: false,
+ extensions: [
+ StarterKit.configure({
+ heading: { levels: [1, 2, 3] },
+ }),
+ Placeholder.configure({
+ placeholder: '开始写作…让灵感自由流淌',
+ }),
+ CharacterCount,
+ Highlight.configure({ multicolor: true }),
+ Underline,
+ TextStyle,
+ Color,
+ FontFamily.configure({
+ types: ['textStyle'],
+ }),
+ TextAlign.configure({
+ types: ['heading', 'paragraph'],
+ alignments: ['left', 'center', 'right', 'justify'],
+ defaultAlignment: 'left',
+ }),
+ Subscript,
+ Superscript,
+ TaskList,
+ TaskItem.configure({
+ nested: true,
+ }),
+ Markdown.configure({
+ html: true,
+ tightLists: true,
+ bulletListMarker: '-',
+ transformPastedText: true,
+ transformCopiedText: false,
+ }),
+ MathInline,
+ MathBlock,
+ PageBreakExtension,
+ GhostMark,
+ ],
+ content: content || '',
+ editable,
+ editorProps: {
+ attributes: {
+ class: 'tiptap',
+ },
+ },
+ onUpdate: ({ editor }) => {
+ if (debounceRef.current) clearTimeout(debounceRef.current);
+ debounceRef.current = setTimeout(() => {
+ const html = editor.getHTML();
+ const text = editor.getText();
+ onUpdate?.({
+ html,
+ text,
+ wordCount: text.replace(/\s/g, '').length,
+ });
+ }, 500);
+ },
+ });
+
+ // 防止父组件传来的 content 稍有差异即导致整个编辑器重置并跳动
+ // 仅当新内容与当前内容脱节时才重置(例如切换章节)
+ const previousChapterId = useRef(content);
+ useEffect(() => {
+ if (!editor || content === undefined) return;
+ const currentHtml = editor.getHTML();
+
+ // 简单启发式:如果长度差距极大(用户不可能一秒内打这么多字),或者内容完全不包含现有内容,才做全量替换
+ if (content !== currentHtml) {
+ // 我们需要区分是“用户打字后传回的最新内容”(不用动)还是“因为点击左侧栏切换了章节”(需要重置)
+ // 如果新传入的 content 和当前存在非常显著差异,才执行 setContent
+ if (Math.abs(content.length - currentHtml.length) > 50 || !currentHtml.includes(content.substring(0, 50))) {
+ editor.commands.setContent(content || '', false);
+ }
+ }
+ }, [content, editor]);
+
+ // 将方法暴露给父组件
+ useEffect(() => {
+ if (editor) {
+ editor.getSelectedText = () => {
+ const { from, to } = editor.state.selection;
+ if (from === to) return editor.getText();
+ return editor.state.doc.textBetween(from, to, ' ');
+ };
+ editor.insertText = (text) => {
+ editor.chain().focus().insertContent(text).run();
+ };
+ editor.replaceSelection = (text) => {
+ const { from, to } = editor.state.selection;
+ if (from === to) {
+ editor.chain().focus().insertContent(text).run();
+ } else {
+ editor.chain().focus().deleteSelection().insertContent(text).run();
+ }
+ };
+ }
+ }, [editor]);
+
+ // 通过 ref 暴露插入方法给父组件(侧栏存档插入用)
+ useImperativeHandle(ref, () => ({
+ insertText: (text) => {
+ editor?.chain().focus().insertContent(text).run();
+ },
+ }), [editor]);
+
+ // ===== 核心:ResizeObserver 监听内容高度,计算页数 =====
+ const observerRef = useRef(null);
+ const contentCallbackRef = useCallback((node) => {
+ // 清理旧 observer
+ if (observerRef.current) {
+ observerRef.current.disconnect();
+ observerRef.current = null;
+ }
+ if (!node) return;
+ contentRef.current = node;
+ const observer = new ResizeObserver(() => {
+ if (!contentRef.current) return;
+ // scrollHeight 更准确地反映内容实际高度
+ const height = contentRef.current.scrollHeight;
+ // 把 PAGE_GAP 补进来算精确数学除法
+ const needed = Math.max(1, Math.ceil((height + PAGE_GAP) / (PAGE_HEIGHT + PAGE_GAP)));
+ setPageCount(prev => prev !== needed ? needed : prev);
+ });
+ observer.observe(node);
+ observerRef.current = observer;
+ }, []);
+
+ if (!editor) return null;
+
+ // 容器总高度 = 页数 × 单页高 + 间隙总高
+ const totalWorkspaceHeight = pageCount * PAGE_HEIGHT + (pageCount - 1) * PAGE_GAP;
+
+ return (
+ <>
+
+ {
+ // 点击灰色空隙处自动聚焦到文末
+ if (e.target.closest('.editor-container') && !e.target.closest('.tiptap')) {
+ editor?.chain().focus('end').run();
+ }
+ }}
+ >
+
+
+ {/* SVG clip definition — 每页一个矩形,文字只在页面内可见 */}
+
+
+
+ {Array.from({ length: pageCount }).map((_, i) => {
+ const pageTop = i * (PAGE_HEIGHT + PAGE_GAP);
+ return ;
+ })}
+
+
+
+
+ {/* ===== 底层:白色纸张卡片阵列 ===== */}
+
+ {Array.from({ length: pageCount }).map((_, i) => (
+
+ ))}
+
+
+ {/* ===== 页间标签(在灰色间隙中显示页码)===== */}
+ {pageCount > 1 && Array.from({ length: pageCount - 1 }).map((_, i) => {
+ const gapTop = (i + 1) * PAGE_HEIGHT + i * PAGE_GAP;
+ return (
+
+
+ 第 {i + 1} 页 / 共 {pageCount} 页
+
+
+ );
+ })}
+
+ {/* ===== 文字层(clipPath 严格裁切到页面区域)===== */}
+
+
+
+
+
+ >
+ );
+});
+
+export default Editor;
+
+// ==================== Inline AI 组件 ====================
+function InlineAI({ editor, onAiRequest, onArchiveGeneration, contextItems, contextSelection, setContextSelection }) {
+ const [visible, setVisible] = useState(false);
+ const [mode, setMode] = useState('continue');
+ const [instruction, setInstruction] = useState('');
+ const [streaming, setStreaming] = useState(false);
+ const [pendingGhost, setPendingGhost] = useState(false);
+ const [position, setPosition] = useState({ top: 0, left: 0 });
+ const abortRef = useRef(null);
+ const inputRef = useRef(null);
+ const popoverRef = useRef(null);
+ const typeQueueRef = useRef([]);
+ const typingRef = useRef(false);
+ // Ghost text tracking
+ const ghostStartRef = useRef(null);
+ const ghostTextRef = useRef('');
+ // Rewrite backup
+ const originalTextRef = useRef(null);
+ const originalRangeRef = useRef(null);
+ const currentModeRef = useRef('continue');
+ // 文档快照:生成前保存,拒绝时恢复
+ const savedDocRef = useRef(null);
+
+ // 获取选中文本
+ const getSelectedText = useCallback(() => {
+ if (!editor) return '';
+ const { from, to } = editor.state.selection;
+ if (from === to) return '';
+ return editor.state.doc.textBetween(from, to, ' ');
+ }, [editor]);
+
+ // 获取上文(用于续写)
+ const getContextText = useCallback(() => {
+ if (!editor) return '';
+ const text = editor.getText();
+ return text.length > 1500 ? text.slice(-1500) : text;
+ }, [editor]);
+
+ // 计算浮窗位置(基于光标,使用视口坐标 position:fixed)
+ const updatePosition = useCallback(() => {
+ if (!editor) return;
+ const { view } = editor;
+ const head = editor.state.selection.head;
+ const coords = view.coordsAtPos(head, -1);
+
+ const GAP = 16;
+ const popoverW = 360;
+ const popoverH = 130;
+ const vw = window.innerWidth;
+ const vh = window.innerHeight;
+
+ let top = coords.bottom + 8;
+ let left = coords.left;
+ left = Math.max(GAP, Math.min(left, vw - popoverW - GAP));
+ if (top + popoverH > vh - GAP) {
+ top = coords.top - popoverH - 8;
+ }
+ if (top < GAP) top = GAP;
+
+ setPosition({ top, left });
+ }, [editor]);
+
+ // 打开浮窗
+ const open = useCallback(() => {
+ if (pendingGhost) return; // 有待确认的 ghost 时不打开新的
+ const selected = getSelectedText();
+ setMode(selected ? 'rewrite' : 'continue');
+ setInstruction('');
+ updatePosition();
+ setVisible(true);
+ }, [getSelectedText, updatePosition, pendingGhost]);
+
+ // 关闭浮窗
+ const close = useCallback(() => {
+ if (streaming || pendingGhost) return;
+ setVisible(false);
+ setInstruction('');
+ editor?.chain().focus().run();
+ }, [streaming, pendingGhost, editor]);
+
+ // 停止生成
+ const stop = useCallback(() => {
+ if (abortRef.current) {
+ abortRef.current.abort();
+ abortRef.current = null;
+ }
+ typeQueueRef.current = [];
+ typingRef.current = false;
+ setStreaming(false);
+ // 如果已经有 ghost 文本,进入待确认状态
+ if (ghostTextRef.current) {
+ setPendingGhost(true);
+ }
+ }, []);
+
+ // 打字机效果:逐字符插入编辑器,带 ghost mark
+ // 使用原生 ProseMirror transaction,彻底避免 scrollIntoView
+ const suppressScrollRef = useRef(false);
+
+ const startTyping = useCallback(() => {
+ if (typingRef.current) return;
+ typingRef.current = true;
+
+ const typeNext = () => {
+ if (typeQueueRef.current.length === 0) {
+ typingRef.current = false;
+ return;
+ }
+ const char = typeQueueRef.current.shift();
+ if (char === '\n') {
+ if (typeQueueRef.current[0] !== '\n') {
+ // 换行:使用原生 split,不调用 scrollIntoView
+ const { state } = editor.view;
+ const tr = state.tr.split(state.selection.from);
+ editor.view.dispatch(tr);
+ }
+ } else {
+ // 用原生 ProseMirror transaction 插入字符 + 标记 ghost
+ const { state } = editor.view;
+ const tr = state.tr.insertText(char);
+ const ghostMark = state.schema.marks.ghostText.create();
+ const to = tr.selection.from;
+ const from = to - char.length;
+ tr.addMark(from, to, ghostMark);
+ // 故意不调用 tr.scrollIntoView() — 防止滚动跳回
+ editor.view.dispatch(tr);
+ ghostTextRef.current += char;
+ }
+ requestAnimationFrame(() => setTimeout(typeNext, 20));
+ };
+ typeNext();
+ }, [editor]);
+
+ // 将文本块加入打字队列
+ const enqueueText = useCallback((text) => {
+ for (const char of text) {
+ typeQueueRef.current.push(char);
+ }
+ startTyping();
+ }, [startTyping]);
+
+ // ========== Ghost 操作 ==========
+
+ // 接受:去掉 ghost mark,文本变成正式内容
+ const acceptGhost = useCallback(() => {
+ editor?.commands.acceptAllGhost();
+ // 归档
+ onArchiveGeneration?.({
+ mode: currentModeRef.current,
+ instruction: instruction.trim(),
+ text: ghostTextRef.current,
+ status: 'accepted',
+ });
+ ghostTextRef.current = '';
+ ghostStartRef.current = null;
+ originalTextRef.current = null;
+ originalRangeRef.current = null;
+ setPendingGhost(false);
+ setVisible(false);
+ editor?.chain().focus().run();
+ }, [editor, instruction, onArchiveGeneration]);
+
+ // 拒绝:删除 ghost 文本(含换行符),改写模式还原原文
+ const rejectGhost = useCallback(() => {
+ // 归档(标记为拒绝)
+ onArchiveGeneration?.({
+ mode: currentModeRef.current,
+ instruction: instruction.trim(),
+ text: ghostTextRef.current,
+ status: 'rejected',
+ });
+ // 直接恢复生成前的文档快照(最可靠,彻底消除残留空行)
+ if (savedDocRef.current && editor) {
+ editor.commands.setContent(savedDocRef.current, false);
+ } else {
+ // 回退:若无快照,使用 mark 删除
+ editor?.commands.removeAllGhost(ghostStartRef.current);
+ if (originalTextRef.current && originalRangeRef.current) {
+ const { from } = originalRangeRef.current;
+ editor?.chain()
+ .focus()
+ .insertContentAt(from, originalTextRef.current)
+ .run();
+ }
+ }
+ ghostTextRef.current = '';
+ ghostStartRef.current = null;
+ originalTextRef.current = null;
+ originalRangeRef.current = null;
+ savedDocRef.current = null;
+ setPendingGhost(false);
+ setVisible(false);
+ editor?.chain().focus().run();
+ }, [editor, instruction, onArchiveGeneration]);
+
+ // 重新生成:拒绝当前 ghost + 重新 generate
+ const regenerate = useCallback(() => {
+ // 先归档拒绝
+ onArchiveGeneration?.({
+ mode: currentModeRef.current,
+ instruction: instruction.trim(),
+ text: ghostTextRef.current,
+ status: 'rejected',
+ });
+ // 恢复文档快照
+ if (savedDocRef.current && editor) {
+ editor.commands.setContent(savedDocRef.current, false);
+ } else {
+ editor?.commands.removeAllGhost(ghostStartRef.current);
+ }
+ ghostTextRef.current = '';
+ setPendingGhost(false);
+ // 触发新一轮生成(savedDocRef 保留不清空,供下次拒绝使用)
+ setTimeout(() => generate(), 50);
+ }, [editor, instruction, onArchiveGeneration]);
+
+ // 执行 AI 生成
+ const generate = useCallback(async () => {
+ if (!onAiRequest || streaming) return;
+
+ const selectedText = getSelectedText();
+ const contextText = getContextText();
+ let actualMode = mode;
+
+ if (AI_MODES.find(m => m.key === mode)?.needsSelection && !selectedText) {
+ actualMode = 'continue';
+ setMode('continue');
+ }
+ currentModeRef.current = actualMode;
+
+ const text = selectedText || contextText;
+ if (!text.trim() && actualMode !== 'continue') return;
+
+ setStreaming(true);
+ setPendingGhost(false);
+ const controller = new AbortController();
+ abortRef.current = controller;
+ typeQueueRef.current = [];
+ ghostTextRef.current = '';
+
+ // 保存生成前的文档快照(在任何修改之前)
+ savedDocRef.current = editor.getJSON();
+
+ // 改写模式:备份原文
+ if (selectedText && actualMode !== 'continue') {
+ const { from, to } = editor.state.selection;
+ originalTextRef.current = selectedText;
+ originalRangeRef.current = { from, to };
+ editor?.chain().focus().deleteSelection().run();
+ } else {
+ originalTextRef.current = null;
+ originalRangeRef.current = null;
+ editor?.chain().focus().run();
+ }
+
+ ghostStartRef.current = editor.state.selection.head;
+
+ try {
+ await onAiRequest({
+ mode: actualMode,
+ text,
+ instruction: instruction.trim(),
+ signal: controller.signal,
+ onChunk: (chunk) => {
+ enqueueText(chunk);
+ },
+ });
+ } catch (err) {
+ if (err.name !== 'AbortError') {
+ console.error('AI 生成错误:', err);
+ }
+ } finally {
+ await new Promise(resolve => {
+ const check = () => {
+ if (typeQueueRef.current.length === 0 && !typingRef.current) resolve();
+ else setTimeout(check, 50);
+ };
+ check();
+ });
+ setStreaming(false);
+ abortRef.current = null;
+ // 进入待确认状态
+ if (ghostTextRef.current) {
+ setPendingGhost(true);
+ // 将光标(ghost 文本末端)滚入可视区域,确保操作栏可见
+ try {
+ const scrollContainer = editor.view.dom.closest('.editor-container');
+ if (scrollContainer) {
+ const head = editor.state.selection.head;
+ const coords = editor.view.coordsAtPos(head, -1);
+ const containerRect = scrollContainer.getBoundingClientRect();
+ const relativeBottom = coords.bottom - containerRect.top + scrollContainer.scrollTop;
+ const targetScroll = relativeBottom - containerRect.height + 80;
+ if (targetScroll > scrollContainer.scrollTop) {
+ scrollContainer.scrollTop = targetScroll;
+ }
+ }
+ } catch { /* 回退:不滚动也不阻塞 */ }
+ } else {
+ setVisible(false);
+ }
+ }
+ }, [onAiRequest, streaming, mode, instruction, getSelectedText, getContextText, editor, enqueueText, updatePosition]);
+
+ // 键盘快捷键:Ctrl+J 打开,Esc 关闭/拒绝,Tab 接受
+ useEffect(() => {
+ const handler = (e) => {
+ if ((e.ctrlKey || e.metaKey) && e.key === 'j') {
+ e.preventDefault();
+ if (pendingGhost) return;
+ if (visible) close();
+ else open();
+ }
+ if (e.key === 'Escape' && (visible || pendingGhost)) {
+ e.preventDefault();
+ if (streaming) stop();
+ else if (pendingGhost) rejectGhost();
+ else close();
+ }
+ // Tab 接受 ghost text
+ if (e.key === 'Tab' && pendingGhost) {
+ e.preventDefault();
+ acceptGhost();
+ }
+ };
+ document.addEventListener('keydown', handler);
+ return () => document.removeEventListener('keydown', handler);
+ }, [visible, streaming, pendingGhost, open, close, stop, rejectGhost, acceptGhost]);
+
+ // 点击外部关闭(但待确认状态不自动关闭)
+ useEffect(() => {
+ if (!visible) return;
+ const handler = (e) => {
+ if (popoverRef.current && !popoverRef.current.contains(e.target)) {
+ if (!streaming && !pendingGhost) close();
+ }
+ };
+ document.addEventListener('mousedown', handler);
+ return () => document.removeEventListener('mousedown', handler);
+ }, [visible, streaming, pendingGhost, close]);
+
+ // 待确认状态时不显示浮窗,改为在幽灵文本末尾显示操作栏
+ if (!visible && !pendingGhost) {
+ return null;
+ }
+
+ // 待确认状态:在幽灵文本末尾内联显示操作栏(Cursor 风格)
+ if (pendingGhost) {
+ // 获取光标位置(幽灵文本末尾)
+ let ghostPos = { top: 0, left: 0 };
+ try {
+ const head = editor.state.selection.head;
+ const coords = editor.view.coordsAtPos(head, -1);
+ ghostPos = { top: coords.bottom + 4, left: coords.left };
+ // 确保不超出视口
+ const vw = window.innerWidth;
+ if (ghostPos.left + 280 > vw) ghostPos.left = vw - 296;
+ if (ghostPos.left < 16) ghostPos.left = 16;
+ } catch { /* 位置获取失败时用默认值 */ }
+
+ return (
+
+
+ ✓ 接受
+
+
+ ✗ 拒绝
+
+
+ ⟳
+
+ Tab 接受 · Esc 拒绝
+
+ );
+ }
+ const selectedText = getSelectedText();
+ const availableModes = selectedText
+ ? AI_MODES
+ : AI_MODES.filter(m => !m.needsSelection);
+
+ return (
+
+ {/* 模式选择 */}
+
+ {availableModes.map(m => (
+ setMode(m.key)}
+ disabled={streaming}
+ title={m.desc}
+ >
+ {m.label}
+
+ ))}
+
+
+ {/* 参考设定集(可折叠) */}
+
+
+ {/* 指令输入 */}
+
+ setInstruction(e.target.value)}
+ onKeyDown={e => {
+ if (e.key === 'Enter' && !streaming) {
+ e.preventDefault();
+ generate();
+ }
+ }}
+ disabled={streaming}
+ />
+ {streaming ? (
+
+ ⬛ 停止
+
+ ) : (
+
+ ✦ 生成
+
+ )}
+
+
+ {/* 状态提示 */}
+ {streaming && (
+
+ ▊ AI 正在写入编辑器…
+
+ )}
+ {!streaming && selectedText && (
+
+ 已选中 {selectedText.length} 字
+
+ )}
+ {!streaming && !selectedText && (
+
+ 将在光标处续写 · Ctrl+J 打开/关闭
+
+ )}
+
+ );
+}
+// ==================== Inline 参考面板(设定集勾选) ====================
+function InlineContextPanel({ contextItems, contextSelection, setContextSelection }) {
+ const [expanded, setExpanded] = useState(false);
+
+ // 只显示设定集条目,不显示对话历史
+ const settingsItems = useMemo(() =>
+ (contextItems || []).filter(it => !it._empty && it.category !== 'dialogue'),
+ [contextItems]);
+
+ // 按分组归类
+ const grouped = useMemo(() => {
+ const groups = {};
+ for (const item of settingsItems) {
+ const g = item.group || '其他';
+ if (!groups[g]) groups[g] = [];
+ groups[g].push(item);
+ }
+ return groups;
+ }, [settingsItems]);
+
+ const selectedCount = settingsItems.filter(it => contextSelection?.has(it.id)).length;
+ const totalCount = settingsItems.length;
+
+ if (totalCount === 0) return null;
+
+ const toggleItem = (itemId) => {
+ setContextSelection?.(prev => {
+ const next = new Set(prev);
+ if (next.has(itemId)) next.delete(itemId);
+ else next.add(itemId);
+ return next;
+ });
+ };
+
+ const toggleGroup = (groupName) => {
+ const items = grouped[groupName] || [];
+ setContextSelection?.(prev => {
+ const next = new Set(prev);
+ const allChecked = items.every(it => prev.has(it.id));
+ items.forEach(it => {
+ if (allChecked) next.delete(it.id);
+ else next.add(it.id);
+ });
+ return next;
+ });
+ };
+
+ return (
+
+
setExpanded(!expanded)}
+ >
+ {expanded ? '▼' : '▶'}
+ 📚 参考
+ ({selectedCount}/{totalCount})
+
+ {expanded && (
+
+ )}
+
+ );
+}
+
+// ==================== 颜色选择器组件 ====================
+const PRESET_COLORS = [
+ '#000000', '#434343', '#666666', '#999999', '#cccccc',
+ '#c0392b', '#e74c3c', '#e67e22', '#f39c12', '#f1c40f',
+ '#27ae60', '#2ecc71', '#1abc9c', '#2980b9', '#3498db',
+ '#8e44ad', '#9b59b6', '#e91e63', '#795548', '#607d8b',
+];
+
+function ColorPicker({ label, currentColor, onSelect, onClose }) {
+ return (
+ e.preventDefault()} onClick={e => e.stopPropagation()}>
+
{label}
+
+ {PRESET_COLORS.map(color => (
+ { onSelect(color); onClose(); }}
+ title={color}
+ />
+ ))}
+
+
{ onSelect(null); onClose(); }}
+ >
+ 清除颜色
+
+
+ );
+}
+
+// ==================== 字体族选项 ====================
+const FONT_FAMILIES = [
+ { label: '默认(宋体)', value: '' },
+ { label: '黑体', value: '"Noto Sans SC", "Microsoft YaHei", sans-serif' },
+ { label: '楷体', value: '"KaiTi", "STKaiti", serif' },
+ { label: '仿宋', value: '"FangSong", "STFangsong", serif' },
+ { label: 'serif', value: '"Noto Serif SC", "Source Han Serif SC", Georgia, serif' },
+ { label: 'monospace', value: '"SF Mono", "Cascadia Code", "Consolas", monospace' },
+];
+
+const FONT_SIZES = [12, 14, 15, 16, 17, 18, 20, 22, 24, 28, 32];
+
+// ==================== 工具栏 ====================
+function EditorToolbar({ editor, margins, setMargins }) {
+ if (!editor) return null;
+
+ const [showFontColor, setShowFontColor] = useState(false);
+ const [showBgColor, setShowBgColor] = useState(false);
+ const [showFontFamily, setShowFontFamily] = useState(false);
+ const [showFontSize, setShowFontSize] = useState(false);
+ const [showTypeset, setShowTypeset] = useState(false);
+ const [showMargins, setShowMargins] = useState(false);
+ const [fontSize, setFontSize] = useState(() => {
+ if (typeof window !== 'undefined') return parseInt(localStorage.getItem('author-font-size')) || 17;
+ return 17;
+ });
+ const [lineHeight, setLineHeight] = useState(() => {
+ if (typeof window !== 'undefined') return parseFloat(localStorage.getItem('author-line-height')) || 1.9;
+ return 1.9;
+ });
+
+ useEffect(() => {
+ document.documentElement.style.setProperty('--editor-font-size', `${fontSize}px`);
+ document.documentElement.style.setProperty('--editor-line-height', String(lineHeight));
+ localStorage.setItem('author-font-size', String(fontSize));
+ localStorage.setItem('author-line-height', String(lineHeight));
+ }, [fontSize, lineHeight]);
+
+ const closeAll = () => {
+ setShowFontColor(false);
+ setShowBgColor(false);
+ setShowFontFamily(false);
+ setShowFontSize(false);
+ setShowTypeset(false);
+ setShowMargins(false);
+ };
+
+ const toolbarRef = useRef(null);
+ useEffect(() => {
+ const handler = (e) => {
+ if (e.target.closest('.toolbar-dropdown-wrap')) return;
+ closeAll();
+ };
+ document.addEventListener('click', handler);
+ return () => document.removeEventListener('click', handler);
+ }, []);
+
+ const currentFontFamily = editor.getAttributes('textStyle').fontFamily || '';
+ const currentFontLabel = FONT_FAMILIES.find(f => f.value === currentFontFamily)?.label || '默认';
+ const currentColor = editor.getAttributes('textStyle').color || '';
+ const currentHighlight = editor.getAttributes('highlight').color || '';
+
+ return (
+ { if (e.target.tagName !== 'INPUT') e.preventDefault(); }}>
+ {/* 撤销/重做 */}
+
+ editor.chain().focus().undo().run()} title="撤销 (Ctrl+Z)">↩
+ editor.chain().focus().redo().run()} title="重做 (Ctrl+Y)">↪
+
+
+
+
+ {/* 字体族 */}
+
e.stopPropagation()}>
+
{ closeAll(); setShowFontFamily(!showFontFamily); }} title="字体">
+ {currentFontLabel} ▾
+
+ {showFontFamily && (
+
+ {FONT_FAMILIES.map(f => (
+ e.preventDefault()}
+ onClick={() => {
+ if (f.value) {
+ editor.chain().focus().setFontFamily(f.value).run();
+ } else {
+ editor.chain().focus().unsetFontFamily().run();
+ }
+ setShowFontFamily(false);
+ }}
+ >
+ {f.label}
+
+ ))}
+
+ )}
+
+
+
+
+ {/* 格式按钮 */}
+
+ editor.chain().focus().toggleBold().run()} title="加粗 (Ctrl+B)" style={{ fontWeight: 'bold' }}>B
+ editor.chain().focus().toggleItalic().run()} title="斜体 (Ctrl+I)" style={{ fontStyle: 'italic' }}>I
+ editor.chain().focus().toggleUnderline().run()} title="下划线 (Ctrl+U)" style={{ textDecoration: 'underline' }}>U
+ editor.chain().focus().toggleStrike().run()} title="删除线" style={{ textDecoration: 'line-through' }}>S
+ editor.chain().focus().toggleSuperscript().run()} title="上标" style={{ fontSize: 11 }}>X²
+ editor.chain().focus().toggleSubscript().run()} title="下标" style={{ fontSize: 11 }}>X₂
+
+
+
+
+ {/* 字体颜色 */}
+
e.stopPropagation()}>
+ { closeAll(); setShowFontColor(!showFontColor); }}
+ title="字体颜色"
+ >
+ A
+ ▾
+
+ {showFontColor && (
+ {
+ if (color) editor.chain().focus().setColor(color).run();
+ else editor.chain().focus().unsetColor().run();
+ }}
+ onClose={() => setShowFontColor(false)}
+ />
+ )}
+
+
+ {/* 背景色/高亮 */}
+
e.stopPropagation()}>
+ { closeAll(); setShowBgColor(!showBgColor); }}
+ title="背景颜色(高亮)"
+ >
+ 高亮
+ ▾
+
+ {showBgColor && (
+ {
+ if (color) editor.chain().focus().toggleHighlight({ color }).run();
+ else editor.chain().focus().unsetHighlight().run();
+ }}
+ onClose={() => setShowBgColor(false)}
+ />
+ )}
+
+
+
+
+ {/* 标题 */}
+
+ editor.chain().focus().toggleHeading({ level: 1 }).run()} title="一级标题" style={{ fontSize: 13, fontWeight: 700 }}>H1
+ editor.chain().focus().toggleHeading({ level: 2 }).run()} title="二级标题" style={{ fontSize: 12, fontWeight: 700 }}>H2
+ editor.chain().focus().toggleHeading({ level: 3 }).run()} title="三级标题" style={{ fontSize: 11, fontWeight: 700 }}>H3
+
+
+
+
+ {/* 对齐 */}
+
+ editor.chain().focus().setTextAlign('left').run()} title="左对齐">≡
+ editor.chain().focus().setTextAlign('center').run()} title="居中">═
+ editor.chain().focus().setTextAlign('right').run()} title="右对齐">≢
+ editor.chain().focus().setTextAlign('justify').run()} title="两端对齐">☰
+
+
+
+
+ {/* 字号行距 */}
+
e.stopPropagation()}>
+
{ closeAll(); setShowTypeset(!showTypeset); }}
+ title="字号与行距"
+ style={{ fontSize: 12 }}
+ >
+ Aa ▾
+
+ {showTypeset && (
+
+
+ 字号
+ setFontSize(Number(e.target.value))}
+ />
+ {fontSize}px
+
+
+ 行距
+ setLineHeight(Number(e.target.value))}
+ />
+ {lineHeight.toFixed(1)}
+
+
{ setFontSize(17); setLineHeight(1.9); }}>
+ 恢复默认
+
+
+ )}
+
+
+ {/* 📄 页面边距 */}
+
e.stopPropagation()}>
+
{ closeAll(); setShowMargins(!showMargins); }}
+ title="页面设置"
+ style={{ fontSize: 12 }}
+ >
+ 📄 ▾
+
+ {showMargins && (
+
+
+ 上下
+ setMargins(prev => ({ ...prev, y: Number(e.target.value) }))}
+ />
+ {margins.y}px
+
+
+ 左右
+ setMargins(prev => ({ ...prev, x: Number(e.target.value) }))}
+ />
+ {margins.x}px
+
+
setMargins({ x: 96, y: 96 })}>
+ 恢复默认
+
+
+ )}
+
+
+
+
+ {/* 列表和引用 */}
+
+ editor.chain().focus().toggleBulletList().run()} title="无序列表">• 列
+ editor.chain().focus().toggleOrderedList().run()} title="有序列表">1. 列
+ editor.chain().focus().toggleTaskList().run()} title="任务列表">☑ 任
+ editor.chain().focus().toggleBlockquote().run()} title="引用块">❝ 引
+ editor.chain().focus().toggleCodeBlock().run()} title="代码块"></>
+ {
+ openMathEditor('', (latex) => {
+ editor.chain().focus().insertContent({ type: 'mathInline', attrs: { latex } }).run();
+ });
+ }} title="插入公式 (也可直接输入 $公式$)">∑
+ editor.chain().focus().setHorizontalRule().run()} title="分割线">——
+
+
+ );
+}
+
+// ==================== 状态栏 ====================
+function StatusBar({ editor, pageCount }) {
+ if (!editor) return null;
+
+ const characterCount = editor.storage.characterCount;
+ const chars = characterCount?.characters() ?? 0;
+ const words = editor.getText().replace(/\s/g, '').length;
+
+ return (
+
+
+ {words} 字
+ {chars} 字符
+ 共 {pageCount} 页
+
+
+ Ctrl+J AI助手
+ 自动保存
+
+
+ );
+}
diff --git a/app/components/GhostMark.js b/app/components/GhostMark.js
new file mode 100644
index 0000000..476da28
--- /dev/null
+++ b/app/components/GhostMark.js
@@ -0,0 +1,99 @@
+'use client';
+
+import { Mark } from '@tiptap/core';
+
+/**
+ * GhostMark — Tiptap Mark 扩展
+ * AI 生成的文本标记为 ghost-text,以半透明样式显示。
+ * 用户接受时去除 mark,拒绝时删除文本。
+ */
+const GhostMark = Mark.create({
+ name: 'ghostText',
+
+ // 不会被持久化到 HTML(保存时自动剥离)
+ addOptions() {
+ return {
+ HTMLAttributes: {
+ class: 'ghost-text',
+ },
+ };
+ },
+
+ parseHTML() {
+ return [
+ {
+ tag: 'span.ghost-text',
+ },
+ ];
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['span', { ...this.options.HTMLAttributes, ...HTMLAttributes }, 0];
+ },
+
+ addCommands() {
+ return {
+ setGhostText: () => ({ commands }) => {
+ return commands.setMark(this.name);
+ },
+ unsetGhostText: () => ({ commands }) => {
+ return commands.unsetMark(this.name);
+ },
+ // 删除文档中所有 ghost 标记(保留文本,去掉半透明)
+ acceptAllGhost: () => ({ tr, dispatch }) => {
+ if (dispatch) {
+ tr.doc.descendants((node, pos) => {
+ if (node.isText) {
+ const ghostMark = node.marks.find(m => m.type.name === this.name);
+ if (ghostMark) {
+ tr.removeMark(pos, pos + node.nodeSize, ghostMark.type);
+ }
+ }
+ });
+ }
+ return true;
+ },
+ // 删除带 ghost 标记的所有文本节点,并清理残留的空段落
+ // ghostStart: 可选,ghost 文本插入起始位置,仅清理该位置之后的空段落
+ removeAllGhost: (ghostStart) => ({ tr, dispatch }) => {
+ if (dispatch) {
+ // 第一步:收集所有 ghost 文本范围
+ const ranges = [];
+ tr.doc.descendants((node, pos) => {
+ if (node.isText) {
+ const ghostMark = node.marks.find(m => m.type.name === this.name);
+ if (ghostMark) {
+ ranges.push({ from: pos, to: pos + node.nodeSize });
+ }
+ }
+ });
+ // 记录 ghost 区域的最小起始位置(用于限定空段落清理范围)
+ const effectiveStart = ghostStart != null
+ ? ghostStart
+ : (ranges.length > 0 ? ranges[0].from : null);
+ // 从后向前删除 ghost 文本
+ for (let i = ranges.length - 1; i >= 0; i--) {
+ tr.delete(ranges[i].from, ranges[i].to);
+ }
+ // 第二步:清理 ghost 区域内因删除文本而产生的空段落
+ if (effectiveStart != null) {
+ const emptyParas = [];
+ tr.doc.descendants((node, pos) => {
+ if (node.type.name === 'paragraph' && node.content.size === 0 && pos >= effectiveStart) {
+ emptyParas.push({ from: pos, to: pos + node.nodeSize });
+ }
+ });
+ for (let i = emptyParas.length - 1; i >= 0; i--) {
+ if (tr.doc.content.childCount > 1) {
+ tr.delete(emptyParas[i].from, emptyParas[i].to);
+ }
+ }
+ }
+ }
+ return true;
+ },
+ };
+ },
+});
+
+export default GhostMark;
diff --git a/app/components/HelpPanel.js b/app/components/HelpPanel.js
new file mode 100644
index 0000000..caf3744
--- /dev/null
+++ b/app/components/HelpPanel.js
@@ -0,0 +1,503 @@
+'use client';
+
+import { useState } from 'react';
+import { useI18n } from '../lib/useI18n';
+
+const HELP_SECTIONS = [
+ {
+ id: 'quickstart',
+ title: '🚀 快速开始',
+ content: `
+## 欢迎使用 Author
+
+Author 是一款面向小说创作者的 **AI 辅助写作平台**,集成智能续写、设定集管理、上下文感知等专业功能,为你打造沉浸式创作体验。
+
+### 第一步:配置 AI
+1. 点击左侧边栏底部的 **⚙️ 设定** 按钮
+2. 在「API 配置」中填写你的 AI 服务信息
+3. 支持 **OpenAI 兼容接口** 和 **Google Gemini 原生接口**
+4. 点击「测试连接」确认配置正确
+
+### 第二步:开始创作
+1. 在左侧边栏点击 **+ 新建** 创建章节
+2. 在编辑器中直接开始写作
+3. 使用顶部工具栏调整格式
+
+### 第三步:AI 辅助
+- **内联 AI**:按 **Ctrl+J** 在光标处唤起 AI,直接在编辑器中续写
+- **AI 聊天**:点击右上角 **✦ AI** 打开侧边栏,与 AI 对话讨论剧情
+- AI 会自动参考你的设定集、前文内容和写作模式
+
+### 界面概览
+| 区域 | 功能 |
+|------|------|
+| **左侧边栏** | 章节管理、字数统计、存档/读档、设定集、主题 |
+| **顶部工具栏** | 文字格式、对齐、列表、公式、排版 |
+| **编辑区域** | 所见即所得的富文本编辑,分页预览 |
+| **右侧 AI 栏** | AI 聊天对话、上下文参考管理 |
+| **底部状态栏** | 写作模式、页数、字数统计 |
+ `,
+ },
+ {
+ id: 'ai-inline',
+ title: '✦ 内联 AI 写作',
+ content: `
+## 内联 AI 写作助手
+
+在编辑器内直接使用 AI,类似 Cursor 的体验。
+
+### 唤起方式
+按下 **Ctrl+J**(Mac: **⌘+J**)在光标位置弹出 AI 面板。
+
+### AI 模式
+
+| 模式 | 功能 | 需要选中文字 |
+|------|------|:----:|
+| ✦ 续写 | 从光标处自然续写故事 | ✗ |
+| ✎ 润色 | 提升文字质量和流畅度 | ✓ |
+| ⊕ 扩写 | 丰富细节与描写 | ✓ |
+| ⊖ 精简 | 浓缩核心内容 | ✓ |
+
+### 使用步骤
+1. 将光标放在要续写的位置,或选中要修改的文字
+2. 按 **Ctrl+J** 打开 AI 面板
+3. 选择所需模式
+4. (可选)在输入框中补充指示,如"写一段打斗场景"
+5. 按 **Enter** 或点击 **✦ 生成** 开始
+6. AI 会以 **Ghost Text**(幽灵文字)效果出现在编辑器中
+7. 点击 **✓ 接受** 确认采用,或 **✕ 拒绝** 撤销
+
+### 上下文感知
+AI 会自动参考:
+- **前文内容**:自动采集前文上下文
+- **设定集**:角色设定、世界观、情节大纲等
+- **写作模式**:网文/纯文学/剧本不同风格
+
+> 提示:在设定集中配置角色和世界观,AI 生成内容会更贴合你的故事。
+ `,
+ },
+ {
+ id: 'ai-chat',
+ title: '💬 AI 聊天侧栏',
+ content: `
+## AI 聊天侧栏
+
+与 AI 进行多轮对话,讨论剧情、角色、世界观,甚至让 AI 直接管理你的设定集。
+
+### 打开方式
+点击编辑器右上角的 **✦ AI** 按钮,或使用快捷键。
+
+### 核心功能
+
+#### 多轮对话
+- 支持完整的多轮对话历史
+- AI 回复支持 **Markdown 渲染**(代码块、表格、列表等)
+- 可以编辑已发送的消息重新生成
+
+#### 上下文参考(参考 Tab)
+切换到「参考」标签页,勾选要注入的上下文:
+- 人物设定、世界观、地点、物品、大纲、写作规则
+- 勾选的内容会作为 AI 的背景知识
+
+#### 会话管理
+- **新建会话**:开启全新对话
+- **切换会话**:在多个对话间切换
+- **重命名/删除**:右键管理会话
+
+#### AI 管理设定集
+对话中 AI 可以生成 **设定操作卡片**,你可以一键应用:
+- **添加**:AI 建议新角色/设定,点击「✅ 应用」直接写入设定集
+- **更新**:AI 修改已有设定(按名称自动匹配)
+- **删除**:AI 建议删除某项设定
+- 点击卡片标题栏 **▼ 展开** 可以查看完整内容
+
+#### 消息变体
+- 对 AI 回复点击「重新生成」可获取不同版本
+- 使用 < 1/3 > 导航切换不同变体
+ `,
+ },
+ {
+ id: 'settings',
+ title: '⚙️ 设定集系统',
+ content: `
+## 设定集管理
+
+设定集是 AI 创作的"记忆",帮助 AI 深入理解你的故事世界,生成更贴合的内容。
+
+### 打开方式
+点击侧栏底部的 **⚙️** 按钮。
+
+### API 配置与模型设置
+这是 AI 助理运作的基石。所有写入的 API 密钥都在 **本地浏览器存储**,绝对安全。
+| 设置项 | 说明 |
+|------|------|
+| **API 提供商** | 支持智谱、DeepSeek、OpenAI、Gemini、Kimi网络等,也可选自定义接入 |
+| **API Key** | 你的大模型服务凭证。必须配置才能唤醒光标 AI |
+| **Base URL** | 接口地址(切换提供商时会自动填写官方地址) |
+| **模型选择** | 点击「从API拉取模型列表」自动更新当前可用的大模型库供你选择 |
+| **独立向量API** | 高级功能。允许你用专用的 Embedding 模型解决长篇巨著的“记忆遗忘”问题 |
+
+### 偏好设置 (Preferences)
+在设定集的最后一栏,你可以随时:
+- 切换语言界面(简体中文、English、Русский)
+- 切换工作台视觉主题(经典纸张 / 现代玻璃)
+
+### 书籍信息
+设置书名、类型、简介——帮助 AI 了解整体方向。
+
+### 设定树
+在左侧树形结构中添加和管理各类设定:
+
+| 分类 | 图标 | 可设置字段 |
+|------|------|---------|
+| **角色设定** | 🎭 | 类型、性别、年龄、外貌、性格、说话风格、背景、动机、能力、关系 |
+| **世界观** | 🌍 | 描述、备注 |
+| **地点/空间** | 📍 | 描述、场景标题、感官细节(视觉/听觉/嗅觉)、氛围 |
+| **物品/道具** | 📦 | 描述、类型、品阶、持有者、数值属性、象征意义 |
+| **剧情大纲** | 📋 | 状态(计划中/写作中/已完成)、描述、备注 |
+| **写作规则** | ✍️ | 规则描述(AI 严格遵守) |
+
+### 额外字段
+AI 生成的非标准字段会自动出现在 **✨ AI 生成的额外字段** 分组中,可查看和编辑。
+
+### 写作模式
+| 模式 | 特点 |
+|------|------|
+| 📱 网文模式 | 节奏紧凑、对话多、爽点密集 |
+| 📖 纯文学模式 | 叙述细腻、描写丰富、注重意境 |
+| 🎬 剧本模式 | 标准剧本格式 |
+ `,
+ },
+ {
+ id: 'chapters',
+ title: '📚 章节管理',
+ content: `
+## 章节管理
+
+### 创建章节
+点击侧栏中的 **+ 新建** 按钮,输入章节标题即可创建。
+
+### 切换章节
+点击侧栏中的章节名称切换到该章节,编辑器会加载其内容。
+
+### 右键操作
+在章节名称上 **右键单击** 可以:
+- **✎ 重命名** — 修改章节标题
+- **↓ 导出 Markdown** — 将当前章节导出为 .md 文件
+- **✕ 删除章节** — 删除该章节(需确认)
+
+### 拖拽排序
+长按章节名称可以拖拽重新排列顺序。
+
+### 字数统计
+- 每个章节旁显示本章字数
+- 侧栏底部显示全书总字数
+
+### 分页预览
+编辑器以类似 Word/Google Docs 的分页视图呈现,白色纸张卡片 + 灰色画布背景,底部状态栏显示当前页数。
+ `,
+ },
+ {
+ id: 'toolbar',
+ title: '🎨 工具栏功能',
+ content: `
+## 工具栏一览
+
+### 撤销 / 重做
+↩ 撤销最近的操作,↪ 恢复已撤销的操作。
+
+### 字体与字号
+- 下拉选择字体:默认(宋体)、黑体、楷体、仿宋、Serif、Monospace
+- 下拉选择字号:从 12px 到 32px
+
+### 文字格式
+| 按钮 | 功能 | 快捷键 |
+|------|------|--------|
+| **B** | 加粗 | Ctrl+B |
+| *I* | 斜体 | Ctrl+I |
+| U | 下划线 | Ctrl+U |
+| ~~S~~ | 删除线 | — |
+| X² | 上标 | — |
+| X₂ | 下标 | — |
+
+### 颜色
+- **A▾** — 文字颜色选择器
+- **高亮▾** — 背景高亮色选择器
+
+### 标题与对齐
+- H1/H2/H3 — 一级/二级/三级标题
+- 左对齐/居中/右对齐/两端对齐
+
+### 排版调节
+点击 **Aa▾** 调节全局字号(14-24px)和行距(1.4-2.6),可一键恢复默认。
+
+### 列表与块元素
+| 按钮 | 功能 |
+|------|------|
+| • 列 | 无序列表 |
+| 1. 列 | 有序列表 |
+| ☑ 任 | 任务列表 |
+| ❝ 引 | 引用块 |
+| > | 代码块 |
+| ∑ | LaTeX 公式 |
+| —— | 水平分割线 |
+ `,
+ },
+ {
+ id: 'data',
+ title: '💾 数据管理',
+ content: `
+## 数据管理
+
+### 自动保存
+编辑内容会 **实时自动保存** 到浏览器的 localStorage,无需手动保存。
+
+### 存档 / 读档(项目级别)
+在侧栏底部:
+- **💾 存档** — 将整个项目(所有章节 + 设定集 + AI 对话历史)导出为 JSON 文件
+- **📂 读档** — 从 JSON 文件恢复完整项目
+
+> 建议定期存档,防止浏览器数据被清空。
+
+### 章节导出
+- **导出本章** — 将当前章节导出为 Markdown (.md) 文件
+- **导出全部** — 将所有章节合并导出为一个 Markdown 文件
+
+### 重要提醒
+- 所有数据存储在 **浏览器本地**,不会上传到任何服务器
+- **清除浏览器数据** 会丢失所有未导出的内容
+- **API Key** 仅存储在本地 localStorage 中
+- 建议定期使用「💾 存档」功能备份
+ `,
+ },
+ {
+ id: 'markdown',
+ title: '📝 Markdown',
+ content: `
+## Markdown 自动渲染
+
+在编辑器中输入 Markdown 语法会 **自动转换** 为富文本格式。
+
+### 支持的语法
+| 输入 | 效果 |
+|------|------|
+| \`**加粗**\` | **加粗** |
+| \`*斜体*\` | *斜体* |
+| \`~~删除线~~\` | ~~删除线~~ |
+| \`# 标题\` | 一级标题 |
+| \`## 标题\` | 二级标题 |
+| \`### 标题\` | 三级标题 |
+| \`- 列表\` | 无序列表 |
+| \`1. 列表\` | 有序列表 |
+| \`> 引用\` | 引用块 |
+| \`---\` | 分割线 |
+
+### AI 聊天中的 Markdown
+AI 聊天侧栏中的回复支持完整的 Markdown 渲染,包括代码块、表格、链接等。
+ `,
+ },
+ {
+ id: 'shortcuts',
+ title: '⌨️ 快捷键',
+ content: `
+## 键盘快捷键
+
+### 编辑
+| 快捷键 | 功能 |
+|--------|------|
+| Ctrl+Z | 撤销 |
+| Ctrl+Y | 重做 |
+| Ctrl+A | 全选 |
+
+### 格式
+| 快捷键 | 功能 |
+|--------|------|
+| Ctrl+B | 加粗 |
+| Ctrl+I | 斜体 |
+| Ctrl+U | 下划线 |
+
+### AI
+| 快捷键 | 功能 |
+|--------|------|
+| Ctrl+J | 打开/关闭内联 AI 面板 |
+| Enter | 开始 AI 生成 |
+| Esc | 关闭 AI 面板 / 取消生成 |
+
+### Markdown 快捷输入
+| 输入 | 触发 |
+|------|------|
+| \`# \` + 空格 | 一级标题 |
+| \`## \` + 空格 | 二级标题 |
+| \`- \` + 空格 | 无序列表 |
+| \`1. \` + 空格 | 有序列表 |
+| \`> \` + 空格 | 引用块 |
+| \`---\` + 回车 | 分割线 |
+ `,
+ },
+ {
+ id: 'theme',
+ title: '🎭 主题 & 排版',
+ content: `
+## 主题与偏好排版
+
+### 双旗舰主题引擎
+目前 Author 提供两套深度打磨的主题集,可在设定集 -> **偏好设置** 中无缝切换:
+- 📜 **经典纸张 (Warm Classic)**: 采用高级护眼的复古暖灰色调,所有卡片呈现日记本般的实体拟物反馈。
+- 🧊 **现代通透 (Modern Glass)**: 苹果 macOS 风格,纯白与冷灰基底,带有极其剔透的毛玻璃 (\`backdrop-filter\`) 层级架构,追求极致的干净。
+
+*注:你可以随时点击侧栏右下角的 🌙 / ☀️ 切换对应主题下的深色/浅色模式。*
+
+### 排版引擎调节
+在顶部工具栏点击 **Aa▾** 按钮:
+- **字号**:滑块无极调节 14px ~ 24px(默认 17px)
+- **行距**:滑块无极调节 1.4 ~ 2.6(默认 1.9)
+- **恢复默认**:一键重置排版参数
+
+### 字体选择
+| 字体 | 适合场景 |
+|------|---------|
+| 默认(宋体) | 正文写作 |
+| 黑体 | 标题 |
+| 楷体 | 古风文 |
+| 仿宋 | 公文风格 |
+| Serif | 英文衬线体 |
+| Monospace | 等宽字体 |
+
+### 排版建议
+| 场景 | 推荐设置 |
+|------|---------|
+| 网文写作 | 字号 16-17px,行距 1.8-2.0 |
+| 纯文学 | 字号 17-18px,行距 2.0-2.2 |
+| 校对审稿 | 字号 14-15px,行距 1.6 |
+ `,
+ },
+ {
+ id: 'about',
+ title: 'ℹ️ 关于',
+ content: `
+## 关于 Author
+
+**Author** 是一款 AI 驱动的小说创作工具,旨在为网文作者和文学创作者提供专业、高效的写作体验。
+
+### 核心特色
+- 🤖 **AI 智能写作** — 内联续写 + 聊天讨论,双模式辅助创作
+- 📖 **上下文感知** — AI 自动参考角色设定、世界观、前文内容
+- 🎭 **设定集管理** — 树形结构管理角色、世界观、大纲、写作规则
+- ✦ **Ghost Text** — 类似 Cursor 的幽灵文字预览,接受/拒绝一键操作
+- 📄 **分页视图** — 类 Word/Google Docs 的白纸分页排版
+- 🌙 **深色模式** — 护眼的暗色主题
+- 💾 **本地优先** — 所有数据存储在本地,隐私安全
+- 📦 **存档/读档** — 一键导出/导入完整项目
+
+### 数据安全
+- 所有创作内容仅存储在你的浏览器本地
+- API Key 不经过任何第三方服务器
+- 支持一键导出全部数据
+
+### 技术栈
+Next.js + Tiptap 编辑器 + AI API(OpenAI 兼容 / Gemini)
+ `,
+ },
+];
+
+export default function HelpPanel({ open, onClose }) {
+ const [activeSection, setActiveSection] = useState('quickstart');
+ const { t } = useI18n();
+
+ if (!open) return null;
+
+ const currentSection = HELP_SECTIONS.find(s => s.id === activeSection);
+
+ return (
+
+
e.stopPropagation()}>
+ {/* 顶栏 */}
+
+
{t('help.title')}
+
+ {
+ localStorage.removeItem('author-onboarding-done');
+ window.location.reload();
+ }}
+ >
+ {t('help.btnRetour')}
+
+ ✕
+
+
+
+
+ {/* 左侧导航 */}
+
+ {HELP_SECTIONS.map(section => (
+ setActiveSection(section.id)}
+ >
+ {section.title}
+
+ ))}
+
+
+ {/* 右侧内容 */}
+
+
+
+
+ );
+}
+
+// 简单的 Markdown → HTML 转换(仅用于帮助文档静态内容)
+function renderSimpleMarkdown(md) {
+ let html = md.trim();
+
+ // 表格
+ html = html.replace(/^(\|.+\|)\n(\|[-: |]+\|)\n((?:\|.+\|\n?)*)/gm, (_, header, sep, body) => {
+ const ths = header.split('|').filter(c => c.trim()).map(c => `${c.trim()} `).join('');
+ const aligns = sep.split('|').filter(c => c.trim()).map(c => {
+ if (c.trim().startsWith(':') && c.trim().endsWith(':')) return 'center';
+ if (c.trim().endsWith(':')) return 'right';
+ return 'left';
+ });
+ const rows = body.trim().split('\n').map(row => {
+ const tds = row.split('|').filter(c => c.trim()).map((c, i) =>
+ `${c.trim()} `
+ ).join('');
+ return `${tds} `;
+ }).join('');
+ return ``;
+ });
+
+ // Headers
+ html = html.replace(/^### (.+)$/gm, '$1 ');
+ html = html.replace(/^## (.+)$/gm, '$1 ');
+
+ // Bold, italic, strikethrough, code
+ html = html.replace(/\*\*(.+?)\*\*/g, '$1 ');
+ html = html.replace(/\*(.+?)\*/g, '$1 ');
+ html = html.replace(/~~(.+?)~~/g, '$1 ');
+ html = html.replace(/`([^`]+)`/g, '$1');
+
+ // Blockquote
+ html = html.replace(/^> (.+)$/gm, '$1 ');
+
+ // List items
+ html = html.replace(/^- (.+)$/gm, '$1 ');
+
+ // Paragraphs (lines that don't start with < )
+ html = html.replace(/^(?!<[a-z/]|$)(.+)$/gm, '$1
');
+
+ // Clean up empty paragraphs
+ html = html.replace(/\s*<\/p>/g, '');
+
+ return html;
+}
diff --git a/app/components/MathExtension.js b/app/components/MathExtension.js
new file mode 100644
index 0000000..0b18457
--- /dev/null
+++ b/app/components/MathExtension.js
@@ -0,0 +1,282 @@
+'use client';
+
+import { Node, mergeAttributes } from '@tiptap/core';
+import { InputRule } from '@tiptap/core';
+import katex from 'katex';
+
+// ==================== 公式编辑弹窗(居中大框) ====================
+export function openMathEditor(currentLatex, onSave) {
+ // 创建遮罩
+ const overlay = document.createElement('div');
+ overlay.className = 'math-editor-overlay';
+
+ // 创建弹窗
+ const dialog = document.createElement('div');
+ dialog.className = 'math-editor-dialog';
+ dialog.innerHTML = `
+
+
+
+
+ `;
+
+ overlay.appendChild(dialog);
+ document.body.appendChild(overlay);
+
+ const textarea = dialog.querySelector('.math-editor-input');
+ const preview = dialog.querySelector('.math-editor-preview');
+ const closeBtn = dialog.querySelector('.math-editor-close');
+ const cancelBtn = dialog.querySelector('.math-editor-cancel');
+ const saveBtn = dialog.querySelector('.math-editor-save');
+
+ textarea.value = currentLatex || '';
+
+ // 实时预览
+ const updatePreview = () => {
+ const val = textarea.value.trim();
+ if (!val) {
+ preview.innerHTML = '此处显示公式预览… ';
+ return;
+ }
+ try {
+ preview.innerHTML = katex.renderToString(val, {
+ throwOnError: false,
+ displayMode: true,
+ });
+ } catch (e) {
+ preview.innerHTML = `${e.message} `;
+ }
+ };
+ updatePreview();
+ textarea.addEventListener('input', updatePreview);
+
+ // 关闭
+ const close = () => {
+ overlay.classList.add('closing');
+ setTimeout(() => overlay.remove(), 150);
+ };
+
+ // 保存
+ const save = () => {
+ const val = textarea.value.trim();
+ if (val) onSave(val);
+ close();
+ };
+
+ closeBtn.addEventListener('click', close);
+ cancelBtn.addEventListener('click', close);
+ saveBtn.addEventListener('click', save);
+ overlay.addEventListener('click', (e) => {
+ if (e.target === overlay) close();
+ });
+
+ // 键盘
+ textarea.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape') { close(); e.preventDefault(); }
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { save(); e.preventDefault(); }
+ });
+
+ // 聚焦
+ requestAnimationFrame(() => {
+ textarea.focus();
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
+ });
+}
+
+// ==================== 行内公式节点 $...$ ====================
+export const MathInline = Node.create({
+ name: 'mathInline',
+ group: 'inline',
+ inline: true,
+ atom: true,
+
+ addAttributes() {
+ return {
+ latex: { default: '' },
+ };
+ },
+
+ parseHTML() {
+ return [{
+ tag: 'span[data-math-inline]',
+ getAttrs: (el) => ({ latex: el.getAttribute('data-latex') || '' }),
+ }];
+ },
+
+ renderHTML({ node }) {
+ return ['span', {
+ 'data-math-inline': '',
+ 'data-latex': node.attrs.latex,
+ class: 'math-inline',
+ contenteditable: 'false',
+ }];
+ },
+
+ addNodeView() {
+ return ({ node, getPos, editor }) => {
+ const dom = document.createElement('span');
+ dom.classList.add('math-inline');
+ dom.contentEditable = 'false';
+
+ const render = (latex) => {
+ try {
+ dom.innerHTML = katex.renderToString(latex, {
+ throwOnError: false,
+ displayMode: false,
+ });
+ } catch {
+ dom.textContent = latex;
+ }
+ };
+ render(node.attrs.latex);
+
+ // 双击打开编辑弹窗
+ dom.addEventListener('dblclick', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ openMathEditor(node.attrs.latex, (newLatex) => {
+ const pos = getPos();
+ if (typeof pos === 'number') {
+ editor.chain().focus()
+ .command(({ tr }) => {
+ tr.setNodeMarkup(pos, undefined, { latex: newLatex });
+ return true;
+ })
+ .run();
+ }
+ });
+ });
+
+ return {
+ dom,
+ update(updatedNode) {
+ if (updatedNode.type.name !== 'mathInline') return false;
+ render(updatedNode.attrs.latex);
+ return true;
+ },
+ };
+ };
+ },
+
+ addInputRules() {
+ return [
+ new InputRule({
+ find: /(? {
+ const latex = match[1];
+ if (!latex.trim()) return null;
+ const { tr } = state;
+ tr.replaceWith(
+ range.from,
+ range.to,
+ this.type.create({ latex })
+ );
+ },
+ }),
+ ];
+ },
+});
+
+
+// ==================== 块级公式节点 $$...$$ ====================
+export const MathBlock = Node.create({
+ name: 'mathBlock',
+ group: 'block',
+ atom: true,
+ defining: true,
+
+ addAttributes() {
+ return {
+ latex: { default: '' },
+ };
+ },
+
+ parseHTML() {
+ return [{
+ tag: 'div[data-math-block]',
+ getAttrs: (el) => ({ latex: el.getAttribute('data-latex') || '' }),
+ }];
+ },
+
+ renderHTML({ node }) {
+ return ['div', {
+ 'data-math-block': '',
+ 'data-latex': node.attrs.latex,
+ class: 'math-block',
+ contenteditable: 'false',
+ }];
+ },
+
+ addNodeView() {
+ return ({ node, getPos, editor }) => {
+ const dom = document.createElement('div');
+ dom.classList.add('math-block');
+ dom.contentEditable = 'false';
+
+ const render = (latex) => {
+ try {
+ dom.innerHTML = katex.renderToString(latex, {
+ throwOnError: false,
+ displayMode: true,
+ });
+ } catch {
+ dom.textContent = latex;
+ }
+ };
+ render(node.attrs.latex);
+
+ // 双击打开编辑弹窗
+ dom.addEventListener('dblclick', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ openMathEditor(node.attrs.latex, (newLatex) => {
+ const pos = getPos();
+ if (typeof pos === 'number') {
+ editor.chain().focus()
+ .command(({ tr }) => {
+ tr.setNodeMarkup(pos, undefined, { latex: newLatex });
+ return true;
+ })
+ .run();
+ }
+ });
+ });
+
+ return {
+ dom,
+ update(updatedNode) {
+ if (updatedNode.type.name !== 'mathBlock') return false;
+ render(updatedNode.attrs.latex);
+ return true;
+ },
+ };
+ };
+ },
+
+ addInputRules() {
+ return [
+ new InputRule({
+ find: /\$\$([^$]+)\$\$$/,
+ handler: ({ state, range, match }) => {
+ const latex = match[1];
+ if (!latex.trim()) return null;
+ const { tr } = state;
+ tr.replaceWith(
+ range.from,
+ range.to,
+ this.type.create({ latex })
+ );
+ },
+ }),
+ ];
+ },
+});
diff --git a/app/components/PageBreakExtension.js b/app/components/PageBreakExtension.js
new file mode 100644
index 0000000..e680771
--- /dev/null
+++ b/app/components/PageBreakExtension.js
@@ -0,0 +1,312 @@
+'use client';
+
+import { Extension } from '@tiptap/core';
+import { Plugin, PluginKey } from '@tiptap/pm/state';
+import { Decoration, DecorationSet } from '@tiptap/pm/view';
+
+const PAGE_HEIGHT = 1056;
+const PAGE_GAP = 24;
+
+const pluginKey = new PluginKey('pageBreakSpacer');
+
+export const PageBreakExtension = Extension.create({
+ name: 'pageBreakSpacer',
+
+ addProseMirrorPlugins() {
+ return [
+ new Plugin({
+ key: pluginKey,
+
+ state: {
+ init() {
+ return DecorationSet.empty;
+ },
+ apply(tr, decoSet) {
+ const meta = tr.getMeta(pluginKey);
+ if (meta) {
+ if (meta.clear) return DecorationSet.empty;
+ if (meta.apply) return meta.decoSet;
+ }
+ // 文档变更时,不再立刻清除装饰器,而是利用 Tiptap 的 Mapping 机制让占位符随着文本自动漂移。
+ // 这样打字时旧的断头台还在,高度不变,绝对不会导致内容突然上缩。
+ if (tr.docChanged) {
+ return decoSet.map(tr.mapping, tr.doc);
+ }
+ return decoSet;
+ },
+ },
+
+ props: {
+ decorations(state) {
+ return pluginKey.getState(state) || DecorationSet.empty;
+ },
+ },
+
+ view(editorView) {
+ let updateTimer = null;
+ let isProcessing = false;
+
+ const performPagination = () => {
+ if (isProcessing) return;
+ isProcessing = true;
+
+ const dom = editorView.dom;
+ if (!dom || !dom.isConnected) {
+ isProcessing = false;
+ return;
+ }
+
+ // 1. 锁定容器高度:防止清空占位符瞬间因高度收缩引发滚动条狂跳
+ // 注意:不再保存/恢复 scrollTop,避免与用户滚动操作冲突
+ const oldMinHeight = dom.style.minHeight;
+ dom.style.minHeight = `${dom.scrollHeight}px`;
+
+ // 2. 瞬间剥离旧排版
+ const oldState = pluginKey.getState(editorView.state);
+ let cleared = false;
+ if (oldState && oldState !== DecorationSet.empty) {
+ const trClear = editorView.state.tr;
+ trClear.setMeta(pluginKey, { clear: true });
+ trClear.setMeta('addToHistory', false);
+ editorView.dispatch(trClear);
+ cleared = true;
+ }
+
+ // 【魔法核心】强制浏览器在当前帧立即进行同步布局重排 (Reflow),暴露天然文档流
+ void dom.offsetHeight;
+
+ // 3. 进入"绝对坐标系"精准测量
+ const doc = editorView.state.doc;
+ const domRect = dom.getBoundingClientRect();
+ const domTop = domRect.top;
+
+ // 自动侦测当前的真实 Padding (不再需要通过 React 传参)
+ const computedStyle = window.getComputedStyle(dom);
+ const marginY = parseFloat(computedStyle.paddingTop) || 96;
+
+ let accumulatedSpacers = 0;
+ let currentPage = 0;
+ const spacerData = [];
+
+ // 纸张可用区域计算公式
+ const getUsableBottom = (page) => page * (PAGE_HEIGHT + PAGE_GAP) + PAGE_HEIGHT - marginY;
+ const getUsableTop = (page) => page * (PAGE_HEIGHT + PAGE_GAP) + marginY;
+
+ // 收集顶级内容块
+ const blocks = [];
+ doc.descendants((node, pos) => {
+ if (node.isTextblock || (node.isBlock && node.isAtom)) {
+ blocks.push({ node, pos });
+ return false;
+ }
+ return true;
+ });
+
+ for (const { node, pos } of blocks) {
+ const domNode = editorView.nodeDOM(pos);
+ if (!domNode || domNode.nodeType !== 1) continue;
+
+ const rect = domNode.getBoundingClientRect();
+ if (rect.height === 0) continue;
+
+ const naturalTop = rect.top - domTop;
+ const naturalBottom = rect.bottom - domTop;
+
+ let actualTop = naturalTop + accumulatedSpacers;
+ let actualBottom = naturalBottom + accumulatedSpacers;
+
+ // 如果段落已经完全位于下一页的内容区域内,直接跳过前面的页码
+ // 注意:用 getUsableTop(next) 而非 getUsableBottom(current),
+ // 避免跳过"边距区"或"间隙区"的段落(它们仍需由跨页逻辑推送)
+ while (actualTop >= getUsableTop(currentPage + 1)) {
+ currentPage++;
+ }
+
+ // 触发跨页逻辑!
+ while (actualBottom > getUsableBottom(currentPage)) {
+ const limitY = getUsableBottom(currentPage);
+ const nextTopY = getUsableTop(currentPage + 1);
+
+ // 无法拆分的原子块(如大图片、公式),整体推向下一页
+ if (node.isAtom || !node.isTextblock) {
+ const spacerHeight = nextTopY - actualTop;
+ if (spacerHeight > 0) {
+ spacerData.push({ pos, height: spacerHeight, isBlock: true });
+ accumulatedSpacers += spacerHeight;
+ actualTop += spacerHeight;
+ actualBottom += spacerHeight;
+ }
+ currentPage++;
+ } else {
+ // 常规文字段落,开始寻找破界点
+ const startPos = pos + 1;
+ const endPos = pos + node.nodeSize - 1;
+
+ if (startPos > endPos) {
+ currentPage++; continue;
+ }
+
+ // 【二分查找】极速锁定越过边界的那一个字符
+ let low = startPos;
+ let high = endPos;
+ let crossPos = null;
+
+ while (low <= high) {
+ let mid = Math.floor((low + high) / 2);
+ let coords;
+ try { coords = editorView.coordsAtPos(mid); } catch (e) { }
+
+ // 容错处理:某些特殊字符可能拿不到坐标,就近偏移
+ if (!coords) {
+ let fallback = null;
+ for (let step = 1; step < 5; step++) {
+ if (mid + step <= high) { try { fallback = editorView.coordsAtPos(mid + step); if (fallback) { mid = mid + step; coords = fallback; break; } } catch (e) { } }
+ if (mid - step >= low) { try { fallback = editorView.coordsAtPos(mid - step); if (fallback) { mid = mid - step; coords = fallback; break; } } catch (e) { } }
+ }
+ if (!coords) { low = mid + 1; continue; }
+ }
+
+ const charActualBottom = (coords.bottom - domTop) + accumulatedSpacers;
+ if (charActualBottom > limitY) {
+ crossPos = mid;
+ high = mid - 1;
+ } else {
+ low = mid + 1;
+ }
+ }
+
+ // 若没找到(通常是段落的 Margin 溢出),忽略
+ if (crossPos === null) {
+ break;
+ }
+
+ // 【精确回溯】找到了越界字,往回倒退,找出这行字的行首
+ let lineStartPos = crossPos;
+ let currentLineTop = null;
+ try {
+ let crossCoords = editorView.coordsAtPos(crossPos);
+ if (crossCoords) {
+ currentLineTop = crossCoords.top - domTop;
+ for (let p = crossPos - 1; p >= startPos; p--) {
+ let c = editorView.coordsAtPos(p);
+ if (!c) continue;
+ let cTop = c.top - domTop;
+ // Y轴剧烈跳跃说明退到了上一行
+ if (cTop < currentLineTop - 4) break;
+ lineStartPos = p;
+ }
+ }
+ } catch (e) { }
+
+ let lineCoords;
+ try { lineCoords = editorView.coordsAtPos(lineStartPos); } catch (e) { }
+ let lineActualTop = lineCoords ? (lineCoords.top - domTop) + accumulatedSpacers : actualTop;
+
+ let spacerHeight = nextTopY - lineActualTop;
+
+ if (spacerHeight > 0) {
+ // 判断:段落整体已经超出当前页可用区域时,整块推走
+ // 否则使用行内断裂(即使越界行是第一行,前面仍有段间空白可利用)
+ if (actualTop >= limitY) {
+ // 段落完全在边界之后,整体推到下一页
+ spacerData.push({ pos, height: nextTopY - actualTop, isBlock: true });
+ accumulatedSpacers += (nextTopY - actualTop);
+ actualBottom += (nextTopY - actualTop);
+ actualTop = nextTopY;
+ } else {
+ // 段落跨越边界,在越界行首处插入行内断裂
+ spacerData.push({ pos: lineStartPos, height: spacerHeight, isBlock: false });
+ accumulatedSpacers += spacerHeight;
+ actualBottom += spacerHeight;
+ actualTop += spacerHeight;
+ }
+ }
+ currentPage++;
+ }
+ }
+ }
+
+ // 4. 重组排版,生成全新的物理占位 Decoration Widget
+ const decorations = spacerData.map(data => {
+ if (data.isBlock) {
+ const spacer = document.createElement('div');
+ spacer.className = 'page-break-spacer-block';
+ spacer.style.height = `${data.height}px`;
+ spacer.style.width = '100%';
+ spacer.style.clear = 'both';
+ spacer.setAttribute('data-pb-spacer', '1');
+ return Decoration.widget(data.pos, spacer, { key: `pb-${data.pos}`, side: -1, ignoreSelection: true });
+ } else {
+ // 隐形断头台:利用 inline-block 占满 100% 宽度,直接把后半行文字生生挤到下一页
+ const spacer = document.createElement('span');
+ spacer.className = 'page-break-spacer-inline';
+ spacer.style.display = 'block';
+ spacer.style.width = '100%';
+ spacer.style.height = `${data.height}px`;
+ spacer.style.lineHeight = '0';
+ spacer.style.fontSize = '0';
+ spacer.style.verticalAlign = 'top';
+ spacer.style.userSelect = 'none'; // 忽略光标焦点
+ spacer.style.pointerEvents = 'none';
+ spacer.setAttribute('data-pb-spacer', '1');
+ return Decoration.widget(data.pos, spacer, { key: `pb-${data.pos}`, side: -1, ignoreSelection: true });
+ }
+ });
+
+ const decoSet = decorations.length > 0 ? DecorationSet.create(doc, decorations) : DecorationSet.empty;
+
+ // 5. 无缝推送回视图
+ if (decorations.length > 0 || cleared) {
+ const trApply = editorView.state.tr;
+ trApply.setMeta(pluginKey, { apply: true, decoSet });
+ trApply.setMeta('addToHistory', false);
+ editorView.dispatch(trApply);
+ }
+
+ // 解除 DOM 高度锁定
+ dom.style.minHeight = oldMinHeight;
+ isProcessing = false;
+ };
+
+ let initTimer = null;
+ let debounceTimer = null;
+
+ // 立即调度(初始化 & 窗口 resize)
+ const scheduleImmediate = () => {
+ if (updateTimer) cancelAnimationFrame(updateTimer);
+ if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }
+ updateTimer = requestAnimationFrame(performPagination);
+ };
+
+ // 延迟调度(文档变更触发,加长防抖避免打字中途重排)
+ // 只要在连续打字,就绝不重排页面;停手 500ms 后再安静地后台重新计算分页
+ const scheduleDebouncedForDoc = () => {
+ if (debounceTimer) clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(() => {
+ debounceTimer = null;
+ if (updateTimer) cancelAnimationFrame(updateTimer);
+ updateTimer = requestAnimationFrame(performPagination);
+ }, 500);
+ };
+
+ // 初始化延迟——等 Editor DOM 完全挂载后首次计算
+ initTimer = setTimeout(scheduleImmediate, 200);
+ window.addEventListener('resize', scheduleImmediate);
+
+ return {
+ update(view, prevState) {
+ // 文档内容变化引发重算(防抖)
+ if (prevState.doc !== view.state.doc) scheduleDebouncedForDoc();
+ },
+ destroy() {
+ if (updateTimer) cancelAnimationFrame(updateTimer);
+ if (debounceTimer) clearTimeout(debounceTimer);
+ if (initTimer) clearTimeout(initTimer);
+ window.removeEventListener('resize', scheduleImmediate);
+ },
+ };
+ },
+ }),
+ ];
+ },
+});
diff --git a/app/components/SettingsItemEditor.js b/app/components/SettingsItemEditor.js
new file mode 100644
index 0000000..0a0ccbf
--- /dev/null
+++ b/app/components/SettingsItemEditor.js
@@ -0,0 +1,457 @@
+'use client';
+
+import { useState } from 'react';
+import { useI18n } from '../lib/useI18n';
+
+// ==================== 分类配色 ====================
+const CATEGORY_COLORS = {
+ character: { color: 'var(--cat-character)', bg: 'var(--cat-character-bg)' },
+ location: { color: 'var(--cat-location)', bg: 'var(--cat-location-bg)' },
+ world: { color: 'var(--cat-world)', bg: 'var(--cat-world-bg)' },
+ object: { color: 'var(--cat-object)', bg: 'var(--cat-object-bg)' },
+ plot: { color: 'var(--cat-plot)', bg: 'var(--cat-plot-bg)' },
+ rules: { color: 'var(--cat-rules)', bg: 'var(--cat-rules-bg)' },
+ custom: { color: 'var(--cat-custom)', bg: 'var(--cat-custom-bg)' },
+};
+
+// ==================== 通用字段组件 ====================
+
+function TextField({ label, value, onChange, placeholder, multiline = false, rows = 3, aiBtn = false }) {
+ const { t } = useI18n();
+ return (
+
+
+ {label}
+ {aiBtn && (
+ ✦
+ )}
+
+ {multiline ? (
+
+ );
+}
+
+function ButtonGroup({ label, value, options, onChange }) {
+ return (
+
+
{label}
+
+ {options.map(opt => (
+ onChange(opt.value)}
+ style={{
+ padding: '5px 12px', borderRadius: 16, fontSize: 12, border: '1px solid var(--border-light)',
+ background: value === opt.value ? 'var(--accent)' : 'transparent',
+ color: value === opt.value ? 'var(--text-inverse)' : 'var(--text-secondary)',
+ cursor: 'pointer', transition: 'all 0.15s', fontFamily: 'var(--font-ui)',
+ }}
+ >
+ {opt.label}
+
+ ))}
+
+
+ );
+}
+
+// ==================== 字段分组折叠 ====================
+
+function FieldGroup({ title, icon, children, defaultCollapsed = false }) {
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
+ return (
+
+
setCollapsed(!collapsed)}>
+
{icon && {icon} }{title}
+ ▼
+
+
+ {children}
+
+
+ );
+}
+
+// ==================== AI 生成的额外字段 ====================
+
+function ExtraFieldsSection({ content, knownFields, onUpdate }) {
+ const { t } = useI18n();
+ const extraKeys = Object.keys(content || {}).filter(k => !knownFields.includes(k) && content[k]);
+ if (extraKeys.length === 0) return null;
+ return (
+
+ {extraKeys.map(k => (
+ onUpdate(k, v)}
+ placeholder=""
+ multiline
+ />
+ ))}
+
+ );
+}
+
+// ==================== 角色卡片预览 ====================
+
+function CharacterCardPreview({ name, content }) {
+ const { t } = useI18n();
+ const c = content || {};
+ const catColor = CATEGORY_COLORS.character;
+ const roleLabels = {
+ protagonist: t('settingsEditor.roles.protagonist'),
+ antagonist: t('settingsEditor.roles.antagonist'),
+ supporting: t('settingsEditor.roles.supporting'),
+ minor: t('settingsEditor.roles.minor')
+ };
+ const roleLabel = roleLabels[c.role] || c.role || t('settingsEditor.charRole');
+
+ // 头像文字:取名字第一个字
+ const avatarChar = (name || t('settingsEditor.unnamedChar'))[0];
+
+ return (
+
+
+
+ {avatarChar}
+
+
+
{name || t('settingsEditor.unnamedChar')}
+
+ {roleLabel}
+
+
+
+
+ {c.gender && {t('settingsEditor.infoGender')} {c.gender} }
+ {c.age && {t('settingsEditor.infoAge')} {c.age} }
+ {c.personality && {t('settingsEditor.infoPersonality')} {c.personality.length > 20 ? c.personality.slice(0, 20) + '…' : c.personality} }
+
+
+ );
+}
+
+// ==================== 各分类编辑器 ====================
+
+function CharacterEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+
+
+
+ update('role', v)}
+ options={[
+ { value: 'protagonist', label: t('settingsEditor.roles.proLabel') },
+ { value: 'antagonist', label: t('settingsEditor.roles.antLabel') },
+ { value: 'supporting', label: t('settingsEditor.roles.supLabel') },
+ { value: 'minor', label: t('settingsEditor.roles.minLabel') },
+ ]}
+ />
+
+ update('gender', v)} placeholder={t('settingsEditor.charGenderPlaceholder')} />
+ update('age', v)} placeholder={t('settingsEditor.charAgePlaceholder')} />
+
+
+
+
+ update('appearance', v)} placeholder={t('settingsEditor.charAppearancePlaceholder')} multiline aiBtn />
+ update('personality', v)} placeholder={t('settingsEditor.charPersonalityPlaceholder')} multiline aiBtn />
+ update('speechStyle', v)} placeholder={t('settingsEditor.charSpeechStylePlaceholder')} multiline aiBtn />
+
+
+
+ update('background', v)} placeholder={t('settingsEditor.charBackgroundPlaceholder')} multiline rows={4} aiBtn />
+ update('motivation', v)} placeholder={t('settingsEditor.charMotivationPlaceholder')} multiline aiBtn />
+ update('arc', v)} placeholder={t('settingsEditor.charArcPlaceholder')} multiline aiBtn />
+
+
+
+ update('skills', v)} placeholder={t('settingsEditor.charSkillsPlaceholder')} multiline aiBtn />
+ update('relationships', v)} placeholder={t('settingsEditor.charRelationshipsPlaceholder')} multiline aiBtn />
+
+
+
+ update('notes', v)} placeholder={t('settingsEditor.charNotesPlaceholder')} multiline />
+
+
+
+
+ );
+}
+
+function LocationEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+
+ update('description', v)} placeholder={t('settingsEditor.locDescriptionPlaceholder')} multiline rows={4} aiBtn />
+ update('slugline', v)} placeholder={t('settingsEditor.locSluglinePlaceholder')} />
+
+
+
+ update('sensoryVisual', v)} placeholder={t('settingsEditor.locVisualPlaceholder')} multiline aiBtn />
+ update('sensoryAudio', v)} placeholder={t('settingsEditor.locAudioPlaceholder')} multiline aiBtn />
+ update('sensorySmell', v)} placeholder={t('settingsEditor.locSmellPlaceholder')} multiline aiBtn />
+
+
+
+ update('mood', v)} placeholder={t('settingsEditor.locMoodPlaceholder')} />
+ update('dangerLevel', v)}
+ options={[
+ { value: 'safe', label: t('settingsEditor.dangerSafe') },
+ { value: 'caution', label: t('settingsEditor.dangerCaution') },
+ { value: 'danger', label: t('settingsEditor.dangerHigh') },
+ ]}
+ />
+
+
+
+
+ );
+}
+
+function ObjectEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+
+ update('description', v)} placeholder={t('settingsEditor.objDescriptionPlaceholder')} multiline rows={4} aiBtn />
+
+ update('objectType', v)} placeholder={t('settingsEditor.objTypePlaceholder')} />
+ update('rank', v)} placeholder={t('settingsEditor.objRankPlaceholder')} />
+
+
+
+
+ update('currentHolder', v)} placeholder={t('settingsEditor.objHolderPlaceholder')} />
+ update('numericStats', v)} placeholder={t('settingsEditor.objStatsPlaceholder')} multiline />
+ update('symbolism', v)} placeholder={t('settingsEditor.objSymbolismPlaceholder')} multiline aiBtn />
+
+
+
+
+ );
+}
+
+function WorldEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+ update('description', v)} placeholder={t('settingsEditor.worldDescriptionPlaceholder')} multiline rows={6} aiBtn />
+ update('notes', v)} placeholder={t('settingsEditor.worldNotesPlaceholder')} multiline />
+
+
+ );
+}
+
+function PlotEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+ update('status', v)}
+ options={[
+ { value: 'planned', label: t('settingsEditor.statusPlanned') },
+ { value: 'writing', label: t('settingsEditor.statusWriting') },
+ { value: 'done', label: t('settingsEditor.statusDone') },
+ ]}
+ />
+ update('description', v)} placeholder={t('settingsEditor.plotDescriptionPlaceholder')} multiline rows={6} aiBtn />
+ update('notes', v)} placeholder={t('settingsEditor.plotNotesPlaceholder')} multiline />
+
+
+ );
+}
+
+function RulesEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+ update('description', v)}
+ placeholder={t('settingsEditor.rulesDescriptionPlaceholder')} multiline rows={6} />
+
+
+ );
+}
+
+function GenericEditor({ node, onUpdate }) {
+ const { t } = useI18n();
+ const content = node.content || {};
+ const update = (field, value) => onUpdate(node.id, { content: { ...content, [field]: value } });
+
+ return (
+
+ update('description', v)} placeholder={t('settingsEditor.genericDescriptionPlaceholder')} multiline rows={6} />
+ update('notes', v)} placeholder={t('settingsEditor.genericNotesPlaceholder')} multiline />
+
+
+ );
+}
+
+// ==================== 面包屑导航 ====================
+
+function Breadcrumb({ node, allNodes, onSelect }) {
+ const path = [];
+ let current = node;
+ while (current) {
+ path.unshift(current);
+ current = current.parentId ? allNodes.find(n => n.id === current.parentId) : null;
+ }
+
+ return (
+
+ {path.map((p, i) => (
+
+ {i > 0 && / }
+ onSelect(p.id)}
+ style={{ cursor: 'pointer', color: i === path.length - 1 ? 'var(--text-primary)' : 'var(--text-muted)', fontWeight: i === path.length - 1 ? 600 : 400, transition: 'color 0.15s' }}
+ onMouseEnter={e => e.target.style.color = 'var(--accent)'}
+ onMouseLeave={e => e.target.style.color = i === path.length - 1 ? 'var(--text-primary)' : 'var(--text-muted)'}
+ >
+ {p.icon} {p.name}
+
+
+ ))}
+
+ );
+}
+
+// ==================== 文件夹信息 ====================
+
+function FolderInfo({ node, nodes, onAdd }) {
+ const { t } = useI18n();
+ const catColor = CATEGORY_COLORS[node.category] || CATEGORY_COLORS.custom;
+ const children = nodes.filter(n => n.parentId === node.id);
+ const folders = children.filter(n => n.type === 'folder');
+ const items = children.filter(n => n.type === 'item');
+
+ return (
+
+
+
{node.icon || '📁'}
+
{node.name}
+
+ {folders.length > 0 && `${folders.length} 个子文件夹 · `}
+ {items.length} 个设定项
+
+
+
+ {children.length === 0 && (
+
+
📝
+
{t('settingsEditor.emptyTitle')}
+
{t('settingsEditor.emptyDesc')}
+
+ )}
+
+
onAdd(node.id, node.category)}
+ >
+ {t('settingsEditor.addBtn')}
+
+
+ );
+}
+
+// ==================== 空状态 ====================
+
+function EmptyState() {
+ const { t } = useI18n();
+ return (
+
+
🎯
+
{t('settingsEditor.selectTitle')}
+
{t('settingsEditor.selectDesc')}
+
+ );
+}
+
+// ==================== 主组件 ====================
+
+export default function SettingsItemEditor({ selectedNode, allNodes, onUpdate, onSelect, onAdd }) {
+ if (!selectedNode) return ;
+
+ // 文件夹 → 显示文件夹信息
+ if (selectedNode.type === 'folder' || selectedNode.type === 'special') {
+ return (
+
+
+
+
+ );
+ }
+
+ // item → 显示对应编辑器
+ const editorMap = {
+ character: CharacterEditor,
+ location: LocationEditor,
+ object: ObjectEditor,
+ world: WorldEditor,
+ plot: PlotEditor,
+ rules: RulesEditor,
+ custom: GenericEditor,
+ };
+ const EditorComponent = editorMap[selectedNode.category] || GenericEditor;
+
+ return (
+
+
+
+
+ );
+}
diff --git a/app/components/SettingsPanel.js b/app/components/SettingsPanel.js
new file mode 100644
index 0000000..5e2274e
--- /dev/null
+++ b/app/components/SettingsPanel.js
@@ -0,0 +1,752 @@
+'use client';
+
+import { useState, useEffect, useMemo } from 'react';
+import { useAppStore } from '../store/useAppStore';
+import {
+ getProjectSettings,
+ saveProjectSettings,
+ getSettingsNodes,
+ addSettingsNode,
+ updateSettingsNode,
+ deleteSettingsNode,
+ WRITING_MODES,
+ getWritingMode,
+ setWritingMode,
+ createWorkNode,
+ saveSettingsNodes,
+ getActiveWorkId,
+ setActiveWorkId,
+ getAllWorks,
+} from '../lib/settings';
+import SettingsTree from './SettingsTree';
+import { useI18n } from '../lib/useI18n';
+import SettingsItemEditor from './SettingsItemEditor';
+
+const CAT_STYLES = {
+ work: { color: 'var(--cat-work)', bg: 'var(--cat-work-bg)', icon: '📕' },
+ character: { color: 'var(--cat-character)', bg: 'var(--cat-character-bg)', icon: '👤' },
+ location: { color: 'var(--cat-location)', bg: 'var(--cat-location-bg)', icon: '🗺️' },
+ world: { color: 'var(--cat-world)', bg: 'var(--cat-world-bg)', icon: '🌍' },
+ object: { color: 'var(--cat-object)', bg: 'var(--cat-object-bg)', icon: '🔮' },
+ plot: { color: 'var(--cat-plot)', bg: 'var(--cat-plot-bg)', icon: '📋' },
+ rules: { color: 'var(--cat-rules)', bg: 'var(--cat-rules-bg)', icon: '📐' },
+};
+
+export default function SettingsPanel() {
+ const {
+ showSettings: open,
+ setShowSettings,
+ setWritingMode: setGlobalWritingMode,
+ incrementSettingsVersion
+ } = useAppStore();
+
+ const onClose = () => {
+ setShowSettings(false);
+ setGlobalWritingMode(getWritingMode());
+ incrementSettingsVersion();
+ };
+
+ const [settings, setSettings] = useState(null);
+ const [activeTab, setActiveTab] = useState('settings');
+ const [nodes, setNodes] = useState([]);
+ const [selectedNodeId, setSelectedNodeId] = useState(null);
+ const [writingMode, setWritingModeState] = useState('webnovel');
+ const [searchQuery, setSearchQuery] = useState('');
+ const [activeWorkId, setActiveWorkIdState] = useState(null);
+ const [showNewWorkInput, setShowNewWorkInput] = useState(false);
+ const [newWorkName, setNewWorkName] = useState('');
+ const { t } = useI18n();
+
+ const [expandedCategory, setExpandedCategory] = useState(null);
+
+ // 获取当前作品的节点
+ useEffect(() => {
+ if (open) {
+ setSettings(getProjectSettings());
+ const loadNodes = async () => {
+ const allNodes = await getSettingsNodes();
+ setNodes(allNodes);
+ setWritingModeState(getWritingMode());
+ setSearchQuery('');
+ // 初始化激活作品
+ let wid = getActiveWorkId();
+ if (!wid || !allNodes.find(n => n.id === wid)) {
+ const firstWork = allNodes.find(n => n.type === 'work');
+ wid = firstWork?.id || null;
+ if (wid) setActiveWorkId(wid);
+ }
+ setActiveWorkIdState(wid);
+ };
+ loadNodes();
+ }
+ }, [open]);
+
+ // 所有作品列表
+ const works = useMemo(() => getAllWorks(nodes), [nodes]);
+
+ // 当前作品下的节点
+ const visibleNodes = useMemo(() => {
+ if (!activeWorkId) return nodes;
+ // 递归收集当前作品的所有后代 id
+ const workDescendants = new Set();
+ const collectDescendants = (parentId) => {
+ nodes.filter(n => n.parentId === parentId).forEach(n => {
+ workDescendants.add(n.id);
+ collectDescendants(n.id);
+ });
+ };
+ workDescendants.add(activeWorkId);
+ collectDescendants(activeWorkId);
+ return nodes.filter(n => workDescendants.has(n.id));
+ }, [nodes, activeWorkId]);
+
+ const stats = useMemo(() => {
+ const items = visibleNodes.filter(n => n.type === 'item');
+ return Object.entries(CAT_STYLES).filter(([cat]) => cat !== 'work').map(([cat, style]) => ({
+ category: cat,
+ count: items.filter(n => n.category === cat).length,
+ label: t(`settings.categories.${cat}`),
+ ...style,
+ }));
+ }, [visibleNodes, t]);
+
+ // 作品管理
+ const handleSwitchWork = (workId) => {
+ setActiveWorkIdState(workId);
+ setActiveWorkId(workId);
+ setSelectedNodeId(null);
+ };
+
+ const handleCreateWork = async () => {
+ const name = newWorkName.trim();
+ if (!name) return;
+ const { workNode, subNodes } = createWorkNode(name);
+ const updatedNodes = [...nodes, workNode, ...subNodes];
+ await saveSettingsNodes(updatedNodes);
+ setNodes(updatedNodes);
+ setActiveWorkIdState(workNode.id);
+ setActiveWorkId(workNode.id);
+ setNewWorkName('');
+ setShowNewWorkInput(false);
+ setSelectedNodeId(null);
+ };
+
+ const handleDeleteWork = async (workId) => {
+ const work = nodes.find(n => n.id === workId);
+ if (!work) return;
+ if (works.length <= 1) { alert(t('settings.deleteWorkAlert')); return; }
+ if (!confirm(t('settings.deleteWorkPrompt').replace('{name}', work.name))) return;
+ // 递归删除作品及其所有后代
+ const toDelete = new Set();
+ const collect = (pid) => { toDelete.add(pid); nodes.filter(n => n.parentId === pid).forEach(n => collect(n.id)); };
+ collect(workId);
+ const updatedNodes = nodes.filter(n => !toDelete.has(n.id));
+ await saveSettingsNodes(updatedNodes);
+ setNodes(updatedNodes);
+ // 切换到第一个存活的作品
+ const nextWork = updatedNodes.find(n => n.type === 'work');
+ if (nextWork) {
+ setActiveWorkIdState(nextWork.id);
+ setActiveWorkId(nextWork.id);
+ }
+ setSelectedNodeId(null);
+ };
+
+ if (!open || !settings) return null;
+
+ const handleSettingsSave = (section, data) => {
+ const newSettings = { ...settings, [section]: data };
+ setSettings(newSettings);
+ saveProjectSettings(newSettings);
+ };
+
+ // 节点操作
+ const handleAddNode = async (parentId, category) => {
+ let cat = category || 'custom';
+ if (parentId && !category) {
+ const parent = nodes.find(n => n.id === parentId);
+ if (parent) cat = parent.category;
+ }
+ const newNode = await addSettingsNode({
+ name: t('settings.newItem'),
+ type: 'item',
+ category: cat,
+ parentId,
+ enabled: true,
+ });
+ setNodes(await getSettingsNodes());
+ setSelectedNodeId(newNode.id);
+ };
+
+ const handleDeleteNode = async (id) => {
+ const node = nodes.find(n => n.id === id);
+ if (!node) return;
+ if (!confirm(t('settings.deleteNodePrompt').replace('{name}', node.name))) return;
+ await deleteSettingsNode(id);
+ setNodes(await getSettingsNodes());
+ if (selectedNodeId === id) setSelectedNodeId(null);
+ };
+
+ const handleRenameNode = async (id, newName) => {
+ await updateSettingsNode(id, { name: newName });
+ setNodes(await getSettingsNodes());
+ };
+
+ const handleToggleEnabled = async (id) => {
+ const node = nodes.find(n => n.id === id);
+ if (!node) return;
+ const newEnabled = node.enabled === false ? true : false;
+ await updateSettingsNode(id, { enabled: newEnabled });
+ setNodes(prev => prev.map(n => n.id === id ? { ...n, enabled: newEnabled } : n));
+ };
+
+ const handleUpdateNode = async (id, updates) => {
+ await updateSettingsNode(id, updates);
+ setNodes(prev => prev.map(n => n.id === id ? { ...n, ...updates, updatedAt: new Date().toISOString() } : n));
+ };
+
+ const selectedNode = visibleNodes.find(n => n.id === selectedNodeId);
+ const showBookInfo = selectedNode?.type === 'special' && selectedNode?.category === 'bookInfo';
+
+ const tabs = [
+ { key: 'settings', label: t('settings.tabSettings') },
+ { key: 'apiConfig', label: t('settings.tabApi') },
+ { key: 'preferences', label: t('settings.tabPreferences') },
+ ];
+
+ return (
+
+
e.stopPropagation()}>
+ {/* 头部 */}
+
+
+ ⚙️ {t('settings.title')}
+ — {t('settings.subtitle')}
+
+ ✕
+
+
+ {/* Tab 导航 */}
+
+ {tabs.map(tab => (
+ setActiveTab(tab.key)}
+ >
+ {tab.label}
+
+ ))}
+
+
+ {/* 内容区 */}
+ {activeTab === 'apiConfig' ? (
+
+
handleSettingsSave('apiConfig', data)} />
+
+ ) : activeTab === 'preferences' ? (
+
+ ) : (
+
+ {/* 写作模式选择器 */}
+
+ {Object.values(WRITING_MODES).map(m => (
+
{ setWritingModeState(m.key); setWritingMode(m.key); }}
+ >
+ {m.icon}
+ {m.label}
+ {m.desc}
+
+ ))}
+
+
+ {/* 作品切换器 */}
+
+
{t('settings.workLabel')}
+
+ {works.map(w => (
+
+ handleSwitchWork(w.id)}
+ >
+ {w.name}
+
+ {works.length > 1 && (
+ handleDeleteWork(w.id)}
+ title={t('common.delete') + ' ' + w.name}
+ >✕
+ )}
+
+ ))}
+ {showNewWorkInput ? (
+
+ setNewWorkName(e.target.value)}
+ onKeyDown={e => { if (e.key === 'Enter') handleCreateWork(); if (e.key === 'Escape') setShowNewWorkInput(false); }}
+ placeholder={t('settings.workNamePlaceholder')}
+ autoFocus
+ />
+ {t('settings.confirmBtn')}
+ setShowNewWorkInput(false)}>{t('common.cancel')}
+
+ ) : (
+
{ setNewWorkName(''); setShowNewWorkInput(true); }}
+ >{t('settings.newWork')}
+ )}
+
+
+
+ {/* 统计栏 */}
+
+ {stats.map(s => (
+
setExpandedCategory(s.category)}
+ >
+ {s.icon}
+ {s.count}
+ {s.label}
+
+ ))}
+
+
+
+ {/* 左侧:搜索 + 树形导航 */}
+
+ {/* 搜索框 */}
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+ {/* 树 */}
+
+ setExpandedCategory(null)}
+ />
+
+
+
+ {/* 右侧:编辑器 */}
+
+ {showBookInfo ? (
+
+ handleSettingsSave('bookInfo', data)} />
+
+ ) : (
+
+ )}
+
+
+
+ )}
+
+
+ );
+}
+
+// ==================== API 配置 ====================
+
+const PROVIDERS = [
+ { key: 'zhipu', label: '智谱AI (GLM)', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', models: ['glm-4-flash', 'glm-4-plus', 'glm-4-long', 'glm-4'] },
+ { key: 'deepseek', label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', models: ['deepseek-chat', 'deepseek-reasoner'] },
+ { key: 'openai', label: 'OpenAI', baseUrl: 'https://api.openai.com/v1', models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'] },
+ { key: 'gemini', label: 'Gemini (OpenAI兼容)', baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', models: ['gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-flash', 'gemini-1.5-pro'] },
+ { key: 'gemini-native', label: 'Gemini(原生格式)', baseUrl: 'https://generativelanguage.googleapis.com/v1beta', models: ['gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-flash', 'gemini-1.5-pro'] },
+ { key: 'siliconflow', label: 'SiliconFlow (硅基流动)', baseUrl: 'https://api.siliconflow.cn/v1', models: ['deepseek-ai/DeepSeek-V3', 'Qwen/Qwen2.5-72B-Instruct', 'THUDM/glm-4-9b-chat'] },
+ { key: 'moonshot', label: 'Moonshot (Kimi)', baseUrl: 'https://api.moonshot.cn/v1', models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'] },
+ { key: 'custom', label: '自定义 (OpenAI兼容)', baseUrl: '', models: [] },
+];
+
+function PreferencesForm() {
+ const { language, setLanguage, visualTheme, setVisualTheme } = useAppStore();
+ const { t } = useI18n();
+
+ return (
+
+
+ {t('preferences.intro')}
+
+
+
+
{t('preferences.langLabel')}
+
+ {['zh', 'en', 'ru'].map(lang => (
+ setLanguage(lang)}
+ >
+ {lang === 'zh' ? '🇨🇳 简体中文' : lang === 'en' ? '🇬🇧 English' : '🇷🇺 Русский'}
+
+ ))}
+
+
+
+
+
{t('preferences.themeLabel')}
+
+ {[{ id: 'warm', label: t('preferences.themeWarm'), desc: t('preferences.themeWarmDesc') }, { id: 'modern', label: t('preferences.themeModern'), desc: t('preferences.themeModernDesc') }].map(theme => (
+
{ if (visualTheme !== theme.id) e.currentTarget.style.transform = 'translateY(-2px)' }}
+ onMouseLeave={e => { if (visualTheme !== theme.id) e.currentTarget.style.transform = 'none' }}
+ onClick={() => {
+ setVisualTheme(theme.id);
+ document.documentElement.setAttribute('data-visual', theme.id);
+ }}
+ >
+
+ {theme.label}
+
+ {theme.desc}
+
+ ))}
+
+
+
+ );
+}
+
+function ApiConfigForm({ data, onChange }) {
+ const update = (field, value) => onChange({ ...data, [field]: value });
+ const [testStatus, setTestStatus] = useState(null);
+ const [fetchedModels, setFetchedModels] = useState(null);
+ const [fetchedEmbedModels, setFetchedEmbedModels] = useState(null);
+ const [savedProfiles, setSavedProfiles] = useState([]);
+ const [profileName, setProfileName] = useState('');
+ const [showSaveInput, setShowSaveInput] = useState(false);
+ const { t } = useI18n();
+
+ useEffect(() => {
+ try {
+ const saved = localStorage.getItem('author-api-profiles');
+ if (saved) setSavedProfiles(JSON.parse(saved));
+ } catch { /* ignore */ }
+ }, []);
+
+ const persistProfiles = (profiles) => {
+ setSavedProfiles(profiles);
+ localStorage.setItem('author-api-profiles', JSON.stringify(profiles));
+ };
+
+ const handleSaveProfile = () => {
+ const name = profileName.trim();
+ if (!name) return;
+ const profile = { id: Date.now().toString(36), name, config: { ...data }, createdAt: new Date().toLocaleString('zh-CN') };
+ const updated = savedProfiles.filter(p => p.name !== name);
+ updated.unshift(profile);
+ persistProfiles(updated);
+ setProfileName('');
+ setShowSaveInput(false);
+ };
+
+ const handleLoadProfile = (profile) => { onChange({ ...profile.config }); setTestStatus(null); setFetchedModels(null); };
+ const handleDeleteProfile = (id) => { persistProfiles(savedProfiles.filter(p => p.id !== id)); };
+
+ const handleProviderChange = (providerKey) => {
+ const provider = PROVIDERS.find(p => p.key === providerKey);
+ if (provider) {
+ onChange({ ...data, provider: providerKey, baseUrl: providerKey === 'custom' ? '' : (provider.baseUrl || data.baseUrl), model: providerKey === 'custom' ? '' : (provider.models[0] || data.model) });
+ }
+ setTestStatus(null);
+ setFetchedModels(null);
+ setFetchedEmbedModels(null);
+ };
+
+ const handleTestConnection = async () => {
+ setTestStatus('loading');
+ try {
+ const res = await fetch('/api/ai/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiConfig: data }) });
+ setTestStatus(await res.json());
+ } catch { setTestStatus({ success: false, error: t('apiConfig.networkError') }); }
+ };
+
+ const handleFetchModels = async () => {
+ setFetchedModels('loading');
+ try {
+ const res = await fetch('/api/ai/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: data.apiKey, baseUrl: data.baseUrl, provider: data.provider }) });
+ const result = await res.json();
+ if (result.error) { setFetchedModels(null); setTestStatus({ success: false, error: result.error }); }
+ else { setFetchedModels(result.models || []); }
+ } catch { setFetchedModels(null); setTestStatus({ success: false, error: t('apiConfig.fetchModelsFailed') }); }
+ };
+
+ const handleFetchEmbedModels = async () => {
+ setFetchedEmbedModels('loading');
+ try {
+ const embedKey = data.embedApiKey || data.apiKey;
+ const embedBase = data.embedBaseUrl || data.baseUrl;
+ const res = await fetch('/api/ai/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: embedKey, baseUrl: embedBase, provider: data.embedProvider }) });
+ const result = await res.json();
+ if (result.error) { setFetchedEmbedModels(null); setTestStatus({ success: false, error: t('apiConfig.embedApiPrefix') + result.error }); }
+ else { setFetchedEmbedModels(result.models || []); }
+ } catch { setFetchedEmbedModels(null); setTestStatus({ success: false, error: t('apiConfig.fetchEmbedModelsFailed') }); }
+ };
+
+ const currentProvider = PROVIDERS.find(p => p.key === data.provider) || PROVIDERS[7];
+ const isCustom = data.provider === 'custom';
+
+ return (
+
+ {savedProfiles.length > 0 && (
+
+
{t('apiConfig.savedProfiles')}
+
+ {savedProfiles.map(p => (
+
+ handleLoadProfile(p)} title={`${p.config.provider} | ${p.config.model}`}>{p.name}
+ handleDeleteProfile(p.id)} title={t('apiConfig.deleteProfile')}>✕
+
+ ))}
+
+
+ )}
+
+
{t('apiConfig.intro')}
+
+ {/* 供应商选择 */}
+
+
{t('apiConfig.provider')}
+
+ {PROVIDERS.map(p => (
+ handleProviderChange(p.key)}>{p.label}
+ ))}
+
+ {data.provider === 'gemini-native' && (
+
+ {t('apiConfig.geminiNativeHint')}
+
+ )}
+
+
+
update('apiKey', v)} placeholder={t('apiConfig.apiKeyPlaceholder')} />
+ {data.apiKey && {t('apiConfig.apiKeyConfigured')}
}
+
+ update('baseUrl', v)} placeholder={t('apiConfig.apiAddressPlaceholder')} />
+
+ {/* 模型选择 */}
+ {currentProvider.models.length > 0 && !isCustom ? (
+
+
+ {t('apiConfig.model')}
+ {data.apiKey && (
+
+ {fetchedModels === 'loading' ? t('apiConfig.fetching') : t('apiConfig.fetchModels')}
+
+ )}
+
+
+ {(Array.isArray(fetchedModels) ? fetchedModels.map(m => m.id) : currentProvider.models).map(m => (
+ update('model', m)}>{m}
+ ))}
+
+ {Array.isArray(fetchedModels) &&
{t('apiConfig.fetchedCount').replace('{count}', fetchedModels.length)}
}
+
+ ) : (
+
+
+ {t('apiConfig.modelName')}
+ {data.apiKey && data.baseUrl && (
+
+ {fetchedModels === 'loading' ? t('apiConfig.fetching') : t('apiConfig.fetchModels')}
+
+ )}
+
+
update('model', e.target.value)} placeholder="例如:gpt-4o-mini" />
+ {Array.isArray(fetchedModels) && fetchedModels.length > 0 && (
+ <>
+
{t('apiConfig.fetchedCountClick').replace('{count}', fetchedModels.length)}
+
+ {fetchedModels.map(m => (
+ update('model', m.id)}>{m.id}
+ ))}
+
+ >
+ )}
+
+ )}
+
+ {/* 独立 Embedding 配置 */}
+
+
+ update('useCustomEmbed', e.target.checked)}
+ style={{ margin: 0 }}
+ />
+ {t('apiConfig.embedTitle')}
+
+
+ {t('apiConfig.embedDesc')}
+
+
+
+ {data.useCustomEmbed && (
+
+
+
{t('apiConfig.embedProvider')}
+
+ {PROVIDERS.filter(p => !['deepseek', 'moonshot', 'siliconflow', 'openai'].includes(p.key)).map(p => (
+ onChange({ ...data, embedProvider: p.key, embedBaseUrl: p.key === 'custom' ? '' : (p.baseUrl || data.embedBaseUrl), embedModel: p.key === 'custom' ? '' : (p.key === 'zhipu' ? 'embedding-3' : 'text-embedding-v3-small') })}>{p.label}
+ ))}
+
+
+
update('embedApiKey', v)} placeholder={t('apiConfig.embedApiKeyPlaceholder')} />
+ update('embedBaseUrl', v)} placeholder="https://api.example.com/v1" />
+
+
+ {t('apiConfig.embedModel')}
+ {data.embedApiKey || data.apiKey ? (
+
+ {fetchedEmbedModels === 'loading' ? t('apiConfig.fetching') : t('apiConfig.fetchEmbedModels')}
+
+ ) : null}
+
+
update('embedModel', e.target.value)} placeholder="例如:text-embedding-v3-small" />
+ {Array.isArray(fetchedEmbedModels) && fetchedEmbedModels.length > 0 && (
+ <>
+
{t('apiConfig.fetchedCountClick').replace('{count}', fetchedEmbedModels.length)}
+
+ {fetchedEmbedModels.map(m => (
+ update('embedModel', m.id)}>{m.id}
+ ))}
+
+ >
+ )}
+
+
+ )}
+
+ {/* 测试连接 */}
+ {data.apiKey && (
+
+
+ {testStatus === 'loading' ? t('apiConfig.testLoading') : t('apiConfig.testBtn')}
+
+ {testStatus && testStatus !== 'loading' && (
+
+ {testStatus.success ? <>{testStatus.message} {testStatus.reply && {t('apiConfig.testReply')}{testStatus.reply} }> : <>❌ {testStatus.error}>}
+
+ )}
+
+ )}
+
+ {/* 保存配置 */}
+ {data.apiKey && (
+
+ {!showSaveInput ? (
+
{ const pl = PROVIDERS.find(p => p.key === data.provider)?.label || data.provider; setProfileName(`${pl} - ${data.model || t('common.confirm')}`); setShowSaveInput(true); }}>
+ {t('apiConfig.saveProfileBtn')}
+
+ ) : (
+
+ setProfileName(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSaveProfile()} placeholder={t('apiConfig.saveProfilePlaceholder')} autoFocus />
+ {t('apiConfig.saveBtn')}
+ setShowSaveInput(false)}>{t('common.cancel')}
+
+ )}
+
+ )}
+
+
+ {t('apiConfig.howToGetKey')}
+ • {t('apiConfig.keyGuide').split('\n').map((line, i) => {line.replace(/^• /, '')} )}
+
+
+ );
+}
+
+// ==================== 表单组件 ====================
+
+function FieldInput({ label, value, onChange, placeholder, multiline, rows }) {
+ const Component = multiline ? 'textarea' : 'input';
+ return (
+
+ {label}
+ onChange(e.target.value)} placeholder={placeholder} rows={rows || 3} />
+
+ );
+}
+
+function BookInfoForm({ data, onChange }) {
+ const update = (field, value) => onChange({ ...data, [field]: value });
+ const { t } = useI18n();
+ return (
+
+
{t('bookInfo.intro')}
+
update('title', v)} placeholder={t('bookInfo.titlePlaceholder')} />
+ update('genre', v)} placeholder={t('bookInfo.genrePlaceholder')} />
+ update('synopsis', v)} placeholder={t('bookInfo.synopsisPlaceholder')} multiline rows={3} />
+ update('style', v)} placeholder={t('bookInfo.stylePlaceholder')} />
+ update('tone', v)} placeholder={t('bookInfo.tonePlaceholder')} />
+ update('pov', v)} placeholder={t('bookInfo.povPlaceholder')} />
+ update('targetAudience', v)} placeholder={t('bookInfo.targetAudiencePlaceholder')} />
+
+ );
+}
diff --git a/app/components/SettingsTree.js b/app/components/SettingsTree.js
new file mode 100644
index 0000000..aa35342
--- /dev/null
+++ b/app/components/SettingsTree.js
@@ -0,0 +1,267 @@
+'use client';
+
+import { useState, useMemo, useEffect, useRef } from 'react';
+
+import { useI18n } from '../lib/useI18n';
+
+// 分类的颜色和标识
+const CATEGORY_STYLES = {
+ work: { color: 'var(--cat-work)', bg: 'var(--cat-work-bg)' },
+ bookInfo: { color: 'var(--cat-bookinfo)', bg: 'var(--cat-bookinfo-bg)' },
+ character: { color: 'var(--cat-character)', bg: 'var(--cat-character-bg)' },
+ location: { color: 'var(--cat-location)', bg: 'var(--cat-location-bg)' },
+ world: { color: 'var(--cat-world)', bg: 'var(--cat-world-bg)' },
+ object: { color: 'var(--cat-object)', bg: 'var(--cat-object-bg)' },
+ plot: { color: 'var(--cat-plot)', bg: 'var(--cat-plot-bg)' },
+ rules: { color: 'var(--cat-rules)', bg: 'var(--cat-rules-bg)' },
+ custom: { color: 'var(--cat-custom)', bg: 'var(--cat-custom-bg)' },
+};
+
+function getCategoryStyle(category) {
+ return CATEGORY_STYLES[category] || CATEGORY_STYLES.custom;
+}
+
+// 单个树节点
+function TreeNode({ node, nodes, selectedId, onSelect, onAdd, onDelete, onRename, onToggleEnabled, collapsedIds, onToggleCollapse, level = 0 }) {
+ const { t } = useI18n();
+ const [isRenaming, setIsRenaming] = useState(false);
+ const [renameValue, setRenameValue] = useState('');
+ const children = nodes.filter(n => n.parentId === node.id);
+ const isFolder = node.type === 'folder' || node.type === 'special' || node.type === 'work';
+ const isWork = node.type === 'work';
+ const isRoot = node.parentId === null;
+ const isCollapsed = collapsedIds.has(node.id);
+ const isSelected = selectedId === node.id;
+ const isDisabled = node.enabled === false;
+ const style = getCategoryStyle(node.category);
+
+ const nodeRef = useRef(null);
+
+ // 计算子项数目(递归)
+ const descendantCount = useMemo(() => {
+ if (!isFolder) return 0;
+ let count = 0;
+ const countChildren = (parentId) => {
+ nodes.filter(n => n.parentId === parentId).forEach(child => {
+ if (child.type === 'item') count++;
+ else countChildren(child.id);
+ });
+ };
+ countChildren(node.id);
+ return count;
+ }, [node.id, nodes, isFolder]);
+
+ const handleRename = () => {
+ if (renameValue.trim()) {
+ onRename(node.id, renameValue.trim());
+ }
+ setIsRenaming(false);
+ };
+
+ // 如果是被点击并展开的节点,自动滚入视图
+ useEffect(() => {
+ if (isSelected && isFolder && nodeRef.current) {
+ // 给一点延迟让折叠动画/渲染完成
+ setTimeout(() => {
+ nodeRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
+ }, 100);
+ }
+ }, [isSelected, isFolder]);
+
+ return (
+ 0 ? 12 : 0 }} ref={nodeRef}>
+
onSelect(node.id)}
+ style={isRoot ? { borderLeft: `3px solid ${style.color}`, marginBottom: 2 } : {}}
+ title={isDisabled ? t('settingsTree.disabledHint') : ''}
+ >
+ {/* 折叠箭头 */}
+ {isFolder && (
+ { e.stopPropagation(); onToggleCollapse(node.id); }}
+ style={{ cursor: 'pointer', color: 'var(--text-muted)', fontSize: 10 }}
+ >
+ {isCollapsed ? '▶' : '▼'}
+
+ )}
+
+ {/* 图标 */}
+ {node.icon || (isFolder ? '📁' : '📄')}
+
+ {/* 名称 */}
+ {isRenaming ? (
+ setRenameValue(e.target.value)}
+ onBlur={handleRename}
+ onKeyDown={e => { if (e.key === 'Enter') handleRename(); if (e.key === 'Escape') setIsRenaming(false); }}
+ autoFocus
+ onClick={e => e.stopPropagation()}
+ style={{ border: '1px solid var(--accent)', borderRadius: 3, padding: '1px 4px', fontSize: 13, background: 'var(--bg-primary)', color: 'var(--text-primary)', outline: 'none' }}
+ />
+ ) : (
+ {node.name}
+ )}
+
+ {/* 启用/禁用开关(仅item节点) */}
+ {!isFolder && !isRoot && (
+ { e.stopPropagation(); onToggleEnabled(node.id); }}
+ title={isDisabled ? t('settingsTree.enableHint') : t('settingsTree.disableHint')}
+ >
+ {isDisabled ? '🚫' : '👁'}
+
+ )}
+
+ {/* 子项计数徽标(folder节点) */}
+ {isFolder && descendantCount > 0 && (
+
+ {descendantCount}
+
+ )}
+
+ {/* 操作按钮 */}
+
+ {/* 添子项 */}
+ {isFolder && (
+ { e.stopPropagation(); onAdd(node.id, node.category); }} title={t('settingsTree.add')}>+
+ )}
+ {/* 重命名 */}
+ {!isRoot && (
+ { e.stopPropagation(); setRenameValue(node.name); setIsRenaming(true); }} title={t('common.rename')}>✏
+ )}
+ {/* 删除 */}
+ {!isRoot && (
+ { e.stopPropagation(); onDelete(node.id); }} title={t('common.delete')}>✕
+ )}
+
+
+
+ {/* 子节点 */}
+ {isFolder && !isCollapsed && (
+
+ {children
+ .sort((a, b) => (a.type === 'folder' ? -1 : 1) - (b.type === 'folder' ? -1 : 1) || (a.sortOrder || 0) - (b.sortOrder || 0))
+ .map(child => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+// 设定树组件
+export default function SettingsTree({
+ nodes,
+ selectedId,
+ onSelect,
+ onAdd,
+ onDelete,
+ onRename,
+ onToggleEnabled,
+ searchQuery = '',
+ expandedCategory = null,
+ onExpandComplete,
+}) {
+ const [collapsedIds, setCollapsedIds] = useState(new Set());
+
+ const toggleCollapse = (id) => {
+ setCollapsedIds(prev => {
+ const next = new Set(prev);
+ if (next.has(id)) next.delete(id);
+ else next.add(id);
+ return next;
+ });
+ };
+
+ // 监听外部控制展开特定分类文件夹
+ useEffect(() => {
+ if (expandedCategory) {
+ const targetFolder = nodes.find(n => n.type === 'folder' && n.category === expandedCategory && n.parentId !== null);
+ if (targetFolder) {
+ // 确保父节点和该节点自己都没被折叠
+ setCollapsedIds(prev => {
+ const next = new Set(prev);
+ next.delete(targetFolder.id);
+ if (targetFolder.parentId) {
+ next.delete(targetFolder.parentId);
+ }
+ return next;
+ });
+ onSelect(targetFolder.id);
+ if (onExpandComplete) {
+ onExpandComplete();
+ }
+ }
+ }
+ }, [expandedCategory, nodes, onSelect, onExpandComplete]);
+
+ // 获取根节点
+ const rootNodes = nodes.filter(n => n.parentId === null);
+
+ // 搜索过滤
+ const filteredNodes = useMemo(() => {
+ if (!searchQuery.trim()) return nodes;
+ const q = searchQuery.toLowerCase();
+ // 找到匹配的节点和它们到根的路径
+ const matchIds = new Set();
+ nodes.forEach(n => {
+ if (n.name.toLowerCase().includes(q) ||
+ (n.content?.description || '').toLowerCase().includes(q) ||
+ (n.content?.personality || '').toLowerCase().includes(q) ||
+ (n.content?.background || '').toLowerCase().includes(q)) {
+ // 添加自己和所有祖先
+ let current = n;
+ while (current) {
+ matchIds.add(current.id);
+ current = current.parentId ? nodes.find(p => p.id === current.parentId) : null;
+ }
+ }
+ });
+ return nodes.filter(n => matchIds.has(n.id));
+ }, [nodes, searchQuery]);
+
+ const filteredRootNodes = rootNodes.filter(n => filteredNodes.some(fn => fn.id === n.id));
+
+ return (
+
+ {filteredRootNodes.map(root => (
+
+ ))}
+
+ );
+}
diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js
new file mode 100644
index 0000000..55cd9ae
--- /dev/null
+++ b/app/components/Sidebar.js
@@ -0,0 +1,321 @@
+'use client';
+
+import { useState, useCallback } from 'react';
+import { useAppStore } from '../store/useAppStore';
+import { useI18n } from '../lib/useI18n';
+import { createChapter, deleteChapter, updateChapter, exportToMarkdown, exportAllToMarkdown } from '../lib/storage';
+import { exportProject, importProject } from '../lib/project-io';
+import { WRITING_MODES } from '../lib/settings';
+
+export default function Sidebar() {
+ const {
+ chapters, addChapter, setChapters, updateChapter: updateChapterStore,
+ activeChapterId, setActiveChapterId,
+ sidebarOpen, setSidebarOpen,
+ theme, setTheme,
+ writingMode,
+ setShowSettings,
+ setShowSnapshots,
+ showToast
+ } = useAppStore();
+
+ const [showNewChapterModal, setShowNewChapterModal] = useState(false);
+ const [newChapterTitle, setNewChapterTitle] = useState('');
+ const [renameId, setRenameId] = useState(null);
+ const [renameTitle, setRenameTitle] = useState('');
+ const [contextMenu, setContextMenu] = useState(null);
+ const { t } = useI18n();
+
+ // 切换主题
+ const toggleTheme = useCallback(() => {
+ const next = theme === 'light' ? 'dark' : 'light';
+ setTheme(next);
+ document.documentElement.setAttribute('data-theme', next);
+ localStorage.setItem('author-theme', next);
+ }, [theme, setTheme]);
+
+ // 创建新章节
+ const handleCreateChapter = useCallback(() => {
+ const title = newChapterTitle.trim() || t('sidebar.defaultChapterTitle').replace('{num}', chapters.length + 1);
+ const ch = createChapter(title);
+ addChapter(ch);
+ setActiveChapterId(ch.id);
+ setShowNewChapterModal(false);
+ setNewChapterTitle('');
+ showToast(t('sidebar.chapterCreated').replace('{title}', title), 'success');
+ }, [newChapterTitle, chapters.length, showToast, addChapter, setActiveChapterId, t]);
+
+ // 删除章节
+ const handleDeleteChapter = useCallback((id) => {
+ if (chapters.length <= 1) {
+ showToast(t('sidebar.alertRetainOne'), 'error');
+ return;
+ }
+ const ch = chapters.find(c => c.id === id);
+ const remaining = deleteChapter(id);
+ setChapters(remaining);
+ if (activeChapterId === id) {
+ setActiveChapterId(remaining[0]?.id || null);
+ }
+ showToast(t('sidebar.chapterDeleted').replace('{title}', ch?.title), 'info');
+ setContextMenu(null);
+ }, [chapters, activeChapterId, showToast, setChapters, setActiveChapterId, t]);
+
+ // 重命名章节
+ const handleRename = useCallback((id) => {
+ const title = renameTitle.trim();
+ if (!title) return;
+ const updated = updateChapter(id, { title });
+ updateChapterStore(id, { title });
+ setRenameId(null);
+ setRenameTitle('');
+ }, [renameTitle, updateChapterStore]);
+
+ // 导出
+ const handleExport = useCallback((type) => {
+ if (type === 'current' && activeChapterId) {
+ const activeChapter = chapters.find(ch => ch.id === activeChapterId);
+ if (activeChapter) {
+ exportToMarkdown(activeChapter);
+ showToast(t('sidebar.exportedChapter'), 'success');
+ }
+ } else if (type === 'all') {
+ exportAllToMarkdown(chapters);
+ showToast(t('sidebar.exportedAll'), 'success');
+ }
+ }, [activeChapterId, chapters, showToast, t]);
+
+ const totalWords = chapters.reduce((sum, ch) => sum + (ch.wordCount || 0), 0);
+
+ return (
+ <>
+
+
+
+ A uthor
+
+
setSidebarOpen(false)} title={t('sidebar.collapseSidebar')}>
+ ✕
+
+
+
+
+ setShowNewChapterModal(true)}
+ >
+ {t('sidebar.newChapter')}
+
+
+
+
+
+ {t('sidebar.chapterList')} ({chapters.length})
+
+
+ {chapters.map(ch => (
+
setActiveChapterId(ch.id)}
+ onContextMenu={(e) => {
+ e.preventDefault();
+ setContextMenu({ id: ch.id, x: e.clientX, y: e.clientY });
+ }}
+ >
+ {renameId === ch.id ? (
+
setRenameTitle(e.target.value)}
+ onBlur={() => handleRename(ch.id)}
+ onKeyDown={e => e.key === 'Enter' && handleRename(ch.id)}
+ onClick={e => e.stopPropagation()}
+ autoFocus
+ />
+ ) : (
+ <>
+
{ch.title}
+
{ch.wordCount || 0}{t('sidebar.wordUnit')}
+
+ {
+ e.stopPropagation();
+ setRenameId(ch.id);
+ setRenameTitle(ch.title);
+ }}
+ title={t('common.rename')}
+ >
+ ✎
+
+ {
+ e.stopPropagation();
+ handleDeleteChapter(ch.id);
+ }}
+ title={t('common.delete')}
+ style={{ color: 'var(--error)' }}
+ >
+ ✕
+
+
+ >
+ )}
+
+ ))}
+
+
+
+
+ {/* 写作模式指示器 */}
+ {(() => {
+ const modeConfig = WRITING_MODES[writingMode];
+ return modeConfig ? (
+
setShowSettings(true)}
+ title={t('sidebar.clickToSwitchMode')}
+ >
+ {modeConfig.icon}
+ {t('sidebar.modeLabel').replace('{mode}', modeConfig.label)}
+
+ ) : null;
+ })()}
+
+ {t('sidebar.totalWords')}
+ {totalWords.toLocaleString()}
+
+
+ handleExport('current')}>
+ {t('sidebar.exportCurrent')}
+
+ handleExport('all')}>
+ {t('sidebar.exportAll')}
+
+ setShowSettings(true)} title={t('sidebar.tooltipSettings')}>
+ ⚙️
+
+
+ {theme === 'light' ? '🌙' : '☀️'}
+
+
+
+ setShowSnapshots(true)} title={t('sidebar.tooltipTimeMachine')}>
+ 🕒
+
+ { exportProject(); }}>
+ {t('sidebar.btnSave')}
+
+ { document.getElementById('project-import-input')?.click(); }}>
+ {t('sidebar.btnLoad')}
+
+ {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ const result = await importProject(file);
+ if (result.success) {
+ alert(result.message + '\n' + t('sidebar.importSuccess'));
+ window.location.reload();
+ } else {
+ alert(result.message);
+ }
+ e.target.value = '';
+ }}
+ />
+
+
+
+
+ {/* ===== 新建章节弹窗 ===== */}
+ {showNewChapterModal && (
+ setShowNewChapterModal(false)}>
+
e.stopPropagation()}>
+
{t('sidebar.newChapter')}
+
setNewChapterTitle(e.target.value)}
+ onKeyDown={e => e.key === 'Enter' && handleCreateChapter()}
+ autoFocus
+ />
+
+ setShowNewChapterModal(false)}>
+ {t('common.cancel')}
+
+
+ {t('common.confirm')}
+
+
+
+
+ )}
+
+ {/* ===== 右键菜单 ===== */}
+ {contextMenu && (
+ setContextMenu(null)}
+ >
+
+ {
+ setRenameId(contextMenu.id);
+ const ch = chapters.find(c => c.id === contextMenu.id);
+ setRenameTitle(ch?.title || '');
+ setContextMenu(null);
+ }}
+ >
+ {t('sidebar.contextRename')}
+
+ {
+ const ch = chapters.find(c => c.id === contextMenu.id);
+ if (ch) exportToMarkdown(ch);
+ setContextMenu(null);
+ }}
+ >
+ {t('sidebar.contextExport')}
+
+ handleDeleteChapter(contextMenu.id)}
+ >
+ {t('sidebar.contextDelete')}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/app/components/SnapshotManager.js b/app/components/SnapshotManager.js
new file mode 100644
index 0000000..8c681fb
--- /dev/null
+++ b/app/components/SnapshotManager.js
@@ -0,0 +1,226 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useAppStore } from '../store/useAppStore';
+import { useI18n } from '../lib/useI18n';
+import { getSnapshots, createSnapshot, restoreSnapshot, deleteSnapshot } from '../lib/snapshots';
+
+export default function SnapshotManager({ onRestored }) {
+ const { showSnapshots: open, setShowSnapshots } = useAppStore();
+ const onClose = () => setShowSnapshots(false);
+ const { t } = useI18n();
+
+ const [snapshots, setSnapshots] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [selectedId, setSelectedId] = useState(null);
+ const [isRestoring, setIsRestoring] = useState(false);
+ const [isCreating, setIsCreating] = useState(false);
+
+ const loadData = async () => {
+ setLoading(true);
+ const data = await getSnapshots();
+ setSnapshots(data);
+ if (data.length > 0 && !selectedId) {
+ setSelectedId(data[0].id);
+ }
+ setLoading(false);
+ };
+
+ useEffect(() => {
+ if (open) {
+ loadData();
+ }
+ }, [open]);
+
+ if (!open) return null;
+
+ const handleCreateManual = async () => {
+ const label = prompt(t('snapshot.promptLabel'), t('snapshot.promptDefault'));
+ if (!label) return;
+ setIsCreating(true);
+ try {
+ const snap = await createSnapshot(label, 'manual');
+ await loadData();
+ setSelectedId(snap.id);
+ } catch (e) {
+ alert(t('snapshot.createFailed') + e.message);
+ } finally {
+ setIsCreating(false);
+ }
+ };
+
+ const handleRestore = async () => {
+ if (!selectedId) return;
+ const snap = snapshots.find(s => s.id === selectedId);
+ if (!snap) return;
+
+ const confirmMsg = t('snapshot.confirmRestore')
+ .replace('{label}', snap.label)
+ .replace('{date}', new Date(snap.timestamp).toLocaleString());
+ if (!confirm(confirmMsg)) {
+ return;
+ }
+
+ setIsRestoring(true);
+ try {
+ await restoreSnapshot(snap.id);
+ alert(t('snapshot.restoreSuccess'));
+ if (onRestored) onRestored();
+ else window.location.reload();
+ } catch (e) {
+ alert(t('snapshot.restoreFailed') + e.message);
+ setIsRestoring(false);
+ }
+ };
+
+ const handleDelete = async (id, e) => {
+ e.stopPropagation();
+ if (!confirm(t('snapshot.confirmDelete'))) return;
+ try {
+ const remaining = await deleteSnapshot(id);
+ setSnapshots(remaining);
+ if (selectedId === id) setSelectedId(remaining[0]?.id || null);
+ } catch (err) {
+ alert(t('snapshot.deleteFailed'));
+ }
+ };
+
+ const selectedSnap = snapshots.find(s => s.id === selectedId);
+
+ const formatDate = (ts) => {
+ const d = new Date(ts);
+ return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
+ };
+
+ return (
+
+
e.stopPropagation()} style={{ width: 800, maxWidth: '90vw', height: '80vh' }}>
+
+
{t('snapshot.title')}
+ — {t('snapshot.subtitle')}
+
+ ✕
+
+
+
+ {/* 左侧列表 */}
+
+
+
+ {isCreating ? t('snapshot.creating') : t('snapshot.createBtn')}
+
+
+
+ {loading ? (
+
{t('snapshot.loading')}
+ ) : snapshots.length === 0 ? (
+
{t('snapshot.empty')}
+ ) : (
+ snapshots.map(s => (
+
setSelectedId(s.id)}
+ style={{
+ padding: '12px 14px',
+ marginBottom: 8,
+ borderRadius: 'var(--radius-md)',
+ border: s.id === selectedId ? '2px solid var(--accent)' : '1px solid var(--border-light)',
+ background: s.id === selectedId ? 'var(--accent-light)' : 'var(--bg-secondary)',
+ cursor: 'pointer',
+ position: 'relative',
+ }}
+ >
+
+
+ {s.type === 'manual' ? '⭐ ' : ''}{s.label}
+
+ {formatDate(s.timestamp)}
+
+
+ {t('snapshot.countChapters').replace('{count}', s.stats?.chapterCount || 0)}
+ {t('snapshot.countWords').replace('{count}', Math.round((s.stats?.totalWords || 0) / 1000))}
+ {t('snapshot.countSettings').replace('{count}', s.stats?.settingCount || 0)}
+
+ {/* 删除按钮 */}
+
handleDelete(s.id, e)}
+ style={{
+ position: 'absolute', right: 10, bottom: 10,
+ background: 'none', border: 'none', color: 'var(--error)',
+ cursor: 'pointer', opacity: 0.5, fontSize: 12
+ }}
+ onMouseEnter={e => e.target.style.opacity = 1}
+ onMouseLeave={e => e.target.style.opacity = 0.5}
+ >
+ {t('snapshot.deleteBtn')}
+
+
+ ))
+ )}
+
+
+
+ {/* 右侧详情 */}
+
+ {selectedSnap ? (
+ <>
+
{selectedSnap.type === 'manual' ? '⭐ ' : ''}{selectedSnap.label}
+
+ {t('snapshot.createdAt')} {new Date(selectedSnap.timestamp).toLocaleString()}
+ {selectedSnap.type === 'auto' && ` ${t('snapshot.autoLabel')}`}
+
+
+
+
+
{selectedSnap.stats?.chapterCount || 0}
+
{t('snapshot.chapterCount')}
+
+
+
{(selectedSnap.stats?.totalWords || 0).toLocaleString()}
+
{t('snapshot.totalWords')}
+
+
+
{selectedSnap.stats?.settingCount || 0}
+
{t('snapshot.settingCount')}
+
+
+
+
+
{t('snapshot.includedChapters')}
+
+ {(selectedSnap.data?.chapters || []).slice(0, 10).map(ch => (
+
+ {ch.title}
+
+ ))}
+ {(selectedSnap.data?.chapters || []).length > 10 && (
+ {t('snapshot.andMore')}
+ )}
+
+
+
+
+
+ {isRestoring ? t('snapshot.restoring') : t('snapshot.restoreBtn')}
+
+
+ {t('snapshot.restoreWarning')}
+
+
+ >
+ ) : (
+
+ {t('snapshot.selectHint')}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/app/components/TourOverlay.js b/app/components/TourOverlay.js
new file mode 100644
index 0000000..f0368db
--- /dev/null
+++ b/app/components/TourOverlay.js
@@ -0,0 +1,280 @@
+'use client';
+
+import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
+import { useAppStore } from '../store/useAppStore';
+import { useI18n } from '../lib/useI18n';
+
+const ONBOARDING_KEY = 'author-onboarding-done';
+
+export default function TourOverlay({ onOpenHelp }) {
+ const { startTour, setStartTour, language, visualTheme } = useAppStore();
+ const { t } = useI18n();
+ const [status, setStatus] = useState('hidden'); // hidden, intro, tour
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+ const [targetRect, setTargetRect] = useState(null);
+ const [windowSize, setWindowSize] = useState({ w: 0, h: 0 });
+
+ // 根据当前语言动态生成 TOUR_STEPS
+ const TOUR_STEPS = useMemo(() => [
+ {
+ targetId: 'tour-new-chapter',
+ title: t('tour.step1Title'),
+ content: t('tour.step1Content'),
+ placement: 'right'
+ },
+ {
+ targetId: 'tour-editor',
+ title: t('tour.step2Title'),
+ content: t('tour.step2Content'),
+ placement: 'center'
+ },
+ {
+ targetId: 'tour-ai-btn',
+ title: t('tour.step3Title'),
+ content: t('tour.step3Content'),
+ placement: 'left'
+ },
+ {
+ targetId: 'tour-settings',
+ title: t('tour.step4Title'),
+ content: t('tour.step4Content'),
+ placement: 'top'
+ },
+ {
+ targetId: 'tour-help',
+ title: t('tour.step5Title'),
+ content: t('tour.step5Content'),
+ placement: 'left'
+ }
+ ], [t]);
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ const isDone = localStorage.getItem(ONBOARDING_KEY);
+ if (!isDone) {
+ if (language && visualTheme) {
+ setStatus('intro');
+ }
+ }
+ setWindowSize({ w: window.innerWidth, h: window.innerHeight });
+ }
+ }, [language, visualTheme]);
+
+ const updateRect = useCallback((index) => {
+ const step = TOUR_STEPS[index];
+ if (!step) return;
+
+ let el = document.getElementById(step.targetId);
+ if (!el && step.targetId === 'tour-editor') {
+ el = document.querySelector('.editor-container');
+ }
+
+ if (el) {
+ const rect = el.getBoundingClientRect();
+ setTargetRect({
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: rect.height
+ });
+ el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
+ } else {
+ setTimeout(() => {
+ const retryEl = document.getElementById(step.targetId) || document.querySelector('.editor-container');
+ if (retryEl) {
+ const r = retryEl.getBoundingClientRect();
+ setTargetRect({ x: r.x, y: r.y, width: r.width, height: r.height });
+ }
+ }, 300);
+ }
+ }, [TOUR_STEPS]);
+
+ const finishTour = () => {
+ localStorage.setItem(ONBOARDING_KEY, 'true');
+ setStatus('hidden');
+ };
+
+ const skipToHelp = () => {
+ setStatus('tour');
+ setCurrentStepIndex(TOUR_STEPS.length - 1);
+ updateRect(TOUR_STEPS.length - 1);
+ };
+
+ const beginTour = useCallback(() => {
+ setStatus('tour');
+ setCurrentStepIndex(0);
+ updateRect(0);
+ }, [updateRect]);
+
+ useEffect(() => {
+ if (startTour) {
+ beginTour();
+ setStartTour(false);
+ }
+ }, [startTour, beginTour, setStartTour]);
+
+ // 监听窗口缩放和滚动
+ useEffect(() => {
+ if (status !== 'tour') return;
+
+ const handleResize = () => {
+ setWindowSize({ w: window.innerWidth, h: window.innerHeight });
+ updateRect(currentStepIndex);
+ };
+
+ window.addEventListener('resize', handleResize);
+ window.addEventListener('scroll', handleResize, true);
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ window.removeEventListener('scroll', handleResize, true);
+ };
+ }, [status, currentStepIndex, updateRect]);
+
+ const nextStep = () => {
+ if (currentStepIndex === TOUR_STEPS.length - 1) {
+ finishTour();
+ } else {
+ setCurrentStepIndex(i => i + 1);
+ updateRect(currentStepIndex + 1);
+ }
+ };
+
+ const prevStep = () => {
+ if (currentStepIndex > 0) {
+ setCurrentStepIndex(i => i - 1);
+ updateRect(currentStepIndex - 1);
+ }
+ };
+
+ if (status === 'hidden') return null;
+
+ // ================= Intro 弹窗 =================
+ if (status === 'intro') {
+ return (
+
+
+
✨
+
{t('tour.introTitle')}
+
{t('tour.introSubtitle')}
+
{t('tour.introHint')}
+
+ {t('tour.btnSkip')}
+ {t('tour.btnStart')}
+
+
+
+ );
+ }
+
+ // ================= 具体步骤向导 (Tour) =================
+ const step = TOUR_STEPS[currentStepIndex];
+ if (!step) return null;
+
+ const padding = 8;
+ const rX = targetRect ? targetRect.x - padding : 0;
+ const rY = targetRect ? targetRect.y - padding : 0;
+ const rW = targetRect ? targetRect.width + padding * 2 : 0;
+ const rH = targetRect ? targetRect.height + padding * 2 : 0;
+ const radius = 8;
+
+ let tooltipStyle = { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };
+ if (targetRect) {
+ if (step.placement === 'right') {
+ tooltipStyle = { top: rY + rH / 2, left: rX + rW + 20, transform: 'translateY(-50%)' };
+ } else if (step.placement === 'left') {
+ tooltipStyle = { top: rY + rH / 2, right: windowSize.w - rX + 20, transform: 'translateY(-50%)' };
+ } else if (step.placement === 'bottom') {
+ tooltipStyle = { top: rY + rH + 20, left: rX + rW / 2, transform: 'translateX(-50%)' };
+ } else if (step.placement === 'center') {
+ tooltipStyle = { top: rY + rH / 2, left: rX + rW / 2, transform: 'translate(-50%, -50%)' };
+ }
+ }
+
+ const tooltipHeightGuess = 200;
+
+ if (tooltipStyle.top && typeof tooltipStyle.top === 'number') {
+ if (tooltipStyle.top < tooltipHeightGuess / 2) {
+ tooltipStyle.top = tooltipHeightGuess / 2;
+ } else if (tooltipStyle.top > windowSize.h - tooltipHeightGuess / 2) {
+ tooltipStyle.top = windowSize.h - tooltipHeightGuess / 2;
+ }
+ }
+
+ if (tooltipStyle.bottom && typeof tooltipStyle.bottom === 'number') {
+ if (tooltipStyle.bottom < 20) {
+ tooltipStyle.bottom = 20;
+ }
+ }
+
+ const validateStyle = { ...tooltipStyle };
+
+ return (
+
+
+
+
+
+ {targetRect && (
+
+ )}
+
+
+
+
+ {targetRect && (
+
+ )}
+
+
+ {targetRect && (
+
+
+
{step.title}
+ {currentStepIndex + 1} / {TOUR_STEPS.length}
+
+
+ {step.content}
+
+
+ {currentStepIndex === TOUR_STEPS.length - 1 ? (
+
+ ) : (
+
{t('tour.btnEndTour')}
+ )}
+
+ {currentStepIndex > 0 && (
+ {t('tour.btnPrev')}
+ )}
+ {currentStepIndex === TOUR_STEPS.length - 1 ? (
+ {t('tour.btnFinish')}
+ ) : (
+ {t('tour.btnNext')}
+ )}
+
+
+
+ )}
+
+ );
+}
diff --git a/app/components/WelcomeModal.js b/app/components/WelcomeModal.js
new file mode 100644
index 0000000..5b9bfdb
--- /dev/null
+++ b/app/components/WelcomeModal.js
@@ -0,0 +1,118 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useAppStore } from '../store/useAppStore';
+import { useI18n } from '../lib/useI18n';
+
+export default function WelcomeModal() {
+ const { language, setLanguage, visualTheme, setVisualTheme } = useAppStore();
+ const { t } = useI18n();
+ const [step, setStep] = useState(1);
+ const [isVisible, setIsVisible] = useState(false);
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ if (!language || !visualTheme) {
+ setIsVisible(true);
+ if (!language) {
+ setStep(1);
+ } else {
+ setStep(2);
+ }
+ }
+ }, [language, visualTheme]);
+
+ if (!mounted || !isVisible) return null;
+
+ const handleSelectLang = (lang) => {
+ setLanguage(lang);
+ setStep(2);
+ };
+
+ const handleSelectTheme = (theme) => {
+ setVisualTheme(theme);
+ // Apply theme to document element immediately for preview
+ document.documentElement.setAttribute('data-visual', theme);
+ };
+
+ const handleStart = () => {
+ if (!visualTheme) {
+ handleSelectTheme('warm');
+ }
+ setIsVisible(false);
+ if (!localStorage.getItem('author-onboarding-done')) {
+ useAppStore.getState().setStartTour(true);
+ }
+ };
+
+ return (
+
+
+ {/* Step 1: Language */}
+ {step === 1 && (
+
+
Welcome / 欢迎 / Добро пожаловать
+
+ handleSelectLang('en')}>
+ 🇬🇧
+ English
+
+ handleSelectLang('zh')}>
+ 🇨🇳
+ 简体中文
+
+ handleSelectLang('ru')}>
+ 🇷🇺
+ Русский
+
+
+
+ )}
+
+ {/* Step 2: Theme */}
+ {step === 2 && (
+
+
{t('welcome.title')}
+
{t('welcome.subtitle')}
+
+
{t('welcome.selectTheme')}
+
+
handleSelectTheme('warm')}
+ >
+
+
+
{t('welcome.themeWarm.name')}
+
{t('welcome.themeWarm.desc')}
+
+
+
+
handleSelectTheme('modern')}
+ >
+
+
+
{t('welcome.themeModern.name')}
+
{t('welcome.themeModern.desc')}
+
+
+
+
+
+
+ {t('welcome.startBtn')}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/app/favicon.ico b/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/app/favicon.ico differ
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..5531159
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,4742 @@
+@import "tailwindcss";
+
+/* ==================== Author 设计系统 ==================== */
+
+:root {
+ /* 基础动画曲线: 更加丝滑的弹性反馈 */
+ --transition-fast: 0.15s cubic-bezier(0.16, 1, 0.3, 1);
+ --transition-normal: 0.25s cubic-bezier(0.16, 1, 0.3, 1);
+ --transition-slow: 0.4s cubic-bezier(0.16, 1, 0.3, 1);
+
+ --font-writing: 'Noto Serif SC', 'Source Han Serif SC', 'STSong', Georgia, serif;
+ --font-ui: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
+
+ --editor-font-size: 17px;
+ --editor-line-height: 1.9;
+ --editor-max-width: 816px;
+}
+
+/* 默认 / 经典暖纸模式 (Warm Classic) */
+:root,
+[data-visual="warm"] {
+ --bg-primary: #faf8f5;
+ --bg-secondary: #f5f0ea;
+ --bg-editor: #fffdf9;
+ --bg-sidebar: #f7f3ed;
+ --bg-hover: #efe9e0;
+ --bg-active: #e8dfd3;
+ --bg-card: #ffffff;
+ --bg-canvas: #e8e4df;
+ --bg-glass: rgba(250, 248, 245, 0.85);
+
+ --text-primary: #2c2417;
+ --text-secondary: #6b5e4f;
+ --text-muted: #9a8d7e;
+ --text-inverse: #faf8f5;
+
+ --border-light: #e5ddd2;
+ --border-medium: #d4c9ba;
+ --border-glass: rgba(229, 221, 210, 0.5);
+
+ --accent: #c4956a;
+ --accent-hover: #b38358;
+ --accent-light: rgba(196, 149, 106, 0.15);
+ --accent-glow: rgba(196, 149, 106, 0.3);
+
+ --shadow-sm: 0 1px 3px rgba(44, 36, 23, 0.06);
+ --shadow-md: 0 4px 12px rgba(44, 36, 23, 0.08);
+ --shadow-lg: 0 12px 32px rgba(44, 36, 23, 0.12);
+ --shadow-page: 0 8px 30px rgba(44, 36, 23, 0.08), 0 1px 3px rgba(44, 36, 23, 0.04);
+
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 20px;
+ --radius-full: 99px;
+}
+
+/* 暖色纸张 暗色模式 */
+[data-visual="warm"][data-theme="dark"],
+:root[data-theme="dark"] {
+ --bg-primary: #1a1814;
+ --bg-secondary: #221f1a;
+ --bg-editor: #1e1b17;
+ --bg-sidebar: #16140f;
+ --bg-hover: #2a2620;
+ --bg-active: #33302a;
+ --bg-card: #242019;
+ --bg-canvas: #121010;
+ --bg-glass: rgba(26, 24, 20, 0.85);
+
+ --text-primary: #e8e0d4;
+ --text-secondary: #a89a88;
+ --text-muted: #756a5c;
+ --text-inverse: #1a1814;
+
+ --border-light: #33302a;
+ --border-medium: #443f36;
+ --border-glass: rgba(51, 48, 42, 0.5);
+
+ --accent: #d4a87a;
+ --accent-hover: #e0b88a;
+ --accent-light: rgba(212, 168, 122, 0.15);
+ --accent-glow: rgba(212, 168, 122, 0.25);
+
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
+ --shadow-page: 0 8px 30px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.3);
+}
+
+/* 现代极简透玻璃主题 (Modern Glass) */
+[data-visual="modern"] {
+ --bg-primary: #fcfcfd;
+ --bg-secondary: #f2f3f5;
+ --bg-editor: #ffffff;
+ --bg-sidebar: #f7f8f9;
+ --bg-hover: #e8eaed;
+ --bg-active: #dfe1e5;
+ --bg-card: #ffffff;
+ --bg-canvas: #eaebed;
+ --bg-glass: rgba(255, 255, 255, 0.7);
+
+ --text-primary: #111827;
+ --text-secondary: #4b5563;
+ --text-muted: #9ca3af;
+ --text-inverse: #ffffff;
+
+ --border-light: #e5e7eb;
+ --border-medium: #d1d5db;
+ --border-glass: rgba(255, 255, 255, 0.5);
+
+ --accent: #3b82f6;
+ --accent-hover: #2563eb;
+ --accent-light: rgba(59, 130, 246, 0.1);
+ --accent-glow: rgba(59, 130, 246, 0.25);
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
+ --shadow-lg: 0 10px 25px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.03);
+ --shadow-page: 0 10px 40px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.02);
+
+ --radius-sm: 8px;
+ --radius-md: 14px;
+ --radius-lg: 24px;
+ --radius-full: 99px;
+}
+
+/* 现代主题 暗色模式 */
+[data-visual="modern"][data-theme="dark"] {
+ --bg-primary: #111111;
+ --bg-secondary: #1a1a1a;
+ --bg-editor: #161616;
+ --bg-sidebar: #141414;
+ --bg-hover: #262626;
+ --bg-active: #333333;
+ --bg-card: #222222;
+ --bg-canvas: #0a0a0a;
+ --bg-glass: rgba(22, 22, 22, 0.7);
+
+ --text-primary: #f3f4f6;
+ --text-secondary: #9ca3af;
+ --text-muted: #6b7280;
+ --text-inverse: #111111;
+
+ --border-light: #2d2d2d;
+ --border-medium: #404040;
+ --border-glass: rgba(255, 255, 255, 0.08);
+
+ --accent: #60a5fa;
+ --accent-hover: #93c5fd;
+ --accent-light: rgba(96, 165, 250, 0.15);
+ --accent-glow: rgba(96, 165, 250, 0.25);
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
+ --shadow-lg: 0 10px 25px -3px rgba(0, 0, 0, 0.6);
+ --shadow-page: 0 10px 40px rgba(0, 0, 0, 0.7), 0 1px 3px rgba(0, 0, 0, 0.5);
+}
+
+/* ==================== 基础样式 ==================== */
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+
+
+body {
+ font-family: var(--font-ui);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ transition: background var(--transition-normal), color var(--transition-normal);
+}
+
+::selection {
+ background: var(--accent-light);
+ color: var(--text-primary);
+}
+
+/* 自定义滚动条 */
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border-medium);
+ border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--text-muted);
+}
+
+/* ==================== 布局 ==================== */
+
+.app-layout {
+ display: flex;
+ height: 100vh;
+ overflow: hidden;
+ position: relative;
+}
+
+.sidebar {
+ position: absolute !important;
+ top: 0 !important;
+ left: 0 !important;
+ bottom: 0 !important;
+ width: 280px !important;
+ background: var(--bg-sidebar);
+ border-right: 1px solid var(--border-light);
+ display: flex;
+ flex-direction: column;
+ transition: transform var(--transition-normal);
+ z-index: 30 !important;
+ transform: translateX(0) !important;
+}
+
+.sidebar.collapsed {
+ transform: translateX(-100%) !important;
+ pointer-events: none !important;
+}
+
+.sidebar-header {
+ padding: 20px 20px 16px;
+ border-bottom: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.sidebar-logo {
+ font-family: var(--font-writing);
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--text-primary);
+ letter-spacing: 1px;
+}
+
+.sidebar-logo span {
+ color: var(--accent);
+}
+
+.sidebar-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px;
+}
+
+.sidebar-footer {
+ padding: 12px;
+ border-top: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* ==================== 章节列表 ==================== */
+
+.chapter-list {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.chapter-item {
+ padding: 10px 14px;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ color: var(--text-secondary);
+ font-size: 14px;
+}
+
+.chapter-item:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.chapter-item.active {
+ background: var(--accent-light);
+ color: var(--accent);
+ font-weight: 500;
+}
+
+.chapter-item .chapter-title {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.chapter-item .chapter-count {
+ font-size: 11px;
+ color: var(--text-muted);
+ flex-shrink: 0;
+}
+
+.chapter-item .chapter-actions {
+ display: none;
+ gap: 4px;
+ flex-shrink: 0;
+}
+
+.chapter-item:hover .chapter-actions {
+ display: flex;
+}
+
+.chapter-item:hover .chapter-count {
+ display: none;
+}
+
+/* ==================== 主内容区 ==================== */
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ position: relative;
+}
+
+.editor-toolbar {
+ padding: 8px 20px;
+ border-bottom: 1px solid var(--border-light);
+ background: var(--bg-secondary);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ flex-wrap: wrap;
+}
+
+.toolbar-group {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+}
+
+.toolbar-divider {
+ width: 1px;
+ height: 20px;
+ background: var(--border-light);
+ margin: 0 6px;
+}
+
+.toolbar-btn {
+ padding: 6px 8px;
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 32px;
+ height: 32px;
+}
+
+.toolbar-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.toolbar-btn.active {
+ background: var(--accent-light);
+ color: var(--accent);
+}
+
+/* 工具栏下拉容器 */
+.toolbar-dropdown-wrap {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.toolbar-dropdown-btn {
+ font-size: 12px !important;
+ gap: 4px;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dropdown-arrow {
+ font-size: 9px;
+ opacity: 0.5;
+ margin-left: 2px;
+}
+
+/* 工具栏下拉菜单 */
+.toolbar-dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 4px;
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-lg);
+ min-width: 180px;
+ z-index: 60;
+ padding: 4px;
+ animation: fadeIn 0.12s ease;
+}
+
+.toolbar-dropdown-item {
+ display: block;
+ width: 100%;
+ padding: 8px 12px;
+ border: none;
+ background: transparent;
+ text-align: left;
+ color: var(--text-primary);
+ font-size: 13px;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: background 0.1s;
+ font-family: var(--font-ui);
+}
+
+.toolbar-dropdown-item:hover {
+ background: var(--bg-hover);
+}
+
+.toolbar-dropdown-item.active {
+ background: var(--accent-light);
+ color: var(--accent);
+ font-weight: 600;
+}
+
+/* 颜色按钮 */
+.toolbar-color-btn {
+ gap: 2px !important;
+ font-weight: 700;
+}
+
+/* 颜色选择器浮层 */
+.color-picker-popover {
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 6px;
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-lg);
+ padding: 12px;
+ z-index: 60;
+ min-width: 200px;
+ animation: fadeIn 0.12s ease;
+}
+
+.color-picker-label {
+ font-size: 11px;
+ color: var(--text-muted);
+ margin-bottom: 8px;
+ font-weight: 500;
+}
+
+.color-picker-grid {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 4px;
+ margin-bottom: 8px;
+}
+
+.color-swatch {
+ width: 28px;
+ height: 28px;
+ border-radius: 4px;
+ border: 2px solid transparent;
+ cursor: pointer;
+ transition: all 0.1s;
+}
+
+.color-swatch:hover {
+ transform: scale(1.15);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+}
+
+.color-swatch.active {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px var(--accent-light);
+}
+
+.color-picker-clear {
+ width: 100%;
+ padding: 5px 0;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 11px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.color-picker-clear:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+/* ==================== 虚拟分页层系统 ==================== */
+
+.editor-container {
+ flex: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ padding: 40px 16px 80px;
+ background: var(--bg-canvas) !important;
+ transition: background var(--transition-normal);
+ /* 禁用滚动锚定,防止虚拟分页元素重排时导致原生滚动条跳动 */
+ overflow-anchor: none;
+}
+
+/* 统率背景卡片和文本的包裹层 */
+.document-workspace {
+ position: relative;
+ width: 100%;
+ max-width: var(--editor-max-width);
+ margin: 0 auto;
+ cursor: text;
+ /* 禁用滚动锚定 */
+ overflow-anchor: none;
+}
+
+/* 纸页背景层(绝对定位在底层)*/
+.pages-bg-layer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ pointer-events: none;
+ z-index: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.page-card {
+ width: 100%;
+ background: var(--bg-editor);
+ box-shadow: var(--shadow-page);
+ border: 1px solid var(--border-light);
+ border-radius: 2px;
+ flex-shrink: 0;
+}
+
+/* 文本前景层(透明且连续流淌)*/
+.pages-fg-layer {
+ position: relative;
+ z-index: 1;
+ width: 100%;
+}
+
+
+.chapter-title-input {
+ font-family: var(--font-writing);
+ font-size: 28px;
+ font-weight: 700;
+ color: var(--text-primary);
+ border: none;
+ outline: none;
+ background: transparent;
+ width: 100%;
+ margin-bottom: 24px;
+ padding: 0;
+ line-height: 1.4;
+}
+
+.chapter-title-input::placeholder {
+ color: var(--text-muted);
+}
+
+/* Tiptap 编辑器样式 */
+.tiptap {
+ font-family: var(--font-writing);
+ font-size: var(--editor-font-size);
+ line-height: var(--editor-line-height);
+ color: var(--text-primary);
+ outline: none;
+ caret-color: var(--accent);
+ background: transparent;
+ box-sizing: border-box;
+ min-height: 1056px;
+ padding: var(--page-margin-y, 96px) var(--page-margin-x, 96px);
+}
+
+.tiptap p {
+ margin-bottom: 0.8em;
+ text-indent: 2em;
+}
+
+.tiptap p:first-child {
+ text-indent: 0;
+}
+
+/* 含公式/特殊元素的段落取消首行缩进 */
+.tiptap p:has(.math-inline),
+.tiptap p:has(.math-block) {
+ text-indent: 0;
+}
+
+.tiptap h1 {
+ font-size: 1.8em;
+ font-weight: 700;
+ margin: 1.5em 0 0.8em;
+ line-height: 1.3;
+}
+
+.tiptap h2 {
+ font-size: 1.4em;
+ font-weight: 600;
+ margin: 1.2em 0 0.6em;
+ line-height: 1.3;
+}
+
+.tiptap h3 {
+ font-size: 1.15em;
+ font-weight: 600;
+ margin: 1em 0 0.5em;
+ line-height: 1.3;
+}
+
+.tiptap strong {
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.tiptap em {
+ font-style: italic;
+}
+
+.tiptap u {
+ text-decoration: underline;
+ text-underline-offset: 3px;
+}
+
+.tiptap mark {
+ background: var(--accent-light);
+ border-radius: 2px;
+ padding: 1px 4px;
+}
+
+.tiptap blockquote {
+ border-left: 3px solid var(--accent);
+ padding-left: 16px;
+ margin: 1em 0;
+ color: var(--text-secondary);
+ font-style: italic;
+}
+
+.tiptap hr {
+ border: none;
+ border-top: 1px solid var(--border-light);
+ margin: 2em 0;
+}
+
+.tiptap ul,
+.tiptap ol {
+ padding-left: 1.5em;
+ margin: 0.8em 0;
+}
+
+.tiptap li {
+ margin-bottom: 0.3em;
+ text-indent: 0;
+}
+
+.tiptap li p {
+ text-indent: 0;
+}
+
+.tiptap .is-editor-empty:first-child::before {
+ content: attr(data-placeholder);
+ float: left;
+ color: var(--text-muted);
+ pointer-events: none;
+ height: 0;
+ font-style: italic;
+}
+
+/* 任务列表 */
+.tiptap ul[data-type="taskList"] {
+ list-style: none;
+ padding-left: 0;
+}
+
+.tiptap ul[data-type="taskList"] li {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin-bottom: 4px;
+}
+
+.tiptap ul[data-type="taskList"] li>label {
+ flex-shrink: 0;
+ margin-top: 4px;
+}
+
+.tiptap ul[data-type="taskList"] li>label input[type="checkbox"] {
+ width: 16px;
+ height: 16px;
+ accent-color: var(--accent);
+ cursor: pointer;
+}
+
+.tiptap ul[data-type="taskList"] li>div {
+ flex: 1;
+}
+
+.tiptap ul[data-type="taskList"] li[data-checked="true"]>div {
+ text-decoration: line-through;
+ color: var(--text-muted);
+}
+
+/* 代码块 */
+.tiptap pre {
+ background: var(--bg-tertiary, #f4f4f5);
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ padding: 12px 16px;
+ margin: 1em 0;
+ overflow-x: auto;
+ font-family: "SF Mono", "Cascadia Code", "Consolas", monospace;
+ font-size: 0.9em;
+ line-height: 1.6;
+}
+
+.tiptap pre code {
+ background: transparent;
+ padding: 0;
+ border: none;
+ font-size: inherit;
+ color: inherit;
+}
+
+/* 行内代码 */
+.tiptap code {
+ background: var(--bg-tertiary, #f4f4f5);
+ padding: 2px 5px;
+ border-radius: 3px;
+ font-family: "SF Mono", "Cascadia Code", "Consolas", monospace;
+ font-size: 0.9em;
+}
+
+/* 删除线 */
+.tiptap s {
+ text-decoration: line-through;
+ color: var(--text-muted);
+}
+
+/* 上下标 */
+.tiptap sub {
+ font-size: 0.75em;
+ vertical-align: sub;
+}
+
+.tiptap sup {
+ font-size: 0.75em;
+ vertical-align: super;
+}
+
+/* 行内公式 — inline-flex 完全无间隙 */
+.math-inline {
+ display: inline-flex !important;
+ vertical-align: baseline;
+ cursor: pointer;
+ border-radius: 2px;
+ transition: background 0.15s;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: none;
+ text-indent: 0;
+}
+
+/* 强制清除 KaTeX 行内公式的所有内部间距 */
+.math-inline .katex {
+ font-size: 1em;
+ margin: 0 !important;
+ padding: 0 !important;
+ text-indent: 0;
+}
+
+.math-inline .katex-html {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.math-inline .katex-mathml {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.math-inline .katex .mspace,
+.math-inline .katex .mord,
+.math-inline .katex .base {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+.math-inline:hover {
+ background: var(--accent-light, #e8e0ff);
+}
+
+/* 块级公式 — 居中,无边框,干净 */
+.math-block {
+ display: block;
+ text-align: center;
+ padding: 16px 0;
+ margin: 0.8em 0;
+ cursor: pointer;
+ transition: background 0.15s;
+ border-radius: var(--radius-sm);
+}
+
+.math-block:hover {
+ background: var(--bg-hover, #f8f8fa);
+}
+
+.math-block .katex {
+ font-size: 1.2em;
+}
+
+/* ==================== 公式编辑弹窗 ==================== */
+.math-editor-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 3000;
+ background: rgba(0, 0, 0, 0.45);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 0.15s ease-out;
+}
+
+.math-editor-overlay.closing {
+ opacity: 0;
+ transition: opacity 0.15s;
+}
+
+.math-editor-dialog {
+ width: 560px;
+ max-width: 92vw;
+ background: var(--bg-primary);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3);
+ overflow: hidden;
+ animation: slideUp 0.2s ease-out;
+}
+
+.math-editor-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 14px 20px;
+ border-bottom: 1px solid var(--border-light);
+}
+
+.math-editor-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.math-editor-close {
+ width: 28px;
+ height: 28px;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 16px;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.15s;
+}
+
+.math-editor-close:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+/* 实时预览区域 */
+.math-editor-preview {
+ min-height: 80px;
+ padding: 24px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-bottom: 1px solid var(--border-light);
+ background: var(--bg-secondary);
+ overflow-x: auto;
+}
+
+.math-editor-preview .katex {
+ font-size: 1.4em;
+}
+
+.math-editor-placeholder {
+ color: var(--text-muted);
+ font-style: italic;
+ font-size: 14px;
+}
+
+.math-editor-error {
+ color: var(--error, #ef4444);
+ font-size: 12px;
+ font-family: monospace;
+}
+
+/* LaTeX 输入框 */
+.math-editor-input {
+ display: block;
+ width: 100%;
+ min-height: 80px;
+ padding: 14px 20px;
+ border: none;
+ outline: none;
+ font-family: "SF Mono", "Cascadia Code", "Consolas", monospace;
+ font-size: 14px;
+ line-height: 1.6;
+ color: var(--text-primary);
+ background: var(--bg-primary);
+ resize: vertical;
+ box-sizing: border-box;
+}
+
+.math-editor-input::placeholder {
+ color: var(--text-muted);
+}
+
+.math-editor-input:focus {
+ background: var(--bg-primary);
+}
+
+/* 底部操作栏 */
+.math-editor-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 20px;
+ border-top: 1px solid var(--border-light);
+ background: var(--bg-secondary);
+}
+
+.math-editor-hint {
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+.math-editor-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.math-editor-cancel {
+ padding: 6px 16px;
+ border: 1px solid var(--border);
+ background: transparent;
+ color: var(--text-secondary);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ font-size: 13px;
+ transition: all 0.15s;
+}
+
+.math-editor-cancel:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.math-editor-save {
+ padding: 6px 20px;
+ border: none;
+ background: var(--accent);
+ color: white;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 600;
+ transition: all 0.15s;
+}
+
+.math-editor-save:hover {
+ filter: brightness(1.1);
+}
+
+/* ==================== AI 面板 ==================== */
+
+.ai-panel {
+ position: fixed;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ width: 400px;
+ background: var(--bg-card);
+ border-left: 1px solid var(--border-light);
+ box-shadow: var(--shadow-lg);
+ z-index: 30;
+ display: flex;
+ flex-direction: column;
+ transform: translateX(100%);
+ transition: transform var(--transition-slow);
+}
+
+.ai-panel.open {
+ transform: translateX(0);
+}
+
+.ai-panel-header {
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.ai-panel-header h3 {
+ font-size: 15px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.ai-panel-body {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.ai-result {
+ padding: 16px;
+ background: var(--bg-secondary);
+ border-radius: var(--radius-md);
+ font-family: var(--font-writing);
+ font-size: 15px;
+ line-height: 1.8;
+ color: var(--text-primary);
+ white-space: pre-wrap;
+ position: relative;
+}
+
+.ai-result-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid var(--border-light);
+}
+
+.ai-panel-footer {
+ padding: 16px 20px;
+ border-top: 1px solid var(--border-light);
+}
+
+.ai-mode-tabs {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 12px;
+}
+
+.ai-mode-tab {
+ padding: 6px 14px;
+ border: 1px solid var(--border-light);
+ background: transparent;
+ color: var(--text-secondary);
+ border-radius: 20px;
+ cursor: pointer;
+ font-size: 13px;
+ transition: all var(--transition-fast);
+}
+
+.ai-mode-tab:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+.ai-mode-tab.active {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: var(--text-inverse);
+}
+
+.ai-input-group {
+ display: flex;
+ gap: 8px;
+}
+
+.ai-input {
+ flex: 1;
+ padding: 10px 14px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ font-size: 14px;
+ font-family: var(--font-ui);
+ resize: none;
+ outline: none;
+ transition: border-color var(--transition-fast);
+}
+
+.ai-input:focus {
+ border-color: var(--accent);
+}
+
+/* ==================== 按钮 (Premium UI) ==================== */
+
+.btn {
+ padding: 8px 16px;
+ border: 1px solid transparent;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ font-family: var(--font-ui);
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ outline: none;
+ box-shadow: var(--shadow-sm);
+}
+
+.btn:active {
+ transform: scale(0.96);
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
+ color: white;
+ box-shadow: 0 4px 12px var(--accent-light);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+}
+
+.btn-primary:hover {
+ background: var(--accent-hover);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px var(--accent-glow);
+}
+
+.btn-primary:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+}
+
+.btn-secondary {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
+ border-color: var(--border-light);
+}
+
+.btn-secondary:hover {
+ background: var(--bg-active);
+ color: var(--text-primary);
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+}
+
+.btn-ghost {
+ background: transparent;
+ color: var(--text-secondary);
+ box-shadow: none;
+}
+
+.btn-ghost:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ transform: translateY(-1px);
+}
+
+/* ==================== 毛玻璃复用类 ==================== */
+.glass-panel {
+ background: var(--bg-glass);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ border: 1px solid var(--border-glass);
+}
+
+.btn-icon {
+ padding: 6px;
+ min-width: 32px;
+ height: 32px;
+ justify-content: center;
+}
+
+.btn-sm {
+ padding: 5px 10px;
+ font-size: 12px;
+}
+
+.btn-stop {
+ background: var(--error);
+ color: #fff;
+ font-weight: 600;
+}
+
+.btn-stop:hover {
+ background: #a33;
+ filter: brightness(1.1);
+}
+
+/* 流式输出光标 */
+.streaming-cursor {
+ display: inline-block;
+ animation: blink 0.6s steps(2) infinite;
+ color: var(--accent);
+ font-weight: 300;
+ margin-left: 1px;
+}
+
+@keyframes blink {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0;
+ }
+}
+
+/* ==================== 状态栏 ==================== */
+
+.status-bar {
+ padding: 6px 20px;
+ border-top: 1px solid var(--border-light);
+ background: var(--bg-secondary);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 12px;
+ color: var(--text-muted);
+ position: relative;
+}
+
+.status-bar-left,
+.status-bar-right {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+
+/* 排版设置浮层 */
+.typeset-popover {
+ position: absolute;
+ bottom: 100%;
+ right: 16px;
+ margin-bottom: 6px;
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-lg);
+ padding: 16px 20px;
+ min-width: 260px;
+ z-index: 50;
+ animation: fadeIn 0.15s ease;
+}
+
+.typeset-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.typeset-row:last-of-type {
+ margin-bottom: 14px;
+}
+
+.typeset-row label {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ min-width: 32px;
+ font-family: var(--font-ui);
+}
+
+.typeset-row input[type="range"] {
+ flex: 1;
+ height: 4px;
+ -webkit-appearance: none;
+ appearance: none;
+ background: var(--border-light);
+ border-radius: 2px;
+ outline: none;
+ cursor: pointer;
+}
+
+.typeset-row input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--accent);
+ border: 2px solid var(--bg-card);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
+ cursor: pointer;
+ transition: transform 0.15s;
+}
+
+.typeset-row input[type="range"]::-webkit-slider-thumb:hover {
+ transform: scale(1.15);
+}
+
+.typeset-value {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent);
+ min-width: 40px;
+ text-align: right;
+ font-family: 'SF Mono', 'Cascadia Code', monospace;
+}
+
+.typeset-reset {
+ width: 100%;
+ padding: 6px 0;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 12px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.typeset-reset:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ border-color: var(--border-medium);
+}
+
+/* ==================== 浮动 AI 按钮 ==================== */
+
+.ai-fab {
+ position: fixed;
+ bottom: 48px;
+ right: 32px;
+ width: 52px;
+ height: 52px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
+ color: white;
+ border: none;
+ cursor: pointer;
+ box-shadow: 0 4px 16px var(--accent-glow);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 22px;
+ transition: all var(--transition-normal);
+ z-index: 25;
+}
+
+.ai-fab:hover {
+ transform: scale(1.08);
+ box-shadow: 0 6px 24px var(--accent-glow);
+}
+
+.ai-fab.active {
+ background: var(--text-secondary);
+ box-shadow: none;
+}
+
+/* ==================== 弹出菜单 ==================== */
+
+.dropdown {
+ position: relative;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 4px;
+ background: var(--bg-glass);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ border: 1px solid var(--border-glass);
+ border-radius: var(--radius-md);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
+ min-width: 160px;
+ z-index: 50;
+ padding: 4px;
+ animation: slideUp 0.2s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.dropdown-item {
+ padding: 8px 12px;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ font-size: 13px;
+ color: var(--text-secondary);
+ transition: all var(--transition-fast);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border: none;
+ background: none;
+ width: 100%;
+ text-align: left;
+}
+
+.dropdown-item:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.dropdown-item.danger:hover {
+ background: rgba(196, 106, 106, 0.1);
+ color: var(--error);
+}
+
+/* ==================== 模态框 ==================== */
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.25);
+ backdrop-filter: blur(4px);
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.modal {
+ background: var(--bg-glass);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+ border: 1px solid var(--border-glass);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.15);
+ width: 90%;
+ max-width: 480px;
+ padding: 24px;
+ animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.modal h2 {
+ font-size: 18px;
+ font-weight: 600;
+ margin-bottom: 16px;
+}
+
+.modal-input {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ font-size: 14px;
+ font-family: var(--font-ui);
+ outline: none;
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
+ margin-bottom: 16px;
+}
+
+.modal-input:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-light);
+ background: var(--bg-primary);
+}
+
+.modal-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+/* ==================== 设置面板 ==================== */
+
+.settings-group {
+ margin-bottom: 20px;
+}
+
+.settings-group label {
+ display: block;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ margin-bottom: 8px;
+}
+
+.settings-slider {
+ width: 100%;
+ accent-color: var(--accent);
+}
+
+/* ==================== 加载动画 ==================== */
+
+.loading-dots {
+ display: inline-flex;
+ gap: 4px;
+}
+
+.loading-dots span {
+ width: 6px;
+ height: 6px;
+ background: var(--accent);
+ border-radius: 50%;
+ animation: bounce 1.4s infinite ease-in-out both;
+}
+
+.loading-dots span:nth-child(1) {
+ animation-delay: -0.32s;
+}
+
+.loading-dots span:nth-child(2) {
+ animation-delay: -0.16s;
+}
+
+.loading-dots span:nth-child(3) {
+ animation-delay: 0s;
+}
+
+/* ==================== Toast 通知 ==================== */
+
+.toast-container {
+ position: fixed;
+ top: 16px;
+ right: 16px;
+ z-index: 200;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.toast {
+ padding: 12px 16px;
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-md);
+ font-size: 13px;
+ color: var(--text-primary);
+ animation: slideIn 0.3s ease;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.toast.success {
+ border-left: 3px solid var(--success);
+}
+
+.toast.error {
+ border-left: 3px solid var(--error);
+}
+
+.toast.info {
+ border-left: 3px solid var(--info);
+}
+
+/* ==================== 动画 ==================== */
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateX(20px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes bounce {
+
+ 0%,
+ 80%,
+ 100% {
+ transform: scale(0);
+ }
+
+ 40% {
+ transform: scale(1);
+ }
+}
+
+/* ==================== 响应式 ==================== */
+
+@media (max-width: 768px) {
+ .sidebar {
+ position: fixed;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 40;
+ box-shadow: var(--shadow-lg);
+ }
+
+ .sidebar.collapsed {
+ transform: translateX(-100%);
+ }
+
+ .ai-panel {
+ width: 100%;
+ }
+
+ .document-workspace {
+ padding: 0;
+ }
+}
+
+/* ==================== 设定集分类配色 ==================== */
+
+:root {
+ --cat-work: #d97706;
+ --cat-work-bg: rgba(217, 119, 6, 0.1);
+ --cat-character: #3b82f6;
+ --cat-character-bg: rgba(59, 130, 246, 0.1);
+ --cat-location: #10b981;
+ --cat-location-bg: rgba(16, 185, 129, 0.1);
+ --cat-world: #8b5cf6;
+ --cat-world-bg: rgba(139, 92, 246, 0.1);
+ --cat-object: #f59e0b;
+ --cat-object-bg: rgba(245, 158, 11, 0.1);
+ --cat-plot: #ef4444;
+ --cat-plot-bg: rgba(239, 68, 68, 0.1);
+ --cat-rules: #6b7280;
+ --cat-rules-bg: rgba(107, 114, 128, 0.1);
+ --cat-custom: #06b6d4;
+ --cat-custom-bg: rgba(6, 182, 212, 0.1);
+ --cat-bookinfo: #c4956a;
+ --cat-bookinfo-bg: rgba(196, 149, 106, 0.1);
+}
+
+[data-theme="dark"] {
+ --cat-work: #f59e0b;
+ --cat-work-bg: rgba(245, 158, 11, 0.15);
+ --cat-character: #60a5fa;
+ --cat-character-bg: rgba(96, 165, 250, 0.15);
+ --cat-location: #34d399;
+ --cat-location-bg: rgba(52, 211, 153, 0.15);
+ --cat-world: #a78bfa;
+ --cat-world-bg: rgba(167, 139, 250, 0.15);
+ --cat-object: #fbbf24;
+ --cat-object-bg: rgba(251, 191, 36, 0.15);
+ --cat-plot: #f87171;
+ --cat-plot-bg: rgba(248, 113, 113, 0.15);
+ --cat-rules: #9ca3af;
+ --cat-rules-bg: rgba(156, 163, 175, 0.15);
+ --cat-custom: #22d3ee;
+ --cat-custom-bg: rgba(34, 211, 238, 0.15);
+ --cat-bookinfo: #d4a574;
+ --cat-bookinfo-bg: rgba(212, 165, 116, 0.15);
+}
+
+/* ==================== 设定集面板 — 全屏覆盖 ==================== */
+
+.settings-panel-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 0.2s ease;
+}
+
+.settings-panel-container {
+ background: var(--bg-card);
+ border-radius: 16px;
+ box-shadow:
+ 0 24px 80px rgba(0, 0, 0, 0.25),
+ 0 0 0 1px rgba(255, 255, 255, 0.06);
+ width: 94%;
+ max-width: 1120px;
+ max-height: 88vh;
+ display: flex;
+ flex-direction: column;
+ animation: settingsSlideUp 0.35s cubic-bezier(0.16, 1, 0.3, 1);
+ overflow: hidden;
+}
+
+@keyframes settingsSlideUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px) scale(0.97);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+/* ==================== 设定集头部 ==================== */
+
+.settings-header {
+ padding: 20px 28px;
+ border-bottom: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-secondary) 100%);
+}
+
+.settings-header h2 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ color: var(--text-primary);
+}
+
+.settings-header .subtitle {
+ font-size: 12px;
+ color: var(--text-muted);
+ font-weight: 400;
+}
+
+/* ==================== 统计栏 ==================== */
+
+.settings-stats {
+ display: flex;
+ gap: 8px;
+ padding: 10px 24px;
+ border-bottom: 1px solid var(--border-light);
+ background: var(--bg-secondary);
+ overflow-x: auto;
+ scrollbar-width: thin;
+}
+
+.stat-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 14px 6px 12px;
+ border-radius: var(--radius-full);
+ font-size: 12px;
+ font-weight: 500;
+ white-space: nowrap;
+ transition: all var(--transition-normal);
+ cursor: pointer;
+ border: 1px solid rgba(128, 128, 128, 0.15);
+ box-shadow: var(--shadow-sm);
+}
+
+.stat-badge:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+ filter: brightness(1.05);
+}
+
+.stat-badge:active {
+ transform: scale(0.96);
+}
+
+.stat-badge .stat-count {
+ font-weight: 700;
+ font-size: 14px;
+}
+
+/* ==================== 搜索框 ==================== */
+
+.settings-search {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--border-light);
+}
+
+.settings-search-input {
+ width: 100%;
+ padding: 8px 12px 8px 34px;
+ border: 1px solid var(--border-light);
+ border-radius: 8px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ outline: none;
+ transition: all 0.2s ease;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%239a8d7e' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: 10px center;
+ background-size: 14px;
+}
+
+.settings-search-input:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-light), 0 0 16px rgba(196, 149, 106, 0.1);
+}
+
+.settings-search-input::placeholder {
+ color: var(--text-muted);
+}
+
+/* ==================== 设定树样式 ==================== */
+
+.settings-tree {
+ padding: 6px 8px;
+}
+
+.tree-node {
+ user-select: none;
+}
+
+.tree-node-row {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 12px;
+ border-radius: var(--radius-md);
+ border: 1px solid transparent;
+ cursor: pointer;
+ transition: all var(--transition-normal);
+ font-size: 13px;
+ color: var(--text-secondary);
+ position: relative;
+ margin-bottom: 2px;
+}
+
+.tree-node-row:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ transform: translateX(2px);
+}
+
+.tree-node-row.selected {
+ background: var(--bg-card);
+ color: var(--accent);
+ font-weight: 600;
+ border-color: var(--accent-light);
+ box-shadow: 0 4px 12px var(--accent-light), 0 0 0 1px var(--accent-glow) inset;
+ transform: translateX(4px);
+}
+
+.tree-node-row.disabled {
+ opacity: 0.35;
+}
+
+.tree-node-row.disabled .tree-node-name {
+ text-decoration: line-through;
+ text-decoration-color: var(--text-muted);
+}
+
+.tree-node-icon {
+ font-size: 15px;
+ flex-shrink: 0;
+ width: 22px;
+ text-align: center;
+ line-height: 1;
+}
+
+.tree-node-name {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ line-height: 1.3;
+}
+
+.tree-node-badge {
+ font-size: 10px;
+ padding: 1px 7px;
+ border-radius: 10px;
+ font-weight: 700;
+ flex-shrink: 0;
+ min-width: 20px;
+ text-align: center;
+}
+
+.tree-node-actions {
+ display: none;
+ gap: 2px;
+ flex-shrink: 0;
+}
+
+.tree-node-row:hover .tree-node-actions {
+ display: flex;
+}
+
+.tree-node-row:hover .tree-node-badge {
+ display: none;
+}
+
+.tree-action-btn {
+ padding: 2px;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 12px;
+ transition: all 0.15s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+}
+
+.tree-action-btn:hover {
+ background: var(--bg-active);
+ color: var(--text-primary);
+}
+
+.tree-action-btn.danger:hover {
+ background: rgba(239, 68, 68, 0.12);
+ color: var(--error);
+}
+
+/* 启用/禁用开关 */
+.tree-toggle-btn {
+ padding: 2px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 12px;
+ transition: all 0.15s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ opacity: 0;
+ color: var(--text-muted);
+}
+
+.tree-node-row:hover .tree-toggle-btn {
+ opacity: 1;
+}
+
+.tree-toggle-btn.visible {
+ opacity: 0.5;
+}
+
+.tree-toggle-btn:hover {
+ opacity: 1 !important;
+ background: var(--bg-active);
+}
+
+.tree-node-children {
+ margin-left: 4px;
+ border-left: 1px solid var(--border-light);
+ padding-left: 4px;
+}
+
+/* AI 生成按钮 */
+.tree-ai-generate-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 8px 14px;
+ margin: 6px 8px 8px;
+ border: 1px dashed var(--accent);
+ border-radius: 8px;
+ background: linear-gradient(135deg, var(--accent-light) 0%, transparent 100%);
+ color: var(--accent);
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 600;
+ transition: all 0.2s ease;
+ font-family: var(--font-ui);
+}
+
+.tree-ai-generate-btn:hover {
+ background: var(--accent-light);
+ border-style: solid;
+ box-shadow: 0 0 16px var(--accent-glow);
+ transform: translateY(-1px);
+}
+
+/* ==================== 角色卡片预览 ==================== */
+
+.character-card-preview {
+ padding: 22px;
+ border-radius: 12px;
+ margin-bottom: 20px;
+ position: relative;
+ overflow: hidden;
+}
+
+.character-card-preview::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ opacity: 0.08;
+ background: linear-gradient(135deg, currentColor 0%, transparent 50%);
+ pointer-events: none;
+}
+
+.character-card-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 16px;
+ position: relative;
+ z-index: 1;
+}
+
+.character-card-avatar {
+ width: 56px;
+ height: 56px;
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24px;
+ color: white;
+ flex-shrink: 0;
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
+ letter-spacing: 0;
+ font-weight: 700;
+}
+
+.character-card-info {
+ flex: 1;
+}
+
+.character-card-name {
+ font-size: 20px;
+ font-weight: 700;
+ margin-bottom: 6px;
+ color: var(--text-primary);
+ letter-spacing: 0.5px;
+}
+
+.character-card-role {
+ display: inline-flex;
+ padding: 3px 12px;
+ border-radius: 12px;
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.3px;
+}
+
+.character-card-quickinfo {
+ display: flex;
+ gap: 20px;
+ flex-wrap: wrap;
+ position: relative;
+ z-index: 1;
+}
+
+.character-card-quickinfo .info-item {
+ font-size: 12px;
+ color: var(--text-secondary);
+ display: flex;
+ gap: 4px;
+}
+
+.character-card-quickinfo .info-label {
+ color: var(--text-muted);
+ font-weight: 500;
+}
+
+/* ==================== 字段分组 ==================== */
+
+.field-group {
+ border: 1px solid var(--border-light);
+ border-radius: 10px;
+ margin-bottom: 12px;
+ overflow: hidden;
+ transition: box-shadow 0.2s ease;
+}
+
+.field-group:not(.collapsed):hover {
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
+}
+
+.field-group.collapsed .field-group-content {
+ display: none;
+}
+
+.field-group-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 11px 16px;
+ background: var(--bg-secondary);
+ cursor: pointer;
+ transition: all 0.15s ease;
+ user-select: none;
+}
+
+.field-group-header:hover {
+ background: var(--bg-hover);
+}
+
+.field-group-header h4 {
+ font-size: 13px;
+ font-weight: 600;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--text-primary);
+}
+
+.field-group-chevron {
+ font-size: 10px;
+ color: var(--text-muted);
+ transition: transform 0.2s ease;
+}
+
+.field-group.collapsed .field-group-chevron {
+ transform: rotate(-90deg);
+}
+
+.field-group-content {
+ padding: 14px 16px;
+}
+
+/* AI 字段填写按钮 */
+.field-ai-btn {
+ padding: 3px 8px;
+ border: 1px solid var(--accent);
+ background: transparent;
+ color: var(--accent);
+ cursor: pointer;
+ font-size: 11px;
+ border-radius: 4px;
+ transition: all 0.15s ease;
+ opacity: 0.5;
+ font-weight: 500;
+}
+
+.field-ai-btn:hover {
+ opacity: 1;
+ background: var(--accent-light);
+ box-shadow: 0 0 8px var(--accent-glow);
+}
+
+/* ==================== 上下文预览面板 ==================== */
+
+.context-preview {
+ background: var(--bg-secondary);
+ border-radius: 8px;
+ border: 1px solid var(--border-light);
+ margin: 8px 0;
+ overflow: hidden;
+}
+
+.context-preview-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ transition: all 0.15s ease;
+ user-select: none;
+}
+
+.context-preview-header:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.context-preview-body {
+ padding: 6px 12px 10px;
+ border-top: 1px solid var(--border-light);
+}
+
+.context-module {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 3px 0;
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+.context-module .module-label {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.context-module .module-tokens {
+ font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
+ font-size: 10px;
+ color: var(--text-muted);
+ background: var(--bg-secondary);
+ padding: 1px 5px;
+ border-radius: 4px;
+}
+
+.context-module.empty {
+ opacity: 0.35;
+}
+
+/* Token 进度条 */
+.token-bar {
+ height: 4px;
+ border-radius: 2px;
+ background: var(--border-light);
+ margin: 8px 0 4px;
+ overflow: hidden;
+}
+
+.token-bar-fill {
+ height: 100%;
+ border-radius: 2px;
+ background: linear-gradient(90deg, var(--accent), var(--accent-hover));
+ transition: width 0.4s ease;
+}
+
+.token-bar-fill.warning {
+ background: linear-gradient(90deg, #f59e0b, #f97316);
+}
+
+.token-bar-fill.danger {
+ background: linear-gradient(90deg, #ef4444, #dc2626);
+}
+
+.token-summary {
+ display: flex;
+ justify-content: space-between;
+ font-size: 10px;
+ color: var(--text-muted);
+ font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
+}
+
+/* ==================== 自由对话输入 ==================== */
+
+.chat-input-area {
+ width: 100%;
+ min-height: 80px;
+ max-height: 160px;
+ padding: 12px 14px;
+ border: 1px solid var(--border-light);
+ border-radius: 10px;
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ font-size: 14px;
+ font-family: var(--font-ui);
+ resize: vertical;
+ outline: none;
+ transition: all 0.2s ease;
+ margin-bottom: 8px;
+ line-height: 1.7;
+}
+
+.chat-input-area:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px var(--accent-light), 0 0 20px rgba(196, 149, 106, 0.08);
+}
+
+.chat-input-area::placeholder {
+ color: var(--text-muted);
+}
+
+/* ==================== 写作模式卡片 ==================== */
+
+.writing-mode-card {
+ flex: 1;
+ padding: 12px 14px;
+ border: 1px solid var(--border-light);
+ border-radius: 10px;
+ background: var(--bg-primary);
+ cursor: pointer;
+ text-align: left;
+ transition: all 0.2s ease;
+ font-family: var(--font-ui);
+}
+
+.writing-mode-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+}
+
+.writing-mode-card.active {
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
+}
+
+/* ==================== 设定集空状态 ==================== */
+
+.settings-empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 24px;
+ text-align: center;
+ color: var(--text-muted);
+}
+
+.settings-empty-state .empty-icon {
+ font-size: 56px;
+ margin-bottom: 20px;
+ opacity: 0.4;
+ filter: grayscale(0.3);
+}
+
+.settings-empty-state h3 {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text-secondary);
+ margin-bottom: 8px;
+}
+
+.settings-empty-state p {
+ font-size: 13px;
+ line-height: 1.7;
+ max-width: 300px;
+ color: var(--text-muted);
+}
+
+/* ==================== Inline AI 内联写作助手 ==================== */
+
+.inline-ai-trigger {
+ position: fixed;
+ bottom: 24px;
+ right: 24px;
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ border: none;
+ background: var(--accent);
+ color: #fff;
+ font-size: 20px;
+ cursor: pointer;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+ z-index: 100;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.inline-ai-trigger:hover {
+ transform: scale(1.1);
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
+}
+
+.inline-ai-popover {
+ position: fixed;
+ width: 360px;
+ max-width: calc(100vw - 32px);
+ background: var(--bg-primary);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.08);
+ z-index: 200;
+ padding: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ backdrop-filter: blur(12px);
+ animation: popoverIn 0.15s ease-out;
+}
+
+@keyframes popoverIn {
+ from {
+ opacity: 0;
+ transform: translateY(8px) scale(0.97);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+.inline-ai-modes {
+ display: flex;
+ gap: 4px;
+ flex-wrap: wrap;
+}
+
+.inline-ai-mode-btn {
+ padding: 4px 10px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.15s;
+ font-family: var(--font-ui);
+}
+
+.inline-ai-mode-btn:hover {
+ background: var(--bg-tertiary);
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+.inline-ai-mode-btn.active {
+ background: var(--accent-light);
+ border-color: var(--accent);
+ color: var(--accent);
+ font-weight: 600;
+}
+
+.inline-ai-mode-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.inline-ai-input-row {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+}
+
+.inline-ai-input {
+ flex: 1;
+ padding: 7px 10px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ outline: none;
+ transition: border-color 0.15s;
+}
+
+.inline-ai-input:focus {
+ border-color: var(--accent);
+}
+
+.inline-ai-input::placeholder {
+ color: var(--text-muted);
+ font-size: 12px;
+}
+
+.inline-ai-go-btn {
+ padding: 7px 14px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: var(--accent);
+ color: #fff;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: all 0.15s;
+ font-family: var(--font-ui);
+}
+
+.inline-ai-go-btn:hover {
+ filter: brightness(1.1);
+}
+
+.inline-ai-stop-btn {
+ padding: 7px 14px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: var(--error);
+ color: #fff;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: all 0.15s;
+ font-family: var(--font-ui);
+}
+
+.inline-ai-stop-btn:hover {
+ filter: brightness(1.1);
+}
+
+.inline-ai-status {
+ font-size: 12px;
+ color: var(--accent);
+ padding: 2px 4px;
+ animation: pulse 1.5s ease-in-out infinite;
+}
+
+@keyframes pulse {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+.inline-ai-hint {
+ font-size: 11px;
+ color: var(--text-muted);
+ padding: 2px 4px;
+}
+
+/* ==================== Inline 参考面板 ==================== */
+
+.inline-context-panel {
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ overflow: hidden;
+}
+
+.inline-context-toggle {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ width: 100%;
+ padding: 5px 8px;
+ border: none;
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ font-size: 12px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: all 0.15s ease;
+ text-align: left;
+}
+
+.inline-context-toggle:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.inline-context-chevron {
+ font-size: 9px;
+ color: var(--text-muted);
+ width: 12px;
+ display: inline-flex;
+ justify-content: center;
+}
+
+.inline-context-count {
+ color: var(--text-muted);
+ font-size: 11px;
+ margin-left: auto;
+}
+
+.inline-context-list {
+ max-height: 180px;
+ overflow-y: auto;
+ border-top: 1px solid var(--border-light);
+ padding: 4px 0;
+}
+
+.inline-context-group {
+ margin-bottom: 2px;
+}
+
+.inline-context-group-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 3px 8px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: background 0.1s;
+}
+
+.inline-context-group-header:hover {
+ background: var(--bg-hover);
+}
+
+.inline-context-group-header input[type="checkbox"] {
+ width: 13px;
+ height: 13px;
+ accent-color: var(--accent);
+ cursor: pointer;
+ flex-shrink: 0;
+}
+
+.inline-context-group-name {
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.inline-context-group-count {
+ color: var(--text-muted);
+ font-size: 10px;
+ font-weight: 400;
+ flex-shrink: 0;
+}
+
+.inline-context-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 2px 8px 2px 24px;
+ font-size: 11px;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: background 0.1s;
+}
+
+.inline-context-item:hover {
+ background: var(--bg-hover);
+}
+
+.inline-context-item input[type="checkbox"] {
+ width: 12px;
+ height: 12px;
+ accent-color: var(--accent);
+ cursor: pointer;
+ flex-shrink: 0;
+}
+
+.inline-context-item-name {
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* ==================== 帮助文档面板 ==================== */
+
+.help-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 2000;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 0.15s;
+}
+
+.help-panel {
+ width: 900px;
+ max-width: 95vw;
+ height: 80vh;
+ max-height: 90vh;
+ background: var(--bg-primary);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.25);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ animation: slideUp 0.2s ease-out;
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(20px);
+ opacity: 0.8;
+ }
+
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+.help-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px;
+ border-bottom: 1px solid var(--border-light);
+ flex-shrink: 0;
+}
+
+.help-header h2 {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.help-close-btn {
+ width: 32px;
+ height: 32px;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 18px;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all 0.15s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.help-close-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.help-body {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+.help-nav {
+ width: 200px;
+ min-width: 200px;
+ border-right: 1px solid var(--border-light);
+ padding: 12px 8px;
+ overflow-y: auto;
+ flex-shrink: 0;
+}
+
+.help-nav-item {
+ display: block;
+ width: 100%;
+ padding: 10px 12px;
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 13px;
+ font-weight: 500;
+ text-align: left;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all 0.15s;
+ margin-bottom: 2px;
+}
+
+.help-nav-item:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.help-nav-item.active {
+ background: var(--accent-light);
+ color: var(--accent);
+ font-weight: 600;
+}
+
+.help-content {
+ flex: 1;
+ padding: 24px 32px;
+ overflow-y: auto;
+}
+
+/* 帮助文档内部 Markdown 样式 */
+.help-markdown h2 {
+ font-size: 20px;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin: 0 0 16px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--border-light);
+}
+
+.help-markdown h3 {
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 20px 0 8px;
+}
+
+.help-markdown p {
+ font-size: 14px;
+ line-height: 1.7;
+ color: var(--text-secondary);
+ margin: 6px 0;
+}
+
+.help-markdown strong {
+ color: var(--text-primary);
+ font-weight: 600;
+}
+
+.help-markdown code {
+ background: var(--bg-tertiary, #f4f4f5);
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-family: "SF Mono", "Cascadia Code", monospace;
+ font-size: 0.9em;
+ color: var(--accent);
+}
+
+.help-markdown table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 12px 0;
+ font-size: 13px;
+}
+
+.help-markdown th {
+ background: var(--bg-secondary);
+ padding: 8px 12px;
+ text-align: left;
+ font-weight: 600;
+ color: var(--text-primary);
+ border-bottom: 2px solid var(--border-light);
+}
+
+.help-markdown td {
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--border-light);
+ color: var(--text-secondary);
+}
+
+.help-markdown tr:hover td {
+ background: var(--bg-hover);
+}
+
+.help-markdown blockquote {
+ border-left: 3px solid var(--accent);
+ padding: 8px 16px;
+ margin: 12px 0;
+ background: var(--accent-light);
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
+ color: var(--text-secondary);
+ font-size: 13px;
+}
+
+.help-markdown s {
+ text-decoration: line-through;
+ color: var(--text-muted);
+}
+
+/* ==================== Ghost Text 样式 ==================== */
+
+.ghost-text {
+ background: rgba(46, 160, 67, 0.15);
+ border-left: 2px solid rgba(46, 160, 67, 0.6);
+ padding-left: 1px;
+ color: var(--text-primary);
+ text-decoration: none;
+ transition: background 0.3s ease;
+}
+
+.ghost-text+.ghost-text {
+ border-left: none;
+}
+
+/* 内联操作栏 — Cursor 风格,定位在幽灵文本末尾 */
+.ghost-inline-bar {
+ position: fixed;
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 8px;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-medium);
+ border-radius: 8px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.04);
+ animation: ghostBarFadeIn 0.15s ease;
+}
+
+@keyframes ghostBarFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(4px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.ghost-inline-bar .ghost-accept-btn,
+.ghost-inline-bar .ghost-reject-btn,
+.ghost-inline-bar .ghost-regen-btn {
+ padding: 4px 10px;
+ font-size: 12px;
+}
+
+.ghost-bar-shortcut {
+ font-size: 10px;
+ color: var(--text-muted);
+ margin-left: 4px;
+ white-space: nowrap;
+}
+
+/* Ghost 操作栏 */
+.ghost-action-bar {
+ display: flex;
+ gap: 6px;
+ padding: 4px 0;
+}
+
+.ghost-accept-btn,
+.ghost-reject-btn,
+.ghost-regen-btn {
+ padding: 6px 14px;
+ border: none;
+ border-radius: var(--radius-sm);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ font-weight: 500;
+}
+
+.ghost-accept-btn {
+ background: var(--success);
+ color: white;
+}
+
+.ghost-accept-btn:hover {
+ filter: brightness(1.1);
+}
+
+.ghost-reject-btn {
+ background: transparent;
+ color: var(--error);
+ border: 1px solid var(--error);
+}
+
+.ghost-reject-btn:hover {
+ background: rgba(196, 106, 106, 0.1);
+}
+
+.ghost-regen-btn {
+ background: transparent;
+ color: var(--text-secondary);
+ border: 1px solid var(--border-medium);
+}
+
+.ghost-regen-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+/* ==================== AI 侧栏浮动圆形图标 ==================== */
+
+.ai-sidebar-toggle {
+ position: fixed;
+ right: 18px;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 40;
+ width: 48px;
+ height: 48px;
+ border: none;
+ border-radius: 50%;
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
+ color: white;
+ font-size: 20px;
+ cursor: pointer;
+ box-shadow: 0 4px 16px rgba(196, 149, 106, 0.35), 0 2px 6px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: togglePulse 3s ease-in-out infinite;
+}
+
+.ai-sidebar-toggle:hover {
+ transform: translateY(-50%) scale(1.12);
+ box-shadow: 0 6px 24px rgba(196, 149, 106, 0.5), 0 3px 10px rgba(0, 0, 0, 0.15);
+ animation: none;
+}
+
+.ai-sidebar-toggle:active {
+ transform: translateY(-50%) scale(0.95);
+}
+
+@keyframes togglePulse {
+
+ 0%,
+ 100% {
+ box-shadow: 0 4px 16px rgba(196, 149, 106, 0.35), 0 2px 6px rgba(0, 0, 0, 0.1);
+ }
+
+ 50% {
+ box-shadow: 0 4px 24px rgba(196, 149, 106, 0.55), 0 2px 8px rgba(0, 0, 0, 0.12);
+ }
+}
+
+/* ==================== AI 对话侧栏 ==================== */
+
+.ai-sidebar {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 380px;
+ background: var(--bg-primary);
+ border-left: 1px solid var(--border-light);
+ display: flex;
+ flex-direction: column;
+ z-index: 30;
+ transform: translateX(0);
+ transition: transform var(--transition-normal), visibility 0s linear 0s;
+ box-shadow: -4px 0 20px rgba(0, 0, 0, 0.06);
+ visibility: visible;
+}
+
+.ai-sidebar.collapsed {
+ transform: translateX(100%);
+ pointer-events: none;
+ visibility: hidden;
+ transition: transform var(--transition-normal), visibility 0s linear 0.3s;
+}
+
+@keyframes slideInRight {
+ from {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+.ai-sidebar-header {
+ padding: 16px 20px;
+ background: linear-gradient(135deg, var(--accent-light), transparent);
+ border-bottom: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.ai-sidebar-title {
+ font-weight: 700;
+ font-size: 16px;
+ color: var(--accent);
+ letter-spacing: 0.5px;
+}
+
+.ai-sidebar-tabs {
+ display: flex;
+ border-bottom: 1px solid var(--border-light);
+ padding: 0;
+ background: var(--bg-secondary);
+}
+
+.ai-sidebar-tab {
+ flex: 1;
+ padding: 12px 8px;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ border-bottom: 2.5px solid transparent;
+ transition: all var(--transition-fast);
+ text-align: center;
+ font-weight: 500;
+}
+
+.ai-sidebar-tab:hover {
+ color: var(--text-primary);
+ background: var(--bg-hover);
+}
+
+.ai-sidebar-tab.active {
+ color: var(--accent);
+ border-bottom-color: var(--accent);
+ font-weight: 600;
+ background: var(--bg-primary);
+}
+
+.ai-sidebar-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ min-height: 0;
+}
+
+/* ===== 对话控制栏 ===== */
+
+.chat-controls {
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--border-light);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ flex-shrink: 0;
+}
+
+.chat-control-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--text-secondary);
+ cursor: pointer;
+}
+
+.chat-control-item input[type="checkbox"] {
+ accent-color: var(--accent);
+ width: 14px;
+ height: 14px;
+}
+
+.chat-window-size-input {
+ width: 40px;
+ padding: 2px 4px;
+ border: 1px solid var(--border-light);
+ border-radius: 4px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 12px;
+ font-family: var(--font-ui);
+ text-align: center;
+}
+
+.chat-control-actions {
+ display: flex;
+ gap: 4px;
+}
+
+/* 小按钮 */
+.btn-mini {
+ padding: 4px 10px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 11px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+}
+
+.btn-mini:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.btn-mini.primary {
+ background: var(--accent);
+ color: white;
+ border-color: var(--accent);
+}
+
+.btn-mini.primary:hover {
+ filter: brightness(1.1);
+}
+
+.btn-mini.danger {
+ color: var(--error);
+ border-color: transparent;
+}
+
+.btn-mini.danger:hover {
+ background: rgba(196, 106, 106, 0.1);
+}
+
+.btn-mini:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+/* ===== 总结编辑器 ===== */
+
+.summary-editor {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--border-light);
+ background: var(--accent-light);
+ flex-shrink: 0;
+}
+
+.summary-editor-label {
+ font-size: 12px;
+ color: var(--accent);
+ font-weight: 600;
+ margin-bottom: 6px;
+}
+
+.summary-textarea {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid var(--border-medium);
+ border-radius: var(--radius-sm);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 12px;
+ font-family: var(--font-ui);
+ line-height: 1.5;
+ resize: vertical;
+}
+
+.summary-actions {
+ display: flex;
+ gap: 6px;
+ margin-top: 6px;
+ justify-content: flex-end;
+}
+
+/* ===== 聊天消息区 ===== */
+
+.chat-messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.chat-empty {
+ text-align: center;
+ color: var(--text-muted);
+ padding: 40px 20px;
+ font-size: 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: center;
+}
+
+.chat-empty div:first-child {
+ font-size: 28px;
+ color: var(--accent);
+}
+
+.chat-empty-hint {
+ font-size: 12px;
+}
+
+.chat-message {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.chat-message-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+.chat-check {
+ width: 13px;
+ height: 13px;
+ accent-color: var(--accent);
+ cursor: pointer;
+}
+
+.chat-role {
+ font-weight: 600;
+ font-size: 12px;
+}
+
+.chat-message.user .chat-role {
+ color: var(--text-secondary);
+}
+
+.chat-message.assistant .chat-role {
+ color: var(--accent);
+}
+
+.chat-message.system .chat-role {
+ color: var(--info);
+}
+
+.chat-time {
+ margin-left: auto;
+}
+
+/* 消息操作按钮 — 默认隐藏,hover 滑入 */
+.chat-msg-actions {
+ display: flex;
+ gap: 1px;
+ margin-left: auto;
+ opacity: 0;
+ transform: translateX(8px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ pointer-events: none;
+}
+
+.chat-message:hover .chat-msg-actions {
+ opacity: 1;
+ transform: translateX(0);
+ pointer-events: auto;
+}
+
+.btn-mini-icon {
+ background: none;
+ border: 1px solid transparent;
+ cursor: pointer;
+ font-size: 11px;
+ padding: 3px 5px;
+ border-radius: 6px;
+ opacity: 0.7;
+ transition: all 0.18s ease;
+ line-height: 1;
+}
+
+.btn-mini-icon:hover {
+ opacity: 1;
+ background: var(--bg-hover);
+ border-color: var(--border-light);
+ transform: scale(1.1);
+}
+
+.btn-mini-icon.danger:hover {
+ background: rgba(220, 60, 60, 0.12);
+ border-color: rgba(220, 60, 60, 0.25);
+}
+
+.btn-mini-icon:disabled {
+ opacity: 0.25;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* 已编辑标记 */
+.chat-edited-badge {
+ font-size: 10px;
+ color: var(--text-muted);
+ font-style: italic;
+ padding: 1px 6px;
+ background: rgba(128, 128, 128, 0.1);
+ border-radius: 8px;
+}
+
+/* 消息编辑模式 */
+.chat-message-editing {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ animation: fadeSlideIn 0.2s ease;
+}
+
+@keyframes fadeSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-4px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.chat-edit-textarea {
+ width: 100%;
+ padding: 10px 12px;
+ border: 2px solid var(--accent);
+ border-radius: 10px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 13px;
+ line-height: 1.6;
+ resize: vertical;
+ font-family: inherit;
+ box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 100, 130, 200), 0.12);
+ transition: border-color 0.2s, box-shadow 0.2s;
+}
+
+.chat-edit-textarea:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 4px rgba(var(--accent-rgb, 100, 130, 200), 0.18);
+}
+
+.chat-edit-actions {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.chat-edit-actions .btn-mini {
+ border-radius: 8px;
+ padding: 5px 14px;
+ font-size: 12px;
+ transition: all 0.15s ease;
+}
+
+/* 流式输出光标 */
+.chat-message.assistant .chat-bubble-content.streaming::after {
+ content: '▊';
+ animation: blink 0.8s steps(2) infinite;
+ color: var(--accent);
+ margin-left: 2px;
+}
+
+/* ===== 聊天 Markdown 渲染 ===== */
+
+.chat-bubble-content {
+ font-size: 13px;
+ line-height: 1.7;
+ word-break: break-word;
+ overflow-wrap: break-word;
+}
+
+.chat-md-p {
+ margin: 0 0 8px;
+}
+
+.chat-md-p:last-child {
+ margin-bottom: 0;
+}
+
+.chat-md-heading {
+ font-weight: 700;
+ margin: 12px 0 6px;
+ color: var(--text-primary);
+}
+
+h3.chat-md-heading {
+ font-size: 15px;
+}
+
+h4.chat-md-heading {
+ font-size: 14px;
+}
+
+h5.chat-md-heading {
+ font-size: 13px;
+}
+
+.chat-md-inline-code {
+ background: var(--bg-tertiary, rgba(0, 0, 0, 0.06));
+ padding: 1px 5px;
+ border-radius: 4px;
+ font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
+ font-size: 0.88em;
+ color: var(--accent);
+ word-break: break-all;
+}
+
+.chat-md-code-block {
+ margin: 8px 0;
+ border-radius: 8px;
+ overflow: hidden;
+ border: 1px solid var(--border-light);
+ background: var(--bg-secondary);
+}
+
+.chat-md-code-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 10px;
+ background: var(--bg-tertiary, rgba(0, 0, 0, 0.04));
+ border-bottom: 1px solid var(--border-light);
+}
+
+.chat-md-code-lang {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.chat-md-copy-btn {
+ padding: 2px 8px;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 10px;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.15s;
+ font-family: var(--font-ui);
+}
+
+.chat-md-copy-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.chat-md-pre {
+ margin: 0;
+ padding: 10px 12px;
+ overflow-x: auto;
+ font-size: 12px;
+ line-height: 1.5;
+ font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
+ color: var(--text-primary);
+}
+
+.chat-md-pre code {
+ font-family: inherit;
+}
+
+.chat-md-table-wrap {
+ overflow-x: auto;
+ margin: 8px 0;
+ border-radius: 6px;
+ border: 1px solid var(--border-light);
+}
+
+.chat-md-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 12px;
+}
+
+.chat-md-table th {
+ background: var(--bg-secondary);
+ padding: 6px 10px;
+ text-align: left;
+ font-weight: 600;
+ color: var(--text-primary);
+ border-bottom: 2px solid var(--border-light);
+ white-space: nowrap;
+}
+
+.chat-md-table td {
+ padding: 5px 10px;
+ border-bottom: 1px solid var(--border-light);
+ color: var(--text-secondary);
+}
+
+.chat-md-table tr:last-child td {
+ border-bottom: none;
+}
+
+.chat-md-table tr:hover td {
+ background: var(--bg-hover);
+}
+
+.chat-md-blockquote {
+ margin: 8px 0;
+ padding: 6px 12px;
+ border-left: 3px solid var(--accent);
+ background: var(--accent-light);
+ border-radius: 0 6px 6px 0;
+ color: var(--text-secondary);
+ font-size: 12px;
+}
+
+.chat-md-blockquote p {
+ margin: 0;
+}
+
+.chat-md-ul,
+.chat-md-ol {
+ margin: 4px 0;
+ padding-left: 20px;
+}
+
+.chat-md-ul li,
+.chat-md-ol li {
+ margin-bottom: 2px;
+}
+
+.chat-md-link {
+ color: var(--accent);
+ text-decoration: none;
+ border-bottom: 1px dotted var(--accent);
+ transition: opacity 0.15s;
+}
+
+.chat-md-link:hover {
+ opacity: 0.8;
+}
+
+
+/* ===== 设定集操作卡片 ===== */
+
+.settings-action-card {
+ margin: 8px 0;
+ padding: 10px 12px;
+ border: 1px solid var(--accent-glow, rgba(100, 130, 200, 0.25));
+ border-radius: 10px;
+ background: linear-gradient(135deg, var(--accent-light, rgba(100, 130, 200, 0.06)), transparent);
+}
+
+.settings-action-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 6px;
+}
+
+.settings-action-badge {
+ display: inline-block;
+ padding: 1px 8px;
+ border-radius: 10px;
+ font-size: 10px;
+ font-weight: 700;
+ background: var(--accent);
+ color: white;
+ text-transform: uppercase;
+ letter-spacing: 0.3px;
+}
+
+.settings-action-cat {
+ font-size: 11px;
+ color: var(--text-muted);
+}
+
+.settings-action-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.settings-action-preview {
+ margin: 6px 0 8px;
+ padding: 6px 8px;
+ background: var(--bg-secondary);
+ border-radius: 6px;
+ font-size: 11px;
+ line-height: 1.5;
+}
+
+.settings-action-field {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 4px;
+}
+
+.settings-action-field-key {
+ color: var(--text-muted);
+ font-weight: 600;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.settings-action-field-val {
+ color: var(--text-secondary);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.settings-action-apply {
+ width: 100%;
+ text-align: center;
+ border-radius: 8px;
+ padding: 5px 0;
+ font-size: 12px;
+}
+
+.settings-action-apply:disabled {
+ background: var(--bg-secondary);
+ color: var(--text-muted);
+ border-color: var(--border-light);
+ cursor: default;
+ opacity: 0.8;
+}
+
+/* ===== 会话列表面板 ===== */
+.session-list-panel {
+ border-bottom: 1px solid var(--border-light);
+ background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 100%);
+ max-height: 300px;
+ display: flex;
+ flex-direction: column;
+ animation: slideDown 0.2s ease;
+}
+
+@keyframes slideDown {
+ from {
+ max-height: 0;
+ opacity: 0;
+ }
+
+ to {
+ max-height: 300px;
+ opacity: 1;
+ }
+}
+
+.session-list-header {
+ padding: 10px 14px 6px;
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.session-list {
+ overflow-y: auto;
+ flex: 1;
+ padding: 2px 8px 8px;
+}
+
+.session-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.18s ease;
+ font-size: 13px;
+ margin-bottom: 2px;
+ border: 1px solid transparent;
+ position: relative;
+}
+
+.session-item:hover {
+ background: var(--bg-hover);
+ border-color: var(--border-light);
+}
+
+.session-item.active {
+ background: linear-gradient(135deg, var(--accent-light), rgba(var(--accent-rgb, 100, 130, 200), 0.08));
+ border-color: var(--accent-glow);
+ box-shadow: 0 1px 4px rgba(var(--accent-rgb, 100, 130, 200), 0.1);
+}
+
+.session-item.active::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 3px;
+ height: 60%;
+ background: var(--accent);
+ border-radius: 0 3px 3px 0;
+}
+
+.session-item-info {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.session-item-title {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.session-item-meta {
+ font-size: 11px;
+ color: var(--text-muted);
+ display: flex;
+ gap: 6px;
+ align-items: center;
+}
+
+.session-item-actions {
+ display: flex;
+ gap: 2px;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+}
+
+.session-item:hover .session-item-actions {
+ opacity: 1;
+}
+
+.session-rename-input {
+ flex: 1;
+ padding: 6px 10px;
+ border: 2px solid var(--accent);
+ border-radius: 8px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: inherit;
+ box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 100, 130, 200), 0.12);
+}
+
+/* ===== 思维链折叠显示 ===== */
+.chat-thinking-block {
+ margin: 4px 0;
+ border-radius: 8px;
+ border: 1px solid rgba(255, 200, 60, 0.2);
+ background: rgba(255, 200, 60, 0.05);
+ overflow: hidden;
+}
+
+.chat-thinking-toggle {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ width: 100%;
+ padding: 6px 10px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 12px;
+ color: var(--text-secondary);
+ text-align: left;
+ transition: background 0.15s;
+}
+
+.chat-thinking-toggle:hover {
+ background: rgba(255, 200, 60, 0.08);
+}
+
+.thinking-chevron {
+ font-size: 9px;
+ transition: transform 0.2s ease;
+ display: inline-block;
+}
+
+.thinking-chevron.open {
+ transform: rotate(90deg);
+}
+
+.thinking-preview {
+ color: var(--text-muted);
+ font-size: 11px;
+ opacity: 0.7;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
+ min-width: 0;
+}
+
+.chat-thinking-content {
+ padding: 8px 12px;
+ font-size: 12px;
+ line-height: 1.6;
+ color: var(--text-secondary);
+ white-space: pre-wrap;
+ word-break: break-word;
+ max-height: 200px;
+ overflow-y: auto;
+ border-top: 1px solid rgba(255, 200, 60, 0.15);
+ animation: fadeSlideIn 0.15s ease;
+}
+
+/* ===== 变体导航 < 1/3 > ===== */
+.chat-variant-nav {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 3px 0;
+ margin-top: 2px;
+}
+
+.variant-indicator {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-weight: 600;
+ min-width: 36px;
+ text-align: center;
+ user-select: none;
+}
+
+.chat-variant-nav .btn-mini-icon {
+ font-size: 9px;
+ padding: 2px 6px;
+ border-radius: 50%;
+}
+
+.chat-bubble-content {
+ padding: 12px 16px;
+ border-radius: var(--radius-lg);
+ font-size: 13px;
+ line-height: 1.6;
+ word-break: break-word;
+ box-shadow: var(--shadow-sm);
+ transition: box-shadow var(--transition-normal);
+}
+
+.chat-bubble-content:hover {
+ box-shadow: var(--shadow-md);
+}
+
+.chat-message.user .chat-bubble-content {
+ background: linear-gradient(135deg, var(--accent-light) 0%, rgba(var(--accent-rgb, 200, 150, 100), 0.05) 100%);
+ color: var(--text-primary);
+ border: 1px solid var(--accent-glow);
+ border-bottom-right-radius: 4px;
+}
+
+.chat-message.assistant .chat-bubble-content {
+ background: var(--bg-card);
+ color: var(--text-primary);
+ border: 1px solid var(--border-glass);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border-bottom-left-radius: 4px;
+}
+
+.chat-message.system .chat-bubble-content {
+ background: rgba(106, 150, 196, 0.06);
+ color: var(--text-secondary);
+ border: 1px solid rgba(106, 150, 196, 0.15);
+ font-size: 12px;
+ border-radius: var(--radius-sm);
+}
+
+.streaming-cursor {
+ animation: blink 0.8s infinite;
+ color: var(--accent);
+}
+
+@keyframes blink {
+
+ 0%,
+ 50% {
+ opacity: 1;
+ }
+
+ 51%,
+ 100% {
+ opacity: 0;
+ }
+}
+
+/* ===== 输入区 ===== */
+
+.chat-input-area {
+ padding: 16px;
+ border-top: 1px solid var(--border-glass);
+ display: flex;
+ gap: 12px;
+ align-items: flex-end;
+ flex-shrink: 0;
+ background: var(--bg-glass);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+}
+
+.chat-input {
+ flex: 1;
+ padding: 12px 16px;
+ border: 1px solid var(--border-glass);
+ border-radius: 20px;
+ background: var(--bg-card);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ line-height: 1.5;
+ resize: none;
+ outline: none;
+ transition: all var(--transition-normal);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.02);
+}
+
+.chat-input:focus {
+ border-color: var(--accent);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.02), 0 0 0 3px var(--accent-light);
+}
+
+.chat-input::placeholder {
+ color: var(--text-muted);
+}
+
+.chat-send-btn {
+ width: 44px;
+ height: 44px;
+ border: none;
+ border-radius: 50%;
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
+ color: white;
+ font-size: 18px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+ flex-shrink: 0;
+ box-shadow: 0 4px 12px var(--accent-glow);
+}
+
+.chat-send-btn:hover:not(:disabled) {
+ transform: translateY(-2px) scale(1.05);
+ box-shadow: 0 8px 20px var(--accent-glow);
+}
+
+.chat-send-btn:active:not(:disabled) {
+ transform: scale(0.95);
+}
+
+.chat-send-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ box-shadow: none;
+ background: var(--border-medium);
+}
+
+/* ===== 存档 Tab ===== */
+
+.archive-search-bar {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--border-light);
+ flex-shrink: 0;
+}
+
+.archive-search-input {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ outline: none;
+ transition: border-color var(--transition-fast);
+}
+
+.archive-search-input:focus {
+ border-color: var(--accent);
+}
+
+.archive-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.archive-item {
+ padding: 10px 12px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ background: var(--bg-card);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+}
+
+.archive-item:hover {
+ border-color: var(--accent-glow);
+ box-shadow: var(--shadow-sm);
+}
+
+.archive-item-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 6px;
+}
+
+.archive-status {
+ font-size: 11px;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 10px;
+}
+
+.archive-status.accepted {
+ background: rgba(107, 158, 107, 0.15);
+ color: var(--success);
+}
+
+.archive-status.rejected {
+ background: rgba(196, 106, 106, 0.15);
+ color: var(--error);
+}
+
+.archive-status.pending {
+ background: var(--accent-light);
+ color: var(--accent);
+}
+
+.archive-mode {
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.archive-time {
+ font-size: 11px;
+ color: var(--text-muted);
+ margin-left: auto;
+}
+
+.archive-preview {
+ font-size: 13px;
+ line-height: 1.5;
+ color: var(--text-secondary);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.archive-actions {
+ margin-top: 8px;
+ display: flex;
+ gap: 6px;
+}
+
+/* ===== 上下文勾选面板(📚 参考 Tab) ===== */
+
+.context-budget-bar {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--border-light);
+ flex-shrink: 0;
+}
+
+.context-budget-label {
+ display: flex;
+ justify-content: space-between;
+ font-size: 11px;
+ color: var(--text-secondary);
+ margin-bottom: 5px;
+ font-weight: 500;
+}
+
+.context-over-budget {
+ color: var(--error) !important;
+ font-weight: 700;
+}
+
+.context-budget-track {
+ width: 100%;
+ height: 6px;
+ background: var(--bg-tertiary, #e5e5e5);
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.context-budget-fill {
+ height: 100%;
+ border-radius: 3px;
+ background: linear-gradient(90deg, var(--success), var(--accent));
+ transition: width 0.3s ease;
+}
+
+.context-budget-fill.over {
+ background: linear-gradient(90deg, var(--error), #d9534f);
+}
+
+.context-search-bar {
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--border-light);
+ flex-shrink: 0;
+}
+
+.context-search-input {
+ width: 100%;
+ padding: 7px 10px;
+ border: 1px solid var(--border-light);
+ border-radius: var(--radius-md);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: var(--font-ui);
+ outline: none;
+ transition: border-color var(--transition-fast);
+}
+
+.context-search-input:focus {
+ border-color: var(--accent);
+}
+
+.context-groups {
+ flex: 1;
+ overflow-y: auto;
+ padding: 4px 0;
+}
+
+.context-group {
+ border-bottom: 1px solid var(--border-light);
+}
+
+.context-group-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 12px;
+ cursor: pointer;
+ transition: background var(--transition-fast);
+ user-select: none;
+}
+
+.context-group-header:hover {
+ background: var(--bg-hover);
+}
+
+.context-collapse-icon {
+ font-size: 10px;
+ color: var(--text-muted);
+ width: 14px;
+ text-align: center;
+ flex-shrink: 0;
+}
+
+.context-group-check {
+ width: 14px;
+ height: 14px;
+ accent-color: var(--accent);
+ cursor: pointer;
+ flex-shrink: 0;
+}
+
+.context-group-name {
+ flex: 1;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.context-group-tokens {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-family: var(--font-mono, monospace);
+ flex-shrink: 0;
+ min-width: 40px;
+ text-align: right;
+}
+
+.context-group-items {
+ padding: 0 12px 6px 20px;
+}
+
+.context-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 8px;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: background var(--transition-fast);
+}
+
+.context-item:hover {
+ background: var(--bg-hover);
+}
+
+.context-item-check {
+ width: 13px;
+ height: 13px;
+ accent-color: var(--accent);
+ cursor: pointer;
+ flex-shrink: 0;
+}
+
+.context-item-name {
+ flex: 1;
+ font-size: 12px;
+ color: var(--text-secondary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.context-item-tokens {
+ font-size: 11px;
+ color: var(--text-muted);
+ font-family: var(--font-mono, monospace);
+ flex-shrink: 0;
+ min-width: 36px;
+ text-align: right;
+}
+
+.context-actions {
+ display: flex;
+ gap: 6px;
+ padding: 8px 12px;
+ border-top: 1px solid var(--border-light);
+ flex-shrink: 0;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+/* ===== 响应式 ===== */
+@media (max-width: 900px) {
+ .ai-sidebar {
+ position: fixed;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 50;
+ box-shadow: var(--shadow-lg);
+ width: 320px;
+ }
+}
+
+/* ==================== 引导向导 (Tour Overlay) ==================== */
+
+.tour-portal {
+ position: fixed;
+ inset: 0;
+ z-index: 10000;
+ pointer-events: none;
+ /* Let clicks pass through the SVG mask */
+}
+
+/* 阻止背后的互动但允许高亮区域互动 */
+.tour-portal>* {
+ pointer-events: auto;
+}
+
+.tour-svg-mask {
+ position: absolute;
+ inset: 0;
+ pointer-events: all;
+ /* block clicks on dark area */
+ cursor: default;
+}
+
+.tour-overlay-bg {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.6);
+ backdrop-filter: blur(8px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10000;
+ animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.tour-intro-modal {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-light);
+ border-radius: 20px;
+ padding: 48px;
+ max-width: 460px;
+ width: 90%;
+ text-align: center;
+ box-shadow: 0 32px 64px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.05) inset;
+ animation: slideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.tour-intro-icon {
+ font-size: 64px;
+ margin-bottom: 24px;
+ line-height: 1;
+ text-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
+ animation: floatIcon 3s ease-in-out infinite;
+}
+
+@keyframes floatIcon {
+
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+
+ 50% {
+ transform: translateY(-10px);
+ }
+}
+
+.tour-intro-modal h2 {
+ font-size: 26px;
+ font-weight: 800;
+ color: var(--text-primary);
+ margin-bottom: 12px;
+ letter-spacing: -0.5px;
+}
+
+.tour-intro-modal p {
+ font-size: 16px;
+ color: var(--text-secondary);
+ line-height: 1.6;
+}
+
+.tour-intro-modal .tour-intro-sub {
+ font-size: 14px;
+ color: var(--text-muted);
+ margin-top: 24px;
+ margin-bottom: 32px;
+}
+
+.tour-intro-actions {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+}
+
+/* Tooltip floating box */
+.tour-tooltip {
+ position: absolute;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-medium);
+ border-radius: 16px;
+ padding: 24px;
+ width: 340px;
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05) inset;
+ z-index: 10001;
+ pointer-events: auto;
+ animation: scaleIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+@keyframes scaleIn {
+ from {
+ transform: scale(0.95) translateY(10px);
+ opacity: 0;
+ }
+
+ to {
+ transform: scale(1) translateY(0);
+ opacity: 1;
+ }
+}
+
+.tour-tooltip-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.tour-tooltip-header h3 {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin: 0;
+}
+
+.tour-step-counter {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent);
+ background: var(--bg-hover);
+ padding: 4px 10px;
+ border-radius: 20px;
+}
+
+.tour-tooltip-content {
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.6;
+ margin-bottom: 24px;
+}
+
+.tour-tooltip-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.tour-skip-text {
+ background: none;
+ border: none;
+ color: var(--text-muted);
+ font-size: 13px;
+ cursor: pointer;
+ padding: 0;
+ transition: color 0.2s;
+}
+
+.tour-skip-text:hover {
+ color: var(--text-primary);
+ text-decoration: underline;
+}
+
+.tour-btn {
+ padding: 10px 20px;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ border: none;
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.tour-btn.primary {
+ background: var(--accent);
+ color: white;
+ box-shadow: 0 4px 12px rgba(var(--accent-rgb, 107, 76, 222), 0.3);
+}
+
+.tour-btn.primary:hover {
+ background: var(--accent-hover);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(var(--accent-rgb, 107, 76, 222), 0.4);
+}
+
+.tour-btn.ghost {
+ background: transparent;
+ color: var(--text-secondary);
+ border: 1px solid var(--border-medium);
+}
+
+.tour-btn.ghost:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+/* ==================== Welcome Modal (Onboarding) ==================== */
+
+.welcome-modal-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 9999;
+ background: var(--bg-glass);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.welcome-modal-container {
+ max-width: 600px;
+ width: 90%;
+ background: var(--bg-card);
+ border: 1px solid var(--border-glass);
+ border-radius: 24px;
+ padding: 48px;
+ box-shadow: var(--shadow-page);
+ text-align: center;
+ position: relative;
+ overflow: hidden;
+ transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.5s;
+}
+
+.welcome-modal-container.step-2 {
+ max-width: 800px;
+}
+
+.welcome-step {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+}
+
+.welcome-title {
+ font-size: 28px;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: 32px;
+ letter-spacing: -0.5px;
+}
+
+.welcome-subtitle {
+ font-size: 16px;
+ color: var(--text-secondary);
+ margin-bottom: 40px;
+}
+
+.welcome-section-title {
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--text-muted);
+ font-weight: 600;
+ margin-bottom: 20px;
+}
+
+.welcome-lang-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 16px;
+ width: 100%;
+}
+
+.welcome-card {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-light);
+ border-radius: 16px;
+ padding: 24px 16px;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ transition: all var(--transition-normal);
+}
+
+.welcome-card:hover {
+ transform: translateY(-4px);
+ background: var(--bg-hover);
+ box-shadow: var(--shadow-md);
+ border-color: var(--border-medium);
+}
+
+.welcome-card:active {
+ transform: scale(0.96);
+}
+
+.welcome-card.active {
+ border-color: var(--accent);
+ background: var(--accent-light);
+ box-shadow: 0 0 0 2px var(--accent-glow);
+}
+
+.welcome-icon {
+ font-size: 40px;
+}
+
+.welcome-label {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.welcome-theme-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 24px;
+ width: 100%;
+ margin-bottom: 40px;
+}
+
+.theme-card {
+ padding: 0;
+ overflow: hidden;
+ text-align: left;
+}
+
+.theme-preview {
+ width: 100%;
+ height: 140px;
+ background-size: cover;
+ background-position: center;
+ border-bottom: 1px solid var(--border-light);
+}
+
+.warm-preview {
+ background: linear-gradient(135deg, #faf8f5 0%, #efe9e0 100%);
+ position: relative;
+}
+
+.warm-preview::after {
+ content: '';
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ right: 20px;
+ bottom: 20px;
+ background: #fffdf9;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(44, 36, 23, 0.05);
+ border: 1px solid #e5ddd2;
+}
+
+.modern-preview {
+ background: linear-gradient(135deg, #f2f3f5 0%, #e8eaed 100%);
+ position: relative;
+}
+
+.modern-preview::after {
+ content: '';
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ right: 30%;
+ bottom: 20px;
+ background: rgba(255, 255, 255, 0.7);
+ backdrop-filter: blur(8px);
+ border-radius: 12px;
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.5);
+}
+
+.theme-info {
+ padding: 20px;
+}
+
+.theme-info h4 {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: 8px;
+}
+
+.theme-info p {
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+}
+
+.welcome-start-btn {
+ padding: 16px 48px;
+ font-size: 18px;
+ border-radius: 99px;
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
+ color: var(--text-inverse);
+ font-weight: 600;
+ border: none;
+ cursor: pointer;
+ transition: all var(--transition-normal);
+ box-shadow: 0 8px 24px var(--accent-glow);
+}
+
+.welcome-start-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 12px 32px var(--accent-glow);
+}
+
+.welcome-start-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+}
\ No newline at end of file
diff --git a/app/layout.js b/app/layout.js
new file mode 100644
index 0000000..99ff272
--- /dev/null
+++ b/app/layout.js
@@ -0,0 +1,42 @@
+'use client';
+
+import "./globals.css";
+import { useEffect, useState } from "react";
+
+// 内联脚本:在 HTML 解析阶段同步读取 theme,避免 hydration 不匹配和闪烁
+const themeInitScript = `
+(function() {
+ try {
+ var t = localStorage.getItem('author-theme') || 'light';
+ document.documentElement.setAttribute('data-theme', t);
+ } catch(e) {}
+})();
+`;
+
+export default function RootLayout({ children }) {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ return (
+
+
+ Author - AI辅助创作平台
+
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/app/lib/chat-sessions.js b/app/lib/chat-sessions.js
new file mode 100644
index 0000000..894bd4f
--- /dev/null
+++ b/app/lib/chat-sessions.js
@@ -0,0 +1,306 @@
+'use client';
+
+// ==================== 多会话管理模块 ====================
+
+import { get, set } from 'idb-keyval';
+
+const STORAGE_KEY = 'author-chat-sessions';
+
+function generateId(prefix = 'session') {
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
+}
+
+/**
+ * 从 IndexedDB 读取全部会话数据 (Async)
+ */
+export async function loadSessionStore() {
+ if (typeof window === 'undefined') return { activeSessionId: null, sessions: [] };
+ try {
+ let store = await get(STORAGE_KEY);
+ if (!store) {
+ // Fallback: migrate from localStorage
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) {
+ store = JSON.parse(raw);
+ await set(STORAGE_KEY, store);
+ }
+ }
+ if (store) return store;
+ } catch { }
+ return { activeSessionId: null, sessions: [] };
+}
+
+/**
+ * 保存全部会话数据到 IndexedDB (Async)
+ */
+export async function saveSessionStore(store) {
+ if (typeof window === 'undefined') return;
+ try {
+ await set(STORAGE_KEY, store);
+ } catch { }
+}
+
+/**
+ * 兼容迁移:将旧 chatHistory 数组迁移为多会话结构
+ */
+export function migrateFromLegacy(legacyChatHistory) {
+ if (!legacyChatHistory || legacyChatHistory.length === 0) return null;
+ const session = {
+ id: generateId(),
+ title: autoTitle(legacyChatHistory),
+ createdAt: legacyChatHistory[0]?.timestamp || Date.now(),
+ updatedAt: Date.now(),
+ messages: legacyChatHistory,
+ };
+ return session;
+}
+
+/**
+ * 从首条用户消息自动生成标题
+ */
+function autoTitle(messages) {
+ const first = messages.find(m => m.role === 'user');
+ if (!first) return '新对话';
+ const text = first.content.slice(0, 30);
+ return text.length < first.content.length ? text + '…' : text;
+}
+
+// ==================== 会话级操作 ====================
+
+/**
+ * 创建新的空白会话
+ */
+export function createSession(store) {
+ const session = {
+ id: generateId(), title: '新对话', createdAt: Date.now(),
+ updatedAt: Date.now(), messages: [],
+ };
+ const newStore = {
+ activeSessionId: session.id,
+ sessions: [...store.sessions, session],
+ };
+ saveSessionStore(newStore); // Run in background
+ return newStore;
+}
+
+/**
+ * 删除会话
+ */
+export function deleteSession(store, sessionId) {
+ const remaining = store.sessions.filter(s => s.id !== sessionId);
+ const newActive = store.activeSessionId === sessionId
+ ? (remaining[remaining.length - 1]?.id || null)
+ : store.activeSessionId;
+ const newStore = { activeSessionId: newActive, sessions: remaining };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 重命名会话
+ */
+export function renameSession(store, sessionId, newTitle) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s =>
+ s.id === sessionId ? { ...s, title: newTitle, updatedAt: Date.now() } : s
+ ),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 切换活动会话
+ */
+export function switchSession(store, sessionId) {
+ const newStore = { ...store, activeSessionId: sessionId };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 获取当前活动会话
+ */
+export function getActiveSession(store) {
+ if (!store.activeSessionId) return null;
+ return store.sessions.find(s => s.id === store.activeSessionId) || null;
+}
+
+// ==================== 消息级操作 ====================
+
+/**
+ * 向活动会话添加消息
+ */
+export function addMessage(store, msg) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ const messages = [...s.messages, msg];
+ const title = s.messages.length === 0 && msg.role === 'user'
+ ? (msg.content.slice(0, 30) + (msg.content.length > 30 ? '…' : ''))
+ : s.title;
+ return { ...s, messages, title, updatedAt: Date.now() };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 更新活动会话的最后一条消息(用于流式输出拼接)
+ */
+export function updateLastMessage(store, updater) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ if (s.messages.length === 0) return s;
+ const messages = [...s.messages];
+ messages[messages.length - 1] = updater(messages[messages.length - 1]);
+ return { ...s, messages, updatedAt: Date.now() };
+ }),
+ };
+ return newStore;
+}
+
+/**
+ * 编辑指定消息的内容
+ */
+export function editMessage(store, msgId, newContent) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ return {
+ ...s,
+ messages: s.messages.map(m =>
+ m.id === msgId ? { ...m, content: newContent, editedAt: Date.now() } : m
+ ),
+ updatedAt: Date.now(),
+ };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 删除指定消息
+ */
+export function deleteMessage(store, msgId) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ return {
+ ...s,
+ messages: s.messages.filter(m => m.id !== msgId),
+ updatedAt: Date.now(),
+ };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 从指定消息创建分支 — 复制该消息及之前的所有消息到新会话
+ */
+export function createBranch(store, msgId) {
+ const activeSession = getActiveSession(store);
+ if (!activeSession) return store;
+
+ const msgIndex = activeSession.messages.findIndex(m => m.id === msgId);
+ if (msgIndex < 0) return store;
+
+ const branchedMessages = activeSession.messages.slice(0, msgIndex + 1).map(m => ({
+ ...m, id: generateId('msg'),
+ }));
+
+ const branchSession = {
+ id: generateId(), title: `${activeSession.title} (分支)`,
+ createdAt: Date.now(), updatedAt: Date.now(), messages: branchedMessages,
+ };
+
+ const newStore = {
+ activeSessionId: branchSession.id,
+ sessions: [...store.sessions, branchSession],
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 替换活动会话的消息列表(用于总结等批量操作)
+ */
+export function replaceMessages(store, newMessages) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ return { ...s, messages: newMessages, updatedAt: Date.now() };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 为指定消息添加一个变体(多次重新生成的结果)
+ */
+export function addVariant(store, msgId, variantData) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ return {
+ ...s,
+ messages: s.messages.map(m => {
+ if (m.id !== msgId) return m;
+ const existingVariants = m.variants || [{
+ content: m.content,
+ thinking: m.thinking || '',
+ timestamp: m.timestamp,
+ }];
+ const newVariants = [...existingVariants, variantData];
+ return {
+ ...m,
+ variants: newVariants,
+ activeVariant: newVariants.length - 1,
+ content: variantData.content,
+ thinking: variantData.thinking || '',
+ };
+ }),
+ updatedAt: Date.now(),
+ };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
+
+/**
+ * 切换指定消息的当前显示变体
+ */
+export function switchVariant(store, msgId, variantIndex) {
+ const newStore = {
+ ...store,
+ sessions: store.sessions.map(s => {
+ if (s.id !== store.activeSessionId) return s;
+ return {
+ ...s,
+ messages: s.messages.map(m => {
+ if (m.id !== msgId || !m.variants) return m;
+ const idx = Math.max(0, Math.min(variantIndex, m.variants.length - 1));
+ const variant = m.variants[idx];
+ return { ...m, activeVariant: idx, content: variant.content, thinking: variant.thinking || '' };
+ }),
+ updatedAt: Date.now(),
+ };
+ }),
+ };
+ saveSessionStore(newStore);
+ return newStore;
+}
diff --git a/app/lib/context-engine.js b/app/lib/context-engine.js
new file mode 100644
index 0000000..cb941a9
--- /dev/null
+++ b/app/lib/context-engine.js
@@ -0,0 +1,827 @@
+// 上下文收集引擎 — 像 Cursor 一样,在每次 AI 调用前自动汇聚所有相关信息
+// 让 AI 完整理解:作品风格、人物设定、世界观、前文脉络、当前位置
+
+import { getChapters } from './storage';
+import { getProjectSettings, getSettingsNodes, getWritingMode, getActiveWorkId } from './settings';
+import { getEmbedding, cosineSimilarity } from './embeddings';
+
+// ==================== Token 预算管理 ====================
+
+export const INPUT_TOKEN_BUDGET = 200000; // 输入预算(发送给AI的上下文)
+export const OUTPUT_TOKEN_BUDGET = 6000; // 输出预算(AI生成的回复长度,约4500字)
+
+// 中文 token 估算:约 1.5 个中文字符 = 1 个 token
+export function estimateTokens(text) {
+ if (!text) return 0;
+ const chineseChars = (text.match(/[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/g) || []).length;
+ const otherChars = text.length - chineseChars;
+ return Math.ceil(chineseChars / 1.5 + otherChars / 4);
+}
+
+// 上下文模块优先级(数字越小越优先)
+const PRIORITY = {
+ writingRules: 1, // 写作规则——必须严格遵守
+ currentChapter: 2, // 当前章节元信息
+ characters: 3, // 人物设定
+ plotOutline: 4, // 大纲
+ bookInfo: 5, // 作品信息
+ worldbuilding: 6, // 世界观
+ locations: 7, // 地点
+ objects: 8, // 物品
+ customSettings: 9, // 自定义设定
+ previousChapters: 10, // 前文回顾(最容易截断)
+};
+
+/**
+ * 获取上下文可勾选条目列表(供「📚 参考」Tab 使用) (Async)
+ * 返回扁平化数组,每个条目包含 id, group, name, tokens, category
+ */
+export async function getContextItems(activeChapterId) {
+ const settings = getProjectSettings();
+ const chapters = await getChapters();
+ const currentIndex = chapters.findIndex(ch => ch.id === activeChapterId);
+
+ const allNodes = await getSettingsNodes();
+ const activeWorkId = getActiveWorkId();
+
+ // 按当前作品过滤
+ let nodes;
+ if (activeWorkId) {
+ const workDesc = new Set();
+ const coll = (pid) => { allNodes.filter(n => n.parentId === pid).forEach(n => { workDesc.add(n.id); coll(n.id); }); };
+ workDesc.add(activeWorkId);
+ coll(activeWorkId);
+ nodes = allNodes.filter(n => workDesc.has(n.id));
+ } else {
+ nodes = allNodes;
+ }
+ const itemNodes = nodes.filter(n => n.type === 'item');
+
+ const items = [];
+
+ // 分类映射
+ const categoryMap = {
+ rules: { group: '📐 写作规则', builder: (ns) => buildRulesContext(ns) },
+ character: { group: '👤 人物设定', builder: (ns) => buildCharactersContext(ns) },
+ location: { group: '🗺️ 空间/地点', builder: (ns) => buildLocationsContext(ns, nodes) },
+ world: { group: '🌍 世界观', builder: (ns) => buildWorldContext(ns, nodes) },
+ object: { group: '🔮 物品/道具', builder: (ns) => buildObjectsContext(ns, nodes) },
+ plot: { group: '📋 大纲', builder: (ns) => buildPlotContext(ns, nodes) },
+ custom: { group: '⚙️ 自定义', builder: (ns) => buildCustomContext(ns, nodes) },
+ };
+
+ // 设定条目
+ for (const [cat, config] of Object.entries(categoryMap)) {
+ const catNodes = itemNodes.filter(n => n.category === cat);
+ if (catNodes.length === 0) {
+ // 检查是否有该分类的文件夹 — 如果有,显示空提示
+ const hasFolder = nodes.some(n => n.type === 'folder' && n.category === cat);
+ if (hasFolder) {
+ items.push({
+ id: `empty-${cat}`,
+ group: config.group,
+ name: '(暂无条目)',
+ tokens: 0,
+ category: cat,
+ enabled: false,
+ _empty: true,
+ });
+ }
+ continue;
+ }
+ for (const n of catNodes) {
+ const text = config.builder([n]);
+ items.push({
+ id: `setting-${n.id}`,
+ group: config.group,
+ name: n.name,
+ tokens: estimateTokens(text),
+ category: cat,
+ enabled: n.enabled !== false,
+ _nodeId: n.id,
+ });
+ }
+ }
+
+ // 作品信息
+ const bookInfo = settings.bookInfo;
+ if (bookInfo && (bookInfo.title || bookInfo.author || bookInfo.genre || bookInfo.synopsis)) {
+ items.push({
+ id: 'bookinfo',
+ group: '📖 作品信息',
+ name: bookInfo.title || '作品信息',
+ tokens: estimateTokens(buildBookInfoContext(bookInfo)),
+ category: 'bookInfo',
+ enabled: true,
+ });
+ }
+
+ // 章节条目 — 显示全部章节
+ chapters.forEach((ch, i) => {
+ const text = stripHtml(ch.content || '');
+ if (i === currentIndex) {
+ // 当前章节
+ items.push({
+ id: `chapter-current`,
+ group: '📑 章节',
+ name: `第${i + 1}章 ${ch.title}(当前)`,
+ tokens: estimateTokens(buildCurrentContext(ch, i, chapters.length)),
+ category: 'currentChapter',
+ enabled: true,
+ });
+ } else {
+ items.push({
+ id: `chapter-${ch.id}`,
+ group: '📑 章节',
+ name: `第${i + 1}章 ${ch.title}${i > currentIndex ? '(后续)' : ''}`,
+ tokens: estimateTokens(text ? text.slice(0, 1500) : ''),
+ category: 'chapter',
+ enabled: i < currentIndex, // 前文章节默认启用,后续章节默认不启用
+ });
+ }
+ });
+
+ return items;
+}
+
+/**
+ * 构建完整的 AI 上下文 (Async)
+ * @param {string} activeChapterId
+ * @param {string} selectedText
+ * @param {Set|null} selectedIds - 如果提供,只包含 id 在此 Set 中的条目
+ */
+export async function buildContext(activeChapterId, selectedText, selectedIds = null) {
+ const settings = getProjectSettings();
+ const chapters = await getChapters();
+ const currentChapter = chapters.find(ch => ch.id === activeChapterId);
+ const currentIndex = chapters.findIndex(ch => ch.id === activeChapterId);
+
+ // 从树形节点读取设定(过滤掉禁用项,并按当前作品过滤)
+ const allNodes = await getSettingsNodes();
+ const activeWorkId = getActiveWorkId();
+
+ // 收集当前作品的所有后代节点
+ let nodes;
+ if (activeWorkId) {
+ const workDescendants = new Set();
+ const collectDesc = (pid) => {
+ allNodes.filter(n => n.parentId === pid).forEach(n => {
+ workDescendants.add(n.id);
+ collectDesc(n.id);
+ });
+ };
+ workDescendants.add(activeWorkId);
+ collectDesc(activeWorkId);
+ nodes = allNodes.filter(n => workDescendants.has(n.id));
+ } else {
+ nodes = allNodes;
+ }
+
+ // 获取所有有效的设定条目
+ const allValidItemNodes = nodes.filter(n => n.type === 'item' && n.enabled !== false);
+
+ // 如果有 selectedIds,提取手动勾选的设定
+ const manualItemNodes = [];
+ const unselectedItemNodes = [];
+
+ for (const n of allValidItemNodes) {
+ // 规则类总是作为手动(强制生效)如果 enabled
+ if (n.category === 'rules') {
+ manualItemNodes.push(n);
+ continue;
+ }
+
+ if (selectedIds && selectedIds.has(`setting-${n.id}`)) {
+ manualItemNodes.push(n);
+ } else if (!selectedIds) {
+ // 如果 selectedIds 为 null,默认所有启用的项都算作手动(兼容旧逻辑,或视情况全转RAG)
+ // 为了发挥 RAG 威力,如果 selectedIds 为 null,我们可以假设除了规则外,全部走向 RAG,
+ // 或者全部走 Manual。这里我们设定期望:全部走 RAG 自动检索。
+ unselectedItemNodes.push(n);
+ } else {
+ unselectedItemNodes.push(n);
+ }
+ }
+
+ // --- RAG 自动检索 ---
+ let autoRetrievedNodes = [];
+ const queryText = (selectedText || '').trim();
+ if (queryText && unselectedItemNodes.length > 0) {
+ try {
+ // 获取 Query 的 Embedding
+ // 此处的文本可以是光标前的一小段文字,为了简便使用 selectedText
+ const { apiConfig } = getProjectSettings();
+ // TODO: 这里如果是空文本,可能需要补当前章节最近的内容
+ let ragSourceText = queryText;
+ if (ragSourceText.length < 50 && currentChapter) {
+ // 文本太短,用当前章节末尾补充
+ const stripChapText = stripHtml(currentChapter.content || '').slice(-200);
+ ragSourceText = ragSourceText + '\n' + stripChapText;
+ }
+
+ const queryVector = await getEmbedding(ragSourceText, apiConfig);
+ if (queryVector) {
+ // 计算相似度
+ const scoredNodes = unselectedItemNodes.map(n => {
+ if (!n.embedding) return { node: n, score: -1 };
+ return { node: n, score: cosineSimilarity(queryVector, n.embedding) };
+ }).filter(x => x.score > 0.3); // 设置一个基础阈值 0.3
+
+ // 按分数降序
+ scoredNodes.sort((a, b) => b.score - a.score);
+
+ // 取 Top-K (比如最多取 5 个)
+ autoRetrievedNodes = scoredNodes.slice(0, 5).map(x => x.node);
+ }
+ } catch (e) {
+ console.error('RAG Retrieval failed:', e);
+ }
+ }
+
+ // 合并手动与自动检索的节点
+ const itemNodes = [...manualItemNodes, ...autoRetrievedNodes];
+ // 去重
+ const finalItemNodes = Array.from(new Set(itemNodes));
+
+ const writingMode = getWritingMode();
+
+ // 先构建各模块的原始文本
+ const rawModules = {
+ bookInfo: (!selectedIds || selectedIds.has('bookinfo')) ? buildBookInfoContext(settings.bookInfo) : '',
+ characters: buildCharactersContext(finalItemNodes.filter(n => n.category === 'character')),
+ locations: buildLocationsContext(finalItemNodes.filter(n => n.category === 'location'), nodes),
+ worldbuilding: buildWorldContext(finalItemNodes.filter(n => n.category === 'world'), nodes),
+ objects: buildObjectsContext(finalItemNodes.filter(n => n.category === 'object'), nodes),
+ plotOutline: buildPlotContext(finalItemNodes.filter(n => n.category === 'plot'), nodes),
+ writingRules: buildRulesContext(finalItemNodes.filter(n => n.category === 'rules')),
+ customSettings: buildCustomContext(finalItemNodes.filter(n => n.category === 'custom'), nodes),
+ previousChapters: selectedIds
+ ? buildPreviousContextFiltered(chapters, currentIndex, selectedIds)
+ : buildPreviousContext(chapters, currentIndex),
+ currentChapter: (!selectedIds || selectedIds.has('chapter-current'))
+ ? buildCurrentContext(currentChapter, currentIndex, chapters.length)
+ : '',
+ };
+
+ // 按优先级分配 token 预算
+ const budgetedModules = applyTokenBudget(rawModules);
+
+ const context = {
+ writingMode,
+ ...budgetedModules,
+ focusText: selectedText || '',
+ };
+
+ return context;
+}
+
+/**
+ * 按优先级分配 token 预算,超出时截断低优先级内容
+ */
+function applyTokenBudget(modules) {
+ // 计算每个模块的 token
+ const entries = Object.entries(modules).map(([key, text]) => ({
+ key,
+ text: text || '',
+ tokens: estimateTokens(text),
+ priority: PRIORITY[key] || 99,
+ }));
+
+ // 按优先级排序
+ entries.sort((a, b) => a.priority - b.priority);
+
+ let remaining = INPUT_TOKEN_BUDGET;
+ const result = {};
+
+ for (const entry of entries) {
+ if (entry.tokens <= remaining) {
+ result[entry.key] = entry.text;
+ remaining -= entry.tokens;
+ } else if (remaining > 100) {
+ // 截断:按比例保留
+ const ratio = remaining / entry.tokens;
+ const keepChars = Math.floor(entry.text.length * ratio * 0.9); // 留10%余量
+ result[entry.key] = entry.text.slice(0, keepChars) + '\n…(因 token 限制,部分内容已省略)';
+ remaining = 0;
+ } else {
+ result[entry.key] = ''; // 预算耗尽
+ }
+ }
+
+ return result;
+}
+
+/**
+ * 获取上下文预览(各模块状态和 token 估算) (Async)
+ */
+export async function getContextPreview(activeChapterId, selectedText) {
+ const settings = getProjectSettings();
+ const chapters = await getChapters();
+ const currentChapter = chapters.find(ch => ch.id === activeChapterId);
+ const currentIndex = chapters.findIndex(ch => ch.id === activeChapterId);
+
+ const allNodes = await getSettingsNodes();
+ const activeWorkId = getActiveWorkId();
+
+ // 按当前作品过滤
+ let nodes;
+ if (activeWorkId) {
+ const workDesc = new Set();
+ const coll = (pid) => { allNodes.filter(n => n.parentId === pid).forEach(n => { workDesc.add(n.id); coll(n.id); }); };
+ workDesc.add(activeWorkId);
+ coll(activeWorkId);
+ nodes = allNodes.filter(n => workDesc.has(n.id));
+ } else {
+ nodes = allNodes;
+ }
+ const allItemNodes = nodes.filter(n => n.type === 'item');
+ const enabledItemNodes = allItemNodes.filter(n => n.enabled !== false);
+
+ const modules = [
+ {
+ key: 'writingRules',
+ label: '📐 写作规则',
+ count: enabledItemNodes.filter(n => n.category === 'rules').length,
+ totalCount: allItemNodes.filter(n => n.category === 'rules').length,
+ tokens: estimateTokens(buildRulesContext(enabledItemNodes.filter(n => n.category === 'rules'))),
+ priority: PRIORITY.writingRules,
+ },
+ {
+ key: 'characters',
+ label: '👤 人物设定',
+ count: enabledItemNodes.filter(n => n.category === 'character').length,
+ totalCount: allItemNodes.filter(n => n.category === 'character').length,
+ tokens: estimateTokens(buildCharactersContext(enabledItemNodes.filter(n => n.category === 'character'))),
+ priority: PRIORITY.characters,
+ },
+ {
+ key: 'locations',
+ label: '🗺️ 空间/地点',
+ count: enabledItemNodes.filter(n => n.category === 'location').length,
+ totalCount: allItemNodes.filter(n => n.category === 'location').length,
+ tokens: estimateTokens(buildLocationsContext(enabledItemNodes.filter(n => n.category === 'location'), nodes)),
+ priority: PRIORITY.locations,
+ },
+ {
+ key: 'worldbuilding',
+ label: '🌍 世界观',
+ count: enabledItemNodes.filter(n => n.category === 'world').length,
+ totalCount: allItemNodes.filter(n => n.category === 'world').length,
+ tokens: estimateTokens(buildWorldContext(enabledItemNodes.filter(n => n.category === 'world'), nodes)),
+ priority: PRIORITY.worldbuilding,
+ },
+ {
+ key: 'objects',
+ label: '🔮 物品/道具',
+ count: enabledItemNodes.filter(n => n.category === 'object').length,
+ totalCount: allItemNodes.filter(n => n.category === 'object').length,
+ tokens: estimateTokens(buildObjectsContext(enabledItemNodes.filter(n => n.category === 'object'), nodes)),
+ priority: PRIORITY.objects,
+ },
+ {
+ key: 'plotOutline',
+ label: '📋 大纲',
+ count: enabledItemNodes.filter(n => n.category === 'plot').length,
+ totalCount: allItemNodes.filter(n => n.category === 'plot').length,
+ tokens: estimateTokens(buildPlotContext(enabledItemNodes.filter(n => n.category === 'plot'), nodes)),
+ priority: PRIORITY.plotOutline,
+ },
+ {
+ key: 'bookInfo',
+ label: '📖 作品信息',
+ count: settings.bookInfo?.title ? 1 : 0,
+ totalCount: 1,
+ tokens: estimateTokens(buildBookInfoContext(settings.bookInfo)),
+ priority: PRIORITY.bookInfo,
+ },
+ {
+ key: 'previousChapters',
+ label: '📑 前文回顾',
+ count: Math.max(0, currentIndex),
+ totalCount: Math.max(0, currentIndex),
+ tokens: estimateTokens(buildPreviousContext(chapters, currentIndex)),
+ priority: PRIORITY.previousChapters,
+ },
+ {
+ key: 'currentChapter',
+ label: '✏️ 当前章节',
+ count: currentChapter ? 1 : 0,
+ totalCount: 1,
+ tokens: estimateTokens(buildCurrentContext(currentChapter, currentIndex, chapters.length)),
+ priority: PRIORITY.currentChapter,
+ },
+ ];
+
+ const totalTokens = modules.reduce((sum, m) => sum + m.tokens, 0);
+
+ return {
+ modules,
+ totalTokens,
+ inputBudget: INPUT_TOKEN_BUDGET,
+ outputBudget: OUTPUT_TOKEN_BUDGET,
+ budget: INPUT_TOKEN_BUDGET, // 兼容旧字段
+ overBudget: totalTokens > INPUT_TOKEN_BUDGET,
+ };
+}
+
+/**
+ * 将上下文编译成系统提示词
+ */
+export function compileSystemPrompt(context, mode) {
+ const sections = [];
+
+ sections.push(getModeRolePrompt(context.writingMode));
+
+ if (context.bookInfo) {
+ sections.push(`【作品信息】\n${context.bookInfo}`);
+ }
+ if (context.characters) {
+ sections.push(`【人物档案】\n以下是本作品中的重要角色,写作时必须严格遵循他们的设定:\n${context.characters}`);
+ }
+ if (context.locations) {
+ sections.push(`【空间/地点】\n以下是本作品中的重要场所:\n${context.locations}`);
+ }
+ if (context.worldbuilding) {
+ sections.push(`【世界观设定】\n以下是本作品的世界观,所有内容必须在这个框架内:\n${context.worldbuilding}`);
+ }
+ if (context.objects) {
+ sections.push(`【物品/道具】\n以下是本作品中的重要物品:\n${context.objects}`);
+ }
+ if (context.plotOutline) {
+ sections.push(`【剧情大纲】\n${context.plotOutline}`);
+ }
+ if (context.writingRules) {
+ sections.push(`【写作规则——必须严格遵守】\n${context.writingRules}`);
+ }
+ if (context.customSettings) {
+ sections.push(`【补充设定】\n${context.customSettings}`);
+ }
+ if (context.previousChapters) {
+ sections.push(`【前文回顾】\n以下是之前章节的主要内容,续写时必须保持连贯:\n${context.previousChapters}`);
+ }
+ if (context.currentChapter) {
+ sections.push(`【当前写作位置】\n${context.currentChapter}`);
+ }
+
+ const modeInstruction = getModeInstruction(mode);
+ sections.push(`【你的任务】\n${modeInstruction}`);
+
+ return sections.join('\n\n---\n\n');
+}
+
+/**
+ * 构建用户提示词
+ */
+export function compileUserPrompt(mode, text, instruction) {
+ switch (mode) {
+ case 'continue':
+ if (!text || !text.trim()) {
+ return instruction
+ ? `请根据以下要求开始创作新内容(约300-500字):\n要求:${instruction}`
+ : '请根据设定集信息,开始撰写新的章节内容(约300-500字)。';
+ }
+ return instruction
+ ? `请续写以下内容(约300-500字),保持风格和情节的连贯性:\n要求:${instruction}\n\n「${text}」`
+ : `请续写以下内容(约300-500字),保持风格和情节的连贯性:\n\n「${text}」`;
+ case 'rewrite':
+ return instruction
+ ? `按照以下要求改写文本:\n要求:${instruction}\n\n原文:\n「${text}」`
+ : `请润色改写以下文本,提升文学质量:\n\n「${text}」`;
+ case 'expand':
+ return `请扩写以下文本,加入更丰富的细节和描写(约为原文1.5-2倍):\n\n「${text}」`;
+ case 'condense':
+ return `请精简以下文本,保留核心信息,删除冗余:\n\n「${text}」`;
+ case 'dialogue':
+ return `请为以下场景优化或续写人物对话,对话须符合各角色的说话风格:\n\n「${text}」`;
+ case 'chat':
+ // 自由对话模式:instruction 是用户的问题,text 可能是选中文本
+ if (text && instruction) {
+ return `${instruction}\n\n参考文本:\n「${text}」`;
+ }
+ return instruction || text || '请根据设定集信息回答我的问题。';
+ default:
+ return instruction ? `${instruction}\n\n「${text}」` : text;
+ }
+}
+
+// ==================== 内部构建函数 ====================
+
+function buildBookInfoContext(bookInfo) {
+ if (!bookInfo) return '';
+ const parts = [];
+ if (bookInfo.title) parts.push(`书名:${bookInfo.title}`);
+ if (bookInfo.genre) parts.push(`题材:${bookInfo.genre}`);
+ if (bookInfo.synopsis) parts.push(`故事简介:${bookInfo.synopsis}`);
+ if (bookInfo.style) parts.push(`写作风格:${bookInfo.style}`);
+ if (bookInfo.tone) parts.push(`整体基调:${bookInfo.tone}`);
+ if (bookInfo.pov) parts.push(`叙事视角:${bookInfo.pov}`);
+ if (bookInfo.targetAudience) parts.push(`目标读者:${bookInfo.targetAudience}`);
+ return parts.length > 0 ? parts.join('\n') : '';
+}
+
+// 从树节点构建人物上下文
+function buildCharactersContext(charNodes) {
+ if (!charNodes || charNodes.length === 0) return '';
+ return charNodes.map(n => {
+ const c = n.content || {};
+ const parts = [`【${n.name}】(${c.role || '角色'})(id: ${n.id})`];
+ if (c.age) parts.push(` 年龄:${c.age}`);
+ if (c.gender) parts.push(` 性别:${c.gender}`);
+ if (c.appearance) parts.push(` 外貌:${c.appearance}`);
+ if (c.personality) parts.push(` 性格:${c.personality}`);
+ if (c.background) parts.push(` 背景:${c.background}`);
+ if (c.motivation) parts.push(` 动机/目标:${c.motivation}`);
+ if (c.skills) parts.push(` 能力:${c.skills}`);
+ if (c.speechStyle) parts.push(` 说话风格:${c.speechStyle}`);
+ if (c.relationships) parts.push(` 人物关系:${c.relationships}`);
+ if (c.arc) parts.push(` 成长弧线:${c.arc}`);
+ if (c.notes) parts.push(` 备注:${c.notes}`);
+ return parts.join('\n');
+ }).join('\n\n');
+}
+
+// 从树节点构建世界观上下文(带层级路径)
+function buildWorldContext(worldNodes, allNodes) {
+ if (!worldNodes || worldNodes.length === 0) return '';
+ return worldNodes.map(n => {
+ const path = getNodePathStr(n, allNodes);
+ const desc = n.content?.description || '';
+ return `[${path}] (id: ${n.id})
+${desc}`;
+ }).join('\n\n');
+}
+
+// 从树节点构建大纲上下文
+function buildPlotContext(plotNodes, allNodes) {
+ if (!plotNodes || plotNodes.length === 0) return '';
+ return plotNodes.map(n => {
+ const path = getNodePathStr(n, allNodes);
+ const status = n.content?.status ? `(${n.content.status})` : '';
+ const desc = n.content?.description || '';
+ return `[${path}]${status} (id: ${n.id})
+${desc}`;
+ }).join('\n\n');
+}
+
+// 从树节点构建写作规则
+function buildRulesContext(rulesNodes) {
+ if (!rulesNodes || rulesNodes.length === 0) return '';
+ return rulesNodes.map(n => {
+ const desc = n.content?.description || '';
+ return `${n.name} (id: ${n.id}):
+${desc}`;
+ }).join('\n\n');
+}
+
+// 从树节点构建自定义设定
+function buildCustomContext(customNodes, allNodes) {
+ if (!customNodes || customNodes.length === 0) return '';
+ return customNodes.map(n => {
+ const path = getNodePathStr(n, allNodes);
+ const desc = n.content?.description || '';
+ return `[${path}] (id: ${n.id})
+${desc}`;
+ }).join('\n\n');
+}
+
+// 获取节点路径字符串(不含根分类)
+function getNodePathStr(node, allNodes) {
+ const path = [];
+ let current = node;
+ while (current) {
+ if (current.parentId !== null) {
+ path.unshift(current.name);
+ }
+ current = current.parentId ? allNodes.find(n => n.id === current.parentId) : null;
+ }
+ return path.join(' / ');
+}
+
+function buildPreviousContext(chapters, currentIndex) {
+ if (currentIndex <= 0) return '';
+
+ const prevChapters = chapters.slice(0, currentIndex);
+
+ return prevChapters.map((ch, i) => {
+ const text = stripHtml(ch.content || '');
+ if (!text) return `第${i + 1}章「${ch.title}」:(空)`;
+
+ if (i === currentIndex - 1) {
+ // 前一章:保留更多内容
+ const excerpt = text.length > 1500 ? '...' + text.slice(-1500) : text;
+ return `第${i + 1}章「${ch.title}」(前一章完整回顾):\n${excerpt}`;
+ }
+
+ // 远距章节:只保留简要摘要
+ if (text.length > 300) {
+ return `第${i + 1}章「${ch.title}」:\n${text.slice(0, 150)}…(中间省略)…${text.slice(-100)}`;
+ }
+ return `第${i + 1}章「${ch.title}」:\n${text}`;
+ }).join('\n\n');
+}
+
+// 按 selectedIds 过滤的前文上下文
+function buildPreviousContextFiltered(chapters, currentIndex, selectedIds) {
+ if (currentIndex <= 0) return '';
+
+ const prevChapters = chapters.slice(0, currentIndex);
+ const selected = prevChapters.filter(ch => selectedIds.has(`chapter-${ch.id}`));
+ if (selected.length === 0) return '';
+
+ return selected.map((ch) => {
+ const i = chapters.indexOf(ch);
+ const text = stripHtml(ch.content || '');
+ if (!text) return `第${i + 1}章「${ch.title}」:(空)`;
+
+ if (i === currentIndex - 1) {
+ const excerpt = text.length > 1500 ? '...' + text.slice(-1500) : text;
+ return `第${i + 1}章「${ch.title}」(前一章完整回顾):\n${excerpt}`;
+ }
+
+ if (text.length > 300) {
+ return `第${i + 1}章「${ch.title}」:\n${text.slice(0, 150)}…(中间省略)…${text.slice(-100)}`;
+ }
+ return `第${i + 1}章「${ch.title}」:\n${text}`;
+ }).join('\n\n');
+}
+
+function buildCurrentContext(chapter, index, totalChapters) {
+ if (!chapter) return '';
+ const text = stripHtml(chapter.content || '');
+ const parts = [
+ `当前章节:第${index + 1}章 / 共${totalChapters}章`,
+ `章节标题:「${chapter.title}」`,
+ ];
+ if (text) {
+ parts.push(`本章已有字数:${text.replace(/\s/g, '').length}字`);
+ parts.push(`\n--- 本章正文 ---\n${text}`);
+ }
+ return parts.join('\n');
+}
+
+function getModeInstruction(mode) {
+ switch (mode) {
+ case 'continue':
+ return `根据前文的情节走向和当前章节的内容,自然地续写故事。
+要求:
+- 续写内容必须与前文情节逻辑严格连贯,不能出现矛盾
+- 如果涉及已有角色,必须符合其性格设定和说话风格
+- 场景描写要符合世界观设定
+- 情节推进要符合大纲规划的方向
+- 约300-500字`;
+
+ case 'rewrite':
+ return `润色和改写指定文本,提升文学质量。
+要求:
+- 保持原文的核心含义和情节不变
+- 提升感官描写和文学表现力
+- 确保改写后的内容仍然符合人物设定和世界观
+- 保持叙事视角一致`;
+
+ case 'expand':
+ return `扩写指定文本,丰富细节。
+要求:
+- 增加环境描写、感官细节、心理活动
+- 深化人物的情感表达
+- 扩写后必须与前后文衔接自然
+- 不改变原有情节走向`;
+
+ case 'condense':
+ return `精简指定文本,提升节奏感。
+要求:
+- 删除冗余修饰和重复表达
+- 保留核心信息和关键描写
+- 保持情节完整性`;
+
+ case 'dialogue':
+ return `优化或续写人物对话。
+要求:
+- 每个角色的对话必须符合其性格设定和说话风格
+- 通过对话推动情节或展现人物关系
+- 加入适当的动作描写和表情细节
+- 对话节奏要自然,长短句交替`;
+
+ case 'chat':
+ return `作为创作顾问,根据上述设定集的全部信息回答作者的问题。
+要求:
+- 回答必须基于已有的设定信息,不能凭空捏造
+- 如果问题涉及设定中未覆盖的内容,可以给出基于现有设定的合理建议
+- 语言简洁明了,尽量具体
+- 回答以中文呈现
+
+【设定集管理能力】
+当用户要求你创建、修改或删除设定集条目时,你可以在回复中嵌入操作指令块。格式如下:
+
+\`\`\`
+[SETTINGS_ACTION]
+{"action":"add","category":"character","name":"角色姓名","content":{"role":"主角","personality":"...","background":"..."}}
+[/SETTINGS_ACTION]
+\`\`\`
+
+可用的 action: "add"(新增)、"update"(更新,需提供 nodeId)、"delete"(删除,需提供 nodeId)
+
+可用的 category 和对应 content 字段:
+- "character":角色。content 可含:role, age, gender, appearance, personality, background, motivation, skills, speechStyle, relationships, arc, notes
+- "world":世界观。content 可含:description
+- "location":地点。content 可含:description, sensoryVisual, sensoryAudio, sensorySmell, mood, dangerLevel
+- "object":物品。content 可含:description, objectType, currentHolder, rank, numericStats, symbolism
+- "plot":大纲。content 可含:description, status
+- "rules":写作规则。content 可含:description
+- "custom":自定义。content 可含:description
+
+使用规则:
+- 每个操作块只包含一个 JSON 对象
+- 如果需要多个操作,使用多个 [SETTINGS_ACTION] 块
+- 操作块前后必须有正常的文字说明
+- update 示例:{"action":"update","nodeId":"具体id","name":"新名称","content":{...}}
+- delete 示例:{"action":"delete","nodeId":"具体id"}
+- 在正文中已有角色/设定出现时,如果用户要求,可以从正文分析内容并自动创建设定`;
+
+ default:
+ return '按照作者的指示完成写作任务,确保内容与已有设定一致。';
+ }
+}
+
+// ==================== 写作模式角色提示词 ====================
+
+function getModeRolePrompt(writingMode) {
+ const base = `你的核心原则:
+- 深度理解作品的世界观和人物,绝不写出与设定矛盾的内容
+- 保持作者已建立的写作风格和语气,你是协作者,不是替代者
+- 对话要体现角色独特的说话方式和性格
+- 避免"然而"、"不禁"、"竟然"、"仿佛"等AI味词汇
+- 情节推进要自然,符合大纲规划和人物动机`;
+
+ switch (writingMode) {
+ case 'webnovel':
+ return `你是一位资深网络小说写手兼编辑,擅长连载节奏把控和爽点设计。你正在协助作者创作一部网络小说。
+${base}
+- 注重节奏感和爽点密度,每个章节要有"钩子"吸引读者
+- 严格维护数值体系(等级、属性、技能冷却等)的一致性
+- 对话简洁有力,推动剧情和人物关系发展`;
+
+ case 'traditional':
+ return `你是一位经验丰富的文学编辑兼小说家,深谙散文美学和主题构建。你正在协助作者创作一部文学作品。
+${base}
+- 使用丰富的感官描写(视觉、听觉、触觉、嗅觉、味觉)
+- 注重主题的编织和意象的呼应,象征具有层次感
+- 深入挖掘人物心理,内心独白要体现角色独特的思维方式
+- 散文质量优先,追求文字的精确性和美感`;
+
+ case 'screenplay':
+ return `你是一位专业的影视编剧兼剧本顾问,精通视觉叙事和对白技巧。你正在协助作者创作一部剧本/脚本。
+${base}
+- 以视觉化思维写作,描述"镜头能看到的"而非抽象概念
+- 对白必须口语化、角色化,每个角色有独特的"声音"
+- 注意场景的物理逻辑:谁在场、在哪里、什么时间
+- 保持光照连续性(日/夜)和空间连续性`;
+
+ default:
+ return `你是一位经验丰富的中文作家兼编辑。你正在协助作者创作一部作品。
+${base}
+- 使用丰富的感官描写(视觉、听觉、触觉、嗅觉、味觉)`;
+ }
+}
+
+// ==================== 新分类上下文构建 ====================
+
+// 空间/地点上下文
+function buildLocationsContext(locationNodes, allNodes) {
+ if (!locationNodes || locationNodes.length === 0) return '';
+ return locationNodes.map(n => {
+ const path = getNodePathStr(n, allNodes);
+ const c = n.content || {};
+ const parts = [`[${path}]`];
+ if (c.description) parts.push(c.description);
+ if (c.slugline) parts.push(`场景标题:${c.slugline}`);
+ if (c.sensoryVisual) parts.push(`视觉:${c.sensoryVisual}`);
+ if (c.sensoryAudio) parts.push(`听觉:${c.sensoryAudio}`);
+ if (c.sensorySmell) parts.push(`嗅觉/触觉:${c.sensorySmell}`);
+ if (c.mood) parts.push(`氛围:${c.mood}`);
+ if (c.dangerLevel) parts.push(`危险等级:${c.dangerLevel}`);
+ return parts.join('\n');
+ }).join('\n\n');
+}
+
+// 物品/道具上下文
+function buildObjectsContext(objectNodes, allNodes) {
+ if (!objectNodes || objectNodes.length === 0) return '';
+ return objectNodes.map(n => {
+ const path = getNodePathStr(n, allNodes);
+ const c = n.content || {};
+ const parts = [`[${path}]`];
+ if (c.description) parts.push(c.description);
+ if (c.objectType) parts.push(`类型:${c.objectType}`);
+ if (c.currentHolder) parts.push(`当前持有者:${c.currentHolder}`);
+ if (c.rank) parts.push(`品阶:${c.rank}`);
+ if (c.numericStats) parts.push(`属性:${c.numericStats}`);
+ if (c.symbolism) parts.push(`象征意义:${c.symbolism}`);
+ return parts.join('\n');
+ }).join('\n\n');
+}
+
+// 去除HTML标签
+function stripHtml(html) {
+ if (!html) return '';
+ return html.replace(/<[^>]*>/g, '').replace(/ /g, ' ').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').trim();
+}
diff --git a/app/lib/embeddings.js b/app/lib/embeddings.js
new file mode 100644
index 0000000..8ed551c
--- /dev/null
+++ b/app/lib/embeddings.js
@@ -0,0 +1,58 @@
+// 文本向量化与余弦相似度计算库
+
+/**
+ * 获取文本的向量化表示 (Embeddings)
+ * @param {string} text 要向量化的文本
+ * @param {object} apiConfig 从 getProjectSettings().apiConfig 传入的配置
+ * @returns {Promise} 浮点数数组形式的向量
+ */
+export async function getEmbedding(text, apiConfig) {
+ if (!text || text.trim() === '') return null;
+
+ try {
+ const res = await fetch('/api/embed', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ text, apiConfig })
+ });
+
+ if (!res.ok) {
+ console.error('getEmbedding HTTP error:', await res.text());
+ return null;
+ }
+
+ const data = await res.json();
+ if (data.error) {
+ console.error('getEmbedding API error:', data.error);
+ return null;
+ }
+
+ return data.embedding;
+ } catch (err) {
+ console.error('getEmbedding fetch error:', err);
+ return null;
+ }
+}
+
+/**
+ * 计算两个向量之间的余弦相似度
+ * @param {number[]} vecA 向量 A
+ * @param {number[]} vecB 向量 B
+ * @returns {number} 相似度得分 (-1.0 到 1.0)
+ */
+export function cosineSimilarity(vecA, vecB) {
+ if (!vecA || !vecB || vecA.length !== vecB.length) return 0;
+
+ let dotProduct = 0;
+ let normA = 0;
+ let normB = 0;
+
+ for (let i = 0; i < vecA.length; i++) {
+ dotProduct += vecA[i] * vecB[i];
+ normA += vecA[i] * vecA[i];
+ normB += vecB[i] * vecB[i];
+ }
+
+ if (normA === 0 || normB === 0) return 0;
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
+}
diff --git a/app/lib/project-io.js b/app/lib/project-io.js
new file mode 100644
index 0000000..7e78ca1
--- /dev/null
+++ b/app/lib/project-io.js
@@ -0,0 +1,134 @@
+/**
+ * 项目导出/导入 — 将所有 localStorage 数据打包为 JSON 文件
+ * 支持:章节、设定集、API 配置、聊天会话
+ */
+
+const PROJECT_FILE_VERSION = 1;
+
+// 需要导出的所有 localStorage keys
+const STORAGE_KEYS = {
+ chapters: 'author-chapters',
+ settings: 'author-project-settings',
+ settingsNodes: 'author-settings-nodes',
+ activeWork: 'author-active-work',
+ chatSessions: 'author-chat-sessions',
+};
+
+// 章节摘要前缀
+const SUMMARY_PREFIX = 'author-chapter-summary-';
+
+/**
+ * 导出整个项目为 JSON 文件并下载
+ */
+export function exportProject() {
+ if (typeof window === 'undefined') return;
+
+ const data = {
+ _version: PROJECT_FILE_VERSION,
+ _exportedAt: new Date().toISOString(),
+ _app: 'Author',
+ };
+
+ // 收集所有主要数据
+ for (const [key, storageKey] of Object.entries(STORAGE_KEYS)) {
+ try {
+ const raw = localStorage.getItem(storageKey);
+ data[key] = raw ? JSON.parse(raw) : null;
+ } catch {
+ data[key] = null;
+ }
+ }
+
+ // 收集章节摘要
+ const summaries = {};
+ for (let i = 0; i < localStorage.length; i++) {
+ const k = localStorage.key(i);
+ if (k?.startsWith(SUMMARY_PREFIX)) {
+ const chapterId = k.slice(SUMMARY_PREFIX.length);
+ summaries[chapterId] = localStorage.getItem(k);
+ }
+ }
+ data.chapterSummaries = summaries;
+
+ // 生成文件名
+ const now = new Date();
+ const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}`;
+ const fileName = `Author_存档_${dateStr}.json`;
+
+ // 下载
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = fileName;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ return fileName;
+}
+
+/**
+ * 从 JSON 文件导入项目数据
+ * @param {File} file - 用户选择的 JSON 文件
+ * @returns {Promise<{ success: boolean, message: string }>}
+ */
+export async function importProject(file) {
+ if (typeof window === 'undefined') return { success: false, message: '环境不支持' };
+
+ try {
+ const text = await file.text();
+ const data = JSON.parse(text);
+
+ // 基本校验
+ if (!data._app || data._app !== 'Author') {
+ return { success: false, message: '文件格式不正确,不是 Author 存档文件' };
+ }
+
+ // 恢复主要数据
+ for (const [key, storageKey] of Object.entries(STORAGE_KEYS)) {
+ if (data[key] !== undefined && data[key] !== null) {
+ localStorage.setItem(storageKey, JSON.stringify(data[key]));
+ }
+ }
+
+ // 恢复章节摘要
+ if (data.chapterSummaries && typeof data.chapterSummaries === 'object') {
+ for (const [chapterId, summary] of Object.entries(data.chapterSummaries)) {
+ if (summary) {
+ localStorage.setItem(SUMMARY_PREFIX + chapterId, summary);
+ }
+ }
+ }
+
+ return { success: true, message: `成功导入存档(导出时间:${data._exportedAt || '未知'})` };
+ } catch (err) {
+ return { success: false, message: `导入失败:${err.message}` };
+ }
+}
+
+/**
+ * 获取当前项目数据的概要信息(用于显示)
+ */
+export function getProjectSummary() {
+ if (typeof window === 'undefined') return null;
+
+ try {
+ const chaptersRaw = localStorage.getItem(STORAGE_KEYS.chapters);
+ const chapters = chaptersRaw ? JSON.parse(chaptersRaw) : [];
+ const nodesRaw = localStorage.getItem(STORAGE_KEYS.settingsNodes);
+ const nodes = nodesRaw ? JSON.parse(nodesRaw) : [];
+ const sessionsRaw = localStorage.getItem(STORAGE_KEYS.chatSessions);
+ const sessions = sessionsRaw ? JSON.parse(sessionsRaw) : {};
+
+ return {
+ chapterCount: chapters.length,
+ settingsNodeCount: nodes.length,
+ sessionCount: Object.keys(sessions.sessions || {}).length,
+ totalChars: chapters.reduce((sum, ch) => sum + (ch.content?.length || 0), 0),
+ };
+ } catch {
+ return null;
+ }
+}
diff --git a/app/lib/settings.js b/app/lib/settings.js
new file mode 100644
index 0000000..3bbbb0e
--- /dev/null
+++ b/app/lib/settings.js
@@ -0,0 +1,717 @@
+// 设定集管理 - 存储人物、世界观、大纲等全局创作信息
+// 这些信息会在每次AI调用时作为上下文传入,让AI像Cursor一样了解整个项目
+// 基于「叙事引擎」架构 — 支持网络小说、传统文学、剧本/脚本三种创作模式
+
+import { get, set } from 'idb-keyval';
+import { getEmbedding } from './embeddings';
+
+const SETTINGS_KEY = 'author-project-settings';
+
+/**
+ * 递归提取节点的所有文本内容,用于向量化
+ */
+function extractTextForEmbedding(node) {
+ if (!node) return '';
+ let text = `Name: ${node.name || ''}\n`;
+
+ const extract = (obj) => {
+ if (typeof obj === 'string') return obj;
+ if (Array.isArray(obj)) return obj.map(extract).join(' ');
+ if (typeof obj === 'object' && obj !== null) {
+ return Object.values(obj).filter(v => v).map(extract).join(' ');
+ }
+ return '';
+ };
+
+ if (node.content) {
+ text += extract(node.content);
+ }
+ return text.trim();
+}
+
+// ==================== 写作模式定义 ====================
+
+export const WRITING_MODES = {
+ webnovel: {
+ key: 'webnovel',
+ label: '网络小说',
+ icon: '📱',
+ color: '#3b82f6',
+ desc: '适合日更连载、修仙玄幻、系统流等网文创作',
+ painPoint: '数值膨胀与连载一致性',
+ extraCharacterFields: [
+ { key: 'level', label: '等级/境界', placeholder: '例:筑基期三层 / Lv.45', multiline: false },
+ { key: 'stats', label: '属性面板', placeholder: '力量:85\n敏捷:72\n智力:90\n体质:68', multiline: true, rows: 4 },
+ { key: 'skillList', label: '技能列表', placeholder: '技能名称、效果、冷却时间...', multiline: true, rows: 3 },
+ { key: 'equipment', label: '装备/法宝', placeholder: '当前装备和持有的重要物品', multiline: true, rows: 2 },
+ ],
+ extraLocationFields: [
+ { key: 'dangerLevel', label: '危险等级', placeholder: '例:S级禁区 / 安全区', multiline: false },
+ { key: 'resources', label: '资源产出', placeholder: '灵石矿脉、药草分布...', multiline: true, rows: 2 },
+ ],
+ extraObjectFields: [
+ { key: 'rank', label: '品阶/等级', placeholder: '例:天级上品 / SSR', multiline: false },
+ { key: 'numericStats', label: '数值属性', placeholder: '攻击力+500\n暴击率+15%', multiline: true, rows: 3 },
+ ],
+ },
+ traditional: {
+ key: 'traditional',
+ label: '传统文学',
+ icon: '📚',
+ color: '#8b5cf6',
+ desc: '适合严肃小说、纯文学、短篇、出版向作品',
+ painPoint: '主题编织与草稿迭代',
+ extraCharacterFields: [
+ { key: 'coreTrauma', label: '核心创伤', placeholder: '角色内心深处的伤痕、驱动行为的心理根源', multiline: true, rows: 2 },
+ { key: 'innerMonologue', label: '内心独白关键词', placeholder: '角色内心世界的典型词汇和思维方式', multiline: true, rows: 2 },
+ { key: 'voice', label: '人物声音/对话标签', placeholder: '独特的措辞习惯、语法特点、方言痕迹...', multiline: true, rows: 2 },
+ { key: 'motifs', label: '反复意象/母题', placeholder: '与角色绑定的象征符号,如“绿光”、“断桥”', multiline: true, rows: 2 },
+ ],
+ extraLocationFields: [
+ { key: 'sensoryVisual', label: '视觉描写', placeholder: '色调、光线、空间感...', multiline: true, rows: 2 },
+ { key: 'sensoryAudio', label: '听觉描写', placeholder: '环境音、远处声响...', multiline: true, rows: 2 },
+ { key: 'sensorySmell', label: '嗅觉/触觉', placeholder: '气味、温度、湿度、质感...', multiline: true, rows: 2 },
+ { key: 'mood', label: '氛围/情绪基调', placeholder: '压抑、温馨、荒凉、神秘...', multiline: false },
+ ],
+ extraObjectFields: [
+ { key: 'symbolism', label: '象征意义', placeholder: '这个物品在主题上代表什么?', multiline: true, rows: 2 },
+ ],
+ },
+ screenplay: {
+ key: 'screenplay',
+ label: '剧本/脚本',
+ icon: '🎬',
+ color: '#f59e0b',
+ desc: '适合影视剧本、舞台剧、广播剧等脚本创作',
+ painPoint: '连续性与制作可行性',
+ extraCharacterFields: [
+ { key: 'castType', label: '角色类型', placeholder: '主演 / 配角 / 客串 / 群演', multiline: false },
+ { key: 'sceneCount', label: '出场场次', placeholder: '出现在哪些场次(如 4, 12, 55)', multiline: false },
+ { key: 'dialogueStyle', label: '对白风格笔记', placeholder: '说话节奏、用语习惯、语气特点...', multiline: true, rows: 3 },
+ ],
+ extraLocationFields: [
+ { key: 'slugline', label: '场景标题', placeholder: '如:INT. 厨房 - DAY / EXT. 街道 - NIGHT', multiline: false },
+ { key: 'shootingNotes', label: '拍摄备注', placeholder: '布景需求、特殊灯光、道具需求...', multiline: true, rows: 2 },
+ { key: 'usedInScenes', label: '使用场次', placeholder: '此场景在哪些场次中被使用', multiline: false },
+ ],
+ extraObjectFields: [
+ { key: 'propCategory', label: '道具分类', placeholder: '手持道具 / 场景道具 / 特效道具', multiline: false },
+ { key: 'requiredScenes', label: '所需场次', placeholder: '需要此道具的场次编号', multiline: false },
+ ],
+ },
+};
+
+// 默认项目设定结构
+const DEFAULT_SETTINGS = {
+ // 写作模式
+ writingMode: 'webnovel',
+
+ // API 配置 — 用户自己填入 API Key
+ apiConfig: {
+ provider: 'zhipu', // 预设供应商标识
+ apiKey: '',
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
+ model: 'glm-4-flash',
+ useCustomEmbed: false, // 是否使用独立的 Embedding API
+ embedProvider: 'zhipu',
+ embedApiKey: '',
+ embedBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',
+ embedModel: 'embedding-3',
+ },
+
+ // 作品基本信息
+ bookInfo: {
+ title: '',
+ genre: '', // 题材类型:玄幻/都市/悬疑/言情/科幻...
+ synopsis: '', // 故事简介/梗概
+ style: '', // 写作风格:如"轻松幽默"、"严肃沉重"、"诗意抒情"
+ tone: '', // 整体基调
+ targetAudience: '', // 目标读者
+ pov: '', // 叙事视角:第一人称/第三人称/全知视角
+ },
+
+ // 人物设定
+ characters: [
+ // 每个人物的数据结构:
+ // {
+ // id: string,
+ // name: string, // 姓名
+ // role: string, // 角色类型:主角/反派/配角/路人
+ // age: string, // 年龄
+ // gender: string, // 性别
+ // appearance: string, // 外貌描写
+ // personality: string, // 性格特征
+ // background: string, // 背景故事
+ // motivation: string, // 动机/目标
+ // skills: string, // 能力/技能
+ // speechStyle: string, // 说话风格/口头禅
+ // relationships: string, // 与其他角色的关系
+ // arc: string, // 角色成长弧线
+ // notes: string, // 其他备注
+ // }
+ ],
+
+ // 世界观设定
+ worldbuilding: {
+ era: '', // 时代背景
+ geography: '', // 地理环境
+ society: '', // 社会制度
+ culture: '', // 文化习俗
+ powerSystem: '', // 力量体系/魔法体系
+ technology: '', // 科技水平
+ rules: '', // 世界特殊规则
+ history: '', // 历史大事件
+ factions: '', // 势力/组织
+ notes: '', // 其他设定
+ },
+
+ // 大纲/剧情规划
+ plotOutline: {
+ mainConflict: '', // 核心矛盾
+ plotPoints: '', // 关键剧情节点(按顺序)
+ subplots: '', // 支线剧情
+ ending: '', // 结局方向
+ currentArc: '', // 当前所处的故事弧
+ foreshadowing: '', // 已埋伏笔
+ notes: '', // 其他备注
+ },
+
+ // 写作规则/禁忌
+ writingRules: {
+ mustDo: '', // 必须遵守的规则
+ mustNotDo: '', // 禁止出现的内容/词汇
+ styleGuide: '', // 风格指南
+ notes: '', // 其他备注
+ },
+};
+
+// 获取项目设定
+export function getProjectSettings() {
+ if (typeof window === 'undefined') return DEFAULT_SETTINGS;
+ try {
+ const data = localStorage.getItem(SETTINGS_KEY);
+ if (!data) return DEFAULT_SETTINGS;
+ return { ...DEFAULT_SETTINGS, ...JSON.parse(data) };
+ } catch {
+ return DEFAULT_SETTINGS;
+ }
+}
+
+// 保存项目设定
+export function saveProjectSettings(settings) {
+ if (typeof window === 'undefined') return;
+ localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
+}
+
+// 添加角色
+export function addCharacter(character) {
+ const settings = getProjectSettings();
+ const newChar = {
+ id: Date.now().toString(36) + Math.random().toString(36).substr(2, 5),
+ name: '',
+ role: '配角',
+ age: '',
+ gender: '',
+ appearance: '',
+ personality: '',
+ background: '',
+ motivation: '',
+ skills: '',
+ speechStyle: '',
+ relationships: '',
+ arc: '',
+ notes: '',
+ ...character,
+ };
+ settings.characters.push(newChar);
+ saveProjectSettings(settings);
+ return newChar;
+}
+
+// 更新角色
+export function updateCharacter(id, updates) {
+ const settings = getProjectSettings();
+ const idx = settings.characters.findIndex(c => c.id === id);
+ if (idx === -1) return null;
+ settings.characters[idx] = { ...settings.characters[idx], ...updates };
+ saveProjectSettings(settings);
+ return settings.characters[idx];
+}
+
+// 删除角色
+export function deleteCharacter(id) {
+ const settings = getProjectSettings();
+ settings.characters = settings.characters.filter(c => c.id !== id);
+ saveProjectSettings(settings);
+}
+
+// ==================== 写作模式读写 ====================
+
+export function getWritingMode() {
+ const settings = getProjectSettings();
+ return settings.writingMode || 'webnovel';
+}
+
+export function setWritingMode(mode) {
+ if (!WRITING_MODES[mode]) return;
+ const settings = getProjectSettings();
+ settings.writingMode = mode;
+ saveProjectSettings(settings);
+}
+
+// ==================== 树形设定集节点系统 ====================
+
+const NODES_KEY = 'author-settings-nodes';
+const ACTIVE_WORK_KEY = 'author-active-work';
+
+function generateNodeId() {
+ return Date.now().toString(36) + Math.random().toString(36).substr(2, 6);
+}
+
+// ==================== 作品级节点系统 ====================
+
+// 每个作品下自动创建的子分类模板
+const WORK_SUB_CATEGORIES = [
+ { suffix: 'bookinfo', name: '作品信息', icon: '📖', category: 'bookInfo', type: 'special' },
+ { suffix: 'characters', name: '人物设定', icon: '👤', category: 'character', type: 'folder' },
+ { suffix: 'locations', name: '空间/地点', icon: '🗺️', category: 'location', type: 'folder' },
+ { suffix: 'world', name: '世界观/设定', icon: '🌍', category: 'world', type: 'folder' },
+ { suffix: 'objects', name: '物品/道具', icon: '🔮', category: 'object', type: 'folder' },
+ { suffix: 'plot', name: '大纲', icon: '📋', category: 'plot', type: 'folder' },
+ { suffix: 'rules', name: '写作规则', icon: '📐', category: 'rules', type: 'folder' },
+];
+
+// 全局根分类(不属于任何作品)— 已废弃,所有规则均归属各作品
+const GLOBAL_ROOT_CATEGORIES = [];
+
+// 旧版 ROOT_CATEGORIES 的 id(用于迁移检测)
+const LEGACY_ROOT_IDS = [
+ 'root-bookinfo', 'root-characters', 'root-locations',
+ 'root-world', 'root-objects', 'root-plot', 'root-rules',
+];
+
+/**
+ * 创建一个作品节点及其下的完整子分类树
+ * @returns {{ workNode, subNodes }} 创建的作品节点和子分类节点数组
+ */
+export function createWorkNode(name, workId) {
+ const id = workId || ('work-' + generateNodeId());
+ const now = new Date().toISOString();
+ const workNode = {
+ id,
+ name: name || '新作品',
+ type: 'work',
+ category: 'work',
+ parentId: null,
+ order: 0,
+ icon: '📕',
+ content: {},
+ collapsed: false,
+ enabled: true,
+ createdAt: now,
+ updatedAt: now,
+ };
+ const subNodes = WORK_SUB_CATEGORIES.map((cat, i) => ({
+ id: `${id}-${cat.suffix}`,
+ name: cat.name,
+ type: cat.type,
+ category: cat.category,
+ parentId: id,
+ order: i,
+ icon: cat.icon,
+ content: {},
+ collapsed: false,
+ createdAt: now,
+ updatedAt: now,
+ }));
+ return { workNode, subNodes };
+}
+
+// ==================== 激活作品管理 ====================
+
+export function getActiveWorkId() {
+ if (typeof window === 'undefined') return null;
+ return localStorage.getItem(ACTIVE_WORK_KEY) || null;
+}
+
+export function setActiveWorkId(workId) {
+ if (typeof window === 'undefined') return;
+ if (workId) {
+ localStorage.setItem(ACTIVE_WORK_KEY, workId);
+ } else {
+ localStorage.removeItem(ACTIVE_WORK_KEY);
+ }
+}
+
+export function getAllWorks(nodes) {
+ const allNodes = nodes || getSettingsNodes();
+ return allNodes.filter(n => n.type === 'work');
+}
+
+// ==================== 节点初始化与迁移 ====================
+
+// 获取默认节点树(包含一个默认作品 + 全局规则)
+function getDefaultNodes() {
+ const { workNode, subNodes } = createWorkNode('默认作品', 'work-default');
+ return [workNode, ...subNodes];
+}
+
+// 获取所有设定节点 (Async)
+export async function getSettingsNodes() {
+ if (typeof window === 'undefined') return getDefaultNodes();
+ try {
+ let nodes = await get(NODES_KEY);
+ if (!nodes) {
+ // 首次使用:尝试从 localStorage fallback 或迁移旧数据
+ const legacyData = localStorage.getItem(NODES_KEY);
+ if (legacyData) {
+ nodes = JSON.parse(legacyData);
+ await set(NODES_KEY, nodes); // 写入 IndexedDB
+ } else {
+ const migrated = await migrateOldSettings();
+ if (migrated) {
+ nodes = await migrateToWorkStructure(migrated);
+ return nodes;
+ }
+ const defaults = getDefaultNodes();
+ await saveSettingsNodes(defaults);
+ if (!getActiveWorkId()) setActiveWorkId('work-default');
+ return defaults;
+ }
+ }
+
+ nodes = await migrateToWorkStructure(nodes);
+ nodes = await migrateGlobalRulesToWork(nodes);
+ nodes = await ensureWorkExists(nodes);
+ return nodes;
+ } catch {
+ return getDefaultNodes();
+ }
+}
+
+// 保存设定集节点 (Async)
+export async function saveSettingsNodes(nodes) {
+ if (typeof window === 'undefined') return;
+ await set(NODES_KEY, nodes);
+}
+
+/**
+ * 将旧的扁平根分类结构迁移到作品结构 (Async)
+ */
+async function migrateToWorkStructure(nodes) {
+ if (nodes.some(n => n.type === 'work')) return nodes;
+
+ const legacyRoots = nodes.filter(n => n.parentId === null && LEGACY_ROOT_IDS.includes(n.id));
+ if (legacyRoots.length === 0) return nodes;
+
+ const { workNode } = createWorkNode('默认作品', 'work-default');
+ const newNodes = [workNode];
+ for (const node of nodes) {
+ if (LEGACY_ROOT_IDS.includes(node.id) && node.parentId === null) {
+ const suffix = node.id.replace('root-', '');
+ const newId = `work-default-${suffix}`;
+ nodes.forEach(child => {
+ if (child.parentId === node.id) child.parentId = newId;
+ });
+ newNodes.push({ ...node, id: newId, parentId: 'work-default' });
+ } else if (!LEGACY_ROOT_IDS.includes(node.id)) {
+ newNodes.push(node);
+ }
+ }
+
+ for (const cat of WORK_SUB_CATEGORIES) {
+ const expectedId = `work-default-${cat.suffix}`;
+ if (!newNodes.find(n => n.id === expectedId)) {
+ newNodes.push({
+ id: expectedId, name: cat.name, type: cat.type, category: cat.category,
+ parentId: 'work-default', order: WORK_SUB_CATEGORIES.indexOf(cat), icon: cat.icon,
+ content: {}, collapsed: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
+ });
+ }
+ }
+
+ await saveSettingsNodes(newNodes);
+ if (!getActiveWorkId()) setActiveWorkId('work-default');
+ return newNodes;
+}
+
+/**
+ * 迁移旧的全局写作规则到默认作品 (Async)
+ */
+async function migrateGlobalRulesToWork(nodes) {
+ const globalRules = nodes.find(n => n.id === 'root-rules' && n.parentId === null);
+ if (!globalRules) return nodes;
+ const activeWorkId = getActiveWorkId() || 'work-default';
+ let targetRulesId = nodes.find(n => n.parentId === activeWorkId && n.category === 'rules')?.id;
+ if (!targetRulesId) {
+ const anyWork = nodes.find(n => n.type === 'work');
+ if (anyWork) targetRulesId = nodes.find(n => n.parentId === anyWork.id && n.category === 'rules')?.id;
+ }
+ if (targetRulesId) {
+ nodes.forEach(n => {
+ if (n.parentId === 'root-rules') n.parentId = targetRulesId;
+ });
+ }
+ nodes = nodes.filter(n => n.id !== 'root-rules');
+ await saveSettingsNodes(nodes);
+ return nodes;
+}
+
+// 确保至少有一个作品存在 (Async)
+async function ensureWorkExists(nodes) {
+ if (!nodes.some(n => n.type === 'work')) {
+ const { workNode, subNodes } = createWorkNode('默认作品', 'work-default');
+ nodes.push(workNode, ...subNodes);
+ await saveSettingsNodes(nodes);
+ }
+ if (!getActiveWorkId()) {
+ const firstWork = nodes.find(n => n.type === 'work');
+ if (firstWork) setActiveWorkId(firstWork.id);
+ }
+ return nodes;
+}
+
+// 添加节点 (Async)
+export async function addSettingsNode({ name, type, category, parentId, icon, content }) {
+ const nodes = await getSettingsNodes();
+ const siblings = nodes.filter(n => n.parentId === parentId);
+ const node = {
+ id: generateNodeId(),
+ name: name || (type === 'folder' ? '新分类' : '新条目'),
+ type: type || 'item',
+ category: category || 'custom',
+ parentId: parentId || null,
+ order: siblings.length,
+ icon: icon || (type === 'folder' ? '📁' : '📄'),
+ content: content || {},
+ collapsed: false,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+
+ if (node.type === 'item') {
+ const textToEmbed = extractTextForEmbedding(node);
+ const { apiConfig } = getProjectSettings();
+ node.embedding = await getEmbedding(textToEmbed, apiConfig);
+ }
+
+ nodes.push(node);
+ await saveSettingsNodes(nodes);
+ return node;
+}
+
+// 更新节点 (Async)
+export async function updateSettingsNode(id, updates) {
+ const nodes = await getSettingsNodes();
+ const idx = nodes.findIndex(n => n.id === id);
+ if (idx === -1) return null;
+ const isProtected = GLOBAL_ROOT_CATEGORIES.some(c => c.id === id) ||
+ nodes[idx].type === 'work' ||
+ (nodes[idx].parentId && nodes.some(p => p.id === nodes[idx].parentId && p.type === 'work') && WORK_SUB_CATEGORIES.some(c => id.endsWith('-' + c.suffix)));
+ if (isProtected) {
+ delete updates.type;
+ delete updates.category;
+ delete updates.parentId;
+ }
+
+ // 如果名称或内容发生改变,且是条目,重新计算 embedding
+ const nodeType = updates.type || nodes[idx].type;
+ if (nodeType === 'item' && (updates.name !== undefined || updates.content !== undefined)) {
+ const tempNode = { ...nodes[idx], ...updates };
+ const textToEmbed = extractTextForEmbedding(tempNode);
+ const { apiConfig } = getProjectSettings();
+ updates.embedding = await getEmbedding(textToEmbed, apiConfig);
+ }
+
+ nodes[idx] = { ...nodes[idx], ...updates, updatedAt: new Date().toISOString() };
+ await saveSettingsNodes(nodes);
+ return nodes[idx];
+}
+
+// 删除节点(及所有子节点) (Async)
+export async function deleteSettingsNode(id) {
+ let nodes = await getSettingsNodes();
+ const node = nodes.find(n => n.id === id);
+ if (node && node.parentId) {
+ const parent = nodes.find(p => p.id === node.parentId);
+ if (parent && parent.type === 'work' && WORK_SUB_CATEGORIES.some(c => id.endsWith('-' + c.suffix))) return false;
+ }
+ const toDelete = new Set();
+ const collect = (parentId) => {
+ toDelete.add(parentId);
+ nodes.filter(n => n.parentId === parentId).forEach(n => collect(n.id));
+ };
+ collect(id);
+ nodes = nodes.filter(n => !toDelete.has(n.id));
+ await saveSettingsNodes(nodes);
+ return true;
+}
+
+// 移动节点 (Async)
+export async function moveSettingsNode(id, newParentId) {
+ const nodes = await getSettingsNodes();
+ const idx = nodes.findIndex(n => n.id === id);
+ if (idx === -1) return null;
+ const siblings = nodes.filter(n => n.parentId === newParentId && n.id !== id);
+ nodes[idx] = {
+ ...nodes[idx],
+ parentId: newParentId,
+ order: siblings.length,
+ updatedAt: new Date().toISOString(),
+ };
+ await saveSettingsNodes(nodes);
+ return nodes[idx];
+}
+
+// 获取指定分类下的所有 item 节点(递归) (Async)
+export async function getItemsByCategory(category) {
+ const nodes = await getSettingsNodes();
+ return nodes.filter(n => n.type === 'item' && n.category === category);
+}
+
+// 获取某节点的所有子节点(直接子节点) (Async)
+export async function getChildren(parentId) {
+ const nodes = await getSettingsNodes();
+ return nodes
+ .filter(n => n.parentId === parentId)
+ .sort((a, b) => {
+ if (a.type === 'folder' && b.type !== 'folder') return -1;
+ if (a.type !== 'folder' && b.type === 'folder') return 1;
+ return a.order - b.order;
+ });
+}
+
+// 获取节点的路径(从根到当前节点的名称链) (Async)
+export async function getNodePath(id) {
+ const nodes = await getSettingsNodes();
+ const path = [];
+ let current = nodes.find(n => n.id === id);
+ while (current) {
+ path.unshift(current.name);
+ current = current.parentId ? nodes.find(n => n.id === current.parentId) : null;
+ }
+ return path;
+}
+
+// ==================== 旧数据迁移 ====================
+
+// ==================== 旧数据迁移 ====================
+
+async function migrateOldSettings() {
+ if (typeof window === 'undefined') return null;
+ try {
+ const oldData = localStorage.getItem(SETTINGS_KEY);
+ if (!oldData) return null;
+
+ const old = JSON.parse(oldData);
+ const nodes = getDefaultNodes();
+ let hasContent = false;
+
+ // 迁移人物设定
+ if (old.characters && old.characters.length > 0) {
+ old.characters.forEach((char, i) => {
+ nodes.push({
+ id: char.id || generateNodeId(),
+ name: char.name || '未命名角色',
+ type: 'item',
+ category: 'character',
+ parentId: 'root-characters',
+ order: i,
+ icon: '📄',
+ content: {
+ role: char.role || '',
+ age: char.age || '',
+ gender: char.gender || '',
+ appearance: char.appearance || '',
+ personality: char.personality || '',
+ background: char.background || '',
+ motivation: char.motivation || '',
+ skills: char.skills || '',
+ speechStyle: char.speechStyle || '',
+ relationships: char.relationships || '',
+ arc: char.arc || '',
+ notes: char.notes || '',
+ },
+ collapsed: false,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ });
+ });
+ hasContent = true;
+ }
+
+ // 迁移世界观
+ if (old.worldbuilding) {
+ const fieldMap = {
+ era: '时代背景', geography: '地理环境', society: '社会制度',
+ culture: '文化习俗', powerSystem: '力量体系', technology: '科技水平',
+ rules: '特殊规则', history: '历史大事件', factions: '势力/组织',
+ notes: '其他设定',
+ };
+ let order = 0;
+ for (const [key, label] of Object.entries(fieldMap)) {
+ if (old.worldbuilding[key]) {
+ nodes.push({
+ id: generateNodeId(),
+ name: label, type: 'item', category: 'world',
+ parentId: 'root-world', order: order++, icon: '📄',
+ content: { description: old.worldbuilding[key] },
+ collapsed: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
+ });
+ hasContent = true;
+ }
+ }
+ }
+
+ // 迁移大纲
+ if (old.plotOutline) {
+ const fieldMap = {
+ mainConflict: '核心矛盾', plotPoints: '关键剧情节点', subplots: '支线剧情',
+ currentArc: '当前故事弧', foreshadowing: '已埋伏笔', ending: '结局方向',
+ notes: '备注',
+ };
+ let order = 0;
+ for (const [key, label] of Object.entries(fieldMap)) {
+ if (old.plotOutline[key]) {
+ nodes.push({
+ id: generateNodeId(),
+ name: label, type: 'item', category: 'plot',
+ parentId: 'root-plot', order: order++, icon: '📄',
+ content: { description: old.plotOutline[key] },
+ collapsed: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
+ });
+ hasContent = true;
+ }
+ }
+ }
+
+ // 迁移写作规则
+ if (old.writingRules) {
+ const fieldMap = {
+ mustDo: '✅ 必须遵守', mustNotDo: '❌ 禁止内容',
+ styleGuide: '📝 风格指南', notes: '备注',
+ };
+ let order = 0;
+ for (const [key, label] of Object.entries(fieldMap)) {
+ if (old.writingRules[key]) {
+ nodes.push({
+ id: generateNodeId(),
+ name: label, type: 'item', category: 'rules',
+ parentId: 'root-rules', order: order++, icon: '📄',
+ content: { description: old.writingRules[key] },
+ collapsed: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
+ });
+ hasContent = true;
+ }
+ }
+ }
+
+ if (hasContent) {
+ await saveSettingsNodes(nodes);
+ return nodes;
+ }
+ return null;
+ } catch {
+ return null;
+ }
+}
diff --git a/app/lib/snapshots.js b/app/lib/snapshots.js
new file mode 100644
index 0000000..0e78e57
--- /dev/null
+++ b/app/lib/snapshots.js
@@ -0,0 +1,101 @@
+import { get, set } from 'idb-keyval';
+import { getChapters, saveChapters } from './storage';
+import { getSettingsNodes, saveSettingsNodes } from './settings';
+
+const SNAPSHOTS_KEY = 'author-snapshots';
+
+/**
+ * 获取所有快照
+ * @returns {Promise} 快照列表(按时间倒序)
+ */
+export async function getSnapshots() {
+ try {
+ const snapshots = await get(SNAPSHOTS_KEY);
+ return Array.isArray(snapshots) ? snapshots : [];
+ } catch (e) {
+ console.error('Failed to get snapshots:', e);
+ return [];
+ }
+}
+
+/**
+ * 创建新快照
+ * @param {string} label - 快照标签描述
+ * @param {string} type - 'auto' | 'manual'
+ * @returns {Promise}
+ */
+export async function createSnapshot(label, type = 'auto') {
+ try {
+ const chapters = await getChapters();
+ const settingsNodes = await getSettingsNodes();
+
+ const snapshot = {
+ id: `snap-${Date.now()}`,
+ timestamp: Date.now(),
+ label: label || (type === 'auto' ? '自动存档' : '手动存档'),
+ type,
+ stats: {
+ chapterCount: chapters.length,
+ totalWords: chapters.reduce((acc, ch) => acc + (ch.wordCount || 0), 0),
+ settingCount: settingsNodes.length,
+ },
+ data: {
+ chapters,
+ settingsNodes,
+ }
+ };
+
+ const existing = await getSnapshots();
+ existing.unshift(snapshot); // 最新在前
+
+ // 限制自动快照数量(例如最多保留 50 个自动快照,超出的按时间删除)
+ const maxAutoSnapshots = 50;
+ let finalSnapshots = existing;
+ const autoSnapshots = existing.filter(s => s.type === 'auto');
+ if (autoSnapshots.length > maxAutoSnapshots) {
+ const toRemove = autoSnapshots.slice(maxAutoSnapshots).map(s => s.id);
+ finalSnapshots = existing.filter(s => !toRemove.includes(s.id));
+ }
+
+ await set(SNAPSHOTS_KEY, finalSnapshots);
+ return snapshot;
+ } catch (e) {
+ console.error('Failed to create snapshot:', e);
+ throw e;
+ }
+}
+
+/**
+ * 恢复到指定快照
+ * @param {string} snapshotId
+ * @returns {Promise}
+ */
+export async function restoreSnapshot(snapshotId) {
+ try {
+ const snapshots = await getSnapshots();
+ const target = snapshots.find(s => s.id === snapshotId);
+ if (!target) throw new Error('Snapshot not found');
+
+ // 发起静默的当前状态备份,以防后悔
+ await createSnapshot('恢复前的备份', 'auto');
+
+ // 覆盖现有数据
+ await saveChapters(target.data.chapters || []);
+ await saveSettingsNodes(target.data.settingsNodes || []);
+
+ return true;
+ } catch (e) {
+ console.error('Failed to restore snapshot:', e);
+ throw e;
+ }
+}
+
+/**
+ * 删除指定快照
+ */
+export async function deleteSnapshot(snapshotId) {
+ const snapshots = await getSnapshots();
+ const remaining = snapshots.filter(s => s.id !== snapshotId);
+ await set(SNAPSHOTS_KEY, remaining);
+ return remaining;
+}
diff --git a/app/lib/storage.js b/app/lib/storage.js
new file mode 100644
index 0000000..4eee575
--- /dev/null
+++ b/app/lib/storage.js
@@ -0,0 +1,137 @@
+// 本地存储工具 - 使用 IndexedDB 管理核心数据 (章节、摘要)
+import { get, set } from 'idb-keyval';
+
+const STORAGE_KEY = 'author-chapters';
+
+// 生成唯一ID
+export function generateId() {
+ return Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
+}
+
+// 获取所有章节 (Async)
+export async function getChapters() {
+ if (typeof window === 'undefined') return [];
+ try {
+ let chapters = await get(STORAGE_KEY);
+ if (!chapters) {
+ // Fallback: 第一次迁移,从 localStorage 拿
+ const legacyData = localStorage.getItem(STORAGE_KEY);
+ if (legacyData) {
+ chapters = JSON.parse(legacyData);
+ await set(STORAGE_KEY, chapters);
+ } else {
+ chapters = [];
+ }
+ }
+ return chapters;
+ } catch {
+ return [];
+ }
+}
+
+// 保存所有章节 (Async)
+export async function saveChapters(chapters) {
+ if (typeof window === 'undefined') return;
+ await set(STORAGE_KEY, chapters);
+}
+
+// 创建新章节 (Async)
+export async function createChapter(title = '未命名章节') {
+ const chapters = await getChapters();
+ const newChapter = {
+ id: generateId(),
+ title,
+ content: '',
+ wordCount: 0,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+ chapters.push(newChapter);
+ await saveChapters(chapters);
+ return newChapter;
+}
+
+// 更新章节 (Async)
+export async function updateChapter(id, updates) {
+ const chapters = await getChapters();
+ const index = chapters.findIndex(ch => ch.id === id);
+ if (index === -1) return null;
+
+ chapters[index] = {
+ ...chapters[index],
+ ...updates,
+ updatedAt: new Date().toISOString(),
+ };
+ await saveChapters(chapters);
+ return chapters[index];
+}
+
+// 删除章节 (Async)
+export async function deleteChapter(id) {
+ const chapters = await getChapters();
+ const newChapters = chapters.filter(ch => ch.id !== id);
+ await saveChapters(newChapters);
+ return newChapters;
+}
+
+// 获取单个章节 (Async)
+export async function getChapter(id) {
+ const chapters = await getChapters();
+ return chapters.find(ch => ch.id === id) || null;
+}
+
+// 导出为 Markdown
+export function exportToMarkdown(chapter) {
+ const md = `# ${chapter.title}\n\n${chapter.content.replace(/<[^>]*>/g, '').replace(/ /g, ' ')}`;
+ const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${chapter.title || '未命名'}.md`;
+ a.click();
+ URL.revokeObjectURL(url);
+}
+
+// 导出所有章节
+export function exportAllToMarkdown(chapters) {
+ const md = chapters.map(ch => {
+ const text = ch.content.replace(/<[^>]*>/g, '').replace(/ /g, ' ');
+ return `# ${ch.title}\n\n${text}`;
+ }).join('\n\n---\n\n');
+
+ const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = '全部章节.md';
+ a.click();
+ URL.revokeObjectURL(url);
+}
+
+// ==================== 章节摘要缓存 ====================
+
+const SUMMARY_PREFIX = 'author-chapter-summary-';
+
+// 获取章节摘要 (Async)
+export async function getChapterSummary(id) {
+ if (typeof window === 'undefined') return null;
+ try {
+ let summary = await get(SUMMARY_PREFIX + id);
+ if (!summary) {
+ // Fallback
+ summary = localStorage.getItem(SUMMARY_PREFIX + id);
+ if (summary) {
+ await set(SUMMARY_PREFIX + id, summary);
+ }
+ }
+ return summary || null;
+ } catch {
+ return null;
+ }
+}
+
+// 保存章节摘要 (Async)
+export async function saveChapterSummary(id, summary) {
+ if (typeof window === 'undefined') return;
+ await set(SUMMARY_PREFIX + id, summary);
+}
diff --git a/app/lib/useI18n.js b/app/lib/useI18n.js
new file mode 100644
index 0000000..4d7f162
--- /dev/null
+++ b/app/lib/useI18n.js
@@ -0,0 +1,38 @@
+'use client';
+
+import { useAppStore } from '../store/useAppStore';
+import en from '../locales/en.json';
+import zh from '../locales/zh.json';
+import ru from '../locales/ru.json';
+
+const translations = { en, zh, ru };
+
+export function useI18n() {
+ // Default to 'zh' if language is null initially
+ const language = useAppStore(state => state.language) || 'zh';
+
+ const t = (path) => {
+ const keys = path.split('.');
+ let current = translations[language] || translations['zh'];
+
+ for (const key of keys) {
+ if (current === undefined || current[key] === undefined) {
+ console.warn(`Translation missing for key: ${path} in lang: ${language}`);
+ // Fallback to Chinese
+ if (language !== 'zh' && translations['zh']) {
+ let fallback = translations['zh'];
+ for (const k of keys) {
+ if (fallback[k] === undefined) return path;
+ fallback = fallback[k];
+ }
+ return fallback;
+ }
+ return path;
+ }
+ current = current[key];
+ }
+ return current;
+ };
+
+ return { t, language };
+}
diff --git a/app/locales/en.json b/app/locales/en.json
new file mode 100644
index 0000000..957c965
--- /dev/null
+++ b/app/locales/en.json
@@ -0,0 +1,405 @@
+{
+ "welcome": {
+ "title": "Welcome to Author",
+ "subtitle": "Your premium workspace for distraction-free creation.",
+ "selectLang": "Select Language",
+ "selectTheme": "Choose Your Workspace Theme",
+ "startBtn": "Enter Workspace",
+ "themeWarm": {
+ "name": "Warm Classic",
+ "desc": "A cozy, paper-like aesthetic that reduces eye strain."
+ },
+ "themeModern": {
+ "name": "Modern Glass",
+ "desc": "Sleek, translucent glassmorphism for a crisp focus."
+ }
+ },
+ "settings": {
+ "title": "Settings Hub",
+ "subtitle": "AI contextual reference",
+ "tabSettings": "📚 Project Settings",
+ "tabApi": "🔑 API Config",
+ "tabPreferences": "Preferences",
+ "newWork": "+ New Project",
+ "searchPlaceholder": "Search settings...",
+ "workLabel": "📕 Project:",
+ "confirmBtn": "Confirm",
+ "newItem": "New Item",
+ "workNamePlaceholder": "Project Name...",
+ "deleteWorkPrompt": "Are you sure you want to delete the project '{name}' and all its settings? This cannot be undone.",
+ "deleteWorkAlert": "At least one project must remain.",
+ "deleteNodePrompt": "Are you sure you want to delete '{name}'? If it's a folder, all its contents will be deleted.",
+ "statsTitle": "Statistics by Category",
+ "categories": {
+ "character": "Character",
+ "location": "Location",
+ "world": "World",
+ "object": "Item",
+ "plot": "Plot",
+ "rules": "Rules"
+ }
+ },
+ "settingsTree": {
+ "disabledHint": "Disabled (Ignored in AI context)",
+ "enableHint": "Click to enable",
+ "disableHint": "Click to disable",
+ "add": "Add"
+ },
+ "settingsEditor": {
+ "aiFill": "AI Auto-Fill",
+ "aiExtraFields": "AI Generated Extra Fields",
+ "unnamedChar": "Unnamed Character",
+ "charRole": "Role",
+ "roles": {
+ "protagonist": "Protagonist",
+ "antagonist": "Antagonist",
+ "supporting": "Supporting",
+ "minor": "Minor",
+ "proLabel": "🌟 Protagonist",
+ "antLabel": "🔥 Antagonist",
+ "supLabel": "👥 Supporting",
+ "minLabel": "🚶 Minor"
+ },
+ "infoGender": "Gender",
+ "infoAge": "Age",
+ "infoPersonality": "Personality",
+ "tabBasic": "Basic Info",
+ "tabAppearance": "Appearance & Personality",
+ "tabBackground": "Background Story",
+ "tabSkills": "Skills & Relationships",
+ "tabNotes": "Notes",
+ "tabSensory": "Sensory Details",
+ "tabMood": "Mood / Atmosphere",
+ "tabStats": "Detailed Attributes",
+ "dangerSafe": "🟢 Safe",
+ "dangerCaution": "🟡 Caution",
+ "dangerHigh": "🔴 Danger",
+ "emptyTitle": "No content yet",
+ "emptyDesc": "Click the button below to add your first setting item.",
+ "addBtn": "+ Add New Item",
+ "selectTitle": "Select an Item",
+ "selectDesc": "Select an item from the tree navigation on the left to view and edit its details.",
+ "statusPlanned": "📌 Planned",
+ "statusWriting": "✏️ Writing",
+ "statusDone": "✅ Done",
+ "charAppearance": "Appearance",
+ "charAppearancePlaceholder": "Describe the character's appearance…",
+ "charPersonality": "Personality Traits",
+ "charPersonalityPlaceholder": "Describe the character's personality…",
+ "charSpeechStyle": "Speech Style",
+ "charSpeechStylePlaceholder": "Catchphrases, tone characteristics…",
+ "charBackground": "Background",
+ "charBackgroundPlaceholder": "Character's origin and past…",
+ "charMotivation": "Motivation / Goal",
+ "charMotivationPlaceholder": "What does the character pursue?",
+ "charArc": "Character Arc",
+ "charArcPlaceholder": "How will the character change?",
+ "charSkills": "Abilities / Skills",
+ "charSkillsPlaceholder": "Martial arts, magic, special talents…",
+ "charRelationships": "Relationships",
+ "charRelationshipsPlaceholder": "Relationships with other characters…",
+ "charNotes": "Other Notes",
+ "charNotesPlaceholder": "Any additional information…",
+ "charGenderPlaceholder": "Male / Female / Other",
+ "charAgePlaceholder": "18 / Unknown",
+ "locDescription": "Location Description",
+ "locDescriptionPlaceholder": "Describe this location…",
+ "locSlugline": "Scene Heading",
+ "locSluglinePlaceholder": "E.g.: INT. PALACE HALL - NIGHT",
+ "locVisual": "Visual",
+ "locVisualPlaceholder": "What can be seen…",
+ "locAudio": "Audio",
+ "locAudioPlaceholder": "What can be heard…",
+ "locSmell": "Smell / Touch",
+ "locSmellPlaceholder": "What can be smelled / felt…",
+ "locMood": "Atmosphere",
+ "locMoodPlaceholder": "Oppressive / Warm / Mysterious…",
+ "locDangerLevel": "Danger Level",
+ "objDescription": "Description",
+ "objDescriptionPlaceholder": "Describe this item…",
+ "objType": "Type",
+ "objTypePlaceholder": "Weapon / Artifact / Potion…",
+ "objRank": "Rank / Tier",
+ "objRankPlaceholder": "Common / Rare / Legendary…",
+ "objHolder": "Current Holder",
+ "objHolderPlaceholder": "Who owns it?",
+ "objStats": "Numeric Stats",
+ "objStatsPlaceholder": "Attack +100…",
+ "objSymbolism": "Symbolism",
+ "objSymbolismPlaceholder": "What does it represent?",
+ "worldDescription": "World Setting Description",
+ "worldDescriptionPlaceholder": "Describe this worldbuilding detail…",
+ "worldNotes": "Notes",
+ "worldNotesPlaceholder": "Additional notes…",
+ "plotDescription": "Plot Description",
+ "plotDescriptionPlaceholder": "Describe this plot point…",
+ "plotStatus": "Status",
+ "plotNotes": "Notes",
+ "plotNotesPlaceholder": "Additional notes…",
+ "rulesDescription": "Rule Description",
+ "rulesDescriptionPlaceholder": "Describe this writing rule; AI will strictly follow it…",
+ "genericDescription": "Description",
+ "genericDescriptionPlaceholder": "Enter description…",
+ "genericNotes": "Notes",
+ "genericNotesPlaceholder": "Additional notes…",
+ "folderSubfolders": "{count} subfolder(s) · ",
+ "folderItems": "{count} item(s)"
+ },
+ "aiSidebar": {
+ "title": "✦ AI Assistant",
+ "tabChat": "💬 Chat",
+ "tabArchive": "📋 Archive",
+ "tabReference": "📚 Reference",
+ "btnSessionList": "Session List",
+ "btnNewSession": "New Chat",
+ "btnClose": "Close",
+ "historyCount": "Chat History ({count})",
+ "rename": "Rename",
+ "delete": "Delete",
+ "slidingWindow": "Sliding Window",
+ "summarize": "📝 Summarize",
+ "summarizeTitle": "Summarize checked chat history into a digest",
+ "clearChat": "🗑",
+ "clearChatTitle": "Clear all chats",
+ "editSummary": "Edit Summary (replaces checked history upon confirmation)",
+ "cancel": "Cancel",
+ "confirmReplace": "✓ Replace",
+ "emptyChatIcon": "✦",
+ "emptyChatTitle": "Start chatting with AI",
+ "emptyChatHint": "Ask any questions about writing",
+ "roleYou": "You",
+ "roleSummary": "📝 Summary",
+ "roleAi": "AI",
+ "edited": "Edited",
+ "btnEdit": "✏️",
+ "btnResend": "🔄",
+ "btnRegenerate": "🔄",
+ "btnBranch": "🔀",
+ "thinkingChain": "💡 Chain of Thought",
+ "save": "✓ Save",
+ "actionsApply": "✅ Apply",
+ "actionsApplied": "✓ Applied",
+ "inputPlaceholder": "Type message... (Enter to send, Shift+Enter for newline)",
+ "searchArchive": "Search generations...",
+ "emptyArchiveIcon": "📋",
+ "emptyArchiveTitle": "No generations yet",
+ "emptyArchiveHint": "AI generated content will be archived here automatically",
+ "insertEditor": "↓ Insert into Editor",
+ "copy": "📋 Copy",
+ "tokenUsage": "Token Usage",
+ "searchContext": "🔍 Search items...",
+ "emptyContextIcon": "📚",
+ "emptyContextTitle": "No context items",
+ "emptyContextHint1": "No matched items",
+ "emptyContextHint2": "Please add items in settings first",
+ "selectAll": "Select All",
+ "selectNone": "Select None",
+ "reset": "Reset",
+ "settings": "⚙️ Settings",
+ "categories": {
+ "character": "👤 Character",
+ "world": "🌍 World",
+ "location": "🗺️ Location",
+ "object": "🔮 Object",
+ "plot": "📋 Plot",
+ "rules": "📐 Rules",
+ "custom": "⚙️ Custom"
+ },
+ "actions": {
+ "add": "Add",
+ "update": "Update",
+ "delete": "Delete"
+ },
+ "modes": {
+ "continue": "Continue",
+ "rewrite": "Rewrite",
+ "expand": "Expand",
+ "condense": "Condense",
+ "dialogue": "Dialogue",
+ "chat": "Chat"
+ },
+ "statuses": {
+ "accepted": "✓ Accepted",
+ "rejected": "✗ Rejected",
+ "pending": "⏳ Pending"
+ }
+ },
+ "sidebar": {
+ "chat": "💬 Chat",
+ "archive": "📋 Archive",
+ "reference": "📚 Ref",
+ "inputPlaceholder": "Type your message... (Enter to send, Shift+Enter for new line)",
+ "newChapter": "+ New Chapter",
+ "chapterList": "Chapter List",
+ "totalWords": "Total Words",
+ "exportCurrent": "Export Current",
+ "exportAll": "Export All",
+ "tooltipSettings": "Settings Hub",
+ "tooltipTimeMachine": "Time Machine (History)",
+ "tooltipThemeLight": "Switch to Light Mode",
+ "tooltipThemeDark": "Switch to Dark Mode",
+ "btnSave": "💾 Save",
+ "btnLoad": "📂 Load",
+ "contextRename": "✎ Rename",
+ "contextExport": "↓ Export Markdown",
+ "contextDelete": "✕ Delete Chapter",
+ "deleteConfirm": "Delete chapter",
+ "alertRetainOne": "At least one chapter must remain",
+ "collapseSidebar": "Collapse Sidebar",
+ "expandSidebar": "Expand Sidebar",
+ "chapterCreated": "Created \"{title}\"",
+ "chapterDeleted": "Deleted \"{title}\"",
+ "wordUnit": " words",
+ "defaultChapterTitle": "Chapter {num}",
+ "modeLabel": "{mode} Mode",
+ "clickToSwitchMode": "Click to switch writing mode",
+ "exportedChapter": "Current chapter exported",
+ "exportedAll": "All chapters exported",
+ "importSuccess": "Page will reload automatically."
+ },
+ "page": {
+ "noChapterHint": "Select or create a chapter to start writing",
+ "expandSidebar": "Expand Sidebar",
+ "openAiAssistant": "Open AI Assistant",
+ "helpAndGuide": "Help & User Guide",
+ "toastInserted": "Inserted into editor",
+ "toastRequestFailed": "Request failed",
+ "toastStopped": "Generation stopped",
+ "toastNetworkError": "Network connection failed",
+ "dialogueUser": "User",
+ "dialogueHistory": "💬 Chat History",
+ "autoSnapshot": "Auto Snapshot",
+ "autoSnapshotFail": "Auto snapshot failed:",
+ "firstChapterTitle": "Chapter 1"
+ },
+ "snapshot": {
+ "title": "🕒 Time Machine (Version Snapshots)",
+ "subtitle": "Automatically save and manage your project's version history",
+ "createBtn": "+ New Manual Snapshot",
+ "creating": "Saving...",
+ "loading": "Loading...",
+ "empty": "No snapshots yet",
+ "deleteBtn": "Delete",
+ "createdAt": "Created at:",
+ "autoLabel": "(Auto Snapshot)",
+ "chapterCount": "Chapters",
+ "totalWords": "Total Words",
+ "settingCount": "Settings",
+ "includedChapters": "Included chapters:",
+ "andMore": "... and more",
+ "restoreBtn": "⚠️ Restore to This Version",
+ "restoring": "Restoring...",
+ "restoreWarning": "After restoring, all unsaved changes to the current project will be overwritten. A snapshot will be created automatically before overwriting just in case.",
+ "selectHint": "Select a version on the left to view details",
+ "promptLabel": "Enter a description for this snapshot (e.g.: Backup before major revision):",
+ "promptDefault": "Manual Snapshot",
+ "createFailed": "Failed to create snapshot: ",
+ "confirmRestore": "Warning: This will overwrite all chapters and settings with the state from \"{label}\" ({date}). Current state will be backed up automatically. Confirm rollback?",
+ "restoreSuccess": "Rollback successful! The page will reload to load the previous state.",
+ "restoreFailed": "Rollback failed: ",
+ "confirmDelete": "Are you sure you want to delete this snapshot? This cannot be undone.",
+ "deleteFailed": "Deletion failed",
+ "countChapters": "{count} ch.",
+ "countWords": "{count}k words",
+ "countSettings": "{count} settings"
+ },
+ "tour": {
+ "step1Title": "📖 Create Your Story",
+ "step1Content": "Click the \"+ New\" button in the left sidebar to create a new chapter. All content is saved locally in your browser in real time.",
+ "step2Title": "✍️ Immersive Writing",
+ "step2Content": "This is your main writing area. Supports Markdown syntax and auto-rendering, providing a paginated paper-like experience. Press Ctrl+J at the cursor to invoke inline AI assistance!",
+ "step3Title": "🤖 Your Personal AI Editor",
+ "step3Content": "Click here to open the AI sidebar, where you can discuss plot with AI or have it help refine your settings and provide writing suggestions.",
+ "step4Title": "⚙️ Settings & API Config",
+ "step4Content": "This is the brain of your workspace. Make sure to configure your AI model API Key here, otherwise the AI assistant won't work. You can also change language, theme, and build your world settings.",
+ "step5Title": "💡 Always Here to Help",
+ "step5Content": "If you skipped the tour or want to review it again, click the 📖 Help button. In the top-right corner of the help panel, you can restart the tour anytime.",
+ "introTitle": "Welcome to Author",
+ "introSubtitle": "Your AI-assisted novel writing platform.",
+ "introHint": "For a better writing experience, would you like a 1-minute tour of the core features?",
+ "btnSkip": "Skip, I'll explore myself",
+ "btnStart": "Start Quick Tour",
+ "btnPrev": "Previous",
+ "btnNext": "Next",
+ "btnFinish": "Got it, let's write!",
+ "btnEndTour": "End Tour"
+ },
+ "help": {
+ "title": "📖 Help Documentation",
+ "btnRetour": "✨ Replay Tour"
+ },
+ "apiConfig": {
+ "savedProfiles": "📁 Saved Profiles",
+ "loadProfile": "Load: {provider} | {model}\nSaved on {date}",
+ "deleteProfile": "Delete this profile",
+ "intro": "🔑 Configure your AI service provider. Your API Key is stored only in your browser and never uploaded to any server.",
+ "provider": "Provider",
+ "geminiNativeHint": "🔷 Calls the Gemini API directly in native format, supporting all native features. Authentication uses a URL parameter (?key=), different from the OpenAI format.",
+ "apiKeyPlaceholder": "Paste your API Key...",
+ "apiKeyConfigured": "✓ Configured",
+ "apiAddress": "API Address",
+ "apiAddressAuto": "API Address (auto-filled)",
+ "apiAddressPlaceholder": "https://api.example.com/v1",
+ "model": "Model",
+ "modelName": "Model Name",
+ "fetchModels": "🔄 Fetch model list from API",
+ "fetching": "Fetching…",
+ "fetchedCount": "Fetched {count} models from API",
+ "fetchedCountClick": "Fetched {count} models, click to select:",
+ "embedTitle": "Separate Embedding (Vector) API",
+ "embedDesc": "When enabled, you can configure a dedicated text embedding model separately from the chat model (the core technology for solving long-text memory issues).",
+ "embedProvider": "Vector Provider",
+ "embedApiKeyPlaceholder": "Leave blank if using the same key as the chat API",
+ "embedApiAddress": "Embedding API Address",
+ "embedApiAddressAuto": "Embedding API Address (auto-filled)",
+ "embedModel": "Embedding Model Name",
+ "fetchEmbedModels": "🔄 Fetch model list from API",
+ "testLoading": "⏳ Testing…",
+ "testBtn": "🔌 Test Connection",
+ "testReply": "Reply: ",
+ "saveProfileBtn": "💾 Save Current Config",
+ "saveProfilePlaceholder": "Profile name",
+ "saveBtn": "Save",
+ "howToGetKey": "💡 How to get an API Key?",
+ "keyGuide": "• ZhipuAI: Visit open.bigmodel.cn to register and create a key\n• DeepSeek: Visit platform.deepseek.com\n• Google Gemini: Visit aistudio.google.com/apikey for a free key\n• SiliconFlow: Visit siliconflow.cn (credits for new users)\n• Moonshot: Visit platform.moonshot.cn",
+ "networkError": "Network connection failed",
+ "fetchModelsFailed": "Failed to fetch model list",
+ "fetchEmbedModelsFailed": "Failed to fetch embedding model list",
+ "embedApiPrefix": "Embedding API: "
+ },
+ "preferences": {
+ "intro": "You can change your language and visual theme preferences here at any time. All changes take effect immediately and are saved locally.",
+ "langLabel": "Interface Language",
+ "themeLabel": "Visual Theme",
+ "themeWarm": "Warm Classic",
+ "themeWarmDesc": "Eye-friendly retro paper texture, focused immersion",
+ "themeModern": "Modern Glass",
+ "themeModernDesc": "Minimal white-cool grey with premium frosted glass experience"
+ },
+ "bookInfo": {
+ "intro": "💡 This information helps AI understand your work's direction and style. Even filling in just one or two fields will significantly improve AI quality.",
+ "title": "Title",
+ "titlePlaceholder": "E.g.: The Daily Life of a Cultivator",
+ "genre": "Genre",
+ "genrePlaceholder": "E.g.: Xianxia / Urban / Mystery / Romance / Sci-Fi / Fantasy / Historical...",
+ "synopsis": "Synopsis",
+ "synopsisPlaceholder": "Summarize the core of the story in a few sentences",
+ "style": "Writing Style",
+ "stylePlaceholder": "E.g.: Light-hearted, serious, poetic, hard-boiled, light novel style...",
+ "tone": "Overall Tone",
+ "tonePlaceholder": "E.g.: Warm and healing, dark and oppressive, intense and passionate...",
+ "pov": "Point of View",
+ "povPlaceholder": "E.g.: First person (protagonist's POV), limited third person, omniscient...",
+ "targetAudience": "Target Audience",
+ "targetAudiencePlaceholder": "E.g.: Male web novel readers aged 18-30, female romance readers..."
+ },
+ "common": {
+ "cancel": "Cancel",
+ "confirm": "Confirm",
+ "delete": "Delete",
+ "rename": "Rename",
+ "allCheck": "Select All",
+ "allUncheck": "Clear All",
+ "reset": "Reset"
+ }
+}
\ No newline at end of file
diff --git a/app/locales/ru.json b/app/locales/ru.json
new file mode 100644
index 0000000..70adc16
--- /dev/null
+++ b/app/locales/ru.json
@@ -0,0 +1,405 @@
+{
+ "welcome": {
+ "title": "Добро пожаловать в Author",
+ "subtitle": "Ваше премиальное пространство для творчества без отвлекающих факторов.",
+ "selectLang": "Выберите язык",
+ "selectTheme": "Выберите тему рабочей области",
+ "startBtn": "Войти в рабочую область",
+ "themeWarm": {
+ "name": "Теплая классика (Warm Classic)",
+ "desc": "Уютная эстетика бумаги, снижающая нагрузку на глаза."
+ },
+ "themeModern": {
+ "name": "Современное стекло (Modern Glass)",
+ "desc": "Изящный полупрозрачный дизайн для максимальной концентрации."
+ }
+ },
+ "settings": {
+ "title": "Центр настроек",
+ "subtitle": "Модуль контекста ИИ",
+ "tabSettings": "📚 Настройки проекта",
+ "tabApi": "🔑 API-конфигурация",
+ "tabPreferences": "Предпочтения",
+ "newWork": "+ Новый проект",
+ "searchPlaceholder": "Поиск настроек...",
+ "workLabel": "📕 Проект:",
+ "confirmBtn": "Подтвердить",
+ "newItem": "Новый элемент",
+ "workNamePlaceholder": "Название проекта...",
+ "deleteWorkPrompt": "Вы уверены, что хотите удалить проект '{name}' и все его настройки? Это действие нельзя отменить.",
+ "deleteWorkAlert": "Должен остаться хотя бы один проект.",
+ "deleteNodePrompt": "Вы уверены, что хотите удалить '{name}'? Если это папка, все ее содержимое будет удалено.",
+ "statsTitle": "Статистика по категориям",
+ "categories": {
+ "character": "Персонаж",
+ "location": "Локация",
+ "world": "Мир",
+ "object": "Предмет",
+ "plot": "Сюжет",
+ "rules": "Правила"
+ }
+ },
+ "settingsTree": {
+ "disabledHint": "Отключено (Игнорируется контекстом ИИ)",
+ "enableHint": "Нажмите, чтобы включить",
+ "disableHint": "Нажмите, чтобы отключить",
+ "add": "Добавить"
+ },
+ "settingsEditor": {
+ "aiFill": "Автозаполнение ИИ",
+ "aiExtraFields": "Дополнительные поля ИИ",
+ "unnamedChar": "Безымянный персонаж",
+ "charRole": "Роль",
+ "roles": {
+ "protagonist": "Протагонист",
+ "antagonist": "Антагонист",
+ "supporting": "Второстепенный",
+ "minor": "Эпизодический",
+ "proLabel": "🌟 Протагонист",
+ "antLabel": "🔥 Антагонист",
+ "supLabel": "👥 Второстепенный",
+ "minLabel": "🚶 Эпизодический"
+ },
+ "infoGender": "Пол",
+ "infoAge": "Возраст",
+ "infoPersonality": "Характер",
+ "tabBasic": "Базовая информация",
+ "tabAppearance": "Внешность и личность",
+ "tabBackground": "Предыстория",
+ "tabSkills": "Навыки и связи",
+ "tabNotes": "Заметки",
+ "tabSensory": "Сенсорные детали",
+ "tabMood": "Атмосфера",
+ "tabStats": "Подробные атрибуты",
+ "dangerSafe": "🟢 Безопасно",
+ "dangerCaution": "🟡 Осторожно",
+ "dangerHigh": "🔴 Опасно",
+ "emptyTitle": "Пока нет содержимого",
+ "emptyDesc": "Нажмите кнопку ниже, чтобы добавить первый элемент настройки.",
+ "addBtn": "+ Добавить новый элемент",
+ "selectTitle": "Выберите элемент",
+ "selectDesc": "Выберите элемент из дерева навигации слева, чтобы просмотреть и отредактировать его детали.",
+ "statusPlanned": "📌 Запланировано",
+ "statusWriting": "✏️ В процессе написания",
+ "statusDone": "✅ Готово",
+ "charAppearance": "Внешность",
+ "charAppearancePlaceholder": "Опишите внешность персонажа…",
+ "charPersonality": "Черты личности",
+ "charPersonalityPlaceholder": "Опишите характер персонажа…",
+ "charSpeechStyle": "Стиль речи",
+ "charSpeechStylePlaceholder": "Коронные фразы, особенности тона…",
+ "charBackground": "Предыстория",
+ "charBackgroundPlaceholder": "Происхождение и прошлое персонажа…",
+ "charMotivation": "Мотивация / Цель",
+ "charMotivationPlaceholder": "К чему стремится персонаж?",
+ "charArc": "Дуга персонажа",
+ "charArcPlaceholder": "Как изменится персонаж?",
+ "charSkills": "Способности / Навыки",
+ "charSkillsPlaceholder": "Боевые искусства, магия, особые таланты…",
+ "charRelationships": "Отношения",
+ "charRelationshipsPlaceholder": "Отношения с другими персонажами…",
+ "charNotes": "Прочие заметки",
+ "charNotesPlaceholder": "Любая дополнительная информация…",
+ "charGenderPlaceholder": "Мужской / Женский / Другой",
+ "charAgePlaceholder": "18 / Неизвестно",
+ "locDescription": "Описание локации",
+ "locDescriptionPlaceholder": "Опишите эту локацию…",
+ "locSlugline": "Заголовок сцены",
+ "locSluglinePlaceholder": "Напр.: ИНТ. ТРОННЫЙ ЗАЛ - НОЧЬ",
+ "locVisual": "Визуальное",
+ "locVisualPlaceholder": "Что видно…",
+ "locAudio": "Звуковое",
+ "locAudioPlaceholder": "Что слышно…",
+ "locSmell": "Запах / Тактильное",
+ "locSmellPlaceholder": "Что чувствуется…",
+ "locMood": "Атмосфера",
+ "locMoodPlaceholder": "Мрачная / Тёплая / Таинственная…",
+ "locDangerLevel": "Уровень опасности",
+ "objDescription": "Описание",
+ "objDescriptionPlaceholder": "Опишите этот предмет…",
+ "objType": "Тип",
+ "objTypePlaceholder": "Оружие / Артефакт / Зелье…",
+ "objRank": "Ранг / Уровень",
+ "objRankPlaceholder": "Обычный / Редкий / Легендарный…",
+ "objHolder": "Текущий владелец",
+ "objHolderPlaceholder": "Кому принадлежит?",
+ "objStats": "Числовые характеристики",
+ "objStatsPlaceholder": "Атака +100…",
+ "objSymbolism": "Символизм",
+ "objSymbolismPlaceholder": "Что это символизирует?",
+ "worldDescription": "Описание мира",
+ "worldDescriptionPlaceholder": "Опишите эту деталь мира…",
+ "worldNotes": "Заметки",
+ "worldNotesPlaceholder": "Дополнительные заметки…",
+ "plotDescription": "Описание сюжета",
+ "plotDescriptionPlaceholder": "Опишите этот сюжетный момент…",
+ "plotStatus": "Статус",
+ "plotNotes": "Заметки",
+ "plotNotesPlaceholder": "Дополнительные заметки…",
+ "rulesDescription": "Описание правила",
+ "rulesDescriptionPlaceholder": "Опишите это правило писательства; ИИ будет строго его соблюдать…",
+ "genericDescription": "Описание",
+ "genericDescriptionPlaceholder": "Введите описание…",
+ "genericNotes": "Заметки",
+ "genericNotesPlaceholder": "Дополнительные заметки…",
+ "folderSubfolders": "{count} подпапка(ок) · ",
+ "folderItems": "{count} элемент(ов)"
+ },
+ "aiSidebar": {
+ "title": "✦ ИИ-ассистент",
+ "tabChat": "💬 Чат",
+ "tabArchive": "📋 Архив",
+ "tabReference": "📚 Справочник",
+ "btnSessionList": "Список сессий",
+ "btnNewSession": "Новый чат",
+ "btnClose": "Закрыть",
+ "historyCount": "История чата ({count})",
+ "rename": "Переименовать",
+ "delete": "Удалить",
+ "slidingWindow": "Скользящее окно",
+ "summarize": "📝 Резюмировать",
+ "summarizeTitle": "Резюмировать отмеченную историю чата в дайджест",
+ "clearChat": "🗑",
+ "clearChatTitle": "Очистить все чаты",
+ "editSummary": "Редактировать резюме (заменит отмеченную историю после подтверждения)",
+ "cancel": "Отмена",
+ "confirmReplace": "✓ Заменить",
+ "emptyChatIcon": "✦",
+ "emptyChatTitle": "Начните чат с ИИ",
+ "emptyChatHint": "Задавайте любые вопросы о писательстве",
+ "roleYou": "Вы",
+ "roleSummary": "📝 Резюме",
+ "roleAi": "ИИ",
+ "edited": "Изменено",
+ "btnEdit": "✏️",
+ "btnResend": "🔄",
+ "btnRegenerate": "🔄",
+ "btnBranch": "🔀",
+ "thinkingChain": "💡 Цепь рассуждений",
+ "save": "✓ Сохранить",
+ "actionsApply": "✅ Применить",
+ "actionsApplied": "✓ Применено",
+ "inputPlaceholder": "Введите сообщение... (Enter для отправки, Shift+Enter для новой строки)",
+ "searchArchive": "Поиск по генерациям...",
+ "emptyArchiveIcon": "📋",
+ "emptyArchiveTitle": "Пока нет генераций",
+ "emptyArchiveHint": "Сгенерированный ИИ контент будет автоматически сохраняться здесь",
+ "insertEditor": "↓ Вставить в редактор",
+ "copy": "📋 Копировать",
+ "tokenUsage": "Использование токенов",
+ "searchContext": "🔍 Поиск элементов...",
+ "emptyContextIcon": "📚",
+ "emptyContextTitle": "Нет элементов контекста",
+ "emptyContextHint1": "Нет совпадений",
+ "emptyContextHint2": "Пожалуйста, сначала добавьте настройки",
+ "selectAll": "Выбрать все",
+ "selectNone": "Снять выделение",
+ "reset": "Сброс",
+ "settings": "⚙️ Настройки",
+ "categories": {
+ "character": "👤 Персонаж",
+ "world": "🌍 Мир",
+ "location": "🗺️ Локация",
+ "object": "🔮 Предмет",
+ "plot": "📋 Сюжет",
+ "rules": "📐 Правила",
+ "custom": "⚙️ Пользовательский"
+ },
+ "actions": {
+ "add": "Добавить",
+ "update": "Обновить",
+ "delete": "Удалить"
+ },
+ "modes": {
+ "continue": "Продолжить",
+ "rewrite": "Переписать",
+ "expand": "Расширить",
+ "condense": "Сократить",
+ "dialogue": "Диалог",
+ "chat": "Чат"
+ },
+ "statuses": {
+ "accepted": "✓ Принято",
+ "rejected": "✗ Отклонено",
+ "pending": "⏳ Ожидание"
+ }
+ },
+ "sidebar": {
+ "chat": "💬 Чат",
+ "archive": "📋 Архив",
+ "reference": "📚 Справка",
+ "inputPlaceholder": "Введите сообщение... (Enter - отправить, Shift+Enter - перенос строки)",
+ "newChapter": "+ Новая глава",
+ "chapterList": "Список глав",
+ "totalWords": "Всего слов",
+ "exportCurrent": "Экспорт текущей",
+ "exportAll": "Экспорт всех",
+ "tooltipSettings": "Управление настройками",
+ "tooltipTimeMachine": "Машина времени (История версий)",
+ "tooltipThemeLight": "Светлая тема",
+ "tooltipThemeDark": "Темная тема",
+ "btnSave": "💾 Сохранить",
+ "btnLoad": "📂 Загрузить",
+ "contextRename": "✎ Переименовать",
+ "contextExport": "↓ Экспорт Markdown",
+ "contextDelete": "✕ Удалить главу",
+ "deleteConfirm": "Удалить главу",
+ "alertRetainOne": "Должна остаться хотя бы одна глава",
+ "collapseSidebar": "Свернуть панель",
+ "expandSidebar": "Развернуть панель",
+ "chapterCreated": "Создано \"{title}\"",
+ "chapterDeleted": "Удалено \"{title}\"",
+ "wordUnit": " сл.",
+ "defaultChapterTitle": "Глава {num}",
+ "modeLabel": "Режим {mode}",
+ "clickToSwitchMode": "Нажмите для смены режима",
+ "exportedChapter": "Текущая глава экспортирована",
+ "exportedAll": "Все главы экспортированы",
+ "importSuccess": "Страница перезагрузится автоматически."
+ },
+ "page": {
+ "noChapterHint": "Выберите или создайте главу, чтобы начать писать",
+ "expandSidebar": "Развернуть панель",
+ "openAiAssistant": "Открыть ИИ-ассистент",
+ "helpAndGuide": "Справка и руководство",
+ "toastInserted": "Вставлено в редактор",
+ "toastRequestFailed": "Запрос не выполнен",
+ "toastStopped": "Генерация остановлена",
+ "toastNetworkError": "Ошибка сетевого соединения",
+ "dialogueUser": "Пользователь",
+ "dialogueHistory": "💬 История чата",
+ "autoSnapshot": "Автосохранение",
+ "autoSnapshotFail": "Ошибка автосохранения:",
+ "firstChapterTitle": "Глава 1"
+ },
+ "snapshot": {
+ "title": "🕒 Машина времени (Снимки версий)",
+ "subtitle": "Автоматически сохраняйте историю версий вашего проекта и управляйте ею",
+ "createBtn": "+ Новый ручной снимок",
+ "creating": "Сохранение...",
+ "loading": "Загрузка...",
+ "empty": "Снимков пока нет",
+ "deleteBtn": "Удалить",
+ "createdAt": "Создан:",
+ "autoLabel": "(Автоснимок)",
+ "chapterCount": "Глав",
+ "totalWords": "Всего слов",
+ "settingCount": "Настроек",
+ "includedChapters": "Включённые главы:",
+ "andMore": "... и ещё",
+ "restoreBtn": "⚠️ Восстановить до этой версии",
+ "restoring": "Восстановление...",
+ "restoreWarning": "После восстановления все несохранённые изменения текущего проекта будут перезаписаны. Снимок будет создан автоматически перед перезаписью, на всякий случай.",
+ "selectHint": "Выберите версию слева для просмотра деталей",
+ "promptLabel": "Введите описание снимка (напр.: Резервная копия перед крупными правками):",
+ "promptDefault": "Ручной снимок",
+ "createFailed": "Не удалось создать снимок: ",
+ "confirmRestore": "Предупреждение: все главы и настройки будут перезаписаны состоянием из «{label}» ({date}). Текущее состояние будет автоматически сохранено. Подтвердить откат?",
+ "restoreSuccess": "Откат выполнен! Страница перезагрузится для загрузки предыдущего состояния.",
+ "restoreFailed": "Откат не выполнен: ",
+ "confirmDelete": "Вы уверены, что хотите удалить этот снимок? Это действие нельзя отменить.",
+ "deleteFailed": "Удаление не выполнено",
+ "countChapters": "{count} гл.",
+ "countWords": "{count}k сл.",
+ "countSettings": "{count} настр."
+ },
+ "tour": {
+ "step1Title": "📖 Создайте свою историю",
+ "step1Content": "Нажмите кнопку «+ Новая» на левой панели, чтобы создать новую главу. Всё содержимое сохраняется локально в вашем браузере в режиме реального времени.",
+ "step2Title": "✍️ Захватывающее написание",
+ "step2Content": "Это ваше основное поле для написания. Поддерживает синтаксис Markdown и автоматический рендеринг, предоставляя постраничный опыт, похожий на бумагу. Нажмите Ctrl+J в месте курсора для вызова встроенного ИИ-помощника!",
+ "step3Title": "🤖 Ваш персональный ИИ-редактор",
+ "step3Content": "Нажмите здесь, чтобы открыть боковую панель ИИ, где вы можете обсуждать сюжет с ИИ или попросить его уточнить настройки и дать советы по написанию.",
+ "step4Title": "⚙️ Настройки и конфигурация API",
+ "step4Content": "Это мозг вашего рабочего пространства. Обязательно настройте здесь API Key вашей языковой модели, иначе ИИ-ассистент не будет работать. Вы также можете изменить язык, тему и создать настройки мира.",
+ "step5Title": "💡 Всегда готов помочь",
+ "step5Content": "Если вы пропустили тур или хотите просмотреть его снова, нажмите кнопку 📖 Справка. В правом верхнем углу панели справки вы можете в любое время перезапустить тур.",
+ "introTitle": "Добро пожаловать в Author",
+ "introSubtitle": "Ваша платформа для написания романов с помощью ИИ.",
+ "introHint": "Для лучшего опыта написания, хотите ли вы пройти 1-минутный тур по основным функциям?",
+ "btnSkip": "Пропустить, я разберусь сам",
+ "btnStart": "Начать быстрый тур",
+ "btnPrev": "Назад",
+ "btnNext": "Далее",
+ "btnFinish": "Понял, начнём писать!",
+ "btnEndTour": "Завершить тур"
+ },
+ "help": {
+ "title": "📖 Справочная документация",
+ "btnRetour": "✨ Повторить тур"
+ },
+ "apiConfig": {
+ "savedProfiles": "📁 Сохранённые профили",
+ "loadProfile": "Загрузить: {provider} | {model}\nСохранено {date}",
+ "deleteProfile": "Удалить этот профиль",
+ "intro": "🔑 Настройте вашего провайдера услуг ИИ. Ваш API Key хранится только в вашем браузере и никогда не загружается ни на один сервер.",
+ "provider": "Провайдер",
+ "geminiNativeHint": "🔷 Вызывает Gemini API напрямую в нативном формате, поддерживая все нативные функции. Аутентификация использует параметр URL (?key=), отличаясь от формата OpenAI.",
+ "apiKeyPlaceholder": "Вставьте ваш API Key...",
+ "apiKeyConfigured": "✓ Настроен",
+ "apiAddress": "API Адрес",
+ "apiAddressAuto": "API Адрес (автозаполнение)",
+ "apiAddressPlaceholder": "https://api.example.com/v1",
+ "model": "Модель",
+ "modelName": "Название модели",
+ "fetchModels": "🔄 Получить список моделей из API",
+ "fetching": "Получение…",
+ "fetchedCount": "Получено {count} моделей из API",
+ "fetchedCountClick": "Получено {count} моделей, нажмите для выбора:",
+ "embedTitle": "Отдельный API Embedding (Векторный)",
+ "embedDesc": "При включении вы можете настроить специальную модель векторного встраивания текста отдельно от модели чата (ключевая технология для решения проблем долговременной памяти).",
+ "embedProvider": "Векторный провайдер",
+ "embedApiKeyPlaceholder": "Оставьте пустым, если используете тот же ключ, что и для чата",
+ "embedApiAddress": "Адрес Embedding API",
+ "embedApiAddressAuto": "Адрес Embedding API (автозаполнение)",
+ "embedModel": "Название Embedding модели",
+ "fetchEmbedModels": "🔄 Получить список моделей из API",
+ "testLoading": "⏳ Тестирование…",
+ "testBtn": "🔌 Проверить подключение",
+ "testReply": "Ответ: ",
+ "saveProfileBtn": "💾 Сохранить текущую конфигурацию",
+ "saveProfilePlaceholder": "Название профиля",
+ "saveBtn": "Сохранить",
+ "howToGetKey": "💡 Как получить API Key?",
+ "keyGuide": "• ZhipuAI: Посетите open.bigmodel.cn для регистрации\n• DeepSeek: Посетите platform.deepseek.com\n• Google Gemini: Посетите aistudio.google.com/apikey\n• SiliconFlow: Посетите siliconflow.cn\n• Moonshot: Посетите platform.moonshot.cn",
+ "networkError": "Ошибка сетевого соединения",
+ "fetchModelsFailed": "Не удалось получить список моделей",
+ "fetchEmbedModelsFailed": "Не удалось получить список моделей Embedding",
+ "embedApiPrefix": "Embedding API: "
+ },
+ "preferences": {
+ "intro": "Здесь вы можете в любое время изменить язык и визуальную тему. Все изменения вступают в силу немедленно и сохраняются локально.",
+ "langLabel": "Язык интерфейса",
+ "themeLabel": "Визуальная тема",
+ "themeWarm": "Тёплая классика (Warm Classic)",
+ "themeWarmDesc": "Бережная для глаз ретро-бумажная текстура, сосредоточенное погружение",
+ "themeModern": "Современное стекло (Modern Glass)",
+ "themeModernDesc": "Минималистичный белый/холодно-серый с премиальным матовым стеклом"
+ },
+ "bookInfo": {
+ "intro": "💡 Эта информация помогает ИИ понять направление и стиль вашего произведения. Даже заполнение одного-двух полей значительно улучшит качество ИИ.",
+ "title": "Название произведения",
+ "titlePlaceholder": "Напр.: Повседневная жизнь культиватора",
+ "genre": "Жанр",
+ "genrePlaceholder": "Напр.: Сянься / Городская проза / Детектив / Романс / Фантастика...",
+ "synopsis": "Синопсис",
+ "synopsisPlaceholder": "Опишите суть истории в нескольких предложениях",
+ "style": "Стиль написания",
+ "stylePlaceholder": "Напр.: Лёгкий и юмористический, серьёзный, поэтический...",
+ "tone": "Общий тон",
+ "tonePlaceholder": "Напр.: Тёплый и исцеляющий, мрачный, интенсивный...",
+ "pov": "Точка зрения",
+ "povPlaceholder": "Напр.: Первое лицо, ограниченное третье лицо, всезнающий...",
+ "targetAudience": "Целевая аудитория",
+ "targetAudiencePlaceholder": "Напр.: Мужская аудитория 18-30 лет, читатели романтики..."
+ },
+ "common": {
+ "cancel": "Отмена",
+ "confirm": "Подтвердить",
+ "delete": "Удалить",
+ "rename": "Переименовать",
+ "allCheck": "Выбрать все",
+ "allUncheck": "Очистить все",
+ "reset": "Сброс"
+ }
+}
\ No newline at end of file
diff --git a/app/locales/zh.json b/app/locales/zh.json
new file mode 100644
index 0000000..0ad3ed0
--- /dev/null
+++ b/app/locales/zh.json
@@ -0,0 +1,405 @@
+{
+ "welcome": {
+ "title": "欢迎来到 Author",
+ "subtitle": "您的尊享级无干扰创作空间。",
+ "selectLang": "选择语言",
+ "selectTheme": "选择您的工作台主题",
+ "startBtn": "进入工作台",
+ "themeWarm": {
+ "name": "经典护眼 (Warm Classic)",
+ "desc": "温润如纸的复古色调,久看书写不疲劳。"
+ },
+ "themeModern": {
+ "name": "现代通透 (Modern Glass)",
+ "desc": "通透干净的磨砂质感,极致专注的悬浮体验。"
+ }
+ },
+ "settings": {
+ "title": "设定集管理",
+ "subtitle": "AI写作时会自动参考这些信息",
+ "tabSettings": "📚 创作设定集",
+ "tabApi": "🔑 API配置",
+ "tabPreferences": "偏好设置",
+ "newWork": "+ 新作品",
+ "searchPlaceholder": "搜索设定…",
+ "workLabel": "📕 作品:",
+ "confirmBtn": "确定",
+ "newItem": "新条目",
+ "workNamePlaceholder": "作品名称…",
+ "deleteWorkPrompt": "确定删除作品「{name}」及其所有设定?此操作不可撤销。",
+ "deleteWorkAlert": "至少需要保留一个作品",
+ "deleteNodePrompt": "确定删除「{name}」?如果是文件夹,其下所有内容都会被删除。",
+ "statsTitle": "按设定类别统计",
+ "categories": {
+ "character": "人物",
+ "location": "地点",
+ "world": "世界观",
+ "object": "物品",
+ "plot": "大纲",
+ "rules": "规则"
+ }
+ },
+ "settingsTree": {
+ "disabledHint": "已禁用(不参与AI上下文)",
+ "enableHint": "点击启用",
+ "disableHint": "点击禁用",
+ "add": "添加"
+ },
+ "settingsEditor": {
+ "aiFill": "AI 智能填写",
+ "aiExtraFields": "AI 生成的额外字段",
+ "unnamedChar": "未命名角色",
+ "charRole": "角色",
+ "roles": {
+ "protagonist": "主角",
+ "antagonist": "反派",
+ "supporting": "配角",
+ "minor": "路人",
+ "proLabel": "🌟 主角",
+ "antLabel": "🔥 反派",
+ "supLabel": "👥 配角",
+ "minLabel": "🚶 路人"
+ },
+ "infoGender": "性别",
+ "infoAge": "年龄",
+ "infoPersonality": "性格",
+ "tabBasic": "基础信息",
+ "tabAppearance": "外貌与性格",
+ "tabBackground": "背景故事",
+ "tabSkills": "能力与关系",
+ "tabNotes": "备注",
+ "tabSensory": "感官细节",
+ "tabMood": "氛围",
+ "tabStats": "详细属性",
+ "dangerSafe": "🟢 安全",
+ "dangerCaution": "🟡 小心",
+ "dangerHigh": "🔴 危险",
+ "emptyTitle": "还没有内容",
+ "emptyDesc": "点击下方按钮添加你的第一个设定项",
+ "addBtn": "+ 添加新设定项",
+ "selectTitle": "选择一个设定项",
+ "selectDesc": "从左侧树形导航中选择一个项目来查看和编辑它的详细信息",
+ "statusPlanned": "📌 计划中",
+ "statusWriting": "✏️ 写作中",
+ "statusDone": "✅ 已完成",
+ "charAppearance": "外貌描写",
+ "charAppearancePlaceholder": "描述角色的外貌特征…",
+ "charPersonality": "性格特征",
+ "charPersonalityPlaceholder": "描述角色的性格…",
+ "charSpeechStyle": "说话风格",
+ "charSpeechStylePlaceholder": "口头禅、语气特点…",
+ "charBackground": "背景经历",
+ "charBackgroundPlaceholder": "角色的来历和过往…",
+ "charMotivation": "动机/目标",
+ "charMotivationPlaceholder": "角色追求什么?",
+ "charArc": "成长弧线",
+ "charArcPlaceholder": "角色将经历怎样的转变?",
+ "charSkills": "能力/技能",
+ "charSkillsPlaceholder": "武功、法术、特长…",
+ "charRelationships": "人物关系",
+ "charRelationshipsPlaceholder": "与其他角色的关系…",
+ "charNotes": "其他备注",
+ "charNotesPlaceholder": "任何补充信息…",
+ "charGenderPlaceholder": "男/女/其他",
+ "charAgePlaceholder": "18岁 / 不详",
+ "locDescription": "地点描述",
+ "locDescriptionPlaceholder": "描述这个地点…",
+ "locSlugline": "场景标题",
+ "locSluglinePlaceholder": "如:内景 - 皇宫大殿 - 夜",
+ "locVisual": "视觉",
+ "locVisualPlaceholder": "看到什么…",
+ "locAudio": "听觉",
+ "locAudioPlaceholder": "听到什么…",
+ "locSmell": "嗅觉/触觉",
+ "locSmellPlaceholder": "闻到/摸到什么…",
+ "locMood": "整体氛围",
+ "locMoodPlaceholder": "压抑/温馨/神秘…",
+ "locDangerLevel": "危险等级",
+ "objDescription": "描述",
+ "objDescriptionPlaceholder": "描述这个物品…",
+ "objType": "类型",
+ "objTypePlaceholder": "武器/法宝/药品…",
+ "objRank": "品阶",
+ "objRankPlaceholder": "凡品/灵品/仙品…",
+ "objHolder": "当前持有者",
+ "objHolderPlaceholder": "谁拥有它?",
+ "objStats": "数值属性",
+ "objStatsPlaceholder": "攻击力+100…",
+ "objSymbolism": "象征意义",
+ "objSymbolismPlaceholder": "它象征着什么?",
+ "worldDescription": "设定描述",
+ "worldDescriptionPlaceholder": "描述这条世界观设定…",
+ "worldNotes": "备注",
+ "worldNotesPlaceholder": "补充说明…",
+ "plotDescription": "剧情描述",
+ "plotDescriptionPlaceholder": "描述这段剧情…",
+ "plotStatus": "状态",
+ "plotNotes": "备注",
+ "plotNotesPlaceholder": "补充说明…",
+ "rulesDescription": "规则描述",
+ "rulesDescriptionPlaceholder": "描述这条写作规则,AI 生成内容时将严格遵守…",
+ "genericDescription": "描述",
+ "genericDescriptionPlaceholder": "输入描述内容…",
+ "genericNotes": "备注",
+ "genericNotesPlaceholder": "补充说明…",
+ "folderSubfolders": "{count} 个子文件夹 · ",
+ "folderItems": "{count} 个设定项"
+ },
+ "aiSidebar": {
+ "title": "✦ AI 助手",
+ "tabChat": "💬 对话",
+ "tabArchive": "📋 存档",
+ "tabReference": "📚 参考",
+ "btnSessionList": "会话列表",
+ "btnNewSession": "新建对话",
+ "btnClose": "关闭",
+ "historyCount": "对话历史 ({count})",
+ "rename": "重命名",
+ "delete": "删除",
+ "slidingWindow": "滑动窗口",
+ "summarize": "📝 总结",
+ "summarizeTitle": "将勾选的对话历史总结为一条摘要",
+ "clearChat": "🗑",
+ "clearChatTitle": "清空全部对话",
+ "editSummary": "编辑摘要(确认后将替代勾选的历史消息)",
+ "cancel": "取消",
+ "confirmReplace": "✓ 确认替代",
+ "emptyChatIcon": "✦",
+ "emptyChatTitle": "开始与 AI 对话",
+ "emptyChatHint": "可以问任何关于创作的问题",
+ "roleYou": "你",
+ "roleSummary": "📝 摘要",
+ "roleAi": "AI",
+ "edited": "已编辑",
+ "btnEdit": "✏️",
+ "btnResend": "🔄",
+ "btnRegenerate": "🔄",
+ "btnBranch": "🔀",
+ "thinkingChain": "💡 思维链",
+ "save": "✓ 保存",
+ "actionsApply": "✅ 应用",
+ "actionsApplied": "✓ 已应用",
+ "inputPlaceholder": "输入消息…(Enter 发送,Shift+Enter 换行)",
+ "searchArchive": "搜索生成内容…",
+ "emptyArchiveIcon": "📋",
+ "emptyArchiveTitle": "暂无生成记录",
+ "emptyArchiveHint": "AI 生成的内容会自动归档到这里",
+ "insertEditor": "↓ 插入编辑器",
+ "copy": "📋 复制",
+ "tokenUsage": "Token 用量",
+ "searchContext": "🔍 搜索条目…",
+ "emptyContextIcon": "📚",
+ "emptyContextTitle": "暂无上下文条目",
+ "emptyContextHint1": "没有匹配的条目",
+ "emptyContextHint2": "请先在设定集中添加设定",
+ "selectAll": "全选",
+ "selectNone": "全不选",
+ "reset": "重置",
+ "settings": "⚙️ 设定集",
+ "categories": {
+ "character": "👤 角色",
+ "world": "🌍 世界观",
+ "location": "🗺️ 地点",
+ "object": "🔮 物品",
+ "plot": "📋 大纲",
+ "rules": "📐 规则",
+ "custom": "⚙️ 自定义"
+ },
+ "actions": {
+ "add": "新增",
+ "update": "更新",
+ "delete": "删除"
+ },
+ "modes": {
+ "continue": "续写",
+ "rewrite": "改写",
+ "expand": "扩写",
+ "condense": "精简",
+ "dialogue": "对话",
+ "chat": "对话"
+ },
+ "statuses": {
+ "accepted": "✓ 已接受",
+ "rejected": "✗ 已拒绝",
+ "pending": "⏳ 待确认"
+ }
+ },
+ "sidebar": {
+ "chat": "💬 对话",
+ "archive": "📋 存档",
+ "reference": "📚 参考",
+ "inputPlaceholder": "输入消息…(Enter 发送,Shift+Enter 换行)",
+ "newChapter": "+ 新建章节",
+ "chapterList": "章节列表",
+ "totalWords": "全书总字数",
+ "exportCurrent": "导出本章",
+ "exportAll": "导出全部",
+ "tooltipSettings": "设定集管理",
+ "tooltipTimeMachine": "时光机(版本历史)",
+ "tooltipThemeLight": "切换浅色模式",
+ "tooltipThemeDark": "切换深色模式",
+ "btnSave": "💾 存档",
+ "btnLoad": "📂 读档",
+ "contextRename": "✎ 重命名",
+ "contextExport": "↓ 导出Markdown",
+ "contextDelete": "✕ 删除章节",
+ "deleteConfirm": "确定删除章节",
+ "alertRetainOne": "至少保留一个章节",
+ "collapseSidebar": "收起侧栏",
+ "expandSidebar": "展开侧栏",
+ "chapterCreated": "已创建「{title}」",
+ "chapterDeleted": "已删除「{title}」",
+ "wordUnit": "字",
+ "defaultChapterTitle": "第{num}章",
+ "modeLabel": "{mode}模式",
+ "clickToSwitchMode": "点击切换写作模式",
+ "exportedChapter": "已导出当前章节",
+ "exportedAll": "已导出全部章节",
+ "importSuccess": "页面将自动刷新。"
+ },
+ "page": {
+ "noChapterHint": "请选择或创建一个章节开始写作",
+ "expandSidebar": "展开侧栏",
+ "openAiAssistant": "打开 AI 助手",
+ "helpAndGuide": "帮助与操作指南",
+ "toastInserted": "已插入到编辑器",
+ "toastRequestFailed": "请求失败",
+ "toastStopped": "已停止生成",
+ "toastNetworkError": "网络连接失败",
+ "dialogueUser": "用户",
+ "dialogueHistory": "💬 对话历史",
+ "autoSnapshot": "系统自动存档",
+ "autoSnapshotFail": "自动存档失败:",
+ "firstChapterTitle": "第一章"
+ },
+ "snapshot": {
+ "title": "🕒 时光机 (版本快照)",
+ "subtitle": "自动保存和管理你的项目历史版本",
+ "createBtn": "+ 新建手动存档",
+ "creating": "存档中...",
+ "loading": "加载中...",
+ "empty": "暂无历史存档",
+ "deleteBtn": "删除",
+ "createdAt": "创建于:",
+ "autoLabel": "(系统自动存档)",
+ "chapterCount": "章节数",
+ "totalWords": "总字数",
+ "settingCount": "设定项",
+ "includedChapters": "包含的章节片段:",
+ "andMore": "... 等等",
+ "restoreBtn": "⚠️ 恢复到此版本",
+ "restoring": "回滚中...",
+ "restoreWarning": "恢复后,当前项目的所有未保存修改将被覆盖。但在覆盖前,系统会自动创建一次快照以防万一。",
+ "selectHint": "请选择左侧的历史版本查看详情",
+ "promptLabel": "请输入手动存档说明(例如:大改前备份):",
+ "promptDefault": "手动存档",
+ "createFailed": "创建存档失败:",
+ "confirmRestore": "警告:这会将所有章节和设定集覆盖为「{label}」({date}) 的状态。当前状态会自动备份。确认回滚吗?",
+ "restoreSuccess": "回滚成功!页面将刷新以加载上一个状态。",
+ "restoreFailed": "回滚失败:",
+ "confirmDelete": "确定删除此存档吗?不可恢复。",
+ "deleteFailed": "删除失败",
+ "countChapters": "{count}章",
+ "countWords": "{count}k字",
+ "countSettings": "{count}项设定"
+ },
+ "tour": {
+ "step1Title": "📖 创建你的故事",
+ "step1Content": "点击左侧边栏的「+ 新建」可以创建新章节。所有内容都会实时保存在你的浏览器本地。",
+ "step2Title": "✍️ 沉浸式创作",
+ "step2Content": "这是你的写作主场。支持 Markdown 语法与自动渲染,提供分页的类纸张体验。在光标处按下 Ctrl+J 即可唤起内联 AI 协助续写!",
+ "step3Title": "🤖 你的专属 AI 编辑",
+ "step3Content": "点击这里打开 AI 侧栏,可以与 AI 讨论剧情,也可以让它帮你修改设定集、提供写作建议。",
+ "step4Title": "⚙️ 设定集与 API 配置",
+ "step4Content": "这是工作台的大脑中枢。请务必在此配置你的大模型 API Key,否则 AI 助理无法工作。另外,你还可以在这里随时更改语言、主题、并建立属于你的世界观设定。",
+ "step5Title": "💡 随时准备为您服务",
+ "step5Content": "如果您跳过了引导,或希望重新回顾,都可以点击这个 📖 帮助按钮。在帮助面板的右上角,您可以随时重新开启向导。",
+ "introTitle": "欢迎来到 Author",
+ "introSubtitle": "你的 AI 辅助小说创作平台。",
+ "introHint": "为了更好的创作体验,需要花 1 分钟为您介绍核心功能吗?",
+ "btnSkip": "跳过,我自己探索",
+ "btnStart": "开启快速向导",
+ "btnPrev": "上一步",
+ "btnNext": "下一步",
+ "btnFinish": "明白,开始创作",
+ "btnEndTour": "直接结束引导"
+ },
+ "help": {
+ "title": "📖 帮助文档",
+ "btnRetour": "✨ 重新查看向导"
+ },
+ "apiConfig": {
+ "savedProfiles": "📁 已保存的配置方案",
+ "loadProfile": "加载:{provider} | {model}\n保存于 {date}",
+ "deleteProfile": "删除此方案",
+ "intro": "🔑 配置你的AI服务提供商。API Key 仅存储在你的浏览器本地,不会上传到任何服务器。",
+ "provider": "服务提供商",
+ "geminiNativeHint": "🔷 原生格式直接调用 Gemini API,支持全部原生功能。认证方式使用 URL 参数(?key=),与 OpenAI 格式不同。",
+ "apiKeyPlaceholder": "粘贴你的 API Key...",
+ "apiKeyConfigured": "✓ 已配置",
+ "apiAddress": "API 地址",
+ "apiAddressAuto": "API 地址(已自动填入)",
+ "apiAddressPlaceholder": "https://api.example.com/v1",
+ "model": "模型",
+ "modelName": "模型名称",
+ "fetchModels": "🔄 从API拉取模型列表",
+ "fetching": "拉取中…",
+ "fetchedCount": "已从 API 拉取到 {count} 个模型",
+ "fetchedCountClick": "已拉取到 {count} 个模型,点击选择:",
+ "embedTitle": "独立配置 Embedding (向量) API",
+ "embedDesc": "开启后,您可以将普通对话模型与专门的文本向量模型(解决长文本记忆的核心技术)分开配置。",
+ "embedProvider": "向量服务提供商",
+ "embedApiKeyPlaceholder": "如果你和对话接口用的是同一个 Key,可留空",
+ "embedApiAddress": "Embedding API 地址",
+ "embedApiAddressAuto": "Embedding API 地址(已自动填入)",
+ "embedModel": "Embedding 模型名称",
+ "fetchEmbedModels": "🔄 从API拉取模型列表",
+ "testLoading": "⏳ 测试中…",
+ "testBtn": "🔌 测试连接",
+ "testReply": "回复:",
+ "saveProfileBtn": "💾 保存当前配置",
+ "saveProfilePlaceholder": "方案名称",
+ "saveBtn": "保存",
+ "howToGetKey": "💡 如何获取 API Key?",
+ "keyGuide": "• 智谱AI:访问 open.bigmodel.cn 注册并创建 Key\n• DeepSeek:访问 platform.deepseek.com\n• Google Gemini:访问 aistudio.google.com/apikey 获取免费 Key\n• SiliconFlow:访问 siliconflow.cn(新用户送额度)\n• Moonshot:访问 platform.moonshot.cn",
+ "networkError": "网络连接失败",
+ "fetchModelsFailed": "拉取模型列表失败",
+ "fetchEmbedModelsFailed": "拉取向量模型列表失败",
+ "embedApiPrefix": "向量 API: "
+ },
+ "preferences": {
+ "intro": "在这里可以随时更改应用程序的语言和视觉主题偏好。所有的更改都会实时生效并保存在本地。",
+ "langLabel": "界面语言 (Language)",
+ "themeLabel": "视觉主题 (Theme)",
+ "themeWarm": "经典纸张 (Warm Classic)",
+ "themeWarmDesc": "护眼的复古纸张质感,专注沉浸",
+ "themeModern": "现代通透 (Modern Glass)",
+ "themeModernDesc": "极简白冷灰搭配高级毛玻璃体验"
+ },
+ "bookInfo": {
+ "intro": "💡 这些信息帮助AI理解你的作品定位和风格,即使只填一两项也会大幅提升AI质量。",
+ "title": "作品名称",
+ "titlePlaceholder": "例:《修仙世界的日常生活》",
+ "genre": "题材类型",
+ "genrePlaceholder": "例:仙侠/都市/悬疑/言情/科幻/奇幻/历史...",
+ "synopsis": "故事简介",
+ "synopsisPlaceholder": "用几句话概括整个故事的核心",
+ "style": "写作风格",
+ "stylePlaceholder": "例:轻松幽默、严肃沉重、诗意抒情、硬汉派、轻小说风格...",
+ "tone": "整体基调",
+ "tonePlaceholder": "例:温暖治愈、黑暗压抑、热血燃向、悬疑诡谲...",
+ "pov": "叙事视角",
+ "povPlaceholder": "例:第一人称(主角视角)、第三人称有限视角、全知视角...",
+ "targetAudience": "目标读者",
+ "targetAudiencePlaceholder": "例:18-30岁男性网文读者、女性言情读者..."
+ },
+ "common": {
+ "cancel": "取消",
+ "confirm": "确定",
+ "delete": "删除",
+ "rename": "重命名",
+ "allCheck": "全选",
+ "allUncheck": "全不选",
+ "reset": "重置"
+ }
+}
\ No newline at end of file
diff --git a/app/page.js b/app/page.js
new file mode 100644
index 0000000..352842d
--- /dev/null
+++ b/app/page.js
@@ -0,0 +1,359 @@
+'use client';
+
+import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
+import dynamic from 'next/dynamic';
+import { useAppStore } from './store/useAppStore';
+import { useI18n } from './lib/useI18n';
+import {
+ getChapters,
+ createChapter,
+ updateChapter,
+ deleteChapter,
+ exportToMarkdown,
+ exportAllToMarkdown,
+} from './lib/storage';
+import { buildContext, compileSystemPrompt, compileUserPrompt, OUTPUT_TOKEN_BUDGET, getContextItems, estimateTokens } from './lib/context-engine';
+import { getProjectSettings, WRITING_MODES, getWritingMode, addSettingsNode, updateSettingsNode, deleteSettingsNode, getSettingsNodes, getActiveWorkId } from './lib/settings';
+import {
+ loadSessionStore, createSession, getActiveSession,
+} from './lib/chat-sessions';
+import { exportProject, importProject } from './lib/project-io';
+import { createSnapshot } from './lib/snapshots';
+// 动态导入编辑器和设定集面板及侧边栏(避免 SSR 问题)
+const Sidebar = dynamic(() => import('./components/Sidebar'), { ssr: false });
+const Editor = dynamic(() => import('./components/Editor'), { ssr: false });
+const SettingsPanel = dynamic(() => import('./components/SettingsPanel'), { ssr: false });
+const HelpPanel = dynamic(() => import('./components/HelpPanel'), { ssr: false });
+const TourOverlay = dynamic(() => import('./components/TourOverlay'), { ssr: false });
+const AiSidebar = dynamic(() => import('./components/AiSidebar'), { ssr: false });
+const SnapshotManager = dynamic(() => import('./components/SnapshotManager'), { ssr: false });
+const WelcomeModal = dynamic(() => import('./components/WelcomeModal'), { ssr: false });
+
+export default function Home() {
+ const {
+ chapters, setChapters, addChapter, updateChapter: updateChapterStore,
+ activeChapterId, setActiveChapterId,
+ sidebarOpen, setSidebarOpen, toggleSidebar,
+ aiSidebarOpen, setAiSidebarOpen, toggleAiSidebar,
+ showSettings, setShowSettings,
+ showSnapshots, setShowSnapshots,
+ theme, setTheme,
+ writingMode, setWritingMode,
+ toast, showToast,
+ contextSelection, setContextSelection,
+ contextItems, setContextItems,
+ settingsVersion, incrementSettingsVersion,
+ sessionStore, setSessionStore,
+ generationArchive, setGenerationArchive,
+ chatStreaming, setChatStreaming
+ } = useAppStore();
+
+ const { t } = useI18n();
+ const [showHelp, setShowHelp] = useState(false);
+ const editorRef = useRef(null);
+
+ // 派生:当前活动会话和消息列表
+ const activeSession = useMemo(() => getActiveSession(sessionStore), [sessionStore]);
+ const chatHistory = useMemo(() => activeSession?.messages || [], [activeSession]);
+
+ // 初始化数据
+ useEffect(() => {
+ const initData = async () => {
+ const saved = await getChapters();
+ if (saved.length === 0) {
+ const first = await createChapter(t('page.firstChapterTitle'));
+ setChapters([first]);
+ setActiveChapterId(first.id);
+ } else {
+ setChapters(saved);
+ setActiveChapterId(saved[0].id);
+ }
+ const savedTheme = localStorage.getItem('author-theme') || 'light';
+ setTheme(savedTheme);
+ setWritingMode(getWritingMode());
+
+ // 加载会话数据
+ let store = await loadSessionStore();
+ if (store.sessions.length === 0) {
+ // 首次使用:创建一个空会话
+ store = createSession(store);
+ }
+ setSessionStore(store);
+ };
+ initData();
+ }, []);
+
+ // 初始化上下文条目和勾选状态(设定集 + 章节 + 对话历史)
+ useEffect(() => {
+ if (!activeChapterId) return;
+
+ const loadContext = async () => {
+ const baseItems = await getContextItems(activeChapterId);
+
+ // 追加对话历史条目 — 逐条生成,供参考面板单独勾选
+ const chatItems = chatHistory.map((m, i) => {
+ const label = m.role === 'user' ? t('page.dialogueUser') : m.isSummary ? t('aiSidebar.roleSummary') : 'AI';
+ const preview = m.content.slice(0, 25) + (m.content.length > 25 ? '…' : '');
+ return {
+ id: `dialogue-${m.id}`,
+ group: t('page.dialogueHistory'),
+ name: `${label}: ${preview}`,
+ tokens: estimateTokens(m.content),
+ category: 'dialogue',
+ enabled: true,
+ _msgId: m.id,
+ };
+ });
+
+ const allItems = [...baseItems, ...chatItems];
+ setContextItems(allItems);
+
+ // 默认全选启用的条目(仅首次)
+ setContextSelection(prev => {
+ if (prev.size === 0) {
+ return new Set(allItems.filter(it => it.enabled).map(it => it.id));
+ }
+ // 自动加入新出现的条目
+ const next = new Set(prev);
+ for (const item of allItems) {
+ if (item.enabled && !prev.has(item.id)) {
+ next.add(item.id);
+ }
+ }
+ return next;
+ });
+ };
+
+ loadContext();
+ }, [activeChapterId, settingsVersion, chatHistory.length]);
+
+ // 定时自动存档 (每 15 分钟)
+ useEffect(() => {
+ // 首次加载后延迟 5 分钟做一次初始存档,之后每 15 分钟做一次
+ const initialTimer = setTimeout(() => {
+ createSnapshot(t('page.autoSnapshot'), 'auto').catch(e => console.error(t('page.autoSnapshotFail'), e));
+ }, 5 * 60 * 1000);
+
+ const intervalTimer = setInterval(() => {
+ createSnapshot(t('page.autoSnapshot'), 'auto').catch(e => console.error(t('page.autoSnapshotFail'), e));
+ }, 15 * 60 * 1000);
+
+ return () => {
+ clearTimeout(initialTimer);
+ clearInterval(intervalTimer);
+ };
+ }, []);
+
+ // 当前活跃章节
+ const activeChapter = chapters.find(ch => ch.id === activeChapterId);
+
+ const handleEditorUpdate = useCallback(async ({ html, wordCount }) => {
+ if (!activeChapterId) return;
+ const updated = await updateChapter(activeChapterId, {
+ content: html,
+ wordCount,
+ });
+ if (updated) {
+ updateChapterStore(activeChapterId, { content: html, wordCount });
+ }
+ }, [activeChapterId, updateChapterStore]);
+
+ // Inline AI 回调:编辑器调用此函数发起 AI 请求
+ const handleInlineAiRequest = useCallback(async ({ mode, text, instruction, signal, onChunk }) => {
+ try {
+ // 使用上下文引擎收集项目信息
+ const context = await buildContext(activeChapterId, text, contextSelection.size > 0 ? contextSelection : null);
+ const systemPrompt = compileSystemPrompt(context, mode);
+ const userPrompt = compileUserPrompt(mode, text, instruction);
+
+ const { apiConfig } = getProjectSettings();
+ const apiEndpoint = apiConfig?.provider === 'gemini-native' ? '/api/ai/gemini' : '/api/ai';
+
+ const res = await fetch(apiEndpoint, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ systemPrompt, userPrompt, apiConfig, maxTokens: OUTPUT_TOKEN_BUDGET }),
+ signal,
+ });
+
+ // 错误响应(JSON)
+ const contentType = res.headers.get('content-type') || '';
+ if (contentType.includes('application/json')) {
+ const data = await res.json();
+ showToast(data.error || t('page.toastRequestFailed'), 'error');
+ return;
+ }
+
+ // 读取 SSE 流
+ const reader = res.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 events = buffer.split('\n\n');
+ buffer = events.pop() || '';
+
+ for (const event of events) {
+ const trimmed = event.trim();
+ if (!trimmed || trimmed === 'data: [DONE]') continue;
+
+ if (trimmed.startsWith('data: ')) {
+ try {
+ const json = JSON.parse(trimmed.slice(6));
+ if (json.text) onChunk(json.text);
+ } catch {
+ // 解析失败跳过
+ }
+ }
+ }
+ }
+ } catch (err) {
+ if (err.name === 'AbortError') {
+ showToast(t('page.toastStopped'), 'info');
+ } else {
+ showToast(t('page.toastNetworkError'), 'error');
+ throw err;
+ }
+ }
+ }, [activeChapterId, contextSelection, showToast]);
+
+ // AI 生成存档 — Editor 的 ghost text 操作会调用此函数
+ const handleArchiveGeneration = useCallback((entry) => {
+ const record = {
+ id: `gen-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
+ timestamp: Date.now(),
+ chapterId: activeChapterId,
+ ...entry,
+ };
+ useAppStore.getState().addGenerationArchive(record);
+ }, [activeChapterId]);
+
+
+
+ // 从存档插入文本到编辑器
+ const handleInsertFromArchive = useCallback((text) => {
+ if (editorRef.current) {
+ editorRef.current.insertText?.(text);
+ showToast(t('page.toastInserted'), 'success');
+ }
+ }, [showToast]);
+
+ return (
+
+ {/* ===== 侧边栏 ===== */}
+
+
+ {/* ===== 主内容 ===== */}
+
+ {!sidebarOpen && (
+ setSidebarOpen(true)}
+ title={t('page.expandSidebar')}
+ >
+ ☰
+
+ )}
+
+ {activeChapter ? (
+
+ ) : (
+
+ {t('page.noChapterHint')}
+
+ )}
+ {/* AI 侧栏浮动开关 */}
+ {!aiSidebarOpen && (
+ setAiSidebarOpen(true)}
+ title={t('page.openAiAssistant')}
+ >
+ ✦
+
+ )}
+
+ {/* 帮助与向导按钮 */}
+ e.currentTarget.style.opacity = '1'}
+ onMouseLeave={(e) => e.currentTarget.style.opacity = '0.8'}
+ onClick={() => setShowHelp(true)}
+ title={t('page.helpAndGuide')}
+ >
+ 📖
+
+
+
+ {/* ===== AI 对话侧栏 ===== */}
+
+
+
+ {/* ===== Toast 通知 ===== */}
+ {toast && (
+
+
+ {toast.type === 'success' && '✓ '}
+ {toast.type === 'error' && '✗ '}
+ {toast.type === 'info' && 'ℹ '}
+ {toast.message}
+
+
+ )}
+
+ {/* ===== 设定库弹窗 ===== */}
+
+
+
+ {/* ===== 帮助文档 ===== */}
+
setShowHelp(false)} />
+
+ {/* ===== 首次引导 ===== */}
+ setShowHelp(true)} />
+
+
+ );
+}
diff --git a/app/store/useAppStore.js b/app/store/useAppStore.js
new file mode 100644
index 0000000..c4cd6fa
--- /dev/null
+++ b/app/store/useAppStore.js
@@ -0,0 +1,90 @@
+import { create } from 'zustand';
+
+export const useAppStore = create((set, get) => ({
+ // --- Chapter State ---
+ chapters: [],
+ activeChapterId: null,
+ setChapters: (chapters) => set({ chapters }),
+ setActiveChapterId: (id) => set({ activeChapterId: id }),
+ addChapter: (chapter) => set((state) => ({ chapters: [...state.chapters, chapter] })),
+ deleteChapter: (id) => set((state) => ({ chapters: state.chapters.filter((ch) => ch.id !== id) })),
+ updateChapter: (id, updates) => set((state) => ({
+ chapters: state.chapters.map((ch) => (ch.id === id ? { ...ch, ...updates } : ch))
+ })),
+
+ // --- UI State ---
+ sidebarOpen: true,
+ setSidebarOpen: (open) => set({ sidebarOpen: open }),
+ toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
+
+ aiSidebarOpen: false,
+ setAiSidebarOpen: (open) => set({ aiSidebarOpen: open }),
+ toggleAiSidebar: () => set((state) => ({ aiSidebarOpen: !state.aiSidebarOpen })),
+
+ showSettings: false,
+ setShowSettings: (show) => set({ showSettings: show }),
+
+ showSnapshots: false,
+ setShowSnapshots: (show) => set({ showSnapshots: show }),
+
+ theme: 'light',
+ setTheme: (theme) => set({ theme }),
+
+ writingMode: 'webnovel',
+ setWritingMode: (mode) => set({ writingMode: mode }),
+
+ // --- Localization & Theming ---
+ language: typeof window !== 'undefined' ? localStorage.getItem('author-lang') || null : null,
+ setLanguage: (lang) => set(() => {
+ if (typeof window !== 'undefined') localStorage.setItem('author-lang', lang);
+ return { language: lang };
+ }),
+
+ visualTheme: typeof window !== 'undefined' ? localStorage.getItem('author-visual') || null : null,
+ setVisualTheme: (vTheme) => set(() => {
+ if (typeof window !== 'undefined') localStorage.setItem('author-visual', vTheme);
+ return { visualTheme: vTheme };
+ }),
+
+ startTour: false,
+ setStartTour: (val) => set({ startTour: val }),
+
+ // --- Toast ---
+ toast: null,
+ setToast: (toast) => {
+ set({ toast });
+ if (toast) {
+ setTimeout(() => set({ toast: null }), 3000);
+ }
+ },
+ showToast: (message, type = 'info') => get().setToast({ message, type }),
+
+ // --- Context & Settings (RAG Preparation) ---
+ contextSelection: typeof window !== 'undefined' && localStorage.getItem('author-context-selection')
+ ? new Set(JSON.parse(localStorage.getItem('author-context-selection')))
+ : new Set(),
+ setContextSelection: (selection) => set((state) => {
+ const newSelection = typeof selection === 'function' ? selection(state.contextSelection) : selection;
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('author-context-selection', JSON.stringify(Array.from(newSelection)));
+ }
+ return { contextSelection: newSelection };
+ }),
+
+ contextItems: [],
+ setContextItems: (items) => set({ contextItems: items }),
+
+ settingsVersion: 0,
+ incrementSettingsVersion: () => set((state) => ({ settingsVersion: state.settingsVersion + 1 })),
+
+ // --- AI Chat & Generation State ---
+ sessionStore: { activeSessionId: null, sessions: [] },
+ setSessionStore: (action) => set((state) => ({ sessionStore: typeof action === 'function' ? action(state.sessionStore) : action })),
+
+ chatStreaming: false,
+ setChatStreaming: (streaming) => set({ chatStreaming: streaming }),
+
+ generationArchive: [],
+ setGenerationArchive: (archive) => set({ generationArchive: typeof archive === 'function' ? archive(get().generationArchive) : archive }),
+ addGenerationArchive: (record) => set((state) => ({ generationArchive: [...state.generationArchive, record] })),
+}));
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..f443835
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,16 @@
+import { defineConfig, globalIgnores } from "eslint/config";
+import nextVitals from "eslint-config-next/core-web-vitals";
+
+const eslintConfig = defineConfig([
+ ...nextVitals,
+ // Override default ignores of eslint-config-next.
+ globalIgnores([
+ // Default ignores of eslint-config-next:
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ]),
+]);
+
+export default eslintConfig;
diff --git a/image-1.png b/image-1.png
new file mode 100644
index 0000000..a139643
Binary files /dev/null and b/image-1.png differ
diff --git a/image-2.png b/image-2.png
new file mode 100644
index 0000000..a139643
Binary files /dev/null and b/image-2.png differ
diff --git a/image-3.png b/image-3.png
new file mode 100644
index 0000000..a139643
Binary files /dev/null and b/image-3.png differ
diff --git a/image.png b/image.png
new file mode 100644
index 0000000..a139643
Binary files /dev/null and b/image.png differ
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..2a2e4b3
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["./*"]
+ }
+ }
+}
diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..7240779
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ devIndicators: false,
+};
+
+export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..9af01ff
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,9565 @@
+{
+ "name": "author-app",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "author-app",
+ "version": "0.1.0",
+ "dependencies": {
+ "@tiptap/extension-character-count": "^3.19.0",
+ "@tiptap/extension-color": "^3.19.0",
+ "@tiptap/extension-font-family": "^3.19.0",
+ "@tiptap/extension-highlight": "^3.19.0",
+ "@tiptap/extension-placeholder": "^3.19.0",
+ "@tiptap/extension-subscript": "^3.19.0",
+ "@tiptap/extension-superscript": "^3.19.0",
+ "@tiptap/extension-task-item": "^3.19.0",
+ "@tiptap/extension-task-list": "^3.19.0",
+ "@tiptap/extension-text-align": "^3.19.0",
+ "@tiptap/extension-text-style": "^3.19.0",
+ "@tiptap/extension-underline": "^3.19.0",
+ "@tiptap/pm": "^3.19.0",
+ "@tiptap/react": "^3.19.0",
+ "@tiptap/starter-kit": "^3.19.0",
+ "idb-keyval": "^6.2.2",
+ "katex": "^0.16.28",
+ "next": "16.1.6",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ "react-markdown": "^10.1.0",
+ "rehype-katex": "^7.0.1",
+ "remark-gfm": "^4.0.1",
+ "remark-math": "^6.0.0",
+ "tiptap-markdown": "^0.9.0",
+ "zustand": "^5.0.11"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "eslint": "^9",
+ "eslint-config-next": "16.1.6",
+ "pdf-parse": "^2.4.5",
+ "tailwindcss": "^4"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
+ "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
+ "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@floating-ui/core": "^1.7.4",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
+ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/canvas": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz",
+ "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "e2e/*"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@napi-rs/canvas-android-arm64": "0.1.80",
+ "@napi-rs/canvas-darwin-arm64": "0.1.80",
+ "@napi-rs/canvas-darwin-x64": "0.1.80",
+ "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80",
+ "@napi-rs/canvas-linux-arm64-gnu": "0.1.80",
+ "@napi-rs/canvas-linux-arm64-musl": "0.1.80",
+ "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80",
+ "@napi-rs/canvas-linux-x64-gnu": "0.1.80",
+ "@napi-rs/canvas-linux-x64-musl": "0.1.80",
+ "@napi-rs/canvas-win32-x64-msvc": "0.1.80"
+ }
+ },
+ "node_modules/@napi-rs/canvas-android-arm64": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz",
+ "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-darwin-arm64": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz",
+ "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-darwin-x64": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz",
+ "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz",
+ "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz",
+ "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm64-musl": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz",
+ "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz",
+ "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-x64-gnu": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz",
+ "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-x64-musl": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz",
+ "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-win32-x64-msvc": {
+ "version": "0.1.80",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz",
+ "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
+ "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
+ "license": "MIT"
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz",
+ "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "3.3.1"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
+ "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
+ "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
+ "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
+ "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
+ "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
+ "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
+ "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
+ "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nolyfill/is-core-module": {
+ "version": "1.0.39",
+ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+ "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.4.0"
+ }
+ },
+ "node_modules/@remirror/core-constants": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
+ "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
+ "license": "MIT"
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
+ "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tiptap/core": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz",
+ "integrity": "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-blockquote": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.19.0.tgz",
+ "integrity": "sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-bold": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz",
+ "integrity": "sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-bubble-menu": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.19.0.tgz",
+ "integrity": "sha512-klNVIYGCdznhFkrRokzGd6cwzoi8J7E5KbuOfZBwFwhMKZhlz/gJfKmYg9TJopeUhrr2Z9yHgWTk8dh/YIJCdQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-bullet-list": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.19.0.tgz",
+ "integrity": "sha512-F9uNnqd0xkJbMmRxVI5RuVxwB9JaCH/xtRqOUNQZnRBt7IdAElCY+Dvb4hMCtiNv+enGM/RFGJuFHR9TxmI7rw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-list": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-character-count": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-3.19.0.tgz",
+ "integrity": "sha512-ElmlJkSD2tx7f2Hw12qQu4PD8GHfOQfY999EbJENQBtaz+uszUE19Grc1GNs5X+0T757OprM1A9EBPnMX667WQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extensions": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-code": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.19.0.tgz",
+ "integrity": "sha512-2kqqQIXBXj2Or+4qeY3WoE7msK+XaHKL6EKOcKlOP2BW8eYqNTPzNSL+PfBDQ3snA7ljZQkTs/j4GYDj90vR1A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-code-block": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.19.0.tgz",
+ "integrity": "sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-color": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.19.0.tgz",
+ "integrity": "sha512-SemOrEEnmbKshhbTeKIvffwMlgqDAUDuOYyizfKqYM2dd5fRjohp+oszExO7scVhDtHtvtP/l3UmkBGBYfl4Tg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-text-style": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-document": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.19.0.tgz",
+ "integrity": "sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-dropcursor": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.19.0.tgz",
+ "integrity": "sha512-sf3dEZXiLvsGqVK2maUIzXY6qtYYCvBumag7+VPTMGQ0D4hiZ1X/4ukt4+6VXDg5R2WP1CoIt/QvUetUjWNhbQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extensions": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-floating-menu": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.19.0.tgz",
+ "integrity": "sha512-JaoEkVRkt+Slq3tySlIsxnMnCjS0L5n1CA1hctjLy0iah8edetj3XD5mVv5iKqDzE+LIjF4nwLRRVKJPc8hFBg==",
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@floating-ui/dom": "^1.0.0",
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-font-family": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-font-family/-/extension-font-family-3.19.0.tgz",
+ "integrity": "sha512-sUMvgETZkhEUts7arczK+mBz2wnokgGCf3TxRwR2Wy6cx4f9+TTAPT+pm0a7GAvPfra6OzoCWnkcVDVcTJA9/Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-text-style": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-gapcursor": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.19.0.tgz",
+ "integrity": "sha512-w7DACS4oSZaDWjz7gropZHPc9oXqC9yERZTcjWxyORuuIh1JFf0TRYspleK+OK28plK/IftojD/yUDn1MTRhvA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extensions": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-hard-break": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz",
+ "integrity": "sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-heading": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.19.0.tgz",
+ "integrity": "sha512-uLpLlfyp086WYNOc0ekm1gIZNlEDfmzOhKzB0Hbyi6jDagTS+p9mxUNYeYOn9jPUxpFov43+Wm/4E24oY6B+TQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-highlight": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.19.0.tgz",
+ "integrity": "sha512-MYwSDCh/aG12KXw30XmHwrruElBRB8b7Ou0jd8n8H2oXb+QexVqnMa2+ylkuTAl+2D5PR7zdIIOeeHRSTmkPPw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-horizontal-rule": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.19.0.tgz",
+ "integrity": "sha512-iqUHmgMGhMgYGwG6L/4JdelVQ5Mstb4qHcgTGd/4dkcUOepILvhdxajPle7OEdf9sRgjQO6uoAU5BVZVC26+ng==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-italic": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz",
+ "integrity": "sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-link": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz",
+ "integrity": "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==",
+ "license": "MIT",
+ "dependencies": {
+ "linkifyjs": "^4.3.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-list": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.19.0.tgz",
+ "integrity": "sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-list-item": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.19.0.tgz",
+ "integrity": "sha512-VsSKuJz4/Tb6ZmFkXqWpDYkRzmaLTyE6dNSEpNmUpmZ32sMqo58mt11/huADNwfBFB0Ve7siH/VnFNIJYY3xvg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-list": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-list-keymap": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.19.0.tgz",
+ "integrity": "sha512-bxgmAgA3RzBGA0GyTwS2CC1c+QjkJJq9hC+S6PSOWELGRiTbwDN3MANksFXLjntkTa0N5fOnL27vBHtMStURqw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-list": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-ordered-list": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.19.0.tgz",
+ "integrity": "sha512-cxGsINquwHYE1kmhAcLNLHAofmoDEG6jbesR5ybl7tU5JwtKVO7S/xZatll2DU1dsDAXWPWEeeMl4e/9svYjCg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-list": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-paragraph": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz",
+ "integrity": "sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-placeholder": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.19.0.tgz",
+ "integrity": "sha512-i15OfgyI4IDCYAcYSKUMnuZkYuUInfanjf9zquH8J2BETiomf/jZldVCp/QycMJ8DOXZ38fXDc99wOygnSNySg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extensions": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-strike": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.19.0.tgz",
+ "integrity": "sha512-xYpabHsv7PccLUBQaP8AYiFCnYbx6P93RHPd0lgNwhdOjYFd931Zy38RyoxPHAgbYVmhf1iyx7lpuLtBnhS5dA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-subscript": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-3.19.0.tgz",
+ "integrity": "sha512-RchUOSIDprlnuzmA/znZIBKMONIlCAXHuR6XkoNX2jFJ/9OHk/si+jEeY8jJfpPDpVqZ3KrwS/zJrqhU/pT+hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-superscript": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-3.19.0.tgz",
+ "integrity": "sha512-PjLUGjM23/7hqFP5HS1DbdywRm63GhjJ5SD6KqNgyZQwcwDZeJTAW2b/LYTHAP+k07OxOLPdj/k4ntQKtgKNow==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-task-item": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-3.19.0.tgz",
+ "integrity": "sha512-1il70SoaoEA5jKr2QS30CpZoB7EzdSLugROMBPRUPc0feIBKAf6yUPhxlFyU4ez/uT4Pazsf1HAgHI3AOr+MtQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-list": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-task-list": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-3.19.0.tgz",
+ "integrity": "sha512-Slb6YZi7XpVT966oAJhqzZu4LVuHtUeZgMxA0bdc8FSZBxntcr8OQWmPbqvR437RAR/Xd7b5quXS3JmSeksyvA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/extension-list": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-text": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.19.0.tgz",
+ "integrity": "sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-text-align": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.19.0.tgz",
+ "integrity": "sha512-cY8bHWYojLTHXZb2j2srdh7ltmDgnwXYvSxbPL4HK4j7XxQOGnOsTakgM/BNhxymOfEj2414i5Otyy8hlgviFA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-text-style": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.19.0.tgz",
+ "integrity": "sha512-R55V6iUfRq03SGt/R2KvaeN+XGFiKJHx1jFJhZzvnWhMV7YqjHSG2r4BLvpTq1HBqteMbEjI+EOnb4t9AKd6aQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extension-underline": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.19.0.tgz",
+ "integrity": "sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/extensions": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.19.0.tgz",
+ "integrity": "sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ }
+ },
+ "node_modules/@tiptap/pm": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz",
+ "integrity": "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-changeset": "^2.3.0",
+ "prosemirror-collab": "^1.3.1",
+ "prosemirror-commands": "^1.6.2",
+ "prosemirror-dropcursor": "^1.8.1",
+ "prosemirror-gapcursor": "^1.3.2",
+ "prosemirror-history": "^1.4.1",
+ "prosemirror-inputrules": "^1.4.0",
+ "prosemirror-keymap": "^1.2.2",
+ "prosemirror-markdown": "^1.13.1",
+ "prosemirror-menu": "^1.2.4",
+ "prosemirror-model": "^1.24.1",
+ "prosemirror-schema-basic": "^1.2.3",
+ "prosemirror-schema-list": "^1.5.0",
+ "prosemirror-state": "^1.4.3",
+ "prosemirror-tables": "^1.6.4",
+ "prosemirror-trailing-node": "^3.0.0",
+ "prosemirror-transform": "^1.10.2",
+ "prosemirror-view": "^1.38.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ }
+ },
+ "node_modules/@tiptap/react": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.19.0.tgz",
+ "integrity": "sha512-GQQMUUXMpNd8tRjc1jDK3tDRXFugJO7C928EqmeBcBzTKDrFIJ3QUoZKEPxUNb6HWhZ2WL7q00fiMzsv4DNSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "fast-equals": "^5.3.3",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "optionalDependencies": {
+ "@tiptap/extension-bubble-menu": "^3.19.0",
+ "@tiptap/extension-floating-menu": "^3.19.0"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/pm": "^3.19.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tiptap/starter-kit": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.19.0.tgz",
+ "integrity": "sha512-dTCkHEz+Y8ADxX7h+xvl6caAj+3nII/wMB1rTQchSuNKqJTOrzyUsCWm094+IoZmLT738wANE0fRIgziNHs/ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@tiptap/core": "^3.19.0",
+ "@tiptap/extension-blockquote": "^3.19.0",
+ "@tiptap/extension-bold": "^3.19.0",
+ "@tiptap/extension-bullet-list": "^3.19.0",
+ "@tiptap/extension-code": "^3.19.0",
+ "@tiptap/extension-code-block": "^3.19.0",
+ "@tiptap/extension-document": "^3.19.0",
+ "@tiptap/extension-dropcursor": "^3.19.0",
+ "@tiptap/extension-gapcursor": "^3.19.0",
+ "@tiptap/extension-hard-break": "^3.19.0",
+ "@tiptap/extension-heading": "^3.19.0",
+ "@tiptap/extension-horizontal-rule": "^3.19.0",
+ "@tiptap/extension-italic": "^3.19.0",
+ "@tiptap/extension-link": "^3.19.0",
+ "@tiptap/extension-list": "^3.19.0",
+ "@tiptap/extension-list-item": "^3.19.0",
+ "@tiptap/extension-list-keymap": "^3.19.0",
+ "@tiptap/extension-ordered-list": "^3.19.0",
+ "@tiptap/extension-paragraph": "^3.19.0",
+ "@tiptap/extension-strike": "^3.19.0",
+ "@tiptap/extension-text": "^3.19.0",
+ "@tiptap/extension-underline": "^3.19.0",
+ "@tiptap/extensions": "^3.19.0",
+ "@tiptap/pm": "^3.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/katex": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz",
+ "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "14.1.2",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/linkify-it": "^5",
+ "@types/mdurl": "^2"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
+ "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/type-utils": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.55.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
+ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
+ "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.55.0",
+ "@typescript-eslint/types": "^8.55.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
+ "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
+ "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
+ "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
+ "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
+ "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.55.0",
+ "@typescript-eslint/tsconfig-utils": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/visitor-keys": "8.55.0",
+ "debug": "^4.4.3",
+ "minimatch": "^9.0.5",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
+ "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.55.0",
+ "@typescript-eslint/types": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
+ "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.55.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
+ "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.0",
+ "es-object-atoms": "^1.1.1",
+ "get-intrinsic": "^1.3.0",
+ "is-string": "^1.1.1",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
+ "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-shim-unscopables": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.11.1",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz",
+ "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001770",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
+ "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.19.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
+ "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz",
+ "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.1",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.1.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.3.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "iterator.prototype": "^1.1.5",
+ "safe-array-concat": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz",
+ "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@next/eslint-plugin-next": "16.1.6",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-import-resolver-typescript": "^3.5.2",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-jsx-a11y": "^6.10.0",
+ "eslint-plugin-react": "^7.37.0",
+ "eslint-plugin-react-hooks": "^7.0.0",
+ "globals": "16.4.0",
+ "typescript-eslint": "^8.46.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=9.0.0",
+ "typescript": ">=3.3.1"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/globals": {
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz",
+ "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@nolyfill/is-core-module": "1.0.39",
+ "debug": "^4.4.0",
+ "get-tsconfig": "^4.10.0",
+ "is-bun-module": "^2.0.0",
+ "stable-hash": "^0.0.5",
+ "tinyglobby": "^0.2.13",
+ "unrs-resolver": "^1.6.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-import-resolver-typescript"
+ },
+ "peerDependencies": {
+ "eslint": "*",
+ "eslint-plugin-import": "*",
+ "eslint-plugin-import-x": "*"
+ },
+ "peerDependenciesMeta": {
+ "eslint-plugin-import": {
+ "optional": true
+ },
+ "eslint-plugin-import-x": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
+ "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
+ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.9",
+ "array.prototype.findlastindex": "^1.2.6",
+ "array.prototype.flat": "^1.3.3",
+ "array.prototype.flatmap": "^1.3.3",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.1",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.16.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.1",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.9",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
+ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aria-query": "^5.3.2",
+ "array-includes": "^3.1.8",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "^4.10.0",
+ "axobject-query": "^4.1.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.includes": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.37.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
+ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.3",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.2.1",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.9",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.12",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
+ "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/generator-function": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
+ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.6",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
+ "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/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-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast-util-from-dom": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz",
+ "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==",
+ "license": "ISC",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hastscript": "^9.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
+ "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.1.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "parse5": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html-isomorphic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
+ "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-from-dom": "^5.0.0",
+ "hast-util-from-html": "^2.0.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
+ "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hastscript": "^9.0.0",
+ "property-information": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-location": "^5.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
+ "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^4.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/idb-keyval": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz",
+ "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bun-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
+ "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.7.1"
+ }
+ },
+ "node_modules/is-bun-module/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
+ "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.4",
+ "generator-function": "^2.0.0",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
+ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "get-proto": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/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.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/katex": {
+ "version": "0.16.28",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz",
+ "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==",
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
+ "node_modules/linkifyjs": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
+ "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
+ "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/markdown-it-task-lists": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz",
+ "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==",
+ "license": "ISC"
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-math": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
+ "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.1.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-math": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
+ "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/katex": "^0.16.0",
+ "devlop": "^1.0.0",
+ "katex": "^0.16.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/next": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
+ "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "16.1.6",
+ "@swc/helpers": "0.5.15",
+ "baseline-browser-mapping": "^2.8.3",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "16.1.6",
+ "@next/swc-darwin-x64": "16.1.6",
+ "@next/swc-linux-arm64-gnu": "16.1.6",
+ "@next/swc-linux-arm64-musl": "16.1.6",
+ "@next/swc-linux-x64-gnu": "16.1.6",
+ "@next/swc-linux-x64-musl": "16.1.6",
+ "@next/swc-win32-arm64-msvc": "16.1.6",
+ "@next/swc-win32-x64-msvc": "16.1.6",
+ "sharp": "^0.34.4"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.51.1",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "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.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz",
+ "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/orderedmap": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
+ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
+ "license": "MIT"
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pdf-parse": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz",
+ "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@napi-rs/canvas": "0.1.80",
+ "pdfjs-dist": "5.4.296"
+ },
+ "bin": {
+ "pdf-parse": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": ">=20.16.0 <21 || >=22.3.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/mehmet-kozan"
+ }
+ },
+ "node_modules/pdfjs-dist": {
+ "version": "5.4.296",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz",
+ "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=20.16.0 || >=22.3.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/canvas": "^0.1.80"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "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/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/prosemirror-changeset": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
+ "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-transform": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-collab": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
+ "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-commands": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
+ "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.10.2"
+ }
+ },
+ "node_modules/prosemirror-dropcursor": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
+ "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.1.0",
+ "prosemirror-view": "^1.1.0"
+ }
+ },
+ "node_modules/prosemirror-gapcursor": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz",
+ "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-keymap": "^1.0.0",
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-view": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-history": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
+ "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.2.2",
+ "prosemirror-transform": "^1.0.0",
+ "prosemirror-view": "^1.31.0",
+ "rope-sequence": "^1.3.0"
+ }
+ },
+ "node_modules/prosemirror-inputrules": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
+ "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-keymap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
+ "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-state": "^1.0.0",
+ "w3c-keyname": "^2.2.0"
+ }
+ },
+ "node_modules/prosemirror-markdown": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
+ "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/markdown-it": "^14.0.0",
+ "markdown-it": "^14.0.0",
+ "prosemirror-model": "^1.25.0"
+ }
+ },
+ "node_modules/prosemirror-menu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz",
+ "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "crelt": "^1.0.0",
+ "prosemirror-commands": "^1.0.0",
+ "prosemirror-history": "^1.0.0",
+ "prosemirror-state": "^1.0.0"
+ }
+ },
+ "node_modules/prosemirror-model": {
+ "version": "1.25.4",
+ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
+ "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
+ "license": "MIT",
+ "dependencies": {
+ "orderedmap": "^2.0.0"
+ }
+ },
+ "node_modules/prosemirror-schema-basic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
+ "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.25.0"
+ }
+ },
+ "node_modules/prosemirror-schema-list": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
+ "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.7.3"
+ }
+ },
+ "node_modules/prosemirror-state": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
+ "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-transform": "^1.0.0",
+ "prosemirror-view": "^1.27.0"
+ }
+ },
+ "node_modules/prosemirror-tables": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
+ "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-keymap": "^1.2.3",
+ "prosemirror-model": "^1.25.4",
+ "prosemirror-state": "^1.4.4",
+ "prosemirror-transform": "^1.10.5",
+ "prosemirror-view": "^1.41.4"
+ }
+ },
+ "node_modules/prosemirror-trailing-node": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
+ "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@remirror/core-constants": "3.0.0",
+ "escape-string-regexp": "^4.0.0"
+ },
+ "peerDependencies": {
+ "prosemirror-model": "^1.22.1",
+ "prosemirror-state": "^1.4.2",
+ "prosemirror-view": "^1.33.8"
+ }
+ },
+ "node_modules/prosemirror-transform": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz",
+ "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.21.0"
+ }
+ },
+ "node_modules/prosemirror-view": {
+ "version": "1.41.6",
+ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz",
+ "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==",
+ "license": "MIT",
+ "dependencies": {
+ "prosemirror-model": "^1.20.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.1.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
+ "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.3"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/react-markdown": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
+ "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "html-url-attributes": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18",
+ "react": ">=18"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rehype-katex": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
+ "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/katex": "^0.16.0",
+ "hast-util-from-html-isomorphic": "^2.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "katex": "^0.16.0",
+ "unist-util-visit-parents": "^6.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-math": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
+ "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-math": "^3.0.0",
+ "micromark-extension-math": "^3.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-stringify": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rope-sequence": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
+ "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
+ "license": "MIT"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stable-hash": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
+ "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
+ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/style-to-js": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "style-to-object": "1.0.14"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tiptap-markdown": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.9.0.tgz",
+ "integrity": "sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==",
+ "license": "MIT",
+ "workspaces": [
+ "example"
+ ],
+ "dependencies": {
+ "@types/markdown-it": "^13.0.7",
+ "markdown-it": "^14.1.0",
+ "markdown-it-task-lists": "^2.1.1",
+ "prosemirror-markdown": "^1.11.1"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^3.0.1"
+ }
+ },
+ "node_modules/tiptap-markdown/node_modules/@types/linkify-it": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz",
+ "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==",
+ "license": "MIT"
+ },
+ "node_modules/tiptap-markdown/node_modules/@types/markdown-it": {
+ "version": "13.0.9",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz",
+ "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/linkify-it": "^3",
+ "@types/mdurl": "^1"
+ }
+ },
+ "node_modules/tiptap-markdown/node_modules/@types/mdurl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz",
+ "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==",
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.55.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
+ "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.55.0",
+ "@typescript-eslint/parser": "8.55.0",
+ "@typescript-eslint/typescript-estree": "8.55.0",
+ "@typescript-eslint/utils": "8.55.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "license": "MIT"
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-remove-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
+ "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-location": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
+ "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+ "license": "MIT"
+ },
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.11",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz",
+ "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b47dc10
--- /dev/null
+++ b/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "author-app",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint"
+ },
+ "dependencies": {
+ "@tiptap/extension-character-count": "^3.19.0",
+ "@tiptap/extension-color": "^3.19.0",
+ "@tiptap/extension-font-family": "^3.19.0",
+ "@tiptap/extension-highlight": "^3.19.0",
+ "@tiptap/extension-placeholder": "^3.19.0",
+ "@tiptap/extension-subscript": "^3.19.0",
+ "@tiptap/extension-superscript": "^3.19.0",
+ "@tiptap/extension-task-item": "^3.19.0",
+ "@tiptap/extension-task-list": "^3.19.0",
+ "@tiptap/extension-text-align": "^3.19.0",
+ "@tiptap/extension-text-style": "^3.19.0",
+ "@tiptap/extension-underline": "^3.19.0",
+ "@tiptap/pm": "^3.19.0",
+ "@tiptap/react": "^3.19.0",
+ "@tiptap/starter-kit": "^3.19.0",
+ "idb-keyval": "^6.2.2",
+ "katex": "^0.16.28",
+ "next": "16.1.6",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ "react-markdown": "^10.1.0",
+ "rehype-katex": "^7.0.1",
+ "remark-gfm": "^4.0.1",
+ "remark-math": "^6.0.0",
+ "tiptap-markdown": "^0.9.0",
+ "zustand": "^5.0.11"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "eslint": "^9",
+ "eslint-config-next": "16.1.6",
+ "pdf-parse": "^2.4.5",
+ "tailwindcss": "^4"
+ }
+}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..61e3684
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/public/file.svg b/public/file.svg
new file mode 100644
index 0000000..004145c
--- /dev/null
+++ b/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
new file mode 100644
index 0000000..567f17b
--- /dev/null
+++ b/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/next.svg b/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
new file mode 100644
index 0000000..7705396
--- /dev/null
+++ b/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/window.svg b/public/window.svg
new file mode 100644
index 0000000..b2b2a44
--- /dev/null
+++ b/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file