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') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ isPresetTemplate(selectedTemplate) ? t('presetTemplates') : t('customTemplates') }}
+
+ {{ templateFormError }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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 @@
+
+
+
+
+
+
+
+
+
正在加载预览
+
仅在当前文件需要时才按需加载预览模块,避免初始页面加载过重。
+
+
+
+
预览失败
+
{{ errorMessage }}
+
+
+
+
+
+
当前格式不再使用内置重型预览引擎
+
为了降低移动端包体积和运行负担,Excel 与 PowerPoint 文件改为下载后使用本地应用查看。
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+
+
+
+ {{ isBusy ? '正在思考' : '等待结果' }}
+ {{ isBusy ? '流式结果会在这里展开。' : '点击重做重新发起一次深度思考。' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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('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 @@
-
-
-
-
-
-
-
-
{{ t('selectOfficeFile') || '请选择 Office 文件开始编辑' }}
-
{{ t('supportedFormats') || '支持 DOCX、XLSX、PPTX 格式' }}
-
-
-
-
-
-
-
-
-
{{ t('selectExportFormat') || '选择导出格式' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
正在初始化 Univer 引擎...
-
提示:当前为空白预览模式
-
-
-
-
⚠
-
预览初始化失败
-
{{ error }}
-
-
提示:当前为空白文档预览模式,Univer 开源版本不支持直接加载 Office 文件内容。
-
-
-
-
-
-
-
-
-
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(` `)
+ 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 @@
-
-
-
-
-
-
-
-
-