commit f7cc81599e94c16bf3ec4b39ef9376fde377327c Author: skyseek Date: Thu Feb 26 02:44:03 2026 +0800 chore: squash repository history diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a4b824b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ae9fea --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Logs +logs +log/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +.golutra/local.json + +# Rust +target/ +Cargo.lock +src-tauri/vendor/ +src-tauri/binaries/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Optional npm cache directory +.npm + +# Release packages +发布包/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..6fd6094 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "printWidth": 100, + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..faa3f5f --- /dev/null +++ b/LICENSE @@ -0,0 +1,36 @@ +Business Source License 1.1 +License text copyright © 2024 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc. + +Licensor: seekskyworld +Licensed Work: golutra +Additional Use Grant: None +Change Date: 2030-02-25 +Change License: GPL-2.0-or-later + +Terms +The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate. + +If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License). TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below. + +Covenants of Licensor +In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor: + +To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation. +To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text “None” to specify a Change Date. Not to modify this License in any other way. + +Notice +The Business Source License (this document, or the “License”) is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License. + +For more information on the use of the Business Source License for MariaDB products, please visit the MariaDB Business Source License FAQ. For more information on the use of the Business Source License generally, please visit the Adopting and Developing Business Source License FAQ. + +Projects using BSL diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9d2ce7 --- /dev/null +++ b/README.md @@ -0,0 +1,243 @@ +# golutra + +## English + +### Tagline + +One person. One AI squad. + +### What is golutra + +`golutra` is a next-generation multi-agent collaboration workspace. +It goes beyond traditional IDE extensions and single-chat AI tools by orchestrating multiple agents to execute complex tasks in parallel. +Built with Vue 3 + Rust as a Tauri desktop application, `golutra` currently supports Windows and macOS. + +### Core Value + +- Unlimited multi-agent collaboration: automatically break down complex tasks and execute in parallel. +- Silent background execution: agents keep running even when you leave the main view. +- Terminal show/hide on demand: switch between high-level control and terminal details instantly. +- Avatar-based inspection: click each agent avatar to view terminal status, logs, and outputs. +- Automatic orchestration: from analysis and generation to strategy and deployment. +- Personal AI team: move from "one person + one editor" to "one person + an AI squad." + +### CLI Compatibility Layer + +Keep your existing CLI and instantly upgrade it into a collaboration hub. + +No terminal switch, no workflow rewrite, no re-training. Keep using the CLI you already trust while adding multi-agent collaboration, orchestration, and result handoff to your current engineering pipeline. + +Supported CLI tools: + +- Claude Code +- Gemini CLI +- Codex CLI +- OpenCode +- Qwen Code + +What you keep: + +- No project migration +- No command relearning +- No single-tool lock-in + +With your existing CLI, you can now: + +- Run parallel multi-agent execution with automatic result handoff +- Track status and scheduling across CLIs in one orchestration layer +- Reuse session-level context and prompts without repeating requirements +- Aggregate test, build, and regression output into one delivery path + +Keep your familiar commands. `golutra` wires them into a complete engineering loop. + +### Stealth Terminal + +Command line power, visual interface simplicity. + +Seamlessly integrate code execution with a background terminal that adapts to your workflow. Experience the raw power of command-line control without leaving the visual interface. + +- Direct Injection: inject prompts directly into the terminal stream for instant agent feedback loops. +- Context Awareness: the terminal understands your project context, offering intelligent autocompletion for complex tasks. + +### Why Beyond Traditional IDEs + +Traditional IDE workflows are usually "single-threaded + manual context switching." +`golutra` is "parallel multi-agent execution + automated orchestration." + +### Repository Scope + +This repository is for source code storage and releases. + +Business Email: [golutra@hotmail.com](mailto:golutra%40hotmail.com) +Official Website: [https://www.golutra.com/](https://www.golutra.com/) +Discord: [https://discord.gg/QyNVu56mpY](https://discord.gg/QyNVu56mpY) +Security Policy: See [SECURITY.md](SECURITY.md) + +### Author + +This software is independently developed and maintained by [seekskyworld](https://github.com/seekskyworld). + +### Open Source Status + +This project is now open source, and related code will be organized and opened in phases. + +To protect security and privacy, any parts involving server keys, account configuration, or other sensitive information will be sanitized and refactored before being gradually published to the repository. + +### Usage License + +- Using `golutra` as a tool to build commercial software is allowed. +- Code and deliverables produced by users through `golutra` belong to the users. +- This project follows the [Business Source License 1.1 (BSL 1.1)](https://mariadb.com/bsl11/) open-source license. + +### Downloads + +- Releases: + +### macOS Security Notice + +When first installing from `golutra_macos_aarch64.dmg`, macOS may show: + +- "App is damaged" or "Developer cannot be verified" + +This is a macOS security mechanism and does not necessarily indicate an issue with the app. + +How to open: + +1. Open Terminal. +2. Run the command below (replace the path with your actual app location). +3. If installed to Applications, for example: + +```bash +xattr -rd com.apple.quarantine /Applications/Golutra.app +``` + +Then open the app again. + +Why this happens: + +- macOS applies quarantine and signature checks to apps downloaded from the internet. +- Development or non-notarized builds may trigger this warning. +- This command only removes the quarantine flag and does not modify app contents. + +If the app still cannot be opened, please contact the publisher for support. + +## 中文 + +### 标语 + +一个人,拥有一个 AI 军团。 + +### 什么是 golutra + +`golutra` 是新一代多 Agent 协同工作软件。 +它不是传统 IDE 的插件增强,也不是单一 AI 对话工具,而是把复杂任务交给多个智能 Agent 并行执行的系统级工作台。 +`golutra` 基于 Vue 3 + Rust 开发,采用 Tauri 桌面应用架构,当前支持 Windows 和 macOS。 + +### 核心价值 + +- 无限 Agent 协作:自动拆解复杂任务,多个 Agent 并行推进。 +- 后台静默运行:离开当前界面时,Agent 仍可持续执行任务。 +- 终端随时显隐:主界面与终端状态快速切换,专注与透明兼得。 +- 头像即入口:点击 Agent 头像即可查看各自终端状态、日志与结果输出。 +- 自动编排流程:从分析、生成、策略到执行部署,形成完整闭环。 +- 个人 AI 团队:从“一个人 + 一个编辑器”升级为“一个人 + 一支 AI 军团”。 + +### CLI 兼容生态 + +保留你原本的 CLI,也能一键升级成协作中枢。 + +不换终端、不改习惯、不重学流程。直接沿用你正在使用的 CLI,把多智能体协作、任务编排与结果回传能力接到现有工程链路里。 + +支持的 CLI 工具: + +- Claude Code +- Gemini CLI +- Codex CLI +- OpenCode +- Qwen Code + +你将保留: + +- 无需迁移项目 +- 无需重学命令 +- 无需绑定单一工具 + +只用原本 CLI,也能做到: + +- 多智能体并行执行,结果自动回传到同一工作流 +- 跨 CLI 的统一调度与状态追踪,减少手动切换成本 +- 会话级上下文记忆与指令复用,避免重复解释需求 +- 测试、构建、回归信息集中汇总,交付路径更短 + +你继续使用熟悉的命令,`golutra` 负责把能力串联成完整工程闭环。 + +### 隐形终端 + +命令行级能力,可视化界面般易用。 + +将代码执行与后台终端无缝融合,让工作流实时响应。无需离开可视界面,也能获得命令行的原生掌控力。 + +- 直连注入:将提示词直接注入终端流,构建即时智能体反馈闭环。 +- 上下文感知:终端理解项目上下文,可为复杂任务提供更智能的自动补全。 + +### 为什么超越传统 IDE + +传统 IDE 工作流通常是“单线程 + 人工切换上下文”。 +`golutra` 是“多 Agent 并行 + 自动化编排协作”。 + +### 仓库说明 + +这个仓库用于源代码存放和版本发布。 + +商务邮箱: [golutra@hotmail.com](mailto:golutra%40hotmail.com) +官网: [https://www.golutra.com/](https://www.golutra.com/) +问题反馈企鹅群: 252090140 +安全策略: 详见 [SECURITY.md](SECURITY.md) + +### 作者 + +本软件由 [seekskyworld](https://github.com/seekskyworld) 独立开发与维护。 + +### 开源状态 + +项目现已开源,并将持续分阶段整理与开放相关代码。 + +为保障安全与隐私,涉及服务器密钥、账号配置及其他敏感信息的部分会先完成脱敏与重构,再逐步同步到仓库。 + +### 使用许可 + +- 允许将 `golutra` 作为工具用于商业软件开发。 +- 用户通过 `golutra` 产出的代码与交付成果归用户所有。 +- 项目遵守 [Business Source License 1.1 (BSL 1.1)](https://mariadb.com/bsl11/) 开源协议。 + +### 下载 + +- Releases: + +### macOS 安全提示说明 + +首次通过 `golutra_macos_aarch64.dmg` 安装应用,打开时,macOS 可能会提示: + +- “App 已损坏”或“无法验证开发者” + +这是 macOS 的安全机制提示,并不一定代表应用本身存在问题。 + +打开方式: + +1. 打开终端。 +2. 输入以下命令(将路径替换为你的 App 实际位置)。 +3. 例如安装后的位置: + +```bash +xattr -rd com.apple.quarantine /Applications/Golutra.app +``` + +然后再次打开应用即可。 + +为什么会出现这个提示: + +- macOS 会对来自互联网的应用进行隔离与签名校验。 +- 开发版或未公证版本可能触发该提示。 +- 该命令仅移除系统隔离标记,不会修改应用内容。 + +如果仍无法打开应用,请联系发布者获取帮助。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5013bf8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +If you discover a security vulnerability, please report it privately and do not open a public issue. + +## Reporting +- Email: golutra@hotmail.com +- Subject: [Security] Vulnerability Report + +## What to include +- A clear description of the issue +- Steps to reproduce (PoC if possible) +- Impact assessment +- Affected version(s) +- Your contact info for follow-up + +We will acknowledge receipt and provide a remediation plan once verified. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..aa68194 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,70 @@ +import js from '@eslint/js'; +import vue from 'eslint-plugin-vue'; +import tsParser from '@typescript-eslint/parser'; +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import vueParser from 'vue-eslint-parser'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; + +export default [ + { + ignores: ['node_modules', 'dist', 'coverage'] + }, + js.configs.recommended, + ...vue.configs['flat/recommended'], + { + files: ['**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + globals: { + ...globals.browser + } + }, + plugins: { + '@typescript-eslint': tsPlugin + }, + rules: { + ...tsPlugin.configs.recommended.rules, + '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/no-explicit-any': 'error' + } + }, + { + files: ['**/*.vue'], + languageOptions: { + parser: vueParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parser: tsParser + }, + globals: { + ...globals.browser + } + }, + plugins: { + '@typescript-eslint': tsPlugin + }, + rules: { + ...tsPlugin.configs.recommended.rules, + '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/no-explicit-any': 'error', + 'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }], + 'vue/multi-word-component-names': ['error', { ignores: ['App', 'Settings'] }], + 'vue/attributes-order': 'off' + } + }, + { + files: ['vite.config.ts', 'eslint.config.js'], + languageOptions: { + globals: { + ...globals.node + } + } + }, + prettier +]; diff --git a/index.html b/index.html new file mode 100644 index 0000000..9ac9080 --- /dev/null +++ b/index.html @@ -0,0 +1,109 @@ + + + + + + + Nexus Dashboard + + + + + + + + + + + + + + +
+ + + diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..e4bfa63 --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Nexus Dashboard Suite", + "description": "A high-fidelity suite of dashboard screens featuring a Skill Store, Plugin Marketplace, Settings, Workspace Selection, and advanced Chat Modals with glassmorphism aesthetics.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a659161 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "nexus-dashboard-suite", + "private": true, + "version": "0.0.0", + "packageManager": "pnpm@10.28.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest run", + "lint": "eslint \"src/**/*.{ts,vue}\" \"vite.config.ts\"", + "lint:fix": "eslint \"src/**/*.{ts,vue}\" \"vite.config.ts\" --fix", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, + "dependencies": { + "vue": "^3.5.12", + "vue-i18n": "^11.2.8" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^8.53.0", + "@typescript-eslint/parser": "^8.53.0", + "@eslint/js": "^9.39.2", + "@types/node": "^22.14.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-vue": "^10.7.0", + "globals": "^17.0.0", + "prettier": "^3.3.3", + "vue-eslint-parser": "^10.2.0", + "@vitejs/plugin-vue": "^5.2.4", + "typescript": "~5.8.2", + "vite": "^6.2.0", + "vitest": "^2.1.9" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..84223ca --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2430 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + vue: + specifier: ^3.5.12 + version: 3.5.26(typescript@5.8.3) + vue-i18n: + specifier: ^11.2.8 + version: 11.2.8(vue@3.5.26(typescript@5.8.3)) + devDependencies: + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 + '@types/node': + specifier: ^22.14.0 + version: 22.19.7 + '@typescript-eslint/eslint-plugin': + specifier: ^8.53.0 + version: 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(typescript@5.8.3) + '@typescript-eslint/parser': + specifier: ^8.53.0 + version: 8.53.0(eslint@9.39.2)(typescript@5.8.3) + '@vitejs/plugin-vue': + specifier: ^5.2.4 + version: 5.2.4(vite@6.4.1(@types/node@22.19.7))(vue@3.5.26(typescript@5.8.3)) + eslint: + specifier: ^9.39.2 + version: 9.39.2 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.39.2) + eslint-plugin-vue: + specifier: ^10.7.0 + version: 10.7.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(vue-eslint-parser@10.2.0(eslint@9.39.2)) + globals: + specifier: ^17.0.0 + version: 17.0.0 + prettier: + specifier: ^3.3.3 + version: 3.8.0 + typescript: + specifier: ~5.8.2 + version: 5.8.3 + vite: + specifier: ^6.2.0 + version: 6.4.1(@types/node@22.19.7) + vitest: + specifier: ^2.1.9 + version: 2.1.9(@types/node@22.19.7) + vue-eslint-parser: + specifier: ^10.2.0 + version: 10.2.0(eslint@9.39.2) + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@intlify/core-base@11.2.8': + resolution: {integrity: sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==} + engines: {node: '>= 16'} + + '@intlify/message-compiler@11.2.8': + resolution: {integrity: sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==} + engines: {node: '>= 16'} + + '@intlify/shared@11.2.8': + resolution: {integrity: sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==} + engines: {node: '>= 16'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.19.7': + resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} + + '@typescript-eslint/eslint-plugin@8.53.0': + resolution: {integrity: sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.53.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.53.0': + resolution: {integrity: sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.53.0': + resolution: {integrity: sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.53.0': + resolution: {integrity: sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.53.0': + resolution: {integrity: sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.53.0': + resolution: {integrity: sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.53.0': + resolution: {integrity: sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.53.0': + resolution: {integrity: sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.53.0': + resolution: {integrity: sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.53.0': + resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + peerDependencies: + vue: 3.5.26 + + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-vue@10.7.0: + resolution: {integrity: sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + vue-eslint-parser: ^10.0.0 + peerDependenciesMeta: + '@stylistic/eslint-plugin': + optional: true + '@typescript-eslint/parser': + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.0.0: + resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.0: + resolution: {integrity: sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vue-eslint-parser@10.2.0: + resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + vue-i18n@11.2.8: + resolution: {integrity: sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==} + engines: {node: '>= 16'} + peerDependencies: + vue: ^3.0.0 + + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': + dependencies: + eslint: 9.39.2 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@intlify/core-base@11.2.8': + dependencies: + '@intlify/message-compiler': 11.2.8 + '@intlify/shared': 11.2.8 + + '@intlify/message-compiler@11.2.8': + dependencies: + '@intlify/shared': 11.2.8 + source-map-js: 1.2.1 + + '@intlify/shared@11.2.8': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.19.7': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.53.0 + '@typescript-eslint/type-utils': 8.53.0(eslint@9.39.2)(typescript@5.8.3) + '@typescript-eslint/utils': 8.53.0(eslint@9.39.2)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.53.0 + eslint: 9.39.2 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.53.0 + '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.53.0 + debug: 4.4.3 + eslint: 9.39.2 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.53.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.8.3) + '@typescript-eslint/types': 8.53.0 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.53.0': + dependencies: + '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/visitor-keys': 8.53.0 + + '@typescript-eslint/tsconfig-utils@8.53.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.53.0(eslint@9.39.2)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.53.0(eslint@9.39.2)(typescript@5.8.3) + debug: 4.4.3 + eslint: 9.39.2 + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.53.0': {} + + '@typescript-eslint/typescript-estree@8.53.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.53.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.8.3) + '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/visitor-keys': 8.53.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.53.0(eslint@9.39.2)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.53.0 + '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.8.3) + eslint: 9.39.2 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.53.0': + dependencies: + '@typescript-eslint/types': 8.53.0 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@22.19.7))(vue@3.5.26(typescript@5.8.3))': + dependencies: + vite: 6.4.1(@types/node@22.19.7) + vue: 3.5.26(typescript@5.8.3) + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.7))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.7) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + '@vue/compiler-core@3.5.26': + dependencies: + '@babel/parser': 7.28.6 + '@vue/shared': 3.5.26 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.26': + dependencies: + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-sfc@3.5.26': + dependencies: + '@babel/parser': 7.28.6 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.26': + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.26': + dependencies: + '@vue/shared': 3.5.26 + + '@vue/runtime-core@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/runtime-dom@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.8.3) + + '@vue/shared@3.5.26': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + cac@6.7.14: {} + + callsites@3.1.0: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + entities@7.0.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.39.2): + dependencies: + eslint: 9.39.2 + + eslint-plugin-vue@10.7.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2)(typescript@5.8.3))(eslint@9.39.2)(vue-eslint-parser@10.2.0(eslint@9.39.2)): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + eslint: 9.39.2 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 7.1.1 + semver: 7.7.3 + vue-eslint-parser: 10.2.0(eslint@9.39.2) + xml-name-validator: 4.0.0 + optionalDependencies: + '@typescript-eslint/parser': 8.53.0(eslint@9.39.2)(typescript@5.8.3) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@17.0.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.8.0: {} + + punycode@2.3.1: {} + + resolve-from@4.0.0: {} + + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + ts-api-utils@2.4.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript@5.8.3: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite-node@2.1.9(@types/node@22.19.7): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@22.19.7) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@22.19.7): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.55.1 + optionalDependencies: + '@types/node': 22.19.7 + fsevents: 2.3.3 + + vite@6.4.1(@types/node@22.19.7): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.55.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.7 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@22.19.7): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.7)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@22.19.7) + vite-node: 2.1.9(@types/node@22.19.7) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.7 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vue-eslint-parser@10.2.0(eslint@9.39.2): + dependencies: + debug: 4.4.3 + eslint: 9.39.2 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + vue-i18n@11.2.8(vue@3.5.26(typescript@5.8.3)): + dependencies: + '@intlify/core-base': 11.2.8 + '@intlify/shared': 11.2.8 + '@vue/devtools-api': 6.6.4 + vue: 3.5.26(typescript@5.8.3) + + vue@3.5.26(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.8.3)) + '@vue/shared': 3.5.26 + optionalDependencies: + typescript: 5.8.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + xml-name-validator@4.0.0: {} + + yocto-queue@0.1.0: {} diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 0000000..502406b --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,4 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +/gen/schemas diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..23b83e3 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "app" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.77.2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2.5.3", features = [] } + +[dependencies] +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +log = "0.4" +tauri = { version = "2.9.5", features = [] } +tauri-plugin-log = "2" diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..795b9b7 --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..c135d7f --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "enables the default permissions", + "windows": [ + "main" + ], + "permissions": [ + "core:default" + ] +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..c1d48b7 Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..36a4b9d Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..8ce285f Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..eb9e1f8 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..62d18fa Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..fe2d0af Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..adb8d23 Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..b67d78f Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..e9f3ca3 Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..6f8c560 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..9ab8f74 Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..4eb0360 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..114af84 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/Transparency.png b/src-tauri/icons/Transparency.png new file mode 100644 index 0000000..e67822e Binary files /dev/null and b/src-tauri/icons/Transparency.png differ diff --git a/src-tauri/icons/icon-unread.png b/src-tauri/icons/icon-unread.png new file mode 100644 index 0000000..6973d0c Binary files /dev/null and b/src-tauri/icons/icon-unread.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..b13e443 Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..d9b0788 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..9710236 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..9c3118c --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,16 @@ +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .setup(|app| { + if cfg!(debug_assertions) { + app.handle().plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + )?; + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..ad5fe83 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + app_lib::run(); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..e7eb335 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "nexus-dashboard-suite", + "version": "0.1.0", + "identifier": "com.tauri.dev", + "build": { + "frontendDist": "../dist", + "devUrl": "http://localhost:3000", + "beforeDevCommand": "pnpm dev", + "beforeBuildCommand": "pnpm build" + }, + "app": { + "windows": [ + { + "title": "Nexus Dashboard Suite", + "width": 800, + "height": 600, + "resizable": true, + "fullscreen": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/src/app/App.vue b/src/app/App.vue new file mode 100644 index 0000000..c96b8e6 --- /dev/null +++ b/src/app/App.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..51c682d --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + const component: DefineComponent, Record, unknown>; + export default component; +} diff --git a/src/features/PluginMarketplace.vue b/src/features/PluginMarketplace.vue new file mode 100644 index 0000000..099914d --- /dev/null +++ b/src/features/PluginMarketplace.vue @@ -0,0 +1,150 @@ + + + diff --git a/src/features/Settings.vue b/src/features/Settings.vue new file mode 100644 index 0000000..a5d41c2 --- /dev/null +++ b/src/features/Settings.vue @@ -0,0 +1,921 @@ + + + diff --git a/src/features/SkillStore.vue b/src/features/SkillStore.vue new file mode 100644 index 0000000..af49bd2 --- /dev/null +++ b/src/features/SkillStore.vue @@ -0,0 +1,185 @@ + + + diff --git a/src/features/WorkspaceSelection.vue b/src/features/WorkspaceSelection.vue new file mode 100644 index 0000000..ba0352f --- /dev/null +++ b/src/features/WorkspaceSelection.vue @@ -0,0 +1,227 @@ + + + diff --git a/src/features/chat/ChatInterface.vue b/src/features/chat/ChatInterface.vue new file mode 100644 index 0000000..551db84 --- /dev/null +++ b/src/features/chat/ChatInterface.vue @@ -0,0 +1,395 @@ + + + + diff --git a/src/features/chat/components/ChatHeader.vue b/src/features/chat/components/ChatHeader.vue new file mode 100644 index 0000000..cb41bb0 --- /dev/null +++ b/src/features/chat/components/ChatHeader.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/features/chat/components/ChatInput.vue b/src/features/chat/components/ChatInput.vue new file mode 100644 index 0000000..3dabfbc --- /dev/null +++ b/src/features/chat/components/ChatInput.vue @@ -0,0 +1,326 @@ + + + diff --git a/src/features/chat/components/ChatSidebar.vue b/src/features/chat/components/ChatSidebar.vue new file mode 100644 index 0000000..6769bb4 --- /dev/null +++ b/src/features/chat/components/ChatSidebar.vue @@ -0,0 +1,307 @@ + + + diff --git a/src/features/chat/components/InviteMenu.vue b/src/features/chat/components/InviteMenu.vue new file mode 100644 index 0000000..6b17536 --- /dev/null +++ b/src/features/chat/components/InviteMenu.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/features/chat/components/MemberRow.vue b/src/features/chat/components/MemberRow.vue new file mode 100644 index 0000000..3aa18cb --- /dev/null +++ b/src/features/chat/components/MemberRow.vue @@ -0,0 +1,98 @@ + + + diff --git a/src/features/chat/components/MembersSidebar.vue b/src/features/chat/components/MembersSidebar.vue new file mode 100644 index 0000000..7a2c3e3 --- /dev/null +++ b/src/features/chat/components/MembersSidebar.vue @@ -0,0 +1,131 @@ + + + diff --git a/src/features/chat/components/MessagesList.vue b/src/features/chat/components/MessagesList.vue new file mode 100644 index 0000000..ad33247 --- /dev/null +++ b/src/features/chat/components/MessagesList.vue @@ -0,0 +1,166 @@ + + + diff --git a/src/features/chat/composables/useChatSession.ts b/src/features/chat/composables/useChatSession.ts new file mode 100644 index 0000000..8f48624 --- /dev/null +++ b/src/features/chat/composables/useChatSession.ts @@ -0,0 +1,428 @@ +import { computed, ref, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { + AI_ASSISTANT_ID, + CURRENT_USER_ID, + DEFAULT_CHANNEL_ID, + DEFAULT_CHANNEL_NAME_KEY, + initialMembers, + initialMessages +} from '../data'; +import { PRIMARY_USER_AVATAR_URL } from '../../../shared/constants/avatars'; +import type { Conversation, ConversationType, Member, Message, MessageStatus } from '../types'; +import { ensureUniqueName, formatMessageTime } from '../utils'; + +const STORAGE_KEY = 'nexus-chat-session-v1'; +const SEND_SIMULATION_DELAY = 200; +const ASSISTANT_REPLY_DELAY = 900; +const MAX_MESSAGE_LENGTH = 1200; + +type StoredSession = { + members: Member[]; + conversations: Conversation[]; + nextMessageId: number; + messages?: Message[]; + directMessageIds?: string[]; +}; + +const buildConversationId = (type: ConversationType, targetId: string) => `${type}:${targetId}`; + +const getMemberAvatar = (members: Member[], id?: string) => { + if (!id) return undefined; + return members.find((member) => member.id === id)?.avatar; +}; + +const getCurrentUserName = (members: Member[]) => members.find((member) => member.id === CURRENT_USER_ID)?.name ?? 'You'; + +const buildUiAvatar = (name: string) => + `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=0b57d0&color=fff`; + +const resolveMessageAvatar = (message: Message, members: Member[]) => { + const memberAvatar = getMemberAvatar(members, message.senderId); + if (memberAvatar) return memberAvatar; + + if (message.avatar && message.avatar !== PRIMARY_USER_AVATAR_URL) { + return message.avatar; + } + + const currentUserName = getCurrentUserName(members); + if (message.user && message.user !== currentUserName) { + return buildUiAvatar(message.user); + } + + return message.avatar || PRIMARY_USER_AVATAR_URL; +}; + +const getNextMessageIdFromConversations = (conversations: Conversation[]) => + conversations.reduce((max, conversation) => { + const conversationMax = conversation.messages.reduce((messageMax, message) => Math.max(messageMax, message.id), 0); + return Math.max(max, conversationMax); + }, 0) + 1; + +const normalizeMessage = (message: Message, members: Member[]): Message => { + const createdAt = typeof message.createdAt === 'number' ? message.createdAt : Date.now(); + const status: MessageStatus = message.status === 'failed' ? 'failed' : 'sent'; + const avatar = resolveMessageAvatar(message, members); + + return { + ...message, + createdAt, + time: message.time || formatMessageTime(createdAt), + status, + avatar + }; +}; + +const createChannelConversation = (messages: Message[], members: Member[]): Conversation => ({ + id: buildConversationId('channel', DEFAULT_CHANNEL_ID), + type: 'channel', + targetId: DEFAULT_CHANNEL_ID, + nameKey: DEFAULT_CHANNEL_NAME_KEY, + pinned: false, + muted: false, + messages: messages.map((message) => normalizeMessage(message, members)) +}); + +const createDirectConversation = (memberId: string): Conversation => ({ + id: buildConversationId('dm', memberId), + type: 'dm', + targetId: memberId, + pinned: false, + muted: false, + messages: [] +}); + +const normalizeConversation = (conversation: Partial, members: Member[]): Conversation | null => { + if (conversation.type !== 'channel' && conversation.type !== 'dm') return null; + if (typeof conversation.id !== 'string' || !conversation.id) return null; + if (typeof conversation.targetId !== 'string' || !conversation.targetId) return null; + + return { + id: conversation.id, + type: conversation.type, + targetId: conversation.targetId, + nameKey: typeof conversation.nameKey === 'string' ? conversation.nameKey : undefined, + customName: typeof conversation.customName === 'string' ? conversation.customName : undefined, + descriptionKey: typeof conversation.descriptionKey === 'string' ? conversation.descriptionKey : undefined, + pinned: Boolean(conversation.pinned), + muted: Boolean(conversation.muted), + messages: Array.isArray(conversation.messages) + ? conversation.messages.map((message) => normalizeMessage(message as Message, members)) + : [] + }; +}; + +const buildDefaultSession = (): StoredSession => { + const members = [...initialMembers]; + const defaultConversation = createChannelConversation(initialMessages, members); + return { + members, + conversations: [defaultConversation], + nextMessageId: getNextMessageIdFromConversations([defaultConversation]) + }; +}; + +const loadStoredSession = (): StoredSession => { + if (typeof window === 'undefined') { + return buildDefaultSession(); + } + + const raw = window.localStorage.getItem(STORAGE_KEY); + if (!raw) { + return buildDefaultSession(); + } + + try { + const parsed = JSON.parse(raw) as Partial; + const members = Array.isArray(parsed.members) ? parsed.members : initialMembers; + let conversations: Conversation[] = []; + + if (Array.isArray(parsed.conversations)) { + conversations = parsed.conversations + .map((conversation) => normalizeConversation(conversation, members)) + .filter((conversation): conversation is Conversation => Boolean(conversation)); + } else { + const messages = Array.isArray(parsed.messages) + ? parsed.messages.map((message) => normalizeMessage(message as Message, members)) + : initialMessages.map((message) => normalizeMessage(message, members)); + conversations = [createChannelConversation(messages, members)]; + + if (Array.isArray(parsed.directMessageIds)) { + parsed.directMessageIds + .filter((id) => typeof id === 'string') + .forEach((memberId) => { + if (members.some((member) => member.id === memberId)) { + conversations.push(createDirectConversation(memberId)); + } + }); + } + } + + conversations = conversations.filter((conversation) => { + if (conversation.type === 'dm') { + return members.some((member) => member.id === conversation.targetId); + } + return true; + }); + + const computedNextId = getNextMessageIdFromConversations(conversations); + const nextMessageId = + typeof parsed.nextMessageId === 'number' ? Math.max(parsed.nextMessageId, computedNextId) : computedNextId; + + return { + members, + conversations, + nextMessageId + }; + } catch { + return buildDefaultSession(); + } +}; + +const persistSession = (session: StoredSession) => { + if (typeof window === 'undefined') return; + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(session)); +}; + +export const useChatSession = () => { + const { t, locale } = useI18n(); + const stored = loadStoredSession(); + + const members = ref(stored.members); + const conversations = ref(stored.conversations); + const nextMessageId = ref(stored.nextMessageId); + const assistantReplyTimeoutId = ref(null); + const assistantTypingConversationId = ref(null); + + const currentUser = computed(() => members.value.find((member) => member.id === CURRENT_USER_ID) ?? members.value[0]); + const assistantMember = computed( + () => + members.value.find((member) => member.id === AI_ASSISTANT_ID) ?? + members.value.find((member) => member.roleType === 'assistant') ?? + null + ); + + const updateConversation = (conversationId: string, updater: (conversation: Conversation) => Conversation) => { + conversations.value = conversations.value.map((conversation) => + conversation.id === conversationId ? updater(conversation) : conversation + ); + }; + + const updateMessageStatus = (conversationId: string, id: number, status: MessageStatus) => { + updateConversation(conversationId, (conversation) => ({ + ...conversation, + messages: conversation.messages.map((message) => (message.id === id ? { ...message, status } : message)) + })); + }; + + const addMessage = (conversationId: string, message: Message) => { + updateConversation(conversationId, (conversation) => ({ + ...conversation, + messages: [...conversation.messages, message] + })); + }; + + const sendMessage = (rawText: string, conversationId: string) => { + const trimmed = rawText.trim(); + if (!trimmed) return null; + const conversation = conversations.value.find((item) => item.id === conversationId); + if (!conversation) return null; + + const text = trimmed.slice(0, MAX_MESSAGE_LENGTH); + const createdAt = Date.now(); + const message: Message = { + id: nextMessageId.value, + senderId: CURRENT_USER_ID, + user: currentUser.value?.name ?? 'You', + avatar: currentUser.value?.avatar ?? PRIMARY_USER_AVATAR_URL, + text, + time: formatMessageTime(createdAt, locale.value), + createdAt, + isAi: false, + status: 'sending' + }; + + nextMessageId.value += 1; + addMessage(conversation.id, message); + + window.setTimeout(() => { + updateMessageStatus(conversation.id, message.id, 'sent'); + }, SEND_SIMULATION_DELAY); + + if (assistantMember.value) { + queueAssistantReply(text, conversation.id); + } + + return message; + }; + + const queueAssistantReply = (prompt: string, conversationId: string) => { + if (assistantReplyTimeoutId.value !== null) { + window.clearTimeout(assistantReplyTimeoutId.value); + } + assistantTypingConversationId.value = conversationId; + const replyText = prompt.includes('?') ? t('chat.messages.autoReplyQuestion') : t('chat.messages.autoReply'); + + assistantReplyTimeoutId.value = window.setTimeout(() => { + const createdAt = Date.now(); + const reply: Message = { + id: nextMessageId.value, + senderId: assistantMember.value?.id, + user: assistantMember.value?.name ?? t('members.roles.aiAssistant'), + avatar: assistantMember.value?.avatar ?? PRIMARY_USER_AVATAR_URL, + text: replyText, + time: formatMessageTime(createdAt, locale.value), + createdAt, + isAi: true, + status: 'sent' + }; + + nextMessageId.value += 1; + addMessage(conversationId, reply); + assistantTypingConversationId.value = null; + assistantReplyTimeoutId.value = null; + }, ASSISTANT_REPLY_DELAY); + }; + + const cancelAssistantReply = () => { + if (assistantReplyTimeoutId.value !== null) { + window.clearTimeout(assistantReplyTimeoutId.value); + assistantReplyTimeoutId.value = null; + } + assistantTypingConversationId.value = null; + }; + + const addMember = (member: Omit & { id?: string }) => { + const id = member.id ?? Date.now().toString(); + members.value = [...members.value, { ...member, id }]; + return id; + }; + + const updateMember = (id: string, updates: Partial) => { + const updatedMember = members.value.find((member) => member.id === id); + if (!updatedMember) return; + + const nextMember = { ...updatedMember, ...updates }; + members.value = members.value.map((member) => (member.id === id ? nextMember : member)); + + conversations.value = conversations.value.map((conversation) => ({ + ...conversation, + messages: conversation.messages.map((message) => { + if (message.senderId !== id) return message; + return { + ...message, + user: updates.name ?? message.user, + avatar: updates.avatar ?? message.avatar + }; + }) + })); + }; + + const removeMember = (id: string) => { + if (id === CURRENT_USER_ID) return; + members.value = members.value.filter((member) => member.id !== id); + conversations.value = conversations.value.filter((conversation) => + conversation.type === 'dm' ? conversation.targetId !== id : true + ); + }; + + const ensureDirectMessage = (memberId: string) => { + if (!memberId || memberId === CURRENT_USER_ID) return null; + if (!members.value.some((member) => member.id === memberId)) return null; + + const existing = conversations.value.find((conversation) => conversation.type === 'dm' && conversation.targetId === memberId); + if (existing) { + return existing.id; + } + + const conversation = createDirectConversation(memberId); + conversations.value = [...conversations.value, conversation]; + return conversation.id; + }; + + const toggleConversationPin = (conversationId: string) => { + updateConversation(conversationId, (conversation) => ({ + ...conversation, + pinned: !conversation.pinned + })); + }; + + const toggleConversationMute = (conversationId: string) => { + updateConversation(conversationId, (conversation) => ({ + ...conversation, + muted: !conversation.muted + })); + }; + + const renameConversation = (conversationId: string, name: string) => { + const trimmed = name.trim(); + updateConversation(conversationId, (conversation) => ({ + ...conversation, + customName: trimmed ? trimmed : undefined + })); + }; + + const clearConversationMessages = (conversationId: string) => { + updateConversation(conversationId, (conversation) => ({ + ...conversation, + messages: [] + })); + }; + + const deleteConversation = (conversationId: string) => { + if (conversationId === buildConversationId('channel', DEFAULT_CHANNEL_ID)) { + return; + } + conversations.value = conversations.value.filter((conversation) => conversation.id !== conversationId); + }; + + const createInviteMember = (name: string, roleType: 'assistant' | 'member', roleKey: string, status: Member['status']) => { + const finalName = ensureUniqueName(name, members.value); + const avatar = roleType === 'assistant' + ? `https://ui-avatars.com/api/?name=${encodeURIComponent(finalName)}&background=0b57d0&color=fff` + : `https://picsum.photos/seed/${encodeURIComponent(finalName)}/200/200`; + + return { + id: Date.now().toString(), + name: finalName, + role: '', + roleKey, + roleType, + avatar, + status + }; + }; + + watch( + [conversations, members, nextMessageId], + () => { + persistSession({ + conversations: conversations.value, + members: members.value, + nextMessageId: nextMessageId.value + }); + }, + { deep: true } + ); + + return { + members, + conversations, + currentUser, + assistantMember, + assistantTypingConversationId, + cancelAssistantReply, + maxMessageLength: MAX_MESSAGE_LENGTH, + sendMessage, + addMember, + updateMember, + removeMember, + createInviteMember, + ensureDirectMessage, + toggleConversationPin, + toggleConversationMute, + renameConversation, + clearConversationMessages, + deleteConversation + }; +}; diff --git a/src/features/chat/data.ts b/src/features/chat/data.ts new file mode 100644 index 0000000..debc8ea --- /dev/null +++ b/src/features/chat/data.ts @@ -0,0 +1,118 @@ +import type { Member, Message } from './types'; +import { PRIMARY_USER_AVATAR_URL } from '../../shared/constants/avatars'; +import { formatMessageTime } from './utils'; + +export const CURRENT_USER_ID = 'me'; +export const AI_ASSISTANT_ID = 'ai-assistant'; +export const DEFAULT_CHANNEL_ID = 'design-critique'; +export const DEFAULT_CHANNEL_NAME_KEY = 'chat.sidebar.channelList.designCritique'; + +const seedBaseTime = Date.now() - 1000 * 60 * 45; + +const getMemberAvatar = (id: string) => initialMembers.find((member) => member.id === id)?.avatar ?? PRIMARY_USER_AVATAR_URL; +const SARAH_AVATAR_URL = 'https://ui-avatars.com/api/?name=Sarah+Jenkins&background=f97316&color=fff'; + +export const initialMembers: Member[] = [ + { + id: 'me', + name: 'You (Owner)', + role: '', + roleKey: 'members.activity.reviewingPRs', + roleType: 'owner', + avatar: PRIMARY_USER_AVATAR_URL, + status: 'online' + }, + { + id: AI_ASSISTANT_ID, + name: 'AI Assistant', + role: '', + roleKey: 'members.roles.aiAssistant', + roleType: 'assistant', + avatar: 'https://ui-avatars.com/api/?name=AI+Assistant&background=0b57d0&color=fff', + status: 'online' + }, + { + id: 'alex', + name: 'Alex Designer', + role: '', + roleKey: 'members.activity.listeningSpotify', + roleType: 'admin', + avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBaSWKB6mJCxTBrtvopWrUkwsow24JW16yM25iBaJ-i9zei2LLH2S2RuQAjClLKuBNbaKzFR9KdlrlnzjOONBvHumzYm6gwPCtacfn8Q1j2EM62pNme3MdwszqcLe-WnRiGFfDWnjvZna6DoiK0mpMNiTQyR5BfbSkirNIuJkIsXfO-uNqs0bwysJtFjeHP22BouFsb7HsObMaenKoOCxLgfgmYhMwTA_0ycwngEFMPuRnSXwfrAJThiBYH1y7nP7_pdwvn5SLJEVQ', + status: 'online' + }, + { + id: 'marcus', + name: 'Marcus Chen', + role: '', + roleKey: 'members.activity.fixingBugs', + roleType: 'admin', + avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuA6Ru1OP242ZM7Pqmcc0E4azuCTf6vQIbkDx_iqStSVeKqyw3_us_PT6uR2ieVDWo2PvDla6v8RvelP6L8mpxUNyJEzg5ch_a6A74BqRQ5ET3yvc0Iffy76hUZm2OG1xulIanIzIgTq6Roi7YG89KWLFjdKvww42xqUfnZE-BZMBVOm3n5EPuj-uUKP38BsWLfg7sPsv6-RquG7EMzizpe131L1FOy338mP5E_lX8bj_DGkZB_LtAa4onKR6wNSUm0x523rU124FFU', + status: 'offline' + }, + { + id: 'elena', + name: 'Elena Rodriguez', + role: '', + roleKey: 'members.activity.doNotDisturb', + roleType: 'assistant', + avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuANDYn5G_lzwb69JjXV0UV20RCfWcczalPe0pR-bPyraoHXJHGDyu6TWus4ohJqw3XZHMdjNjeg6nPslzIBQsDvxzpZhZ1N-4Qrz9kJGhzxY29NPp5Om9mssWsQnYbUZX6yxX44uj8TVVPb25Wft6Xsf39EMCb0Phw7liJjW4G1jbw0_nkffIq8h6UGHjoYAszOmHSKsfeIPKB78f5MICNpst_Cg6gYCc_PrX4DfXeAFupP9Ur1SlE1gH-hw2OPj9oFc46WNApWrEA', + status: 'dnd' + }, + { + id: 'david', + name: 'David Kim', + role: '', + roleType: 'member', + avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBFQzuk64FLzX_ajt_3zGChUGLWu1uKdEIOD2Dhf-vzZ_qsa4W9PaFKPoBZbHJWwOzqMuqSzlcI-8qJsp1S7zzFOiNOrVtRhwe3pyPbWdd6KXY_EiNKRTk7T7odtGYfgww3ZB98j9bs0kY0wXgMLPGvKUHECZtbXbPX3OUW67dhx5Yj_IxM-cQnmYCMSAs7S_eYfBi7aVonF2Ab8Yb8lhxA5mC8S9Vdp2i3hmSihMheDqZY97OTqwh4vX-yzFai4f_K1WK-nRWyB7Q', + status: 'offline' + }, + { + id: 'priya', + name: 'Priya Patel', + role: '', + roleType: 'member', + avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBS4LtfNbFLYhN3jlSqNIjwYajAlCLQehKY1GrzGpz7HuLfg_5Hp2_Nant5AasdB-kQg-3K9XgPfV2yHnT3k0OYMi4hAb7ZgtgG21tEINu95frS1lG58-HH_NYb4eH-2BLSvnxpxkUKjWxueAkDNBcJ4VM3FZMYZ94039lGD17Z7ha6oGb9XgD73hrhR6PaiAam5uaJxxUOTEpvlBgRbeYoSVIgQfh3B_fVZmSgppRfZliShDFtm4uWn16K4WIopmqqiNe68biESLE', + status: 'offline' + } +]; + +export const initialMessages: Message[] = [ + { + id: 1, + user: 'Sarah Jenkins', + avatar: SARAH_AVATAR_URL, + text: 'Hey everyone! 👋 I just pushed the new mockups for the dashboard. Would love some eyes on the spacing for the sidebar.', + time: formatMessageTime(seedBaseTime), + createdAt: seedBaseTime, + isAi: false, + status: 'sent', + attachment: { + type: 'image', + name: 'dashboard_v2_final.png', + size: '2.4 MB', + url: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAXwrGif3OTugu7R6Ry57VrWDqm-ccgrPxj2i2TLmIiwGt1GBqG5ekNNmrF-8FN0auYgBF_kd72SzQayNrryi57HXUT9eQXLeEqdnFwo4T4uwg8vzfF_l9qg_fRwZcqL6DmPlnYBxfErcbpj4ehD1UqZP5QdlZTFcpIfdcYP01LJ77YSTXSwjiEL3oK2eFAyD6lJlYoKzuOQgyvC6o4hmfwW7QG_mhp_6IlG207xJQ0YJJuvpE7CP3MSJAP0iiFESKx7OiNguxi-IE' + } + }, + { + id: 2, + senderId: 'alex', + user: 'Alex Designer', + avatar: getMemberAvatar('alex'), + text: 'Looks great! I love the coral accent color you chose. It really pops against the dark background. 🔥', + time: formatMessageTime(seedBaseTime + 1000 * 60 * 3), + createdAt: seedBaseTime + 1000 * 60 * 3, + isAi: false, + status: 'sent' + }, + { + id: 3, + senderId: 'marcus', + user: 'Marcus Chen', + avatar: getMemberAvatar('marcus'), + text: 'Agreed. @Sarah Jenkins when do we plan to ship this? I need to prep the backend.', + time: formatMessageTime(seedBaseTime + 1000 * 60 * 10), + createdAt: seedBaseTime + 1000 * 60 * 10, + isAi: false, + status: 'sent' + } +]; diff --git a/src/features/chat/modals/InviteAdminModal.vue b/src/features/chat/modals/InviteAdminModal.vue new file mode 100644 index 0000000..0a4c66a --- /dev/null +++ b/src/features/chat/modals/InviteAdminModal.vue @@ -0,0 +1,98 @@ + + + diff --git a/src/features/chat/modals/InviteAssistantModal.vue b/src/features/chat/modals/InviteAssistantModal.vue new file mode 100644 index 0000000..3c3d130 --- /dev/null +++ b/src/features/chat/modals/InviteAssistantModal.vue @@ -0,0 +1,186 @@ + + + diff --git a/src/features/chat/modals/ManageMemberModal.vue b/src/features/chat/modals/ManageMemberModal.vue new file mode 100644 index 0000000..b18f1f0 --- /dev/null +++ b/src/features/chat/modals/ManageMemberModal.vue @@ -0,0 +1,69 @@ + + + diff --git a/src/features/chat/modals/RenameConversationModal.vue b/src/features/chat/modals/RenameConversationModal.vue new file mode 100644 index 0000000..bc6ab75 --- /dev/null +++ b/src/features/chat/modals/RenameConversationModal.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/features/chat/modals/RoadmapModal.vue b/src/features/chat/modals/RoadmapModal.vue new file mode 100644 index 0000000..dab9592 --- /dev/null +++ b/src/features/chat/modals/RoadmapModal.vue @@ -0,0 +1,239 @@ + + + diff --git a/src/features/chat/modals/SkillDetailModal.vue b/src/features/chat/modals/SkillDetailModal.vue new file mode 100644 index 0000000..2c239a8 --- /dev/null +++ b/src/features/chat/modals/SkillDetailModal.vue @@ -0,0 +1,133 @@ + + + diff --git a/src/features/chat/modals/SkillManagementModal.vue b/src/features/chat/modals/SkillManagementModal.vue new file mode 100644 index 0000000..03ed3da --- /dev/null +++ b/src/features/chat/modals/SkillManagementModal.vue @@ -0,0 +1,205 @@ + + + diff --git a/src/features/chat/types.ts b/src/features/chat/types.ts new file mode 100644 index 0000000..ee0edd5 --- /dev/null +++ b/src/features/chat/types.ts @@ -0,0 +1,66 @@ +export type MemberRole = 'owner' | 'admin' | 'assistant' | 'member'; +export type MemberStatus = 'online' | 'offline' | 'dnd'; + +export type Member = { + id: string; + name: string; + role: string; + roleKey?: string; + roleType: MemberRole; + avatar: string; + status: MemberStatus; +}; + +export type MemberAction = 'send-message' | 'mention' | 'rename' | 'remove'; + +export type ConversationType = 'channel' | 'dm'; + +export type Conversation = { + id: string; + type: ConversationType; + targetId: string; + nameKey?: string; + customName?: string; + descriptionKey?: string; + pinned: boolean; + muted: boolean; + messages: Message[]; +}; + +export type ConversationAction = 'pin' | 'unpin' | 'rename' | 'mute' | 'unmute' | 'clear' | 'delete'; + +export type MessageAttachment = + | { + type: 'image'; + name: string; + size: string; + url: string; + } + | { + type: 'roadmap'; + title: string; + }; + +export type MessageStatus = 'sending' | 'sent' | 'failed'; + +export type Message = { + id: number; + senderId?: string; + user: string; + avatar: string; + text: string; + time: string; + createdAt: number; + isAi: boolean; + attachment?: MessageAttachment; + status?: MessageStatus; +}; + +export type RoadmapTaskStatus = 'done' | 'in-progress' | 'pending'; + +export type RoadmapTask = { + id: number; + number: string; + title: string; + status: RoadmapTaskStatus; +}; diff --git a/src/features/chat/utils.ts b/src/features/chat/utils.ts new file mode 100644 index 0000000..25a15b2 --- /dev/null +++ b/src/features/chat/utils.ts @@ -0,0 +1,77 @@ +import type { Member, Message } from './types'; + +export const splitMentions = (text: string) => text.split(/(@[\w\s]+)/g).filter(Boolean); + +export const ensureUniqueName = (name: string, members: Member[]) => { + // Enforce case-insensitive uniqueness to match the UI's duplicate prevention. + const lowerNames = new Set(members.map((member) => member.name.toLowerCase())); + + if (!lowerNames.has(name.toLowerCase())) { + return name; + } + + let counter = 1; + let candidate = `${name}-${counter}`; + while (lowerNames.has(candidate.toLowerCase())) { + counter += 1; + candidate = `${name}-${counter}`; + } + + return candidate; +}; + +export const formatMessageTime = (timestamp: number, locale?: string) => { + const formatter = new Intl.DateTimeFormat(locale, { + hour: '2-digit', + minute: '2-digit' + }); + return formatter.format(new Date(timestamp)); +}; + +export const getMessageDayKey = (timestamp: number) => { + const date = new Date(timestamp); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +}; + +export const formatDayLabel = (timestamp: number, locale?: string) => { + const formatter = new Intl.DateTimeFormat(locale, { + month: 'long', + day: 'numeric', + year: 'numeric' + }); + return formatter.format(new Date(timestamp)); +}; + +export type MessageDisplayItem = + | { type: 'separator'; id: string; label: string } + | { type: 'message'; id: string; message: Message }; + +export const groupMessagesByDay = (messages: Message[], locale?: string): MessageDisplayItem[] => { + const items: MessageDisplayItem[] = []; + let lastDayKey = ''; + + [...messages] + .sort((a, b) => a.createdAt - b.createdAt) + .forEach((message) => { + const dayKey = getMessageDayKey(message.createdAt); + if (dayKey !== lastDayKey) { + items.push({ + type: 'separator', + id: `separator-${dayKey}`, + label: formatDayLabel(message.createdAt, locale) + }); + lastDayKey = dayKey; + } + + items.push({ + type: 'message', + id: `message-${message.id}`, + message + }); + }); + + return items; +}; diff --git a/src/features/skills/skillLibrary.ts b/src/features/skills/skillLibrary.ts new file mode 100644 index 0000000..4e98d19 --- /dev/null +++ b/src/features/skills/skillLibrary.ts @@ -0,0 +1,84 @@ +export type SkillLibraryItem = { + id: number; + nameKey: string; + descKey: string; + icon: string; + color: string; + bg: string; + ring: string; + gradient: string; + ver: string; + assetsKey: string; + checked: boolean; +}; + +const librarySkillSeed: SkillLibraryItem[] = [ + { + id: 1, + nameKey: 'skills.items.frontendToolkit.name', + descKey: 'skills.items.frontendToolkit.desc', + icon: 'code', + color: 'text-blue-400', + bg: 'bg-blue-500/10', + ring: 'ring-blue-500/20', + gradient: 'from-blue-500/10', + ver: 'v2.4.0', + assetsKey: 'skills.assets.frontendToolkit', + checked: true + }, + { + id: 2, + nameKey: 'skills.items.iconAssetPack.name', + descKey: 'skills.items.iconAssetPack.desc', + icon: 'category', + color: 'text-purple-400', + bg: 'bg-purple-500/10', + ring: 'ring-purple-500/20', + gradient: 'from-purple-500/10', + ver: 'v1.1.0', + assetsKey: 'skills.assets.iconAssetPack', + checked: false + }, + { + id: 3, + nameKey: 'skills.items.motionPresets.name', + descKey: 'skills.items.motionPresets.desc', + icon: 'motion_photos_on', + color: 'text-orange-400', + bg: 'bg-orange-500/10', + ring: 'ring-orange-500/20', + gradient: 'from-orange-500/10', + ver: 'v3.0.0', + assetsKey: 'skills.assets.motionPresets', + checked: true + }, + { + id: 4, + nameKey: 'skills.items.shellCommands.name', + descKey: 'skills.items.shellCommands.desc', + icon: 'terminal', + color: 'text-emerald-400', + bg: 'bg-emerald-500/10', + ring: 'ring-emerald-500/20', + gradient: 'from-emerald-500/10', + ver: 'v0.9.1', + assetsKey: 'skills.assets.shellCommands', + checked: false + }, + { + id: 5, + nameKey: 'skills.items.brandColors.name', + descKey: 'skills.items.brandColors.desc', + icon: 'palette', + color: 'text-pink-400', + bg: 'bg-pink-500/10', + ring: 'ring-pink-500/20', + gradient: 'from-pink-500/10', + ver: 'v4.2', + assetsKey: 'skills.assets.brandColors', + checked: false + } +]; + +export const createLibrarySkills = (): SkillLibraryItem[] => + librarySkillSeed.map((skill) => ({ ...skill })); diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..8290adb --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,42 @@ +import { createI18n } from 'vue-i18n'; +import enUS from './locales/en-US'; +import zhCN from './locales/zh-CN'; + +export const LOCALE_STORAGE_KEY = 'nexus-locale'; + +export const messages = { + 'en-US': enUS, + 'zh-CN': zhCN +} as const; + +export type AppLocale = keyof typeof messages; + +const detectLocale = (): AppLocale => { + if (typeof window === 'undefined') { + return 'en-US'; + } + + const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY); + if (stored && stored in messages) { + return stored as AppLocale; + } + + return 'en-US'; +}; + +const initialLocale = detectLocale(); + +export const i18n = createI18n({ + legacy: false, + locale: initialLocale, + fallbackLocale: 'en-US', + messages +}); + +export const setDocumentLang = (locale: AppLocale) => { + if (typeof document !== 'undefined') { + document.documentElement.lang = locale; + } +}; + +setDocumentLang(initialLocale); diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts new file mode 100644 index 0000000..48d1ac8 --- /dev/null +++ b/src/i18n/locales/en-US.ts @@ -0,0 +1,535 @@ +export default { + app: { + name: 'Nexus Dashboard' + }, + nav: { + chat: 'Chat & Modals', + workspaces: 'Workspaces', + store: 'Skill Store', + plugins: 'Plugins', + settings: 'Settings' + }, + common: { + userAvatarAlt: 'User', + remove: 'Remove' + }, + chat: { + channelName: 'design-critique', + channelDisplay: '#design-critique', + channelDescription: '', + directMessageDescription: 'Direct message with {name}', + header: { + todo: 'Todo', + inventory: 'Inventory' + }, + input: { + placeholder: 'Message {channel}', + directPlaceholder: 'Message @{name}', + send: 'Send', + stop: 'Stop', + hint: 'Enter to send • Shift+Enter for newline', + quickPrompts: { + summarize: 'Summarize the latest discussion', + draftReply: 'Draft a polite reply', + extractTasks: 'Extract action items' + } + }, + sidebar: { + workspaceName: 'Frontend Squad', + channels: 'Channels', + directMessages: 'Direct Messages', + channelList: { + announcements: 'announcements', + generalChat: 'general-chat', + designCritique: 'design-critique', + resources: 'resources' + } + }, + messages: { + dateSeparator: 'October 24, 2023', + roadmapHint: 'Click to view roadmap', + userJoined: '{name} joined the server', + joinedUser: 'James', + sampleMessage: { + user: 'Sarah Jenkins', + time: '11:05 AM', + text: 'Hopefully by Friday! Just need final sign-off from product.' + }, + autoReply: 'I have processed your request. Is there anything else you need?', + autoReplyQuestion: 'Let me take a closer look and get back to you shortly.', + jumpToLatest: 'Jump to latest', + typing: '{name} is typing...', + status: { + sending: 'Sending...', + failed: 'Failed to send' + } + }, + conversation: { + actions: { + pin: 'Pin', + unpin: 'Unpin', + rename: 'Rename Group', + mute: 'Mute Notifications', + unmute: 'Unmute Notifications', + clear: 'Clear Chat History', + deleteChannel: 'Delete Group Chat', + deleteDirect: 'Delete Conversation' + }, + renameTitle: 'Rename Group Chat', + renameLabel: 'Group Name', + renamePlaceholder: 'Enter group name' + } + }, + members: { + title: 'Members', + sections: { + owner: 'Group Owner — {count}', + admin: 'Admins — {count}', + assistant: 'Assistants — {count}', + member: 'General Members — {count}' + }, + roles: { + owner: 'Owner', + admin: 'Admin', + assistant: 'Assistant', + member: 'Member', + aiAssistant: 'AI Assistant' + }, + actions: { + sendMessage: 'Send Message', + mention: '@ Mention', + rename: 'Rename' + }, + manage: { + title: 'Manage Member', + displayName: 'Display Name', + remove: 'Remove from Group' + }, + activity: { + reviewingPRs: 'Reviewing PRs', + listeningSpotify: 'Listening to Spotify', + fixingBugs: 'Fixing bugs', + doNotDisturb: 'Do Not Disturb' + } + }, + invite: { + menu: { + title: 'Invite to Server', + subtitle: 'Generate a unique invite link', + admin: 'Invite as Admin', + adminDesc: 'Full server access', + assistant: 'Invite as Assistant', + assistantDesc: 'Moderation permissions', + member: 'General Member', + memberDesc: 'Standard access' + }, + admin: { + title: 'Invite as Admin', + subtitle: 'Configure access level and duration', + uniqueLink: 'Unique Invite Link', + regenerate: 'Regenerate', + userIdentifier: 'User Identifier', + userPlaceholder: 'Username or email address', + permissions: 'Permissions Level', + send: 'Send Invitation', + permissionsList: { + fullAccess: { + title: 'Full Server Access', + desc: 'Can modify settings, channels & roles' + }, + billing: { + title: 'Billing Access', + desc: 'Manage subscription and payments' + }, + memberManagement: { + title: 'Member Management', + desc: 'Kick, ban, and assign lower roles' + } + } + }, + assistant: { + title: 'Invite as Assistant', + subtitle: 'Select an AI model to join the workspace', + instances: 'Number of Instances', + unlimitedAccess: 'Unlimited Access', + unlimitedAccessDesc: 'Bypass usage limits', + sandboxed: 'Sandboxed environment', + send: 'Send Invitation', + models: { + gemini: 'Gemini CLI', + codex: 'Codex', + claude: 'Claude Code', + custom: 'Custom CLI' + } + }, + member: { + title: 'Invite as Member' + } + }, + roadmap: { + title: 'Project Roadmap', + objectiveLabel: 'Objective:', + taskPlaceholder: 'Enter task title...', + newTask: 'New Task', + status: { + done: 'DONE', + inProgress: 'IN PROGRESS', + pending: 'PENDING' + }, + actions: { + edit: 'Edit Task', + changeOrder: 'Change Order', + markPriority: 'Mark as Priority', + delete: 'Delete' + }, + footer: '{count} Tasks • {percent}% Complete', + addTask: 'Add Task' + }, + skills: { + management: { + title: 'Skill Management', + subtitle: 'Configure active skills for {channel}', + tabs: { + current: 'Current Skills', + library: 'My Skills Library' + } + }, + current: { + activeFolders: 'Active Folders', + syncAll: 'Sync All', + updated: 'Updated 2h ago', + active: 'Active' + }, + library: { + searchPlaceholder: 'Search your library...', + importTitle: 'Import Skill', + importSubtitle: 'From URL or Local File', + browseShop: 'Browse Skill Shop' + }, + footer: { + documentation: 'Documentation', + privacy: 'Privacy', + newFolder: 'New Folder', + lastSynced: 'Last synced: 2 mins ago' + }, + tags: { + typography: 'Typography', + colorPalette: 'Color Palette', + components: 'Components' + }, + items: { + designSystemCore: { + name: 'Design System Core' + }, + uxResearchPatterns: { + name: 'UX Research Patterns' + }, + a11yGuidelines: { + name: 'A11y Guidelines' + }, + frontendToolkit: { + name: 'Frontend Toolkit', + desc: 'Essential snippets for React, Vue, and Tailwind CSS development.' + }, + iconAssetPack: { + name: 'Icon Asset Pack', + desc: 'Premium outline and solid icons for modern interface design.' + }, + motionPresets: { + name: 'Motion Presets', + desc: 'Standardized animation curves and transition timings.' + }, + shellCommands: { + name: 'Shell Commands', + desc: 'Quick access to common CLI scripts and deployment hooks.' + }, + brandColors: { + name: 'Brand Colors', + desc: 'Company color palettes and accessible contrast ratios.' + } + }, + assets: { + frontendToolkit: '45 assets', + iconAssetPack: '1.2k icons', + motionPresets: '12 presets', + shellCommands: '24 cmds', + brandColors: '8 swatches' + }, + detail: { + sourceConfig: 'Source Configuration', + source: { + github: 'GitHub Repo', + command: 'Command Source', + local: 'Local Path' + }, + repoLabel: 'Repository URL', + repoPlaceholder: 'https://github.com/username/repository.git', + repoHint: 'Supports HTTPS and SSH URLs from GitHub, GitLab, and Bitbucket.', + syncPreferences: 'Sync Preferences', + autoSync: 'Auto-sync Updates', + autoSyncDesc: 'Automatically pull latest changes from source', + updateFrequency: 'Update Frequency', + updateFrequencyDesc: 'How often to check for new versions', + frequency: { + every15: 'Every 15 minutes', + hour: 'Every hour', + daily: 'Daily', + manual: 'Manual only' + }, + targetBranch: 'Target Branch', + targetBranchDesc: 'Branch to track for updates', + deleteSkill: 'Delete Skill', + cancel: 'Cancel', + saveChanges: 'Save Changes' + } + }, + marketplace: { + title: 'Plugin Marketplace', + searchPlaceholder: 'Search plugins, integrations, and themes...', + browseStore: 'Browse Store', + myPlugins: 'My Plugins', + categories: { + all: 'All Plugins', + productivity: 'Productivity', + development: 'Development', + design: 'Design', + communication: 'Communication', + music: 'Music' + }, + install: 'Install', + installed: 'Installed', + plugins: { + github: { + title: 'GitHub Integration', + desc: 'Connect your repositories, track issues, and manage pull requests directly.' + }, + spotify: { + title: 'Spotify Player', + desc: 'Listen together. Control playback and share your favorite tracks.' + }, + taskManager: { + title: 'Task Manager', + desc: 'A simple Kanban board for your team. Create, assign, and complete tasks.' + }, + calendar: { + title: 'Calendar Sync', + desc: 'Never miss a meeting. Sync with Google Calendar and Outlook.' + }, + aiAssistant: { + title: 'AI Assistant', + desc: 'Your personal AI companion. Ask questions, generate text, and summarize.' + }, + terminal: { + title: 'Terminal', + desc: 'Run commands and scripts directly from the chat. For power users only.' + }, + figma: { + title: 'Figma Preview', + desc: 'Embed live Figma files and prototypes. Get feedback instantly.' + }, + quickNotes: { + title: 'Quick Notes', + desc: 'Jot down ideas and share them with your team. Supports markdown.' + } + } + }, + skillStore: { + title: 'Skill Store', + searchPlaceholder: 'Search skill folders, templates, and toolkits...', + tabs: { + store: 'Store', + installed: 'Installed' + }, + filters: { + all: 'All Skills', + engineering: 'Engineering', + design: 'Design', + management: 'Management', + marketing: 'Marketing', + finance: 'Finance' + }, + syncPlaceholder: 'Paste sync URL...', + syncNow: 'Sync now', + installFolder: 'Install Folder', + installed: 'Installed', + skills: { + automation: { + title: 'Automation Skill', + desc: 'Streamline workflows with pre-built scripts. Auto-syncs with your repo.' + }, + uiToolkit: { + title: 'UI Design Toolkit', + desc: 'Centralized design assets and brand guidelines. Syncs with Figma files.' + }, + projectTracking: { + title: 'Project Tracking', + desc: 'Task lists and kanban boards for active sprints. Syncs with Jira or Trello.' + }, + marketingAssets: { + title: 'Marketing Assets', + desc: 'Campaign materials and social media templates. Syncs with Drive/Dropbox.' + }, + devOpsConfig: { + title: 'Dev Ops Config', + desc: 'Shared environment variables and docker configs. Syncs secure vaults.' + }, + researchLibrary: { + title: 'Research Library', + desc: 'Competitor analysis and market trends. Syncs with Notion or Evernote pages.' + } + } + }, + workspace: { + createTitle: 'Create New Team', + createSubtitle: 'Initialize a fresh collaborative environment', + recentTitle: 'Recent Workspaces', + more: 'More', + searchPlaceholder: 'Search teams...', + menu: { + settings: 'Workspace Settings', + invite: 'Invite Members', + copyId: 'Copy ID', + leave: 'Leave Workspace' + }, + activeLabel: 'ACTIVE', + times: { + twoMinutes: '2m ago', + fourHours: '4h ago', + oneDay: '1d ago' + }, + list: { + frontend: { + title: 'Frontend Squad', + desc: 'Main monorepo for the consumer-facing React application.' + }, + mobile: { + title: 'Mobile API', + desc: 'GraphQL gateway and microservices for iOS/Android apps.' + }, + infrastructure: { + title: 'Infrastructure', + desc: 'Terraform scripts and Kubernetes configuration files.' + } + }, + recent: { + designSystems: 'Design Systems', + marketingTeam: 'Marketing Team', + backendCore: 'Backend Core' + } + }, + settings: { + title: 'Settings', + preferences: 'Preferences', + preferencesSubtitle: 'Customize your account, notifications, and workspace preferences.', + accountSubtitle: 'Manage your profile, presence, and contact details.', + displayName: 'Display Name', + displayNamePlaceholder: 'Enter your display name', + emailAddress: 'Email Address', + emailPlaceholder: 'name@example.com', + jobTitle: 'Job Title', + jobTitlePlaceholder: 'e.g. Product Designer', + timeZone: 'Time Zone', + status: 'Status', + statusMessage: 'Status Message', + statusMessagePlaceholder: 'Share what you are working on', + statusOptions: { + online: 'Online', + away: 'Away', + dnd: 'Do Not Disturb' + }, + language: 'Language', + languageDefault: 'Default', + changesApply: 'Changes apply after restart.', + spellCheck: 'Spell Check', + defaultMember: 'Default Member', + selectMember: 'Select Member', + refreshList: 'Refresh List', + cancel: 'Cancel', + saveChanges: 'Save Changes', + userSettings: 'User Settings', + myAccount: 'My Account', + appSettings: 'App Settings', + appearance: 'Appearance', + appearanceSubtitle: 'Switch themes and match the workspace look to your environment.', + members: 'Members', + notifications: 'Notifications', + keybinds: 'Keybinds', + leaveTeam: 'Leave Team', + logOut: 'Log Out', + memberName: 'Member Name', + memberNamePlaceholder: 'Enter member label', + commandInput: 'Command Line Input', + commandPlaceholder: 'e.g. /usr/local/bin/my-cli', + confirm: 'Confirm', + notificationsSubtitle: 'Choose when and how you want to be notified.', + notificationOptions: { + desktop: 'Desktop Notifications', + desktopDesc: 'Show system notifications for new messages.', + sound: 'Sound Alerts', + soundDesc: 'Play a sound when new messages arrive.', + mentionsOnly: 'Mentions Only', + mentionsOnlyDesc: 'Only notify when you are mentioned.', + previews: 'Message Previews', + previewsDesc: 'Display message content in alerts.', + weeklyDigest: 'Weekly Digest', + weeklyDigestDesc: 'Receive a weekly summary of activity.', + quietHours: 'Quiet Hours', + quietHoursDesc: 'Silence alerts during scheduled hours.' + }, + quietHoursFrom: 'From', + quietHoursTo: 'To', + keybindsSubtitle: 'Configure shortcuts and keybinding profiles.', + keybindsProfile: 'Keybinding Profile', + keybindsEnable: 'Enable Keyboard Shortcuts', + keybindsHints: 'Show Shortcut Hints', + keybindsReset: 'Reset to Defaults', + keybindsListTitle: 'Shortcut Reference', + keybindProfiles: { + default: 'Default', + vscode: 'VS Code', + slack: 'Slack' + }, + keybindActions: { + focusSearch: 'Focus search', + newMessage: 'New message', + toggleSidebar: 'Toggle sidebar', + toggleMute: 'Toggle mute', + jumpToLatest: 'Jump to latest', + openSettings: 'Open settings' + }, + memberOptions: { + gemini: 'Gemini CLI', + codex: 'Codex', + claude: 'Claude Code', + custom: 'Custom CLI' + }, + memberKind: { + default: 'Default Terminal', + custom: 'Custom Terminal' + }, + memberActions: { + menuLabel: 'Terminal actions', + test: 'Test Terminal', + edit: 'Edit', + remove: 'Delete' + }, + themeOptions: { + dark: { + label: 'Dark', + desc: 'Default theme designed for low-light focus.' + }, + light: { + label: 'Light', + desc: 'Bright layout optimized for daylight work.' + }, + system: { + label: 'System', + desc: 'Follows your operating system appearance.' + } + } + }, + language: { + enUS: 'English (United States)', + zhCN: 'Chinese (Simplified)' + } +}; diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts new file mode 100644 index 0000000..e9ff2b4 --- /dev/null +++ b/src/i18n/locales/zh-CN.ts @@ -0,0 +1,535 @@ +export default { + app: { + name: 'Nexus Dashboard' + }, + nav: { + chat: '聊天与弹窗', + workspaces: '工作区', + store: '技能商店', + plugins: '插件', + settings: '设置' + }, + common: { + userAvatarAlt: '用户', + remove: '删除' + }, + chat: { + channelName: 'design-critique', + channelDisplay: '#design-critique', + channelDescription: '', + directMessageDescription: '与 {name} 的私信', + header: { + todo: '待办', + inventory: '技能库' + }, + input: { + placeholder: '发送到 {channel}', + directPlaceholder: '发送给 @{name}', + send: '发送', + stop: '停止生成', + hint: 'Enter 发送 • Shift+Enter 换行', + quickPrompts: { + summarize: '总结最新讨论', + draftReply: '生成礼貌回复', + extractTasks: '提取行动项' + } + }, + sidebar: { + workspaceName: '前端小队', + channels: '频道', + directMessages: '私信', + channelList: { + announcements: 'announcements', + generalChat: 'general-chat', + designCritique: 'design-critique', + resources: 'resources' + } + }, + messages: { + dateSeparator: '2023年10月24日', + roadmapHint: '点击查看路线图', + userJoined: '{name} 加入了服务器', + joinedUser: 'James', + sampleMessage: { + user: 'Sarah Jenkins', + time: '11:05 AM', + text: '希望周五前完成!只差产品的最终确认。' + }, + autoReply: '我已处理你的请求。还有其他需要吗?', + autoReplyQuestion: '我正在查看这个问题,稍后给你更新。', + jumpToLatest: '跳到最新消息', + typing: '{name} 正在输入...', + status: { + sending: '发送中...', + failed: '发送失败' + } + }, + conversation: { + actions: { + pin: '置顶', + unpin: '取消置顶', + rename: '修改群聊名称', + mute: '消息免打扰', + unmute: '取消免打扰', + clear: '清空聊天记录', + deleteChannel: '删除群聊', + deleteDirect: '删除对话' + }, + renameTitle: '修改群聊名称', + renameLabel: '群聊名称', + renamePlaceholder: '输入群聊名称' + } + }, + members: { + title: '成员', + sections: { + owner: '群主 — {count}', + admin: '管理员 — {count}', + assistant: '助手 — {count}', + member: '普通成员 — {count}' + }, + roles: { + owner: '群主', + admin: '管理员', + assistant: '助手', + member: '成员', + aiAssistant: 'AI 助手' + }, + actions: { + sendMessage: '发送消息', + mention: '@', + rename: '更改名称' + }, + manage: { + title: '管理成员', + displayName: '显示名称', + remove: '移出群组' + }, + activity: { + reviewingPRs: '审核 PR', + listeningSpotify: '在听 Spotify', + fixingBugs: '修复 Bug', + doNotDisturb: '请勿打扰' + } + }, + invite: { + menu: { + title: '邀请加入服务器', + subtitle: '生成唯一邀请链接', + admin: '以管理员身份邀请', + adminDesc: '完全服务器权限', + assistant: '以助手身份邀请', + assistantDesc: '管理权限', + member: '普通成员', + memberDesc: '标准访问权限' + }, + admin: { + title: '以管理员身份邀请', + subtitle: '配置访问级别与时长', + uniqueLink: '唯一邀请链接', + regenerate: '重新生成', + userIdentifier: '用户标识', + userPlaceholder: '用户名或邮箱地址', + permissions: '权限等级', + send: '发送邀请', + permissionsList: { + fullAccess: { + title: '完全服务器权限', + desc: '可修改设置、频道与角色' + }, + billing: { + title: '账单权限', + desc: '管理订阅与付款' + }, + memberManagement: { + title: '成员管理', + desc: '踢出、封禁并分配低级角色' + } + } + }, + assistant: { + title: '以助手身份邀请', + subtitle: '选择要加入工作区的 AI 模型', + instances: '实例数量', + unlimitedAccess: '无限访问', + unlimitedAccessDesc: '绕过使用限制', + sandboxed: '沙盒环境', + send: '发送邀请', + models: { + gemini: 'Gemini CLI', + codex: 'Codex', + claude: 'Claude Code', + custom: '自定义 CLI' + } + }, + member: { + title: '以成员身份邀请' + } + }, + roadmap: { + title: '项目路线图', + objectiveLabel: '目标:', + taskPlaceholder: '输入任务标题...', + newTask: '新任务', + status: { + done: '已完成', + inProgress: '进行中', + pending: '待处理' + }, + actions: { + edit: '编辑任务', + changeOrder: '调整顺序', + markPriority: '标记为优先', + delete: '删除' + }, + footer: '{count} 个任务 • 完成 {percent}%', + addTask: '添加任务' + }, + skills: { + management: { + title: '技能管理', + subtitle: '为 {channel} 配置已启用技能', + tabs: { + current: '当前技能', + library: '我的技能库' + } + }, + current: { + activeFolders: '已启用的文件夹', + syncAll: '全部同步', + updated: '2 小时前更新', + active: '启用中' + }, + library: { + searchPlaceholder: '搜索你的技能库...', + importTitle: '导入技能', + importSubtitle: '从 URL 或本地文件', + browseShop: '浏览技能商店' + }, + footer: { + documentation: '文档', + privacy: '隐私', + newFolder: '新建文件夹', + lastSynced: '上次同步:2 分钟前' + }, + tags: { + typography: '排版', + colorPalette: '色板', + components: '组件' + }, + items: { + designSystemCore: { + name: '设计系统核心' + }, + uxResearchPatterns: { + name: 'UX 研究模式' + }, + a11yGuidelines: { + name: '无障碍指南' + }, + frontendToolkit: { + name: '前端工具包', + desc: '适用于 React、Vue 和 Tailwind CSS 的常用片段。' + }, + iconAssetPack: { + name: '图标资源包', + desc: '现代界面设计的高品质线框与实心图标。' + }, + motionPresets: { + name: '动效预设', + desc: '标准化的动画曲线与过渡时序。' + }, + shellCommands: { + name: 'Shell 命令集', + desc: '快速访问常用 CLI 脚本和部署钩子。' + }, + brandColors: { + name: '品牌色板', + desc: '公司色板与可访问性对比度规范。' + } + }, + assets: { + frontendToolkit: '45 个资源', + iconAssetPack: '1.2k 图标', + motionPresets: '12 个预设', + shellCommands: '24 条命令', + brandColors: '8 个色板' + }, + detail: { + sourceConfig: '来源配置', + source: { + github: 'GitHub 仓库', + command: '命令来源', + local: '本地路径' + }, + repoLabel: '仓库地址', + repoPlaceholder: 'https://github.com/username/repository.git', + repoHint: '支持 GitHub、GitLab 与 Bitbucket 的 HTTPS 和 SSH 地址。', + syncPreferences: '同步偏好', + autoSync: '自动同步更新', + autoSyncDesc: '自动拉取来源的最新变更', + updateFrequency: '更新频率', + updateFrequencyDesc: '多久检查一次新版本', + frequency: { + every15: '每 15 分钟', + hour: '每小时', + daily: '每日', + manual: '仅手动' + }, + targetBranch: '目标分支', + targetBranchDesc: '用于更新跟踪的分支', + deleteSkill: '删除技能', + cancel: '取消', + saveChanges: '保存修改' + } + }, + marketplace: { + title: '插件市场', + searchPlaceholder: '搜索插件、集成和主题...', + browseStore: '浏览商店', + myPlugins: '我的插件', + categories: { + all: '全部插件', + productivity: '效率', + development: '开发', + design: '设计', + communication: '沟通', + music: '音乐' + }, + install: '安装', + installed: '已安装', + plugins: { + github: { + title: 'GitHub 集成', + desc: '连接你的仓库,跟踪问题并直接管理 PR。' + }, + spotify: { + title: 'Spotify 播放器', + desc: '一起听歌。控制播放并分享喜欢的曲目。' + }, + taskManager: { + title: '任务管理', + desc: '适合团队的简洁看板。创建、分配并完成任务。' + }, + calendar: { + title: '日历同步', + desc: '不错过会议。同步 Google 日历与 Outlook。' + }, + aiAssistant: { + title: 'AI 助手', + desc: '你的 AI 伙伴。提问、生成文本并做摘要。' + }, + terminal: { + title: '终端', + desc: '从聊天直接运行命令和脚本。适合高阶用户。' + }, + figma: { + title: 'Figma 预览', + desc: '嵌入实时 Figma 文件与原型,即时反馈。' + }, + quickNotes: { + title: '快速笔记', + desc: '记录想法并与团队分享。支持 Markdown。' + } + } + }, + skillStore: { + title: '技能商店', + searchPlaceholder: '搜索技能文件夹、模板和工具包...', + tabs: { + store: '商店', + installed: '已安装' + }, + filters: { + all: '全部技能', + engineering: '工程', + design: '设计', + management: '管理', + marketing: '营销', + finance: '财务' + }, + syncPlaceholder: '粘贴同步 URL...', + syncNow: '立即同步', + installFolder: '安装文件夹', + installed: '已安装', + skills: { + automation: { + title: '自动化技能', + desc: '通过预置脚本简化流程,可与仓库自动同步。' + }, + uiToolkit: { + title: 'UI 设计工具包', + desc: '集中式设计资产与品牌规范,同步 Figma 文件。' + }, + projectTracking: { + title: '项目跟踪', + desc: '用于冲刺的任务清单与看板,可同步 Jira 或 Trello。' + }, + marketingAssets: { + title: '营销素材', + desc: '活动物料与社媒模板,同步 Drive/Dropbox。' + }, + devOpsConfig: { + title: 'Dev Ops 配置', + desc: '共享环境变量与 Docker 配置,同步安全密钥库。' + }, + researchLibrary: { + title: '研究资料库', + desc: '竞品分析与市场趋势,Notion/Evernote 同步。' + } + } + }, + workspace: { + createTitle: '创建新团队', + createSubtitle: '初始化全新的协作环境', + recentTitle: '最近的工作区', + more: '更多', + searchPlaceholder: '搜索团队...', + menu: { + settings: '工作区设置', + invite: '邀请成员', + copyId: '复制 ID', + leave: '退出工作区' + }, + activeLabel: '活跃', + times: { + twoMinutes: '2 分钟前', + fourHours: '4 小时前', + oneDay: '1 天前' + }, + list: { + frontend: { + title: '前端小队', + desc: '面向用户的 React 应用主仓库。' + }, + mobile: { + title: '移动端 API', + desc: '面向 iOS/Android 的 GraphQL 网关与微服务。' + }, + infrastructure: { + title: '基础设施', + desc: 'Terraform 脚本与 Kubernetes 配置文件。' + } + }, + recent: { + designSystems: '设计系统', + marketingTeam: '市场团队', + backendCore: '后端核心' + } + }, + settings: { + title: '设置', + preferences: '偏好设置', + preferencesSubtitle: '自定义账号、通知与工作区偏好。', + accountSubtitle: '管理你的资料、状态与联系方式。', + displayName: '显示名称', + displayNamePlaceholder: '输入显示名称', + emailAddress: '邮箱地址', + emailPlaceholder: 'name@example.com', + jobTitle: '职位', + jobTitlePlaceholder: '例如 产品设计师', + timeZone: '时区', + status: '状态', + statusMessage: '状态信息', + statusMessagePlaceholder: '分享你正在做的事情', + statusOptions: { + online: '在线', + away: '离开', + dnd: '请勿打扰' + }, + language: '语言', + languageDefault: '默认', + changesApply: '重启后生效。', + spellCheck: '拼写检查', + defaultMember: '默认成员', + selectMember: '选择成员', + refreshList: '刷新列表', + cancel: '取消', + saveChanges: '保存修改', + userSettings: '用户设置', + myAccount: '我的账号', + appSettings: '应用设置', + appearance: '外观', + appearanceSubtitle: '切换主题并匹配你的工作环境。', + members: '成员', + notifications: '通知', + keybinds: '快捷键', + leaveTeam: '退出团队', + logOut: '退出账号', + memberName: '成员名称', + memberNamePlaceholder: '输入成员标签', + commandInput: '命令行输入', + commandPlaceholder: '例如 /usr/local/bin/my-cli', + confirm: '确认', + notificationsSubtitle: '选择你接收通知的方式与时机。', + notificationOptions: { + desktop: '桌面通知', + desktopDesc: '新消息时显示系统通知。', + sound: '声音提醒', + soundDesc: '新消息到达时播放提示音。', + mentionsOnly: '仅提醒提及', + mentionsOnlyDesc: '只有被提及时才提醒。', + previews: '消息预览', + previewsDesc: '在通知中显示消息内容。', + weeklyDigest: '每周摘要', + weeklyDigestDesc: '每周发送一次活动摘要。', + quietHours: '静默时段', + quietHoursDesc: '在设定时间段内关闭提醒。' + }, + quietHoursFrom: '开始时间', + quietHoursTo: '结束时间', + keybindsSubtitle: '配置快捷键与键位方案。', + keybindsProfile: '键位方案', + keybindsEnable: '启用快捷键', + keybindsHints: '显示快捷键提示', + keybindsReset: '恢复默认', + keybindsListTitle: '快捷键参考', + keybindProfiles: { + default: '默认', + vscode: 'VS Code', + slack: 'Slack' + }, + keybindActions: { + focusSearch: '聚焦搜索', + newMessage: '新建消息', + toggleSidebar: '切换侧边栏', + toggleMute: '切换静音', + jumpToLatest: '跳到最新消息', + openSettings: '打开设置' + }, + memberOptions: { + gemini: 'Gemini CLI', + codex: 'Codex', + claude: 'Claude Code', + custom: '自定义 CLI' + }, + memberKind: { + default: '默认终端', + custom: '自定义终端' + }, + memberActions: { + menuLabel: '终端操作', + test: '测试终端', + edit: '更改', + remove: '删除' + }, + themeOptions: { + dark: { + label: '深色', + desc: '默认深色主题,更适合低光环境。' + }, + light: { + label: '浅色', + desc: '明亮布局,适合白天或高亮环境。' + }, + system: { + label: '系统', + desc: '跟随操作系统外观设置。' + } + } + }, + language: { + enUS: '英文(美国)', + zhCN: '中文(简体)' + } +}; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..a0a8e46 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,9 @@ +import { createApp } from 'vue'; +import App from './app/App.vue'; +import { i18n } from './i18n'; +import { initializeTheme } from './shared/composables/useTheme'; +import './styles/global.css'; + +initializeTheme(); + +createApp(App).use(i18n).mount('#app'); diff --git a/src/shared/components/SidebarNav.vue b/src/shared/components/SidebarNav.vue new file mode 100644 index 0000000..40da126 --- /dev/null +++ b/src/shared/components/SidebarNav.vue @@ -0,0 +1,103 @@ + + + diff --git a/src/shared/composables/useLocale.ts b/src/shared/composables/useLocale.ts new file mode 100644 index 0000000..b4dd5fc --- /dev/null +++ b/src/shared/composables/useLocale.ts @@ -0,0 +1,31 @@ +import { computed } from 'vue'; +import { i18n, LOCALE_STORAGE_KEY, setDocumentLang, type AppLocale } from '@/i18n'; + +export type LocaleOption = { + id: AppLocale; + labelKey: string; + flag: string; +}; + +export const localeOptions: LocaleOption[] = [ + { id: 'en-US', labelKey: 'language.enUS', flag: '🇺🇸' }, + { id: 'zh-CN', labelKey: 'language.zhCN', flag: '🇨🇳' } +]; + +export const useLocale = () => { + const locale = computed(() => i18n.global.locale.value as AppLocale); + + const setLocale = (next: AppLocale) => { + i18n.global.locale.value = next; + if (typeof window !== 'undefined') { + window.localStorage.setItem(LOCALE_STORAGE_KEY, next); + } + setDocumentLang(next); + }; + + return { + locale, + setLocale, + localeOptions + }; +}; diff --git a/src/shared/composables/useSettings.ts b/src/shared/composables/useSettings.ts new file mode 100644 index 0000000..5291b8d --- /dev/null +++ b/src/shared/composables/useSettings.ts @@ -0,0 +1,248 @@ +import { computed, ref } from 'vue'; + +export type AccountStatus = 'online' | 'away' | 'dnd'; + +export type AccountSettings = { + displayName: string; + email: string; + title: string; + timezone: string; + status: AccountStatus; + statusMessage: string; +}; + +export type NotificationSettings = { + desktop: boolean; + sound: boolean; + mentionsOnly: boolean; + previews: boolean; + weeklyDigest: boolean; + quietHoursEnabled: boolean; + quietHoursStart: string; + quietHoursEnd: string; +}; + +export type KeybindProfile = 'default' | 'vscode' | 'slack'; + +export type KeybindSettings = { + enabled: boolean; + showHints: boolean; + profile: KeybindProfile; +}; + +export type CustomMember = { + id: string; + name: string; + command: string; +}; + +export type MemberSettings = { + defaultMemberId: string; + customMembers: CustomMember[]; +}; + +export type LanguageSettings = { + spellCheck: boolean; +}; + +export type SettingsState = { + account: AccountSettings; + notifications: NotificationSettings; + keybinds: KeybindSettings; + members: MemberSettings; + language: LanguageSettings; +}; + +const SETTINGS_STORAGE_KEY = 'nexus-settings'; + +const DEFAULT_SETTINGS: SettingsState = { + account: { + displayName: '', + email: '', + title: '', + timezone: 'UTC', + status: 'online', + statusMessage: '' + }, + notifications: { + desktop: true, + sound: false, + mentionsOnly: false, + previews: true, + weeklyDigest: true, + quietHoursEnabled: false, + quietHoursStart: '22:00', + quietHoursEnd: '07:00' + }, + keybinds: { + enabled: true, + showHints: true, + profile: 'default' + }, + members: { + defaultMemberId: 'gemini-cli', + customMembers: [] + }, + language: { + spellCheck: true + } +}; + +const BASE_MEMBER_IDS = ['gemini-cli', 'codex', 'claude-code']; +const ALLOWED_MEMBER_IDS = new Set(BASE_MEMBER_IDS); +const ALLOWED_STATUSES = new Set(['online', 'away', 'dnd']); +const ALLOWED_KEYBINDS = new Set(['default', 'vscode', 'slack']); +const CUSTOM_MEMBER_PREFIX = 'custom-cli-'; + +const isTimeValue = (value: string) => /^\d{2}:\d{2}$/.test(value); +const safeString = (value: unknown, fallback = '') => (typeof value === 'string' ? value : fallback); + +export const cloneSettings = (value: SettingsState): SettingsState => JSON.parse(JSON.stringify(value)) as SettingsState; + +const normalizeCustomMember = (candidate: Partial, fallbackId: string): CustomMember | null => { + const id = safeString(candidate.id).trim() || fallbackId; + const name = safeString(candidate.name).trim(); + const command = safeString(candidate.command).trim(); + if (!id || (!name && !command)) { + return null; + } + return { id, name, command }; +}; + +const buildCustomMembers = (candidate: SettingsState): CustomMember[] => { + const rawList = Array.isArray(candidate.members.customMembers) ? candidate.members.customMembers : []; + const normalized: CustomMember[] = []; + + rawList.forEach((member, index) => { + const next = normalizeCustomMember(member, `${CUSTOM_MEMBER_PREFIX}${index + 1}`); + if (next) { + normalized.push(next); + } + }); + + if ((candidate.members as { customMember?: Partial }).customMember) { + const legacy = normalizeCustomMember( + (candidate.members as { customMember?: Partial }).customMember ?? {}, + `${CUSTOM_MEMBER_PREFIX}${normalized.length + 1}` + ); + if (legacy) { + normalized.push(legacy); + } + } + + const seen = new Set(); + return normalized.filter((member) => { + if (seen.has(member.id)) { + return false; + } + seen.add(member.id); + return true; + }); +}; + +const normalizeSettings = (candidate: SettingsState): SettingsState => { + const account = { + displayName: safeString(candidate.account.displayName).trim(), + email: safeString(candidate.account.email).trim().toLowerCase(), + title: safeString(candidate.account.title).trim(), + timezone: safeString(candidate.account.timezone).trim() || DEFAULT_SETTINGS.account.timezone, + status: ALLOWED_STATUSES.has(candidate.account.status) ? candidate.account.status : DEFAULT_SETTINGS.account.status, + statusMessage: safeString(candidate.account.statusMessage).trim() + }; + + const notifications = { + desktop: Boolean(candidate.notifications.desktop), + sound: Boolean(candidate.notifications.sound), + mentionsOnly: Boolean(candidate.notifications.mentionsOnly), + previews: Boolean(candidate.notifications.previews), + weeklyDigest: Boolean(candidate.notifications.weeklyDigest), + quietHoursEnabled: Boolean(candidate.notifications.quietHoursEnabled), + quietHoursStart: isTimeValue(safeString(candidate.notifications.quietHoursStart)) + ? safeString(candidate.notifications.quietHoursStart) + : DEFAULT_SETTINGS.notifications.quietHoursStart, + quietHoursEnd: isTimeValue(safeString(candidate.notifications.quietHoursEnd)) + ? safeString(candidate.notifications.quietHoursEnd) + : DEFAULT_SETTINGS.notifications.quietHoursEnd + }; + + const keybinds = { + enabled: Boolean(candidate.keybinds.enabled), + showHints: Boolean(candidate.keybinds.showHints), + profile: ALLOWED_KEYBINDS.has(candidate.keybinds.profile) ? candidate.keybinds.profile : DEFAULT_SETTINGS.keybinds.profile + }; + + const customMembers = buildCustomMembers(candidate); + const allowedMemberIds = new Set([...ALLOWED_MEMBER_IDS, ...customMembers.map((member) => member.id)]); + const requestedMemberId = + candidate.members.defaultMemberId === 'custom-cli' && customMembers.length > 0 ? customMembers[0].id : candidate.members.defaultMemberId; + const defaultMemberId = allowedMemberIds.has(requestedMemberId) ? requestedMemberId : DEFAULT_SETTINGS.members.defaultMemberId; + + return { + account, + notifications, + keybinds, + members: { + defaultMemberId, + customMembers + }, + language: { + spellCheck: Boolean(candidate.language.spellCheck) + } + }; +}; + +const buildSettings = (candidate?: Partial): SettingsState => { + const merged: SettingsState = { + account: { ...DEFAULT_SETTINGS.account, ...candidate?.account }, + notifications: { ...DEFAULT_SETTINGS.notifications, ...candidate?.notifications }, + keybinds: { ...DEFAULT_SETTINGS.keybinds, ...candidate?.keybinds }, + members: { ...DEFAULT_SETTINGS.members, ...candidate?.members }, + language: { ...DEFAULT_SETTINGS.language, ...candidate?.language } + }; + return normalizeSettings(merged); +}; + +const loadSettings = (): SettingsState => { + if (typeof window === 'undefined') { + return cloneSettings(DEFAULT_SETTINGS); + } + const raw = window.localStorage.getItem(SETTINGS_STORAGE_KEY); + if (!raw) { + return cloneSettings(DEFAULT_SETTINGS); + } + try { + const parsed = JSON.parse(raw) as Partial; + return buildSettings(parsed); + } catch { + return cloneSettings(DEFAULT_SETTINGS); + } +}; + +const settingsRef = ref(loadSettings()); + +const persistSettings = (next: SettingsState) => { + if (typeof window === 'undefined') return; + window.localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(next)); +}; + +export const useSettings = () => { + const saveSettings = (next: SettingsState) => { + const normalized = normalizeSettings(next); + settingsRef.value = normalized; + persistSettings(normalized); + return normalized; + }; + + const resetSettings = () => { + const next = cloneSettings(DEFAULT_SETTINGS); + settingsRef.value = next; + persistSettings(next); + return next; + }; + + return { + settings: computed(() => settingsRef.value), + saveSettings, + resetSettings + }; +}; diff --git a/src/shared/composables/useTheme.ts b/src/shared/composables/useTheme.ts new file mode 100644 index 0000000..dbc908f --- /dev/null +++ b/src/shared/composables/useTheme.ts @@ -0,0 +1,101 @@ +import { computed, ref } from 'vue'; + +export type AppTheme = 'dark' | 'light' | 'system'; + +export const THEME_STORAGE_KEY = 'nexus-theme'; + +export type ThemeOption = { + id: AppTheme; + labelKey: string; + descriptionKey: string; +}; + +export const themeOptions: ThemeOption[] = [ + { + id: 'dark', + labelKey: 'settings.themeOptions.dark.label', + descriptionKey: 'settings.themeOptions.dark.desc' + }, + { + id: 'light', + labelKey: 'settings.themeOptions.light.label', + descriptionKey: 'settings.themeOptions.light.desc' + }, + { + id: 'system', + labelKey: 'settings.themeOptions.system.label', + descriptionKey: 'settings.themeOptions.system.desc' + } +]; + +const getStoredTheme = (): AppTheme => { + if (typeof window === 'undefined') return 'dark'; + const stored = window.localStorage.getItem(THEME_STORAGE_KEY); + if (stored === 'dark' || stored === 'light' || stored === 'system') { + return stored; + } + return 'dark'; +}; + +const theme = ref(getStoredTheme()); + +const getSystemTheme = (): 'dark' | 'light' => { + if (typeof window === 'undefined' || !window.matchMedia) { + return 'dark'; + } + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +}; + +const applyTheme = (value: AppTheme) => { + if (typeof document === 'undefined') return; + const root = document.documentElement; + root.dataset.theme = value; + const resolved = value === 'system' ? getSystemTheme() : value; + root.dataset.resolvedTheme = resolved; + root.classList.toggle('dark', resolved === 'dark'); +}; + +let mediaQuery: MediaQueryList | null = null; +let mediaHandler: ((event: MediaQueryListEvent) => void) | null = null; + +const startSystemListener = () => { + if (typeof window === 'undefined' || !window.matchMedia || mediaQuery) return; + mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + mediaHandler = () => applyTheme('system'); + mediaQuery.addEventListener('change', mediaHandler); +}; + +const stopSystemListener = () => { + if (!mediaQuery || !mediaHandler) return; + mediaQuery.removeEventListener('change', mediaHandler); + mediaQuery = null; + mediaHandler = null; +}; + +export const initializeTheme = () => { + applyTheme(theme.value); + if (theme.value === 'system') { + startSystemListener(); + } +}; + +export const useTheme = () => { + const setTheme = (next: AppTheme) => { + theme.value = next; + if (typeof window !== 'undefined') { + window.localStorage.setItem(THEME_STORAGE_KEY, next); + } + applyTheme(next); + if (next === 'system') { + startSystemListener(); + } else { + stopSystemListener(); + } + }; + + return { + theme: computed(() => theme.value), + setTheme, + themeOptions + }; +}; diff --git a/src/shared/constants/avatars.ts b/src/shared/constants/avatars.ts new file mode 100644 index 0000000..30ad486 --- /dev/null +++ b/src/shared/constants/avatars.ts @@ -0,0 +1 @@ +export const PRIMARY_USER_AVATAR_URL = 'https://picsum.photos/id/64/200/200'; diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..f6ecbd9 --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,228 @@ +.custom-scrollbar::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: transparent; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background: rgb(var(--scrollbar-thumb)); + border-radius: 3px; +} + +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: rgb(var(--scrollbar-thumb-hover)); +} + +.custom-scrollbar { + scrollbar-width: thin; + scrollbar-color: rgb(var(--scrollbar-thumb)) transparent; +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} +:root { + --color-background: 15 15 18; + --color-surface: 24 24 27; + --color-panel: 18 18 21; + --color-panel-strong: 20 20 23; + --color-panel-soft: 30 30 36; + --color-text: 248 250 252; + --color-text-muted: 148 163 184; + --color-border: 255 255 255; + --color-overlay: 255 255 255; + --color-primary: 56 189 248; + --color-primary-hover: 14 165 233; + --color-secondary: 100 116 139; + --scrollbar-thumb: 51 65 85; + --scrollbar-thumb-hover: 71 85 105; + --shadow-modal: 0 25px 50px -12px rgb(0 0 0 / 0.5); + --app-gradient: radial-gradient(1200px circle at top right, rgb(var(--color-primary) / 0.18), rgb(var(--color-background)) 55%, rgb(5 5 5) 100%); + --app-gradient-spot: radial-gradient(900px circle at 20% 20%, rgb(var(--color-primary) / 0.12), transparent 60%); + color-scheme: dark; +} + +:root[data-theme='light'] { + --color-background: 248 250 253; + --color-surface: 255 255 255; + --color-panel: 240 244 249; + --color-panel-strong: 233 238 246; + --color-panel-soft: 255 255 255; + --color-text: 31 31 31; + --color-text-muted: 68 71 70; + --color-border: 116 119 117; + --color-overlay: 31 31 31; + --color-primary: 11 87 208; + --color-primary-hover: 10 79 184; + --color-secondary: 0 99 155; + --scrollbar-thumb: 196 199 197; + --scrollbar-thumb-hover: 116 119 117; + --shadow-modal: 0 25px 50px -12px rgb(15 23 42 / 0.16); + --app-gradient: radial-gradient(1200px circle at top right, rgb(var(--color-primary) / 0.12), rgb(var(--color-background)) 45%, rgb(var(--color-panel)) 100%); + --app-gradient-spot: radial-gradient(800px circle at 20% 15%, rgb(194 231 255 / 0.45), transparent 60%); + color-scheme: light; +} + +:root[data-theme='system'] { + color-scheme: dark; +} + +@media (prefers-color-scheme: light) { + :root[data-theme='system'] { + --color-background: 248 250 253; + --color-surface: 255 255 255; + --color-panel: 240 244 249; + --color-panel-strong: 233 238 246; + --color-panel-soft: 255 255 255; + --color-text: 31 31 31; + --color-text-muted: 68 71 70; + --color-border: 116 119 117; + --color-overlay: 31 31 31; + --color-primary: 11 87 208; + --color-primary-hover: 10 79 184; + --color-secondary: 0 99 155; + --scrollbar-thumb: 196 199 197; + --scrollbar-thumb-hover: 116 119 117; + --shadow-modal: 0 25px 50px -12px rgb(15 23 42 / 0.16); + --app-gradient: radial-gradient(1200px circle at top right, rgb(var(--color-primary) / 0.12), rgb(var(--color-background)) 45%, rgb(var(--color-panel)) 100%); + --app-gradient-spot: radial-gradient(800px circle at 20% 15%, rgb(194 231 255 / 0.45), transparent 60%); + color-scheme: light; + } +} + +.app-shell { + background-color: rgb(var(--color-background)); + background-image: var(--app-gradient), var(--app-gradient-spot); + background-repeat: no-repeat; + background-size: cover; +} + +:root[data-resolved-theme='light'] .message-bubble--me { + background-color: rgb(var(--color-panel-strong)) !important; + border: 1px solid rgb(var(--color-border) / 0.35); + color: rgb(var(--color-text)) !important; +} + +.text-on-primary { + color: #fff !important; +} + +.hover\:text-on-primary:hover { + color: #fff !important; +} + +.group:hover .group-hover\:text-on-primary { + color: #fff !important; +} + +:root[data-resolved-theme='light'] .text-white { + color: rgb(var(--color-text) / 1) !important; +} + +:root[data-resolved-theme='light'] .text-white\/90 { + color: rgb(var(--color-text) / 0.9) !important; +} + +:root[data-resolved-theme='light'] .text-white\/80 { + color: rgb(var(--color-text) / 0.8) !important; +} + +:root[data-resolved-theme='light'] .text-white\/70 { + color: rgb(var(--color-text) / 0.7) !important; +} + +:root[data-resolved-theme='light'] .text-white\/60 { + color: rgb(var(--color-text) / 0.6) !important; +} + +:root[data-resolved-theme='light'] .text-white\/50 { + color: rgb(var(--color-text) / 0.5) !important; +} + +:root[data-resolved-theme='light'] .text-white\/40 { + color: rgb(var(--color-text) / 0.4) !important; +} + +:root[data-resolved-theme='light'] .text-white\/30 { + color: rgb(var(--color-text) / 0.3) !important; +} + +:root[data-resolved-theme='light'] .text-white\/20 { + color: rgb(var(--color-text) / 0.2) !important; +} + +:root[data-resolved-theme='light'] .placeholder-white\/30::placeholder { + color: rgb(var(--color-text) / 0.3) !important; +} + +:root[data-resolved-theme='light'] .placeholder-white\/20::placeholder { + color: rgb(var(--color-text) / 0.2) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/20 { + background-color: rgb(var(--color-overlay) / 0.2) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/10 { + background-color: rgb(var(--color-overlay) / 0.1) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/5 { + background-color: rgb(var(--color-overlay) / 0.05) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/\[0\.02\] { + background-color: rgb(var(--color-overlay) / 0.02) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/\[0\.03\] { + background-color: rgb(var(--color-overlay) / 0.03) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/\[0\.04\] { + background-color: rgb(var(--color-overlay) / 0.04) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/\[0\.05\] { + background-color: rgb(var(--color-overlay) / 0.05) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/\[0\.06\] { + background-color: rgb(var(--color-overlay) / 0.06) !important; +} + +:root[data-resolved-theme='light'] .bg-white\/\[0\.07\] { + background-color: rgb(var(--color-overlay) / 0.07) !important; +} + +:root[data-resolved-theme='light'] .border-white\/20 { + border-color: rgb(var(--color-border) / 0.45) !important; +} + +:root[data-resolved-theme='light'] .border-white\/10 { + border-color: rgb(var(--color-border) / 0.3) !important; +} + +:root[data-resolved-theme='light'] .border-white\/5 { + border-color: rgb(var(--color-border) / 0.2) !important; +} + +:root[data-resolved-theme='light'] .ring-white\/20 { + --tw-ring-color: rgb(var(--color-border) / 0.45) !important; +} + +:root[data-resolved-theme='light'] .ring-white\/10 { + --tw-ring-color: rgb(var(--color-border) / 0.3) !important; +} + +:root[data-resolved-theme='light'] .ring-white\/5 { + --tw-ring-color: rgb(var(--color-border) / 0.2) !important; +} diff --git a/src/tests/chat-utils.spec.ts b/src/tests/chat-utils.spec.ts new file mode 100644 index 0000000..7e951a8 --- /dev/null +++ b/src/tests/chat-utils.spec.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from 'vitest'; +import { ensureUniqueName, groupMessagesByDay, splitMentions } from '../features/chat/utils'; +import type { Member, Message } from '../features/chat/types'; + +describe('chat utils', () => { + it('splits mentions while preserving plain text', () => { + const parts = splitMentions('Hello @Jane Doe and @Sam'); + expect(parts).toEqual(['Hello ', '@Jane Doe and ', '@Sam']); + }); + + it('ensures unique member names case-insensitively', () => { + const members: Member[] = [ + { id: '1', name: 'Alex', role: '', roleType: 'member', avatar: '', status: 'offline' }, + { id: '2', name: 'Alex-1', role: '', roleType: 'member', avatar: '', status: 'offline' } + ]; + + expect(ensureUniqueName('Alex', members)).toBe('Alex-2'); + expect(ensureUniqueName('Jamie', members)).toBe('Jamie'); + }); + + it('groups messages by day with separators', () => { + const dayOne = new Date('2024-02-01T09:00:00').getTime(); + const dayTwo = new Date('2024-02-02T10:00:00').getTime(); + + const messages: Message[] = [ + { + id: 1, + senderId: 'me', + user: 'You', + avatar: '', + text: 'First message', + time: '09:00', + createdAt: dayOne, + isAi: false, + status: 'sent' + }, + { + id: 2, + senderId: 'me', + user: 'You', + avatar: '', + text: 'Second message', + time: '09:05', + createdAt: dayOne + 1000 * 60 * 5, + isAi: false, + status: 'sent' + }, + { + id: 3, + senderId: 'assistant', + user: 'AI', + avatar: '', + text: 'Next day message', + time: '10:00', + createdAt: dayTwo, + isAi: true, + status: 'sent' + } + ]; + + const grouped = groupMessagesByDay(messages, 'en-US'); + const separatorCount = grouped.filter((item) => item.type === 'separator').length; + const messageCount = grouped.filter((item) => item.type === 'message').length; + + expect(separatorCount).toBe(2); + expect(messageCount).toBe(3); + }); +}); diff --git a/startup_processmd.md b/startup_processmd.md new file mode 100644 index 0000000..a0fa0ba --- /dev/null +++ b/startup_processmd.md @@ -0,0 +1,35 @@ +# Nexus Dashboard Suite (Vue 3) + +This project is a Vue 3 + TypeScript rebuild of the original Nexus dashboard UI. The UI and behavior are kept consistent while the codebase is refactored into Vue components with Composition API. + +## Run Locally + +**Prerequisites:** Node.js, pnpm + +1. Install dependencies: + `pnpm install` +2. (Optional) Set `GEMINI_API_KEY` in `.env.local` +3. Start the dev server: + `pnpm dev` + +## Tests + +Run unit tests: +`pnpm test` + +## Lint & Format + +- `pnpm lint` +- `pnpm format:check` + +## Project Structure + +- `src/app/App.vue`: App shell + navigation state +- `src/features`: Feature modules (store, marketplace, settings, workspace, chat) +- `src/shared`: Reusable components and composables +- `src/i18n`: Locale files and i18n setup +- `src/styles/global.css`: Scrollbar and layout utilities + +See `docs/ENGINEERING_GUIDE.md` for conventions and structure details. + +Legacy React code is moved to `C:\project\user\nexus-dashboard-suite-legacy`. diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..65547e6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node", + "vite/client" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.vue", + "vite.config.ts" + ] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..82c0a65 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,28 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + strictPort: true, + }, + plugins: [vue()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + } + }, + test: { + environment: 'node', + include: ['src/tests/**/*.spec.ts'] + } + }; +});