From 59334e4057f03d54bc1e8211918dda964bf04649 Mon Sep 17 00:00:00 2001 From: ydy0615 Date: Sun, 24 May 2026 23:30:32 +0800 Subject: [PATCH] Stabilize pro editing without heavy office runtime The workspace now carries the pro editing flow, streaming completion path, and lighter Office preview state as one checkpoint so the remote has the current runnable project shape. Constraint: Preserve the current workspace as a single reviewable project commit while excluding local agent state and verification artifacts. Removed stale Univer runtime dependencies from the lockfile so installs match package.json. Rejected: Commit runtime screenshots, .omx state, and coverage files | they are local artifacts rather than source state. Confidence: medium Scope-risk: broad Directive: Keep package.json and package-lock.json synchronized when changing frontend dependencies. Tested: npm run build; C:\Users\ydy\.conda\envs\llmwebsite\python.exe -m pytest backend/tests/test_main_endpoints.py backend/tests/test_main_cancel.py backend/tests/test_llm.py backend/tests/test_llm_extended.py -v -o addopts= (44 passed). Not-tested: Full pytest with repository coverage addopts currently reports 0% coverage because pytest-cov watches backend.* module names while tests import top-level backend modules. Co-authored-by: OmX --- .gitignore | 8 +- backend/llm.py | 190 +- backend/main.py | 151 +- backend/prompt.py | 10 +- backend/tests/test_llm.py | 27 +- backend/tests/test_llm_extended.py | 90 +- backend/tests/test_main_endpoints.py | 40 + package-lock.json | 3429 +------------------------- package.json | 5 - src/AGENTS.md | 3 +- src/components/ContextMenu.vue | 24 +- src/components/DocBlockCrepe.vue | 5 + src/components/FileContent.vue | 20 +- src/components/HiddenTextCrepe.vue | 221 ++ src/components/MarkdownPreview.vue | 3 + src/components/MilkdownEditor.vue | 854 ++++++- src/components/OfficePreview.vue | 245 ++ src/components/ProBlockCrepe.vue | 334 +++ src/components/SettingsPanel.vue | 15 + src/components/UniverEditor.vue | 206 -- src/components/UniverPreview.vue | 407 --- src/components/UploadBlockCrepe.vue | 240 ++ src/main.js | 8 - src/plugins/hiddenTextPlugin.ts | 246 ++ src/plugins/mermaidPlugin.ts | 12 +- src/plugins/proBlockPlugin.ts | 665 +++++ src/plugins/uploadBlockPlugin.ts | 263 ++ src/router/index.js | 5 +- src/services/officeDetection.js | 62 +- src/services/univerBridge.js | 302 --- src/stores/office.js | 186 -- src/stores/settings.js | 12 +- src/stores/templates.js | 259 ++ src/style.css | 21 + src/utils/api.js | 190 +- src/utils/config.js | 1 + src/utils/hiddenText.js | 162 ++ src/utils/i18n.js | 54 + src/utils/proBlock.js | 72 + src/utils/uploadBlock.js | 220 ++ src/views/UniverView.vue | 46 - 41 files changed, 4438 insertions(+), 4875 deletions(-) create mode 100644 src/components/HiddenTextCrepe.vue create mode 100644 src/components/OfficePreview.vue create mode 100644 src/components/ProBlockCrepe.vue delete mode 100644 src/components/UniverEditor.vue delete mode 100644 src/components/UniverPreview.vue create mode 100644 src/components/UploadBlockCrepe.vue create mode 100644 src/plugins/hiddenTextPlugin.ts create mode 100644 src/plugins/proBlockPlugin.ts create mode 100644 src/plugins/uploadBlockPlugin.ts delete mode 100644 src/services/univerBridge.js delete mode 100644 src/stores/office.js create mode 100644 src/stores/templates.js create mode 100644 src/utils/hiddenText.js create mode 100644 src/utils/proBlock.js create mode 100644 src/utils/uploadBlock.js delete mode 100644 src/views/UniverView.vue diff --git a/.gitignore b/.gitignore index 98186f7..a06f80f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ env/ .mypy_cache/ .ruff_cache/ htmlcov/ +.coverage api_performance_report.md # Env files @@ -46,4 +47,9 @@ api_performance_report.md # IDE directories .kilocode/ .kilo/ -.codex/ \ No newline at end of file +.codex/ + +# Agent/runtime state and local verification artifacts +.omx/ +.tmp-*.png +tmp-*.txt diff --git a/backend/llm.py b/backend/llm.py index 90886ed..391b6f0 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -3,6 +3,7 @@ import time import logging import asyncio from datetime import datetime +from typing import AsyncIterator import ollama from dotenv import load_dotenv @@ -11,6 +12,7 @@ from prompts import get_vlm_ocr_prompt load_dotenv() OLLAMA_MODEL = os.getenv('OLLAMA_MODEL', 'gpt-oss:20b') +PRO_OLLAMA_MODEL = os.getenv('PRO_OLLAMA_MODEL', OLLAMA_MODEL) OLLAMA_HOST = os.getenv('OLLAMA_HOST', 'http://localhost:11434') VLM_MODEL = os.getenv('VLM_MODEL', 'qwen3-vl:30b') @@ -29,14 +31,73 @@ def _extract_message(response) -> tuple[str, str]: if hasattr(response, 'message') and response.message: content = response.message.content or "" thinking = getattr(response.message, 'thinking', '') or "" - elif isinstance(response, dict): + elif isinstance(response, dict) and 'message' in response: msg = response.get('message', {}) content = msg.get('content', '') or "" thinking = msg.get('thinking', '') or "" + # fallback for generate + if not content: + if hasattr(response, 'response'): + content = getattr(response, 'response', '') or "" + elif isinstance(response, dict) and 'response' in response: + content = response.get('response', '') or "" + return content, thinking +def _build_prompt(prompt: str, system_prompt: str | None = None) -> str: + if system_prompt and system_prompt.strip(): + return f"{system_prompt}\n\n{prompt}" + return prompt + + +def _resolve_model_name(model: str | None = None, *, use_pro_model: bool = False) -> str: + candidate = (model or '').strip() + if candidate: + return candidate + return PRO_OLLAMA_MODEL if use_pro_model else OLLAMA_MODEL + + +def _build_generate_kwargs( + prompt: str, + *, + system_prompt: str | None = None, + temperature: float = 0.7, + thinking: str | None = None, + model: str | None = None, + use_pro_model: bool = False, + stream: bool = False, +) -> dict: + kwargs = { + "model": _resolve_model_name(model, use_pro_model=use_pro_model), + "prompt": _build_prompt(prompt, system_prompt), + "stream": stream, + "raw": True, + "options": { + 'temperature': temperature, + 'repeat_penalty': 1.1, + }, + } + if thinking: + kwargs["think"] = thinking + return kwargs + + +def _extract_stream_text(chunk) -> str: + content, _ = _extract_message(chunk) + if content: + return content + + if isinstance(chunk, dict): + return chunk.get('response', '') or '' + + if hasattr(chunk, 'response'): + return getattr(chunk, 'response', '') or '' + + return '' + + async def call_ollama( prompt: str, *, @@ -44,16 +105,19 @@ async def call_ollama( tag: str = "default", temperature: float = 0.7, thinking: str | None = None, + model: str | None = None, + use_pro_model: bool = False, ) -> dict: """ 调用 Ollama API 并返回 content 和 thinking。 """ start = time.perf_counter() start_dt = datetime.now() + model_name = _resolve_model_name(model, use_pro_model=use_pro_model) logger.info( "[LLM][%s] request model=%s host=%s prompt_chars=%d system_chars=%d temp=%.2f thinking=%s", tag, - OLLAMA_MODEL, + model_name, OLLAMA_HOST, len(prompt), len(system_prompt or ""), @@ -62,24 +126,17 @@ async def call_ollama( ) try: - messages = [] - if system_prompt and system_prompt.strip(): - messages.append({"role": "system", "content": system_prompt}) - messages.append({"role": "user", "content": prompt}) + kwargs = _build_generate_kwargs( + prompt, + system_prompt=system_prompt, + temperature=temperature, + thinking=thinking, + model=model, + use_pro_model=use_pro_model, + stream=False, + ) - kwargs = { - "model": OLLAMA_MODEL, - "messages": messages, - "stream": False, - "options": { - 'temperature': temperature, - 'repeat_penalty': 1.1, - }, - } - if thinking: - kwargs["think"] = thinking - - response = await asyncio.wait_for(client.chat(**kwargs), timeout=COMPLETION_TIMEOUT) + response = await asyncio.wait_for(client.generate(**kwargs), timeout=COMPLETION_TIMEOUT) except asyncio.CancelledError: elapsed_ms = (time.perf_counter() - start) * 1000 end_dt = datetime.now() @@ -126,6 +183,101 @@ async def call_ollama( return {"content": content, "think": thinking} + +async def stream_ollama( + prompt: str, + *, + system_prompt: str | None = None, + tag: str = "default-stream", + temperature: float = 0.7, + thinking: str | None = None, + model: str | None = None, + use_pro_model: bool = False, +) -> AsyncIterator[str]: + start = time.perf_counter() + start_dt = datetime.now() + model_name = _resolve_model_name(model, use_pro_model=use_pro_model) + yielded_chars = 0 + + logger.info( + "[LLM][%s] stream request model=%s host=%s prompt_chars=%d system_chars=%d temp=%.2f thinking=%s", + tag, + model_name, + OLLAMA_HOST, + len(prompt), + len(system_prompt or ""), + temperature, + thinking, + ) + + try: + kwargs = _build_generate_kwargs( + prompt, + system_prompt=system_prompt, + temperature=temperature, + thinking=thinking, + model=model, + use_pro_model=use_pro_model, + stream=True, + ) + stream = await client.generate(**kwargs) + iterator = stream.__aiter__() + deadline = time.perf_counter() + COMPLETION_TIMEOUT + + while True: + remaining = deadline - time.perf_counter() + if remaining <= 0: + raise TimeoutError("LLM stream timed out") + + try: + chunk = await asyncio.wait_for(iterator.__anext__(), timeout=remaining) + except StopAsyncIteration: + break + + text = _extract_stream_text(chunk) + if not text: + continue + + yielded_chars += len(text) + yield text + except asyncio.CancelledError: + elapsed_ms = (time.perf_counter() - start) * 1000 + end_dt = datetime.now() + logger.info( + "[LLM][%s] stream_time [%s --> %s]", + tag, + start_dt.strftime("%H:%M:%S"), + end_dt.strftime("%H:%M:%S"), + ) + logger.warning("[LLM][%s] stream cancelled after %.1fms", tag, elapsed_ms) + raise + except Exception: + elapsed_ms = (time.perf_counter() - start) * 1000 + end_dt = datetime.now() + logger.info( + "[LLM][%s] stream_time [%s --> %s]", + tag, + start_dt.strftime("%H:%M:%S"), + end_dt.strftime("%H:%M:%S"), + ) + logger.exception("[LLM][%s] stream failed after %.1fms", tag, elapsed_ms) + raise + + elapsed_ms = (time.perf_counter() - start) * 1000 + end_dt = datetime.now() + logger.info( + "[LLM][%s] stream_time [%s --> %s]", + tag, + start_dt.strftime("%H:%M:%S"), + end_dt.strftime("%H:%M:%S"), + ) + logger.info( + "[LLM][%s] stream finished in %.1fms yielded_chars=%d", + tag, + elapsed_ms, + yielded_chars, + ) + async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str: start = time.perf_counter() start_dt = datetime.now() diff --git a/backend/main.py b/backend/main.py index c8deae2..136b78f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,6 @@ import asyncio import base64 +import json import logging import os import re @@ -11,12 +12,12 @@ from typing import Optional from fastapi import FastAPI, HTTPException, Request, Security from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse +from fastapi.responses import JSONResponse, StreamingResponse from fastapi.security import APIKeyHeader from pydantic import BaseModel from geoip import get_ip_location_text -from llm import call_ollama, call_vlm_ocr +from llm import call_ollama, call_vlm_ocr, stream_ollama from models import UserPreferences from prompt import build_completion_prompts, prepare_prompt_context import markitdown @@ -78,6 +79,8 @@ class CompletionRequest(BaseModel): model_thinking: str = "low" privacy_mode: bool = False user_preferences: Optional[UserPreferences] = None + model: Optional[str] = None + temperature: float = 0.7 class CancelCompletionRequest(BaseModel): @@ -143,6 +146,14 @@ def get_client_ip(request: Request) -> str: return request.headers.get("X-Client-IP") or "unknown" +def _clamp_temperature(value: float, default: float = 0.7) -> float: + try: + numeric = float(value) + except (TypeError, ValueError): + return default + return max(0.0, min(numeric, 1.2)) + + @app.post("/v1/completions") async def create_completion(request: Request, req: CompletionRequest, api_key: str = Security(get_api_key)): request_id = request.headers.get("X-Request-Id") or str(uuid.uuid4()) @@ -189,8 +200,9 @@ async def create_completion(request: Request, req: CompletionRequest, api_key: s user_prompt, system_prompt=system_prompt, tag=f"{request_tag}-primary", - temperature=0.7, + temperature=_clamp_temperature(req.temperature, 0.7), thinking=req.model_thinking if req.model_thinking != "none" else None, + model=req.model, ) ) @@ -224,6 +236,124 @@ async def create_completion(request: Request, req: CompletionRequest, api_key: s ACTIVE_COMPLETIONS.pop(request_id, None) +@app.post("/v1/pro/completions/stream") +async def create_pro_completion_stream(request: Request, req: CompletionRequest, api_key: str = Security(get_api_key)): + request_id = request.headers.get("X-Request-Id") or str(uuid.uuid4()) + request_tag = request_id[:8] + queue: asyncio.Queue[Optional[tuple[str, str]]] = asyncio.Queue() + + client_ip = "hidden" + location = "" + + if not req.privacy_mode: # pragma: no cover + client_ip = get_client_ip(request) + location = get_ip_location_text(client_ip) + if location: + logger.info("[%s] client_location=%s", request_tag, location) + + logger.info( + "[%s] /v1/pro/completions/stream request_id=%s client_ip=%s prefix_chars=%d suffix_chars=%d lang=%s thinking=%s privacy=%s model=%s temp=%.2f", + request_tag, + request_id, + client_ip, + len(req.prefix or ""), + len(req.suffix or ""), + req.languageId, + req.model_thinking, + req.privacy_mode, + req.model or "", + _clamp_temperature(req.temperature, 0.7), + ) + + llm_prefix, llm_suffix = prepare_prompt_context(req.prefix or "", req.suffix or "") + logger.info("[%s] pro_llm_input_prefix=%r", request_tag, llm_prefix) + logger.info("[%s] pro_llm_input_suffix=%r", request_tag, llm_suffix) + + system_prompt, user_prompt = build_completion_prompts( + req.prefix, + req.suffix, + req.languageId, + location=location, + thinking_level=req.model_thinking, + preferences=req.user_preferences, + ) + + async def producer() -> None: + chunks: list[str] = [] + try: + async for delta in stream_ollama( + user_prompt, + system_prompt=system_prompt, + tag=f"{request_tag}-pro", + temperature=_clamp_temperature(req.temperature, 0.7), + thinking=req.model_thinking if req.model_thinking != "none" else None, + model=req.model, + use_pro_model=True, + ): + chunks.append(delta) + await queue.put(("chunk", json.dumps({"delta": delta}, ensure_ascii=False))) + + content = "".join(chunks) + logger.info( + "[%s] pro stream resolved request_id=%s content_chars=%d content_preview='%s'", + request_tag, + request_id, + len(content), + _preview(content, 120), + ) + await queue.put(( + "done", + json.dumps({"content": content, "request_id": request_id}, ensure_ascii=False), + )) + except asyncio.CancelledError: + logger.info("[%s] /v1/pro/completions/stream cancelled request_id=%s", request_tag, request_id) + await queue.put(( + "cancelled", + json.dumps({"cancelled": True, "request_id": request_id}, ensure_ascii=False), + )) + raise + except Exception as e: + logger.exception("[%s] /v1/pro/completions/stream failed request_id=%s: %s", request_tag, request_id, e) + await queue.put(( + "error", + json.dumps({"error": str(e), "request_id": request_id}, ensure_ascii=False), + )) + finally: + await queue.put(None) + + producer_task = asyncio.create_task(producer()) + existing = ACTIVE_COMPLETIONS.get(request_id) + if existing and not existing.done(): + existing.cancel() + ACTIVE_COMPLETIONS[request_id] = producer_task + + async def event_stream(): + try: + while True: + item = await queue.get() + if item is None: + break + + event_name, data = item + yield f"event: {event_name}\ndata: {data}\n\n" + except asyncio.CancelledError: + producer_task.cancel() + raise + finally: + active = ACTIVE_COMPLETIONS.get(request_id) + if active is producer_task: + ACTIVE_COMPLETIONS.pop(request_id, None) + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", + }, + ) + + @app.post("/v1/completions/cancel") async def cancel_completion(req: CancelCompletionRequest, api_key: str = Security(get_api_key)): request_tag = str(uuid.uuid4())[:8] @@ -349,8 +479,19 @@ async def convert_to_markdown(request: ConvertRequest, api_key: str = Security(g # TTS and ASR routes (lazy loaded to avoid heavy import on startup) def _register_tts_asr_routes(): - from tts_asr import register_tts_asr_routes - register_tts_asr_routes(app) + try: + from tts_asr import register_tts_asr_routes + except ModuleNotFoundError as exc: + logger.warning("Skipping TTS/ASR route registration because a dependency is missing: %s", exc) + return + except Exception as exc: + logger.warning("Skipping TTS/ASR route registration because import failed: %s", exc) + return + + try: + register_tts_asr_routes(app) + except Exception as exc: + logger.warning("Failed to register TTS/ASR routes: %s", exc) _register_tts_asr_routes() diff --git a/backend/prompt.py b/backend/prompt.py index 8c90936..b1673e6 100644 --- a/backend/prompt.py +++ b/backend/prompt.py @@ -331,15 +331,7 @@ Step 3: Choose newline type === NOW COMPLETE THE TASK === - -{recent_prefix} - - - -{recent_suffix} - - -Output:""" +<|fim_prefix|>{recent_prefix}<|fim_suffix|>{recent_suffix}<|fim_middle|>""" system_prompt = build_inline_system_prompt(safe_language_id) return system_prompt.strip(), user_prompt.strip() diff --git a/backend/tests/test_llm.py b/backend/tests/test_llm.py index 59c713d..dd627b8 100644 --- a/backend/tests/test_llm.py +++ b/backend/tests/test_llm.py @@ -19,11 +19,11 @@ except ModuleNotFoundError: def test_call_ollama_messages_roles_with_system(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs["messages"] - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama( @@ -35,20 +35,18 @@ def test_call_ollama_messages_roles_with_system(monkeypatch): ) assert result["content"] == "ok" - assert captured["messages"][0]["role"] == "system" - assert captured["messages"][0]["content"] == "system prompt body" - assert captured["messages"][1]["role"] == "user" - assert captured["messages"][1]["content"] == "user prompt body" + assert captured["kwargs"]["prompt"] == "system prompt body\n\nuser prompt body" + assert captured["kwargs"]["raw"] is True def test_call_ollama_messages_roles_without_system(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs["messages"] - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama( @@ -60,6 +58,5 @@ def test_call_ollama_messages_roles_without_system(monkeypatch): ) assert result["content"] == "ok" - assert len(captured["messages"]) == 1 - assert captured["messages"][0]["role"] == "user" - assert captured["messages"][0]["content"] == "user prompt only" + assert captured["kwargs"]["prompt"] == "user prompt only" + assert captured["kwargs"]["raw"] is True diff --git a/backend/tests/test_llm_extended.py b/backend/tests/test_llm_extended.py index 2cce77c..cc51dda 100644 --- a/backend/tests/test_llm_extended.py +++ b/backend/tests/test_llm_extended.py @@ -85,46 +85,45 @@ def test_extract_message_empty_dict(): def test_call_ollama_no_system_message(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs.get("messages", []) - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama("user prompt body", system_prompt=None, tag="no-system", temperature=0.1) ) assert result["content"] == "ok" - assert len(captured["messages"]) == 1 - assert captured["messages"][0]["role"] == "user" - assert captured["messages"][0]["content"] == "user prompt body" + assert captured["kwargs"]["prompt"] == "user prompt body" + assert captured["kwargs"]["raw"] is True def test_call_ollama_whitespace_system_message(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs.get("messages", []) - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama("user prompt", system_prompt=" ", tag="whitespace-system", temperature=0.1) ) assert result["content"] == "ok" - assert len(captured["messages"]) == 1 - assert captured["messages"][0]["role"] == "user" + assert captured["kwargs"]["prompt"] == "user prompt" + assert captured["kwargs"]["raw"] is True def test_call_ollama_thinking_in_kwargs(monkeypatch): captured = {} - async def fake_chat(**kwargs): + async def fake_generate(**kwargs): captured.update(kwargs) - return {"message": {"content": "ok", "thinking": "boom"}} + return {"response": "ok", "message": {"thinking": "boom"}} # keep thinking for backward test though it might not be perfect - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) res = asyncio.run( llm.call_ollama("prompt", thinking="boom", tag="think-flag", temperature=0.7) @@ -134,10 +133,10 @@ def test_call_ollama_thinking_in_kwargs(monkeypatch): def test_call_ollama_cancelled_reraises(monkeypatch): - async def fake_chat(**kwargs): + async def fake_generate(**kwargs): raise asyncio.CancelledError - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) with pytest.raises(asyncio.CancelledError): asyncio.run( @@ -146,10 +145,10 @@ def test_call_ollama_cancelled_reraises(monkeypatch): def test_call_ollama_chat_raises_rethrows(monkeypatch): - async def fake_chat(**kwargs): + async def fake_generate(**kwargs): raise ValueError("boom") - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) with pytest.raises(ValueError): asyncio.run( @@ -158,10 +157,10 @@ def test_call_ollama_chat_raises_rethrows(monkeypatch): def test_call_ollama_returns_content_and_think_from_response(monkeypatch): - async def fake_chat(**kwargs): - return {"message": {"content": "final", "thinking": "process"}} + async def fake_generate(**kwargs): + return {"response": "final", "message": {"thinking": "process"}} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) res = asyncio.run( llm.call_ollama("prompt", system_prompt=None, tag="return", temperature=0.7) @@ -169,6 +168,51 @@ def test_call_ollama_returns_content_and_think_from_response(monkeypatch): assert res["content"] == "final" and res["think"] == "process" +def test_stream_ollama_uses_requested_model_and_yields_chunks(monkeypatch): + captured = {} + + class FakeStream: + def __init__(self, chunks): + self._chunks = iter(chunks) + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return next(self._chunks) + except StopIteration: + raise StopAsyncIteration + + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return FakeStream([ + {"response": "深度"}, + {"response": "回答"}, + ]) + + monkeypatch.setattr(llm.client, "generate", fake_generate) + + chunks = [] + + async def collect_stream(): + async for chunk in llm.stream_ollama( + "prompt", + system_prompt="system", + tag="stream", + temperature=0.8, + model="pro-model", + use_pro_model=True, + ): + chunks.append(chunk) + + asyncio.run(collect_stream()) + + assert "".join(chunks) == "深度回答" + assert captured["kwargs"]["model"] == "pro-model" + assert captured["kwargs"]["stream"] is True + + def test_call_vlm_ocr_passes_image_and_prompt(monkeypatch): image_bytes = b"image-bytes" called = {} diff --git a/backend/tests/test_main_endpoints.py b/backend/tests/test_main_endpoints.py index 031927c..75eb3a6 100644 --- a/backend/tests/test_main_endpoints.py +++ b/backend/tests/test_main_endpoints.py @@ -1,6 +1,7 @@ import os import sys import base64 +import types import pytest from unittest.mock import MagicMock from fastapi.testclient import TestClient @@ -10,6 +11,11 @@ BACKEND_DIR = os.path.abspath(os.path.join(CURRENT_DIR, "..")) if BACKEND_DIR not in sys.path: sys.path.insert(0, BACKEND_DIR) +if "tts_asr" not in sys.modules: + fake_tts_asr = types.ModuleType("tts_asr") + fake_tts_asr.register_tts_asr_routes = lambda app: None + sys.modules["tts_asr"] = fake_tts_asr + import main # type: ignore API_KEY = main.API_KEY @@ -112,6 +118,40 @@ def test_post_completions_privacy_mode(monkeypatch): assert data.get("content") == "done" +def test_post_pro_stream_returns_sse(monkeypatch): + captured = {} + + async def fake_stream(*args, **kwargs): + captured["kwargs"] = kwargs + yield "深度" + yield "回答" + + monkeypatch.setattr(main, "stream_ollama", fake_stream) + monkeypatch.setattr(main, "build_completion_prompts", lambda *a, **k: ("sys", "user")) + monkeypatch.setattr(main, "prepare_prompt_context", lambda *a, **k: ("p", "s")) + + client = TestClient(main.app) + with client.stream("POST", "/v1/pro/completions/stream", headers=HEADERS, json={ + "prefix": "hello", + "suffix": "", + "languageId": "markdown", + "model_thinking": "high", + "privacy_mode": True, + "model": "pro-model", + "temperature": 0.95, + }) as resp: + assert resp.status_code == 200 + body = "".join(resp.iter_text()) + + assert "event: chunk" in body + assert "event: done" in body + assert "深度" in body + assert "回答" in body + assert captured["kwargs"]["model"] == "pro-model" + assert captured["kwargs"]["use_pro_model"] is True + assert main.ACTIVE_COMPLETIONS == {} + + def test_post_ocr_mocked(monkeypatch): async def fake_ocr(*args, **kwargs): return "OCR result text" diff --git a/package-lock.json b/package-lock.json index af0d6f4..cdd1502 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,6 @@ "@milkdown/kit": "^7.18.0", "@milkdown/theme-nord": "^7.18.0", "@milkdown/vue": "^7.18.0", - "@univerjs/preset-docs-core": "^0.20.0", - "@univerjs/preset-sheets-core": "^0.20.0", - "@univerjs/presets": "^0.20.0", - "@univerjs/slides": "^0.20.0", - "@univerjs/slides-ui": "^0.20.0", "docx": "^9.6.0", "docx-preview": "^0.3.7", "docx2pdf-converter": "^2.1.1", @@ -1209,12 +1204,6 @@ "node": ">=18" } }, - "node_modules/@flatten-js/interval-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@flatten-js/interval-tree/-/interval-tree-1.1.3.tgz", - "integrity": "sha512-xhFWUBoHJFF77cJO1D6REjdgJEMRf2Y2Z+eKEPav8evGKcLSnj1ud5pLXQSbGuxF3VSvT1rWhMfVpXEKJLTL+A==", - "license": "MIT" - }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -1871,27 +1860,6 @@ "vue": "^3.0.0" } }, - "node_modules/@noble/ed25519": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz", - "integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@ocavue/utils": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@ocavue/utils/-/utils-1.6.0.tgz", @@ -1901,779 +1869,6 @@ "url": "https://github.com/sponsors/ocavue" } }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", - "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" - }, "node_modules/@remirror/core-constants": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", @@ -3739,2165 +2934,6 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, - "node_modules/@univerjs-pro/collaboration": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/collaboration/-/collaboration-0.20.0.tgz", - "integrity": "sha512-iCsyTAArvtvh6e4a2xIt6AvA5uHFUkh/t6mCXHR2AuBo1ZZud5jZTPnV9HkdLpf7VIV3qe+6KI7/N8MTOdemjA==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "uuid": "^13.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/collaboration-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/collaboration-client/-/collaboration-client-0.20.0.tgz", - "integrity": "sha512-dcBuRj5IodiIgGm6fWKW49CSvAqNJf6SjupSJaRDDisRF546hsS4IlulFEhlRaARgIlstqoM9aRDCjN4CPlfDw==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/telemetry": "0.20.0", - "crypto-js": "4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/collaboration-client-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/collaboration-client-ui/-/collaboration-client-ui-0.20.0.tgz", - "integrity": "sha512-7FFM/9VdeFHioxSJ6r+t67kurzLA1w57O/J71ZC9goHxCf/rC61p/iwNKCSwlnrc73DCey39TFcZ2QdC/I9g2g==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0", - "crypto-js": "4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/collaboration/node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/@univerjs-pro/docs-exchange-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/docs-exchange-client/-/docs-exchange-client-0.20.0.tgz", - "integrity": "sha512-/Ioyox2kFk6S8kwvmgPx5vscuccoee/8mpK4As7DAMxibFGbGvfWUfIbgnFm9k7FFNrwX4c/K6o30EekxTy77A==", - "dependencies": { - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/docs-print": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/docs-print/-/docs-print-0.20.0.tgz", - "integrity": "sha512-CdcBxekBgLMJyMVq2kz523lbfxDXca/4j8oycIx8zI+RBDCRW2M+RDy2OcHaJ8Djhy9pnpCDOdD2k/FRvaJHlA==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/print": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/edit-history-loader": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/edit-history-loader/-/edit-history-loader-0.20.0.tgz", - "integrity": "sha512-0OvhK/z+lpSB4pvPhmzVLHEIva3tJ2y8QwsxEfTJ//V/cTPQFyLc3Iv9oLPlK/EeHurJq0eFiRskvtCfgaF5Jg==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0", - "@univerjs-pro/edit-history-viewer": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs-pro/sheets-chart-ui": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs-pro/sheets-shape-ui": "0.20.0", - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs-pro/sheets-sparkline-ui": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-conditional-formatting-ui": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-data-validation-ui": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-filter-ui": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-hyper-link-ui": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/edit-history-viewer": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/edit-history-viewer/-/edit-history-viewer-0.20.0.tgz", - "integrity": "sha512-tfZqyY3Sin0XwCHYmbza8w7bWhySUWc78RPMElzqgoQtJ5wHw8yQiKsK3kDj1piBTOkV1vzKpO116izXvqJaBQ==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/engine-chart": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-chart/-/engine-chart-0.20.0.tgz", - "integrity": "sha512-PmBPBYe7MdTTi53oxiKxUur08wK1kKdMlpmAfbRKHM8iQ1jKiX+jppsiWW6DG4Zc/FUWhgTSo5Mvqp+EiX/wUA==", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/engine-formula": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-formula/-/engine-formula-0.20.0.tgz", - "integrity": "sha512-iHbQPkUsweym+APtkKR9Tvg6x9bkQMH1vWtEo2IBZRykG6Cg2ZH+m2mBjfGd+yar7hgsNybAvcGuST1NaIcKYA==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/engine-pivot": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-pivot/-/engine-pivot-0.20.0.tgz", - "integrity": "sha512-6eEceMhiMKkAwujDvcSjkgg/V513TpitItvvodfYk8RzCaS12XgCBtXcG4Hw4ckyzulFB4BgHZd5FjKH0sy6yg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/engine-shape": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-shape/-/engine-shape-0.20.0.tgz", - "integrity": "sha512-4fivQmgiAsc5UoAK9ZQSlRW+KtRztr1+NDDuz3WrXxmsIIcLEBRAv6aQbXOGA7TNOJitipnyIT3m3P/o2lL+6Q==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/exchange-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/exchange-client/-/exchange-client-0.20.0.tgz", - "integrity": "sha512-sLSd/DX7InjDy5psH9zNzCxI5eTuaH6JkEECaf7L10Prmur7ug74FDZdV5ulfB7+xOcqVqT4Yd4UKVEIZYcfMQ==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/ui": "0.20.0", - "pako": "^2.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/license": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/license/-/license-0.20.0.tgz", - "integrity": "sha512-JoppYlkQUuxhvuWWuGrGruOZTOI+ur08GNIFika5OhcSZBz8PTuvMNhSSxIPsSzv+ndK4y0uMUt9YYmRi6np2A==", - "dependencies": { - "@noble/ed25519": "2.3.0", - "@noble/hashes": "1.8.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/print": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/print/-/print-0.20.0.tgz", - "integrity": "sha512-4Yd+auUSgLQ3QpVaHF1gAzrwjAp/lQD4u3vEP6lkPLBB4/rAhQchTq7LMg4fjALUFewYDJ8eCpg+dtpl7RE9IQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/sheets-chart": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-chart/-/sheets-chart-0.20.0.tgz", - "integrity": "sha512-AXTuqzfycKh0MJ05glVlD1uYmO3zPptkzu1VuPHlhFH12xdM+R1TK6f963K7apsZLuBWe3ZMA0pyQiiql5MQvQ==", - "dependencies": { - "@univerjs-pro/engine-chart": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-chart-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-chart-ui/-/sheets-chart-ui-0.20.0.tgz", - "integrity": "sha512-MlzmLm49u4wLpghsLFmDw7mHygeLZZh95AsMmdK5mU7SXv5Tles6b1pw2X3uJilX9vobLcLXuYVPcDzSZiqCuQ==", - "dependencies": { - "@univerjs-pro/engine-chart": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-exchange-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-exchange-client/-/sheets-exchange-client-0.20.0.tgz", - "integrity": "sha512-OqsqDnAKgOTLfqZpPfWAcf5+qTSfIN1He5Jk5ItqRFYZlok8aWwbH8fhsDPbfrg1Wq9e7Y/rljkvHFblXbFP1w==", - "dependencies": { - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/sheets-pivot": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-pivot/-/sheets-pivot-0.20.0.tgz", - "integrity": "sha512-P71zkINalbuzVoYsfOgBBoG7sJeeAZO96sAAfBPzh3wuPGIc3U2sSS72ZuNc1yJOEab9WWZk6vJ6e2r8YthX3w==", - "dependencies": { - "@univerjs-pro/engine-pivot": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-filter": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-pivot-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-pivot-ui/-/sheets-pivot-ui-0.20.0.tgz", - "integrity": "sha512-MfaJFY2hl9j3aiZaQmoTS7pnA/HtjqOcUnck26fQyMnA1GBkGFtNI4GXIXFCZRNukn16pIeSHPg+x5YBLNCw5A==", - "dependencies": { - "@univerjs-pro/engine-pivot": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-print": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-print/-/sheets-print-0.20.0.tgz", - "integrity": "sha512-I014PqYG6a15hpLql7tPJOPP6A+Kf6BHgS4K4a5t5mk49f88mukpOxrA+vCFAl9UMa5xx9xF839mCJpc6c4eDw==", - "dependencies": { - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/print": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-shape": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-shape/-/sheets-shape-0.20.0.tgz", - "integrity": "sha512-dSrxLta1r18IkfD+iAOHgdzY34F3xXPc+k3p/HsT//GMMh9o+KNYX/L0WURwgCgg7u9oJnKHec4HQx6Od8sJ3A==", - "dependencies": { - "@univerjs-pro/engine-shape": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/sheets-shape-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-shape-ui/-/sheets-shape-ui-0.20.0.tgz", - "integrity": "sha512-rwOhfxBPdI3cuArXebDJI63/6o3iZNNatiNJBweZtLBx+x/dljc9CsLq1fitfxtnre0Tu4hS3ZvgRM9nOzHkdA==", - "dependencies": { - "@univerjs-pro/engine-shape": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-sparkline": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-sparkline/-/sheets-sparkline-0.20.0.tgz", - "integrity": "sha512-A86oJHWgLWRZz86PbIH73wkt6vblWY6bFkutcrAkNpMOahveVIsfbdBZHPPoUrzF1dZKnVwvfMg66qIWVqdJcQ==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-sparkline-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-sparkline-ui/-/sheets-sparkline-ui-0.20.0.tgz", - "integrity": "sha512-lFEcIRkWaMdTAojNJpepavP1eutH2cKCHmMyvBsyRKxLJPiShb8qGIsQGlAfVLtJJM7ms/ZrvQZpDRrf/C1v4Q==", - "dependencies": { - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-graphics": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/thread-comment-datasource": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/thread-comment-datasource/-/thread-comment-datasource-0.20.0.tgz", - "integrity": "sha512-dWAKNE9wtKiaWArclHS4Cxd1XEEEqR09eo4Vs4yYTcQuv4xY+2Ha9sRbyc20eUWswtUO4hX+NE3b/9oalYT4ww==", - "dependencies": { - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/core/-/core-0.20.0.tgz", - "integrity": "sha512-sXz0qf0aWI9rEBZ24/+vUw/uMMwMQhKbBQz9YDafZF8MXl2ey1/n3GqXNBHrCKSpo25vB8rGT3sPk3PvZ7jWeQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/protocol": "0.1.48", - "@univerjs/themes": "0.20.0", - "@wendellhu/redi": "1.1.1", - "async-lock": "^1.4.1", - "dayjs": "^1.11.20", - "fast-diff": "1.3.0", - "kdbush": "^4.0.2", - "lodash-es": "^4.17.23", - "nanoid": "5.1.7", - "numfmt": "^3.2.3", - "ot-json1": "^1.0.2", - "rbush": "^4.0.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/data-validation": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/data-validation/-/data-validation-0.20.0.tgz", - "integrity": "sha512-OZjXC+nsrnmlPXSrCsYQfgJjuAmZdzPfunx1zExSKrAcsvCdJHjvEAZZhURcGbJcPG4CRJiGafcm/htjxxwFEA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/design": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/design/-/design-0.20.0.tgz", - "integrity": "sha512-wXRi5Lnafnpw1SDij3zGjqmV+gwL3qj7+LOWXB9QQ6WTl0Tk6IILz/KMyUuEyPCOTryLQruJ3FlE7++y0ocALw==", - "license": "Apache-2.0", - "dependencies": { - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@univerjs/icons": "^1.1.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "dayjs": "^1.11.20", - "react-transition-group": "^4.4.5", - "sonner": "^2.0.7", - "tailwind-merge": "2.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/@univerjs/docs": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs/-/docs-0.20.0.tgz", - "integrity": "sha512-mLGOQEj7MUzWc4gDoH/l2phFyYjOHPCf9Jj8KSlQAOw97wvjSnXHXaRdpr7e6IM22481XVIR8kLW0j3NRmkUjQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-drawing/-/docs-drawing-0.20.0.tgz", - "integrity": "sha512-U1AeJFuPktZTWdT41gKMdmLfNxEBe4SvTnCsubl2nuwHxYud0RnXAywqD596MP04uDRjYknOh1vZ4i1Ba0pJcQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/drawing": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/docs-drawing-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-drawing-ui/-/docs-drawing-ui-0.20.0.tgz", - "integrity": "sha512-kjNZWW1LklzEKM1BaCWTjH8wv8cqdxSiOBcBsr+SlsOmkqEUCh7EiLSoShIzKBNrGbz/fjEBCXh76upy0+jjpw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-hyper-link/-/docs-hyper-link-0.20.0.tgz", - "integrity": "sha512-YrKFbL80evv9oeIjIFPkbYJKj3i5/V3/WB/rAQd2jQqqJ8sMt+8bPEv9y0s2n3ULoLEQoKwdQZ3Pqml/CVgTPA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/docs-hyper-link-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-hyper-link-ui/-/docs-hyper-link-ui-0.20.0.tgz", - "integrity": "sha512-0jLJrssCuYZenCOFDJ1eMOgssU7HGDpjR3PByIfUca2nFSuAnLn5E/iSvYT+EZzUyB99gwiW/Tc5TS2Yj0IY+A==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-hyper-link": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-thread-comment-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-thread-comment-ui/-/docs-thread-comment-ui-0.20.0.tgz", - "integrity": "sha512-xW1vqIbmfLa74anstw55wdcZ7jzuKt9wR0a/1TZiECXRt9oY6QzVFNLJ7xA1cINGFbjRIiGfeG2vtvDAafmkIA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-ui/-/docs-ui-0.20.0.tgz", - "integrity": "sha512-CAkmK9BUt6ZNXpqSRvqbXX2rJ3jQLhQM38WvrMhebpmaHxi0eFMRGOJ3AHTwUne03weLLSK59JkyAl5CXa60yA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/drawing/-/drawing-0.20.0.tgz", - "integrity": "sha512-Uto+4m8WLCp3OaTrXObNG6SoTymgS1nz5LCwUk23FBrcd2JCfRiTBZ7wXEYbf90P8KSx32XBKUDtmyRQNR5bNw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "ot-json1": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/drawing-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/drawing-ui/-/drawing-ui-0.20.0.tgz", - "integrity": "sha512-KrIGOeYYUbO5wXe7NLYg0RNW4Bi+Zwj2+xxiLjNd+KPvfmxOMnVZ0qy8T7La6ivPvXf255je4aLKNBir+RStXw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/engine-formula": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/engine-formula/-/engine-formula-0.20.0.tgz", - "integrity": "sha512-2ZCz9lNIFie1ZXMTI6UWqwHQicjv8t/ixruSdXbecqW8+zDIktoI999Sm7EFEgK5KX3bUyhWI/NcEK4OWHERLw==", - "license": "Apache-2.0", - "dependencies": { - "@flatten-js/interval-tree": "1.1.3", - "@univerjs/core": "0.20.0", - "@univerjs/rpc": "0.20.0", - "decimal.js": "^10.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/engine-render": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/engine-render/-/engine-render-0.20.0.tgz", - "integrity": "sha512-gIuYe41N/YIXwr6zvYn4+WIryeE37Xu0gUiopP5dbB2ls3fZX3mdBdw9VvP1DsZ/GE8Cz19OmtOePKnZbMIqjQ==", - "license": "Apache-2.0", - "dependencies": { - "@floating-ui/dom": "^1.7.4", - "@floating-ui/utils": "^0.2.10", - "@univerjs/core": "0.20.0", - "cjk-regex": "^3.4.0", - "franc-min": "^6.2.0", - "opentype.js": "^1.3.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/find-replace": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/find-replace/-/find-replace-0.20.0.tgz", - "integrity": "sha512-PeG4FwTKzVq4ybZUqXHr3APuYHxRcBC1KTdlacAwbpzIW7nrTfkXP7+8ZK64VyL5pdfVi3wMOv7Usrra8XQk3Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/icons": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@univerjs/icons/-/icons-1.1.1.tgz", - "integrity": "sha512-3agWxYwNEyfpiCerajLZvZWfa+Fsx2LhGY9EeacSiPk+32BX9NwmCa9uTpDVe0F94iEO+fGfkTB8pCeIFU4l8w==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@univerjs/network": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/network/-/network-0.20.0.tgz", - "integrity": "sha512-OKdkMmRTorIunrlijkWWkRfSHP1exy6JR5KLJDmZBG1x1xdTaSRtFb2Jo7bUQpZUP41yMnnEZDDHiV1ub+fnJg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-advanced": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-advanced/-/preset-docs-advanced-0.20.0.tgz", - "integrity": "sha512-wrfOU8/iEi2IlJ3uEUybwVtDQVE+6VqBw2lg4BlaYkUs7GyAe12ou0eBjrnElN86C3GdVQPFUhST7IEDkcZtJw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/docs-exchange-client": "0.20.0", - "@univerjs-pro/docs-print": "0.20.0", - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs-pro/license": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-collaboration": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-collaboration/-/preset-docs-collaboration-0.20.0.tgz", - "integrity": "sha512-GqqYfGmTpH1Z5q2won80aAVBG7VXMtLUa0GCSj5jmSN/Z4uOXPy432waymEz2+/4FxPWWWup/hmpBqLDKnsg0Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-core/-/preset-docs-core-0.20.0.tgz", - "integrity": "sha512-IVEwvZE9LmoRn+nLhJPolAId/PqgyxBCPOoD5u5dySQMY0aHUSGo+s2gEolK2neqSoF/PLQZZ495rqD/JrPCiA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-drawing/-/preset-docs-drawing-0.20.0.tgz", - "integrity": "sha512-b+WTsR2Eh0L8t7cpFngUwfnapMg4NvEZ/K8m8Bmyq4WB1kkExvTUuHqcpHP3IlVLRSFYyuMAQme7JbO3rC2MnA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-drawing-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-hyper-link/-/preset-docs-hyper-link-0.20.0.tgz", - "integrity": "sha512-8gA4BCqImWQ/FLzdnpc5OeHh6DKuP1FPgqyYufQ0XfT7QXDn2YGy5tgMjo6wpNxOl9ztZ4PRXyGG05tVq84ywA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-hyper-link": "0.20.0", - "@univerjs/docs-hyper-link-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-node-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-node-core/-/preset-docs-node-core-0.20.0.tgz", - "integrity": "sha512-unsiv8aMsr+MF0uPwH0vwuRWeNlhnm58TFTmrRrNcjyV/jloxdQHFXu72kWJsmOOxDmkTgZW3Hb218dfGAtUWA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs": "0.20.0", - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-hyper-link": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc-node": "0.20.0", - "@univerjs/thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-thread-comment/-/preset-docs-thread-comment-0.20.0.tgz", - "integrity": "sha512-zNrH1NNMBmM8Hh1Se6kERQF+m8QcmSuwafxSLt35LZE8SWD4QiDkGLTqmXD86Q8C0q8R+Js2ubQAy38Y/S1XRw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-thread-comment-ui": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-advanced": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-advanced/-/preset-sheets-advanced-0.20.0.tgz", - "integrity": "sha512-5+spZkgX6poD425SzLvIq9zIjCCAVkNqdJNPTY0VBf2JAT673VeFfclUiPlPUSi96OC1CgSJkOrab1c0zz0R6g==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/engine-chart": "0.20.0", - "@univerjs-pro/engine-formula": "0.20.0", - "@univerjs-pro/engine-shape": "0.20.0", - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs-pro/sheets-chart-ui": "0.20.0", - "@univerjs-pro/sheets-exchange-client": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs-pro/sheets-pivot-ui": "0.20.0", - "@univerjs-pro/sheets-print": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs-pro/sheets-shape-ui": "0.20.0", - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs-pro/sheets-sparkline-ui": "0.20.0", - "@univerjs/sheets-graphics": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-collaboration": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-collaboration/-/preset-sheets-collaboration-0.20.0.tgz", - "integrity": "sha512-kGtr9BFnuqUhuPb8hbb+9rtGF7c5Yxnaz+qcVLpcG1SA/Hqj83Zyp8x+JRElS4FpcJnHSLx9OD74QhQgo93vMA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0", - "@univerjs-pro/edit-history-loader": "0.20.0", - "@univerjs-pro/edit-history-viewer": "0.20.0", - "@univerjs-pro/thread-comment-datasource": "0.20.0", - "@univerjs/preset-sheets-advanced": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-conditional-formatting": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-conditional-formatting/-/preset-sheets-conditional-formatting-0.20.0.tgz", - "integrity": "sha512-vpT2KGFKRZEPHJ7mtSFNm/AaW3lyr7jvmOG9XnH8/rzhTmzE+XrN/uy8N+5SPRgl6MTg/czUlE4YaeZs4UwE/w==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-conditional-formatting-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-core/-/preset-sheets-core-0.20.0.tgz", - "integrity": "sha512-UkVhdOyswfDfkB5WyvGocVgUfAvWrvcC8JLgw1VBV04ck43g5RzXQGy2h/Fyxztl+6FaznimPH3QuPuVYjEN3Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-numfmt-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-data-validation": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-data-validation/-/preset-sheets-data-validation-0.20.0.tgz", - "integrity": "sha512-qGQUnhveNgzWvk0PFS+bbv8iz0EfQGMiNUXBfCwUcH1jnsYZlPPHy+BBMxexEh5mnz2heHTDR24tbBk3oeG2yA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/data-validation": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-data-validation-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-drawing/-/preset-sheets-drawing-0.20.0.tgz", - "integrity": "sha512-4duVTQkYbvh+En+89cE3Ip00OuXhemIsglEM7TBPywUg1v3F4zNBAgzS64SWf1CLZMo1UYjouemCPOuNWalnJw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-filter": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-filter/-/preset-sheets-filter-0.20.0.tgz", - "integrity": "sha512-WqqxuRhiIFE2pbsf43SvIddUzt9riRFS74/Lf4UOqXrm/SYa6BtwM+cFfLnxr5fZxBRl6iOFUNBByHDclcIbxQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-filter-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-find-replace": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-find-replace/-/preset-sheets-find-replace-0.20.0.tgz", - "integrity": "sha512-pRNe7RxiijgAgyqEs2eis7UpMRm7vEWIJeA2F5ZeVvNh7Po+RWgpZR4ZBX939nampc/AtK+zD0Pt4/L1Op4X7w==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/find-replace": "0.20.0", - "@univerjs/sheets-find-replace": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-hyper-link/-/preset-sheets-hyper-link-0.20.0.tgz", - "integrity": "sha512-z2r/ffTjetkAH6H6slWoq5YJ6iivT4fEJ8w7kdoXLWUVCBaCOocfVj/oKoWsQ6A63yAKzoQcKWOBstKAY/knEQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-hyper-link-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-node-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-node-core/-/preset-sheets-node-core-0.20.0.tgz", - "integrity": "sha512-sJVOc7jTUAPSQgpLCBjek4DlNwblSWGrtGUS/aeprKtNIYXnykXvSpHAXm6Y56VEM1RunTev/kqXQtzVnw8trg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc-node": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-thread-comment": "0.20.0", - "@univerjs/thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-note": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-note/-/preset-sheets-note-0.20.0.tgz", - "integrity": "sha512-zFNLPSoHdMQvn2Z+s5c9YTMtMagxtbas7yF/fRuA0AQZdkIzuM+OdPwV1TztWcGKhAmFfhaabDW9eTofwCBENw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-note": "0.20.0", - "@univerjs/sheets-note-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-sort": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-sort/-/preset-sheets-sort-0.20.0.tgz", - "integrity": "sha512-SmtU01v6XLYukhJdiAwJ4ubDy7fcCC7dWGcLoxtpZwqo7KegsQnICW+2ZqpvyWqgZwSFyG7s+st6Tp7NI0+3BQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-sort-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-table": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-table/-/preset-sheets-table-0.20.0.tgz", - "integrity": "sha512-seFq1ptOzPuu05y1JXBysGjNHzpfuAUA2v3zquEVlC1QAc7gE6xRyq7tDxlkF9UKd1RSl0ZBJHKHEeIkVuBGYw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-table-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-thread-comment/-/preset-sheets-thread-comment-0.20.0.tgz", - "integrity": "sha512-dDr18hv+e00lkiSMKbeisKa4qCTkFI1b6uvGKAfpjem3Td75AqCo0aldliu9bdQGNMDFhTyz3n776rF/9+zIIA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-thread-comment": "0.20.0", - "@univerjs/sheets-thread-comment-ui": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/presets": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/presets/-/presets-0.20.0.tgz", - "integrity": "sha512-nwry3E0W/rz4z+Q4QXHm+f0om+wo5xK+gd6cw/hSMF+cFzQfPwW1FLx20wxHvb1MlgIYF73cMgan93VhpJ2/dQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/preset-docs-advanced": "0.20.0", - "@univerjs/preset-docs-collaboration": "0.20.0", - "@univerjs/preset-docs-core": "0.20.0", - "@univerjs/preset-docs-drawing": "0.20.0", - "@univerjs/preset-docs-hyper-link": "0.20.0", - "@univerjs/preset-docs-node-core": "0.20.0", - "@univerjs/preset-docs-thread-comment": "0.20.0", - "@univerjs/preset-sheets-advanced": "0.20.0", - "@univerjs/preset-sheets-collaboration": "0.20.0", - "@univerjs/preset-sheets-conditional-formatting": "0.20.0", - "@univerjs/preset-sheets-core": "0.20.0", - "@univerjs/preset-sheets-data-validation": "0.20.0", - "@univerjs/preset-sheets-drawing": "0.20.0", - "@univerjs/preset-sheets-filter": "0.20.0", - "@univerjs/preset-sheets-find-replace": "0.20.0", - "@univerjs/preset-sheets-hyper-link": "0.20.0", - "@univerjs/preset-sheets-node-core": "0.20.0", - "@univerjs/preset-sheets-note": "0.20.0", - "@univerjs/preset-sheets-sort": "0.20.0", - "@univerjs/preset-sheets-table": "0.20.0", - "@univerjs/preset-sheets-thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/protocol": { - "version": "0.1.48", - "resolved": "https://registry.npmjs.org/@univerjs/protocol/-/protocol-0.1.48.tgz", - "integrity": "sha512-nFHNtGAWOV0u1+IqoznH9K7hV/M9OZ61Vqwy8JMWKlgLLsx12m3vJqodkrVlLkI2YU5WuwjaUT1+J8/nM+kcUg==" - }, - "node_modules/@univerjs/rpc": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/rpc/-/rpc-0.20.0.tgz", - "integrity": "sha512-9sLzahk/zSlS5ajJjOMD5T/f6IeUUOiR4IZiHfLWFYRBr0EUHr+SxnnveiPwc5kWCER39Z7DGyqYCC7Z0c0NBA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/rpc-node": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/rpc-node/-/rpc-node-0.20.0.tgz", - "integrity": "sha512-ZZJkkpLg+lZbloxY00oOCwHTdgnXMJU56OMMDcbYXjhMtPTgUvlOhU7IdaYoqj9aFAe8VDoCQERo4ypwuUlImg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/rpc": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets/-/sheets-0.20.0.tgz", - "integrity": "sha512-1Z02wg0zhigqwvJ0FWDnH55jFpOo04JrTUZQckYymgLyHazLHkJZAy/+ixyVcAj83xNKPYMaj1Q+Y3iHG+V35Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/rpc": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-conditional-formatting": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-conditional-formatting/-/sheets-conditional-formatting-0.20.0.tgz", - "integrity": "sha512-BqgAi/H8iKME6G6geRItaUUEoGliafN7tJAyOAx7pjjJlfDsZ0gI9crTnttUDvqOFcaJcWZZz8fWL3HCIlQigQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-conditional-formatting-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-conditional-formatting-ui/-/sheets-conditional-formatting-ui-0.20.0.tgz", - "integrity": "sha512-fUeL8u9SspV/K+ZXxLojS5jvDbpYa53uvyytoX9sZeHm+UUTQ3k5omW4JGmzW1lTmu+VWVZPFIsPgDN+CYOOvg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-data-validation": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-data-validation/-/sheets-data-validation-0.20.0.tgz", - "integrity": "sha512-w3rRKLhXr3Ce9UAy/WenCmVeN3Jg6OgayNYORq0SCCppoqJbLMzjWHz0mU0T2ZPrgioalfxdbKYPGlCIxbDluA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-data-validation-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-data-validation-ui/-/sheets-data-validation-ui-0.20.0.tgz", - "integrity": "sha512-b42nbURw/CrRSQWmxL0/FkJ1eQl/FAb4IM5bwRVcBV3al1ucD/qKjKa3S/zbdeSNv/uk9ymu1syipUGJYCbikA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-drawing/-/sheets-drawing-0.20.0.tgz", - "integrity": "sha512-/8/P+hayAL4Yp6CsPwKv3OiyD/tBbWHIE122qbU4QoOZgyscD9DVzV+K0M7940T7EcJ0xZbxZHlGzHhl54MmOA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/sheets-drawing-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-drawing-ui/-/sheets-drawing-ui-0.20.0.tgz", - "integrity": "sha512-c3vnt5cNm8ZL7UjZOyYawF82oCm9pQG1ld8g9j+NgP7hbsiJlsJRyNk0rHKfxBbsvADC96RXy7KJDL0yxJTx1Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-filter": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-filter/-/sheets-filter-0.20.0.tgz", - "integrity": "sha512-WJKOjHz/3Ol/j5mkM/h475wWzWqfY5mVo8q0y0OzO7aOCu4mjHF4xOPr+X6Q4m7CTnK7j++UMWEF6XIBPSPEGg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-filter-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-filter-ui/-/sheets-filter-ui-0.20.0.tgz", - "integrity": "sha512-hgdhdBSe2W5H08tef/2Tgr0m+VrtNtHbiCWNacJf6z6p9iZ1t1rWMI3XheijqH/MX6W6/FidLJxK3i9iC51Cvw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-find-replace": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-find-replace/-/sheets-find-replace-0.20.0.tgz", - "integrity": "sha512-foz/7cRPvTmu5EwALfIecDAyYVMxmEdBe+iHruRc0WCcnRuSdi1l44g0/GPdzJbZalWejGw0ocHnKgYVPptgHg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/find-replace": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-formula": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-formula/-/sheets-formula-0.20.0.tgz", - "integrity": "sha512-ERF8O9itqpEcVuz5hH0HHe/UQP0o12zJptk5TgWN7QaZottcZ/mZ4iJjo1FPI1Rv/MsfZtWEFpooUjaPRqXMQw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-formula-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-formula-ui/-/sheets-formula-ui-0.20.0.tgz", - "integrity": "sha512-2085X4qBcMWm3DQxJDzVUHWBJotPg1aacGPg97MBIRjfk62Afc+Szj60r4n5xOMt3EwSMnipyuidzkgLqWwXag==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-graphics": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-graphics/-/sheets-graphics-0.20.0.tgz", - "integrity": "sha512-oDR/4pTQ/eTW/uKSmkppZOeXgBVN44D1pX6c7F7YSXAlTV3JU3HZ3FeGEB/fpxmjUV512q+HiCoG7VQnyYZcEA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/sheets-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-hyper-link/-/sheets-hyper-link-0.20.0.tgz", - "integrity": "sha512-VidctE2XcmgEYxzjn0piVtlhnbJzdZSZa3Y1Jb1XnusDh2Kv6RukYma3vyact1gBlWg9QX08kaZzQhJqJ5LlVg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-hyper-link-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-hyper-link-ui/-/sheets-hyper-link-ui-0.20.0.tgz", - "integrity": "sha512-9RqnKqbQnI8F1hrkdD2IIw4zm2IpzNqROSFTbZub6puhNWBgffF5MlK2mk1tG9wiEBScBxfwVEa2X8xdYriUqA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-note": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-note/-/sheets-note-0.20.0.tgz", - "integrity": "sha512-PsjUtaw/69wlKNcSUdDmZhQp1NIXMwQbwyP6fl1MyvZRlj6t9YS0wfyIt1Duy3L4e+AvPwL5E3hNdyTIyl+u6g==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-note-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-note-ui/-/sheets-note-ui-0.20.0.tgz", - "integrity": "sha512-H9ez9hpRL0tOycZDPnZF7195TmSMFi3YNrZymc/VtqLPsYOMl4tfHub4VVDQ97EWbKpgh5nLgVDD17Y29kHsbA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-note": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-numfmt": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-numfmt/-/sheets-numfmt-0.20.0.tgz", - "integrity": "sha512-3USGuBFf/IS4Ui7IpFmStcj89Jnv7N37d2dhzzYQWADS0xHn1FzHbIZUizQ/kLMMacE3758ZlhEwYnkcKWHJ7A==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-numfmt-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-numfmt-ui/-/sheets-numfmt-ui-0.20.0.tgz", - "integrity": "sha512-0ClpQx9jM2k3Yk+hDu6RXVpDQ+cXT64jis0dfCazblBnwNPVzKHk+wMYFQGT1sdJTVeD5/VzsPVgQVxhpsfloQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-sort": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-sort/-/sheets-sort-0.20.0.tgz", - "integrity": "sha512-Z6eeGRhQZyNfnQ2sfdV6qTHUdlcadQNAg9An4qB2nFn5QS5uvm2OcLd+sRteVqA2TXzP6KlZxQmFV+waiENaWQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/sheets-sort-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-sort-ui/-/sheets-sort-ui-0.20.0.tgz", - "integrity": "sha512-tyB0yTpMrj6ZTV6TXTRtt0hAWcJ1if8sIMJBfGI2Jhx+2VzXjYn5d+SzvVYgiLNOXkBFtex/J+jLA876SGlEmQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-table": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-table/-/sheets-table-0.20.0.tgz", - "integrity": "sha512-mZGVpQYRTApPC1qr2rRhrZJMG9fJ8U9M30y6r2ZEBRIwpZJLIo3Wpt2dfhDP7RsPEV42BgInMDiasxOpMbaaJA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-table-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-table-ui/-/sheets-table-ui-0.20.0.tgz", - "integrity": "sha512-nycmmfgfNdGxsYTqZmAxA93QsHcCImRm8dVFVWy+WLWle/1i3Ol9EfiS1Us9Tau8QDjvtm3iuzi8IPm/nfVluA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-thread-comment/-/sheets-thread-comment-0.20.0.tgz", - "integrity": "sha512-SczsVh6O+ueNKScRRzFpslMJiTTowJWdaKgK51pYspNx47GvPwhLRVBW267jds0VTzVSlG24oC+JxKFFHgwgxw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-thread-comment-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-thread-comment-ui/-/sheets-thread-comment-ui-0.20.0.tgz", - "integrity": "sha512-aqB/UPJFxyJLd7FUAJbAFIP23FBBXwuMiHTjIC5UX9IS0J54NhJRgxoiAAxMXDf7nspUKo1/hI2oLyfvpD8zLQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-thread-comment": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-ui/-/sheets-ui-0.20.0.tgz", - "integrity": "sha512-HdLBYjJQXq5QhAlpW1srUe1K9TuHFh4t+LqknFb1Ep2OQ6naHbb38bIXOPDktOQrmtwuWGyHNGJxbwC7nfGDoA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/telemetry": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/slides": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/slides/-/slides-0.20.0.tgz", - "integrity": "sha512-afm3MGu/sPLIP7XTHjWzWjTUs4OLjFCM89kjyUsEU67ZnZuhpoCuUd6sB+vDtA91ZHDs45d8YCyJn79eGXWfDA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/slides-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/slides-ui/-/slides-ui-0.20.0.tgz", - "integrity": "sha512-4ffb/HvnGSwsJTHt/aRC2Jg5nJamvFGsDYeiSzLwiTAS2WxIGlNYvA7BI/H0mJoOhHS8WoLleaYznxXqTVpyIA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/slides": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/telemetry": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/telemetry/-/telemetry-0.20.0.tgz", - "integrity": "sha512-zDwdeBl2vDnQwmbPSrMfNbef8fgzHVbnhSEItl5thvQQ7KIZr0sR7XeRXlI9VJGOGHg1WruJQ5zDdWeNQhP+Ig==", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/themes": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/themes/-/themes-0.20.0.tgz", - "integrity": "sha512-dJ9m3mY1uhyhvozK6m0qsnRF406cIAMBOAh4qBo7N1h4G9Ff9TnUTN/ebF1AQmwOoAcJkxmDeiD0Ordt1KLA8g==", - "license": "Apache-2.0", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/thread-comment/-/thread-comment-0.20.0.tgz", - "integrity": "sha512-F4iARrow5esrb3uog1IPSKwb1WaZCCBTunNpgGKxhic4LClyZW3tx9oO0QdUaaQWBCNiY++J2jBs/ktTD+rdmg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/thread-comment-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/thread-comment-ui/-/thread-comment-ui-0.20.0.tgz", - "integrity": "sha512-wBGDtRXcHCW+7hN/bSnxtLggWVkTjKt245G0FUCcWzMQV2+H7Lnlt8zpsGE4kfILIbZHjzefuPpX4qlfZcwBSQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/ui/-/ui-0.20.0.tgz", - "integrity": "sha512-XKxcH3hsYAOhL9wswyMglrHDRzCweUFetzwdlulaaztgwp5mKC7qNp1qgy6HUBb9OaB1yZYa8raBg7dN49DVvQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@wendellhu/redi": "1.1.1", - "localforage": "^1.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, "node_modules/@upsetjs/venn.js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", @@ -6210,20 +3246,6 @@ "vue-component-meta": "3.2.6" } }, - "node_modules/@wendellhu/redi": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@wendellhu/redi/-/redi-1.1.1.tgz", - "integrity": "sha512-y2fuAgHJ2n8sI8Pe/1QtAuPQ6ZbZ9/Dn3uVQI8cctVqLZzp/0OpLM7DSMOU6vmGYXNsIQwsquR91WcxZ4jrRvA==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -6465,18 +3487,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", @@ -6604,12 +3614,6 @@ "license": "MIT", "optional": true }, - "node_modules/async-lock": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", - "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7307,19 +4311,6 @@ "node": ">=10" } }, - "node_modules/cjk-regex": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cjk-regex/-/cjk-regex-3.4.0.tgz", - "integrity": "sha512-m+gbmlIP6gAG7tDvo2kpeSPAz/uh5wY5/zx10ymjdpbbiTHNTNoYnP2lCiyqtmbLxwhEdq8/lsVbsy4GTc9oUw==", - "license": "MIT", - "dependencies": { - "regexp-util": "^2.0.3", - "unicode-regex": "^4.2.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -7373,18 +4364,6 @@ "node": ">=0.10.0" } }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, "node_modules/cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -7421,16 +4400,6 @@ "@codemirror/view": "^6.0.0" } }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -7651,12 +4620,6 @@ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "license": "MIT" }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "license": "MIT" - }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -8260,12 +5223,6 @@ "node": ">=0.10.0" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, "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", @@ -8428,12 +5385,6 @@ "node": ">=8" } }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, "node_modules/detective": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", @@ -8506,16 +5457,6 @@ "adm-zip": "^0.5.16" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -8890,12 +5831,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "license": "Apache-2.0" - }, "node_modules/fast-equals": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", @@ -9049,19 +5984,6 @@ "node": ">=0.10.0" } }, - "node_modules/franc-min": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/franc-min/-/franc-min-6.2.0.tgz", - "integrity": "sha512-1uDIEUSlUZgvJa2AKYR/dmJC66v/PvGQ9mWfI9nOr/kPpMFyvswK0gPXOwpYJYiYD008PpHLkGfG58SPjQJFxw==", - "license": "MIT", - "dependencies": { - "trigram-utils": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -9201,15 +6123,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -10517,12 +7430,6 @@ "katex": "cli.js" } }, - "node_modules/kdbush": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", - "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", - "license": "ISC" - }, "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", @@ -10659,24 +7566,6 @@ "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", "license": "MIT" }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "license": "Apache-2.0", - "dependencies": { - "lie": "3.1.1" - } - }, - "node_modules/localforage/node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", @@ -10723,24 +7612,6 @@ "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==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loose-envify/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==", - "license": "MIT" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -11953,16 +8824,6 @@ "dev": true, "license": "MIT" }, - "node_modules/n-gram": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", - "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/nan": { "version": "2.26.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", @@ -12130,12 +8991,6 @@ "set-blocking": "^2.0.0" } }, - "node_modules/numfmt": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/numfmt/-/numfmt-3.2.6.tgz", - "integrity": "sha512-MXc2KP3j+2usdHTY5/ENUc2S+3BRF/cJqnR6RHeq6LBqKoIZOAQ62DQw974nnaZOencbfkmkTPyTmMnlkCpjzg==", - "license": "MIT" - }, "node_modules/nwsapi": { "version": "2.2.23", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", @@ -12157,6 +9012,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12274,22 +9130,6 @@ "wrappy": "1" } }, - "node_modules/opentype.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", - "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", - "license": "MIT", - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -12337,24 +9177,6 @@ "node": ">=0.10.0" } }, - "node_modules/ot-json1": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ot-json1/-/ot-json1-1.0.2.tgz", - "integrity": "sha512-IhxkqVWQqlkWULoi/Q2AdzKk0N5vQRbUMUwubFXFCPcY4TsOZjmp2YKrk0/z1TeiECPadWEK060sdFdQ3Grokg==", - "license": "ISC", - "dependencies": { - "ot-text-unicode": "4" - } - }, - "node_modules/ot-text-unicode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", - "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", - "license": "ISC", - "dependencies": { - "unicount": "1.1" - } - }, "node_modules/output-file-sync": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", @@ -12657,17 +9479,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "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==", - "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", @@ -13128,12 +9939,6 @@ "node": ">=0.6" } }, - "node_modules/quickselect": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", - "license": "ISC" - }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -13179,15 +9984,6 @@ "node": ">=0.10.0" } }, - "node_modules/rbush": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz", - "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", - "license": "MIT", - "dependencies": { - "quickselect": "^3.0.0" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -13220,97 +10016,6 @@ "react": "*" } }, - "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==", - "license": "MIT" - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -13679,15 +10384,6 @@ "node": ">=0.10.0" } }, - "node_modules/regexp-util": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/regexp-util/-/regexp-util-2.0.3.tgz", - "integrity": "sha512-GP6h9OgJmhAZpb3dbNbXTfRWVnGcoMhWRZv/HxgM4/qCVqs1P9ukQdYxaUhjWBSAs9oJ/uPXUUvGT1VMe0Bs0Q==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/regexpu": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz", @@ -14281,16 +10977,6 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -14581,16 +11267,6 @@ "node": ">= 0.4" } }, - "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", - "license": "MIT", - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -14819,12 +11495,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", - "license": "MIT" - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -14924,16 +11594,6 @@ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, - "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -14982,12 +11642,6 @@ "license": "MIT", "optional": true }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" - }, "node_modules/tinyexec": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", @@ -15112,20 +11766,6 @@ "punycode": "^2.1.0" } }, - "node_modules/trigram-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/trigram-utils/-/trigram-utils-2.0.1.tgz", - "integrity": "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==", - "license": "MIT", - "dependencies": { - "collapse-white-space": "^2.0.0", - "n-gram": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -15190,12 +11830,6 @@ "node": ">=6.10" } }, - "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/tui-code-snippet": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tui-code-snippet/-/tui-code-snippet-2.3.3.tgz", @@ -15295,24 +11929,6 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, - "node_modules/unicode-regex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unicode-regex/-/unicode-regex-4.2.0.tgz", - "integrity": "sha512-fEYz7CCnvHDAdrb8OYAP7qlQCWzXBO5cHXQ3XI+HoZaBpiAwyC6b2nixMGl91yrDYEIRm7NDskgTvnLZ7mqrKQ==", - "license": "MIT", - "dependencies": { - "regexp-util": "^2.0.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/unicount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.1.0.tgz", - "integrity": "sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==", - "license": "ISC" - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -15534,49 +12150,6 @@ "node": ">=0.10.0" } }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "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", diff --git a/package.json b/package.json index d35a6b8..d4cf2b1 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,6 @@ "@milkdown/kit": "^7.18.0", "@milkdown/theme-nord": "^7.18.0", "@milkdown/vue": "^7.18.0", - "@univerjs/preset-docs-core": "^0.20.0", - "@univerjs/preset-sheets-core": "^0.20.0", - "@univerjs/presets": "^0.20.0", - "@univerjs/slides": "^0.20.0", - "@univerjs/slides-ui": "^0.20.0", "docx": "^9.6.0", "docx-preview": "^0.3.7", "docx2pdf-converter": "^2.1.1", diff --git a/src/AGENTS.md b/src/AGENTS.md index c3b83c2..a7f56f9 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -15,8 +15,8 @@ - 路由:router/index.js - 编辑页:views/EditorView.vue - 文档页:views/DocsView.vue -- Univer 页:views/UniverView.vue - 编辑器主组件:components/MilkdownEditor.vue +- 文件预览:components/FileContent.vue、components/OfficePreview.vue - 设置面板:components/SettingsPanel.vue - TTS 组件:components/TTSMenu.vue、components/TTSPlayer.vue - 插件层:plugins/ @@ -31,7 +31,6 @@ - / -> EditorView - /docs -> DocsView -- /univer -> UniverView ## 核心行为 diff --git a/src/components/ContextMenu.vue b/src/components/ContextMenu.vue index 81e58cc..8d301ad 100644 --- a/src/components/ContextMenu.vue +++ b/src/components/ContextMenu.vue @@ -1,5 +1,5 @@ diff --git a/src/components/DocBlockCrepe.vue b/src/components/DocBlockCrepe.vue index c3ecfcd..3d701c0 100644 --- a/src/components/DocBlockCrepe.vue +++ b/src/components/DocBlockCrepe.vue @@ -38,6 +38,7 @@ import { replaceAll } from '@milkdown/kit/utils' import { Crepe } from '@milkdown/crepe' import { editorViewCtx } from '@milkdown/kit/core' import { copilotPlugin, copilotConfigCtx, copilotGhostMark, setCopilotEnabled, clearGhostSuggestion } from '../plugins/copilotPlugin' +import { hiddenTextInputPlugin, hiddenTextNode, hiddenTextRemark, hiddenTextView } from '../plugins/hiddenTextPlugin' import { fetchSuggestion } from '../utils/api.js' const props = defineProps({ @@ -145,6 +146,10 @@ onMounted(async () => { crepe.editor.use(copilotConfigCtx) crepe.editor.use(copilotGhostMark) crepe.editor.use(copilotPlugin) + crepe.editor.use(hiddenTextRemark) + crepe.editor.use(hiddenTextNode) + crepe.editor.use(hiddenTextView) + crepe.editor.use(hiddenTextInputPlugin) await crepe.create() crepe.on((listener) => { diff --git a/src/components/FileContent.vue b/src/components/FileContent.vue index 816fcd6..a0d9a29 100644 --- a/src/components/FileContent.vue +++ b/src/components/FileContent.vue @@ -1,9 +1,11 @@ + + \ No newline at end of file diff --git a/src/components/MarkdownPreview.vue b/src/components/MarkdownPreview.vue index fbaa1b5..8703eba 100644 --- a/src/components/MarkdownPreview.vue +++ b/src/components/MarkdownPreview.vue @@ -7,6 +7,7 @@ import { computed } from 'vue' import MarkdownIt from 'markdown-it' import katex from 'katex' import 'katex/dist/katex.min.css' +import { hiddenTextMarkdownItPlugin } from '../utils/hiddenText.js' const props = defineProps({ content: { @@ -21,6 +22,8 @@ const md = new MarkdownIt({ typographer: true }) +md.use(hiddenTextMarkdownItPlugin) + // 预处理 markdown,转换 $...$ 为 ... const preprocessLatex = (text) => { // 处理 $$...$$ 块级公式 diff --git a/src/components/MilkdownEditor.vue b/src/components/MilkdownEditor.vue index 28dd24d..2930a3a 100644 --- a/src/components/MilkdownEditor.vue +++ b/src/components/MilkdownEditor.vue @@ -111,7 +111,7 @@ {{ aiButtonLabel }} - +
+
+ +
+
+
{{ t('presetTemplates') }}
+ +
+ +
+
{{ t('customTemplates') }}
+

{{ t('noTemplates') }}

+ +
+ + +
+
+ +
+ + + + +
+ @@ -1349,6 +1726,10 @@ for (const url of Array.from(objectUrls)) { z-index: 99999; } +.template-btn-wrapper { + position: relative; +} + .action-btn { width: 44px; height: 44px; @@ -1574,6 +1955,225 @@ for (const url of Array.from(objectUrls)) { background: var(--crepe-color-hover); } +.template-dropdown { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 260px; + max-width: min(360px, calc(100vw - 40px)); + background: var(--panel-bg); + border: 1px solid var(--panel-border); + border-radius: 12px; + box-shadow: var(--panel-shadow); + overflow: hidden; + z-index: 100000; + display: flex; + flex-direction: column; +} + +.template-dropdown-section { + padding: 8px 0; +} + +.template-dropdown-title { + padding: 0 16px 8px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted-text); +} + +.template-dropdown-item { + display: flex; + width: 100%; + padding: 10px 16px; + border: none; + background: none; + text-align: left; + cursor: pointer; + font-size: 14px; + color: var(--app-text); +} + +.template-dropdown-item:hover { + background: var(--crepe-color-hover); +} + +.template-dropdown-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.template-dropdown-empty { + margin: 0; + padding: 0 16px 8px; + font-size: 12px; + color: var(--muted-text); +} + +.template-dropdown-footer { + padding: 10px 16px 14px; + border-top: 1px solid var(--panel-border); +} + +.template-dropdown-action { + width: 100%; + padding: 10px 12px; + border: 1px dashed var(--panel-border); + border-radius: 10px; + background: transparent; + color: var(--app-text); + cursor: pointer; +} + +.template-dropdown-action:hover { + background: var(--crepe-color-hover); + border-color: var(--btn-hover-bg); +} + +.template-dialog-overlay { + position: fixed; + inset: 0; + background: var(--overlay-bg); + display: flex; + align-items: center; + justify-content: center; + z-index: 100001; + padding: 20px; +} + +.template-dialog { + width: min(720px, calc(100vw - 40px)); + max-height: calc(100vh - 40px); + overflow: auto; + background: var(--panel-bg); + border: 1px solid var(--panel-border); + border-radius: 16px; + box-shadow: var(--panel-shadow); + padding: 20px; +} + +.template-dialog-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 12px; +} + +.template-dialog-kicker { + margin: 0 0 4px; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted-text); +} + +.template-dialog-header h3 { + margin: 0; + font-size: 18px; + color: var(--app-text); +} + +.template-dialog-close { + width: 32px; + height: 32px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--muted-text); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.template-dialog-close:hover { + background: var(--crepe-color-hover); + color: var(--app-text); +} + +.template-dialog-meta { + margin: 0 0 12px; + font-size: 13px; + color: var(--muted-text); +} + +.template-preview-textarea { + width: 100%; + min-height: 300px; + box-sizing: border-box; + padding: 14px 16px; + border: 1px solid var(--panel-border); + border-radius: 12px; + background: var(--crepe-color-background); + color: var(--app-text); + font-family: inherit; + line-height: 1.6; + resize: vertical; +} + +.template-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.template-form-label { + font-size: 13px; + color: var(--muted-text); +} + +.template-form-input, +.template-form-textarea { + width: 100%; + box-sizing: border-box; + padding: 12px 14px; + border: 1px solid var(--panel-border); + border-radius: 12px; + background: var(--crepe-color-background); + color: var(--app-text); + font-size: 14px; +} + +.template-form-textarea { + min-height: 300px; + resize: vertical; +} + +.template-form-input:focus, +.template-form-textarea:focus { + outline: none; + border-color: var(--focus-ring); +} + +.template-dialog-error { + margin: 0; + font-size: 13px; + color: var(--danger-text); +} + +.template-dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.dialog-btn.danger { + background: rgba(220, 38, 38, 0.08); + color: var(--danger-text); + border-color: rgba(220, 38, 38, 0.2); +} + +.dialog-btn.danger:hover { + background: rgba(220, 38, 38, 0.14); +} + .url-dialog-overlay { position: fixed; top: 0; @@ -1793,6 +2393,26 @@ for (const url of Array.from(objectUrls)) { background-color: var(--ghost-code-bg); } +.pro-block-accepted-highlight { + background: rgba(59, 130, 246, 0.16); + animation: pro-accept-fade 1s ease forwards; +} + +.pro-block-accepted-block { + background: linear-gradient(180deg, rgba(219, 234, 254, 0.72) 0%, rgba(239, 246, 255, 0.22) 100%); + border-radius: 14px; + animation: pro-accept-fade 1s ease forwards; +} + +@keyframes pro-accept-fade { + from { + background-color: rgba(59, 130, 246, 0.18); + } + to { + background-color: transparent; + } +} + .upload-progress-overlay { position: fixed; top: 0; diff --git a/src/components/OfficePreview.vue b/src/components/OfficePreview.vue new file mode 100644 index 0000000..0a85c1c --- /dev/null +++ b/src/components/OfficePreview.vue @@ -0,0 +1,245 @@ + + + + + \ No newline at end of file diff --git a/src/components/ProBlockCrepe.vue b/src/components/ProBlockCrepe.vue new file mode 100644 index 0000000..d9ad5a7 --- /dev/null +++ b/src/components/ProBlockCrepe.vue @@ -0,0 +1,334 @@ + + + + + \ No newline at end of file diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue index 3829ecc..d2318e7 100644 --- a/src/components/SettingsPanel.vue +++ b/src/components/SettingsPanel.vue @@ -237,6 +237,21 @@ const switchView = (view) => { +
+

{{ t('proMode') || 'PRO 模式' }}

+ +
+ + +

{{ t('proModelDesc') }} {{ t('proModelEmptyHint') || '留空则使用后端默认 PRO 模型。' }}

+
+
+

{{ t('modelIntelligence') }}

diff --git a/src/components/UniverEditor.vue b/src/components/UniverEditor.vue deleted file mode 100644 index 505b2c8..0000000 --- a/src/components/UniverEditor.vue +++ /dev/null @@ -1,206 +0,0 @@ - - - - - diff --git a/src/components/UniverPreview.vue b/src/components/UniverPreview.vue deleted file mode 100644 index 9d6b0d0..0000000 --- a/src/components/UniverPreview.vue +++ /dev/null @@ -1,407 +0,0 @@ - - - - - diff --git a/src/components/UploadBlockCrepe.vue b/src/components/UploadBlockCrepe.vue new file mode 100644 index 0000000..367382c --- /dev/null +++ b/src/components/UploadBlockCrepe.vue @@ -0,0 +1,240 @@ + + + + + \ No newline at end of file diff --git a/src/main.js b/src/main.js index 7974ede..e9ff1ad 100644 --- a/src/main.js +++ b/src/main.js @@ -11,11 +11,3 @@ const app = createApp(App) app.use(createPinia()) app.use(router) app.mount('#app') - -if (import.meta.env.PROD && 'serviceWorker' in navigator && false) { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/sw.js').catch(() => { - // Service worker registration failed, silently ignore - }) - }) -} diff --git a/src/plugins/hiddenTextPlugin.ts b/src/plugins/hiddenTextPlugin.ts new file mode 100644 index 0000000..f5adfd3 --- /dev/null +++ b/src/plugins/hiddenTextPlugin.ts @@ -0,0 +1,246 @@ +import { createApp, h, reactive } from 'vue' +import { Plugin, PluginKey } from '@milkdown/prose/state' +import { $node, $prose, $remark, $view } from '@milkdown/kit/utils' +import type { Node as ProseNode } from '@milkdown/prose/model' +import type { EditorView, NodeView } from '@milkdown/prose/view' +import HiddenTextCrepe from '../components/HiddenTextCrepe.vue' +import { HIDDEN_TEXT_NODE_TYPE, parseHiddenTextAt, serializeHiddenTextSyntax, splitTextWithHiddenSyntax } from '../utils/hiddenText.js' + +const HIDDEN_TEXT_INPUT_PLUGIN_KEY = new PluginKey('milkdown-hidden-text-input') +const HIDDEN_TEXT_INPUT_META = 'hidden-text-input-meta' + +function transformHiddenTextChildren(node: any) { + if (!node || !Array.isArray(node.children)) return + + const nextChildren: any[] = [] + + for (const child of node.children) { + if (child?.type === 'text' && typeof child.value === 'string') { + const segments = splitTextWithHiddenSyntax(child.value) + if (segments) { + nextChildren.push(...segments) + continue + } + } + + transformHiddenTextChildren(child) + nextChildren.push(child) + } + + node.children = nextChildren +} + +function findHiddenTextReplacements(doc: ProseNode) { + const replacements: Array<{ from: number; to: number; displayed: string; hidden: string; marks: ProseNode['marks'] }> = [] + + doc.descendants((node, pos, parent) => { + if (!node.isText || !node.text) return true + if (parent?.type.spec.code) return true + if (node.marks.some((mark) => mark.type.spec.code)) return true + + let index = 0 + while (index < node.text.length) { + const match = parseHiddenTextAt(node.text, index) + if (!match) { + index += 1 + continue + } + + replacements.push({ + from: pos + match.start, + to: pos + match.end, + displayed: match.displayed, + hidden: match.hidden, + marks: node.marks, + }) + + index = match.end + } + + return true + }) + + return replacements +} + +class HiddenTextNodeView implements NodeView { + node: ProseNode + view: EditorView + getPos: (() => number) | boolean + dom: HTMLElement + app: ReturnType | null = null + props: Record + + constructor(node: ProseNode, view: EditorView, getPos: (() => number) | boolean) { + this.node = node + this.view = view + this.getPos = getPos + this.dom = document.createElement('span') + this.dom.className = 'hidden-text-node-view' + this.dom.setAttribute('contenteditable', 'false') + this.props = reactive({ + displayed: node.attrs.displayed, + hidden: node.attrs.hidden, + expanded: node.attrs.expanded, + updateTexts: ({ displayed, hidden }: { displayed: string; hidden: string }) => { + this.updateAttrs({ displayed, hidden }) + }, + updateExpanded: (expanded: boolean) => this.updateAttrs({ expanded }), + }) + this.mount() + } + + mount() { + this.app = createApp({ + render: () => h(HiddenTextCrepe, this.props), + }) + this.app.mount(this.dom) + } + + getPosValue() { + if (typeof this.getPos === 'function') { + try { + const pos = this.getPos() + if (typeof pos === 'number') return pos + } catch { + // Fall back to DOM-based resolution below. + } + } + + try { + const pos = this.view.posAtDOM(this.dom, 0) + return typeof pos === 'number' ? pos : undefined + } catch { + return undefined + } + } + + updateAttrs(patch: Record) { + const pos = this.getPosValue() + if (pos === undefined) return + const nextAttrs = { ...this.node.attrs, ...patch } + const nextNode = this.node.type.create(nextAttrs, undefined, this.node.marks) + this.view.dispatch(this.view.state.tr.replaceWith(pos, pos + this.node.nodeSize, nextNode)) + } + + update(node: ProseNode) { + if (node.type !== this.node.type) return false + this.node = node + this.props.displayed = node.attrs.displayed + this.props.hidden = node.attrs.hidden + this.props.expanded = node.attrs.expanded + return true + } + + stopEvent(event: Event) { + const target = event.target as Node | null + return Boolean(target && this.dom.contains(target)) + } + + ignoreMutation() { + return true + } + + destroy() { + this.app?.unmount() + this.app = null + } +} + +export const hiddenTextRemark = $remark('hiddenTextRemark', () => () => { + return (tree: any) => { + transformHiddenTextChildren(tree) + } +}) + +export const hiddenTextNode = $node(HIDDEN_TEXT_NODE_TYPE, () => ({ + group: 'inline', + inline: true, + atom: true, + selectable: true, + draggable: false, + attrs: { + displayed: { default: '' }, + hidden: { default: '' }, + expanded: { default: false }, + }, + parseDOM: [ + { + tag: 'span[data-hidden-text="true"]', + getAttrs: (dom) => ({ + displayed: (dom as HTMLElement).getAttribute('data-hidden-display') || '', + hidden: (dom as HTMLElement).getAttribute('data-hidden-value') || '', + expanded: ((dom as HTMLElement).getAttribute('data-hidden-expanded') || '') === 'true', + }), + }, + ], + toDOM: (node) => [ + 'span', + { + 'data-hidden-text': 'true', + 'data-hidden-display': node.attrs.displayed, + 'data-hidden-value': node.attrs.hidden, + 'data-hidden-expanded': String(Boolean(node.attrs.expanded)), + }, + ], + parseMarkdown: { + match: (node) => node.type === HIDDEN_TEXT_NODE_TYPE, + runner: (state, node, type) => { + state.addNode(type, { + displayed: String(node.displayed || ''), + hidden: String(node.hidden || ''), + expanded: false, + }) + }, + }, + toMarkdown: { + match: (node) => node.type.name === HIDDEN_TEXT_NODE_TYPE, + runner: (state, node) => { + state.addNode('text', undefined, serializeHiddenTextSyntax(node.attrs.displayed, node.attrs.hidden)) + }, + }, + leafText: (node) => serializeHiddenTextSyntax(node.attrs.displayed, node.attrs.hidden), +})) + +export const hiddenTextView = $view(hiddenTextNode, () => { + return (node, view, getPos) => new HiddenTextNodeView(node, view, getPos) +}) + +export const hiddenTextInputPlugin = $prose(() => { + return new Plugin({ + key: HIDDEN_TEXT_INPUT_PLUGIN_KEY, + appendTransaction: (transactions, _oldState, newState) => { + if (!transactions.some((transaction) => transaction.docChanged)) return null + if (transactions.some((transaction) => transaction.getMeta(HIDDEN_TEXT_INPUT_META))) return null + + const hiddenTextType = newState.schema.nodes[HIDDEN_TEXT_NODE_TYPE] + if (!hiddenTextType) return null + + const replacements = findHiddenTextReplacements(newState.doc) + if (replacements.length === 0) return null + + let tr = newState.tr + + for (let index = replacements.length - 1; index >= 0; index -= 1) { + const replacement = replacements[index] + tr = tr.replaceWith( + replacement.from, + replacement.to, + hiddenTextType.create( + { + displayed: replacement.displayed, + hidden: replacement.hidden, + expanded: false, + }, + undefined, + replacement.marks + ) + ) + } + + if (!tr.docChanged) return null + tr.setMeta(HIDDEN_TEXT_INPUT_META, true) + return tr + }, + }) +}) \ No newline at end of file diff --git a/src/plugins/mermaidPlugin.ts b/src/plugins/mermaidPlugin.ts index 5a46679..61fd3e5 100644 --- a/src/plugins/mermaidPlugin.ts +++ b/src/plugins/mermaidPlugin.ts @@ -373,8 +373,8 @@ async function renderMermaidBlock(block: HTMLElement, token: number): Promise { const block = document.querySelector(targetSelector) @@ -382,13 +382,9 @@ function scheduleMermaidRender(token: number) { void renderMermaidBlock(block, token) return } - if (attempt >= maxAttempts) return - - if (typeof window.requestAnimationFrame === 'function') { - window.requestAnimationFrame(() => run(attempt + 1)) - } else { - window.setTimeout(() => run(attempt + 1), 16) - } + const nextDelay = retryDelays[attempt] + if (nextDelay === undefined) return + window.setTimeout(() => run(attempt + 1), nextDelay) } run(0) diff --git a/src/plugins/proBlockPlugin.ts b/src/plugins/proBlockPlugin.ts new file mode 100644 index 0000000..1512887 --- /dev/null +++ b/src/plugins/proBlockPlugin.ts @@ -0,0 +1,665 @@ +import { createApp, h, reactive } from 'vue' +import { parserCtx, serializerCtx } from '@milkdown/kit/core' +import { $ctx, $node, $prose, $remark, $view } from '@milkdown/kit/utils' +import { Plugin, PluginKey, Selection } from '@milkdown/prose/state' +import { type Node as ProseNode, Slice, type Schema } from '@milkdown/prose/model' +import { Decoration, DecorationSet, type EditorView, type NodeView } from '@milkdown/prose/view' +import ProBlockCrepe from '../components/ProBlockCrepe.vue' +import { extractDocBlockContextFromMarkdown } from '../utils/docBlock.js' +import { extractTextFromOCR, getOcrCache } from '../utils/ocrCache' +import { PRO_BLOCK_NODE_TYPE, PRO_DISPLAY_LABEL, PRO_TRIGGER_TEXT, parseProBlockSyntax, serializeProBlockSyntax } from '../utils/proBlock.js' + +const PRO_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-pro-block-input') +const PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY = new PluginKey('milkdown-pro-block-highlight') +const PRO_BLOCK_INPUT_META = 'pro-block-input-meta' +const PRO_CONTEXT_LIMIT = 32 * 1024 +const FALLBACK_BLOCK_SEPARATOR = '\n\n' +const FALLBACK_LEAF_TEXT = '\n' +const IMAGE_NODE_TYPES = new Set(['image', 'image-block', 'imageBlock']) + +interface ProBlockConfig { + fetchSuggestionStream: (payload: { + prefix: string + suffix: string + languageId: string + signal?: AbortSignal + model?: string + temperature?: number + onChunk?: (chunk: string) => void + }) => Promise + getProModel: () => string + showError: (message: string) => void + isThinkingActive: () => boolean + setThinkingActive: (value: boolean) => void +} + +interface ProRequestPayload { + prefix: string + suffix: string + languageId: string + blocked: boolean +} + +function serializeRangeToMarkdown( + doc: ProseNode, + from: number, + to: number, + schema: Schema, + serializer: (content: ProseNode) => string +): string { + if (from >= to) return '' + const fallback = doc.textBetween(from, to, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT) + if (typeof serializer !== 'function') return fallback + + const slice = doc.slice(from, to) + if (slice.content.size <= 0) return '' + + try { + const sliceDoc = schema.topNodeType.createAndFill(undefined, slice.content) + return sliceDoc ? serializer(sliceDoc) : fallback + } catch { + return fallback + } +} + +function transformProBlockChildren(node: any) { + if (!node || !Array.isArray(node.children)) return + + node.children = node.children.map((child: any) => { + const replacement = parseProParagraphNode(child) + if (replacement) return replacement + transformProBlockChildren(child) + return child + }) +} + +function parseProParagraphNode(node: any) { + if (!node || node.type !== 'paragraph' || !Array.isArray(node.children)) return null + if (node.children.length !== 1) return null + + const textNode = node.children[0] + if (!textNode || textNode.type !== 'text' || typeof textNode.value !== 'string') return null + + const parsed = parseProBlockSyntax(textNode.value) + if (!parsed) return null + + return { + type: PRO_BLOCK_NODE_TYPE, + content: parsed.content, + autoStart: false, + } +} + +function findProBlockReplacements(doc: ProseNode) { + const replacements: Array<{ from: number; to: number; content: string; autoStart: boolean }> = [] + + doc.descendants((node, pos) => { + if (node.type.name !== 'paragraph' || node.childCount !== 1) return true + + const firstChild = node.firstChild + if (!firstChild?.isText) return true + + const parsed = parseProBlockSyntax(node.textContent) + if (!parsed) return true + + replacements.push({ + from: pos, + to: pos + node.nodeSize, + content: parsed.content, + autoStart: parsed.autoStart, + }) + + return false + }) + + return replacements +} + +function buildOcrContext(doc: ProseNode) { + const lines: string[] = [] + + doc.descendants((node) => { + if (!IMAGE_NODE_TYPES.has(node.type.name)) return true + const src = typeof node.attrs?.src === 'string' ? node.attrs.src : '' + if (!src) return true + const ocrText = getOcrCache(src) + if (!ocrText) return true + + const preview = extractTextFromOCR(ocrText, 120) + if (!preview) return true + + const label = typeof node.attrs?.alt === 'string' && node.attrs.alt.trim() ? node.attrs.alt.trim() : 'image' + lines.push(`![${label}](${src}) `) + return true + }) + + return lines.join('\n') +} + +function createHighlightDecorations(doc: ProseNode, from: number, to: number) { + if (to <= from) return DecorationSet.empty + + const decorations = [ + Decoration.inline(from, to, { class: 'pro-block-accepted-highlight' }), + ] + + doc.nodesBetween(from, to, (node, pos) => { + if (!node.isBlock) return true + const nodeFrom = pos + const nodeTo = pos + node.nodeSize + if (nodeFrom >= from && nodeTo <= to) { + decorations.push(Decoration.node(nodeFrom, nodeTo, { class: 'pro-block-accepted-block' })) + } + return true + }) + + return DecorationSet.create(doc, decorations) +} + +function replaceWithParsedMarkdownSlice(tr: any, from: number, to: number, parsedDoc: ProseNode) { + if (!parsedDoc || parsedDoc.content.size <= 0) return null + + const parsedSlice = Slice.maxOpen(parsedDoc.content) + if (!parsedSlice || parsedSlice.size <= 0) return null + + tr.replaceRange(from, to, parsedSlice) + const startPos = tr.mapping.map(from, -1) + const endPos = tr.mapping.map(to, 1) + if (endPos <= startPos) return null + return { from: startPos, to: endPos } +} + +function insertProBlockNode(view: EditorView, autoStart: boolean) { + const proBlockType = view.state.schema.nodes[PRO_BLOCK_NODE_TYPE] + if (!proBlockType) return false + + const { from, to } = view.state.selection + const blockNode = proBlockType.create({ content: '', autoStart }) + const tr = view.state.tr.replaceRangeWith(from, to, blockNode) + const nextPos = Math.min(from + blockNode.nodeSize, tr.doc.content.size) + tr.setSelection(Selection.near(tr.doc.resolve(nextPos), 1)) + view.dispatch(tr.scrollIntoView()) + view.focus() + return true +} + +function replaceParagraphWithProBlock( + view: EditorView, + from: number, + to: number, + content: string, + autoStart: boolean +) { + const proBlockType = view.state.schema.nodes[PRO_BLOCK_NODE_TYPE] + if (!proBlockType) return false + + const blockNode = proBlockType.create({ + content, + autoStart: autoStart && content === '', + }) + const tr = view.state.tr.replaceWith(from, to, blockNode) + const nextPos = Math.min(from + blockNode.nodeSize, tr.doc.content.size) + tr.setMeta(PRO_BLOCK_INPUT_META, true) + tr.setSelection(Selection.near(tr.doc.resolve(nextPos), 1)) + view.dispatch(tr.scrollIntoView()) + view.focus() + return true +} + +function tryHandleProTriggerTextInput(view: EditorView, insertedText: string) { + if (!insertedText || !insertedText.includes(']')) return false + + const { state } = view + const { $from, from, to } = state.selection + const paragraph = $from.parent + if (!paragraph || paragraph.type.name !== 'paragraph') return false + if (!$from.sameParent(state.selection.$to)) return false + + const paragraphDepth = $from.depth + const paragraphStart = $from.start(paragraphDepth) + const startOffset = from - paragraphStart + const endOffset = to - paragraphStart + const nextText = `${paragraph.textContent.slice(0, startOffset)}${insertedText}${paragraph.textContent.slice(endOffset)}` + const parsed = parseProBlockSyntax(nextText) + if (!parsed?.autoStart || parsed.content) return false + + const blockFrom = $from.before(paragraphDepth) + const blockTo = blockFrom + paragraph.nodeSize + return replaceParagraphWithProBlock(view, blockFrom, blockTo, parsed.content, parsed.autoStart) +} + +function isAbortError(error: unknown) { + return Boolean(error && typeof error === 'object' && 'name' in error && (error as { name?: string }).name === 'AbortError') +} + +export const proBlockConfigCtx = $ctx({ + fetchSuggestionStream: async () => '', + getProModel: () => '', + showError: () => {}, + isThinkingActive: () => false, + setThinkingActive: () => {}, +}, 'proBlockConfig') + +class ProBlockNodeView implements NodeView { + node: ProseNode + view: EditorView + getPos: (() => number) | boolean + dom: HTMLElement + app: ReturnType | null = null + props: Record + parser: (markdown: string) => Promise + serializer: (content: ProseNode) => string + config: ProBlockConfig + abortController: AbortController | null = null + requestSeq = 0 + redoCount = 0 + destroyed = false + highlightTimer: ReturnType | null = null + + constructor( + node: ProseNode, + view: EditorView, + getPos: (() => number) | boolean, + parser: (markdown: string) => Promise, + serializer: (content: ProseNode) => string, + config: ProBlockConfig + ) { + this.node = node + this.view = view + this.getPos = getPos + this.parser = parser + this.serializer = serializer + this.config = config + this.dom = document.createElement('div') + this.dom.className = 'pro-block-node-view' + this.props = reactive({ + stage: node.attrs.content ? 'done' : 'idle', + previewContent: '', + savedContent: node.attrs.content || '', + isBusy: false, + title: PRO_DISPLAY_LABEL, + activateAction: () => { + void this.startThinking(false) + }, + discardAction: () => { + this.discardResult() + }, + redoAction: () => { + void this.startThinking(true) + }, + acceptAction: () => { + void this.acceptResult() + }, + }) + this.mount() + queueMicrotask(() => { + if (!this.destroyed) { + this.consumeAutoStart() + } + }) + } + + mount() { + this.app = createApp({ + render: () => h(ProBlockCrepe, { ...this.props }), + }) + this.app.mount(this.dom) + } + + getPosValue() { + if (typeof this.getPos === 'function') { + try { + const pos = this.getPos() + if (typeof pos === 'number') return pos + } catch { + // Ignore and fall back to DOM lookup. + } + } + + try { + const pos = this.view.posAtDOM(this.dom, 0) + return typeof pos === 'number' ? pos : undefined + } catch { + return undefined + } + } + + updateAttrs(patch: Record) { + const pos = this.getPosValue() + if (pos === undefined) return + const nextAttrs = { ...this.node.attrs, ...patch } + this.view.dispatch(this.view.state.tr.setNodeMarkup(pos, undefined, nextAttrs)) + } + + setStage(stage: 'idle' | 'thinking' | 'streaming' | 'done', previewContent = '') { + this.props.stage = stage + this.props.previewContent = previewContent + this.props.isBusy = stage === 'thinking' || stage === 'streaming' + } + + buildRequestPayload(): ProRequestPayload { + const pos = this.getPosValue() + if (pos === undefined) { + return { + prefix: '', + suffix: '', + languageId: 'markdown', + blocked: true, + } + } + + const doc = this.view.state.doc + const schema = this.view.state.schema + const prefixMarkdown = serializeRangeToMarkdown(doc, 0, pos, schema, this.serializer) + || doc.textBetween(0, pos, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT) + const suffixStart = Math.min(pos + this.node.nodeSize, doc.content.size) + const suffixMarkdown = serializeRangeToMarkdown(doc, suffixStart, doc.content.size, schema, this.serializer) + || doc.textBetween(suffixStart, doc.content.size, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT) + + const ocrContext = buildOcrContext(doc) + const docContext = extractDocBlockContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600) + const fullPrefix = [ocrContext, docContext, prefixMarkdown].filter(Boolean).join('\n\n') + + return { + prefix: fullPrefix, + suffix: suffixMarkdown, + languageId: 'markdown', + blocked: fullPrefix.length + suffixMarkdown.length > PRO_CONTEXT_LIMIT, + } + } + + consumeAutoStart() { + if (!this.node.attrs.autoStart) return + this.updateAttrs({ autoStart: false }) + void this.startThinking(false) + } + + stopThinkingLock() { + this.config.setThinkingActive(false) + } + + abortActive(reason = 'abort') { + if (!this.abortController) return + this.abortController.abort(reason) + this.abortController = null + this.stopThinkingLock() + } + + discardResult() { + this.abortActive('discard') + this.redoCount = 0 + this.props.savedContent = '' + this.setStage('idle', '') + this.updateAttrs({ content: '', autoStart: false }) + } + + async startThinking(isRedo: boolean) { + if (this.props.isBusy) return + const previousContent = this.node.attrs.content || '' + let requestSeq = this.requestSeq + let requestStarted = false + + try { + if (this.config.isThinkingActive()) { + this.config.showError('当前已有一个 PRO 思考正在进行,请先等待它完成。') + return + } + + const payload = this.buildRequestPayload() + if (payload.blocked) { + this.config.showError('PRO 模式上下文超过 32KB,无法继续思考。') + return + } + + requestSeq = this.requestSeq + 1 + this.requestSeq = requestSeq + this.abortController = new AbortController() + this.config.setThinkingActive(true) + requestStarted = true + this.setStage('thinking', '') + + const temperature = Math.min(1.2, 0.7 + (isRedo ? (this.redoCount + 1) * 0.2 : 0)) + + const result = await this.config.fetchSuggestionStream({ + ...payload, + signal: this.abortController.signal, + model: this.config.getProModel(), + temperature, + onChunk: (chunk) => { + if (this.destroyed || this.requestSeq !== requestSeq) return + const nextContent = `${this.props.previewContent || ''}${chunk}` + this.setStage('streaming', nextContent) + }, + }) + + if (this.destroyed || this.requestSeq !== requestSeq) return + + const content = String(result || this.props.previewContent || '').trim() + this.redoCount = isRedo ? this.redoCount + 1 : 0 + this.props.savedContent = content + this.setStage(content ? 'done' : 'idle', '') + this.updateAttrs({ content, autoStart: false }) + } catch (error) { + if (!isAbortError(error)) { + this.config.showError(error instanceof Error ? error.message : 'PRO 模式思考失败') + } + + this.props.savedContent = previousContent + this.setStage(previousContent ? 'done' : 'idle', '') + } finally { + if (requestStarted && this.requestSeq === requestSeq) { + this.abortController = null + this.stopThinkingLock() + } + } + } + + async acceptResult() { + if (this.props.isBusy) return + + const source = String(this.props.savedContent || this.node.attrs.content || '').trim() + if (!source) return + + const pos = this.getPosValue() + if (pos === undefined) return + + const from = pos + const to = pos + this.node.nodeSize + const tr = this.view.state.tr + let insertedRange: { from: number; to: number } | null = null + + try { + const parsedDoc = await this.parser(source) + insertedRange = replaceWithParsedMarkdownSlice(tr, from, to, parsedDoc) + } catch { + insertedRange = null + } + + if (!insertedRange) { + tr.insertText(source, from, to) + insertedRange = { from, to: from + source.length } + } + + const endPos = Math.min(insertedRange.to, tr.doc.content.size) + tr.setSelection(Selection.near(tr.doc.resolve(endPos), 1)) + tr.setMeta(PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY, { set: insertedRange }) + this.view.dispatch(tr.scrollIntoView()) + this.view.focus() + + if (this.highlightTimer) { + clearTimeout(this.highlightTimer) + } + this.highlightTimer = setTimeout(() => { + if (this.destroyed) return + this.view.dispatch(this.view.state.tr.setMeta(PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY, { clear: true })) + }, 1000) + } + + update(node: ProseNode) { + if (node.type !== this.node.type) return false + this.node = node + + if (!this.props.isBusy) { + this.props.savedContent = node.attrs.content || '' + this.props.previewContent = '' + this.props.stage = node.attrs.content ? 'done' : 'idle' + } + + return true + } + + stopEvent(event: Event) { + const target = event.target as Node | null + return Boolean(target && this.dom.contains(target)) + } + + ignoreMutation() { + return true + } + + destroy() { + this.destroyed = true + this.abortActive('destroy') + if (this.highlightTimer) { + clearTimeout(this.highlightTimer) + this.highlightTimer = null + } + this.app?.unmount() + this.app = null + } +} + +export const proBlockRemark = $remark('proBlockRemark', () => () => { + return (tree: any) => { + transformProBlockChildren(tree) + } +}) + +export const proBlockNode = $node(PRO_BLOCK_NODE_TYPE, () => ({ + group: 'block', + atom: true, + isolating: true, + selectable: true, + draggable: false, + marks: '', + attrs: { + content: { default: '' }, + autoStart: { default: false }, + }, + parseDOM: [ + { + tag: 'div[data-pro-block="true"]', + getAttrs: (dom) => ({ + content: (dom as HTMLElement).getAttribute('data-pro-content') || '', + autoStart: ((dom as HTMLElement).getAttribute('data-pro-autostart') || '') === 'true', + }), + }, + ], + toDOM: (node) => [ + 'div', + { + 'data-pro-block': 'true', + 'data-pro-content': node.attrs.content || '', + 'data-pro-autostart': String(Boolean(node.attrs.autoStart)), + }, + ], + parseMarkdown: { + match: (node) => node.type === PRO_BLOCK_NODE_TYPE, + runner: (state, node, type) => { + state.addNode(type, { + content: String(node.content || ''), + autoStart: false, + }) + }, + }, + toMarkdown: { + match: (node) => node.type.name === PRO_BLOCK_NODE_TYPE, + runner: (state, node) => { + state.addNode('paragraph', [{ + type: 'text', + value: serializeProBlockSyntax(node.attrs.content), + }]) + }, + }, + leafText: (node) => serializeProBlockSyntax(node.attrs.content), +})) + +export const proBlockView = $view(proBlockNode, (ctx) => { + const parser = ctx.get(parserCtx) + const serializer = ctx.get(serializerCtx) + const config = ctx.get(proBlockConfigCtx.key) + return (node, view, getPos) => new ProBlockNodeView(node, view, getPos, parser, serializer, config) +}) + +export const proBlockInputPlugin = $prose(() => { + return new Plugin({ + key: PRO_BLOCK_INPUT_PLUGIN_KEY, + props: { + handleTextInput: (view, _from, _to, text) => { + return tryHandleProTriggerTextInput(view, text) + }, + handleKeyDown: (view, event) => { + const pressed = (event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === 'p' + if (!pressed) return false + event.preventDefault() + return insertProBlockNode(view, false) + }, + }, + appendTransaction: (transactions, _oldState, newState) => { + if (!transactions.some((transaction) => transaction.docChanged)) return null + if (transactions.some((transaction) => transaction.getMeta(PRO_BLOCK_INPUT_META))) return null + + const proBlockType = newState.schema.nodes[PRO_BLOCK_NODE_TYPE] + if (!proBlockType) return null + + const replacements = findProBlockReplacements(newState.doc) + if (replacements.length === 0) return null + + let tr = newState.tr + + for (let index = replacements.length - 1; index >= 0; index -= 1) { + const replacement = replacements[index] + tr = tr.replaceWith( + replacement.from, + replacement.to, + proBlockType.create({ + content: replacement.content, + autoStart: replacement.autoStart && replacement.content === '', + }) + ) + } + + if (!tr.docChanged) return null + tr.setMeta(PRO_BLOCK_INPUT_META, true) + return tr + }, + }) +}) + +export const proBlockHighlightPlugin = $prose(() => { + return new Plugin({ + key: PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY, + state: { + init: () => DecorationSet.empty, + apply: (tr, value) => { + const meta = tr.getMeta(PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY) + if (meta?.clear) { + return DecorationSet.empty + } + if (meta?.set) { + return createHighlightDecorations(tr.doc, meta.set.from, meta.set.to) + } + return value.map(tr.mapping, tr.doc) + }, + }, + props: { + decorations: (state) => PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY.getState(state), + }, + }) +}) + +export function insertProBlockAtSelection(view: EditorView, autoStart = false) { + return insertProBlockNode(view, autoStart) +} + +export { PRO_TRIGGER_TEXT } \ No newline at end of file diff --git a/src/plugins/uploadBlockPlugin.ts b/src/plugins/uploadBlockPlugin.ts new file mode 100644 index 0000000..cdb8410 --- /dev/null +++ b/src/plugins/uploadBlockPlugin.ts @@ -0,0 +1,263 @@ +import { createApp, h, reactive } from 'vue' +import { Plugin, PluginKey } from '@milkdown/prose/state' +import { $ctx, $node, $prose, $remark, $view } from '@milkdown/kit/utils' +import type { Node as ProseNode } from '@milkdown/prose/model' +import type { EditorView, NodeView } from '@milkdown/prose/view' +import UploadBlockCrepe from '../components/UploadBlockCrepe.vue' +import { + DEFAULT_UPLOAD_BLOCK_TYPES, + UPLOAD_BLOCK_NODE_TYPE, + getUploadBlockAccept, + getUploadBlockMenuOptions, + normalizeUploadBlockTypes, + parseUploadBlockSyntax, + serializeUploadBlockSyntax, +} from '../utils/uploadBlock.js' + +const UPLOAD_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-upload-block-input') +const UPLOAD_BLOCK_INPUT_META = 'upload-block-input-meta' + +interface UploadBlockConfig { + requestUpload: (payload: { file: File; allowedTypes: string[]; from: number; to: number }) => Promise +} + +export const uploadBlockConfigCtx = $ctx({ + requestUpload: async () => {}, +}, 'uploadBlockConfig') + +function transformUploadBlockChildren(node: any) { + if (!node || !Array.isArray(node.children)) return + + node.children = node.children.map((child: any) => { + const replacement = parseUploadParagraphNode(child) + if (replacement) return replacement + transformUploadBlockChildren(child) + return child + }) +} + +function parseUploadParagraphNode(node: any) { + if (!node || node.type !== 'paragraph' || !Array.isArray(node.children)) return null + if (node.children.length !== 1) return null + + const textNode = node.children[0] + if (!textNode || textNode.type !== 'text' || typeof textNode.value !== 'string') return null + + const parsed = parseUploadBlockSyntax(textNode.value) + if (!parsed) return null + + return { + type: UPLOAD_BLOCK_NODE_TYPE, + allowedTypes: parsed.allowedTypes, + } +} + +function findUploadBlockReplacements(doc: ProseNode) { + const replacements: Array<{ from: number; to: number; allowedTypes: string[] }> = [] + + doc.descendants((node, pos) => { + if (node.type.name !== 'paragraph' || node.childCount !== 1) return true + + const firstChild = node.firstChild + if (!firstChild?.isText) return true + + const parsed = parseUploadBlockSyntax(node.textContent) + if (!parsed) return true + + replacements.push({ + from: pos, + to: pos + node.nodeSize, + allowedTypes: parsed.allowedTypes, + }) + + return false + }) + + return replacements +} + +class UploadBlockNodeView implements NodeView { + node: ProseNode + view: EditorView + getPos: (() => number) | boolean + dom: HTMLElement + app: ReturnType | null = null + props: Record + requestUpload: UploadBlockConfig['requestUpload'] + + constructor(node: ProseNode, view: EditorView, getPos: (() => number) | boolean, requestUpload: UploadBlockConfig['requestUpload']) { + this.node = node + this.view = view + this.getPos = getPos + this.requestUpload = requestUpload + this.dom = document.createElement('div') + this.dom.className = 'upload-block-node-view' + + const allowedTypes = normalizeUploadBlockTypes(node.attrs.allowedTypes) + + this.props = reactive({ + allowedTypes, + accept: getUploadBlockAccept(allowedTypes), + menuOptions: getUploadBlockMenuOptions(allowedTypes), + onUploadRequest: (file: File) => this.handleUploadRequest(file), + }) + + this.mount() + } + + mount() { + this.app = createApp({ + render: () => h(UploadBlockCrepe, this.props), + }) + this.app.mount(this.dom) + } + + getPosValue() { + if (typeof this.getPos === 'function') { + try { + const pos = this.getPos() + if (typeof pos === 'number') return pos + } catch { + // Ignore and fall back to DOM lookup. + } + } + + try { + const pos = this.view.posAtDOM(this.dom, 0) + return typeof pos === 'number' ? pos : undefined + } catch { + return undefined + } + } + + async handleUploadRequest(file: File) { + const pos = this.getPosValue() + if (pos === undefined || !file) return + + await this.requestUpload({ + file, + allowedTypes: normalizeUploadBlockTypes(this.node.attrs.allowedTypes), + from: pos, + to: pos + this.node.nodeSize, + }) + } + + update(node: ProseNode) { + if (node.type !== this.node.type) return false + this.node = node + + const allowedTypes = normalizeUploadBlockTypes(node.attrs.allowedTypes) + this.props.allowedTypes = allowedTypes + this.props.accept = getUploadBlockAccept(allowedTypes) + this.props.menuOptions = getUploadBlockMenuOptions(allowedTypes) + return true + } + + stopEvent(event: Event) { + const target = event.target as Node | null + return Boolean(target && this.dom.contains(target)) + } + + ignoreMutation() { + return true + } + + destroy() { + this.app?.unmount() + this.app = null + } +} + +export const uploadBlockRemark = $remark('uploadBlockRemark', () => () => { + return (tree: any) => { + transformUploadBlockChildren(tree) + } +}) + +export const uploadBlockNode = $node(UPLOAD_BLOCK_NODE_TYPE, () => ({ + group: 'block', + atom: true, + isolating: true, + selectable: true, + draggable: false, + marks: '', + attrs: { + allowedTypes: { default: [...DEFAULT_UPLOAD_BLOCK_TYPES] }, + }, + parseDOM: [ + { + tag: 'div[data-upload-block="true"]', + getAttrs: (dom) => ({ + allowedTypes: normalizeUploadBlockTypes( + String((dom as HTMLElement).getAttribute('data-upload-types') || '') + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + ), + }), + }, + ], + toDOM: (node) => [ + 'div', + { + 'data-upload-block': 'true', + 'data-upload-types': normalizeUploadBlockTypes(node.attrs.allowedTypes).join(','), + }, + ], + parseMarkdown: { + match: (node) => node.type === UPLOAD_BLOCK_NODE_TYPE, + runner: (state, node, type) => { + state.addNode(type, { + allowedTypes: normalizeUploadBlockTypes(node.allowedTypes), + }) + }, + }, + toMarkdown: { + match: (node) => node.type.name === UPLOAD_BLOCK_NODE_TYPE, + runner: (state, node) => { + state.addNode('paragraph', [{ + type: 'text', + value: serializeUploadBlockSyntax(node.attrs.allowedTypes), + }]) + }, + }, + leafText: (node) => serializeUploadBlockSyntax(node.attrs.allowedTypes), +})) + +export const uploadBlockView = $view(uploadBlockNode, (ctx) => { + const requestUpload = ctx.get(uploadBlockConfigCtx.key).requestUpload + return (node, view, getPos) => new UploadBlockNodeView(node, view, getPos, requestUpload) +}) + +export const uploadBlockInputPlugin = $prose(() => { + return new Plugin({ + key: UPLOAD_BLOCK_INPUT_PLUGIN_KEY, + appendTransaction: (transactions, _oldState, newState) => { + if (!transactions.some((transaction) => transaction.docChanged)) return null + if (transactions.some((transaction) => transaction.getMeta(UPLOAD_BLOCK_INPUT_META))) return null + + const uploadBlockType = newState.schema.nodes[UPLOAD_BLOCK_NODE_TYPE] + if (!uploadBlockType) return null + + const replacements = findUploadBlockReplacements(newState.doc) + if (replacements.length === 0) return null + + let tr = newState.tr + + for (let index = replacements.length - 1; index >= 0; index -= 1) { + const replacement = replacements[index] + tr = tr.replaceWith( + replacement.from, + replacement.to, + uploadBlockType.create({ + allowedTypes: normalizeUploadBlockTypes(replacement.allowedTypes), + }) + ) + } + + if (!tr.docChanged) return null + tr.setMeta(UPLOAD_BLOCK_INPUT_META, true) + return tr + }, + }) +}) \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index dbb1cfa..4833b39 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -12,9 +12,8 @@ const routes = [ component: () => import('../views/DocsView.vue') }, { - path: '/univer', - name: 'Univer', - component: () => import('../views/UniverView.vue') + path: '/:pathMatch(.*)*', + redirect: '/' } ] diff --git a/src/services/officeDetection.js b/src/services/officeDetection.js index 7731730..20ddaf1 100644 --- a/src/services/officeDetection.js +++ b/src/services/officeDetection.js @@ -1,7 +1,12 @@ /** * Office 文件类型检测工具 */ -import { OfficeFormat, OfficePresetType } from './univerBridge' + +export const OfficeFormat = { + DOCX: 'docx', + XLSX: 'xlsx', + PPTX: 'pptx' +} /** * 支持的 Office 文件扩展名 @@ -73,63 +78,10 @@ export function getOfficeFormat(file) { return null } -/** - * 获取文件图标类型 - */ -export function getOfficeIcon(format) { - switch (format) { - case OfficeFormat.DOCX: - return 'doc' - case OfficeFormat.XLSX: - return 'xls' - case OfficeFormat.PPTX: - return 'ppt' - default: - return 'file' - } -} - -/** - * 获取格式显示名称 - */ -export function getFormatDisplayName(format, locale = 'zh-CN') { - const names = { - 'zh-CN': { - [OfficeFormat.DOCX]: 'Word 文档', - [OfficeFormat.XLSX]: 'Excel 表格', - [OfficeFormat.PPTX]: 'PowerPoint 演示文稿' - }, - 'en-US': { - [OfficeFormat.DOCX]: 'Word Document', - [OfficeFormat.XLSX]: 'Excel Spreadsheet', - [OfficeFormat.PPTX]: 'PowerPoint Presentation' - } - } - - return names[locale]?.[format] || format?.toUpperCase() || '未知格式' -} - -/** - * 获取对应的 Preset 类型 - */ -export function getPresetTypeByFormat(format) { - switch (format) { - case OfficeFormat.DOCX: - case OfficeFormat.PPTX: - return OfficePresetType.DOCS - case OfficeFormat.XLSX: - return OfficePresetType.SHEETS - default: - return null - } -} - export default { + OfficeFormat, isOfficeFile, getOfficeFormat, - getOfficeIcon, - getFormatDisplayName, - getPresetTypeByFormat, SUPPORTED_EXTENSIONS, MIME_TYPES } diff --git a/src/services/univerBridge.js b/src/services/univerBridge.js deleted file mode 100644 index 7153484..0000000 --- a/src/services/univerBridge.js +++ /dev/null @@ -1,302 +0,0 @@ -/** -* 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' -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', - PPTX: 'pptx' -} - -export const OfficePresetType = { - DOCS: 'docs', - SHEETS: 'sheets', - SLIDES: 'slides' -} - -/** - * 根据文件扩展名判断 Office 格式 - */ -export function detectOfficeFormat(filename) { - const ext = filename?.toLowerCase().split('.').pop() || '' - if (ext === 'docx') return OfficeFormat.DOCX - if (ext === 'xlsx') return OfficeFormat.XLSX - if (ext === 'pptx') return OfficeFormat.PPTX - return null -} - -/** - * 根据格式获取对应的 Preset 类型 - */ -export function getPresetType(format) { - switch (format) { - case OfficeFormat.DOCX: - return OfficePresetType.DOCS - case OfficeFormat.XLSX: - return OfficePresetType.SHEETS - case OfficeFormat.PPTX: - return OfficePresetType.SLIDES - default: - return null - } -} - -/** - * 创建 Univer 实例 - */ -export async function createUniverInstance(container, options = {}) { - const { - format = OfficeFormat.DOCX, - locale = 'zh-CN', - theme = 'light' - } = options - - const localeType = locale === 'zh-CN' ? LocaleType.ZH_CN : LocaleType.EN_US - // 合并所有语言包(包括 Slides) - const locales = locale === 'zh-CN' - ? { [LocaleType.ZH_CN]: merge({}, DocsCoreZhCN, SheetsCoreZhCN, SlidesZhCN) } - : { [LocaleType.EN_US]: merge({}, DocsCoreEnUS, SheetsCoreEnUS, SlidesEnUS) } - - const presets = [] - const extraPlugins = [] - - // 根据格式添加对应的 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' - })) - } - - 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}`) - } -} - -/** - * Univer 编辑器实例包装类 - */ -export class UniverEditorInstance { - constructor() { - this.univer = null - this.univerAPI = null - this.container = null - this.currentFormat = null - } - - /** - * 初始化编辑器 - */ - async init(container, options = {}) { - if (this.univer) { - await this.destroy() - } - - this.container = container - this.currentFormat = options.format || OfficeFormat.DOCX - - const result = await createUniverInstance(container, { - format: this.currentFormat, - ...options - }) - - this.univer = result.univer - this.univerAPI = result.univerAPI - - // 创建初始文档 - if (this.currentFormat === OfficeFormat.XLSX) { - this.univerAPI.createWorkbook({}) - } else { - this.univerAPI.createUniverDoc({}) - } - - return this - } - - /** - * 从字节数组加载文档 - */ - async loadFromBytes(bytes, format) { - if (!this.univerAPI) { - throw new Error('Univer 实例未初始化') - } - - // 注意:纯前端模式下,Univer 不支持直接从 DOCX/XLSX/PPTX 字节流加载 - // 这里需要使用快照模式或后端服务来解析 - // 当前实现为占位,实际需要配合快照格式 - console.warn('纯前端模式暂不支持从 DOCX/XLSX/PPTX 字节流加载,请使用快照模式') - return false - } - - /** - * 导出为快照数据 - */ - async exportSnapshot() { - if (!this.univerAPI) { - throw new Error('Univer 实例未初始化') - } - - const activeDoc = this.univerAPI.getActiveDocument() - const activeSheet = this.univerAPI.getActiveWorkbook() - - if (activeSheet) { - return { - type: OfficePresetType.SHEETS, - format: OfficeFormat.XLSX, - data: activeSheet.getSnapshot() - } - } - - if (activeDoc) { - return { - type: OfficePresetType.DOCS, - format: OfficeFormat.DOCX, - data: activeDoc.getSnapshot() - } - } - - return null - } - - /** - * 从快照数据导入 - */ - async importSnapshot(snapshot) { - if (!this.univerAPI || !snapshot?.data) { - throw new Error('无效的快照数据') - } - - // 快照数据可以直接用于恢复文档状态 - // 具体实现取决于 Univer API - console.log('导入快照:', snapshot.type, snapshot.format) - return true - } - - /** - * 监听文档变化 - */ - onChange(callback) { - if (!this.univerAPI) return - - // Univer API 的事件监听 - this.univerAPI.addEvent(this.univerAPI.Event.CommandExecuted, (event) => { - callback({ - type: 'command', - data: event - }) - }) - } - - /** - * 销毁实例 - */ - async destroy() { - if (this.univer) { - this.univer.dispose() - this.univer = null - this.univerAPI = null - this.container = null - this.currentFormat = null - } - } - - /** - * 获取当前格式 - */ - getFormat() { - return this.currentFormat - } - - /** - * 检查是否已初始化 - */ - isInitialized() { - return this.univer !== null && this.univerAPI !== null - } -} - -/** - * 创建 Univer 编辑器实例 - */ -export function createUniverEditor() { - return new UniverEditorInstance() -} - -export default { - createUniverInstance, - createUniverEditor, - detectOfficeFormat, - getPresetType, - OfficeFormat, - OfficePresetType, - UniverEditorInstance -} diff --git a/src/stores/office.js b/src/stores/office.js deleted file mode 100644 index 230721e..0000000 --- a/src/stores/office.js +++ /dev/null @@ -1,186 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import { OfficeFormat, OfficePresetType } from '../services/univerBridge' - -export const useOfficeStore = defineStore('office', () => { - // 当前文档状态 - const currentFileName = ref('') - const currentFormat = ref(null) - const currentFileSize = ref(0) - const currentBytes = ref(null) - - // 快照模式 - const isSnapshotMode = ref(true) // 默认启用快照模式 - const currentSnapshot = ref(null) - - // 编辑状态 - const isEditing = ref(false) - const hasUnsavedChanges = ref(false) - - // 视图状态 - const activeView = ref('milkdown') // 'milkdown' | 'univer' - - // 文档加载状态(新增) - const documentLoadStatus = ref('idle') // 'idle' | 'loading' | 'success' | 'error' - const documentErrorMessage = ref('') - - // 计算属性 - const hasDocument = computed(() => { - return currentFileName.value && currentFormat.value - }) - - const documentInfo = computed(() => { - if (!hasDocument.value) return null - return { - name: currentFileName.value, - format: currentFormat.value, - size: currentFileSize.value, - isSnapshot: isSnapshotMode.value, - loadStatus: documentLoadStatus.value - } - }) - - /** - * 设置当前文档 - */ - function setCurrentDocument(file, bytes) { - if (!file) { - clearCurrentDocument() - return - } - - currentFileName.value = file.name || '未命名' - currentFormat.value = getFormatFromFileName(file.name) - currentFileSize.value = file.size || 0 - currentBytes.value = bytes - hasUnsavedChanges.value = false - documentLoadStatus.value = 'idle' - documentErrorMessage.value = '' - } - - /** - * 清除当前文档 - */ - function clearCurrentDocument() { - currentFileName.value = '' - currentFormat.value = null - currentFileSize.value = 0 - currentBytes.value = null - currentSnapshot.value = null - hasUnsavedChanges.value = false - documentLoadStatus.value = 'idle' - documentErrorMessage.value = '' - } - - /** - * 设置快照数据 - */ - function setSnapshot(snapshot) { - currentSnapshot.value = snapshot - hasUnsavedChanges.value = false - } - - /** - * 标记有未保存的更改 - */ - function markAsChanged() { - hasUnsavedChanges.value = true - } - - /** - * 切换视图 - */ - function switchView(view) { - activeView.value = view - } - - /** - * 切换快照模式 - */ - function toggleSnapshotMode() { - isSnapshotMode.value = !isSnapshotMode.value - } - - // 新增:文档加载状态管理方法 - /** - * 开始加载文档 - */ - function startDocumentLoad() { - documentLoadStatus.value = 'loading' - documentErrorMessage.value = '' - } - - /** - * 文档加载成功 - */ - function completeDocumentLoad() { - documentLoadStatus.value = 'success' - documentErrorMessage.value = '' - } - - /** - * 文档加载失败 - */ - function failDocumentLoad(message) { - documentLoadStatus.value = 'error' - documentErrorMessage.value = message || '文档加载失败' - } - - /** - * 重置文档加载状态 - */ - function resetDocumentLoadStatus() { - documentLoadStatus.value = 'idle' - documentErrorMessage.value = '' - } - - return { - // 状态 - currentFileName, - currentFormat, - currentFileSize, - currentBytes, - isSnapshotMode, - currentSnapshot, - isEditing, - hasUnsavedChanges, - activeView, - documentLoadStatus, - documentErrorMessage, - - // 计算属性 - hasDocument, - documentInfo, - - // 方法 - setCurrentDocument, - clearCurrentDocument, - setSnapshot, - markAsChanged, - switchView, - toggleSnapshotMode, - startDocumentLoad, - completeDocumentLoad, - failDocumentLoad, - resetDocumentLoadStatus - } -}) - -/** - * 从文件名获取格式 - */ -function getFormatFromFileName(filename) { - const ext = filename?.toLowerCase().split('.').pop() || '' - switch (ext) { - case 'docx': - return OfficeFormat.DOCX - case 'xlsx': - return OfficeFormat.XLSX - case 'pptx': - return OfficeFormat.PPTX - default: - return null - } -} - -export default useOfficeStore diff --git a/src/stores/settings.js b/src/stores/settings.js index 32e7cc0..ba8c988 100644 --- a/src/stores/settings.js +++ b/src/stores/settings.js @@ -11,6 +11,7 @@ export const useSettingsStore = defineStore('settings', () => { // 2. Model Behavior const modelThinking = ref('low') // 'low' | 'medium' | 'high' const debounceMs = ref(1000) // 1000 - 5000 + const proModel = ref('') // 3. Privacy const privacyMode = ref(true) @@ -44,7 +45,10 @@ export const useSettingsStore = defineStore('settings', () => { // We will let the backend handle 'auto' currency if needed, or stick to auto label. const t = computed(() => { - return translations[uiLanguage.value] || translations['en'] + return { + ...translations['en'], + ...(translations[uiLanguage.value] || {}), + } }) const initialMarkdown = computed(() => { @@ -63,6 +67,7 @@ export const useSettingsStore = defineStore('settings', () => { if (data.theme) theme.value = data.theme if (data.modelThinking) modelThinking.value = data.modelThinking if (data.debounceMs) debounceMs.value = data.debounceMs + if (typeof data.proModel === 'string') proModel.value = data.proModel if (typeof data.privacyMode === 'boolean') privacyMode.value = data.privacyMode if (data.language) language.value = data.language if (data.currency) currency.value = data.currency @@ -86,6 +91,7 @@ export const useSettingsStore = defineStore('settings', () => { theme: theme.value, modelThinking: modelThinking.value, debounceMs: debounceMs.value, + proModel: proModel.value, privacyMode: privacyMode.value, language: language.value, currency: currency.value, @@ -105,6 +111,7 @@ export const useSettingsStore = defineStore('settings', () => { theme.value = 'system' modelThinking.value = 'low' debounceMs.value = 1000 + proModel.value = '' privacyMode.value = false language.value = 'auto' currency.value = 'auto' @@ -121,6 +128,7 @@ export const useSettingsStore = defineStore('settings', () => { theme, modelThinking, debounceMs, + proModel, privacyMode, language, currency, @@ -141,6 +149,7 @@ export const useSettingsStore = defineStore('settings', () => { theme, modelThinking, debounceMs, + proModel, privacyMode, language, currency, @@ -148,6 +157,7 @@ export const useSettingsStore = defineStore('settings', () => { backgroundImage, backgroundOpacity, ttsInstruct, + detectedTimezone, uiLanguage, t, initialMarkdown, diff --git a/src/stores/templates.js b/src/stores/templates.js new file mode 100644 index 0000000..95ade67 --- /dev/null +++ b/src/stores/templates.js @@ -0,0 +1,259 @@ +import { defineStore } from 'pinia' +import { ref, watch } from 'vue' + +const STORAGE_KEY = 'llm-in-text-templates' +const STORAGE_VERSION = 1 + +const PRESET_TEMPLATES = [ + { + id: 'preset-meeting-notes', + name: '会议纪要', + content: `# 会议纪要 + +- 日期: +- 时间: +- 地点: +- 参会人: + +## 议题 + +## 讨论记录 + +## 决议 + +## 待办事项 +- [ ] `, + }, + { + id: 'preset-article-outline', + name: '文章大纲', + content: `# 标题 + +> 一句话摘要 + +## 背景 + +## 结构 + +## 关键点 + +## 结论`, + }, + { + id: 'preset-project-plan', + name: '项目计划', + content: `# 项目计划 + +## 目标 + +## 范围 + +## 里程碑 + +## 风险 + +## 负责人`, + }, + { + id: 'preset-research-notes', + name: '研究笔记', + content: `# 研究笔记 + +## 问题定义 + +## 资料来源 + +## 观察 + +## 结论 + +## 下一步`, + }, +] + +const normalizeTemplateName = (value) => String(value ?? '').replace(/\s+/g, ' ').trim() + +const normalizeTemplateContent = (value) => String(value ?? '').replace(/\r\n?/g, '\n').replace(/\s+$/, '') + +const makeTemplateId = () => { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return `custom-${crypto.randomUUID()}` + } + + return `custom-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` +} + +const normalizeStoredTemplate = (template) => { + if (!template || typeof template !== 'object') return null + + const name = normalizeTemplateName(template.name) + const content = normalizeTemplateContent(template.content) + if (!name || !content.trim()) return null + + const createdAt = typeof template.createdAt === 'string' && template.createdAt ? template.createdAt : new Date().toISOString() + const updatedAt = typeof template.updatedAt === 'string' && template.updatedAt ? template.updatedAt : createdAt + + return { + id: typeof template.id === 'string' && template.id ? template.id : makeTemplateId(), + name, + content, + createdAt, + updatedAt, + } +} + +export const useTemplatesStore = defineStore('templates', () => { + const customTemplates = ref([]) + let isHydrated = false + + const loadTemplates = () => { + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (!stored) return + + const parsed = JSON.parse(stored) + const rawTemplates = Array.isArray(parsed) + ? parsed + : Array.isArray(parsed?.customTemplates) + ? parsed.customTemplates + : Array.isArray(parsed?.data) + ? parsed.data + : [] + + customTemplates.value = rawTemplates + .map(normalizeStoredTemplate) + .filter(Boolean) + } catch { + customTemplates.value = [] + } + } + + const saveTemplates = () => { + if (!isHydrated) return + + try { + localStorage.setItem( + STORAGE_KEY, + JSON.stringify({ + version: STORAGE_VERSION, + customTemplates: customTemplates.value, + }) + ) + } catch { + // Ignore persistence failures in browser storage + } + } + + const isCustomTemplateNameTaken = (name, ignoreId = '') => { + const normalizedName = normalizeTemplateName(name) + if (!normalizedName) return false + + return customTemplates.value.some((template) => { + if (ignoreId && template.id === ignoreId) return false + return template.name.toLowerCase() === normalizedName.toLowerCase() + }) + } + + const buildUniqueCustomTemplateName = (baseName, ignoreId = '') => { + const normalizedBaseName = normalizeTemplateName(baseName) || 'Untitled Template' + let candidate = normalizedBaseName + let suffix = 2 + + while (isCustomTemplateNameTaken(candidate, ignoreId)) { + candidate = `${normalizedBaseName} ${suffix}` + suffix += 1 + } + + return candidate + } + + const createCustomTemplate = ({ name, content }) => { + const nextName = normalizeTemplateName(name) + const nextContent = normalizeTemplateContent(content) + + if (!nextName || !nextContent.trim()) { + return { ok: false, error: 'invalid' } + } + + if (isCustomTemplateNameTaken(nextName)) { + return { ok: false, error: 'duplicate' } + } + + const now = new Date().toISOString() + const template = { + id: makeTemplateId(), + name: nextName, + content: nextContent, + createdAt: now, + updatedAt: now, + } + + customTemplates.value = [...customTemplates.value, template] + return { ok: true, template } + } + + const updateCustomTemplate = (id, { name, content }) => { + const index = customTemplates.value.findIndex((template) => template.id === id) + if (index === -1) { + return { ok: false, error: 'not_found' } + } + + const nextName = normalizeTemplateName(name) + const nextContent = normalizeTemplateContent(content) + + if (!nextName || !nextContent.trim()) { + return { ok: false, error: 'invalid' } + } + + if (isCustomTemplateNameTaken(nextName, id)) { + return { ok: false, error: 'duplicate' } + } + + const now = new Date().toISOString() + const nextTemplates = [...customTemplates.value] + nextTemplates[index] = { + ...nextTemplates[index], + name: nextName, + content: nextContent, + updatedAt: now, + } + + customTemplates.value = nextTemplates + return { ok: true, template: nextTemplates[index] } + } + + const deleteCustomTemplate = (id) => { + const nextTemplates = customTemplates.value.filter((template) => template.id !== id) + if (nextTemplates.length === customTemplates.value.length) { + return false + } + + customTemplates.value = nextTemplates + return true + } + + const getTemplateById = (id) => { + if (!id) return null + + return PRESET_TEMPLATES.find((template) => template.id === id) + || customTemplates.value.find((template) => template.id === id) + || null + } + + watch(customTemplates, saveTemplates, { deep: true }) + + loadTemplates() + isHydrated = true + saveTemplates() + + return { + presetTemplates: PRESET_TEMPLATES, + customTemplates, + createCustomTemplate, + updateCustomTemplate, + deleteCustomTemplate, + getTemplateById, + buildUniqueCustomTemplateName, + isCustomTemplateNameTaken, + } +}) \ No newline at end of file diff --git a/src/style.css b/src/style.css index b14cd14..5878bfa 100644 --- a/src/style.css +++ b/src/style.css @@ -244,6 +244,27 @@ body { } } +.hidden-text-preview { + display: inline-flex; + align-items: center; + vertical-align: baseline; +} + +.hidden-text-preview__summary { + display: inline-flex; + align-items: center; + min-height: 1.8em; + padding: 0.08em 0.52em; + border-radius: 0.45em; + background: rgba(148, 163, 184, 0.26); + color: inherit; + white-space: pre-wrap; +} + +:root[data-theme='dark'] .hidden-text-preview__summary { + background: rgba(100, 116, 139, 0.32); +} + /* ── Mermaid diagram blocks ─────────────────────────────────────────── */ .mermaid-block { position: relative; diff --git a/src/utils/api.js b/src/utils/api.js index 519d326..ec64515 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,4 +1,4 @@ -import { API_URL, API_KEY, TTS_URL, TTS_STATUS_URL, TTS_CONFIG_URL } from './config.js' +import { API_URL, API_KEY, PRO_STREAM_URL, TTS_URL, TTS_STATUS_URL, TTS_CONFIG_URL } from './config.js' import { useSettingsStore } from '../stores/settings' function generateRequestId() { @@ -11,6 +11,9 @@ function generateRequestId() { function getCancelUrl(apiUrl) { const normalized = String(apiUrl || '').replace(/\/+$/, '') if (!normalized) return '/v1/completions/cancel' + if (/\/v1\/pro\/completions\/stream$/i.test(normalized)) { + return normalized.replace(/\/v1\/pro\/completions\/stream$/i, '/v1/completions/cancel') + } if (normalized.endsWith('/v1/completions')) { return `${normalized}/cancel` } @@ -42,6 +45,50 @@ async function sendCancelRequest(cancelUrl, requestId, reason) { } } +function createAbortError(message = 'Request aborted') { + const error = new Error(message) + error.name = 'AbortError' + return error +} + +function buildCompletionBody(settings, prefix, suffix, languageId, extra = {}) { + return { + prefix, + suffix, + languageId, + model_thinking: settings.modelThinking, + privacy_mode: settings.privacyMode, + user_preferences: { + language: settings.language, + currency: settings.currency, + timezone: settings.detectedTimezone, + }, + ...extra, + } +} + +function parseSseEvent(rawEvent) { + const lines = String(rawEvent || '').replace(/\r/g, '').split('\n') + let event = 'message' + const dataLines = [] + + for (const line of lines) { + if (!line) continue + if (line.startsWith('event:')) { + event = line.slice(6).trim() || 'message' + continue + } + if (line.startsWith('data:')) { + dataLines.push(line.slice(5).trimStart()) + } + } + + return { + event, + data: dataLines.join('\n'), + } +} + export async function fetchSuggestion(prefix, suffix, languageId, signal, apiUrl = API_URL) { let normalizedLanguageId = 'markdown' if (typeof languageId === 'string' && languageId.trim()) { @@ -77,18 +124,7 @@ export async function fetchSuggestion(prefix, suffix, languageId, signal, apiUrl 'X-API-Key': API_KEY, } - const body = { - prefix, - suffix, - languageId: normalizedLanguageId, - model_thinking: settings.modelThinking, - privacy_mode: settings.privacyMode, - user_preferences: { - language: settings.language, - currency: settings.currency, - timezone: settings.detectedTimezone, - }, - } + const body = buildCompletionBody(settings, prefix, suffix, normalizedLanguageId) const res = await fetch(apiUrl, { method: 'POST', @@ -117,6 +153,134 @@ export async function fetchSuggestion(prefix, suffix, languageId, signal, apiUrl } } +export async function fetchProSuggestionStream(payload, apiUrl = PRO_STREAM_URL) { + const { + prefix = '', + suffix = '', + languageId = 'markdown', + signal, + model = '', + temperature = 0.7, + timeoutMs = 600000, + onChunk, + } = payload || {} + + const settings = useSettingsStore() + const requestId = generateRequestId() + const cancelUrl = getCancelUrl(apiUrl) + const requestController = new AbortController() + const timeoutId = setTimeout(() => { + requestController.abort('timeout') + }, timeoutMs) + + const relayAbort = () => { + requestController.abort(signal?.reason || 'abort') + } + + const onAbort = () => { + const reason = normalizeAbortReason(requestController.signal.reason) + void sendCancelRequest(cancelUrl, requestId, reason) + } + + requestController.signal.addEventListener('abort', onAbort, { once: true }) + + if (signal) { + if (signal.aborted) { + relayAbort() + } else { + signal.addEventListener('abort', relayAbort, { once: true }) + } + } + + try { + const res = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Request-Id': requestId, + 'X-API-Key': API_KEY, + }, + body: JSON.stringify( + buildCompletionBody(settings, prefix, suffix, String(languageId || 'markdown').trim() || 'markdown', { + model, + temperature, + }) + ), + signal: requestController.signal, + }) + + if (!res.ok) { + const errorText = await res.text() + throw new Error(`HTTP ${res.status}: ${errorText}`) + } + + if (!res.body) { + throw new Error('PRO 模式流式响应不可用') + } + + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buffer = '' + let finalContent = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + + let boundary = buffer.indexOf('\n\n') + while (boundary >= 0) { + const chunk = buffer.slice(0, boundary) + buffer = buffer.slice(boundary + 2) + + const parsed = parseSseEvent(chunk) + if (parsed.event === 'chunk' && parsed.data) { + const data = JSON.parse(parsed.data) + const delta = String(data.delta || '') + if (delta) { + finalContent += delta + onChunk?.(delta) + } + } + + if (parsed.event === 'done' && parsed.data) { + const data = JSON.parse(parsed.data) + return String(data.content || finalContent || '') + } + + if (parsed.event === 'error' && parsed.data) { + const data = JSON.parse(parsed.data) + throw new Error(String(data.error || 'PRO 模式请求失败')) + } + + if (parsed.event === 'cancelled') { + throw createAbortError('PRO 模式请求已取消') + } + + boundary = buffer.indexOf('\n\n') + } + } + + if (requestController.signal.aborted) { + throw createAbortError('PRO 模式请求已中止') + } + + return finalContent + } catch (e) { + if (e?.name === 'AbortError') { + throw e + } + throw e + } finally { + clearTimeout(timeoutId) + requestController.signal.removeEventListener('abort', onAbort) + if (signal) { + signal.removeEventListener('abort', relayAbort) + } + } +} + export async function fetchTTS(text, instruct = '', apiUrl = TTS_URL) { const res = await fetch(apiUrl, { method: 'POST', diff --git a/src/utils/config.js b/src/utils/config.js index 3b15813..31e76ca 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -4,6 +4,7 @@ const DEFAULT_API_BASE_URL = import.meta.env.DEV ? '' : 'https://api.imageteach. const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || DEFAULT_API_BASE_URL export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions` +export const PRO_STREAM_URL = import.meta.env.VITE_PRO_STREAM_URL || `${API_BASE_URL}/v1/pro/completions/stream` export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr` export const CONVERT_URL = import.meta.env.VITE_CONVERT_URL || `${API_BASE_URL}/v1/convert` export const EXPORT_PDF_URL = import.meta.env.VITE_EXPORT_PDF_URL || '/v1/export/pdf' diff --git a/src/utils/hiddenText.js b/src/utils/hiddenText.js new file mode 100644 index 0000000..b0dace2 --- /dev/null +++ b/src/utils/hiddenText.js @@ -0,0 +1,162 @@ +export const HIDDEN_TEXT_NODE_TYPE = 'hiddenText' + +function escapeHtml(value = '') { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +export function normalizeHiddenTextValue(value = '') { + return String(value ?? '').replace(/\r\n?/g, ' ') +} + +export function escapeHiddenTextSegment(value = '', closingChar) { + const normalized = normalizeHiddenTextValue(value) + const closingCharPattern = new RegExp(`\\${closingChar}`, 'g') + return normalized + .replace(/\\/g, '\\\\') + .replace(closingCharPattern, `\\${closingChar}`) +} + +export function serializeHiddenTextSyntax(displayed = '', hidden = '') { + const safeDisplayed = escapeHiddenTextSegment(displayed, ')') + const safeHidden = escapeHiddenTextSegment(hidden, '}') + return `(${safeDisplayed}){${safeHidden}}` +} + +function readHiddenTextSegment(text = '', start = 0, closingChar = ')') { + let index = start + let value = '' + + while (index < text.length) { + const char = text[index] + + if (char === '\\') { + const nextChar = text[index + 1] + if (nextChar === undefined) return null + value += nextChar + index += 2 + continue + } + + if (char === '\n' || char === '\r') return null + if (char === closingChar) { + return { + value, + end: index + 1, + } + } + + value += char + index += 1 + } + + return null +} + +export function parseHiddenTextAt(text = '', start = 0) { + if (text[start] !== '(') return null + + const displayed = readHiddenTextSegment(text, start + 1, ')') + if (!displayed) return null + if (text[displayed.end] !== '{') return null + + const hidden = readHiddenTextSegment(text, displayed.end + 1, '}') + if (!hidden) return null + + return { + start, + end: hidden.end, + displayed: displayed.value, + hidden: hidden.value, + raw: text.slice(start, hidden.end), + } +} + +export function extractHiddenTextMatches(text = '') { + const matches = [] + let index = 0 + + while (index < text.length) { + const match = parseHiddenTextAt(text, index) + if (match) { + matches.push(match) + index = match.end + continue + } + + index += 1 + } + + return matches +} + +export function splitTextWithHiddenSyntax(text = '') { + const matches = extractHiddenTextMatches(text) + if (matches.length === 0) return null + + const segments = [] + let cursor = 0 + + for (const match of matches) { + if (match.start > cursor) { + segments.push({ + type: 'text', + value: text.slice(cursor, match.start), + }) + } + + segments.push({ + type: HIDDEN_TEXT_NODE_TYPE, + displayed: match.displayed, + hidden: match.hidden, + }) + + cursor = match.end + } + + if (cursor < text.length) { + segments.push({ + type: 'text', + value: text.slice(cursor), + }) + } + + return segments.filter((segment) => segment.type !== 'text' || segment.value) +} + +export function renderHiddenTextPreviewHtml(displayed = '', hidden = '') { + const summary = escapeHtml(displayed || '未命名文本') + + return [ + ``, + `${summary}`, + '', + ].join('') +} + +export function hiddenTextMarkdownItPlugin(md) { + md.inline.ruler.before('emphasis', 'hidden_text', (state, silent) => { + const match = parseHiddenTextAt(state.src, state.pos) + if (!match) return false + + if (!silent) { + const token = state.push('hidden_text', '', 0) + token.meta = { + displayed: match.displayed, + hidden: match.hidden, + } + } + + state.pos = match.end + return true + }) + + md.renderer.rules.hidden_text = (tokens, index) => { + const meta = tokens[index]?.meta || {} + return renderHiddenTextPreviewHtml(meta.displayed, meta.hidden) + } +} \ No newline at end of file diff --git a/src/utils/i18n.js b/src/utils/i18n.js index a0f4d1b..3ecf659 100644 --- a/src/utils/i18n.js +++ b/src/utils/i18n.js @@ -22,6 +22,12 @@ export const translations = { mediumDesc: 'Brief analysis before suggesting', highDesc: 'Deep, step-by-step analysis (Slowest)', debounceTime: 'Debounce Time', + proMode: 'PRO Mode', + proModeThinking: 'PRO Thinking', + proModel: 'PRO Model', + proModelPlaceholder: 'e.g. qwen3:32b', + proModelDesc: 'Optional stronger model name used only by PRO mode.', + proModelEmptyHint: 'Leave empty to use the backend default PRO model.', privacyPreferences: 'Privacy & Preferences', privacyMode: 'Privacy Mode', privacyDesc: 'Prevent sending IP and preferences to the AI', @@ -50,6 +56,27 @@ export const translations = { uploading: 'Uploading files...', enableAI: 'Enable AI', disableAI: 'Disable AI', + template: 'Template', + presetTemplates: 'Preset Templates', + customTemplates: 'Custom Templates', + newTemplate: 'New Template', + previewTemplate: 'Preview Template', + applyTemplate: 'Apply Template', + copyAsTemplate: 'Copy as Custom Template', + editTemplate: 'Edit Template', + saveTemplate: 'Save Template', + templateName: 'Template Name', + templateContent: 'Template Content', + templateNamePlaceholder: 'e.g. Meeting Notes', + templateContentPlaceholder: 'Enter template content here...', + noTemplates: 'No custom templates yet', + templateNameRequired: 'Template name is required.', + templateContentRequired: 'Template content is required.', + templateNameDuplicate: 'Template name already exists.', + templateDeleteConfirm: 'Delete this template?', + templateSaved: 'Template saved.', + templateUpdated: 'Template updated.', + templateDeleted: 'Template deleted.', insertUrl: 'Insert Image from URL', insert: 'Insert', cancel: 'Cancel', @@ -114,6 +141,12 @@ export const translations = { mediumDesc: '简要分析上下文后建议', highDesc: '深度逐步分析(最慢但质量最高)', debounceTime: '防抖时间', + proMode: 'PRO模式思考', + proModeThinking: 'PRO 正在思考', + proModel: 'PRO 模型', + proModelPlaceholder: '例如 qwen3:32b', + proModelDesc: '可选。仅在 PRO 模式下使用的更强模型名称。', + proModelEmptyHint: '留空则使用后端默认 PRO 模型。', privacyPreferences: '隐私与偏好', privacyMode: '隐私模式', privacyDesc: '不向 AI 发送 IP 地址和偏好设置', @@ -142,6 +175,27 @@ export const translations = { uploading: '正在上传文件...', enableAI: '启用 AI', disableAI: '禁用 AI', + template: '模板', + presetTemplates: '预设模板', + customTemplates: '自定义模板', + newTemplate: '新建模板', + previewTemplate: '预览模板', + applyTemplate: '应用模板', + copyAsTemplate: '复制为自定义模板', + editTemplate: '编辑模板', + saveTemplate: '保存模板', + templateName: '模板名称', + templateContent: '模板内容', + templateNamePlaceholder: '例如:会议纪要', + templateContentPlaceholder: '在这里输入模板内容...', + noTemplates: '暂无自定义模板', + templateNameRequired: '请输入模板名称', + templateContentRequired: '请输入模板内容', + templateNameDuplicate: '模板名称已存在', + templateDeleteConfirm: '确认删除此模板吗?', + templateSaved: '模板已保存', + templateUpdated: '模板已更新', + templateDeleted: '模板已删除', insertUrl: '通过 URL 插入图片', insert: '插入', cancel: '取消', diff --git a/src/utils/proBlock.js b/src/utils/proBlock.js new file mode 100644 index 0000000..eb44df3 --- /dev/null +++ b/src/utils/proBlock.js @@ -0,0 +1,72 @@ +export const PRO_BLOCK_NODE_TYPE = 'pro_block' +export const PRO_TRIGGER_TEXT = '[PRO]' +export const PRO_DISPLAY_LABEL = 'PRO模式思考' + +const PRO_PENDING_PREFIX = '[Pro][{' +const PRO_PENDING_SUFFIX = '}]' +const PRO_TRIGGER_RE = /^\[pro\]$/i + +function normalizeMarkdownText(value = '') { + return String(value || '').replace(/\r\n?/g, '\n') +} + +export function escapeProBlockContent(value = '') { + return normalizeMarkdownText(value) + .replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/]/g, '\\]') + .replace(/}/g, '\\}') +} + +export function unescapeProBlockContent(value = '') { + const normalized = String(value || '') + let result = '' + + for (let index = 0; index < normalized.length; index += 1) { + const char = normalized[index] + if (char !== '\\' || index === normalized.length - 1) { + result += char + continue + } + + const next = normalized[index + 1] + if (next === 'n') { + result += '\n' + } else { + result += next + } + index += 1 + } + + return result +} + +export function serializeProBlockSyntax(content = '') { + const normalized = normalizeMarkdownText(content) + if (!normalized) return PRO_TRIGGER_TEXT + return `${PRO_PENDING_PREFIX}${escapeProBlockContent(normalized)}${PRO_PENDING_SUFFIX}` +} + +export function parseProBlockSyntax(value = '') { + const text = normalizeMarkdownText(value).trim() + if (!text) return null + + if (PRO_TRIGGER_RE.test(text)) { + return { + content: '', + autoStart: true, + } + } + + const prefix = text.slice(0, PRO_PENDING_PREFIX.length) + if (prefix.toLowerCase() === PRO_PENDING_PREFIX.toLowerCase() && text.endsWith(PRO_PENDING_SUFFIX)) { + return { + content: unescapeProBlockContent( + text.slice(PRO_PENDING_PREFIX.length, text.length - PRO_PENDING_SUFFIX.length) + ), + autoStart: false, + } + } + + return null +} \ No newline at end of file diff --git a/src/utils/uploadBlock.js b/src/utils/uploadBlock.js new file mode 100644 index 0000000..2181a78 --- /dev/null +++ b/src/utils/uploadBlock.js @@ -0,0 +1,220 @@ +export const UPLOAD_BLOCK_NODE_TYPE = 'upload_block' + +export const DEFAULT_UPLOAD_BLOCK_TYPES = [ + 'docx', + 'pptx', + 'pdf', + 'txt', + 'json', + 'toml', + 'yaml', + 'images', +] + +const TYPE_ALIAS = { + doc: 'docx', + docx: 'docx', + word: 'docx', + ppt: 'pptx', + pptx: 'pptx', + powerpoint: 'pptx', + pdf: 'pdf', + txt: 'txt', + text: 'txt', + plain: 'txt', + json: 'json', + toml: 'toml', + yaml: 'yaml', + yml: 'yaml', + '[images]': 'images', + image: 'images', + images: 'images', +} + +const TYPE_LABELS = { + docx: 'DOCX', + pptx: 'PPTX', + pdf: 'PDF', + txt: 'TXT', + json: 'JSON', + toml: 'TOML', + yaml: 'YAML', + images: '图片', +} + +const TYPE_ACCEPT_MAP = { + docx: [ + '.docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ], + pptx: [ + '.pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + pdf: [ + '.pdf', + 'application/pdf', + ], + txt: [ + '.txt', + 'text/plain', + ], + json: [ + '.json', + 'application/json', + ], + toml: [ + '.toml', + 'application/toml', + 'text/toml', + ], + yaml: [ + '.yaml', + '.yml', + 'text/yaml', + 'text/x-yaml', + 'application/x-yaml', + ], + images: ['image/*'], +} + +const IMAGE_EXT_RE = /\.(png|jpe?g|gif|webp|bmp|svg|heic|heif|avif)$/i +const SYNTAX_RE = /^\{\{\{([\s\S]*?)\}\}\}$/ +const UPLOAD_TYPE_PREFIX_RE = /^upload\s+file\s+type\s*:\s*(.+)$/i + +function parseStrictUploadBlockTypes(values = []) { + const normalized = [] + + for (const value of values) { + const next = normalizeUploadBlockType(value) + if (!next) return null + if (!normalized.includes(next)) { + normalized.push(next) + } + } + + return normalized.length > 0 ? normalized : null +} + +export function normalizeUploadBlockType(value = '') { + const key = String(value || '').trim().toLowerCase() + return TYPE_ALIAS[key] || '' +} + +export function normalizeUploadBlockTypes(value) { + const source = Array.isArray(value) ? value : [] + const normalized = [] + + for (const item of source) { + const next = normalizeUploadBlockType(item) + if (!next || normalized.includes(next)) continue + normalized.push(next) + } + + return normalized.length > 0 ? normalized : [...DEFAULT_UPLOAD_BLOCK_TYPES] +} + +export function getUploadBlockMenuOptions(allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + return normalizeUploadBlockTypes(allowedTypes).map((type) => ({ + value: type, + label: TYPE_LABELS[type] || type.toUpperCase(), + })) +} + +export function getUploadBlockAccept(allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + const entries = new Set() + + normalizeUploadBlockTypes(allowedTypes).forEach((type) => { + const values = TYPE_ACCEPT_MAP[type] || [] + values.forEach((entry) => entries.add(entry)) + }) + + return Array.from(entries).join(',') +} + +export function getUploadBlockAcceptForType(type = '') { + const normalized = normalizeUploadBlockType(type) + if (!normalized) return '' + return (TYPE_ACCEPT_MAP[normalized] || []).join(',') +} + +function parseUploadBlockInner(raw = '') { + const inner = String(raw || '').trim() + if (!inner) { + return { allowedTypes: [...DEFAULT_UPLOAD_BLOCK_TYPES] } + } + + const matched = inner.match(UPLOAD_TYPE_PREFIX_RE) + if (!matched) return null + + const segments = matched[1] + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + + if (segments.length === 0) return null + + const allowedTypes = parseStrictUploadBlockTypes(segments) + if (!allowedTypes) return null + + return allowedTypes.length > 0 ? { allowedTypes } : null +} + +export function parseUploadBlockSyntax(value = '') { + const matched = String(value || '').trim().match(SYNTAX_RE) + if (!matched) return null + return parseUploadBlockInner(matched[1]) +} + +export function serializeUploadBlockSyntax(allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + const normalized = normalizeUploadBlockTypes(allowedTypes) + const isDefault = + normalized.length === DEFAULT_UPLOAD_BLOCK_TYPES.length && + normalized.every((type, index) => type === DEFAULT_UPLOAD_BLOCK_TYPES[index]) + + if (isDefault) return '{{{}}}' + + const serialized = normalized + .map((type) => (type === 'images' ? '[images]' : type)) + .join(',') + + return `{{{upload file type:${serialized}}}}` +} + +export function isUploadBlockImageFile(file) { + if (!file) return false + const name = String(file.name || '').toLowerCase() + const type = String(file.type || '').toLowerCase() + return type.startsWith('image/') || IMAGE_EXT_RE.test(name) +} + +export function getUploadBlockFileType(file) { + if (!file) return '' + if (isUploadBlockImageFile(file)) return 'images' + + const name = String(file.name || '').toLowerCase() + if (name.endsWith('.docx')) return 'docx' + if (name.endsWith('.pptx')) return 'pptx' + if (name.endsWith('.pdf')) return 'pdf' + if (name.endsWith('.json')) return 'json' + if (name.endsWith('.toml')) return 'toml' + if (name.endsWith('.yaml') || name.endsWith('.yml')) return 'yaml' + if (name.endsWith('.txt')) return 'txt' + + const mime = String(file.type || '').toLowerCase() + if (mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') return 'docx' + if (mime === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') return 'pptx' + if (mime === 'application/pdf') return 'pdf' + if (mime === 'application/json') return 'json' + if (mime === 'application/toml' || mime === 'text/toml') return 'toml' + if (mime === 'text/yaml' || mime === 'text/x-yaml' || mime === 'application/x-yaml') return 'yaml' + if (mime === 'text/plain') return 'txt' + + return '' +} + +export function isUploadBlockTypeAllowed(file, allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + const fileType = getUploadBlockFileType(file) + if (!fileType) return false + return normalizeUploadBlockTypes(allowedTypes).includes(fileType) +} \ No newline at end of file diff --git a/src/views/UniverView.vue b/src/views/UniverView.vue deleted file mode 100644 index 481df2a..0000000 --- a/src/views/UniverView.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - -