diff --git a/AGENTS.md b/AGENTS.md index aad16e8..4b65eba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,65 +1,90 @@ -# rules.md +# LLM in Text 项目知识库 -在构建这个LLM应用网页时,你需要基于VUE3开发。我需要前端只运行渲染和数据回传,后端负责llm api调用,类似copilet的auto inline suggustions实现和数据解析。 +**生成时间:** 2025-04-10 +**Commit:** 2fdc996 +**Branch:** main -# **重要** : 在回复用户消息时,一定要使用中文 +## 概述 -## 指导原则 +智能 Markdown 编辑器,集成 LLM 实时补全建议。前端 Vue3 + Vite + Milkdown,后端 FastAPI + Python + Ollama。核心功能:AI 补全、OCR 图片识别、文档转换、TTS/ASR 语音功能。 -- 不要擅自用npm或者yarn运行网页,你既看不到网页的内容,也无法阻止命令暂停。但是,你可以用npm run build检查代码。 -- 应该保证代码效率,不多定义变量,不写冗余注释,把降低延迟放在第一位。 -- 每次完成任务前都要反复阅读检查代码,确保代码准确无误。 -- 尽量不要搜索关键字,而是了解代码结构后查询整个问题代码明确问题所在。 -- @/milkdown-docs/ 代表milkdown的最新官方文档,不要修改,涉及到前端编辑器的指令时要核对官方文档。 +## 结构 - -# 仓库指南 - -## 语言约定 -项目文档、日志、错误提示以及对外返回的文字信息统一使用 **中文**。前端 UI 默认展示中文,若需多语言支持请在相应模块实现。 - -## 项目结构 \& 模块组织 ``` -backend/ # FastAPI 后端(Python) - ├─ main.py # API 入口 - ├─ llm.py # LLM 包装工具 - ├─ prompt.py # Prompt 构建辅助 - └─ tests/ # pytest 测试套件 -public/ # 前端静态资源 -src/ # 前端源码(Vite + React) -dist/ # 构建产出(生成文件) +llm-in-text/ +├── backend/ # FastAPI 后端 (Python) +│ ├── main.py # API 入口,路由定义 +│ ├── llm.py # Ollama 调用封装 +│ ├── prompt.py # Prompt 构建逻辑 +│ ├── prompts/ # JSON 格式的提示模板 +│ └── tests/ # pytest 测试套件 +├── src/ # 前端源码 (Vue3 + Vite) +│ ├── main.js # Vue 入口 +│ ├── App.vue # 根组件 +│ ├── components/ # Vue 组件 +│ ├── plugins/ # Milkdown/Copilot 插件 +│ ├── stores/ # Pinia 状态管理 +│ ├── views/ # 页面视图 +│ └── utils/ # 工具函数 +├── public/ # 静态资源 +├── milkdown-docs/ # Milkdown 官方文档(只读) +└── index.html # HTML 入口 ``` -生产代码主要位于 `backend/`(Python)和 `src/`(JS/TS)。测试文件与被测模块并置。 -## 构建、测试、开发命令 -| 命令 | 说明 | -|----------------------------------------------|--------------------------------------------------| -| `npm install` | 安装前端依赖 | -| `npm run dev` | 启动 Vite 开发服务器 | -| `uvicorn backend.main:app --reload` | 本地运行 FastAPI 服务 | -| `pytest` | 运行 Python 测试套件 | -| `npm run build` | 生成生产环境构建产物至 `dist/` | +## 查找指南 -## 编码风格 \& 命名约定 -- **Python**:使用 4 空格缩进,`snake_case` 命名函数/变量,`PascalCase` 命名类。提交前请使用 `ruff`/`black` 格式化。 -- **JavaScript/TypeScript**:使用 2 空格缩进,`camelCase` 命名变量/函数,`PascalCase` 命名 React 组件。使用 `eslint` 与 `prettier` 检查。 -- 文件名采用全小写加短横线,例如 `my-module.py`、`my-component.tsx`。 +| 任务 | 位置 | 说明 | +|------|------|------| +| 后端 API 入口 | `backend/main.py` | FastAPI 路由、CORS、启动逻辑 | +| LLM 调用 | `backend/llm.py` | Ollama 异步调用、超时控制 | +| Prompt 构建 | `backend/prompt.py` | 系统提示、上下文准备 | +| AI 补全核心 | `src/plugins/copilotPlugin.ts` | ProseMirror Mark、ghost text | +| 编辑器组件 | `src/components/MilkdownEditor.vue` | Milkdown 编辑器封装 | +| 状态管理 | `src/stores/settings.js` | 用户设置、主题、偏好 | +| API 调用 | `src/utils/api.js` | fetchSuggestion、TTS 接口 | +| 测试运行 | `pytest.ini` + `backend/tests/` | 测试配置与用例 | -## 测试指南 -- 后端使用 **pytest**,测试文件放在对应模块目录下,命名为 `test_.py`。 -- 目标覆盖率 ≥ 80%(`pytest --cov=backend`)。 -- 在虚拟环境中运行:`pip install -r backend/requirements.txt && pytest`。 +## 约定(项目特定) -## 提交 \& Pull Request 规范 -- 提交信息遵循 **Conventional Commits**:`feat:` 新功能、`fix:` 修复、`docs:` 文档、`refactor:` 重构等。 -- PR 必须包含: - - 与提交信息匹配的标题。 - - 关联的 Issue(如 `Fixes #123`)。 - - UI 变更或 API 示例的截图/示例。 - - 所有 CI 检查(代码检查、测试、类型检查)均通过。 +- **前端入口**:`src/main.js`(非 TypeScript),使用 Vue3 + Pinia + Vue Router +- **后端入口**:`backend/main.py`,端口 8001,uvicorn 启动 +- **代理配置**:开发时 `/v1` 代理到远程 API,生产需调整 +- **文件命名**:全小写+短横线(`my-module.py`、`my-component.vue`) +- **语言**:UI 默认中文,响应必须使用中文 -## 安全 \& 配置建议 -- 敏感信息请放入 `.env` 并确保已在 `.gitignore` 中。 -- 按照 `backend/main.py` 中的实现,对上传文件的大小和类型进行校验,防止滥用。 -- 定期审计依赖安全(`npm audit`、`pip-audit`)。 +## 反模式(本项目禁止) + +- ❌ 硬编码 API_KEY(必须从环境变量读取) +- ❌ 在前端暴露密钥(应通过后端代理) +- ❌ `npm run dev` 运行网页(无法看到内容) +- ❌ 修改 `milkdown-docs/` 目录 +- ❌ 类型错误使用 `as any` / `@ts-ignore` +- ❌ 空的 catch 块 + +## 命令 + +```bash +# 前端开发 +npm install +npm run dev # 端口 5173 +npm run build # 构建到 dist/ + +# 后端运行 +pip install -r backend/requirements.txt +python backend/main.py # 端口 8001 +# 或 +uvicorn backend.main:app --reload --port 8001 + +# 测试 +pytest # 运行所有测试,覆盖率要求 90% +python backend/tests/run_tests.py unit # 单元测试 +python backend/tests/run_tests.py integration # 集成测试 +``` + +## 注意事项 + +- **架构分离**:前端仅渲染和数据回传,后端负责 LLM API 调用和数据解析 +- **延迟优先**:代码效率优先,降低延迟放在第一位 +- **大小限制**:文档超过 32KB 自动禁用 AI 补全 +- **milkdown-docs/**:官方文档参考,不可修改,编辑器相关问题需核对此目录 diff --git a/backend/AGENTS.md b/backend/AGENTS.md new file mode 100644 index 0000000..b2a111f --- /dev/null +++ b/backend/AGENTS.md @@ -0,0 +1,40 @@ +# Backend 模块指南 + +## OVERVIEW +FastAPI 后端,处理 AI 补全、OCR、文档转换、TTS/ASR。 + +## STRUCTURE +- main.py - API 入口、路由、CORS、启动逻辑 +- llm.py - Ollama 异步调用、超时控制、日志 +- prompt.py - Prompt 构建、上下文准备、语言处理 +- geoip.py - IP 地理位置查询 +- tts_asr.py - TTS/ASR 处理、Apple Silicon 优化 +- prompts/ - JSON 格式提示模板(PromptManager 单例) +- tests/ - pytest 测试套件(见子目录 AGENTS.md) + +## WHERE TO LOOK + +| 任务 | 文件 | 说明 | +|------|------|------| +| API 路由定义 | main.py | /v1/completions、/v1/ocr、/v1/convert 等 | +| LLM 调用封装 | llm.py | call_ollama、call_vlm_ocr、超时控制 | +| Prompt 构建 | prompt.py | build_completion_prompts、语言处理 | +| 提示模板 | prompts/__init__.py | PromptManager、JSON 模板加载 | +| TTS/ASR | tts_asr.py | 模型预热、设备检测、音频处理 | +| 测试 | tests/ | pytest 测试套件 | + +## CONVENTIONS +- Python 4 空格缩进 +- 函数/变量:snake_case +- 类:PascalCase +- 文件名:全小写+短横线 + +## ANTI-PATTERNS +- 硬编码 API_KEY(必须从环境变量读取) +- 空 catch 块 +- 类型错误使用 as any / @ts-ignore + +## 注意事项 +- 端口:8001 +- 启动:`python backend/main.py` 或 `uvicorn backend.main:app --reload` +- 依赖:`pip install -r backend/requirements.txt` diff --git a/backend/llm.py b/backend/llm.py index c69955e..90886ed 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -17,7 +17,6 @@ VLM_MODEL = os.getenv('VLM_MODEL', 'qwen3-vl:30b') # Timeouts in seconds (10 minutes for large model loading) COMPLETION_TIMEOUT = 600 OCR_TIMEOUT = 600 -CONVERT_TIMEOUT = 600 client = ollama.AsyncClient(host=OLLAMA_HOST) logger = logging.getLogger("llm") diff --git a/backend/main.py b/backend/main.py index 2781163..a0343d0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -17,6 +17,7 @@ from pydantic import BaseModel from geoip import get_ip_location_text from llm import call_ollama, call_vlm_ocr +from models import UserPreferences from prompt import build_completion_prompts, prepare_prompt_context import markitdown @@ -57,7 +58,7 @@ app.add_middleware( allow_headers=["*", "X-API-Key", "X-Client-IP", "X-Request-Id"], ) -API_KEY = "your-secret-key-here" +API_KEY = os.getenv("API_KEY", "your-secret-key-here") api_key_header = APIKeyHeader(name="X-API-Key") @@ -70,12 +71,6 @@ async def get_api_key(api_key: str = Security(api_key_header)): # pragma: no co return api_key -class UserPreferences(BaseModel): - language: str = "auto" - currency: str = "auto" - timezone: str = "auto" - - class CompletionRequest(BaseModel): prefix: str suffix: str diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..adef2a1 --- /dev/null +++ b/backend/models.py @@ -0,0 +1,9 @@ +"""共享的 Pydantic 模型定义""" +from pydantic import BaseModel + + +class UserPreferences(BaseModel): + """用户偏好设置""" + language: str = "auto" + currency: str = "auto" + timezone: str = "auto" diff --git a/backend/prompt.py b/backend/prompt.py index ac8a830..3ff9ea3 100644 --- a/backend/prompt.py +++ b/backend/prompt.py @@ -1,17 +1,11 @@ from datetime import datetime, timedelta, timezone import re -from typing import Protocol, Tuple, runtime_checkable +from typing import Tuple +from models import UserPreferences from prompts import get_language_guidance_map, get_system_prompt_template, get_inline_examples -@runtime_checkable -class UserPreferences(Protocol): - language: str - currency: str - timezone: str - - def _get_current_datetime(timezone_pref: str = "auto") -> str: # Default to UTC+8 if auto or not specified. offset = 8 diff --git a/backend/tests/AGENTS.md b/backend/tests/AGENTS.md new file mode 100644 index 0000000..871f0b6 --- /dev/null +++ b/backend/tests/AGENTS.md @@ -0,0 +1,45 @@ +OVERVIEW: pytest 测试套件,覆盖率要求 90% +STRUCTURE: +- test_*.py - 各模块测试 +- run_tests.py - 测试执行脚本(unit/integration/all) +- simulate_macos.py - macOS 环境模拟 +- TESTING_GUIDE.md - 测试指南文档 + +WHERE TO LOOK +表格 + +| Area | Path | +|---|---| +| 单元测试 | backend/tests/ | +| 集成测试 | backend/tests/ | +| 测试执行脚本 | backend/tests/run_tests.py | +| macOS 模拟 | backend/tests/simulate_macos.py | +| 测试指南 | backend/tests/TESTING_GUIDE.md | + +运行命令: +- pytest - 运行所有测试 +- python backend/tests/run_tests.py unit - 单元测试 +- python backend/tests/run_tests.py integration - 集成测试 + +测试命名约定:test_*.py、Test* 类、test_* 函数 + +ANTI-PATTERNS:删除测试以通过覆盖率 + +验证 +- 保证测试覆盖率≥90% 时,报告合格 +- 使用 CI 运行 pytest,确保通过率 + +注意事项 +- 不要重复父目录内容 +- 不要超过 60 行 + +测试应尽量独立,不要依赖全局状态 +- 运行单元测试时应使用 unit 标签 +- 运行集成测试时应使用 integration 标签 + +区分环境 +- unit 测试应尽量快速、稳定 +- integration 测试应覆盖接口和数据库交互 + +维护 +- 如扩展新模块,优先增加 test_*.py 文件并在其中添加对应的测试类和方法 diff --git a/backend/tts_asr.py b/backend/tts_asr.py index 7e94312..fdb20dc 100644 --- a/backend/tts_asr.py +++ b/backend/tts_asr.py @@ -1,9 +1,13 @@ import asyncio import base64 import logging +import os from io import BytesIO from typing import Optional +# 设置 Hugging Face 镜像源为国内镜像 +os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com") + import torch from fastapi import APIRouter, HTTPException from pydantic import BaseModel @@ -29,8 +33,8 @@ def _get_device_map() -> str: try: if hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): return "mps" - except Exception: - pass + except Exception as e: + logger.debug("MPS check failed: %s", e) return "cpu" diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 0000000..6a8cf6d --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,42 @@ +# Src 前端模块指南 + +## OVERVIEW +Vue3 前端核心,Milkdown 编辑器,AI 补全插件。 + +## STRUCTURE +- main.js - Vue 入口,Pinia + Router 挂载 +- App.vue - 根组件,主题/背景控制 +- plugins/ - Milkdown/Copilot 插件(见子目录 AGENTS.md) +- components/ - Vue 组件(MilkdownEditor、SettingsPanel 等) +- stores/ - Pinia 状态管理(settings.js) +- views/ - 页面视图(EditorView、DocsView) +- utils/ - 工具函数(api.js、config.js) +- router/ - 路由定义 + +## WHERE TO LOOK + +| 任务 | 文件 | 说明 | +|------|------|------| +| Vue 入口 | main.js | createApp、Pinia、Router 挂载 | +| 根组件 | App.vue | 主题切换、背景设置 | +| 编辑器组件 | components/MilkdownEditor.vue | Milkdown 编辑器封装 | +| AI 补全核心 | plugins/copilotPlugin.ts | ghost text、防抖、交互 | +| 状态管理 | stores/settings.js | 用户设置、主题、偏好 | +| API 调用 | utils/api.js | fetchSuggestion、TTS 接口 | + +## CONVENTIONS +- JS/TS 2 空格缩进 +- 变量/函数:camelCase +- Vue 组件:PascalCase +- 文件名:全小写+短横线 + +## ANTI-PATTERNS +- 在前端暴露密钥 +- 空 catch 块 +- 类型错误使用 as any + +## 注意事项 +- 端口:5173 +- 启动:`npm run dev` +- 构建:`npm run build` +- UI 默认中文 diff --git a/src/components/FileContent.vue b/src/components/FileContent.vue index 3439c1c..c8dc3af 100644 --- a/src/components/FileContent.vue +++ b/src/components/FileContent.vue @@ -1,6 +1,8 @@ diff --git a/src/components/UniverPreview.vue b/src/components/UniverPreview.vue new file mode 100644 index 0000000..d6049f6 --- /dev/null +++ b/src/components/UniverPreview.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/composables/useFileSystem.js b/src/composables/useFileSystem.js index 2d727d6..9c763ab 100644 --- a/src/composables/useFileSystem.js +++ b/src/composables/useFileSystem.js @@ -35,15 +35,20 @@ async function withStore(mode, handler) { return new Promise((resolve, reject) => { const transaction = db.transaction(STORE_NAME, mode) const store = transaction.objectStore(STORE_NAME) - let result + let request try { - result = handler(store) + request = handler(store) } catch (error) { reject(error) return } - transaction.oncomplete = () => resolve(result) - transaction.onerror = () => reject(transaction.error || new Error('本地数据库写入失败')) + if (request && typeof request.onsuccess === 'function') { + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(request.error || new Error('本地数据库操作失败')) + } else { + transaction.oncomplete = () => resolve(request) + transaction.onerror = () => reject(transaction.error || new Error('本地数据库操作失败')) + } transaction.onabort = () => reject(transaction.error || new Error('本地数据库操作已取消')) }) } @@ -62,43 +67,32 @@ function getExtension(name = '') { } function isTextExtension(ext) { - return [ - 'md', - 'markdown', - 'txt', - 'json', - 'js', - 'jsx', - 'ts', - 'tsx', - 'css', - 'scss', - 'less', - 'html', - 'htm', - 'py', - 'vue', - 'xml', - 'yaml', - 'yml', - 'csv', - 'log', - 'sql', - 'toml', - 'ini', - 'cfg', - 'conf', - 'sh', - 'bat', - 'ps1', - 'java', - 'c', - 'cpp', - 'h', - 'hpp', - 'go', - 'rs' - ].includes(ext) + const textExtensions = [ + 'md', 'markdown', 'txt', 'json', 'js', 'jsx', 'ts', 'tsx', + 'css', 'scss', 'less', 'html', 'htm', 'py', 'vue', 'xml', + 'yaml', 'yml', 'csv', 'log', 'sql', 'toml', 'ini', 'cfg', + 'conf', 'sh', 'bat', 'ps1', 'java', 'c', 'cpp', 'h', 'hpp', + 'go', 'rs', 'swift', 'kt', 'rb', 'php', 'pl', 'r', 'scala', + 'gradle', 'properties', 'env', 'gitignore', 'dockerfile' + ] + return textExtensions.includes(ext) +} + +function isBinaryExtension(ext) { + const binaryExtensions = [ + 'exe', 'dll', 'so', 'dylib', 'bin', 'dat', 'obj', 'o', 'a', + 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', + 'pdf', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', + 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', 'svg', + 'mp3', 'mp4', 'wav', 'avi', 'mov', 'mkv', 'flv', 'wmv', + 'ttf', 'otf', 'woff', 'woff2', 'eot', + 'class', 'pyc', 'pyo', 'jar', 'war', 'ear', + 'db', 'sqlite', 'mdb', 'accdb', + 'pem', 'key', 'crt', 'cer', 'p12', 'pfx', 'jks', + 'msg', 'eml', 'pst', 'ost', + 'dwg', 'dxf', 'step', 'stl', 'obj', 'fbx', '3ds', 'blend' + ] + return binaryExtensions.includes(ext.toLowerCase()) } function inferMimeType(name, fallback = '') { @@ -143,6 +137,8 @@ function inferMimeType(name, fallback = '') { function isTextFile(record) { const ext = getExtension(record?.name) + // 二进制文件不提供预览 + if (isBinaryExtension(ext)) return false const mime = String(record?.mimeType || '') return isTextExtension(ext) || mime.startsWith('text/') || mime.includes('json') || mime.includes('xml') } @@ -218,6 +214,7 @@ async function readFilePayload(file) { const mimeType = inferMimeType(file.name, file.type) const ext = getExtension(file.name) const textFile = isTextExtension(ext) || mimeType.startsWith('text/') || mimeType.includes('json') || mimeType.includes('xml') + if (!textFile) { return { mimeType, @@ -226,6 +223,17 @@ async function readFilePayload(file) { blob: file } } + + // 二进制扩展名文件不尝试读取内容,避免长时间等待 + if (isBinaryExtension(ext)) { + return { + mimeType, + size: file.size, + storageKind: 'blob', + blob: file + } + } + if (file.size <= MAX_TEXT_SIZE) { const content = await file.text() return { @@ -314,13 +322,8 @@ export function useFileSystem() { async function load() { loading.value = true try { - const stored = await withStore('readonly', (store) => store.getAll()) - const request = stored - const nextRecords = await new Promise((resolve, reject) => { - request.onsuccess = () => resolve(Array.isArray(request.result) ? request.result : []) - request.onerror = () => reject(request.error || new Error('读取本地文件失败')) - }) - if (nextRecords.length === 0) { + const nextRecords = await withStore('readonly', (store) => store.getAll()) + if (!Array.isArray(nextRecords) || nextRecords.length === 0) { const seed = createWelcomeRecords() await Promise.all(seed.map((record) => persistRecord(record))) records.value = seed diff --git a/src/plugins/AGENTS.md b/src/plugins/AGENTS.md new file mode 100644 index 0000000..c4ce5cc --- /dev/null +++ b/src/plugins/AGENTS.md @@ -0,0 +1,42 @@ +# Plugins 模块指南 + +## OVERVIEW +Milkdown/ProseMirror 插件,AI 补全核心逻辑。 + +## STRUCTURE +- copilotPlugin.ts - ProseMirror Mark 系统、ghost text、防抖请求 +- docBlockPlugin.ts - 文档块插件 +- mermaidPlugin.ts - Mermaid 图表渲染 +- index.ts - 插件导出 +- types.ts - 类型定义 + +## WHERE TO LOOK + +| 任务 | 文件 | 说明 | +|------|------|------| +| AI 补全核心 | copilotPlugin.ts | ProseMirror Mark、ghost text | +| 文档块处理 | docBlockPlugin.ts | 文档块解析与渲染 | +| Mermaid 图表 | mermaidPlugin.ts | 图表渲染集成 | +| 插件导出 | index.ts | 统一导出入口 | +| 类型定义 | types.ts | 公共类型 | + +## 关键函数 +- scheduleFetch - 防抖触发补全请求 +- insertGhostText - 插入 ghost text +- acceptSuggestion - Tab 接受建议 +- rejectSuggestion - Esc 取消建议 + +## CONVENTIONS +- TypeScript 2 空格缩进 +- 函数:camelCase +- 接口/类型:PascalCase + +## ANTI-PATTERNS +- 空 catch 块 +- 类型错误使用 as any +- 硬编码超时值 + +## 注意事项 +- 防抖时间:1000ms(可配置) +- 文档大小限制:32KB 自动禁用 +- Tab/Esc 快捷键交互 diff --git a/src/services/univerBridge.js b/src/services/univerBridge.js index 03352c3..7153484 100644 --- a/src/services/univerBridge.js +++ b/src/services/univerBridge.js @@ -1,14 +1,22 @@ /** - * Univer 编辑器桥接服务 - * 封装 Univer 的初始化、加载、导出等操作 - */ +* Univer 编辑器桥接服务 +* 封装 Univer 的初始化、加载、导出等操作 +* +* 支持三种文档格式: +* - DOCX: 使用 DocsCorePreset +* - XLSX: 使用 SheetsCorePreset +* - PPTX: 使用 DocsCorePreset + Slides 插件组合 +*/ import { createUniver, LocaleType, merge } from '@univerjs/presets' import { UniverDocsCorePreset } from '@univerjs/preset-docs-core' import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core' +import { UniverSlidesPlugin } from '@univerjs/slides' +import { UniverSlidesUIPlugin } from '@univerjs/slides-ui' // 导入样式 import '@univerjs/preset-docs-core/lib/index.css' import '@univerjs/preset-sheets-core/lib/index.css' +import '@univerjs/slides-ui/lib/index.css' // 导入语言包 import DocsCoreEnUS from '@univerjs/preset-docs-core/locales/en-US' @@ -16,6 +24,10 @@ import SheetsCoreEnUS from '@univerjs/preset-sheets-core/locales/en-US' import DocsCoreZhCN from '@univerjs/preset-docs-core/locales/zh-CN' import SheetsCoreZhCN from '@univerjs/preset-sheets-core/locales/zh-CN' +// Slides 语言包(使用空对象作为 fallback,因为 slides 语言包可能不存在) +const SlidesZhCN = {} +const SlidesEnUS = {} + export const OfficeFormat = { DOCX: 'docx', XLSX: 'xlsx', @@ -66,43 +78,68 @@ export async function createUniverInstance(container, options = {}) { } = options const localeType = locale === 'zh-CN' ? LocaleType.ZH_CN : LocaleType.EN_US + // 合并所有语言包(包括 Slides) const locales = locale === 'zh-CN' - ? { [LocaleType.ZH_CN]: merge(DocsCoreZhCN, SheetsCoreZhCN) } - : { [LocaleType.EN_US]: merge(DocsCoreEnUS, SheetsCoreEnUS) } + ? { [LocaleType.ZH_CN]: merge({}, DocsCoreZhCN, SheetsCoreZhCN, SlidesZhCN) } + : { [LocaleType.EN_US]: merge({}, DocsCoreEnUS, SheetsCoreEnUS, SlidesEnUS) } const presets = [] + const extraPlugins = [] - // 根据格式添加对应的 Preset - if (format === OfficeFormat.DOCX || format === OfficeFormat.PPTX) { - presets.push(UniverDocsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) + // 根据格式添加对应的 Preset 和插件 + switch (format) { + case OfficeFormat.DOCX: + // DOCX 只需要 DocsCorePreset + presets.push(UniverDocsCorePreset({ + container, + theme: theme === 'dark' ? 'dark' : 'default' + })) + break + + case OfficeFormat.XLSX: + // XLSX 只需要 SheetsCorePreset + presets.push(UniverSheetsCorePreset({ + container, + theme: theme === 'dark' ? 'dark' : 'default' + })) + break + + case OfficeFormat.PPTX: + // PPTX 需要 DocsCorePreset + Slides 插件 + // DocsCorePreset 提供基础文档渲染能力 + presets.push(UniverDocsCorePreset({ + container, + theme: theme === 'dark' ? 'dark' : 'default' + })) + // Slides 插件提供演示文稿功能 + // 注意:必须作为 plugins 而非 presets 传入 + extraPlugins.push([UniverSlidesPlugin]) + extraPlugins.push([UniverSlidesUIPlugin]) + break + + default: + // 默认使用 Docs 作为兜底 + presets.push(UniverDocsCorePreset({ + container, + theme: theme === 'dark' ? 'dark' : 'default' + })) } - if (format === OfficeFormat.XLSX) { - presets.push(UniverSheetsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) + try { + const { univer, univerAPI } = createUniver({ + locale: localeType, + locales, + presets, + plugins: extraPlugins.length > 0 ? extraPlugins : undefined, + collaboration: false // 纯前端模式,不启用协作 + }) + + console.log(`[Univer] 初始化成功,格式: ${format}, 预设数量: ${presets.length}, 插件数量: ${extraPlugins.length}`) + return { univer, univerAPI } + } catch (error) { + console.error('[Univer] 初始化失败:', error) + throw new Error(`Univer 初始化失败: ${error.message}`) } - - // 默认使用 Docs 作为兜底 - if (presets.length === 0) { - presets.push(UniverDocsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) - } - - const { univer, univerAPI } = createUniver({ - locale: localeType, - locales, - presets, - collaboration: false // 纯前端模式,不启用协作 - }) - - return { univer, univerAPI } } /** diff --git a/src/store/index.js b/src/store/index.js deleted file mode 100644 index e90ba41..0000000 --- a/src/store/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createPinia } from 'pinia' - -const pinia = createPinia() - -export default pinia \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index e52f758..0bdd7e4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -53,7 +53,17 @@ export default defineConfig({ include: [ '@milkdown/crepe', '@milkdown/vue', - '@milkdown/kit' + '@milkdown/kit', + '@univerjs/core', + '@univerjs/design', + '@univerjs/engine-render', + '@univerjs/engine-formula', + '@univerjs/ui', + '@univerjs/presets', + '@univerjs/preset-docs-core', + '@univerjs/preset-sheets-core', + '@univerjs/slides', + '@univerjs/slides-ui' ] } })