diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 019fbb6baea6..68b7bf27d534 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -3,7 +3,7 @@ name: Release on: push: branches: - - main # or whatever branch you want to use + - master # or whatever branch you want to use jobs: release: diff --git a/README.md b/README.md index 5d846c2dd1f7..a7001b7962db 100644 --- a/README.md +++ b/README.md @@ -1,233 +1,106 @@ -# Open WebUI 👋 +
-![GitHub stars](https://img.shields.io/github/stars/open-webui/open-webui?style=social) -![GitHub forks](https://img.shields.io/github/forks/open-webui/open-webui?style=social) -![GitHub watchers](https://img.shields.io/github/watchers/open-webui/open-webui?style=social) -![GitHub repo size](https://img.shields.io/github/repo-size/open-webui/open-webui) -![GitHub language count](https://img.shields.io/github/languages/count/open-webui/open-webui) -![GitHub top language](https://img.shields.io/github/languages/top/open-webui/open-webui) -![GitHub last commit](https://img.shields.io/github/last-commit/open-webui/open-webui?color=red) -[![Discord](https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white)](https://discord.gg/5rJgQTnV4s) -[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/tjbck) +# Kael - JumpServer Chat AI powered by Open WebUI -**Open WebUI is an [extensible](https://docs.openwebui.com/features/plugin/), feature-rich, and user-friendly self-hosted AI platform designed to operate entirely offline.** It supports various LLM runners like **Ollama** and **OpenAI-compatible APIs**, with **built-in inference engine** for RAG, making it a **powerful AI deployment solution**. +基于 [open-webui](https://github.com/open-webui/open-webui) -Passionate about open-source AI? [Join our team →](https://careers.openwebui.com/) +
-![Open WebUI Demo](./demo.gif) +## 项目简介 -> [!TIP] -> **Looking for an [Enterprise Plan](https://docs.openwebui.com/enterprise)?** – **[Speak with Our Sales Team Today!](https://docs.openwebui.com/enterprise)** -> -> Get **enhanced capabilities**, including **custom theming and branding**, **Service Level Agreement (SLA) support**, **Long-Term Support (LTS) versions**, and **more!** +本项目在开源的 Open WebUI 基础上进行集成与深度定制,重点优化界面适配、交互反馈与主题风格,使其更契合现有产品体系。我们保留了 Open WebUI 的核心能力,并针对业务需求扩展了页面布局、导航策略以及常用功能入口,以提升在真实业务场景中的可用性与一致性。 -For more information, be sure to check out our [Open WebUI Documentation](https://docs.openwebui.com/). +## 快速开始 -## Key Features of Open WebUI ⭐ +### 运行前置 -- 🚀 **Effortless Setup**: Install seamlessly using Docker or Kubernetes (kubectl, kustomize or helm) for a hassle-free experience with support for both `:ollama` and `:cuda` tagged images. +- Node.js `>= 18.13.0 <= 22.x.x` +- npm `>= 6.0.0` -- 🤝 **Ollama/OpenAI API Integration**: Effortlessly integrate OpenAI-compatible APIs for versatile conversations alongside Ollama models. Customize the OpenAI API URL to link with **LMStudio, GroqCloud, Mistral, OpenRouter, and more**. +推荐使用 [nvm](https://github.com/nvm-sh/nvm) 管理 Node 版本: -- 🛡️ **Granular Permissions and User Groups**: By allowing administrators to create detailed user roles and permissions, we ensure a secure user environment. This granularity not only enhances security but also allows for customized user experiences, fostering a sense of ownership and responsibility amongst users. - -- 🔄 **SCIM 2.0 Support**: Enterprise-grade user and group provisioning through SCIM 2.0 protocol, enabling seamless integration with identity providers like Okta, Azure AD, and Google Workspace for automated user lifecycle management. - -- 📱 **Responsive Design**: Enjoy a seamless experience across Desktop PC, Laptop, and Mobile devices. - -- 📱 **Progressive Web App (PWA) for Mobile**: Enjoy a native app-like experience on your mobile device with our PWA, providing offline access on localhost and a seamless user interface. - -- ✒️🔢 **Full Markdown and LaTeX Support**: Elevate your LLM experience with comprehensive Markdown and LaTeX capabilities for enriched interaction. - -- 🎤📹 **Hands-Free Voice/Video Call**: Experience seamless communication with integrated hands-free voice and video call features, allowing for a more dynamic and interactive chat environment. - -- 🛠️ **Model Builder**: Easily create Ollama models via the Web UI. Create and add custom characters/agents, customize chat elements, and import models effortlessly through [Open WebUI Community](https://openwebui.com/) integration. - -- 🐍 **Native Python Function Calling Tool**: Enhance your LLMs with built-in code editor support in the tools workspace. Bring Your Own Function (BYOF) by simply adding your pure Python functions, enabling seamless integration with LLMs. - -- 📚 **Local RAG Integration**: Dive into the future of chat interactions with groundbreaking Retrieval Augmented Generation (RAG) support. This feature seamlessly integrates document interactions into your chat experience. You can load documents directly into the chat or add files to your document library, effortlessly accessing them using the `#` command before a query. - -- 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, `Serply`, `DuckDuckGo`, `TavilySearch`, `SearchApi` and `Bing` and inject the results directly into your chat experience. - -- 🌐 **Web Browsing Capability**: Seamlessly integrate websites into your chat experience using the `#` command followed by a URL. This feature allows you to incorporate web content directly into your conversations, enhancing the richness and depth of your interactions. - -- 🎨 **Image Generation Integration**: Seamlessly incorporate image generation capabilities using options such as AUTOMATIC1111 API or ComfyUI (local), and OpenAI's DALL-E (external), enriching your chat experience with dynamic visual content. - -- ⚙️ **Many Models Conversations**: Effortlessly engage with various models simultaneously, harnessing their unique strengths for optimal responses. Enhance your experience by leveraging a diverse set of models in parallel. - -- 🔐 **Role-Based Access Control (RBAC)**: Ensure secure access with restricted permissions; only authorized individuals can access your Ollama, and exclusive model creation/pulling rights are reserved for administrators. - -- 🌐🌍 **Multilingual Support**: Experience Open WebUI in your preferred language with our internationalization (i18n) support. Join us in expanding our supported languages! We're actively seeking contributors! - -- 🧩 **Pipelines, Open WebUI Plugin Support**: Seamlessly integrate custom logic and Python libraries into Open WebUI using [Pipelines Plugin Framework](https://github.com/open-webui/pipelines). Launch your Pipelines instance, set the OpenAI URL to the Pipelines URL, and explore endless possibilities. [Examples](https://github.com/open-webui/pipelines/tree/main/examples) include **Function Calling**, User **Rate Limiting** to control access, **Usage Monitoring** with tools like Langfuse, **Live Translation with LibreTranslate** for multilingual support, **Toxic Message Filtering** and much more. - -- 🌟 **Continuous Updates**: We are committed to improving Open WebUI with regular updates, fixes, and new features. - -Want to learn more about Open WebUI's features? Check out our [Open WebUI documentation](https://docs.openwebui.com/features) for a comprehensive overview! - ---- - -We are incredibly grateful for the generous support of our sponsors. Their contributions help us to maintain and improve our project, ensuring we can continue to deliver quality work to our community. Thank you! - -## How to Install 🚀 - -### Installation via Python pip 🐍 - -Open WebUI can be installed using pip, the Python package installer. Before proceeding, ensure you're using **Python 3.11** to avoid compatibility issues. - -1. **Install Open WebUI**: - Open your terminal and run the following command to install Open WebUI: - - ```bash - pip install open-webui - ``` - -2. **Running Open WebUI**: - After installation, you can start Open WebUI by executing: - - ```bash - open-webui serve - ``` - -This will start the Open WebUI server, which you can access at [http://localhost:8083](http://localhost:8083) - -### Quick Start with Docker 🐳 - -> [!NOTE] -> Please note that for certain Docker environments, additional configurations might be needed. If you encounter any connection issues, our detailed guide on [Open WebUI Documentation](https://docs.openwebui.com/) is ready to assist you. - -> [!WARNING] -> When using Docker to install Open WebUI, make sure to include the `-v open-webui:/app/backend/data` in your Docker command. This step is crucial as it ensures your database is properly mounted and prevents any loss of data. - -> [!TIP] -> If you wish to utilize Open WebUI with Ollama included or CUDA acceleration, we recommend utilizing our official images tagged with either `:cuda` or `:ollama`. To enable CUDA, you must install the [Nvidia CUDA container toolkit](https://docs.nvidia.com/dgx/nvidia-container-runtime-upgrade/) on your Linux/WSL system. - -### Installation with Default Configuration - -- **If Ollama is on your computer**, use this command: - - ```bash - docker run -d -p 3000:8083 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main - ``` - -- **If Ollama is on a Different Server**, use this command: - - To connect to Ollama on another server, change the `OLLAMA_BASE_URL` to the server's URL: - - ```bash - docker run -d -p 3000:8083 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main - ``` - -- **To run Open WebUI with Nvidia GPU support**, use this command: - - ```bash - docker run -d -p 3000:8083 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda - ``` - -### Installation for OpenAI API Usage Only - -- **If you're only using OpenAI API**, use this command: - - ```bash - docker run -d -p 3000:8083 -e OPENAI_API_KEY=your_secret_key -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main - ``` - -### Installing Open WebUI with Bundled Ollama Support - -This installation method uses a single container image that bundles Open WebUI with Ollama, allowing for a streamlined setup via a single command. Choose the appropriate command based on your hardware setup: - -- **With GPU Support**: - Utilize GPU resources by running the following command: - - ```bash - docker run -d -p 3000:8083 --gpus=all -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama - ``` - -- **For CPU Only**: - If you're not using a GPU, use this command instead: - - ```bash - docker run -d -p 3000:8083 -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:ollama - ``` - -Both commands facilitate a built-in, hassle-free installation of both Open WebUI and Ollama, ensuring that you can get everything up and running swiftly. - -After installation, you can access Open WebUI at [http://localhost:3000](http://localhost:3000). Enjoy! 😄 - -### Other Installation Methods - -We offer various installation alternatives, including non-Docker native installation methods, Docker Compose, Kustomize, and Helm. Visit our [Open WebUI Documentation](https://docs.openwebui.com/getting-started/) or join our [Discord community](https://discord.gg/5rJgQTnV4s) for comprehensive guidance. - -Look at the [Local Development Guide](https://docs.openwebui.com/getting-started/advanced-topics/development) for instructions on setting up a local development environment. - -### Troubleshooting - -Encountering connection issues? Our [Open WebUI Documentation](https://docs.openwebui.com/troubleshooting/) has got you covered. For further assistance and to join our vibrant community, visit the [Open WebUI Discord](https://discord.gg/5rJgQTnV4s). +```bash +nvm use 20 +``` -#### Open WebUI: Server Connection Error +### 安装依赖 -If you're experiencing connection issues, it’s often due to the WebUI docker container not being able to reach the Ollama server at 127.0.0.1:11434 (host.docker.internal:11434) inside the container . Use the `--network=host` flag in your docker command to resolve this. Note that the port changes from 3000 to 8083, resulting in the link: `http://localhost:8083`. +```bash +npm install +``` -**Example Docker Command**: +### 本地开发 ```bash -docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main +npm run dev ``` -### Keeping Your Docker Installation Up-to-Date - -In case you want to update your local Docker installation to the latest version, you can do it with [Watchtower](https://containrrr.dev/watchtower/): +默认会启动 Vite 开发服务器并监听局域网访问;若需固定端口,可执行: ```bash -docker run --rm --volume /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --run-once open-webui +npm run dev:5050 ``` -In the last part of the command, replace `open-webui` with your container name if it is different. +### 质量检查 -Check our Updating Guide available in our [Open WebUI Documentation](https://docs.openwebui.com/getting-started/updating). +- 前端类型检查:`npm run check` +- ESLint & 格式化修复:`npm run lint` / `npm run format` +- 前端单测 (Vitest):`npm run test:frontend` +- 后端代码质量 (pylint):`npm run lint:backend` -### Using the Dev Branch 🌙 +> 若仅需要自动修复前端 lint,可执行 `npm run lint:frontend`。 -> [!WARNING] -> The `:dev` branch contains the latest unstable features and changes. Use it at your own risk as it may have bugs or incomplete features. - -If you want to try out the latest bleeding-edge features and are okay with occasional instability, you can use the `:dev` tag like this: +### 构建与预览 ```bash -docker run -d -p 3000:8083 -v open-webui:/app/backend/data --name open-webui --add-host=host.docker.internal:host-gateway --restart always ghcr.io/open-webui/open-webui:dev +npm run build +npm run preview ``` -### Offline Mode +`build` 将生成生产环境静态资源,`preview` 用于本地验证构建产物。 -If you are running Open WebUI in an offline environment, you can set the `HF_HUB_OFFLINE` environment variable to `1` to prevent attempts to download models from the internet. +## 项目结构速览 -```bash -export HF_HUB_OFFLINE=1 ``` +├── backend/ # 后端接口与服务 +├── src/ # Svelte 前端源码 +│ ├── lib/ # 可复用组件、hooks 与工具 +│ ├── routes/ # 页面与布局 +│ └── app.d.ts # 类型定义与应用配置 +├── static/ # 静态资源 +├── svelte.config.js # SvelteKit 配置 +├── package.json # npm 配置与脚本 +└── README.md # 项目说明 +``` + +> 目录仅示意关键部分,实际结构以仓库为准。 -## What's Next? 🌟 +## 定制说明 -Discover upcoming features on our roadmap in the [Open WebUI Documentation](https://docs.openwebui.com/roadmap/). +- **样式主题**:统一由 `src/lib/theme` 及相关 Tailwind 配置管理;可通过 `tailwind.config.js` 调整设计变量。 +- **导航与路由**:集中在 `src/routes` 目录,采用 SvelteKit 默认约定;新增页面时请确保更新导航映射。 +- **接口对接**:后端接口位于 `backend/`,前端通过 `src/lib/api` 进行封装与调用。 +- **国际化**:若启用多语言,请使用 `npm run i18n:parse` 更新词条,并在 `src/lib/i18n` 维护翻译资源。 -## License 📜 +## 部署建议 -This project contains code under multiple licenses. The current codebase includes components licensed under the Open WebUI License with an additional requirement to preserve the "Open WebUI" branding, as well as prior contributions under their respective original licenses. For a detailed record of license changes and the applicable terms for each section of the code, please refer to [LICENSE_HISTORY](./LICENSE_HISTORY). For complete and updated licensing details, please see the [LICENSE](./LICENSE) and [LICENSE_HISTORY](./LICENSE_HISTORY) files. +1. 执行 `npm run build` 生成静态资源。 +2. 根据部署目标选择适配器(默认 `adapter-auto`),或在 `svelte.config.js` 中切换为 `adapter-node`/`adapter-static`。 +3. 将 `build/` 目录产物整合进主产品的构建流程中,确保静态资源路径与代理配置一致。 +4. 若集成在现有后端中,建议使用反向代理统一鉴权与会话管理。 -## Support 💬 +## 常见问题 -If you have any questions, suggestions, or need assistance, please open an issue or join our -[Open WebUI Discord community](https://discord.gg/5rJgQTnV4s) to connect with us! 🤝 +- **依赖安装失败**:请确认 Node 与 npm 版本符合要求,可尝试删除 `node_modules` 与 `package-lock.json` 后重新安装。 +- **样式未生效**:确认已执行 `npm run dev`,并检查 Tailwind 配置是否被覆盖。 +- **构建体积过大**:可通过移除未使用的插件、启用代码分割或调整 `vite.config.ts` 进行优化。 -## Star History +## 贡献指南 - - - - - Star History Chart - - +1. Fork 仓库并新建分支(推荐 `feature/xxx` 或 `fix/xxx` 命名)。 +2. 完成开发后执行 `npm run lint` 与 `npm run test:frontend` 确保质量。 +3. 提交 Pull Request,并在描述中说明变更背景与测试情况。 ---- +## 许可证 -Created by [Timothy Jaeryang Baek](https://github.com/tjbck) - Let's make Open WebUI even more amazing together! 💪 +本项目基于 Open WebUI,遵循其上游开源协议。项目内新增或修改的部分,如无特殊声明,默认沿用上游许可。请在合规前提下使用与分发。 diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 14f7e61f018e..4a2d271d0c89 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -956,7 +956,7 @@ def feishu_oauth_register(oauth: OAuth): ENABLE_OLLAMA_API = PersistentConfig( "ENABLE_OLLAMA_API", "ollama.enable", - os.environ.get("ENABLE_OLLAMA_API", "True").lower() == "true", + os.environ.get("ENABLE_OLLAMA_API", "False").lower() == "true", ) OLLAMA_API_BASE_URL = os.environ.get( @@ -1114,14 +1114,14 @@ def feishu_oauth_register(oauth: OAuth): ( False if not WEBUI_AUTH - else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true" + else os.environ.get("ENABLE_SIGNUP", "False").lower() == "true" ), ) ENABLE_LOGIN_FORM = PersistentConfig( "ENABLE_LOGIN_FORM", "ui.ENABLE_LOGIN_FORM", - os.environ.get("ENABLE_LOGIN_FORM", "True").lower() == "true", + os.environ.get("ENABLE_LOGIN_FORM", "False").lower() == "true", ) @@ -1322,11 +1322,11 @@ def feishu_oauth_register(oauth: OAuth): ) USER_PERMISSIONS_CHAT_TTS = ( - os.environ.get("USER_PERMISSIONS_CHAT_TTS", "True").lower() == "true" + os.environ.get("USER_PERMISSIONS_CHAT_TTS", "False").lower() == "true" ) USER_PERMISSIONS_CHAT_CALL = ( - os.environ.get("USER_PERMISSIONS_CHAT_CALL", "True").lower() == "true" + os.environ.get("USER_PERMISSIONS_CHAT_CALL", "False").lower() == "true" ) USER_PERMISSIONS_CHAT_MULTIPLE_MODELS = ( @@ -1426,13 +1426,13 @@ def feishu_oauth_register(oauth: OAuth): ENABLE_NOTES = PersistentConfig( "ENABLE_NOTES", "notes.enable", - os.environ.get("ENABLE_NOTES", "True").lower() == "true", + os.environ.get("ENABLE_NOTES", "False").lower() == "true", ) ENABLE_EVALUATION_ARENA_MODELS = PersistentConfig( "ENABLE_EVALUATION_ARENA_MODELS", "evaluation.arena.enable", - os.environ.get("ENABLE_EVALUATION_ARENA_MODELS", "True").lower() == "true", + os.environ.get("ENABLE_EVALUATION_ARENA_MODELS", "False").lower() == "true", ) EVALUATION_ARENA_MODELS = PersistentConfig( "EVALUATION_ARENA_MODELS", @@ -1475,7 +1475,7 @@ def feishu_oauth_register(oauth: OAuth): ENABLE_COMMUNITY_SHARING = PersistentConfig( "ENABLE_COMMUNITY_SHARING", "ui.enable_community_sharing", - os.environ.get("ENABLE_COMMUNITY_SHARING", "True").lower() == "true", + os.environ.get("ENABLE_COMMUNITY_SHARING", "False").lower() == "true", ) ENABLE_MESSAGE_RATING = PersistentConfig( @@ -1595,13 +1595,36 @@ class BannerModel(BaseModel): "task.title.prompt_template", os.environ.get("TITLE_GENERATION_PROMPT_TEMPLATE", ""), ) +# TODO: re-enable examples after testing +# DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task: +# Generate a concise, 3-5 word title with an emoji summarizing the chat history. +# ### Guidelines: +# - The title should clearly represent the main theme or subject of the conversation. +# - Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting. +# - Write the title in the chat's primary language; default to English if multilingual. +# - Prioritize accuracy over excessive creativity; keep it clear and simple. +# - Your entire response must consist solely of the JSON object, without any introductory or concluding text. +# - The output must be a single, raw JSON object, without any markdown code fences or other encapsulating text. +# - Ensure no conversational text, affirmations, or explanations precede or follow the raw JSON output, as this will cause direct parsing failure. +# ### Output: +# JSON format: { "title": "your concise title here" } +# ### Examples: +# - { "title": "📉 Stock Market Trends" }, +# - { "title": "🍪 Perfect Chocolate Chip Recipe" }, +# - { "title": "Evolution of Music Streaming" }, +# - { "title": "Remote Work Productivity Tips" }, +# - { "title": "Artificial Intelligence in Healthcare" }, +# - { "title": "🎮 Video Game Development Insights" } +# ### Chat History: +# +# {{MESSAGES:END:2}} +# """ DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task: -Generate a concise, 3-5 word title with an emoji summarizing the chat history. +Generate a concise, 3-5 word title. ### Guidelines: - The title should clearly represent the main theme or subject of the conversation. -- Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting. -- Write the title in the chat's primary language; default to English if multilingual. +- Use the primary language of the chat to write the title; if the chat contains multiple languages, generate the title in the primary language used. - Prioritize accuracy over excessive creativity; keep it clear and simple. - Your entire response must consist solely of the JSON object, without any introductory or concluding text. - The output must be a single, raw JSON object, without any markdown code fences or other encapsulating text. @@ -1609,12 +1632,12 @@ class BannerModel(BaseModel): ### Output: JSON format: { "title": "your concise title here" } ### Examples: -- { "title": "📉 Stock Market Trends" }, -- { "title": "🍪 Perfect Chocolate Chip Recipe" }, +- { "title": "Stock Market Trends" }, +- { "title": "Perfect Chocolate Chip Recipe" }, - { "title": "Evolution of Music Streaming" }, - { "title": "Remote Work Productivity Tips" }, - { "title": "Artificial Intelligence in Healthcare" }, -- { "title": "🎮 Video Game Development Insights" } +- { "title": "Video Game Development Insights" } ### Chat History: {{MESSAGES:END:2}} diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 87f8ef12f3c7..45afb546eef2 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -111,7 +111,7 @@ log.setLevel(SRC_LOG_LEVELS["CONFIG"]) -WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI") +WEBUI_NAME = os.environ.get("WEBUI_NAME", "JumpServer Chat AI &") if WEBUI_NAME != "Open WebUI": WEBUI_NAME += " (Open WebUI)" diff --git a/backend/open_webui/jms/chat.py b/backend/open_webui/jms/chat.py index 0eeaaa396f09..8f4d56a524ae 100644 --- a/backend/open_webui/jms/chat.py +++ b/backend/open_webui/jms/chat.py @@ -18,25 +18,25 @@ class ChatHandler(BaseWisp): def list(self, query: Optional[Dict[str, Any]] = None) -> List[dict]: query = self._normalize_query(query) resp = self._request("GET", CHAT_URL, query=query, action="list chats") - return self._loads(resp.body) + return self._loads(CHAT_URL, resp.body) def retrieve(self, chat_id: str) -> dict: self._ensure_id(chat_id) path = f"{CHAT_URL}{chat_id}/" resp = self._request("GET", path, action=f"retrieve chat {chat_id}") - return self._loads(resp.body) + return self._loads(path, resp.body) def create(self, data: Dict[str, Any]) -> dict: body = self._dumps(data) resp = self._request("POST", CHAT_URL, body=body, action="create chat") - return self._loads(resp.body) + return self._loads(CHAT_URL, resp.body) def update(self, chat_id: Optional[str] = None, data: Dict[str, Any] = None, query: Dict[str, Any] = None) -> dict: self._ensure_id(chat_id) path = f"{CHAT_URL}{chat_id}/" body = self._dumps(data) resp = self._request("PATCH", path, query=query, body=body, action=f"update chat {chat_id}") - return self._loads(resp.body) + return self._loads(path, resp.body) def destroy(self, chat_id: Optional[str] = None, query: Dict[str, Any] = None) -> None: """ @@ -100,11 +100,11 @@ def _dumps(data: Dict[str, Any]) -> bytes: raise WispError(f"Failed to serialize request body: {e}") from e @staticmethod - def _loads(b: bytes) -> Any: + def _loads(path: str, b: bytes) -> Any: try: return json.loads(b.decode("utf-8")) if b else None except (UnicodeDecodeError, json.JSONDecodeError) as e: - raise WispError(f"Failed to parse response body: {e}") from e + raise WispError(f"Failed to parse response body: {e} path {path}") from e @staticmethod def _safe_decode(b: Optional[bytes]) -> str: diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index e67f8fe2c59f..8d6b1fc03b56 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -114,6 +114,7 @@ class ChatResponse(BaseModel): archived: bool pinned: Optional[bool] = False meta: dict = {} + session_info: dict = {} folder_id: Optional[str] = None @@ -150,7 +151,7 @@ def insert_new_chat(self, form_data: ChatForm, sid: str, request: Request, user: 'title': form_data.chat['title'] if 'title' in form_data.chat else 'New Chat', 'chat': form_data.chat, 'socket_id': sid, - 'folder_id': form_data.folder_id, + 'folder_id': form_data.folder_id or '', 'session_info': { 'org_id': session.org_id, 'asset': session.asset, @@ -194,7 +195,7 @@ def import_chat( } ) - result = Chat(**chat.model_dump()) + result = Chat(**chat) db.add(result) db.commit() db.refresh(result) @@ -230,7 +231,7 @@ def update_chat_tags_by_id( self.delete_all_tags_by_id_and_user_id(id, user.id) - for tag in chat.meta.get("tags", []): + for tag in chat['meta'].get("tags", []): if self.count_chats_by_tag_name_and_user_id(tag, user.id) == 0: Tags.delete_tag_by_name_and_user_id(tag, user.id) @@ -315,19 +316,19 @@ def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: # Get the existing chat to share chat = db.get(Chat, chat_id) # Check if the chat is already shared - if chat.share_id: - return self.get_chat_by_id_and_user_id(chat.share_id, "shared") + if chat['share_id']: + return self.get_chat_by_id_and_user_id(chat['share_id'], "shared") # Create a new chat with the same data, but with a new ID shared_chat = ChatModel( **{ "id": str(uuid.uuid4()), "user_id": f"shared-{chat_id}", - "title": chat.title, - "chat": chat.chat, - "meta": chat.meta, - "pinned": chat.pinned, - "folder_id": chat.folder_id, - "created_at": chat.created_at, + "title": chat['title'], + "chat": chat['chat'], + "meta": chat['meta'], + "pinned": chat['pinned'], + "folder_id": chat['folder_id'], + "created_at": chat['created_at'], "updated_at": int(time.time()), } ) @@ -357,11 +358,11 @@ def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: if shared_chat is None: return self.insert_shared_chat_by_chat_id(chat_id) - shared_chat.title = chat.title - shared_chat.chat = chat.chat - shared_chat.meta = chat.meta - shared_chat.pinned = chat.pinned - shared_chat.folder_id = chat.folder_id + shared_chat.title = chat['title'] + shared_chat.chat = chat['chat'] + shared_chat.meta = chat['meta'] + shared_chat.pinned = chat['pinned'] + shared_chat.folder_id = chat['folder_id'] shared_chat.updated_at = int(time.time()) db.commit() db.refresh(shared_chat) @@ -702,7 +703,7 @@ def update_chat_folder_id_by_id_and_user_id( def get_chat_tags_by_id_and_user_id(self, id: str, user_id: str) -> list[TagModel]: with get_db() as db: chat = db.get(Chat, id) - tags = chat.meta.get("tags", []) + tags = chat['meta'].get("tags", []) return [Tags.get_tag_by_name_and_user_id(tag, user_id) for tag in tags] # TODO @@ -749,10 +750,10 @@ def add_chat_tag_by_id_and_user_id_and_tag_name( chat = db.get(Chat, _id) tag_id = tag.id - if tag_id not in chat.meta.get("tags", []): + if tag_id not in chat['meta'].get("tags", []): chat.meta = { - **chat.meta, - "tags": list(set(chat.meta.get("tags", []) + [tag_id])), + **chat['meta'], + "tags": list(set(chat['meta'].get("tags", []) + [tag_id])), } db.commit() @@ -882,7 +883,7 @@ def delete_shared_chats_by_user_id(self, user_id: str) -> bool: try: with get_db() as db: chats_by_user = db.query(Chat).filter_by(user_id=user_id).all() - shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user] + shared_chat_ids = [f"shared-{chat['id']}" for chat in chats_by_user] db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete() db.commit() diff --git a/backend/open_webui/retrieval/models/external.py b/backend/open_webui/retrieval/models/external.py index 964ddcfae1b2..6b3fbd5d271b 100644 --- a/backend/open_webui/retrieval/models/external.py +++ b/backend/open_webui/retrieval/models/external.py @@ -49,8 +49,8 @@ def predict( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index da570330b372..72f8b0469fcf 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -595,9 +595,9 @@ def get_sources_from_items( # Chat Attached chat = Chats.get_chat_by_id(item.get("id")) - if chat and (user.role == "admin" or chat.user_id == user.id): - messages_map = chat.chat.get("history", {}).get("messages", {}) - message_id = chat.chat.get("history", {}).get("currentId") + if chat and (user.role == "admin" or chat['user_id'] == user.id): + messages_map = chat['chat'].get("history", {}).get("messages", {}) + message_id = chat['chat'].get("history", {}).get("currentId") if messages_map and message_id: # Reconstruct the message list in order @@ -612,7 +612,7 @@ def get_sources_from_items( # User has access to the chat query_result = { "documents": [[message_history]], - "metadatas": [[{"file_id": chat.id, "name": chat.title}]], + "metadatas": [[{"file_id": chat['id'], "name": chat['title']}]], } elif item.get("type") == "url": @@ -797,6 +797,7 @@ def get_sources_from_items( def get_model_path(model: str, update_model: bool = False): # Construct huggingface_hub kwargs with local_files_only to return the snapshot path + return cache_dir = os.getenv("SENTENCE_TRANSFORMERS_HOME") local_files_only = not update_model @@ -861,8 +862,8 @@ def generate_openai_batch_embeddings( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -910,8 +911,8 @@ def generate_azure_openai_batch_embeddings( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -960,8 +961,8 @@ def generate_ollama_batch_embeddings( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS else {} diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 45b4f1e69233..3eac52cc9ad9 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -374,8 +374,8 @@ async def speech(request: Request, user=Depends(get_verified_user)): { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS else {} diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 8889ca978db8..0eefb6d6812a 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -156,7 +156,7 @@ async def import_chat(form_data: ChatImportForm, user=Depends(get_verified_user) try: chat = Chats.import_chat(user.id, form_data) if chat: - tags = chat.meta.get("tags", []) + tags = chat['meta'].get("tags", []) for tag_id in tags: tag_id = tag_id.replace(" ", "_").lower() tag_name = " ".join([word.capitalize() for word in tag_id.split("_")]) @@ -237,7 +237,7 @@ async def get_chat_list_by_folder_id( skip = (page - 1) * limit return [ - {"title": chat.title, "id": chat.id, "updated_at": chat.updated_at} + {"title": chat['title'], "id": chat['id'], "updated_at": chat['updated_at']} for chat in Chats.get_chats_by_folder_id_and_user_id( folder_id, user.id, skip=skip, limit=limit ) @@ -439,9 +439,9 @@ async def get_user_chat_list_by_tag_name( ############################ -@router.get("/{id}", response_model=Optional[ChatResponse]) -async def get_chat_by_id(id: str, user=Depends(get_verified_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +@router.get("/{_id}", response_model=Optional[ChatResponse]) +async def get_chat_by_id(_id: str, user=Depends(get_verified_user)): + chat = Chats.get_chat_by_id_and_user_id(_id, user.id) if chat: return ChatResponse(**chat) @@ -463,7 +463,7 @@ async def update_chat_by_id( ): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - updated_chat = {**chat.chat, **form_data.chat} + updated_chat = {**chat['chat'], **form_data.chat} chat = Chats.update_chat_by_id(id, updated_chat) return ChatResponse(**chat) else: @@ -492,7 +492,7 @@ async def update_chat_message_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - if chat.user_id != user.id and user.role != "admin": + if chat['user_id'] != user.id and user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -550,7 +550,7 @@ async def send_chat_message_event_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - if chat.user_id != user.id and user.role != "admin": + if chat['user_id'] != user.id and user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -583,7 +583,7 @@ async def send_chat_message_event_by_id( async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified_user)): if user.role == "admin": chat = Chats.get_chat_by_id(id) - for tag in chat.meta.get("tags", []): + for tag in chat['meta'].get("tags", []): if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1: Tags.delete_tag_by_name_and_user_id(tag, user.id) @@ -600,7 +600,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified ) chat = Chats.get_chat_by_id(id) - for tag in chat.meta.get("tags", []): + for tag in chat['meta'].get("tags", []): if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 1: Tags.delete_tag_by_name_and_user_id(tag, user.id) @@ -617,7 +617,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_verified async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - return chat.pinned + return chat['pinned'] else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT() @@ -657,10 +657,10 @@ async def clone_chat_by_id( chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: updated_chat = { - **chat.chat, - "originalChatId": chat.id, - "branchPointMessageId": chat.chat["history"]["currentId"], - "title": form_data.title if form_data.title else f"Clone of {chat.title}", + **chat['chat'], + "originalChatId": chat['id'], + "branchPointMessageId": chat['chat']["history"]["currentId"], + "title": form_data.title if form_data.title else f"Clone of {chat['title']}", } chat = Chats.import_chat( @@ -668,9 +668,9 @@ async def clone_chat_by_id( ChatImportForm( **{ "chat": updated_chat, - "meta": chat.meta, - "pinned": chat.pinned, - "folder_id": chat.folder_id, + "meta": chat['meta'], + "pinned": chat['pinned'], + "folder_id": chat['folder_id'], } ), ) @@ -697,10 +697,10 @@ async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user)): if chat: updated_chat = { - **chat.chat, - "originalChatId": chat.id, - "branchPointMessageId": chat.chat["history"]["currentId"], - "title": f"Clone of {chat.title}", + **chat['chat'], + "originalChatId": chat['id'], + "branchPointMessageId": chat['chat']["history"]["currentId"], + "title": f"Clone of {chat['title']}", } chat = Chats.import_chat( @@ -708,9 +708,9 @@ async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user)): ChatImportForm( **{ "chat": updated_chat, - "meta": chat.meta, - "pinned": chat.pinned, - "folder_id": chat.folder_id, + "meta": chat['meta'], + "pinned": chat['pinned'], + "folder_id": chat['folder_id'], } ), ) @@ -733,13 +733,13 @@ async def archive_chat_by_id(id: str, user=Depends(get_verified_user)): chat = Chats.toggle_chat_archive_by_id(id) # Delete tags if chat is archived - if chat.archived: - for tag_id in chat.meta.get("tags", []): + if chat['archived']: + for tag_id in chat['meta'].get("tags", []): if Chats.count_chats_by_tag_name_and_user_id(tag_id, user.id) == 0: log.debug(f"deleting tag: {tag_id}") Tags.delete_tag_by_name_and_user_id(tag_id, user.id) else: - for tag_id in chat.meta.get("tags", []): + for tag_id in chat['meta'].get("tags", []): tag = Tags.get_tag_by_name_and_user_id(tag_id, user.id) if tag is None: log.debug(f"inserting tag: {tag_id}") @@ -772,11 +772,11 @@ async def share_chat_by_id(request: Request, id: str, user=Depends(get_verified_ chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - if chat.share_id: - shared_chat = Chats.update_shared_chat_by_chat_id(chat.id) + if chat['share_id']: + shared_chat = Chats.update_shared_chat_by_chat_id(chat['id']) return ChatResponse(**shared_chat) - shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id) + shared_chat = Chats.insert_shared_chat_by_chat_id(chat['id']) if not shared_chat: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -800,7 +800,7 @@ async def share_chat_by_id(request: Request, id: str, user=Depends(get_verified_ async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - if not chat.share_id: + if not chat['share_id']: return False result = Chats.delete_shared_chat_by_chat_id(id) @@ -844,11 +844,11 @@ async def update_chat_folder_id_by_id( ############################ -@router.get("/{id}/tags", response_model=list[TagModel]) -async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +@router.get("/{_id}/tags", response_model=list[TagModel]) +async def get_chat_tags_by_id(_id: str, user=Depends(get_verified_user)): + chat = Chats.get_chat_by_id_and_user_id(_id, user.id) if chat: - tags = chat.meta.get("tags", []) + tags = chat['meta'].get("tags", []) return Tags.get_tags_by_ids_and_user_id(tags, user.id) else: raise HTTPException( @@ -867,7 +867,7 @@ async def add_tag_by_id_and_tag_name( ): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - tags = chat.meta.get("tags", []) + tags = chat['meta'].get("tags", []) tag_id = form_data.name.replace(" ", "_").lower() if tag_id == "none": @@ -882,7 +882,7 @@ async def add_tag_by_id_and_tag_name( ) chat = Chats.get_chat_by_id_and_user_id(id, user.id) - tags = chat.meta.get("tags", []) + tags = chat['meta'].get("tags", []) return Tags.get_tags_by_ids_and_user_id(tags, user.id) else: raise HTTPException( @@ -907,7 +907,7 @@ async def delete_tag_by_id_and_tag_name( Tags.delete_tag_by_name_and_user_id(form_data.name, user.id) chat = Chats.get_chat_by_id_and_user_id(id, user.id) - tags = chat.meta.get("tags", []) + tags = chat['meta'].get("tags", []) return Tags.get_tags_by_ids_and_user_id(tags, user.id) else: raise HTTPException( @@ -926,7 +926,7 @@ async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user)): if chat: Chats.delete_all_tags_by_id_and_user_id(id, user.id) - for tag in chat.meta.get("tags", []): + for tag in chat['meta'].get("tags", []): if Chats.count_chats_by_tag_name_and_user_id(tag, user.id) == 0: Tags.delete_tag_by_name_and_user_id(tag, user.id) diff --git a/backend/open_webui/routers/common.py b/backend/open_webui/routers/common.py index 01640da0bfb3..b9564ec68627 100644 --- a/backend/open_webui/routers/common.py +++ b/backend/open_webui/routers/common.py @@ -214,16 +214,17 @@ async def chat_completion( raise Exception("Model not found") model = request.app.state.MODELS[model_id] - model_info = Models.get_model_by_id(model_id) + # model_info = Models.get_model_by_id(model_id) + model_info = None # Check if user has access to the model - if not BYPASS_MODEL_ACCESS_CONTROL and ( - user.role != "admin" or not BYPASS_ADMIN_ACCESS_CONTROL - ): - try: - check_model_access(user, model) - except Exception as e: - raise e + # if not BYPASS_MODEL_ACCESS_CONTROL and ( + # user.role != "admin" or not BYPASS_ADMIN_ACCESS_CONTROL + # ): + # try: + # check_model_access(user, model) + # except Exception as e: + # raise e else: model = model_item model_info = None @@ -440,7 +441,7 @@ async def list_tasks_by_chat_id_endpoint( request: Request, chat_id: str, user=Depends(get_verified_user) ): chat = Chats.get_chat_by_id(chat_id) - if chat is None or chat.user_id != user.id: + if chat is None or chat['user_id'] != user.id: return {"task_ids": []} task_ids = await list_task_ids_by_item_id(request.app.state.redis, chat_id) diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index 2a5c3e5bb169..f6993dd9f449 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -206,7 +206,7 @@ def upload_file_handler( file.file, filename, { - "OpenWebUI-User-Email": user.email, + "OpenWebUI-User-Username": user.username, "OpenWebUI-User-Id": user.id, "OpenWebUI-User-Name": user.name, "OpenWebUI-File-Id": id, diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 64b0687afa09..c73564eae3ff 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -91,7 +91,7 @@ async def send_get_request(url, key=None, user: UserModel = None): { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, + "X-OpenWebUI-User-Username": user.username, "X-OpenWebUI-User-Role": user.role, } if ENABLE_FORWARD_USER_INFO_HEADERS and user @@ -143,7 +143,7 @@ async def send_post_request( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, + "X-OpenWebUI-User-Username": user.username, "X-OpenWebUI-User-Role": user.role, **( {"X-OpenWebUI-Chat-Id": metadata.get("chat_id")} @@ -254,7 +254,7 @@ async def verify_connection( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, + "X-OpenWebUI-User-Username": user.username, "X-OpenWebUI-User-Role": user.role, } if ENABLE_FORWARD_USER_INFO_HEADERS and user @@ -478,8 +478,8 @@ async def get_ollama_tags( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -848,8 +848,8 @@ async def copy_model( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -919,8 +919,8 @@ async def delete_model( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -983,8 +983,8 @@ async def show_model_info( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -1074,8 +1074,8 @@ async def embed( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -1161,8 +1161,8 @@ async def embeddings( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 9f94d67ee4ff..d42377b395b1 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -74,8 +74,8 @@ async def send_get_request(url, key=None, user: UserModel = None): { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -144,7 +144,7 @@ async def get_headers_and_cookies( { "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, + "X-OpenWebUI-User-Username": user.username, "X-OpenWebUI-User-Role": user.role, **( {"X-OpenWebUI-Chat-Id": metadata.get("chat_id")} @@ -822,40 +822,40 @@ async def generate_chat_completion( metadata = payload.pop("metadata", None) model_id = form_data.get("model") - model_info = Models.get_model_by_id(model_id) + # model_info = Models.get_model_by_id(model_id) # Check model info and override the payload - if model_info: - if model_info.base_model_id: - payload["model"] = model_info.base_model_id - model_id = model_info.base_model_id - - params = model_info.params.model_dump() - - if params: - system = params.pop("system", None) - - payload = apply_model_params_to_body_openai(params, payload) - payload = apply_system_prompt_to_body(system, payload, metadata, user) - - # Check if user has access to the model - if not bypass_filter and user.role == "user": - if not ( - user.id == model_info.user_id - or has_access( - user.id, type="read", access_control=model_info.access_control - ) - ): - raise HTTPException( - status_code=403, - detail="Model not found", - ) - elif not bypass_filter: - if user.role != "admin": - raise HTTPException( - status_code=403, - detail="Model not found", - ) + # if model_info: + # if model_info.base_model_id: + # payload["model"] = model_info.base_model_id + # model_id = model_info.base_model_id + # + # params = model_info.params.model_dump() + # + # if params: + # system = params.pop("system", None) + # + # payload = apply_model_params_to_body_openai(params, payload) + # payload = apply_system_prompt_to_body(system, payload, metadata, user) + # + # # Check if user has access to the model + # if not bypass_filter and user.role == "user": + # if not ( + # user.id == model_info.user_id + # or has_access( + # user.id, type="read", access_control=model_info.access_control + # ) + # ): + # raise HTTPException( + # status_code=403, + # detail="Model not found", + # ) + # elif not bypass_filter: + # if user.role != "admin": + # raise HTTPException( + # status_code=403, + # detail="Model not found", + # ) await get_all_models(request, user=user) model = request.app.state.OPENAI_MODELS.get(model_id) @@ -884,8 +884,8 @@ async def generate_chat_completion( payload["user"] = { "name": user.name, "id": user.id, - "email": user.email, - "role": user.role, + "username": user.username, + "role": 'admin', } url = request.app.state.config.OPENAI_API_BASE_URLS[idx] diff --git a/backend/open_webui/routers/pipelines.py b/backend/open_webui/routers/pipelines.py index f80ea91f848b..e7945a8e9905 100644 --- a/backend/open_webui/routers/pipelines.py +++ b/backend/open_webui/routers/pipelines.py @@ -58,7 +58,7 @@ def get_sorted_filters(model_id, models): async def process_pipeline_inlet_filter(request, payload, user, models): - user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role} + user = {"id": user.id, "name": user.name, "role": user.role} model_id = payload["model"] sorted_filters = get_sorted_filters(model_id, models) model = models[model_id] @@ -111,7 +111,7 @@ async def process_pipeline_inlet_filter(request, payload, user, models): async def process_pipeline_outlet_filter(request, payload, user, models): - user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role} + user = {"id": user.id, "name": user.name, "role": user.role} model_id = payload["model"] sorted_filters = get_sorted_filters(model_id, models) model = models[model_id] diff --git a/backend/open_webui/routers/tasks.py b/backend/open_webui/routers/tasks.py index 7585466f69c1..30bfe90830db 100644 --- a/backend/open_webui/routers/tasks.py +++ b/backend/open_webui/routers/tasks.py @@ -190,7 +190,7 @@ async def generate_title( ) log.debug( - f"generating chat title using model {task_model_id} for user {user.email} " + f"generating chat title using model {task_model_id} for user {user.username} " ) if request.app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE != "": @@ -274,7 +274,7 @@ async def generate_follow_ups( ) log.debug( - f"generating chat title using model {task_model_id} for user {user.email} " + f"generating chat title using model {task_model_id} for user {user.username} " ) if request.app.state.config.FOLLOW_UP_GENERATION_PROMPT_TEMPLATE != "": @@ -347,7 +347,7 @@ async def generate_chat_tags( ) log.debug( - f"generating chat tags using model {task_model_id} for user {user.email} " + f"generating chat tags using model {task_model_id} for user {user.username} " ) if request.app.state.config.TAGS_GENERATION_PROMPT_TEMPLATE != "": @@ -413,7 +413,7 @@ async def generate_image_prompt( ) log.debug( - f"generating image prompt using model {task_model_id} for user {user.email} " + f"generating image prompt using model {task_model_id} for user {user.username} " ) if request.app.state.config.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE != "": @@ -498,7 +498,7 @@ async def generate_queries( ) log.debug( - f"generating {type} queries using model {task_model_id} for user {user.email}" + f"generating {type} queries using model {task_model_id} for user {user.username}" ) if (request.app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE).strip() != "": @@ -583,7 +583,7 @@ async def generate_autocompletion( ) log.debug( - f"generating autocompletion using model {task_model_id} for user {user.email}" + f"generating autocompletion using model {task_model_id} for user {user.username}" ) if (request.app.state.config.AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE).strip() != "": @@ -649,7 +649,7 @@ async def generate_emoji( models, ) - log.debug(f"generating emoji using model {task_model_id} for user {user.email} ") + log.debug(f"generating emoji using model {task_model_id} for user {user.username} ") template = DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 9ee3f9f88c00..f46862de9de8 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -3,12 +3,10 @@ import base64 import io - from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.responses import Response, StreamingResponse, FileResponse from pydantic import BaseModel - from open_webui.models.auths import Auths from open_webui.models.oauth_sessions import OAuthSessions @@ -25,7 +23,6 @@ UserUpdateForm, ) - from open_webui.socket.main import ( get_active_status_by_user_id, get_active_user_ids, @@ -34,11 +31,9 @@ from open_webui.constants import ERROR_MESSAGES from open_webui.env import SRC_LOG_LEVELS, STATIC_DIR - from open_webui.utils.auth import get_admin_user, get_password_hash, get_verified_user from open_webui.utils.access_control import get_permissions, has_permission - log = logging.getLogger(__name__) log.setLevel(SRC_LOG_LEVELS["MODELS"]) @@ -52,7 +47,7 @@ @router.get("/active") async def get_active_users( - user=Depends(get_verified_user), + user=Depends(get_verified_user), ): """ Get a list of active users. @@ -72,11 +67,11 @@ async def get_active_users( @router.get("/", response_model=UserListResponse) async def get_users( - query: Optional[str] = None, - order_by: Optional[str] = None, - direction: Optional[str] = None, - page: Optional[int] = 1, - user=Depends(get_admin_user), + query: Optional[str] = None, + order_by: Optional[str] = None, + direction: Optional[str] = None, + page: Optional[int] = 1, + user=Depends(get_admin_user), ): limit = PAGE_ITEM_COUNT @@ -96,15 +91,15 @@ async def get_users( @router.get("/all", response_model=UserInfoListResponse) async def get_all_users( - user=Depends(get_admin_user), + user=Depends(get_admin_user), ): return Users.get_users() @router.get("/search", response_model=UserIdNameListResponse) async def search_users( - query: Optional[str] = None, - user=Depends(get_verified_user), + query: Optional[str] = None, + user=Depends(get_verified_user), ): limit = PAGE_ITEM_COUNT @@ -135,11 +130,12 @@ async def get_user_groups(user=Depends(get_verified_user)): @router.get("/permissions") async def get_user_permissisions(request: Request, user=Depends(get_verified_user)): - user_permissions = get_permissions( - user.id, request.app.state.config.USER_PERMISSIONS - ) - - return user_permissions + # user_permissions = get_permissions( + # user.id, request.app.state.config.USER_PERMISSIONS + # ) + # + # return user_permissions + return request.app.state.config.USER_PERMISSIONS ############################ @@ -217,7 +213,7 @@ async def get_default_user_permissions(request: Request, user=Depends(get_admin_ @router.post("/default/permissions") async def update_default_user_permissions( - request: Request, form_data: UserPermissions, user=Depends(get_admin_user) + request: Request, form_data: UserPermissions, user=Depends(get_admin_user) ): request.app.state.config.USER_PERMISSIONS = form_data.model_dump() return request.app.state.config.USER_PERMISSIONS @@ -247,17 +243,17 @@ async def get_user_settings_by_session_user(user=Depends(get_verified_user)): @router.post("/user/settings/update", response_model=UserSettings) async def update_user_settings_by_session_user( - request: Request, form_data: UserSettings, user=Depends(get_verified_user) + request: Request, form_data: UserSettings, user=Depends(get_verified_user) ): updated_user_settings = form_data.model_dump() if ( - user.role != "admin" - and "toolServers" in updated_user_settings.get("ui").keys() - and not has_permission( - user.id, - "features.direct_tool_servers", - request.app.state.config.USER_PERMISSIONS, - ) + user.role != "admin" + and "toolServers" in updated_user_settings.get("ui").keys() + and not has_permission( + user.id, + "features.direct_tool_servers", + request.app.state.config.USER_PERMISSIONS, + ) ): # If the user is not an admin and does not have permission to use tool servers, remove the key updated_user_settings["ui"].pop("toolServers", None) @@ -296,7 +292,7 @@ async def get_user_info_by_session_user(user=Depends(get_verified_user)): @router.post("/user/info/update", response_model=Optional[dict]) async def update_user_info_by_session_user( - form_data: dict, user=Depends(get_verified_user) + form_data: dict, user=Depends(get_verified_user) ): user = Users.get_user_by_id(user.id) if user: @@ -337,7 +333,7 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)): chat_id = user_id.replace("shared-", "") chat = Chats.get_chat_by_id(chat_id) if chat: - user_id = chat.user_id + user_id = chat['user_id'] else: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -429,9 +425,9 @@ async def get_user_active_status_by_id(user_id: str, user=Depends(get_verified_u @router.post("/{user_id}/update", response_model=Optional[UserModel]) async def update_user_by_id( - user_id: str, - form_data: UserUpdateForm, - session_user=Depends(get_admin_user), + user_id: str, + form_data: UserUpdateForm, + session_user=Depends(get_admin_user), ): # Prevent modification of the primary admin user by other admins try: diff --git a/backend/open_webui/test/apps/webui/routers/test_chats.py b/backend/open_webui/test/apps/webui/routers/test_chats.py index a36a01fb1497..1ddd3bf7a156 100644 --- a/backend/open_webui/test/apps/webui/routers/test_chats.py +++ b/backend/open_webui/test/apps/webui/routers/test_chats.py @@ -213,7 +213,7 @@ def test_archive_chat_by_id(self): assert response.status_code == 200 chat = self.chats.get_chat_by_id(chat_id) - assert chat.archived is True + assert chat['archived'] is True def test_share_chat_by_id(self): chat_id = self.chats.get_chats()[0].id @@ -222,7 +222,7 @@ def test_share_chat_by_id(self): assert response.status_code == 200 chat = self.chats.get_chat_by_id(chat_id) - assert chat.share_id is not None + assert chat['share_id'] is not None def test_delete_shared_chat_by_id(self): chat_id = self.chats.get_chats()[0].id @@ -233,4 +233,4 @@ def test_delete_shared_chat_by_id(self): assert response.status_code chat = self.chats.get_chat_by_id(chat_id) - assert chat.share_id is None + assert chat['share_id'] is None diff --git a/backend/open_webui/utils/headers.py b/backend/open_webui/utils/headers.py index 3caee5033407..d42ba661a409 100644 --- a/backend/open_webui/utils/headers.py +++ b/backend/open_webui/utils/headers.py @@ -6,6 +6,6 @@ def include_user_info_headers(headers, user): **headers, "X-OpenWebUI-User-Name": quote(user.name, safe=" "), "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Username": user.username, + "X-OpenWebUI-User-Role": 'admin', } diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index e5b84a3d792b..4423f0c9bb88 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1,30 +1,23 @@ import time import logging import sys -import os -import base64 import textwrap import asyncio -from aiocache import cached -from typing import Any, Optional -import random +from typing import Optional import json import html -import inspect import re import ast from uuid import uuid4 from concurrent.futures import ThreadPoolExecutor - from fastapi import Request, HTTPException from fastapi.responses import HTMLResponse -from starlette.responses import Response, StreamingResponse, JSONResponse - +from starlette.responses import StreamingResponse, JSONResponse -from open_webui.models.oauth_sessions import OAuthSessions +from open_webui.jms import chat_manager, CommandHandler, CommandRecord, ReplayHandler from open_webui.models.chats import Chats from open_webui.models.folders import Folders from open_webui.models.users import Users @@ -63,14 +56,12 @@ get_image_url_from_base64, ) - from open_webui.models.users import UserModel from open_webui.models.functions import Functions from open_webui.models.models import Models from open_webui.retrieval.utils import get_sources_from_items - from open_webui.utils.chat import generate_chat_completion from open_webui.utils.task import ( get_task_model_id, @@ -101,7 +92,6 @@ from open_webui.utils.payload import apply_system_prompt_to_body from open_webui.utils.mcp.client import MCPClient - from open_webui.config import ( CACHE_DIR, DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE, @@ -119,12 +109,10 @@ ) from open_webui.constants import TASKS - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) log = logging.getLogger(__name__) log.setLevel(SRC_LOG_LEVELS["MAIN"]) - DEFAULT_REASONING_TAGS = [ ("", ""), ("", ""), @@ -140,13 +128,13 @@ def process_tool_result( - request, - tool_function_name, - tool_result, - tool_type, - direct_tool=False, - metadata=None, - user=None, + request, + tool_function_name, + tool_result, + tool_type, + direct_tool=False, + metadata=None, + user=None, ): tool_result_embeds = [] @@ -184,7 +172,7 @@ def process_tool_result( tool_result = tool_result.body.decode("utf-8", "replace") elif (tool_type == "external" and isinstance(tool_result, tuple)) or ( - direct_tool and isinstance(tool_result, list) and len(tool_result) == 2 + direct_tool and isinstance(tool_result, list) and len(tool_result) == 2 ): tool_result, tool_response_headers = tool_result @@ -283,7 +271,7 @@ def process_tool_result( async def chat_completion_tools_handler( - request: Request, body: dict, extra_params: dict, user: UserModel, models, tools + request: Request, body: dict, extra_params: dict, user: UserModel, models, tools ) -> tuple[dict, dict]: async def get_content_from_response(response) -> Optional[str]: content = None @@ -359,7 +347,7 @@ def get_tools_function_calling_payload(messages, task_model_id, content): return body, {} try: - content = content[content.find("{") : content.rfind("}") + 1] + content = content[content.find("{"): content.rfind("}") + 1] if not content: raise Exception("No JSON object found in the response") @@ -488,9 +476,9 @@ async def tool_call_handler(tool_call): ) if ( - tools[tool_function_name] - .get("metadata", {}) - .get("file_handler", False) + tools[tool_function_name] + .get("metadata", {}) + .get("file_handler", False) ): skip_files = True @@ -517,7 +505,7 @@ async def tool_call_handler(tool_call): async def chat_memory_handler( - request: Request, form_data: dict, extra_params: dict, user + request: Request, form_data: dict, extra_params: dict, user ): try: results = await query_memory( @@ -556,7 +544,7 @@ async def chat_memory_handler( async def chat_web_search_handler( - request: Request, form_data: dict, extra_params: dict, user + request: Request, form_data: dict, extra_params: dict, user ): event_emitter = extra_params["__event_emitter__"] await event_emitter( @@ -649,7 +637,7 @@ async def chat_web_search_handler( if results.get("collection_names"): for col_idx, collection_name in enumerate( - results.get("collection_names") + results.get("collection_names") ): files.append( { @@ -734,7 +722,7 @@ def get_last_images(message_list): async def chat_image_generation_handler( - request: Request, form_data: dict, extra_params: dict, user + request: Request, form_data: dict, extra_params: dict, user ): metadata = extra_params.get("__metadata__", {}) chat_id = metadata.get("chat_id", None) @@ -751,8 +739,8 @@ async def chat_image_generation_handler( } ) - messages_map = chat.chat.get("history", {}).get("messages", {}) - message_id = chat.chat.get("history", {}).get("currentId") + messages_map = chat['chat'].get("history", {}).get("messages", {}) + message_id = chat['chat'].get("history", {}).get("currentId") message_list = get_message_list(messages_map, message_id) user_message = get_last_user_message(message_list) @@ -906,7 +894,7 @@ async def chat_image_generation_handler( async def chat_completion_files_handler( - request: Request, body: dict, extra_params: dict, user: UserModel + request: Request, body: dict, extra_params: dict, user: UserModel ) -> tuple[dict, dict[str, list]]: __event_emitter__ = extra_params["__event_emitter__"] sources = [] @@ -987,7 +975,7 @@ async def chat_completion_files_handler( hybrid_bm25_weight=request.app.state.config.HYBRID_BM25_WEIGHT, hybrid_search=request.app.state.config.ENABLE_RAG_HYBRID_SEARCH, full_context=all_full_context - or request.app.state.config.RAG_FULL_CONTEXT, + or request.app.state.config.RAG_FULL_CONTEXT, user=user, ), ) @@ -1008,9 +996,9 @@ async def chat_completion_files_handler( for index, _ in enumerate(documents): metadata = metadatas[index] if index < len(metadatas) else None _id = ( - (metadata or {}).get("source") - or (src_info or {}).get("id") - or "N/A" + (metadata or {}).get("source") + or (src_info or {}).get("id") + or "N/A" ) unique_ids.add(_id) @@ -1143,8 +1131,8 @@ async def process_chat_payload(request, form_data, user, metadata, model): chat_id = metadata.get("chat_id", None) if chat_id and user: chat = Chats.get_chat_by_id_and_user_id(chat_id, user.id) - if chat and chat.folder_id: - folder = Folders.get_folder_by_id_and_user_id(chat.folder_id, user.id) + if chat and chat['folder_id']: + folder = Folders.get_folder_by_id_and_user_id(chat['folder_id'], user.id) if folder and folder.data: if "system_prompt" in folder.data: @@ -1305,15 +1293,15 @@ async def process_chat_payload(request, form_data, user, metadata, model): for tool_id in tool_ids: if tool_id.startswith("server:mcp:"): try: - server_id = tool_id[len("server:mcp:") :] + server_id = tool_id[len("server:mcp:"):] mcp_server_connection = None for ( - server_connection + server_connection ) in request.app.state.config.TOOL_SERVER_CONNECTIONS: if ( - server_connection.get("type", "") == "mcp" - and server_connection.get("info", {}).get("id") == server_id + server_connection.get("type", "") == "mcp" + and server_connection.get("info", {}).get("id") == server_id ): mcp_server_connection = server_connection break @@ -1367,7 +1355,6 @@ async def process_chat_payload(request, form_data, user, metadata, model): tool_specs = await mcp_clients[server_id].list_tool_specs() for tool_spec in tool_specs: - def make_tool_function(client, function_name): async def tool_function(**kwargs): return await client.call_tool( @@ -1468,22 +1455,22 @@ async def tool_function(**kwargs): for source in sources: if "document" in source: for document_text, document_metadata in zip( - source["document"], source["metadata"] + source["document"], source["metadata"] ): source_name = source.get("source", {}).get("name", None) source_id = ( - document_metadata.get("source", None) - or source.get("source", {}).get("id", None) - or "N/A" + document_metadata.get("source", None) + or source.get("source", {}).get("id", None) + or "N/A" ) if source_id not in citation_idx_map: citation_idx_map[source_id] = len(citation_idx_map) + 1 context_string += ( - f'{document_text}\n" + f'{document_text}\n" ) context_string = context_string.strip() @@ -1506,7 +1493,7 @@ async def tool_function(**kwargs): source for source in sources if source.get("source", {}).get("name", "") - or source.get("source", {}).get("id", "") + or source.get("source", {}).get("id", "") ] if len(sources) > 0: @@ -1525,11 +1512,13 @@ async def tool_function(**kwargs): } ) + chat_id = metadata.get("chat_id", '') + asyncio.create_task(ReplayHandler(chat_id).write_input(user_message)) return form_data, metadata, events async def process_chat_response( - request, response, form_data, user, metadata, model, events, tasks + request, response, form_data, user, metadata, model, events, tasks ): async def background_tasks_handler(): message = None @@ -1581,8 +1570,8 @@ async def background_tasks_handler(): if message and "model" in message: if tasks and messages: if ( - TASKS.FOLLOW_UP_GENERATION in tasks - and tasks[TASKS.FOLLOW_UP_GENERATION] + TASKS.FOLLOW_UP_GENERATION in tasks + and tasks[TASKS.FOLLOW_UP_GENERATION] ): res = await generate_follow_ups( request, @@ -1608,8 +1597,8 @@ async def background_tasks_handler(): follow_ups_string = "" follow_ups_string = follow_ups_string[ - follow_ups_string.find("{") : follow_ups_string.rfind("}") - + 1 + follow_ups_string.find("{"): follow_ups_string.rfind("}") + + 1 ] try: @@ -1638,7 +1627,7 @@ async def background_tasks_handler(): pass if not metadata.get("chat_id", "").startswith( - "local:" + "local:" ): # Only update titles and tags for non-temp chats if TASKS.TITLE_GENERATION in tasks: user_message = get_last_user_message(messages) @@ -1664,17 +1653,17 @@ async def background_tasks_handler(): ) title_string = ( - response_message.get("content") - or response_message.get( - "reasoning_content", - ) - or message.get("content", user_message) + response_message.get("content") + or response_message.get( + "reasoning_content", + ) + or message.get("content", user_message) ) else: title_string = "" title_string = title_string[ - title_string.find("{") : title_string.rfind("}") + 1 + title_string.find("{"): title_string.rfind("}") + 1 ] try: @@ -1734,7 +1723,7 @@ async def background_tasks_handler(): tags_string = "" tags_string = tags_string[ - tags_string.find("{") : tags_string.rfind("}") + 1 + tags_string.find("{"): tags_string.rfind("}") + 1 ] try: @@ -1755,12 +1744,12 @@ async def background_tasks_handler(): event_emitter = None event_caller = None if ( - "session_id" in metadata - and metadata["session_id"] - and "chat_id" in metadata - and metadata["chat_id"] - and "message_id" in metadata - and metadata["message_id"] + "session_id" in metadata + and metadata["session_id"] + and "chat_id" in metadata + and metadata["chat_id"] + and "message_id" in metadata + and metadata["message_id"] ): event_emitter = get_event_emitter(metadata) event_caller = get_event_call(metadata) @@ -1775,7 +1764,7 @@ async def background_tasks_handler(): response = response[0] if isinstance(response, JSONResponse) and isinstance( - response.body, bytes + response.body, bytes ): try: response_data = json.loads( @@ -1918,8 +1907,8 @@ async def background_tasks_handler(): # Non standard response if not any( - content_type in response.headers["Content-Type"] - for content_type in ["text/event-stream", "application/x-ndjson"] + content_type in response.headers["Content-Type"] + for content_type in ["text/event-stream", "application/x-ndjson"] ): return response @@ -1957,7 +1946,7 @@ async def background_tasks_handler(): def split_content_and_whitespace(content): content_stripped = content.rstrip() original_whitespace = ( - content[len(content_stripped) :] + content[len(content_stripped):] if len(content) > len(content_stripped) else "" ) @@ -2075,8 +2064,8 @@ def serialize_content_blocks(content_blocks, raw=False): if is_opening_code_block(content_stripped): # Remove trailing backticks that would open a new block content = ( - content_stripped.rstrip("`").rstrip() - + original_whitespace + content_stripped.rstrip("`").rstrip() + + original_whitespace ) else: # Keep content as is - either closing backticks or no backticks @@ -2189,7 +2178,7 @@ def extract_attributes(tag_content): : match.start() ] # Content before opening tag after_tag = content[ - match.end() : + match.end(): ] # Content after opening tag # Remove the start tag and after from the currently handling text block @@ -2358,8 +2347,8 @@ def extract_attributes(tag_content): reasoning_tags = [] if DETECT_REASONING_TAGS: if ( - isinstance(reasoning_tags_param, list) - and len(reasoning_tags_param) == 2 + isinstance(reasoning_tags_param, list) + and len(reasoning_tags_param) == 2 ): reasoning_tags = [ (reasoning_tags_param[0], reasoning_tags_param[1]) @@ -2432,7 +2421,7 @@ async def flush_pending_delta_data(threshold: int = 0): continue # Remove the prefix - data = data[len("data:") :].strip() + data = data[len("data:"):].strip() try: data = json.loads(data) @@ -2447,7 +2436,7 @@ async def flush_pending_delta_data(threshold: int = 0): if data: if "event" in data and not getattr( - request.state, "direct", False + request.state, "direct", False ): await event_emitter(data.get("event", {})) @@ -2508,11 +2497,11 @@ async def flush_pending_delta_data(threshold: int = 0): # Check if the tool call already exists current_response_tool_call = None for ( - response_tool_call + response_tool_call ) in response_tool_calls: if ( - response_tool_call.get("index") - == tool_call_index + response_tool_call.get("index") + == tool_call_index ): current_response_tool_call = ( response_tool_call @@ -2559,14 +2548,14 @@ async def flush_pending_delta_data(threshold: int = 0): value = delta.get("content") reasoning_content = ( - delta.get("reasoning_content") - or delta.get("reasoning") - or delta.get("thinking") + delta.get("reasoning_content") + or delta.get("reasoning") + or delta.get("thinking") ) if reasoning_content: if ( - not content_blocks - or content_blocks[-1]["type"] != "reasoning" + not content_blocks + or content_blocks[-1]["type"] != "reasoning" ): reasoning_block = { "type": "reasoning", @@ -2592,13 +2581,13 @@ async def flush_pending_delta_data(threshold: int = 0): if value: if ( - content_blocks - and content_blocks[-1]["type"] - == "reasoning" - and content_blocks[-1] - .get("attributes", {}) - .get("type") - == "reasoning_content" + content_blocks + and content_blocks[-1]["type"] + == "reasoning" + and content_blocks[-1] + .get("attributes", {}) + .get("type") + == "reasoning_content" ): reasoning_block = content_blocks[-1] reasoning_block["ended_at"] = time.time() @@ -2624,7 +2613,7 @@ async def flush_pending_delta_data(threshold: int = 0): ) content_blocks[-1]["content"] = ( - content_blocks[-1]["content"] + value + content_blocks[-1]["content"] + value ) if DETECT_REASONING_TAGS: @@ -2736,8 +2725,8 @@ async def flush_pending_delta_data(threshold: int = 0): tool_call_retries = 0 while ( - len(tool_calls) > 0 - and tool_call_retries < CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES + len(tool_calls) > 0 + and tool_call_retries < CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES ): tool_call_retries += 1 @@ -2934,8 +2923,8 @@ async def flush_pending_delta_data(threshold: int = 0): retries = 0 while ( - content_blocks[-1]["type"] == "code_interpreter" - and retries < MAX_RETRIES + content_blocks[-1]["type"] == "code_interpreter" + and retries < MAX_RETRIES ): await event_emitter( @@ -2977,8 +2966,8 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0): code = blocking_code + "\n" + code if ( - request.app.state.config.CODE_INTERPRETER_ENGINE - == "pyodide" + request.app.state.config.CODE_INTERPRETER_ENGINE + == "pyodide" ): output = await event_caller( { @@ -2993,8 +2982,8 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0): } ) elif ( - request.app.state.config.CODE_INTERPRETER_ENGINE - == "jupyter" + request.app.state.config.CODE_INTERPRETER_ENGINE + == "jupyter" ): output = await execute_code_jupyter( request.app.state.config.CODE_INTERPRETER_JUPYTER_URL, @@ -3002,13 +2991,13 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0): ( request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN if request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH - == "token" + == "token" else None ), ( request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD if request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH - == "password" + == "password" else None ), request.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT, @@ -3115,6 +3104,17 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0): "title": title, } + chat_id = metadata.get("chat_id", '') + + chat_data = chat_manager.retrieve(chat_id) + user_message = get_last_user_message(form_data["messages"]) + + command_handler = CommandHandler(chat_id, chat_data['session_info']) + command_handler.command_record = CommandRecord( + input=user_message, output=data['content'] + ) + asyncio.create_task(ReplayHandler(chat_id).write_input(data['content'])) + if not ENABLE_REALTIME_CHAT_SAVE: # Save message in the database Chats.upsert_message_to_chat_by_id_and_message_id( diff --git a/package-lock.json b/package-lock.json index c86757e9a5a4..f889f6003d2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,15 @@ "version": "0.6.36", "dependencies": { "@azure/msal-browser": "^4.5.0", + "@codemirror/autocomplete": "^6.19.1", + "@codemirror/commands": "^6.10.0", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", + "@codemirror/language": "^6.11.3", "@codemirror/language-data": "^6.5.1", + "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.38.8", "@floating-ui/dom": "^1.7.2", "@huggingface/transformers": "^3.0.0", "@joplin/turndown-plugin-gfm": "^1.0.62", @@ -97,6 +102,7 @@ "vega-lite": "^6.4.1", "vite-plugin-static-copy": "^2.2.0", "y-prosemirror": "^1.3.7", + "y-protocols": "^1.0.6", "yaml": "^2.7.1", "yjs": "^13.6.27" }, @@ -177,22 +183,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, "node_modules/@azure/msal-browser": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.5.0.tgz", @@ -236,7 +226,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz", "integrity": "sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==", - "devOptional": true, + "dev": true, "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@chevrotain/cst-dts-gen": { @@ -279,26 +269,22 @@ "license": "Apache-2.0" }, "node_modules/@codemirror/autocomplete": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz", - "integrity": "sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==", + "version": "6.19.1", + "resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz", + "integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==", + "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" - }, - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" } }, "node_modules/@codemirror/commands": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz", - "integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==", + "version": "6.10.0", + "resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", @@ -550,9 +536,10 @@ } }, "node_modules/@codemirror/language": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", - "integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", + "version": "6.11.3", + "resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -620,9 +607,13 @@ } }, "node_modules/@codemirror/state": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", - "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" + "version": "6.5.2", + "resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } }, "node_modules/@codemirror/theme-one-dark": { "version": "6.1.2", @@ -636,11 +627,13 @@ } }, "node_modules/@codemirror/view": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.0.tgz", - "integrity": "sha512-fo7CelaUDKWIyemw4b+J57cWuRkOu4SWCCPfNDkPvfWkGjM9D5racHQXr4EQeYCD6zEBIBxGCeaKkQo+ysl0gA==", + "version": "6.38.8", + "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.8.tgz", + "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", + "license": "MIT", "dependencies": { - "@codemirror/state": "^6.4.0", + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } @@ -655,131 +648,6 @@ "node": ">=0.1.90" } }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@cypress/request": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz", @@ -1988,6 +1856,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1998,6 +1867,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2008,6 +1878,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2022,6 +1893,7 @@ "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2193,6 +2065,12 @@ "@lezer/lr": "^1.4.0" } }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@mediapipe/tasks-vision": { "version": "0.10.17", "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", @@ -2442,6 +2320,7 @@ "version": "1.0.0-next.28", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, "license": "MIT" }, "node_modules/@popperjs/core": { @@ -2840,6 +2719,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -2885,6 +2765,7 @@ "version": "2.22.4", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.22.4.tgz", "integrity": "sha512-BXK9hTbP8AeQIfoz6+P3uoyVYStVHc5CIKqoTSF7hXm3Q5P9BwFMdEus4jsQuhaYmXGHzukcGlxe2QrsE8BJfQ==", + "dev": true, "license": "MIT", "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", @@ -2922,6 +2803,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz", "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==", + "dev": true, "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", @@ -2943,6 +2825,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.7" @@ -2960,6 +2843,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2977,12 +2861,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/@sveltejs/vite-plugin-svelte/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3000,6 +2886,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/@swc/helpers": { @@ -3403,23 +3290,6 @@ "lowlight": "^2 || ^3" } }, - "node_modules/@tiptap/extension-collaboration": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-3.4.5.tgz", - "integrity": "sha512-JyPXTYkYi2XzUWsmObv2cogMrs7huAvfq6l7d5hAwsU2FnA1vMycaa48N4uekogySP6VBkiQNDf9B4T09AwwqA==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.4.5", - "@tiptap/pm": "^3.4.5", - "@tiptap/y-tiptap": "^3.0.0-beta.3", - "yjs": "^13" - } - }, "node_modules/@tiptap/extension-document": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.0.7.tgz", @@ -3662,21 +3532,6 @@ "@tiptap/suggestion": "^3.0.9" } }, - "node_modules/@tiptap/extension-node-range": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-node-range/-/extension-node-range-3.4.5.tgz", - "integrity": "sha512-mHCjdJZX8DZCpnw9wBqioanANy6tRoy20/OcJxMW1T7naeRCuCU4sFjwO37yb/tmYk1BQA2/L1/H2r0fVoZwtA==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.4.5", - "@tiptap/pm": "^3.4.5" - } - }, "node_modules/@tiptap/extension-ordered-list": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.0.7.tgz", @@ -3743,20 +3598,6 @@ "@tiptap/core": "^3.0.7" } }, - "node_modules/@tiptap/extension-text-style": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.0.7.tgz", - "integrity": "sha512-naJ1XxlbFJ1qlpA+i54lQYKuhWP1dnkUslM86OT0TZt0zJBeu7LIrqSOVGmMB++lF/btnQLMnYkYSSnkLgIw3A==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.0.7" - } - }, "node_modules/@tiptap/extension-typography": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-3.0.7.tgz", @@ -3890,31 +3731,11 @@ "@tiptap/pm": "^3.4.2" } }, - "node_modules/@tiptap/y-tiptap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tiptap/y-tiptap/-/y-tiptap-3.0.0.tgz", - "integrity": "sha512-HIeJZCj+KYJde2x6fONzo4o6kd7gW7eonwhQsv2p2VQnUgwNXMVhN+D6Z3AH/2i541Sq33y1PO4U/1ThCPjqbA==", - "license": "MIT", - "peer": true, - "dependencies": { - "lib0": "^0.2.100" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.7.1", - "prosemirror-state": "^1.2.3", - "prosemirror-view": "^1.9.10", - "y-protocols": "^1.0.1", - "yjs": "^13.5.38" - } - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true }, "node_modules/@types/d3": { "version": "7.4.3", @@ -4661,18 +4482,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -4818,6 +4627,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -4911,6 +4721,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -5204,7 +5015,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", - "devOptional": true, + "dev": true, "license": "MIT/X11" }, "node_modules/buffer-crc32": { @@ -5737,6 +5548,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5845,7 +5657,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/colors": { @@ -5914,6 +5726,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -6026,31 +5839,6 @@ "node": ">=4" } }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/cypress": { "version": "13.15.0", "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz", @@ -6706,22 +6494,6 @@ "node": ">=0.10" } }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -6744,15 +6516,6 @@ } } }, - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -6834,7 +6597,8 @@ "node_modules/devalue": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==" + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "dev": true }, "node_modules/devlop": { "version": "1.1.0", @@ -7373,6 +7137,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, "license": "MIT" }, "node_modules/espree": { @@ -7408,6 +7173,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.1.tgz", "integrity": "sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -8129,7 +7895,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } @@ -8241,21 +8007,6 @@ "node": ">=12.0.0" } }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/html-entities": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.3.tgz", @@ -8324,22 +8075,6 @@ "entities": "^4.5.0" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/http-signature": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", @@ -8354,22 +8089,6 @@ "node": ">=0.10" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -8500,7 +8219,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/import-fresh": { @@ -8718,15 +8437,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -8830,73 +8540,6 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, - "node_modules/jsdom": { - "version": "24.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", - "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cssstyle": "^4.0.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^2.11.2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -9019,6 +8662,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, "engines": { "node": ">=6" } @@ -9138,7 +8782,7 @@ "version": "1.29.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz", "integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==", - "devOptional": true, + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^1.0.3" @@ -9170,6 +8814,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9190,6 +8835,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9210,6 +8856,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9230,6 +8877,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9250,6 +8898,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9270,6 +8919,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9290,6 +8940,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9310,6 +8961,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9330,6 +8982,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9350,6 +9003,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -9367,7 +9021,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "detect-libc": "bin/detect-libc.js" @@ -9486,7 +9140,8 @@ "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true }, "node_modules/locate-path": { "version": "6.0.0", @@ -9994,6 +9649,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, "engines": { "node": ">=4" } @@ -10002,6 +9658,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10086,15 +9743,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/nwsapi": { - "version": "2.2.21", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", - "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10531,6 +10179,7 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10662,6 +10311,7 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -11458,15 +11108,6 @@ "points-on-path": "^0.2.1" } }, - "node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -11508,7 +11149,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "devOptional": true, + "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -11517,6 +11158,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, "dependencies": { "mri": "^1.1.0" }, @@ -11539,7 +11181,7 @@ "version": "1.81.0", "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.81.0.tgz", "integrity": "sha512-uZQ2Faxb1oWBHpeSSzjxnhClbMb3QadN0ql0ZFNuqWOLUxwaVhrMlMhPq6TDPbbfDUjihuwrMCuy695Bgna5RA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@bufbuild/protobuf": "^2.0.0", @@ -11587,6 +11229,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11603,6 +11246,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11619,6 +11263,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11635,6 +11280,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11651,6 +11297,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11667,6 +11314,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11683,6 +11331,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11699,6 +11348,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11715,6 +11365,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11731,6 +11382,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11747,6 +11399,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11763,6 +11416,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11779,6 +11433,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11795,6 +11450,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11811,6 +11467,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11827,6 +11484,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11843,6 +11501,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11859,6 +11518,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11875,6 +11535,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11891,6 +11552,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11904,7 +11566,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -11916,21 +11578,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -11945,7 +11592,8 @@ "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true }, "node_modules/set-function-length": { "version": "1.2.2", @@ -12068,6 +11716,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", + "dev": true, "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -12143,6 +11792,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -12390,6 +12040,7 @@ "version": "5.42.2", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.42.2.tgz", "integrity": "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -12469,21 +12120,6 @@ } } }, - "node_modules/svelte-check/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/svelte-check/node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -12547,12 +12183,14 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/svelte/node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -12568,15 +12206,6 @@ "node": ">=12.0.0" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/symlink-or-copy": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", @@ -12587,7 +12216,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "sync-message-port": "^1.0.0" @@ -12600,7 +12229,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=16.0.0" @@ -12809,6 +12438,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12838,21 +12468,6 @@ "node": ">= 4.0.0" } }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -13079,7 +12694,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/vega": { @@ -13588,6 +13203,7 @@ "version": "5.4.19", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -13691,6 +13307,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "aix" @@ -13706,6 +13323,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -13721,6 +13339,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -13736,6 +13355,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -13751,6 +13371,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -13766,6 +13387,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -13781,6 +13403,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -13796,6 +13419,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -13811,6 +13435,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13826,6 +13451,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13841,6 +13467,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13856,6 +13483,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13871,6 +13499,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13886,6 +13515,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13901,6 +13531,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13916,6 +13547,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13931,6 +13563,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13946,6 +13579,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -13961,6 +13595,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -13976,6 +13611,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -13991,6 +13627,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -14006,6 +13643,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -14021,6 +13659,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -14033,6 +13672,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -14070,6 +13710,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, "license": "MIT", "workspaces": [ "tests/deps/*", @@ -14327,21 +13968,6 @@ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/walk-sync": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", @@ -14380,18 +14006,6 @@ "node": "*" } }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -14415,22 +14029,6 @@ "node": ">=18" } }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/wheel": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz", @@ -14578,27 +14176,6 @@ } } }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", @@ -14642,10 +14219,9 @@ }, "node_modules/y-protocols": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", + "resolved": "https://registry.npmmirror.com/y-protocols/-/y-protocols-1.0.6.tgz", "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.85" }, @@ -14810,6 +14386,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, "license": "MIT" } } diff --git a/package.json b/package.json index 023238ce84ca..5ced88dcfe74 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.6.36", "private": true, "scripts": { + "start": "vite dev --host", "dev": "vite dev --host", "dev:5050": "vite dev --port 5050", "build": "vite build", @@ -53,10 +54,15 @@ "type": "module", "dependencies": { "@azure/msal-browser": "^4.5.0", + "@codemirror/autocomplete": "^6.19.1", + "@codemirror/commands": "^6.10.0", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", + "@codemirror/language": "^6.11.3", "@codemirror/language-data": "^6.5.1", + "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.38.8", "@floating-ui/dom": "^1.7.2", "@huggingface/transformers": "^3.0.0", "@joplin/turndown-plugin-gfm": "^1.0.62", @@ -141,6 +147,7 @@ "vega-lite": "^6.4.1", "vite-plugin-static-copy": "^2.2.0", "y-prosemirror": "^1.3.7", + "y-protocols": "^1.0.6", "yaml": "^2.7.1", "yjs": "^13.6.27" }, diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 46952ac3adb2..a254dbb774fd 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -10,7 +10,6 @@ export const createNewChat = async (token: string, chat: object, folderId: strin headers: { Accept: 'application/json', 'Content-Type': 'application/json', - authorization: `Bearer ${token}` }, body: JSON.stringify({ chat: chat, diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 728f3e48e1d1..a11b474a08a3 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1,9 +1,8 @@ -import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; +import { WEBUI_BASE_URL } from '$lib/constants'; import { convertOpenApiToToolPayload } from '$lib/utils'; import { getOpenAIModelsDirect } from './openai'; import { parse } from 'yaml'; -import { toast } from 'svelte-sonner'; import { apiFetch } from '$lib/net/apiFetch'; export const getModels = async ( diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index ac057359a529..b00c0324cd74 100644 --- a/src/lib/apis/users/index.ts +++ b/src/lib/apis/users/index.ts @@ -221,61 +221,87 @@ export const searchUsers = async (token: string, query: string) => { return res; }; +const USER_SETTINGS_STORAGE_KEY = 'kael:user-settings'; +const DEFAULT_USER_SETTINGS = { + ui: { + models: ['gpt-4o-mini'], + version: '4.0.0' + } +}; -export const getUserSettings = async (token: string) => { - let error = null; - const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/settings`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` +const getUserSettingsKey = (userName?: string | null) => { + if (typeof localStorage === 'undefined') { + return USER_SETTINGS_STORAGE_KEY; + } + + const identifier = userName?.trim() || ''; + + return identifier ? `${USER_SETTINGS_STORAGE_KEY}:${identifier}` : USER_SETTINGS_STORAGE_KEY; +}; + +const readLocalSettings = (userName?: string | null) => { + if (typeof localStorage === 'undefined') { + return { ...DEFAULT_USER_SETTINGS }; + } + + try { + const storageKey = getUserSettingsKey(userName); + const stored = localStorage.getItem(storageKey); + + if (!stored) { + return { ...DEFAULT_USER_SETTINGS }; } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - console.error(err); - error = err.detail; - return null; - }); - if (error) { - throw error; + const parsed = JSON.parse(stored); + + return { + ...DEFAULT_USER_SETTINGS, + ...parsed, + ui: { + ...DEFAULT_USER_SETTINGS.ui, + ...(parsed?.ui ?? {}) + } + }; + } catch (err) { + console.error(err); + return { ...DEFAULT_USER_SETTINGS }; } +}; - return res; +const writeLocalSettings = (settings: object, userName?: string | null) => { + if (typeof localStorage === 'undefined') return; + + try { + const storageKey = getUserSettingsKey(userName); + localStorage.setItem(storageKey, JSON.stringify(settings)); + } catch (err) { + console.error(err); + } }; -export const updateUserSettings = async (token: string, settings: object) => { - let error = null; +export const getUserSettings = async (userName?: string | null) => { + return readLocalSettings(userName); +}; - const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/settings/update`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - }, - body: JSON.stringify({ - ...settings - }) - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - console.error(err); - error = err.detail; - return null; - }); +export const updateUserSettings = async ( + settings: Record, + userName?: string | null +) => { + const currentSettings = readLocalSettings(userName); + const incomingUi = (settings.ui as Record) ?? {}; + const updatedSettings = { + ...currentSettings, + ...settings, + ui: { + ...DEFAULT_USER_SETTINGS.ui, + ...(currentSettings?.ui ?? {}), + ...incomingUi + } + }; - if (error) { - throw error; - } + writeLocalSettings(updatedSettings, userName); - return res; + return updatedSettings; }; export const getUserById = async (token: string, userId: string) => { diff --git a/src/lib/components/ChangelogModal.svelte b/src/lib/components/ChangelogModal.svelte index d0d1c638a7b4..dc8635d1eaf0 100644 --- a/src/lib/components/ChangelogModal.svelte +++ b/src/lib/components/ChangelogModal.svelte @@ -4,7 +4,7 @@ import { onMount, getContext } from 'svelte'; import { Confetti } from 'svelte-confetti'; - import { WEBUI_NAME, config, settings } from '$lib/stores'; +import { WEBUI_NAME, config, settings, user } from '$lib/stores'; import { WEBUI_VERSION } from '$lib/constants'; import { getChangelog } from '$lib/apis'; @@ -105,7 +105,7 @@ on:click={async () => { localStorage.version = $config.version; await settings.set({ ...$settings, ...{ version: $config.version } }); - await updateUserSettings(localStorage.token, { ui: $settings }); + await updateUserSettings({ ui: $settings }, $user?.name); show = false; }} class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full" diff --git a/src/lib/components/admin/Evaluations.svelte b/src/lib/components/admin/Evaluations.svelte index d29dee746cb7..12a1415c59a9 100644 --- a/src/lib/components/admin/Evaluations.svelte +++ b/src/lib/components/admin/Evaluations.svelte @@ -65,7 +65,7 @@ ? '' : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}" on:click={() => { - goto('/admin/evaluations/leaderboard'); + goto('/kael/admin/evaluations/leaderboard'); }} >
@@ -92,7 +92,7 @@ ? '' : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}" on:click={() => { - goto('/admin/evaluations/feedbacks'); + goto('/kael/admin/evaluations/feedbacks'); }} >
diff --git a/src/lib/components/admin/Functions/FunctionEditor.svelte b/src/lib/components/admin/Functions/FunctionEditor.svelte index 1ef7bddc16f4..ffb4d8a95bea 100644 --- a/src/lib/components/admin/Functions/FunctionEditor.svelte +++ b/src/lib/components/admin/Functions/FunctionEditor.svelte @@ -306,7 +306,7 @@ class Pipe:
diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 6a30f7c0f529..0369462b8324 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -14,6 +14,7 @@ import { createNewFeedback, getFeedbackById, updateFeedbackById } from '$lib/apis/evaluations'; import { getChatById } from '$lib/apis/chats'; import { generateTags } from '$lib/apis'; + import ModelIcon from '$lib/components/icons/models/ModelIcon.svelte'; import { audioQueue, @@ -577,13 +578,14 @@ id="message-{message.id}" dir={$settings.chatDirection} > -