diff --git a/.gitmodules b/.gitmodules index e69de29..019da05 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/markdown-editor"] + path = packages/markdown-editor + url = git@git.xiongxiao.me:kevisual/markdown-editor.git diff --git a/package.json b/package.json index e1712c3..9b27609 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "@kevisual/astro-template", + "name": "@kevisual/ai-pages", "version": "0.0.1", "description": "", "main": "index.js", - "basename": "/root/astro-template", + "basename": "/root/ai-pages", "scripts": { "dev": "astro dev", "build": "astro build", "preview": "astro preview", - "pub": "envision deploy ./dist -k astro-template -v 0.0.1 -u", + "pub": "ev deploy ./dist -k ai-pages -v 0.0.1 -u", "git:submodule": "git submodule update --init --recursive", "sn": "pnpm dlx shadcn@latest add " }, @@ -17,11 +17,11 @@ "license": "MIT", "type": "module", "dependencies": { - "@astrojs/mdx": "^4.2.6", - "@astrojs/react": "^4.2.7", + "@astrojs/mdx": "^4.3.0", + "@astrojs/react": "^4.3.0", "@astrojs/sitemap": "^3.4.0", - "@kevisual/query": "^0.0.18", - "@kevisual/query-login": "^0.0.5", + "@kevisual/cache": "^0.0.3", + "@kevisual/query-login": "^0.0.6", "@kevisual/registry": "^0.0.1", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.1.14", @@ -31,30 +31,47 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.7", - "astro": "^5.7.13", + "astro": "^5.8.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "dayjs": "^1.11.13", + "highlight.js": "^11.11.1", + "i18next": "^25.2.0", + "i18next-browser-languagedetector": "^8.1.0", + "i18next-http-backend": "^3.0.2", "lodash-es": "^4.17.21", "lucide-react": "^0.511.0", + "marked": "^15.0.12", + "marked-highlight": "^2.2.1", "nanoid": "^5.1.5", + "pretty-bytes": "^7.0.0", + "re-resizable": "^6.11.2", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-draggable": "^4.4.6", "react-hook-form": "^7.56.4", + "react-i18next": "^15.5.2", + "react-sortablejs": "^6.1.4", "react-toastify": "^11.0.5", + "sortablejs": "^1.15.6", "tailwind-merge": "^3.3.0", - "zustand": "^5.0.4" + "zustand": "^5.0.5" }, "publishConfig": { "access": "public" }, "devDependencies": { + "@kevisual/markdown-editor": "workspace:*", + "@kevisual/query": "^0.0.20", "@kevisual/query-awesome": "^0.0.2", "@kevisual/router": "^0.0.21", + "@kevisual/store": "^0.0.8", "@kevisual/types": "^0.0.10", - "@types/react": "^19.1.4", + "@types/lodash-es": "^4.17.12", + "@types/react": "^19.1.5", "@types/react-dom": "^19.1.5", + "@types/sortablejs": "^1.15.8", "@vitejs/plugin-basic-ssl": "^2.0.0", "commander": "^14.0.0", "dotenv": "^16.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d0d6fe..2419daa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,50 +9,50 @@ importers: .: dependencies: '@astrojs/mdx': - specifier: ^4.2.6 - version: 4.2.6(astro@5.7.13(@types/node@18.19.87)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3)) + specifier: ^4.3.0 + version: 4.3.0(astro@5.8.0(@types/node@22.15.21)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3)) '@astrojs/react': - specifier: ^4.2.7 - version: 4.2.7(@types/node@18.19.87)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^4.3.0 + version: 4.3.0(@types/node@22.15.21)(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@astrojs/sitemap': specifier: ^3.4.0 version: 3.4.0 - '@kevisual/query': - specifier: ^0.0.18 - version: 0.0.18(zod@3.24.3) + '@kevisual/cache': + specifier: ^0.0.3 + version: 0.0.3 '@kevisual/query-login': - specifier: ^0.0.5 - version: 0.0.5(@kevisual/query@0.0.18(zod@3.24.3))(rollup@4.40.1)(tslib@2.8.1)(typescript@5.8.3) + specifier: ^0.0.6 + version: 0.0.6(@kevisual/query@0.0.20(ws@8.18.2)(zod@3.24.3))(rollup@4.40.1)(tslib@2.8.1)(typescript@5.8.3) '@kevisual/registry': specifier: ^0.0.1 version: 0.0.1(typescript@5.8.3) '@radix-ui/react-alert-dialog': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-dialog': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-label': specifier: ^2.1.7 - version: 2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 2.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-popover': specifier: ^1.1.14 - version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-select': specifier: ^2.2.5 - version: 2.2.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 2.2.5(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-slot': specifier: ^1.2.3 - version: 1.2.3(@types/react@19.1.4)(react@19.1.0) + version: 1.2.3(@types/react@19.1.5)(react@19.1.0) '@radix-ui/react-tooltip': specifier: ^1.2.7 - version: 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tailwindcss/vite': specifier: ^4.1.7 - version: 4.1.7(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)) + version: 4.1.7(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) astro: - specifier: ^5.7.13 - version: 5.7.13(@types/node@18.19.87)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3) + specifier: ^5.8.0 + version: 5.8.0(@types/node@22.15.21)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -61,56 +61,107 @@ importers: version: 2.1.1 cmdk: specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 1.1.1(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) dayjs: specifier: ^1.11.13 version: 1.11.13 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + i18next: + specifier: ^25.2.0 + version: 25.2.0(typescript@5.8.3) + i18next-browser-languagedetector: + specifier: ^8.1.0 + version: 8.1.0 + i18next-http-backend: + specifier: ^3.0.2 + version: 3.0.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 lucide-react: specifier: ^0.511.0 version: 0.511.0(react@19.1.0) + marked: + specifier: ^15.0.12 + version: 15.0.12 + marked-highlight: + specifier: ^2.2.1 + version: 2.2.1(marked@15.0.12) nanoid: specifier: ^5.1.5 version: 5.1.5 + pretty-bytes: + specifier: ^7.0.0 + version: 7.0.0 + re-resizable: + specifier: ^6.11.2 + version: 6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) + react-draggable: + specifier: ^4.4.6 + version: 4.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-hook-form: specifier: ^7.56.4 version: 7.56.4(react@19.1.0) + react-i18next: + specifier: ^15.5.2 + version: 15.5.2(i18next@25.2.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + react-sortablejs: + specifier: ^6.1.4 + version: 6.1.4(@types/sortablejs@1.15.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sortablejs@1.15.6) react-toastify: specifier: ^11.0.5 version: 11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + sortablejs: + specifier: ^1.15.6 + version: 1.15.6 tailwind-merge: specifier: ^3.3.0 version: 3.3.0 zustand: - specifier: ^5.0.4 - version: 5.0.4(@types/react@19.1.4)(react@19.1.0) + specifier: ^5.0.5 + version: 5.0.5(@types/react@19.1.5)(immer@10.1.1)(react@19.1.0) devDependencies: + '@kevisual/markdown-editor': + specifier: workspace:* + version: link:packages/markdown-editor + '@kevisual/query': + specifier: ^0.0.20 + version: 0.0.20(ws@8.18.2)(zod@3.24.3) '@kevisual/query-awesome': specifier: ^0.0.2 version: 0.0.2 '@kevisual/router': specifier: ^0.0.21 version: 0.0.21 + '@kevisual/store': + specifier: ^0.0.8 + version: 0.0.8 '@kevisual/types': specifier: ^0.0.10 version: 0.0.10 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/react': - specifier: ^19.1.4 - version: 19.1.4 + specifier: ^19.1.5 + version: 19.1.5 '@types/react-dom': specifier: ^19.1.5 - version: 19.1.5(@types/react@19.1.4) + version: 19.1.5(@types/react@19.1.5) + '@types/sortablejs': + specifier: ^1.15.8 + version: 1.15.8 '@vitejs/plugin-basic-ssl': specifier: ^2.0.0 - version: 2.0.0(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)) + version: 2.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) commander: specifier: ^14.0.0 version: 14.0.0 @@ -128,7 +179,137 @@ importers: version: 1.3.0 vite-plugin-remote-assets: specifier: ^2.0.0 - version: 2.0.0(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)) + version: 2.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) + + packages/markdown-editor: + dependencies: + '@kevisual/router': + specifier: 0.0.10 + version: 0.0.10 + '@tiptap/core': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/extension-code-block-lowlight': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-code-block@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(highlight.js@11.11.1)(lowlight@3.3.0) + '@tiptap/extension-document': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-highlight': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-paragraph': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-placeholder': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/extension-text': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-typography': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/pm': + specifier: ^2.11.7 + version: 2.12.0 + '@tiptap/starter-kit': + specifier: ^2.11.7 + version: 2.12.0 + '@tiptap/suggestion': + specifier: ^2.11.7 + version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 + github-markdown-css: + specifier: ^5.8.1 + version: 5.8.1 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + idb: + specifier: ^8.0.2 + version: 8.0.3 + idb-keyval: + specifier: ^6.2.1 + version: 6.2.1 + immer: + specifier: ^10.1.1 + version: 10.1.1 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + lowlight: + specifier: ^3.3.0 + version: 3.3.0 + lucide-react: + specifier: ^0.487.0 + version: 0.487.0(react@19.1.0) + marked: + specifier: ^15.0.7 + version: 15.0.12 + nanoid: + specifier: ^5.1.5 + version: 5.1.5 + react: + specifier: ^19.1.0 + version: 19.1.0 + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + react-toastify: + specifier: ^11.0.5 + version: 11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + tiptap-markdown: + specifier: ^0.8.10 + version: 0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + turndown: + specifier: ^7.2.0 + version: 7.2.0 + zustand: + specifier: ^5.0.3 + version: 5.0.5(@types/react@19.1.5)(immer@10.1.1)(react@19.1.0) + devDependencies: + '@kevisual/query': + specifier: 0.0.15 + version: 0.0.15(ws@8.18.2)(zod@3.24.3) + '@kevisual/types': + specifier: ^0.0.6 + version: 0.0.6 + '@tailwindcss/vite': + specifier: ^4.1.3 + version: 4.1.7(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) + '@types/node': + specifier: ^22.14.0 + version: 22.15.21 + '@types/react': + specifier: ^19.1.0 + version: 19.1.5 + '@types/react-dom': + specifier: ^19.1.1 + version: 19.1.5(@types/react@19.1.5) + '@types/turndown': + specifier: ^5.0.5 + version: 5.0.5 + '@vitejs/plugin-basic-ssl': + specifier: ^2.0.0 + version: 2.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.4.1(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) + tailwindcss: + specifier: ^4.1.3 + version: 4.1.7 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vite: + specifier: ^6.2.5 + version: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) packages: @@ -142,22 +323,22 @@ packages: '@astrojs/internal-helpers@0.6.1': resolution: {integrity: sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==} - '@astrojs/markdown-remark@6.3.1': - resolution: {integrity: sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg==} + '@astrojs/markdown-remark@6.3.2': + resolution: {integrity: sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q==} - '@astrojs/mdx@4.2.6': - resolution: {integrity: sha512-0i/GmOm6d0qq1/SCfcUgY/IjDc/bS0i42u7h85TkPFBmlFOcBZfkYhR5iyz6hZLwidvJOEq5yGfzt9B1Azku4w==} - engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0} + '@astrojs/mdx@4.3.0': + resolution: {integrity: sha512-OGX2KvPeBzjSSKhkCqrUoDMyzFcjKt5nTE5SFw3RdoLf0nrhyCXBQcCyclzWy1+P+XpOamn+p+hm1EhpCRyPxw==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} peerDependencies: astro: ^5.0.0 - '@astrojs/prism@3.2.0': - resolution: {integrity: sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw==} - engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0} + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - '@astrojs/react@4.2.7': - resolution: {integrity: sha512-/wM90noT/6QyJEOGdDmDbq2D9qZooKTJNG1M8olmsW5ns6bJ7uxG5fzkYxcpA3WUTD6Dj6NtpEqchvb5h8Fa+g==} - engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0} + '@astrojs/react@4.3.0': + resolution: {integrity: sha512-N02aj52Iezn69qHyx5+XvPqgsPMEnel9mI5JMbGiRMTzzLMuNaxRVoQTaq2024Dpr7BLsxCjqMkNvelqMDhaHA==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} peerDependencies: '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 @@ -167,9 +348,9 @@ packages: '@astrojs/sitemap@3.4.0': resolution: {integrity: sha512-C5m/xsKvRSILKM3hy47n5wKtTQtJXn8epoYuUmCCstaE9XBt20yInym3Bz2uNbEiNfv11bokoW0MqeXPIvjFIQ==} - '@astrojs/telemetry@3.2.1': - resolution: {integrity: sha512-SSVM820Jqc6wjsn7qYfV9qfeQvePtVc1nSofhyap7l0/iakUKywj3hfy3UJAOV4sGV4Q/u450RD4AaCaFvNPlg==} - engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0} + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} @@ -238,10 +419,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.27.0': - resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.27.1': resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} engines: {node: '>=6.9.0'} @@ -559,29 +736,47 @@ packages: '@kevisual/cache@0.0.2': resolution: {integrity: sha512-2Cl5KF2Gi27uLfhO6CdTMFnRzx9vYnqevAo7d9ab3rOaqTgF8tLeAXglXyRbaWW3WUbHU2XaOb4r98uUsqIQQw==} + '@kevisual/cache@0.0.3': + resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==} + '@kevisual/query-awesome@0.0.2': resolution: {integrity: sha512-+dvUmilxlQEWqv0qbXT9dBGbOCyDBxH56OKiDq8wicNqFOChmwsB99ua2X51IloRXKLh0NoylJks91r7Btqy2A==} - '@kevisual/query-login@0.0.5': - resolution: {integrity: sha512-389cMMWAisjQoafxX+cUEa2z41S5koDjiyHkucfCkhRoP4M6g0iqbBMavLKmLOWSKx3R8e3ZmXT6RfsYGBb8Ww==} + '@kevisual/query-login@0.0.6': + resolution: {integrity: sha512-ZdX+sxeQaM3PV9fZXofMlxFz1RmpYIkoi47exzUgw6DADjEryBAQKRXe2/oL20NsBTV8owqaagRqffAVjq5c5g==} peerDependencies: '@kevisual/query': ^0.0.17 - '@kevisual/query@0.0.18': - resolution: {integrity: sha512-I2vHTu0I6AyD9PJyr+vxyp9jIJ6rd2EZqLVHTv/+zrVKVc2SS76Tg7aGNkmAFqqLSCB8kLLsmMGtSJU1Qb8VVg==} + '@kevisual/query@0.0.15': + resolution: {integrity: sha512-DK41qvyOiJMmlj70QyVP/48M0gszA39DdnBLtgU94YwAe6OqKrr9tYXHLjZrOROmUVMezIIBQuWMLedSAvb54A==} + + '@kevisual/query@0.0.20': + resolution: {integrity: sha512-IpkQd1Pz50namxDNytLJpyR2CRLJWKTkmk68Fiw6CEkTXQDkdyBh2kIOIf7+Geu8quai/J9io3KjWzt17AeYdg==} '@kevisual/registry@0.0.1': resolution: {integrity: sha512-//OHu9m4JDrMjgP8o8dcjZd3D3IAUkRVlkTSviouZEH7r5m7mccA3Hvzw0XJ/lelx6exC6LWsyv6c4uV0Dp+gw==} + '@kevisual/router@0.0.10': + resolution: {integrity: sha512-prQGiMIboQhDNN1Eubp8x7YDyRCmAsUqpHQwzfu9f7WvgisVWSLOWSaLbqjqNssV2xcc1DgVrHIKdLhbx8HCqQ==} + '@kevisual/router@0.0.21': resolution: {integrity: sha512-XKTxbNO924cT18UOAGplWErZ+hMze8Y53F2jYCk18v4jsdsvjRho5uXXjJb6HSVsuITMtQR4R3rG0IcM3jkDKQ==} + '@kevisual/store@0.0.8': + resolution: {integrity: sha512-eIAk7lPTCBCCuQKvRJH38jfYjT9uIY3oI4ZVllzqIrJTUAyvd+QudMp/zFduGHC+Nbkr/ldPsVbKF4ZPkl2Pbw==} + '@kevisual/types@0.0.10': resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} + '@kevisual/types@0.0.6': + resolution: {integrity: sha512-7yxe1QmuC5g7lI/1Hm+zXly8if0z+ZqGM1SVOVv2VNRwRAVYBJDc365zWCCfRwE+5YaB2daWTe5zBOU4EkltkQ==} + '@mdx-js/mdx@3.1.0': resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} @@ -915,6 +1110,9 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rollup/plugin-commonjs@28.0.3': resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} @@ -1169,6 +1367,148 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 + '@tiptap/core@2.12.0': + resolution: {integrity: sha512-3qX8oGVKFFZzQ0vit+ZolR6AJIATBzmEmjAA0llFhWk4vf3v64p1YcXcJsOBsr5scizJu5L6RYWEFatFwqckRg==} + peerDependencies: + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.12.0': + resolution: {integrity: sha512-XUC2A77YAPMJS2SqZ2S62IGcUH8gZ7cdhoWlYQb1pR4ZzXFByeKDJPxfYeAePSiuI01YGrlzgY2c6Ncx/DtO0A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.12.0': + resolution: {integrity: sha512-lAUtoLDLRc5ofD2I9MFY6MQ7d1qBLLqS1rvpwaPjOaoQb/GPVnaHj9qXYG0SY9K3erMtto48bMFpAcscjZHzZQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bullet-list@2.12.0': + resolution: {integrity: sha512-YTCjztB8MaIpwyxFYr81H4+LdKCq1VlaSXQyrPdB44mVdhhRqc46BYQb8/B//XE3UIu3X2QWFjwrqRlUq6vUiw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-code-block-lowlight@2.12.0': + resolution: {integrity: sha512-q5dg3GbWCMT0xniVuxjDwAd3CdMfGXFmptulzgaV31HLU+6nu4zZ5sNtMLDA7RU05lnvatKLeXNUhJ2gPouhtw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-code-block': ^2.7.0 + '@tiptap/pm': ^2.7.0 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@2.12.0': + resolution: {integrity: sha512-1D7cYAjgxEFHdfC/35Ooi4GqWKB5sszbW8iI7N16XILNln26xb0d5KflXqYrwr9CN/ZnZoCl2o6YsP7xEObcZA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.12.0': + resolution: {integrity: sha512-R7RaS+hJeHFim7alImQ9L9CSWSMjWXvz0Ote568x9ea5gdBGUYW8PcH+5a91lh8e1XGYWBM12a8oJZRyxg/tQA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-document@2.12.0': + resolution: {integrity: sha512-sA1Q+mxDIv0Y3qQTBkYGwknNbDcGFiJ/fyAFholXpqbrcRx3GavwR/o0chBdsJZlFht0x7AWGwUYWvIo7wYilA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-dropcursor@2.12.0': + resolution: {integrity: sha512-zcZSOXFj+7LVnmdPWTfKr5AoxYIzFPFlLJe35AdTQC5IhkljLn1Exct8I30ZREojX/00hKYsO7JJmePS6TEVlQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.12.0': + resolution: {integrity: sha512-k8ji5v9YKn7bNjo8UtI9hEfXfl4tKUp1hpJOEmUxGJQa3LIwrwSbReupUTnHszGQelzxikS/l1xO9P0TIGwRoA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.12.0': + resolution: {integrity: sha512-08MNS2PK5DzdnAfqXn4krmJ/xebKmWpRpYqqN5EM8AvetYKlAJyTVSpo0ZUeGbZ3EZiPm9djgSnrLqpFUDjRCg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.12.0': + resolution: {integrity: sha512-9DfES4Wd5TX1foI70N9sAL+35NN1UHrtzDYN2+dTHupnmKir9RaMXyZcbkUb4aDVzYrGxIqxJzHBVkquKIlTrw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-highlight@2.12.0': + resolution: {integrity: sha512-dQNaDXslN9ion0NgVVOOKJNS3MP8Lnx9xT01mDa/R9Qdulh7xmK6n8O2GCb86TnjX1OyZURx+fEK/WLM8oKK+A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.12.0': + resolution: {integrity: sha512-+B9CAf2BFURC6mQiM1OQtahVTzdEOEgT/UUNlRZkeeBc0K5of3dr6UdBqaoaMAefja3jx5PqiQ7mhUBAjSt6AA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.12.0': + resolution: {integrity: sha512-Vi2+6RIehDSpoJn/7PDuOieUj7W7WrEb4wBxK9TG8PDscihR0mehhhzm/K2xhH4TN48iPJGRsjDFrFjTbXmcnw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-italic@2.12.0': + resolution: {integrity: sha512-JKcXK3LmEsmxNzEq5e06rPUGMRLUxmJ2mYtBY4NlJ6yLM9XMDljtgeTnWT0ySLYmfINSFTkX4S7WIRbpl9l4pw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-list-item@2.12.0': + resolution: {integrity: sha512-4YwZooC8HP+gPxs6YrkB1ayggyYbgVvJx/rWBT6lKSW2MVVg8QXi1zAcSI3MhIhHmqDysXXFPL8JURlbeGjaFA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-ordered-list@2.12.0': + resolution: {integrity: sha512-1ys0e/oqk09oXxrB1WzAx5EntK/QreObG/V1yhgihGm429fxHMsxzIYN6dKAYxx0YOPQG7qEZRrrPuWU70Ms7g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.12.0': + resolution: {integrity: sha512-QNK5cgewCunWFxpLlbvvoO1rrLgEtNKxiY79fctP9toV+e59R+1i1Q9lXC1O5mOfDgVxCb6uFDMsqmKhFjpPog==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-placeholder@2.12.0': + resolution: {integrity: sha512-K7irDox4P+NLAMjVrJeG72f0sulsCRYpx1Cy4gxKCdi1LTivj5VkXa6MXmi42KTCwBu3pWajBctYIOAES1FTAA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-strike@2.12.0': + resolution: {integrity: sha512-nBaa5YtBsLJPZFfSs36sBz4Zgi/c8b3MsmS/Az8uXaHb0R9yPewOVUMDIQbxMct8SXUlIo9VtKlOL+mVJ3Nkpw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.12.0': + resolution: {integrity: sha512-Pxwt23ZlvbQUahV0PvHy8Ej6IAuKR1FvHobUvwP3T8AiY7hob66fWRe7tQbESzSAzm5Vv2xkvyHeU8vekMTezA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.12.0': + resolution: {integrity: sha512-0ytN9V1tZYTXdiYDQg4FB2SQ56JAJC9r/65snefb9ztl+gZzDrIvih7CflHs1ic9PgyjexfMLeH+VzuMccNyZw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-typography@2.12.0': + resolution: {integrity: sha512-Sh95D8ON3KcGR/6wQ1B2PwVQNQXSHItQFodDAEgT9c6CCPzrz0omLWJvG7lx7NzMoZ7cvPE/BBoEujsDIT1XRA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.12.0': + resolution: {integrity: sha512-TNzVwpeNzFfHAcYTOKqX9iU4fRxliyoZrCnERR+RRzeg7gWrXrCLubQt1WEx0sojMAfznshSL3M5HGsYjEbYwA==} + + '@tiptap/starter-kit@2.12.0': + resolution: {integrity: sha512-wlcEEtexd6u0gbR311/OFZnbtRWU97DUsY6/GsSQzN4rqZ7Ra6YbfHEN5Lutu+I/anomK8vKy8k9NyvfY5Hllg==} + + '@tiptap/suggestion@2.12.0': + resolution: {integrity: sha512-bsXLoZbjUo1oOF1Z+XSfoGzbcnrTcYtJdfylM/FerMLU9T12dhsM/Ri2SKLX4IR5D0HJ07FcsEHCrGEy8Y5y0A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1196,9 +1536,33 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.17': + resolution: {integrity: sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==} + + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -1220,13 +1584,16 @@ packages: '@types/node@18.19.87': resolution: {integrity: sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==} + '@types/node@22.15.21': + resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==} + '@types/react-dom@19.1.5': resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==} peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.1.4': - resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==} + '@types/react@19.1.5': + resolution: {integrity: sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1234,6 +1601,12 @@ packages: '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + '@types/sortablejs@1.15.8': + resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} + + '@types/turndown@5.0.5': + resolution: {integrity: sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1313,9 +1686,9 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - astro@5.7.13: - resolution: {integrity: sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w==} - engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + astro@5.8.0: + resolution: {integrity: sha512-G57ELkdIntDiSrucA5lQaRtBOjquaZ9b9NIwoz2f471ZuuJcynLjWgItgBzlrz5UMY4WqnFbVWUCKlJb7nt9bA==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true asynckit@0.4.0: @@ -1394,6 +1767,9 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + classnames@2.3.1: + resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -1402,6 +1778,10 @@ packages: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1456,6 +1836,9 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-fetch@3.2.0: resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} @@ -1562,6 +1945,10 @@ packages: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@6.0.0: resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==} engines: {node: '>=0.12'} @@ -1600,6 +1987,10 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -1698,6 +2089,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + github-markdown-css@5.8.1: + resolution: {integrity: sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==} + engines: {node: '>=10'} + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -1763,6 +2158,10 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -1784,8 +2183,8 @@ packages: i18next-http-backend@3.0.2: resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==} - i18next@25.1.3: - resolution: {integrity: sha512-VY1iKox3YWPRTNMHFdgk5TV+Jq6rNKexLCLpPmP5oXXJ5Kl7yDBi3ycZ5fyEKZ1tNBW5gOqD4WV0XqE7rl3JUg==} + i18next@25.2.0: + resolution: {integrity: sha512-ERhJICsxkw1vE7G0lhCUYv4ZxdBEs03qblt1myJs94rYRK9loJF3xDj8mgQz3LmCyp0yYrNjbN/1/GWZTZDGCA==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -1795,6 +2194,12 @@ packages: idb-keyval@6.2.1: resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} + idb@8.0.3: + resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} + + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} @@ -1948,18 +2353,33 @@ packages: resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.487.0: + resolution: {integrity: sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.509.0: resolution: {integrity: sha512-xCJHn6Uh5qF6PGml25vveCTrHJZcqS1G1MVzWZK54ZQsOiCVJk4fwY3oyo5EXS2S+aqvTpWYIfJN+PesJ0quxg==} peerDependencies: @@ -1980,9 +2400,26 @@ packages: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked-highlight@2.2.1: + resolution: {integrity: sha512-SiCIeEiQbs9TxGwle9/OwbOejHCZsohQRaNTY2u8euEXYt2rYUFoiImUirThU3Gd/o6Q1gHGtH9qloHlbJpNIA==} + peerDependencies: + marked: '>=4 <16' + + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2041,6 +2478,9 @@ packages: mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2222,6 +2662,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + ofetch@1.4.1: resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} @@ -2246,6 +2690,9 @@ packages: zod: optional: true + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + p-limit@6.2.0: resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} engines: {node: '>=18'} @@ -2295,6 +2742,10 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + pretty-bytes@7.0.0: + resolution: {integrity: sha512-U5otLYPR3L0SVjHGrkEUx5mf7MxV2ceXeE7VwWPk+hyzC5drNohsOGNPDZqxCqyX1lkbEN4kl1LiI8QFd7r0ZA==} + engines: {node: '>=20'} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -2303,28 +2754,105 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + prosemirror-changeset@2.3.0: + resolution: {integrity: sha512-8wRKhlEwEJ4I13Ju54q2NZR1pVKGTgJ/8XsQ8L5A5uUsQ/YQScQJuEAuh8Bn8i6IwAMjjLRABd9lVli+DlIiVw==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.3.2: + resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + + prosemirror-history@1.4.1: + resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + + prosemirror-inputrules@1.5.0: + resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.2: + resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==} + + prosemirror-menu@1.2.5: + resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + + prosemirror-model@1.25.1: + resolution: {integrity: sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + + prosemirror-tables@1.7.1: + resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.10.4: + resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==} + + prosemirror-view@1.39.3: + resolution: {integrity: sha512-bY/7kg0LzRE7ytR0zRdSMWX3sknEjw68l836ffLPMh0OG3OYnNuBDUSF3v0vjvnzgYjgY9ZH/RypbARURlcMFA==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + re-resizable@6.11.2: + resolution: {integrity: sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==} + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 + react-draggable@4.4.6: + resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + react-hook-form@7.56.4: resolution: {integrity: sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-i18next@15.5.1: - resolution: {integrity: sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==} + react-i18next@15.5.2: + resolution: {integrity: sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==} peerDependencies: i18next: '>= 23.2.3' react: '>= 16.8.0' @@ -2339,6 +2867,9 @@ packages: typescript: optional: true + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -2363,6 +2894,14 @@ packages: '@types/react': optional: true + react-sortablejs@6.1.4: + resolution: {integrity: sha512-fc7cBosfhnbh53Mbm6a45W+F735jwZ1UFIYSrIqcO/gRIFoDyZeMtgKlpV4DdyQfbCzdh5LoALLTDRxhMpTyXQ==} + peerDependencies: + '@types/sortablejs': '1' + react: '>=16.9.0' + react-dom: '>=16.9.0' + sortablejs: '1' + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -2399,9 +2938,6 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -2477,6 +3013,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} @@ -2518,6 +3057,9 @@ packages: resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==} engines: {node: '>= 18'} + sortablejs@1.15.6: + resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2578,6 +3120,9 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.2.0: + resolution: {integrity: sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -2585,6 +3130,11 @@ packages: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} + tiptap-markdown@0.8.10: + resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==} + peerDependencies: + '@tiptap/core': ^2.0.3 + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -2607,6 +3157,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turndown@7.2.0: + resolution: {integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==} + tw-animate-css@1.3.0: resolution: {integrity: sha512-jrJ0XenzS9KVuDThJDvnhalbl4IYiMQ/XvpA0a2FL8KmlK+6CSMviO7ROY/I7z1NnUs5NnDhlM6fXmF40xPxzw==} @@ -2619,6 +3172,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} @@ -2631,6 +3187,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicode-properties@1.4.1: resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} @@ -2824,6 +3383,9 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -2849,6 +3411,18 @@ packages: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -2889,8 +3463,8 @@ packages: zod@3.24.3: resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} - zustand@5.0.4: - resolution: {integrity: sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==} + zustand@5.0.5: + resolution: {integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -2921,10 +3495,10 @@ snapshots: '@astrojs/internal-helpers@0.6.1': {} - '@astrojs/markdown-remark@6.3.1': + '@astrojs/markdown-remark@6.3.2': dependencies: '@astrojs/internal-helpers': 0.6.1 - '@astrojs/prism': 3.2.0 + '@astrojs/prism': 3.3.0 github-slugger: 2.0.0 hast-util-from-html: 2.0.3 hast-util-to-text: 4.0.2 @@ -2947,12 +3521,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.2.6(astro@5.7.13(@types/node@18.19.87)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3))': + '@astrojs/mdx@4.3.0(astro@5.8.0(@types/node@22.15.21)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3))': dependencies: - '@astrojs/markdown-remark': 6.3.1 + '@astrojs/markdown-remark': 6.3.2 '@mdx-js/mdx': 3.1.0(acorn@8.14.1) acorn: 8.14.1 - astro: 5.7.13(@types/node@18.19.87)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3) + astro: 5.8.0(@types/node@22.15.21)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -2966,19 +3540,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/prism@3.2.0': + '@astrojs/prism@3.3.0': dependencies: prismjs: 1.30.0 - '@astrojs/react@4.2.7(@types/node@18.19.87)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@astrojs/react@4.3.0(@types/node@22.15.21)(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) - '@vitejs/plugin-react': 4.4.1(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) + '@vitejs/plugin-react': 4.4.1(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) ultrahtml: 1.6.0 - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - '@types/node' - jiti @@ -2999,7 +3573,7 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.24.3 - '@astrojs/telemetry@3.2.1': + '@astrojs/telemetry@3.3.0': dependencies: ci-info: 4.2.0 debug: 4.4.0 @@ -3098,10 +3672,6 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/runtime@7.27.0': - dependencies: - regenerator-runtime: 0.14.1 - '@babel/runtime@7.27.1': {} '@babel/template@7.27.0': @@ -3340,21 +3910,33 @@ snapshots: - tslib - typescript + '@kevisual/cache@0.0.3': + dependencies: + idb-keyval: 6.2.1 + '@kevisual/query-awesome@0.0.2': {} - '@kevisual/query-login@0.0.5(@kevisual/query@0.0.18(zod@3.24.3))(rollup@4.40.1)(tslib@2.8.1)(typescript@5.8.3)': + '@kevisual/query-login@0.0.6(@kevisual/query@0.0.20(ws@8.18.2)(zod@3.24.3))(rollup@4.40.1)(tslib@2.8.1)(typescript@5.8.3)': dependencies: '@kevisual/cache': 0.0.2(rollup@4.40.1)(tslib@2.8.1)(typescript@5.8.3) - '@kevisual/query': 0.0.18(zod@3.24.3) + '@kevisual/query': 0.0.20(ws@8.18.2)(zod@3.24.3) dotenv: 16.5.0 transitivePeerDependencies: - rollup - tslib - typescript - '@kevisual/query@0.0.18(zod@3.24.3)': + '@kevisual/query@0.0.15(ws@8.18.2)(zod@3.24.3)': dependencies: - openai: 4.100.0(zod@3.24.3) + openai: 4.100.0(ws@8.18.2)(zod@3.24.3) + transitivePeerDependencies: + - encoding + - ws + - zod + + '@kevisual/query@0.0.20(ws@8.18.2)(zod@3.24.3)': + dependencies: + openai: 4.100.0(ws@8.18.2)(zod@3.24.3) transitivePeerDependencies: - encoding - ws @@ -3364,13 +3946,13 @@ snapshots: dependencies: class-variance-authority: 0.7.1 clsx: 2.1.1 - i18next: 25.1.3(typescript@5.8.3) + i18next: 25.2.0(typescript@5.8.3) i18next-browser-languagedetector: 8.1.0 i18next-http-backend: 3.0.2 lucide-react: 0.509.0(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-i18next: 15.5.1(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + react-i18next: 15.5.2(i18next@25.2.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) react-toastify: 11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: 3.3.0 transitivePeerDependencies: @@ -3378,13 +3960,26 @@ snapshots: - react-native - typescript + '@kevisual/router@0.0.10': + dependencies: + path-to-regexp: 8.2.0 + selfsigned: 2.4.1 + ws: 8.18.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@kevisual/router@0.0.21': dependencies: path-to-regexp: 8.2.0 selfsigned: 2.4.1 + '@kevisual/store@0.0.8': {} + '@kevisual/types@0.0.10': {} + '@kevisual/types@0.0.6': {} + '@mdx-js/mdx@3.1.0(acorn@8.14.1)': dependencies: '@types/estree': 1.0.7 @@ -3415,324 +4010,328 @@ snapshots: - acorn - supports-color + '@mixmark-io/domino@2.2.0': {} + '@oslojs/encoding@1.1.0': {} '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.2': {} - '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-context@1.1.2(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-context@1.1.2(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.5)(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-remove-scroll: 2.7.0(@types/react@19.1.4)(react@19.1.0) + react-remove-scroll: 2.7.0(@types/react@19.1.5)(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-direction@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-direction@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-id@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-id@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.5)(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-remove-scroll: 2.7.0(@types/react@19.1.4)(react@19.1.0) + react-remove-scroll: 2.7.0(@types/react@19.1.5)(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.5)(react@19.1.0) '@radix-ui/rect': 1.1.1 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-remove-scroll: 2.7.0(@types/react@19.1.4)(react@19.1.0) + react-remove-scroll: 2.7.0(@types/react@19.1.5)(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-slot@1.2.3(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-slot@1.2.3(@types/react@19.1.5)(react@19.1.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.5)(react@19.1.0)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.5)(react@19.1.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: '@radix-ui/rect': 1.1.1 react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-use-size@1.1.1(@types/react@19.1.4)(react@19.1.0)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.5)(react@19.1.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.5)(react@19.1.0) react: 19.1.0 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 - '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) '@radix-ui/rect@1.1.1': {} + '@remirror/core-constants@3.0.0': {} + '@rollup/plugin-commonjs@28.0.3(rollup@4.40.1)': dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.40.1) @@ -3933,12 +4532,168 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.7 '@tailwindcss/oxide-win32-x64-msvc': 4.1.7 - '@tailwindcss/vite@4.1.7(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.7(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@tailwindcss/node': 4.1.7 '@tailwindcss/oxide': 4.1.7 tailwindcss: 4.1.7 - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) + + '@tiptap/core@2.12.0(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-blockquote@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-bold@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-bullet-list@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-code-block-lowlight@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-code-block@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(highlight.js@11.11.1)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/extension-code-block': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-code@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-document@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-dropcursor@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-gapcursor@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-hard-break@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-heading@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-highlight@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-history@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-horizontal-rule@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-italic@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-list-item@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-ordered-list@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-paragraph@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-placeholder@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 + + '@tiptap/extension-strike@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-text-style@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-text@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/extension-typography@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + + '@tiptap/pm@2.12.0': + dependencies: + prosemirror-changeset: 2.3.0 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.1 + prosemirror-inputrules: 1.5.0 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.2 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.1 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.7.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3) + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.3 + + '@tiptap/starter-kit@2.12.0': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/extension-blockquote': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-bold': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-bullet-list': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-code': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-code-block': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/extension-document': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-dropcursor': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/extension-gapcursor': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/extension-hard-break': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-heading': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-history': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/extension-horizontal-rule': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) + '@tiptap/extension-italic': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-list-item': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-ordered-list': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-paragraph': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-strike': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-text': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/extension-text-style': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) + '@tiptap/pm': 2.12.0 + + '@tiptap/suggestion@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)': + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@tiptap/pm': 2.12.0 '@types/babel__core@7.20.5': dependencies: @@ -3973,16 +4728,40 @@ snapshots: '@types/fontkit@2.0.8': dependencies: - '@types/node': 18.19.87 + '@types/node': 22.15.21 '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 + '@types/linkify-it@3.0.5': {} + + '@types/linkify-it@5.0.0': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.17 + + '@types/lodash@4.17.17': {} + + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@1.0.5': {} + + '@types/mdurl@2.0.0': {} + '@types/mdx@2.0.13': {} '@types/ms@2.1.0': {} @@ -3993,12 +4772,12 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 18.19.87 + '@types/node': 22.15.21 form-data: 4.0.2 '@types/node-forge@1.3.11': dependencies: - '@types/node': 18.19.87 + '@types/node': 22.15.21 '@types/node@17.0.45': {} @@ -4006,11 +4785,15 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/react-dom@19.1.5(@types/react@19.1.4)': + '@types/node@22.15.21': dependencies: - '@types/react': 19.1.4 + undici-types: 6.21.0 - '@types/react@19.1.4': + '@types/react-dom@19.1.5(@types/react@19.1.5)': + dependencies: + '@types/react': 19.1.5 + + '@types/react@19.1.5': dependencies: csstype: 3.1.3 @@ -4018,7 +4801,11 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 18.19.87 + '@types/node': 22.15.21 + + '@types/sortablejs@1.15.8': {} + + '@types/turndown@5.0.5': {} '@types/unist@2.0.11': {} @@ -4026,18 +4813,18 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-basic-ssl@2.0.0(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1))': + '@vitejs/plugin-basic-ssl@2.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) - '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1))': + '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color @@ -4084,12 +4871,12 @@ snapshots: astring@1.9.0: {} - astro@5.7.13(@types/node@18.19.87)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3): + astro@5.8.0(@types/node@22.15.21)(idb-keyval@6.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.1)(typescript@5.8.3): dependencies: '@astrojs/compiler': 2.11.0 '@astrojs/internal-helpers': 0.6.1 - '@astrojs/markdown-remark': 6.3.1 - '@astrojs/telemetry': 3.2.1 + '@astrojs/markdown-remark': 6.3.2 + '@astrojs/telemetry': 3.3.0 '@capsizecss/unpack': 2.4.0 '@oslojs/encoding': 1.1.0 '@rollup/pluginutils': 5.1.4(rollup@4.40.1) @@ -4116,6 +4903,7 @@ snapshots: github-slugger: 2.0.0 html-escaper: 3.0.3 http-cache-semantics: 4.1.1 + import-meta-resolve: 4.1.0 js-yaml: 4.1.0 kleur: 4.1.5 magic-string: 0.30.17 @@ -4138,8 +4926,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.16.0(idb-keyval@6.2.1) vfile: 6.0.3 - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) - vitefu: 1.0.6(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) + vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.2 @@ -4250,18 +5038,22 @@ snapshots: dependencies: clsx: 2.1.1 + classnames@2.3.1: {} + cli-boxes@3.0.0: {} clone@2.1.2: {} + clsx@1.2.1: {} + clsx@2.1.1: {} - cmdk@1.1.1(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + cmdk@1.1.1(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.5)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: @@ -4308,6 +5100,8 @@ snapshots: cookie@1.0.2: {} + crelt@1.0.6: {} + cross-fetch@3.2.0: dependencies: node-fetch: 2.7.0 @@ -4394,6 +5188,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + entities@4.5.0: {} + entities@6.0.0: {} es-define-property@1.0.1: {} @@ -4457,6 +5253,8 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} estree-util-attach-comments@3.0.0: @@ -4568,6 +5366,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + github-markdown-css@5.8.1: {} + github-slugger@2.0.0: {} globals@11.12.0: {} @@ -4726,6 +5526,8 @@ snapshots: property-information: 7.0.0 space-separated-tokens: 2.0.2 + highlight.js@11.11.1: {} + html-escaper@3.0.3: {} html-parse-stringify@3.0.1: @@ -4742,7 +5544,7 @@ snapshots: i18next-browser-languagedetector@8.1.0: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.27.1 i18next-http-backend@3.0.2: dependencies: @@ -4750,7 +5552,7 @@ snapshots: transitivePeerDependencies: - encoding - i18next@25.1.3(typescript@5.8.3): + i18next@25.2.0(typescript@5.8.3): dependencies: '@babel/runtime': 7.27.1 optionalDependencies: @@ -4758,6 +5560,10 @@ snapshots: idb-keyval@6.2.1: {} + idb@8.0.3: {} + + immer@10.1.1: {} + import-meta-resolve@4.1.0: {} inline-style-parser@0.2.4: {} @@ -4865,16 +5671,34 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + lodash-es@4.17.21: {} longest-streak@3.1.0: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@10.4.3: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lucide-react@0.487.0(react@19.1.0): + dependencies: + react: 19.1.0 + lucide-react@0.509.0(react@19.1.0): dependencies: react: 19.1.0 @@ -4895,8 +5719,25 @@ snapshots: markdown-extensions@2.0.0: {} + markdown-it-task-lists@2.1.1: {} + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + markdown-table@3.0.4: {} + marked-highlight@2.2.1(marked@15.0.12): + dependencies: + marked: 15.0.12 + + marked@15.0.12: {} + math-intrinsics@1.1.0: {} mdast-util-definitions@6.0.0: @@ -5070,6 +5911,8 @@ snapshots: mdn-data@2.12.2: {} + mdurl@2.0.0: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.1.0 @@ -5378,6 +6221,8 @@ snapshots: normalize-path@3.0.0: {} + object-assign@4.1.1: {} + ofetch@1.4.1: dependencies: destr: 2.0.5 @@ -5394,7 +6239,7 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 - openai@4.100.0(zod@3.24.3): + openai@4.100.0(ws@8.18.2)(zod@3.24.3): dependencies: '@types/node': 18.19.87 '@types/node-fetch': 2.6.12 @@ -5404,10 +6249,13 @@ snapshots: formdata-node: 4.4.1 node-fetch: 2.7.0 optionalDependencies: + ws: 8.18.2 zod: 3.24.3 transitivePeerDependencies: - encoding + orderedmap@2.1.1: {} + p-limit@6.2.0: dependencies: yocto-queue: 1.2.1 @@ -5462,6 +6310,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + pretty-bytes@7.0.0: {} + prismjs@1.30.0: {} prompts@2.4.2: @@ -5469,59 +6319,193 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + property-information@6.5.0: {} property-information@7.0.0: {} + prosemirror-changeset@2.3.0: + dependencies: + prosemirror-transform: 1.10.4 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.3 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.3 + + prosemirror-gapcursor@1.3.2: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.39.3 + + prosemirror-history@1.4.1: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.3 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.0: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.2: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.25.1 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.4.1 + prosemirror-state: 1.4.3 + + prosemirror-model@1.25.1: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-state@1.4.3: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.3 + + prosemirror-tables@1.7.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.3 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.39.3 + + prosemirror-transform@1.10.4: + dependencies: + prosemirror-model: 1.25.1 + + prosemirror-view@1.39.3: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + punycode.js@2.3.1: {} + radix3@1.1.2: {} + re-resizable@6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 scheduler: 0.26.0 + react-draggable@4.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-hook-form@7.56.4(react@19.1.0): dependencies: react: 19.1.0 - react-i18next@15.5.1(i18next@25.1.3(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3): + react-i18next@15.5.2(i18next@25.2.0(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3): dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.27.1 html-parse-stringify: 3.0.1 - i18next: 25.1.3(typescript@5.8.3) + i18next: 25.2.0(typescript@5.8.3) react: 19.1.0 optionalDependencies: react-dom: 19.1.0(react@19.1.0) typescript: 5.8.3 + react-is@16.13.1: {} + react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.1.4)(react@19.1.0): + react-remove-scroll-bar@2.3.8(@types/react@19.1.5)(react@19.1.0): dependencies: react: 19.1.0 - react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.5)(react@19.1.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - react-remove-scroll@2.7.0(@types/react@19.1.4)(react@19.1.0): + react-remove-scroll@2.7.0(@types/react@19.1.5)(react@19.1.0): dependencies: react: 19.1.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.1.4)(react@19.1.0) - react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0) + react-remove-scroll-bar: 2.3.8(@types/react@19.1.5)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.5)(react@19.1.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.1.4)(react@19.1.0) - use-sidecar: 1.1.3(@types/react@19.1.4)(react@19.1.0) + use-callback-ref: 1.3.3(@types/react@19.1.5)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.5)(react@19.1.0) optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - react-style-singleton@2.2.3(@types/react@19.1.4)(react@19.1.0): + react-sortablejs@6.1.4(@types/sortablejs@1.15.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sortablejs@1.15.6): + dependencies: + '@types/sortablejs': 1.15.8 + classnames: 2.3.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + sortablejs: 1.15.6 + tiny-invariant: 1.2.0 + + react-style-singleton@2.2.3(@types/react@19.1.5)(react@19.1.0): dependencies: get-nonce: 1.0.1 react: 19.1.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 react-toastify@11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: @@ -5563,8 +6547,6 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 - regenerator-runtime@0.14.1: {} - regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -5723,6 +6705,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.40.1 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + sax@1.4.1: {} scheduler@0.26.0: {} @@ -5790,6 +6774,8 @@ snapshots: smol-toml@1.3.4: {} + sortablejs@1.15.6: {} + source-map-js@1.2.1: {} source-map@0.7.4: {} @@ -5850,6 +6836,8 @@ snapshots: tiny-inflate@1.0.3: {} + tiny-invariant@1.2.0: {} + tinyexec@0.3.2: {} tinyglobby@0.2.13: @@ -5857,6 +6845,14 @@ snapshots: fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 + tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)): + dependencies: + '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) + '@types/markdown-it': 13.0.9 + markdown-it: 14.1.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.2 + tr46@0.0.3: {} trim-lines@3.0.1: {} @@ -5869,12 +6865,18 @@ snapshots: tslib@2.8.1: {} + turndown@7.2.0: + dependencies: + '@mixmark-io/domino': 2.2.0 + tw-animate-css@1.3.0: {} type-fest@4.40.1: {} typescript@5.8.3: {} + uc.micro@2.1.0: {} + ufo@1.6.1: {} ultrahtml@1.6.0: {} @@ -5883,6 +6885,8 @@ snapshots: undici-types@5.26.5: {} + undici-types@6.21.0: {} + unicode-properties@1.4.1: dependencies: base64-js: 1.5.1 @@ -5973,20 +6977,20 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - use-callback-ref@1.3.3(@types/react@19.1.4)(react@19.1.0): + use-callback-ref@1.3.3(@types/react@19.1.5)(react@19.1.0): dependencies: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - use-sidecar@1.1.3(@types/react@19.1.4)(react@19.1.0): + use-sidecar@1.1.3(@types/react@19.1.5)(react@19.1.0): dependencies: detect-node-es: 1.1.0 react: 19.1.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 vfile-location@5.0.3: dependencies: @@ -6003,17 +7007,17 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-remote-assets@2.0.0(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)): + vite-plugin-remote-assets@2.0.0(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)): dependencies: debug: 4.4.0 magic-string: 0.30.17 node-fetch-native: 1.6.6 ohash: 2.0.11 - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color - vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1): + vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1): dependencies: esbuild: 0.25.3 fdir: 6.4.4(picomatch@4.0.2) @@ -6022,17 +7026,19 @@ snapshots: rollup: 4.40.1 tinyglobby: 0.2.13 optionalDependencies: - '@types/node': 18.19.87 + '@types/node': 22.15.21 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 - vitefu@1.0.6(vite@6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1)): + vitefu@1.0.6(vite@6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)): optionalDependencies: - vite: 6.3.5(@types/node@18.19.87)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1) void-elements@3.1.0: {} + w3c-keyname@2.2.8: {} + web-namespaces@2.0.1: {} web-streams-polyfill@4.0.0-beta.3: {} @@ -6056,6 +7062,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 + ws@8.18.2: {} + xxhash-wasm@1.1.0: {} yallist@3.1.1: {} @@ -6083,9 +7091,10 @@ snapshots: zod@3.24.3: {} - zustand@5.0.4(@types/react@19.1.4)(react@19.1.0): + zustand@5.0.5(@types/react@19.1.5)(immer@10.1.1)(react@19.1.0): optionalDependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 + immer: 10.1.1 react: 19.1.0 zwitch@2.0.4: {} diff --git a/src/apps/ai-chat/chat-context/Dialog.tsx b/src/apps/ai-chat/chat-context/Dialog.tsx new file mode 100644 index 0000000..8987ed7 --- /dev/null +++ b/src/apps/ai-chat/chat-context/Dialog.tsx @@ -0,0 +1,124 @@ +import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx'; +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { Button } from '@/components/a/button.tsx'; +import { TextEditor } from '@kevisual/markdown-editor/tiptap/editor.ts'; +import { Select } from '@/components/a/select.tsx'; +import { useEffect, useRef, useState } from 'react'; +import { getSuggestionItems } from '../editor/suggestion/item'; +import { html2md } from '@kevisual/markdown-editor/tiptap/index.ts'; +import { chatId } from '../utils/uuid'; + +export const ChatContextDialog = () => { + const computedHeight = getComputedHeight(); + const { showContext, setShowContext, updateMessage, contextMessage, setContextMessage, setModelId, modelId } = useChatStore( + useShallow((state) => ({ + showContext: state.showContext, + setShowContext: state.setShowContext, + updateMessage: state.updateMessage, + contextMessage: state.contextMessage, + setContextMessage: state.setContextMessage, + setModelId: state.setModelId, + modelId: state.modelId, + })), + ); + + const ref = useRef(null); + const editorRef = useRef(null); + useEffect(() => { + if (ref.current) { + editorRef.current = new TextEditor(); + editorRef.current.createEditor(ref.current, { + items: getSuggestionItems(), + }); + } + return () => { + if (ref.current && editorRef.current) { + editorRef.current?.destroy?.(); + } + }; + }, []); + useEffect(() => { + if (showContext && contextMessage?.content) { + contentToEditor(contextMessage.content); + contextMessage?.role && setRole(contextMessage.role); + } else if (showContext && typeof contextMessage === 'undefined') { + editorRef.current?.setContent(''); + } + }, [showContext]); + const contentToEditor = async (content: string) => { + editorRef.current?.setContent(content); + }; + const onSave = async () => { + const hasId = contextMessage?.id; + const html = editorRef.current?.getHtml()!; + const md = await html2md(html); + const newMessage = { + ...contextMessage, + role: role, + id: hasId || chatId(), + content: md, + name: 'user', + }; + if (!hasId) { + newMessage.createdAt = new Date().getTime(); + } + newMessage.updatedAt = new Date().getTime(); + updateMessage(newMessage); + setShowContext(false); + }; + const [role, setRole] = useState('user'); + return ( + { + setModelId('chat-context'); + }} + onClose={() => { + setShowContext(false); + }} + /> + } + containerClassName={showContext ? '' : 'hidden'} + content={ +
+
+ 角色: + +
+
+ + ); +}; diff --git a/src/apps/ai-chat/chat-history/Dialog.tsx b/src/apps/ai-chat/chat-history/Dialog.tsx new file mode 100644 index 0000000..7d88711 --- /dev/null +++ b/src/apps/ai-chat/chat-history/Dialog.tsx @@ -0,0 +1,41 @@ +import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx'; +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { ChatHistoryList } from './List'; + +export const ChatHistoryDialog = ({ storeId }: { storeId: string }) => { + const { showList, setShowList, setModelId, modelId } = useChatStore( + useShallow((state) => ({ showList: state.showList, setShowList: state.setShowList, setModelId: state.setModelId, modelId: state.modelId })), + ); + const computedHeight = getComputedHeight(); + if (!showList) { + return null; + } + return ( + setShowList(false)} + onClick={() => { + setModelId('chat-history'); + }} + /> + } + content={ +
+ +
+ } + defaultSize={{ + width: 600, + height: 400, + }} + style={{ + left: computedHeight.width / 2 - 300, + top: computedHeight.height / 2 - 200, + }} + /> + ); +}; diff --git a/src/apps/ai-chat/chat-history/List.tsx b/src/apps/ai-chat/chat-history/List.tsx new file mode 100644 index 0000000..3102c1a --- /dev/null +++ b/src/apps/ai-chat/chat-history/List.tsx @@ -0,0 +1,117 @@ +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { IconButton, Button } from '@/components/a/button.tsx'; +import { Tooltip } from '@/components/a/tooltip'; +import { Edit, Plus, Trash, X } from 'lucide-react'; +import { Input as TextField } from '@/components/a/input'; +import { getHistoryState, setHistoryState } from '@kevisual/store/web-page.js'; +import clsx from 'clsx'; +import { Controller, useForm } from 'react-hook-form'; +import { Confirm } from '@/components/a/confirm'; + +export const ChatHistoryList = ({ storeId }: { storeId: string }) => { + const { historyList, setId, updateChat, deleteChat, setShowList, newChat, setNewChat } = useChatStore( + useShallow((state) => ({ + historyList: state.historyList, + setId: state.setId, + updateChat: state.updateChat, + deleteChat: state.deleteChat, + setShowList: state.setShowList, + newChat: state.newChat, + setNewChat: state.setNewChat, + })), + ); + const onChoose = (id: string) => { + setId(id); + setShowList(false); + setHistoryState({ + [storeId]: { + chatId: id, + }, + }); + const url = new URL(location.href); + url.searchParams.set('chatId', id); + setHistoryState({}, url.toString()); + }; + const { control, handleSubmit, reset, getValues } = useForm({ defaultValues: { title: '', id: '' } }); + const onFinish = async (data: any) => { + const res = await updateChat(data); + if (res.code === 200) { + setNewChat(false); + reset({}); + } + }; + const id = getValues('id'); + return ( +
+
+ + setNewChat(!newChat)}>{newChat ? : } + +
+
+
+ {historyList.map((item) => { + const chatState = getHistoryState()[storeId]; + const isCurrent = chatState?.chatId === item.id; + return ( +
+
{ + onChoose(item.id); + }}> + {item.title} +
+
+ + { + e.stopPropagation(); + setNewChat(true); + console.log('setNewChat', item.id); + reset({ + id: item.id, + title: item.title, + }); + }}> + + + + { + e.stopPropagation(); + deleteChat(item.id); + }}> + + + + +
+
+ ); + })} + {historyList.length === 0 &&
No chat history
} +
+
+
+ } /> +
+ + +
+ +
+
+
+ ); +}; diff --git a/src/apps/ai-chat/chat-model-setting/Dialog.tsx b/src/apps/ai-chat/chat-model-setting/Dialog.tsx new file mode 100644 index 0000000..46f346d --- /dev/null +++ b/src/apps/ai-chat/chat-model-setting/Dialog.tsx @@ -0,0 +1,46 @@ +import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx'; +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { ChatSettingList } from './List'; + +export const ChatModelSettingDialog = () => { + const { showChatSetting, setShowChatSetting, setModelId, modelId } = useChatStore( + useShallow((state) => ({ + showChatSetting: state.showChatSetting, + setShowChatSetting: state.setShowChatSetting, + setModelId: state.setModelId, + modelId: state.modelId, + })), + ); + const computedHeight = getComputedHeight(); + if (!showChatSetting) { + return null; + } + return ( + setShowChatSetting(false)} + onClick={() => { + setModelId('chat-model-setting'); + }} + /> + } + content={ +
+ +
+ } + defaultSize={{ + width: 600, + height: 400, + }} + style={{ + left: computedHeight.width / 2 - 300, + top: computedHeight.height / 2 - 200, + }} + /> + ); +}; diff --git a/src/apps/ai-chat/chat-model-setting/List.tsx b/src/apps/ai-chat/chat-model-setting/List.tsx new file mode 100644 index 0000000..c0df9b6 --- /dev/null +++ b/src/apps/ai-chat/chat-model-setting/List.tsx @@ -0,0 +1,67 @@ +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { IconButton } from '@/components/a/button.tsx' +import { CircleCheckBig, Edit, LassoSelectIcon } from 'lucide-react'; +import clsx from 'clsx'; +import { Tooltip } from '@/components/a/tooltip.tsx' + +export const ChatSettingList = () => { + const { currentUserModel, setCurrentUserModel, modelList, showChatSetting, setShowChatSetting } = useChatStore( + useShallow((state) => ({ + currentUserModel: state.currentUserModel, + setCurrentUserModel: state.setCurrentUserModel, + modelList: state.modelList, + showChatSetting: state.showChatSetting, + setShowChatSetting: state.setShowChatSetting, + })), + ); + + return ( +
+ {modelList?.map?.((model, index) => { + const selectOpts = model.selectOpts || ([] as { group: string; provider: string; model: string }[]); + const isCurrentUserModel = currentUserModel?.username === model.username; + const isLastModel = index === modelList.length - 1; + return ( +
+
+ +
user: {model.username}
+
+
+ {selectOpts.map((opt, index) => { + const isLast = index === selectOpts.length - 1; + const isSelected = isCurrentUserModel && currentUserModel?.model?.group === opt.group && currentUserModel?.model?.model === opt.model; + return ( +
+
+
group: {opt.group}
+
provider: {opt.provider}
+
model: {opt.model}
+ {opt.description &&
{opt.description}
} +
+
+ + { + setCurrentUserModel({ + username: model.username, + model: opt, + }); + setShowChatSetting(false); + }}> + + + +
+
+ ); + })} +
+ ); + })} +
+ ); +}; diff --git a/src/apps/ai-chat/chat-setting/Dialog.tsx b/src/apps/ai-chat/chat-setting/Dialog.tsx new file mode 100644 index 0000000..2add8c6 --- /dev/null +++ b/src/apps/ai-chat/chat-setting/Dialog.tsx @@ -0,0 +1,46 @@ +import { DragModal, DragModalTitle, getComputedHeight } from '@/components/a/drag-modal/index.tsx'; +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { ChatSettingList } from './List'; + +export const ChatSettingDialog = () => { + const { showSetting, setShowSetting, setModelId, modelId } = useChatStore( + useShallow((state) => ({ + showSetting: state.showSetting, + setShowSetting: state.setShowSetting, + setModelId: state.setModelId, + modelId: state.modelId, + })), + ); + const computedHeight = getComputedHeight(); + if (!showSetting) { + return null; + } + return ( + setShowSetting(false)} + onClick={() => { + setModelId('chat-setting'); + }} + /> + } + content={ +
+ +
+ } + defaultSize={{ + width: 600, + height: 400, + }} + style={{ + left: computedHeight.width / 2 - 300, + top: computedHeight.height / 2 - 200, + }} + /> + ); +}; diff --git a/src/apps/ai-chat/chat-setting/List.tsx b/src/apps/ai-chat/chat-setting/List.tsx new file mode 100644 index 0000000..5d2f781 --- /dev/null +++ b/src/apps/ai-chat/chat-setting/List.tsx @@ -0,0 +1,136 @@ +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { IconButton } from '@/components/a/button.tsx'; +import { BookmarkX, CircleCheckBig, Copy, Edit, LassoSelectIcon, RefreshCcw, Trash } from 'lucide-react'; +import clsx from 'clsx'; +import { Divider } from '@/components/a/divider.tsx' +import { Tooltip } from '@/components/a/tooltip.tsx' +import { useEffect, useState } from 'react'; +import { queryChat } from '../store'; +import { toast } from 'react-toastify'; +import prettyBytes from 'pretty-bytes'; +import { copyText } from '../utils/copy'; +export type SelfUsage = { + group: string; + model: string; + token: number; + [key: string]: any; +}; +export type UsageData = { + rootUsage?: { + token?: number; + }; + selfUsage?: SelfUsage[]; +}; +export const ChatSettingList = () => { + const store = useChatStore( + useShallow((state) => ({ + currentUserModel: state.currentUserModel, + setCurrentUserModel: state.setCurrentUserModel, + modelList: state.modelList, + clearConfigCache: state.clearConfigCache, + showSetting: state.showSetting, + getModelList: state.getModelList, + messages: state.messages, + })), + ); + const [usageData, setUsageData] = useState({}); + useEffect(() => { + if (store.showSetting) { + getUsage(); + } + }, [store.showSetting]); + const getUsage = async () => { + const res = await queryChat.getChatUsage(); + console.log(res); + if (res.code === 200) { + setUsageData(res.data); + } else { + toast.error(res.message); + } + }; + const clearSelfUsage = async () => { + const res = await queryChat.clearSelfUsage(); + if (res.code === 200) { + toast.success(res.message); + } else { + toast.error(res.message); + } + }; + return ( +
+
+ + store.getModelList(true)}> + + + + + + { + console.log(store.messages); + copyText(JSON.stringify(store.messages)); + }}> + + + + + + { + store.clearConfigCache(); + }}> + + + + + { + clearSelfUsage(); + }}> + + + +
+ +
+
使用root的模型统计: token: {prettyBytes(usageData.rootUsage?.token || 0)}
+
+ 自己模型使用统计 +
+ {usageData.selfUsage?.map((item) => { + const keys = Object.keys(item); + const defaultKeys = ['model', 'token', 'group']; + const days = keys.filter((key) => !defaultKeys.includes(key)); + return ( +
+
+
group: {item.group}
+
+
{item.model} +
+
token: {prettyBytes(item.token)}
+
+ {days.map((day) => { + return ( +
+ {day}: {item[day] || 0} 次 +
+ ); + })} +
+
+
+ ); + })} +
+
+
+
+ ); +}; diff --git a/src/apps/ai-chat/components/RenderMarkdown.tsx b/src/apps/ai-chat/components/RenderMarkdown.tsx new file mode 100644 index 0000000..5aeec22 --- /dev/null +++ b/src/apps/ai-chat/components/RenderMarkdown.tsx @@ -0,0 +1,115 @@ +import { throttle } from 'lodash-es'; +import { Marked } from 'marked'; +import 'highlight.js/styles/github.css'; // 你可以选择其他样式 +import { useRef, useEffect } from 'react'; + +import { markedHighlight } from 'marked-highlight'; +import hljs from 'highlight.js'; +import clsx from 'clsx'; + +const marked = new Marked( + markedHighlight({ + emptyLangClass: 'hljs', + langPrefix: 'hljs language-', + highlight(code, lang, info) { + const language = hljs.getLanguage(lang) ? lang : 'plaintext'; + return hljs.highlight(code, { language }).value; + }, + }), +); + +type ResponseTextProps = { + response: Response; + onFinish?: (text: string) => void; + onChange?: (text: string) => void; + className?: string; + id?: string; +}; +export const ResponseText = (props: ResponseTextProps) => { + const ref = useRef(null); + const render = async () => { + const response = props.response; + if (!response) return; + const msg = ref.current!; + if (!msg) { + console.log('msg is null'); + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + const reader = response.body?.getReader(); + const decoder = new TextDecoder('utf-8'); + let done = false; + + while (!done) { + const { value, done: streamDone } = await reader!.read(); + done = streamDone; + + if (value) { + const chunk = decoder.decode(value, { stream: true }); + // 更新状态,实时刷新 UI + msg.innerHTML += chunk; + } + } + if (done) { + props.onFinish && props.onFinish(msg.innerHTML); + } + }; + useEffect(() => { + render(); + }, []); + return
; +}; + +export const ResponseMarkdown = (props: ResponseTextProps) => { + const ref = useRef(null); + let content = ''; + const render = async () => { + const response = props.response; + if (!response) return; + const msg = ref.current!; + if (!msg) { + console.log('msg is null'); + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + const reader = response.body?.getReader(); + const decoder = new TextDecoder('utf-8'); + let done = false; + + while (!done) { + const { value, done: streamDone } = await reader!.read(); + done = streamDone; + + if (value) { + const chunk = decoder.decode(value, { stream: true }); + content = content + chunk; + renderThrottle(content); + } + } + if (done) { + props.onFinish && props.onFinish(msg.innerHTML); + } + }; + const renderThrottle = throttle(async (markdown: string) => { + const msg = ref.current!; + msg.innerHTML = await marked.parse(markdown); + props.onChange?.(msg.innerHTML); + }, 100); + useEffect(() => { + render(); + }, [props.response]); + return
; +}; + +export const Markdown = (props: { className?: string; markdown: string; id?: string }) => { + const ref = useRef(null); + const parse = async () => { + const md = await marked.parse(props.markdown); + const msg = ref.current!; + msg.innerHTML = md; + }; + useEffect(() => { + parse(); + }, []); + return
; +}; diff --git a/src/apps/ai-chat/editor/Editor.tsx b/src/apps/ai-chat/editor/Editor.tsx new file mode 100644 index 0000000..0999f88 --- /dev/null +++ b/src/apps/ai-chat/editor/Editor.tsx @@ -0,0 +1,18 @@ +import { TextEditor } from '@kevisual/markdown-editor/tiptap/editor.ts'; + +import { useEffect, useRef } from 'react'; +import { getSuggestionItems } from './suggestion/item'; + +export const MarkdownEditor = ({ onSave }: { onSave: () => void }) => { + const ref = useRef(null); + useEffect(() => { + if (ref.current) { + const editor = new TextEditor(); + editor.createEditor(ref.current, { + markdown: '## Hello World', + items: getSuggestionItems(), + }); + } + }, []); + return
; +}; diff --git a/src/apps/ai-chat/editor/suggestion/item.ts b/src/apps/ai-chat/editor/suggestion/item.ts new file mode 100644 index 0000000..3ab174c --- /dev/null +++ b/src/apps/ai-chat/editor/suggestion/item.ts @@ -0,0 +1,25 @@ +export interface CommandItem { + title: string; + description: string; + content: string; +} + +export const getSuggestionItems = (): CommandItem[] => { + return [ + { + title: 'Now', + description: 'Insert current time', + content: new Date().toLocaleTimeString(), + }, + { + title: 'Today', + description: "Insert today's date", + content: new Date().toLocaleDateString(), + }, + { + title: 'Datetime', + description: 'Insert current date and time', + content: new Date().toLocaleString(), + }, + ]; +}; diff --git a/src/apps/ai-chat/index.css b/src/apps/ai-chat/index.css new file mode 100644 index 0000000..f5f6728 --- /dev/null +++ b/src/apps/ai-chat/index.css @@ -0,0 +1,14 @@ +@import 'tailwindcss'; +@import '@kevisual/markdown-editor/index.css'; + +.sortable-swap-highlight { + border: 1px solid red; + background-color: aquamarine; +} +.sortable-ghost { + @apply bg-gray-200 border border-gray-400 shadow-sm rounded-lg; +} + +.sortable-drag { + @apply border bg-gray-500 border-gray-200 shadow-sm rounded-lg text-white; +} diff --git a/src/apps/ai-chat/index.tsx b/src/apps/ai-chat/index.tsx new file mode 100644 index 0000000..cbc7760 --- /dev/null +++ b/src/apps/ai-chat/index.tsx @@ -0,0 +1,115 @@ +import { StoreContextProvider, useStore } from '@kevisual/store/react'; +import { useShallow } from 'zustand/shallow'; +import { createChatStore, useChatStore } from './store'; +import { Messages } from './modules/MessageList'; +import { useEffect, useLayoutEffect } from 'react'; +import { ModelNav } from './modules/ModelNav'; +import { Menu } from 'lucide-react'; +import { IconButton } from '@/components/a/button.tsx'; +import { ChatHistoryDialog } from './chat-history/Dialog'; +import { getHistoryState } from '@kevisual/store/web-page.js'; +import { ChatContextDialog } from './chat-context/Dialog'; +import { ChatModelSettingDialog } from './chat-model-setting/Dialog'; +import { ChatSettingDialog } from './chat-setting/Dialog'; +import { ChatCopyDialog } from './chat-copy/Dialog'; +import { ToastProvider } from '@/modules/toast/Provider'; +import clsx from 'clsx'; +import { ChatHistoryList } from './chat-history/List'; +import './index.css'; + +type AppProps = { + storeId?: string; +}; +export const App = (props: AppProps) => { + const storeId = props.storeId || 'ai-chat-store'; + useLayoutEffect(() => { + // const token = localStorage.getItem('token'); + // if (!token) { + // toastLogin(); + // } + }, []); + return ( + + + + + + ); +}; + +export const Chat = ({ storeId }: { storeId: string }) => { + const { id, init, messages, showList, setShowList, setId, getChatList, chatData } = useChatStore( + useShallow((state) => { + return { + id: state.id, + init: state.init, + messages: state.messages, + showList: state.showList, + setShowList: state.setShowList, + setId: state.setId, + getChatList: state.getChatList, + chatData: state.chatData, + }; + }), + ); + useEffect(() => { + if (showList) { + getChatList(); + } + }, [showList]); + useEffect(() => { + init(id); + }, [id]); + useEffect(() => { + const chat = getHistoryState()[storeId]; + const chatId = chat?.chatId; + const url = new URL(window.location.href); + const urlChatId = url.searchParams.get('chatId'); + if (chatId) { + setId(chatId); + } else if (urlChatId) { + setId(urlChatId); + } else { + getChatList(); + } + }, []); + + return ( +
+ {!id && ( +
+ +
+ )} +
+ setShowList(!showList)}> + + + +
+ +
+
+
+
{chatData?.title}
+ +
+
+
+ + + + + +
+ ); +}; diff --git a/src/apps/ai-chat/modules/MessageList.tsx b/src/apps/ai-chat/modules/MessageList.tsx new file mode 100644 index 0000000..0960d6a --- /dev/null +++ b/src/apps/ai-chat/modules/MessageList.tsx @@ -0,0 +1,199 @@ +import { ResponseMarkdown, Markdown } from '@/components/ai/RenderMarkdown'; +import clsx from 'clsx'; +import { Actions } from './actions/Actions'; +import { ChastHistoryMessage } from '../store/type'; +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { IconButton } from '@/components/a/button'; +import { Eraser, Move, Plus } from 'lucide-react'; +import { Tooltip } from '@/components/a/tooltip'; +import { ReactSortable } from 'react-sortablejs'; +import { useEffect, useMemo } from 'react'; + +export type MessageData = { + content: string; + role: 'user' | 'assistant' | 'system'; + response?: Response | null; + className?: string; + onFinish?: (text: string) => void; + // onFinish之前,每200ms调用一次 + onChange?: (text: string) => void; + id?: string; + randomId?: string; +}; +export type MessageProps = { data: ChastHistoryMessage } & MessageData; +export const AssistantMessage = (props: MessageProps) => { + if (props.response) { + return ; + } + return ; +}; + +export const Message = (props: MessageProps) => { + const isUser = props.role === 'user'; + const isHide = !!props.data.hide; + const isNoUse = !!props.data.noUse; + const { showAllContext, updateMessage } = useChatStore( + useShallow((state) => { + return { + showAllContext: state.showAllContext, + updateMessage: state.updateMessage, + }; + }), + ); + if (!showAllContext && isHide) { + // return
; + return null; + } + const isNotShow = !showAllContext && isHide; + const isContext = !isHide && !isNoUse; + const onNoUseChange = () => { + const data = props.data; + const newData = { ...data, noUse: !data.noUse }; + updateMessage(newData); + }; + // const imgUrl = '/root/center/panda.jpg' + const imgUrl = 'https://kevisual.xiongxiao.me/root/center/panda.jpg'; + return ( +
+
+
+
+
+
{ + onNoUseChange(); + }}> +
+ +
+ {props.role} +
+
+
+ +
+ {props.role} +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+ ); +}; +export const Messages = (props: { messages: ChastHistoryMessage[] }) => { + const { messages } = props; + const { setMessages, setContextMessage, setShowContext, showAllContext } = useChatStore( + useShallow((state) => { + return { + showAllContext: state.showAllContext, + setMessages: state.setMessages, + setContextMessage: state.setContextMessage, + setShowContext: state.setShowContext, + }; + }), + ); + + const onEraser = () => { + const newMessages = messages.map((message) => { + return { + ...message, + noUse: true, + }; + }); + setMessages([...newMessages]); + }; + const list = messages.filter((message) => { + const isHide = !!message.hide; + return !(!showAllContext && isHide); + }); + const reactSortable = useMemo(() => { + return ( + {}} + onEnd={(event) => { + const { newIndex, oldIndex } = event; + const newMessages = [...messages]; + // @ts-ignore + const _oldIndex = messages.findIndex((item) => item.id === list[oldIndex].id); + // @ts-ignore + const _newIndex = messages.findIndex((item) => item.id === list[newIndex].id); + const tmp = newMessages[_oldIndex]; + newMessages[_oldIndex] = newMessages[_newIndex]; + newMessages[_newIndex] = tmp; + setMessages(newMessages); + }} + direction={'vertical'} + swap={true} + chosenClass='sortable-chosen' + dragClass='sortable-drag' + swapThreshold={0.6} + animation={200}> + {messages.map((message) => { + const id = message.id; + const onChange = (text: string) => {}; + const onFinish = (text: string) => {}; + return ( + + ); + })} + + ); + }, [messages, list]); + return ( + <> + {reactSortable} +
+ + { + setContextMessage(undefined); + setShowContext(true); + }}> + + + + + + + + +
+ + ); +}; diff --git a/src/apps/ai-chat/modules/ModelNav.tsx b/src/apps/ai-chat/modules/ModelNav.tsx new file mode 100644 index 0000000..b4b0fc3 --- /dev/null +++ b/src/apps/ai-chat/modules/ModelNav.tsx @@ -0,0 +1,118 @@ +import { Copy, Eye, EyeOff, Menu, Plus, Save, Send, Settings, Trash } from 'lucide-react'; +import { useChatStore } from '../store'; +import { useShallow } from 'zustand/shallow'; +import { IconButton } from '@/components/a/button.tsx'; +import { Tooltip } from '@/components/a/tooltip.tsx'; +import { Divider } from '@/components/a/divider'; + +export const ModelNav = () => { + const { + id, + messages, + showAllContext, + setShowAllContext, + setShowContext, + setContextMessage, + updateChat, + currentUserModel, + setShowChatSetting, + chat, + setShowSetting, + setShowCopy, + } = useChatStore( + useShallow((state) => { + return { + id: state.id, + messages: state.messages, + showAllContext: state.showAllContext, + setShowAllContext: state.setShowAllContext, + showContext: state.showContext, + setShowContext: state.setShowContext, + setContextMessage: state.setContextMessage, + updateChat: state.updateChat, + currentUserModel: state.currentUserModel, + setShowChatSetting: state.setShowChatSetting, + chat: state.chat, + setShowSetting: state.setShowSetting, + setShowCopy: state.setShowCopy, + }; + }), + ); + return ( +
+
提示词规划器
+
setShowChatSetting(true)}> + {currentUserModel && ( + + 使用 [ {currentUserModel.username} ] 用户的对话模型 +
Group: {currentUserModel.model?.group} +
Provider: {currentUserModel.model?.provider} +
Model: {currentUserModel.model?.model} +
+ }> +
+
{currentUserModel.model?.model}
+
+ + )} + {!currentUserModel &&
Not Selected Model
} +
+
+ {/* + { + setShowSetting(true); + }}> + + + */} + + setShowAllContext(!showAllContext)}> + {showAllContext ? : } + + + + { + setShowCopy(true); + }}> + + + + + { + setContextMessage(undefined); + setShowContext(true); + }}> + + + + + + { + updateChat({ + id: id, + data: { + messages: messages, + }, + }); + }}> + + + + + { + chat(); + }}> + + + +
+
+ ); +}; diff --git a/src/apps/ai-chat/modules/actions/Actions.tsx b/src/apps/ai-chat/modules/actions/Actions.tsx new file mode 100644 index 0000000..f0f6a9e --- /dev/null +++ b/src/apps/ai-chat/modules/actions/Actions.tsx @@ -0,0 +1,71 @@ +import { useChatStore } from '@/apps/ai-chat/store'; +import { MessageProps } from '../MessageList'; +import { Edit, EyeOff, Eye, Trash, MonitorOff, Monitor, Copy } from 'lucide-react'; +import { useShallow } from 'zustand/shallow'; +import { ChastHistoryMessage } from '@/apps/ai-chat/store/type'; +import clsx from 'clsx'; +import { Tooltip } from '@/components/a/tooltip'; +import { copyText } from '@/apps/ai-chat/utils/copy'; +import { Confirm } from '@/components/a/confirm'; +type ActionsProps = { data: ChastHistoryMessage }; +export const Actions = (props: ActionsProps) => { + const { data } = props; + + const { deleteMessage, updateMessage, setContextMessage, contextMessage, setShowContext } = useChatStore( + useShallow((state) => { + return { + deleteMessage: state.deleteMessage, + updateMessage: state.updateMessage, + setContextMessage: state.setContextMessage, + contextMessage: state.contextMessage, + setShowContext: state.setShowContext, + }; + }), + ); + const onEyeChange = () => { + const newData = { ...data, hide: !data.hide }; + updateMessage(newData); + }; + const onDelete = () => { + data.id && deleteMessage(data.id); + }; + const onNoUseChange = () => { + const newData = { ...data, noUse: !data.noUse }; + updateMessage(newData); + }; + const isEyeHide = data.hide; + const isNoUse = data.noUse; + const EyeIcon = isEyeHide ? Eye : EyeOff; + const MonitorIcon = isNoUse ? Monitor : MonitorOff; + return ( +
+ + { + setContextMessage(data); + setShowContext(true); + }} + /> + + + copyText(data.content)} /> + + + + + + + + + { + onDelete(); + }}> + + + +
+ ); +}; diff --git a/src/apps/ai-chat/query/chat.ts b/src/apps/ai-chat/query/chat.ts new file mode 100644 index 0000000..f096990 --- /dev/null +++ b/src/apps/ai-chat/query/chat.ts @@ -0,0 +1,153 @@ +import { query } from '@/modules/query'; +import { Query } from '@kevisual/query'; +import { DataOpts } from '@kevisual/query/query'; + +export class QueryChat { + query: Query; + + constructor({ query }: { query: Query }) { + this.query = query; + } + + getChatList(opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'get-chat-list', + }, + opts, + ); + } + + getChat(id: string, opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'get-chat', + data: { + id, + }, + }, + opts, + ); + } + updateChat(data: any, opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'update-chat', + data, + }, + opts, + ); + } + + deleteChat(id: string, opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'delete-chat', + data: { + id, + }, + }, + opts, + ); + } + /** + * 获取模型列表 + * @param opts + * @returns + */ + getModelList(data?: { usernames?: string[] }, opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'get-model-list', + data, + }, + opts, + ); + } + /** + * 聊天对话模型 + * @param data + * @param chatOpts + * @param opts + * @returns + */ + chat(data: ChatDataOpts, chatOpts: ChatOpts, opts?: DataOpts) { + const { username, model, group, getFull = true } = chatOpts; + if (!username || !model || !group) { + throw new Error('username, model, group is required'); + } + return this.query.post( + { + path: 'ai', + key: 'chat', + ...chatOpts, + getFull, + data, + }, + opts, + ); + } + clearConfigCache(opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'clear-cache', + }, + opts, + ); + } + /** + * 获取聊天使用情况 + * @param opts + * @returns + */ + getChatUsage(opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'get-chat-usage', + }, + opts, + ); + } + + /** + * 清除当前用户模型自己的统计 + * @param opts + * @returns + */ + clearSelfUsage(opts?: DataOpts) { + return this.query.post( + { + path: 'ai', + key: 'clear-chat-limit', + }, + opts, + ); + } +} +export type ChatDataOpts = { + id?: string; + title?: string; + messages?: any[]; + data?: any; + type?: 'temp' | 'keep' | string; +}; +export type ChatOpts = { + username: string; + model: string; + /** + * 获取完整消息回复 + */ + getFull?: boolean; + group: string; + /** + * openai的参数 + */ + options?: any; +}; diff --git a/src/apps/ai-chat/store/demo.ts b/src/apps/ai-chat/store/demo.ts new file mode 100644 index 0000000..2bbd1e0 --- /dev/null +++ b/src/apps/ai-chat/store/demo.ts @@ -0,0 +1,61 @@ +import { ChastHistoryMessage } from './type'; + +export const messagesDemo1: ChastHistoryMessage[] = [ + { + role: 'user', + content: '你好', + name: '用户', + }, + { + role: 'assistant', + content: '你好,我是AI助手,很高兴认识你!', + name: 'AI', + }, +]; + +export const messagesDemo2: ChastHistoryMessage[] = [ + { + role: 'user', + content: '你能帮我解释一下什么是人工智能吗?', + name: '用户', + }, + { + role: 'assistant', + content: + '人工智能(AI)是计算机科学的一个分支,致力于创建能够模拟人类智能行为的系统。这些系统可以学习、推理、感知环境、理解语言并解决问题。目前常见的AI应用包括语音助手、推荐系统、自动驾驶和自然语言处理等。需要了解更具体的AI领域吗?', + name: 'AI助手', + }, + { + role: 'user', + content: '机器学习和深度学习有什么区别?', + name: '用户', + }, + { + role: 'assistant', + content: + '机器学习和深度学习的主要区别是:\n\n1. **机器学习**是AI的一个子集,使用统计方法让计算机系统通过数据"学习"并改进性能,无需明确编程。常见的机器学习算法包括决策树、支持向量机和随机森林等。\n\n2. **深度学习**是机器学习的一个子集,使用多层神经网络(称为深度神经网络)来模拟人脑的工作方式。它能自动从大量数据中提取特征,特别适合处理图像识别、自然语言处理等复杂任务。\n\n简单来说,深度学习是机器学习的一种高级形式,通过更复杂的模型结构处理更庞大的数据集。', + name: 'AI助手', + }, + { + role: 'user', + content: '你能给我写一个简单的Python函数来计算斐波那契数列吗?', + name: '用户', + }, + { + role: 'assistant', + content: + '当然,这里有两种计算斐波那契数列的Python函数:\n\n```python\n# 递归方法(简单但效率较低)\ndef fibonacci_recursive(n):\n if n <= 0:\n return 0\n elif n == 1:\n return 1\n else:\n return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)\n\n# 迭代方法(更高效)\ndef fibonacci_iterative(n):\n if n <= 0:\n return 0\n elif n == 1:\n return 1\n \n a, b = 0, 1\n for _ in range(2, n+1):\n a, b = b, a + b\n return b\n```\n\n迭代方法在处理较大的n时效率更高,因为它避免了递归方法中的重复计算问题。', + name: 'AI助手', + }, + { + role: 'user', + content: '谢谢你的帮助!今天的天气真不错。', + name: '用户', + }, + { + role: 'assistant', + content: + '不客气,很高兴能帮到你!确实,好天气总能让人心情愉快。如果你有任何其他问题,无论是关于编程、AI还是其他话题,都欢迎随时向我咨询。祝你度过美好的一天!', + name: 'AI助手', + }, +]; diff --git a/src/apps/ai-chat/store/generte-text.ts b/src/apps/ai-chat/store/generte-text.ts new file mode 100644 index 0000000..d37e8e7 --- /dev/null +++ b/src/apps/ai-chat/store/generte-text.ts @@ -0,0 +1,91 @@ +/** + * message type 是 {role: 'user' | 'assistant' | 'system', content: string, hide: boolean, noUse: boolean} + * @param messages + * @returns + */ +export const generateTextPrompt = (messages) => { + const newMessages = [...messages].filter((message) => !message.hide && !message.noUse) as any; + const lastReverseUserMessageIndex = newMessages.findLastIndex((message) => message.role === 'user'); + + const text = newMessages + .slice(0, lastReverseUserMessageIndex) + .map((message) => { + return `----\n${message.role}: \n ${message.content}\n----`; + }) + .join('\n'); + let prompt = ` +你是一个ai对话的助手,请根据用户的问题和用户的历史对话,给出最合适的回答。 + +用户的问题是:\n +${newMessages[lastReverseUserMessageIndex].content} + +用户的历史对话是: +${text} \n + `; + return prompt; +}; + +/** + * message type 是 {role: 'user' | 'assistant' | 'system', content: string, hide: boolean, noUse: boolean} + * @param messages + * @returns + */ +export const generateText = (messages) => { + const newMessages = [...messages].filter((message) => !message.hide && !message.noUse); + + const text = newMessages + .map((message) => { + return '----' + message.role + '\n' + message.content + '\n----'; + }) + .join('\n'); + + return text; +}; + +export const getCodeTemplate = () => { + return `/** + * message type 是 {role: 'user' | 'assistant' | 'system', content: string, hide: boolean, noUse: boolean} + * @param messages + * @returns + */ +export const generateText = (messages) => { + const newMessages = [...messages].filter((message) => !message.hide && !message.noUse); + + const text = newMessages + .map((message) => { + return '----' + message.role + '\\n' + message.content + '\\n----'; + }) + .join('\\n'); + + return text; +}; + `; +}; + +export const runCode = (code) => { + // 创建一个Blob对象,包含要执行的代码 + const blob = new Blob([code], { type: 'application/javascript' }); + + // 将代码转换为URL + const codeUrl = URL.createObjectURL(blob); + + try { + // 使用动态import导入代码 + return import(/* @vite-ignore */ codeUrl) + .then((module) => { + // 导入成功后释放URL + console.log('module', module); + URL.revokeObjectURL(codeUrl); + return module; + }) + .catch((error) => { + // 发生错误时也释放URL + URL.revokeObjectURL(codeUrl); + throw new Error(`代码执行失败: ${error.message}`); + }); + } catch (error: any) { + // 捕获任何其他错误 + URL.revokeObjectURL(codeUrl); + throw new Error(`无法导入代码: ${error.message}`); + } +}; diff --git a/src/apps/ai-chat/store/index.ts b/src/apps/ai-chat/store/index.ts new file mode 100644 index 0000000..f428ffc --- /dev/null +++ b/src/apps/ai-chat/store/index.ts @@ -0,0 +1,313 @@ +import { StoreManager } from '@kevisual/store'; +import { useContextKey } from '@kevisual/store/context'; +import { StateCreator } from 'zustand'; +import { query } from '@/modules/query'; +import { useStore, BoundStore } from '@kevisual/store/react'; +import { toast } from 'react-toastify'; +import { ChastHistoryMessage } from './type'; +import { QueryChat } from '../query/chat'; +import { CacheStore } from '@kevisual/cache/cache-store'; +import { QueryMark } from '@/query/query-mark/query-mark'; +export const queryMark = new QueryMark({ query: query, markType: 'chat' }); +export const queryChat = new QueryChat({ query }); + +const dbName = 'chat'; +export const store = useContextKey('store', () => { + return new StoreManager(); +}); + +type ChatStore = { + id: string; + setId: (id: string) => void; + loading: boolean; + setLoading: (loading: boolean) => void; + messages: ChastHistoryMessage[]; + updateTime: number; + setMessages: (messages: ChastHistoryMessage[], needCache?: boolean) => void; + + init: (id: string) => void; + /** + * 删除消息 + * @param index 消息索引 + */ + deleteMessage: (id: string) => void; + /** + * 清空消息 + */ + clearMessage: () => void; + /** + * 更新消息 + * @param message 消息 + */ + updateMessage: (message: ChastHistoryMessage) => void; + + showAllContext: boolean; + setShowAllContext: (showAllContext: boolean) => void; + + showChatSetting: boolean; + setShowChatSetting: (showChatSetting: boolean) => void; + + showList: boolean; + setShowList: (showList: boolean) => void; + newChat: boolean; + setNewChat: (newChat: boolean) => void; + + historyList: any[]; + setHistoryList: (historyList: any[]) => void; + + chatData: any; + setChatData: (chatData: any) => void; + + getChatList: () => Promise; + updateChat: (data: any) => Promise; + deleteChat: (id: string) => Promise; + + // 上下文对话框编辑 + showContext: boolean; + setShowContext: (showContext: boolean) => void; + contextMessage?: ChastHistoryMessage; + setContextMessage: (contextMessage?: ChastHistoryMessage) => void; + + currentUserModel?: { username: string; model: { group: string; provider: string; model: string } }; + setCurrentUserModel: (currentUserModel: { username: string; model: { group: string; provider: string; model: string } }) => void; + modelList: any[]; + setModelList: (modelList: any[]) => void; + getModelList: (force?: boolean) => Promise; + chat: () => Promise; + clearConfigCache: () => Promise; + + modelId: string; + setModelId: (modelId: string) => void; + + showSetting: boolean; + setShowSetting: (showSetting: boolean) => void; + + showCopy: boolean; + setShowCopy: (showCopy: boolean) => void; +}; +export const createChatStore: StateCreator = (set, get, store) => { + return { + id: '', + setId: (id: string) => set(() => ({ id })), + loading: false, + setLoading: (loading: boolean) => set(() => ({ loading })), + updateTime: 0, + messages: [], + setMessages: (messages: ChastHistoryMessage[], needCache = true) => { + const id = get().id; + if (needCache) { + const chatData = get().chatData; + if (chatData) { + const data = chatData.data || {}; + data.messages = messages; + chatData.data = data; + const cache = new CacheStore({ dbName }); + cache.set(id, chatData); + } + } + const now = Date.now(); + set(() => ({ updateTime: now })); + set({ messages }); + }, + + init: async (id: string) => { + if (!id) { + return; + } + // clear default + const { setMessages } = get(); + setMessages([], false); + + const { getModelList, setCurrentUserModel } = get(); + await getModelList(); + const cache = new CacheStore({ dbName }); + const chatData = await cache.get(id); + const currentUserModel = await cache.get('currentUserModel'); + if (currentUserModel) { + setCurrentUserModel(currentUserModel); + } + if (chatData) { + const messages = chatData.data?.messages || []; + set(() => ({ messages: messages, chatData })); + } + + const res = await queryMark.getMark(id); + + if (res.code === 200) { + const resChatData = res.data as any; + if (chatData && resChatData.updatedAt === chatData.updatedAt) { + console.log('no update', 'time is ', resChatData.updatedAt, chatData.updatedAt); + return; + } + console.log('update chat data', resChatData); + get().setMessages(resChatData?.data?.messages || []); + set(() => ({ chatData: resChatData })); + } else { + toast.error(res.message); + } + }, + deleteMessage: (id: string) => { + const { messages, setMessages } = get(); + const newMessages = messages.filter((m) => m.id !== id); + setMessages(newMessages); + }, + clearMessage: () => { + const { setMessages } = get(); + setMessages([]); + }, + updateMessage: (message: ChastHistoryMessage) => { + const { messages, setMessages } = get(); + let isNew = true; + let newMessages = messages.map((m) => { + if (m.id === message.id) { + isNew = false; + return { ...m, ...message, updatedAt: new Date().getTime() }; + } + return m; + }); + if (isNew) { + newMessages.push(message); + } + setMessages(newMessages); + }, + showAllContext: false, + setShowAllContext: (showAllContext: boolean) => set(() => ({ showAllContext })), + showChatSetting: false, + setShowChatSetting: (showChatSetting: boolean) => set(() => ({ showChatSetting })), + showList: false, + setShowList: (showList: boolean) => set(() => ({ showList })), + newChat: false, + setNewChat: (newChat: boolean) => set(() => ({ newChat })), + historyList: [], + setHistoryList: (historyList: any[]) => set(() => ({ historyList })), + getChatList: async () => { + const res = await queryMark.getMarkList(); + if (res.code === 200) { + const len = res.data.list.length; + // if (len === 0) { + // set(() => ({ newChat: true })); + // } + set(() => ({ historyList: res.data.list })); + } else { + toast.error(res.message || 'request chat list error'); + } + }, + updateChat: async (data: any) => { + const res = await queryMark.updateMark(data); + console.log('update chat res', data); + if (res.code === 200) { + toast.success('update success'); + get().setChatData(res.data); + get().getChatList(); + } else { + toast.error(res.message || 'error'); + } + return res; + }, + deleteChat: async (id: string) => { + const res = await queryMark.deleteMark(id); + if (res.code === 200) { + toast.success('delete success'); + get().getChatList(); + } else { + toast.error(res.message || 'error'); + } + return res; + }, + chatData: {}, + setChatData: (chatData: any) => { + const cache = new CacheStore({ dbName }); + cache.set(chatData.id, chatData); + set(() => ({ chatData })); + }, + showContext: false, + setShowContext: (showContext: boolean) => set(() => ({ showContext })), + contextMessage: { + role: 'user', + content: '', + name: 'user', + }, + setContextMessage: (contextMessage?: ChastHistoryMessage) => set(() => ({ contextMessage })), + modelList: [], + setModelList: (modelList: any[]) => set(() => ({ modelList })), + getModelList: async (force?: boolean) => { + const cache = new CacheStore({ dbName }); + const modelList = await cache.get('modelList'); + if (modelList && !force) { + set(() => ({ modelList })); + return; + } + // const res = await queryChat.getModelList(); + // if (res.code === 200) { + // set(() => ({ modelList: res.data.list })); + // cache.set('modelList', res.data.list); + // } else { + // toast.error(res.message || 'error'); + // } + }, + currentUserModel: undefined, + setCurrentUserModel: (currentUserModel: { username: string; model: { group: string; provider: string; model: string } }) => { + set(() => ({ currentUserModel })); + const cache = new CacheStore({ dbName }); + cache.set('currentUserModel', currentUserModel); + }, + chat: async () => { + const { id, currentUserModel, messages } = get(); + if (!currentUserModel) { + toast.error('请先设置当前用户模型'); + return; + } + const { username, model } = currentUserModel; + const { group, provider, model: modelName } = model; + const chatOpts = { + username, + group, + model: modelName, + }; + set(() => ({ loading: true })); + const loaded = toast.loading('loading...'); + const res = await queryChat.chat({ id, messages }, chatOpts); + toast.dismiss(loaded); + set(() => ({ loading: false })); + console.log('chat res', res); + if (res.code === 200) { + const data = res.data; + const aiChatHistory = data.aiChatHistory; + console.log('aiChatHistory ', data); + if (aiChatHistory) { + const { messages } = aiChatHistory; + get().setChatData(aiChatHistory); + get().setMessages(messages); + console.log('aiChatHistory update', aiChatHistory); + } else if (data.message) { + const message = data.message; + get().updateMessage(message); + } + } else { + toast.error(res.message || 'error'); + } + }, + clearConfigCache: async () => { + const res = await queryChat.clearConfigCache(); + // 清理缓存 + if (res.code === 200) { + toast.success('clear config cache success'); + setTimeout(() => { + get().getModelList(true); + }, 4000); + } else { + toast.error(res.message || 'error'); + } + }, + modelId: '', + setModelId: (modelId: string) => set(() => ({ modelId })), + + showSetting: false, + setShowSetting: (showSetting: boolean) => set(() => ({ showSetting })), + + showCopy: false, + setShowCopy: (showCopy: boolean) => set(() => ({ showCopy })), + }; +}; + +export const useChatStore = useStore as BoundStore; diff --git a/src/apps/ai-chat/store/type.ts b/src/apps/ai-chat/store/type.ts new file mode 100644 index 0000000..0e919bb --- /dev/null +++ b/src/apps/ai-chat/store/type.ts @@ -0,0 +1,10 @@ +export type ChastHistoryMessage = { + role: string; + content: string; + name: string; + id?: string; + createdAt?: number; + updatedAt?: number; + hide?: boolean; + noUse?: boolean; +}; diff --git a/src/apps/ai-chat/utils/copy.ts b/src/apps/ai-chat/utils/copy.ts new file mode 100644 index 0000000..28b4354 --- /dev/null +++ b/src/apps/ai-chat/utils/copy.ts @@ -0,0 +1,18 @@ +import { toast } from 'react-toastify'; + +// 复制 +export const copy = (text: string) => { + navigator.clipboard.writeText(text); +}; + +export const copyText = (text: string, showToast = true) => { + copy(text); + if (showToast) { + toast.success('复制成功'); + } +}; + +export const copyHtml = (html: string) => { + copy(html); + toast.success('复制成功'); +}; diff --git a/src/apps/ai-chat/utils/uuid.ts b/src/apps/ai-chat/utils/uuid.ts new file mode 100644 index 0000000..82d5b04 --- /dev/null +++ b/src/apps/ai-chat/utils/uuid.ts @@ -0,0 +1,12 @@ +import { customAlphabet } from 'nanoid'; + +export const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'; +export const nanoid = customAlphabet(alphabet, 16); + +export function uuid() { + return nanoid(); +} + +export const chatId = () => { + return `chat-${nanoid()}`; +}; diff --git a/src/components/a/button.tsx b/src/components/a/button.tsx index 92c6989..7805165 100644 --- a/src/components/a/button.tsx +++ b/src/components/a/button.tsx @@ -4,7 +4,7 @@ export const IconButton: typeof UiButton = (props) => { return ; }; -export const Button: typeof UiButton = (props) => { +export const Button = (props: Parameters[0]) => { return ; }; diff --git a/src/components/a/confirm.tsx b/src/components/a/confirm.tsx index 4c6a413..682a217 100644 --- a/src/components/a/confirm.tsx +++ b/src/components/a/confirm.tsx @@ -14,7 +14,7 @@ import { useEffect, useMemo, useState } from 'react'; type useConfirmOptions = { confrimProps: ConfirmProps; }; -type Fn = () => void; +type Fn = (e: React.MouseEvent) => void; export const useConfirm = (opts?: useConfirmOptions) => { const [open, setOpen] = useState(false); type ConfirmOptions = { @@ -78,19 +78,21 @@ export const Confirm = (props: ConfirmProps) => { {!props?.footer && ( <> { - props?.onCancle?.(); + props?.onCancle?.(e); e.stopPropagation(); }}> {props?.onCancelText ?? '取消'} { - props?.onOk?.(); + props?.onOk?.(e); e.stopPropagation(); }}> {props?.onOkText ?? '确定'} - {' '} + )} diff --git a/src/components/a/divider.tsx b/src/components/a/divider.tsx new file mode 100644 index 0000000..91d4f3e --- /dev/null +++ b/src/components/a/divider.tsx @@ -0,0 +1,21 @@ +export const Divider = (props: { orientation?: 'horizontal' | 'vertical' }) => { + const { orientation = 'horizontal' } = props; + + const dividerStyle: React.CSSProperties = { + display: 'block', + backgroundColor: '#e5e7eb', // 淡灰色分割线 + ...(orientation === 'horizontal' + ? { + width: '100%', + height: '1px', + } + : { + alignSelf: 'stretch', + width: '1px', + display: 'inline-block', + margin: '2px 4px', + }), + }; + + return
; +}; diff --git a/src/components/a/drag-modal/index.tsx b/src/components/a/drag-modal/index.tsx new file mode 100644 index 0000000..6f8c407 --- /dev/null +++ b/src/components/a/drag-modal/index.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef, useState } from 'react'; +import Draggable from 'react-draggable'; +import { cn as clsxMerge } from '@/lib/utils'; +import { Resizable } from 're-resizable'; +import { X } from 'lucide-react'; + +type DragModalProps = { + title?: React.ReactNode; + content?: React.ReactNode; + onClose?: () => void; + containerClassName?: string; + handleClassName?: string; + contentClassName?: string; + focus?: boolean; + /** + * 默认大小, 单位为px + * width: defaultSize.width || 320 + * height: defaultSize.height || 400 + */ + defaultSize?: { + width: number; + height: number; + }; + style?: React.CSSProperties; +}; +export const DragModal = (props: DragModalProps) => { + const dragRef = useRef(null); + + return ( + { + // console.log(e, data); + }} + handle='.handle' + grid={[1, 1]} + scale={1} + bounds='parent' + defaultPosition={{ + x: 0, + y: 0, + }}> +
+
{props.title || 'Move'}
+ { + // console.log(e, direction, ref, d); + }} + enable={{ + bottom: true, + right: true, + bottomRight: true, + }}> + {props.content} + +
+
+ ); +}; + +type DragModalTitleProps = { + title?: React.ReactNode; + className?: string; + onClose?: () => void; + children?: React.ReactNode; + onClick?: () => void; +}; +export const DragModalTitle = (props: DragModalTitleProps) => { + return ( +
{ + e.stopPropagation(); + props.onClick?.(); + }}> +
+ {props.title} + {props.children} +
+
{ + e.stopPropagation(); + props.onClose?.(); + }}> + +
+
+ ); +}; + +export const getComputedHeight = () => { + const height = window.innerHeight; + const width = window.innerWidth; + return { height, width }; +}; + +export const useComputedHeight = () => { + const [computedHeight, setComputedHeight] = useState({ + height: 0, + width: 0, + }); + useEffect(() => { + const height = window.innerHeight; + const width = window.innerWidth; + setComputedHeight({ height, width }); + }, []); + return computedHeight; +}; diff --git a/src/components/a/input.tsx b/src/components/a/input.tsx new file mode 100644 index 0000000..948c0c9 --- /dev/null +++ b/src/components/a/input.tsx @@ -0,0 +1,6 @@ +import { Input as UIInput } from '@/components/ui/input'; + +export type InputProps = { label?: string } & React.ComponentProps<'input'>; +export const Input = (props: InputProps) => { + return ; +}; diff --git a/src/components/a/select.tsx b/src/components/a/select.tsx index 6b968bd..b3a1ced 100644 --- a/src/components/a/select.tsx +++ b/src/components/a/select.tsx @@ -5,10 +5,12 @@ type Option = { label?: string; }; type SelectProps = { + className?: string; options?: Option[]; value?: string; placeholder?: string; - onChange?: (value: string) => any; + onChange?: (value: any) => any; + size?: 'small' | 'medium' | 'large'; }; export const Select = (props: SelectProps) => { const options = props.options || []; diff --git a/src/components/a/tooltip.tsx b/src/components/a/tooltip.tsx index d07a4d9..5e82081 100644 --- a/src/components/a/tooltip.tsx +++ b/src/components/a/tooltip.tsx @@ -1,6 +1,7 @@ import { Tooltip as UITooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import React from 'react'; -export const Tooltip = (props: { children?: React.ReactNode; title?: string }) => { +export const Tooltip = (props: { children?: React.ReactNode; title?: React.ReactNode }) => { return ( diff --git a/src/components/ai/RenderMarkdown.tsx b/src/components/ai/RenderMarkdown.tsx new file mode 100644 index 0000000..5aeec22 --- /dev/null +++ b/src/components/ai/RenderMarkdown.tsx @@ -0,0 +1,115 @@ +import { throttle } from 'lodash-es'; +import { Marked } from 'marked'; +import 'highlight.js/styles/github.css'; // 你可以选择其他样式 +import { useRef, useEffect } from 'react'; + +import { markedHighlight } from 'marked-highlight'; +import hljs from 'highlight.js'; +import clsx from 'clsx'; + +const marked = new Marked( + markedHighlight({ + emptyLangClass: 'hljs', + langPrefix: 'hljs language-', + highlight(code, lang, info) { + const language = hljs.getLanguage(lang) ? lang : 'plaintext'; + return hljs.highlight(code, { language }).value; + }, + }), +); + +type ResponseTextProps = { + response: Response; + onFinish?: (text: string) => void; + onChange?: (text: string) => void; + className?: string; + id?: string; +}; +export const ResponseText = (props: ResponseTextProps) => { + const ref = useRef(null); + const render = async () => { + const response = props.response; + if (!response) return; + const msg = ref.current!; + if (!msg) { + console.log('msg is null'); + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + const reader = response.body?.getReader(); + const decoder = new TextDecoder('utf-8'); + let done = false; + + while (!done) { + const { value, done: streamDone } = await reader!.read(); + done = streamDone; + + if (value) { + const chunk = decoder.decode(value, { stream: true }); + // 更新状态,实时刷新 UI + msg.innerHTML += chunk; + } + } + if (done) { + props.onFinish && props.onFinish(msg.innerHTML); + } + }; + useEffect(() => { + render(); + }, []); + return
; +}; + +export const ResponseMarkdown = (props: ResponseTextProps) => { + const ref = useRef(null); + let content = ''; + const render = async () => { + const response = props.response; + if (!response) return; + const msg = ref.current!; + if (!msg) { + console.log('msg is null'); + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + const reader = response.body?.getReader(); + const decoder = new TextDecoder('utf-8'); + let done = false; + + while (!done) { + const { value, done: streamDone } = await reader!.read(); + done = streamDone; + + if (value) { + const chunk = decoder.decode(value, { stream: true }); + content = content + chunk; + renderThrottle(content); + } + } + if (done) { + props.onFinish && props.onFinish(msg.innerHTML); + } + }; + const renderThrottle = throttle(async (markdown: string) => { + const msg = ref.current!; + msg.innerHTML = await marked.parse(markdown); + props.onChange?.(msg.innerHTML); + }, 100); + useEffect(() => { + render(); + }, [props.response]); + return
; +}; + +export const Markdown = (props: { className?: string; markdown: string; id?: string }) => { + const ref = useRef(null); + const parse = async () => { + const md = await marked.parse(props.markdown); + const msg = ref.current!; + msg.innerHTML = md; + }; + useEffect(() => { + parse(); + }, []); + return
; +}; diff --git a/src/components/html/blank.astro b/src/components/html/blank.astro index 1db1589..ca09a7b 100644 --- a/src/components/html/blank.astro +++ b/src/components/html/blank.astro @@ -6,6 +6,16 @@ AI Pages + diff --git a/src/data/blogs/202502/20250223-16-srvice.md b/src/data/blogs/202502/20250223-16-service.md similarity index 98% rename from src/data/blogs/202502/20250223-16-srvice.md rename to src/data/blogs/202502/20250223-16-service.md index 0b610e0..f9bcd1b 100644 --- a/src/data/blogs/202502/20250223-16-srvice.md +++ b/src/data/blogs/202502/20250223-16-service.md @@ -1,4 +1,4 @@ -# srvice +# service 关于服务器构思分享。 diff --git a/src/modules/query.ts b/src/modules/query.ts index f7c25d9..722980b 100644 --- a/src/modules/query.ts +++ b/src/modules/query.ts @@ -1,5 +1,5 @@ -import { Query } from '@kevisual/query'; +import { Query, ClientQuery } from '@kevisual/query/query'; export const query = new Query(); -export const clientQuery = new Query({ url: '/client/router' }); +export const clientQuery = new ClientQuery(); diff --git a/src/pages/index.astro b/src/pages/index.astro index 93757e0..0ba1aa2 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,8 +1,9 @@ --- import '../styles/global.css'; import Blank from '@/components/html/blank.astro'; +import { App as AIChat } from '@/apps/ai-chat/index.tsx'; --- -
sdf
+
diff --git a/src/query/kevisual.json b/src/query/kevisual.json new file mode 100644 index 0000000..ca52328 --- /dev/null +++ b/src/query/kevisual.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://kevisual.xiongxiao.me/root/ai/kevisual/tools/kevisual-sync/schema.json?v=2", + "metadata": { + "share": "public" + }, + "checkDir": { + "src/query": { + "url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query", + "enabled": true + } + }, + "syncDirectory": [ + { + "files": [ + "src/query/**/*" + ], + "ignore": [], + "registry": "https://kevisual.xiongxiao.me/root/ai/code/registry", + "replace": { + "src/": "" + } + } + ], + "sync": {} +} \ No newline at end of file diff --git a/src/query/query-ai/defines/ai.ts b/src/query/query-ai/defines/ai.ts new file mode 100644 index 0000000..b143bed --- /dev/null +++ b/src/query/query-ai/defines/ai.ts @@ -0,0 +1,21 @@ +import { QueryUtil } from '@/query/index.ts'; + +type Message = { + role?: 'user' | 'assistant' | 'system' | 'tool'; + content?: string; + name?: string; +}; +export type PostChat = { + messages?: Message[]; + model?: string; + group?: string; + user?: string; +}; + +export const appDefine = QueryUtil.create({ + chat: { + path: 'ai', + key: 'chat', + description: '与 AI 进行对话, 调用 GPT 的AI 服务,生成结果,并返回。', + }, +}); diff --git a/src/query/query-ai/query-ai.ts b/src/query/query-ai/query-ai.ts new file mode 100644 index 0000000..817c108 --- /dev/null +++ b/src/query/query-ai/query-ai.ts @@ -0,0 +1,25 @@ +import { appDefine } from './defines/ai.ts'; +import { PostChat } from './defines/ai.ts'; + +import { BaseQuery, DataOpts, Query } from '@kevisual/query/query'; + +export { appDefine }; + +export class QueryApp extends BaseQuery { + constructor(opts?: { query: T }) { + super({ + ...opts, + query: opts?.query!, + queryDefine: appDefine, + }); + } + /** + * 与 AI 进行对话, 调用 GPT 的AI 服务,生成结果,并返回。 + * @param data + * @param opts + * @returns + */ + postChat(data: PostChat, opts?: DataOpts) { + return this.chain('chat').post(data, opts); + } +} diff --git a/src/query/query-login/login-cache.ts b/src/query/query-login/login-cache.ts new file mode 100644 index 0000000..0ee1bf9 --- /dev/null +++ b/src/query/query-login/login-cache.ts @@ -0,0 +1,204 @@ +export interface Cache { + /** + * @update 获取缓存 + */ + get(key: string): Promise; + /** + * @update 设置缓存 + */ + set(key: string, value: any): Promise; + /** + * @update 删除缓存 + */ + del(): Promise; + /** + * 初始化 + */ + init?: () => Promise; +} +type User = { + avatar?: string; + description?: string; + id?: string; + needChangePassword?: boolean; + orgs?: string[]; + type?: string; + username?: string; +}; + +export type CacheLoginUser = { + user?: User; + id?: string; + accessToken?: string; + refreshToken?: string; +}; +type CacheLogin = { + loginUsers: CacheLoginUser[]; +} & CacheLoginUser; + +export type CacheStore = { + name: string; + /** + * 缓存数据 + * @important 需要先调用init + */ + cacheData: CacheLogin; + /** + * 实际操作的cache, 需要先调用init + */ + cache: T; + + /** + * 设置当前用户 + */ + setLoginUser(user: CacheLoginUser): Promise; + /** + * 获取当前用户 + */ + getCurrentUser(): Promise; + /** + * 获取当前用户列表 + */ + getCurrentUserList(): Promise; + /** + * 获取缓存的refreshToken + */ + getRefreshToken(): Promise; + /** + * 获取缓存的accessToken + */ + getAccessToken(): Promise; + /** + * 清除当前用户 + */ + clearCurrentUser(): Promise; + /** + * 清除所有用户 + */ + clearAll(): Promise; + + getValue(): Promise; + setValue(value: CacheLogin): Promise; + delValue(): Promise; + init(): Promise; +}; + +export type LoginCacheStoreOpts = { + name: string; + cache: Cache; +}; +export class LoginCacheStore implements CacheStore { + cache: Cache; + name: string; + cacheData: CacheLogin; + constructor(opts: LoginCacheStoreOpts) { + if (!opts.cache) { + throw new Error('cache is required'); + } + // @ts-ignore + this.cache = opts.cache; + this.cacheData = { + loginUsers: [], + user: undefined, + id: undefined, + accessToken: undefined, + refreshToken: undefined, + }; + this.name = opts.name; + } + /** + * 设置缓存 + * @param key + * @param value + * @returns + */ + async setValue(value: CacheLogin) { + await this.cache.set(this.name, value); + this.cacheData = value; + return value; + } + /** + * 删除缓存 + */ + async delValue() { + await this.cache.del(); + } + getValue(): Promise { + return this.cache.get(this.name); + } + /** + * 初始化,设置默认值 + */ + async init() { + const defaultData = { + loginUsers: [], + user: null, + id: null, + accessToken: null, + refreshToken: null, + }; + if (this.cache.init) { + try { + const cacheData = await this.cache.init(); + this.cacheData = cacheData || defaultData; + } catch (error) { + console.log('cacheInit error', error); + } + } else { + this.cacheData = (await this.getValue()) || defaultData; + } + } + /** + * 设置当前用户 + * @param user + */ + async setLoginUser(user: CacheLoginUser) { + const has = this.cacheData.loginUsers.find((u) => u.id === user.id); + if (has) { + this.cacheData.loginUsers = this.cacheData?.loginUsers?.filter((u) => u?.id && u.id !== user.id); + } + this.cacheData.loginUsers.push(user); + this.cacheData.user = user.user; + this.cacheData.id = user.id; + this.cacheData.accessToken = user.accessToken; + this.cacheData.refreshToken = user.refreshToken; + await this.setValue(this.cacheData); + } + + getCurrentUser(): Promise { + const cacheData = this.cacheData; + return Promise.resolve(cacheData.user!); + } + getCurrentUserList(): Promise { + return Promise.resolve(this.cacheData.loginUsers.filter((u) => u?.id)); + } + getRefreshToken(): Promise { + const cacheData = this.cacheData; + return Promise.resolve(cacheData.refreshToken || ''); + } + getAccessToken(): Promise { + const cacheData = this.cacheData; + return Promise.resolve(cacheData.accessToken || ''); + } + + async clearCurrentUser() { + const user = await this.getCurrentUser(); + const has = this.cacheData.loginUsers.find((u) => u.id === user.id); + if (has) { + this.cacheData.loginUsers = this.cacheData?.loginUsers?.filter((u) => u?.id && u.id !== user.id); + } + this.cacheData.user = undefined; + this.cacheData.id = undefined; + this.cacheData.accessToken = undefined; + this.cacheData.refreshToken = undefined; + await this.setValue(this.cacheData); + } + async clearAll() { + this.cacheData.loginUsers = []; + this.cacheData.user = undefined; + this.cacheData.id = undefined; + this.cacheData.accessToken = undefined; + this.cacheData.refreshToken = undefined; + await this.setValue(this.cacheData); + } +} diff --git a/src/query/query-login/login-node-cache.ts b/src/query/query-login/login-node-cache.ts new file mode 100644 index 0000000..a0e2b26 --- /dev/null +++ b/src/query/query-login/login-node-cache.ts @@ -0,0 +1,132 @@ +import { Cache } from './login-cache.ts'; +import { homedir } from 'node:os'; +import { join, dirname } from 'node:path'; +import fs from 'node:fs'; +import { readFileSync, writeFileSync, accessSync } from 'node:fs'; +import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises'; +export const fileExists = async ( + filePath: string, + { createIfNotExists = true, isFile = true, isDir = false }: { createIfNotExists?: boolean; isFile?: boolean; isDir?: boolean } = {}, +) => { + try { + accessSync(filePath, fs.constants.F_OK); + return true; + } catch (error) { + if (createIfNotExists && isDir) { + await mkdir(filePath, { recursive: true }); + return true; + } else if (createIfNotExists && isFile) { + await mkdir(dirname(filePath), { recursive: true }); + return false; + } + return false; + } +}; +export const readConfigFile = (filePath: string) => { + try { + const data = readFileSync(filePath, 'utf-8'); + const jsonData = JSON.parse(data); + return jsonData; + } catch (error) { + return {}; + } +}; +export const writeConfigFile = (filePath: string, data: any) => { + writeFileSync(filePath, JSON.stringify(data, null, 2)); +}; +export const getHostName = () => { + const configDir = join(homedir(), '.config', 'envision'); + const configFile = join(configDir, 'config.json'); + const config = readConfigFile(configFile); + const baseURL = config.baseURL || 'https://kevisual.cn'; + const hostname = new URL(baseURL).hostname; + return hostname; +}; +export class StorageNode implements Storage { + cacheData: any; + filePath: string; + constructor() { + this.cacheData = {}; + const configDir = join(homedir(), '.config', 'envision'); + const hostname = getHostName(); + this.filePath = join(configDir, 'config', `${hostname}-storage.json`); + fileExists(this.filePath, { isFile: true }); + } + async loadCache() { + const filePath = this.filePath; + try { + const data = await readConfigFile(filePath); + this.cacheData = data; + } catch (error) { + this.cacheData = {}; + await writeFile(filePath, JSON.stringify(this.cacheData, null, 2)); + } + } + get length() { + return Object.keys(this.cacheData).length; + } + getItem(key: string) { + return this.cacheData[key]; + } + setItem(key: string, value: any) { + this.cacheData[key] = value; + writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2)); + } + removeItem(key: string) { + delete this.cacheData[key]; + writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2)); + } + clear() { + this.cacheData = {}; + writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2)); + } + key(index: number) { + return Object.keys(this.cacheData)[index]; + } +} +export class LoginNodeCache implements Cache { + filepath: string; + + constructor(filepath?: string) { + this.filepath = filepath || join(homedir(), '.config', 'envision', 'config', `${getHostName()}-login.json`); + fileExists(this.filepath, { isFile: true }); + } + async get(_key: string) { + try { + const filePath = this.filepath; + const data = readConfigFile(filePath); + return data; + } catch (error) { + console.log('get error', error); + return {}; + } + } + async set(_key: string, value: any) { + try { + const data = readConfigFile(this.filepath); + const newData = { ...data, ...value }; + writeConfigFile(this.filepath, newData); + } catch (error) { + console.log('set error', error); + } + } + async del() { + await unlink(this.filepath); + } + async loadCache(filePath: string) { + try { + const data = await readFile(filePath, 'utf-8'); + const jsonData = JSON.parse(data); + return jsonData; + } catch (error) { + // console.log('loadCache error', error); + console.log('create new cache file:', filePath); + const defaultData = { loginUsers: [] }; + writeConfigFile(filePath, defaultData); + return defaultData; + } + } + async init() { + return await this.loadCache(this.filepath); + } +} diff --git a/src/query/query-login/query-login-browser.ts b/src/query/query-login/query-login-browser.ts new file mode 100644 index 0000000..2d131cb --- /dev/null +++ b/src/query/query-login/query-login-browser.ts @@ -0,0 +1,12 @@ +import { QueryLogin, QueryLoginOpts } from './query-login.ts'; +import { MyCache } from '@kevisual/cache'; +type QueryLoginNodeOptsWithoutCache = Omit; + +export class QueryLoginBrowser extends QueryLogin { + constructor(opts: QueryLoginNodeOptsWithoutCache) { + super({ + ...opts, + cache: new MyCache('login'), + }); + } +} diff --git a/src/query/query-login/query-login-node.ts b/src/query/query-login/query-login-node.ts new file mode 100644 index 0000000..5cb6527 --- /dev/null +++ b/src/query/query-login/query-login-node.ts @@ -0,0 +1,14 @@ +import { QueryLogin, QueryLoginOpts } from './query-login.ts'; +import { LoginNodeCache, StorageNode } from './login-node-cache.ts'; +type QueryLoginNodeOptsWithoutCache = Omit; +export const storage = new StorageNode(); +await storage.loadCache(); +export class QueryLoginNode extends QueryLogin { + constructor(opts: QueryLoginNodeOptsWithoutCache) { + super({ + ...opts, + storage, + cache: new LoginNodeCache(), + }); + } +} diff --git a/src/query/query-login/query-login.ts b/src/query/query-login/query-login.ts new file mode 100644 index 0000000..6a278ea --- /dev/null +++ b/src/query/query-login/query-login.ts @@ -0,0 +1,419 @@ +import { Query, BaseQuery } from '@kevisual/query'; +import type { Result, DataOpts } from '@kevisual/query/query'; +import { setBaseResponse } from '@kevisual/query/query'; +import { LoginCacheStore, CacheStore } from './login-cache.ts'; +import { Cache } from './login-cache.ts'; + +export type QueryLoginOpts = { + query?: Query; + isBrowser?: boolean; + onLoad?: () => void; + storage?: Storage; + cache: Cache; +}; +export type QueryLoginData = { + username?: string; + password: string; + email?: string; +}; +export type QueryLoginResult = { + accessToken: string; + refreshToken: string; +}; + +export class QueryLogin extends BaseQuery { + /** + * query login cache, 非实际操作, 一个cache的包裹模块 + */ + cacheStore: CacheStore; + isBrowser: boolean; + load?: boolean; + storage: Storage; + onLoad?: () => void; + + constructor(opts?: QueryLoginOpts) { + super({ + query: opts?.query || new Query(), + }); + this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts.cache }); + this.isBrowser = opts?.isBrowser ?? true; + this.init(); + this.onLoad = opts?.onLoad; + this.storage = opts?.storage || localStorage; + } + setQuery(query: Query) { + this.query = query; + } + private async init() { + await this.cacheStore.init(); + this.load = true; + this.onLoad?.(); + } + async post(data: any, opts?: DataOpts) { + try { + return this.query.post({ path: 'user', ...data }, opts); + } catch (error) { + console.log('error', error); + return { + code: 400, + } as any; + } + } + /** + * 登录, + * @param data + * @returns + */ + async login(data: QueryLoginData) { + const res = await this.post({ key: 'login', ...data }); + if (res.code === 200) { + const { accessToken, refreshToken } = res?.data || {}; + this.storage.setItem('token', accessToken || ''); + await this.beforeSetLoginUser({ accessToken, refreshToken }); + } + return res; + } + /** + * 手机号登录 + * @param data + * @returns + */ + async loginByCode(data: { phone: string; code: string }) { + const res = await this.post({ path: 'sms', key: 'login', data }); + if (res.code === 200) { + const { accessToken, refreshToken } = res?.data || {}; + this.storage.setItem('token', accessToken || ''); + await this.beforeSetLoginUser({ accessToken, refreshToken }); + } + return res; + } + /** + * 设置token + * @param token + */ + async setLoginToken(token: { accessToken: string; refreshToken: string }) { + const { accessToken, refreshToken } = token; + this.storage.setItem('token', accessToken || ''); + await this.beforeSetLoginUser({ accessToken, refreshToken }); + } + async loginByWechat(data: { code: string }) { + const res = await this.post({ path: 'wx', key: 'open-login', code: data.code }); + if (res.code === 200) { + const { accessToken, refreshToken } = res?.data || {}; + this.storage.setItem('token', accessToken || ''); + await this.beforeSetLoginUser({ accessToken, refreshToken }); + } + return res; + } + /** + * 检测微信登录,登陆成功后,调用onSuccess,否则调用onError + * @param param0 + */ + async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) { + const url = new URL(window.location.href); + const code = url.searchParams.get('code'); + const state = url.searchParams.get('state'); + if (code && state) { + const res = await this.loginByWechat({ code }); + if (res.code === 200) { + onSuccess?.(res.data); + } else { + onError?.(res); + } + } + } + /** + * 登陆成功,需要获取用户信息进行缓存 + * @param param0 + */ + async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) { + if (accessToken && refreshToken) { + const resUser = await this.getMe(accessToken, check401); + if (resUser.code === 200) { + const user = resUser.data; + if (user) { + this.cacheStore.setLoginUser({ + user, + id: user.id, + accessToken, + refreshToken, + }); + } else { + console.error('登录失败'); + } + } + } + } + /** + * 刷新token + * @param refreshToken + * @returns + */ + async queryRefreshToken(refreshToken?: string) { + const _refreshToken = refreshToken || this.cacheStore.getRefreshToken(); + let data = { refreshToken: _refreshToken }; + if (!_refreshToken) { + await this.cacheStore.clearCurrentUser(); + return { + code: 401, + message: '请先登录', + data: {} as any, + }; + } + return this.post( + { key: 'refreshToken', data }, + { + afterResponse: async (response, ctx) => { + setBaseResponse(response); + return response as any; + }, + }, + ); + } + /** + * 检查401错误,并刷新token, 如果refreshToken存在,则刷新token, 否则返回401 + * 拦截请求,请使用run401Action, 不要直接使用 afterCheck401ToRefreshToken + * @param response + * @param ctx + * @param refetch + * @returns + */ + async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) { + const that = this; + if (response?.code === 401) { + const hasRefreshToken = await that.cacheStore.getRefreshToken(); + if (hasRefreshToken) { + const res = await that.queryRefreshToken(hasRefreshToken); + if (res.code === 200) { + const { accessToken, refreshToken } = res?.data || {}; + that.storage.setItem('token', accessToken || ''); + await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false }); + if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) { + await new Promise((resolve) => setTimeout(resolve, 1500)); + const url = ctx.req?.url; + const body = ctx.req?.body; + const headers = ctx.req?.headers; + const res = await ctx.fetch(url, { + method: 'POST', + body: body, + headers: { ...headers, Authorization: `Bearer ${accessToken}` }, + }); + setBaseResponse(res); + return res; + } + } else { + that.storage.removeItem('token'); + await that.cacheStore.clearCurrentUser(); + } + return res; + } + } + return response as any; + } + /** + * 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401 + * refetch 是否重新请求, 会有bug,无限循环,按需要使用 + * TODO: + * @param response + * @param ctx + * @param opts + * @returns + */ + async run401Action( + response: Result, + ctx?: { req?: any; res?: any; fetch?: any }, + opts?: { + /** + * 是否重新请求, 会有bug,无限循环,按需要使用 + */ + refetch?: boolean; + /** + * check之后的回调 + */ + afterCheck?: (res: Result) => any; + /** + * 401处理后, 还是401, 则回调 + */ + afterAlso401?: (res: Result) => any; + }, + ) { + const that = this; + const refetch = opts?.refetch ?? false; + if (response?.code === 401) { + if (that.query.stop === true) { + return { code: 500, success: false, message: 'refresh token loading...' }; + } + that.query.stop = true; + const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch); + that.query.stop = false; + opts?.afterCheck?.(res); + if (res.code === 401) { + opts?.afterAlso401?.(res); + } + return res; + } else { + return response as any; + } + } + /** + * 获取用户信息 + * @param token + * @returns + */ + async getMe(token?: string, check401: boolean = true) { + const _token = token || this.storage.getItem('token'); + const that = this; + return that.post( + { key: 'me' }, + { + beforeRequest: async (config) => { + if (config.headers) { + config.headers['Authorization'] = `Bearer ${_token}`; + } + if (!_token) { + // TODO: 取消请求,因为没有登陆 + } + return config; + }, + afterResponse: async (response, ctx) => { + if (response?.code === 401 && check401 && !token) { + return await that.afterCheck401ToRefreshToken(response, ctx); + } + return response as any; + }, + }, + ); + } + /** + * 检查本地用户,如果本地用户存在,则返回本地用户,否则返回null + * @returns + */ + async checkLocalUser() { + const user = await this.cacheStore.getCurrentUser(); + if (user) { + return user; + } + return null; + } + /** + * 检查本地token是否存在,简单的判断是否已经属于登陆状态 + * @returns + */ + async checkLocalToken() { + const token = this.storage.getItem('token'); + return !!token; + } + /** + * 请求更新,切换用户, 使用switchUser + * @param username + * @returns + */ + private async postSwitchUser(username: string) { + return this.post({ key: 'switchCheck', data: { username } }); + } + /** + * 切换用户 + * @param username + * @returns + */ + async switchUser(username: string) { + const localUserList = await this.cacheStore.getCurrentUserList(); + const user = localUserList.find((userItem) => userItem.user.username === username); + if (user) { + this.storage.setItem('token', user.accessToken || ''); + await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken }); + return { + code: 200, + data: { + accessToken: user.accessToken, + refreshToken: user.refreshToken, + }, + success: true, + message: '切换用户成功', + }; + } + const res = await this.postSwitchUser(username); + + if (res.code === 200) { + const { accessToken, refreshToken } = res?.data || {}; + this.storage.setItem('token', accessToken || ''); + await this.beforeSetLoginUser({ accessToken, refreshToken }); + } + return res; + } + /** + * 退出登陆,去掉token, 并删除缓存 + * @returns + */ + async logout() { + this.storage.removeItem('token'); + const users = await this.cacheStore.getCurrentUserList(); + const tokens = users + .map((user) => { + return user?.accessToken; + }) + .filter(Boolean); + this.cacheStore.delValue(); + return this.post({ key: 'logout', data: { tokens } }); + } + /** + * 检查用户名的组,这个用户是否存在 + * @param username + * @returns + */ + async hasUser(username: string) { + const that = this; + return this.post( + { + path: 'org', + key: 'hasUser', + data: { + username, + }, + }, + { + afterResponse: async (response, ctx) => { + if (response?.code === 401) { + const res = await that.afterCheck401ToRefreshToken(response, ctx, true); + return res; + } + return response as any; + }, + }, + ); + } + /** + * 检查登录状态 + * @param token + * @returns + */ + async checkLoginStatus(token: string) { + const res = await this.post({ + path: 'user', + key: 'checkLoginStatus', + loginToken: token, + }); + if (res.code === 200) { + const accessToken = res.data?.accessToken; + this.storage.setItem('token', accessToken || ''); + await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken }); + return res; + } + return false; + } + /** + * 使用web登录,创建url地址, 需要MD5和jsonwebtoken + */ + loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) { + const randomId = Math.random().toString(36).substring(2, 15); + const timestamp = Date.now(); + const tokenSecret = 'xiao' + randomId; + const sign = MD5(`${tokenSecret}${timestamp}`).toString(); + const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, { + // 10分钟过期 + expiresIn: 60 * 10, // 10分钟 + }); + const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`; + return { url, token, tokenSecret }; + } +} diff --git a/src/query/query-mark/query-mark.ts b/src/query/query-mark/query-mark.ts new file mode 100644 index 0000000..729499d --- /dev/null +++ b/src/query/query-mark/query-mark.ts @@ -0,0 +1,154 @@ +import { Query } from '@kevisual/query'; +import type { Result, DataOpts } from '@kevisual/query/query'; + +export type SimpleObject = Record; +export const markType = ['simple', 'md', 'mdx', 'wallnote', 'excalidraw', 'chat'] as const; +export type MarkType = (typeof markType)[number]; +export type MarkData = { + nodes?: any[]; + edges?: any[]; + elements?: any[]; + permission?: any; + + [key: string]: any; +}; +export type Mark = { + id: string; + title: string; + description: string; + markType: MarkType; + link: string; + data?: MarkData; + uid: string; + puid: string; + summary: string; + thumbnail?: string; + tags: string[]; + createdAt: string; + updatedAt: string; + version: number; +}; +export type ShowMarkPick = Pick; + +export type SearchOpts = { + page?: number; + pageSize?: number; + search?: string; + sort?: string; // DESC, ASC + markType?: MarkType; // 类型 + [key: string]: any; +}; + +export type QueryMarkOpts = { + query?: Query; + isBrowser?: boolean; + onLoad?: () => void; +} & T; + +export type ResultMarkList = { + list: Mark[]; + pagination: { + pageSize: number; + current: number; + total: number; + }; +}; +export type QueryMarkData = { + id?: string; + title?: string; + description?: string; + [key: string]: any; +}; +export type QueryMarkResult = { + accessToken: string; + refreshToken: string; +}; + +export class QueryMarkBase { + query: Query; + isBrowser: boolean; + load?: boolean; + storage?: Storage; + onLoad?: () => void; + + constructor(opts?: QueryMarkOpts) { + this.query = opts?.query || new Query(); + this.isBrowser = opts?.isBrowser ?? true; + this.init(); + this.onLoad = opts?.onLoad; + } + setQuery(query: Query) { + this.query = query; + } + private async init() { + this.load = true; + this.onLoad?.(); + } + + async post>(data: any, opts?: DataOpts): Promise { + try { + return this.query.post({ path: 'mark', ...data }, opts) as Promise; + } catch (error) { + console.log('error', error); + return { + code: 400, + } as any; + } + } + + async getMarkList(search: SearchOpts, opts?: DataOpts) { + return this.post>({ key: 'list', ...search }, opts); + } + + async getMark(id: string, opts?: DataOpts) { + return this.post>({ key: 'get', id }, opts); + } + async getVersion(id: string, opts?: DataOpts) { + return this.post>({ key: 'getVersion', id }, opts); + } + /** + * 检查版本 + * 当需要更新时,返回true + * @param id + * @param version + * @param opts + * @returns + */ + async checkVersion(id: string, version?: number, opts?: DataOpts) { + if (!version) { + return true; + } + const res = await this.getVersion(id, opts); + if (res.code === 200) { + if (res.data!.version > version) { + return true; + } + return false; + } + return true; + } + + async updateMark(data: any, opts?: DataOpts) { + return this.post>({ key: 'update', data }, opts); + } + + async deleteMark(id: string, opts?: DataOpts) { + return this.post>({ key: 'delete', id }, opts); + } +} +export class QueryMark extends QueryMarkBase { + markType: string; + constructor(opts?: QueryMarkOpts & { markType?: MarkType }) { + super(opts); + this.markType = opts?.markType || 'simple'; + } + async getMarkList(search?: SearchOpts, opts?: DataOpts) { + return this.post({ key: 'list', ...search, markType: this.markType }, opts); + } + async updateMark(data: any, opts?: DataOpts) { + if (!data.id) { + data.markType = this.markType || 'simple'; + } + return super.updateMark(data, opts); + } +} diff --git a/src/query/query-shop/defines/query-shop-define.ts b/src/query/query-shop/defines/query-shop-define.ts new file mode 100644 index 0000000..160a089 --- /dev/null +++ b/src/query/query-shop/defines/query-shop-define.ts @@ -0,0 +1,27 @@ +import { QueryUtil } from '@/query/index.ts'; + +export const shopDefine = QueryUtil.create({ + getRegistry: { + path: 'shop', + key: 'get-registry', + description: '获取应用商店注册表信息', + }, + + listInstalled: { + path: 'shop', + key: 'list-installed', + description: '列出当前已安装的所有应用', + }, + + install: { + path: 'shop', + key: 'install', + description: '安装指定的应用,可以指定 id、type、force 和 yes 参数', + }, + + uninstall: { + path: 'shop', + key: 'uninstall', + description: '卸载指定的应用,可以指定 id 和 type 参数', + }, +}); diff --git a/src/query/query-shop/query-shop.ts b/src/query/query-shop/query-shop.ts new file mode 100644 index 0000000..c5eba7e --- /dev/null +++ b/src/query/query-shop/query-shop.ts @@ -0,0 +1,17 @@ +import { shopDefine } from './defines/query-shop-define.ts'; + +import { BaseQuery, DataOpts, Query } from '@kevisual/query/query'; + +export { shopDefine }; + +export class QueryShop extends BaseQuery { + constructor(opts?: { query: T }) { + super({ + query: opts?.query!, + queryDefine: shopDefine, + }); + } + getInstall(data: any, opts?: DataOpts) { + return this.queryDefine.queryChain('install').post(data, opts); + } +} diff --git a/src/query/query-upload/core/upload-chunk.ts b/src/query/query-upload/core/upload-chunk.ts new file mode 100644 index 0000000..6b7a4dd --- /dev/null +++ b/src/query/query-upload/core/upload-chunk.ts @@ -0,0 +1,134 @@ +import { randomId } from '../utils/random-id.ts'; +import { UploadProgress } from './upload-progress.ts'; +export type ConvertOpts = { + appKey?: string; + version?: string; + username?: string; + directory?: string; + isPublic?: boolean; + filename?: string; + /** + * 是否不检查应用文件, 默认 true,默认不检测 + */ + noCheckAppFiles?: boolean; +}; + +// createEventSource: (baseUrl: string, searchParams: URLSearchParams) => { +// return new EventSource(baseUrl + '/api/s1/events?' + searchParams.toString()); +// }, +export type UploadOpts = { + uploadProgress: UploadProgress; + /** + * 创建 EventSource 兼容 nodejs + * @param baseUrl 基础 URL + * @param searchParams 查询参数 + * @returns EventSource + */ + createEventSource: (baseUrl: string, searchParams: URLSearchParams) => EventSource; + baseUrl?: string; + token: string; + FormDataFn: any; +}; +export const uploadFileChunked = async (file: File, opts: ConvertOpts, opts2: UploadOpts) => { + const { directory, appKey, version, username, isPublic, noCheckAppFiles = true } = opts; + const { uploadProgress, createEventSource, baseUrl = '', token, FormDataFn } = opts2 || {}; + return new Promise(async (resolve, reject) => { + const taskId = randomId(); + const filename = opts.filename || file.name; + uploadProgress?.start(`${filename} 上传中...`); + + const searchParams = new URLSearchParams(); + searchParams.set('taskId', taskId); + if (isPublic) { + searchParams.set('public', 'true'); + } + if (noCheckAppFiles) { + searchParams.set('noCheckAppFiles', '1'); + } + const eventSource = createEventSource(baseUrl + '/api/s1/events', searchParams); + let isError = false; + // 监听服务器推送的进度更新 + eventSource.onmessage = function (event) { + console.log('Progress update:', event.data); + const parseIfJson = (data: string) => { + try { + return JSON.parse(data); + } catch (e) { + return data; + } + }; + const receivedData = parseIfJson(event.data); + if (typeof receivedData === 'string') return; + const progress = Number(receivedData.progress); + const progressFixed = progress.toFixed(2); + uploadProgress?.set(progress, { ...receivedData, progressFixed, filename, taskId }); + }; + eventSource.onerror = function (event) { + console.log('eventSource.onerror', event); + isError = true; + reject(event); + }; + + const chunkSize = 1 * 1024 * 1024; // 1MB + const totalChunks = Math.ceil(file.size / chunkSize); + + for (let currentChunk = 0; currentChunk < totalChunks; currentChunk++) { + const start = currentChunk * chunkSize; + const end = Math.min(start + chunkSize, file.size); + const chunk = file.slice(start, end); + + const formData = new FormDataFn(); + formData.append('file', chunk, filename); + formData.append('chunkIndex', currentChunk.toString()); + formData.append('totalChunks', totalChunks.toString()); + const isLast = currentChunk === totalChunks - 1; + if (directory) { + formData.append('directory', directory); + } + if (appKey && version) { + formData.append('appKey', appKey); + formData.append('version', version); + } + if (username) { + formData.append('username', username); + } + try { + const res = await fetch(baseUrl + '/api/s1/resources/upload/chunk?taskId=' + taskId, { + method: 'POST', + body: formData, + headers: { + 'task-id': taskId, + Authorization: `Bearer ${token}`, + }, + }).then((response) => response.json()); + + if (res?.code !== 200) { + console.log('uploadChunk error', res); + uploadProgress?.error(res?.message || '上传失败'); + isError = true; + eventSource.close(); + + uploadProgress?.done(); + reject(new Error(res?.message || '上传失败')); + return; + } + if (isLast) { + fetch(baseUrl + '/api/s1/events/close?taskId=' + taskId); + eventSource.close(); + uploadProgress?.done(); + resolve(res); + } + // console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res); + } catch (error) { + console.log('Error uploading chunk', error); + fetch(baseUrl + '/api/s1/events/close?taskId=' + taskId); + reject(error); + return; + } + } + // 循环结束 + if (!uploadProgress?.end) { + uploadProgress?.done(); + } + }); +}; diff --git a/src/query/query-upload/core/upload-progress.ts b/src/query/query-upload/core/upload-progress.ts new file mode 100644 index 0000000..1de66a6 --- /dev/null +++ b/src/query/query-upload/core/upload-progress.ts @@ -0,0 +1,103 @@ +interface UploadNProgress { + start: (msg?: string) => void; + done: () => void; + set: (progress: number) => void; +} +export type UploadProgressData = { + progress: number; + progressFixed: number; + filename?: string; + taskId?: string; +}; +type UploadProgressOpts = { + onStart?: () => void; + onDone?: () => void; + onProgress?: (progress: number, data?: UploadProgressData) => void; +}; +export class UploadProgress implements UploadNProgress { + /** + * 进度 + */ + progress: number; + /** + * 开始回调 + */ + onStart: (() => void) | undefined; + /** + * 结束回调 + */ + onDone: (() => void) | undefined; + /** + * 消息回调 + */ + onProgress: ((progress: number, data?: UploadProgressData) => void) | undefined; + /** + * 数据 + */ + data: any; + /** + * 是否结束 + */ + end: boolean; + constructor(uploadOpts: UploadProgressOpts) { + this.progress = 0; + this.end = false; + const mockFn = () => {}; + this.onStart = uploadOpts.onStart || mockFn; + this.onDone = uploadOpts.onDone || mockFn; + this.onProgress = uploadOpts.onProgress || mockFn; + } + start(msg?: string) { + this.progress = 0; + msg && this.info(msg); + this.end = false; + this.onStart?.(); + } + done() { + this.progress = 100; + this.end = true; + this.onDone?.(); + } + set(progress: number, data?: UploadProgressData) { + this.progress = progress; + this.data = data; + this.onProgress?.(progress, data); + console.log('uploadProgress set', progress, data); + } + /** + * 开始回调 + */ + setOnStart(callback: () => void) { + this.onStart = callback; + } + /** + * 结束回调 + */ + setOnDone(callback: () => void) { + this.onDone = callback; + } + /** + * 消息回调 + */ + setOnProgress(callback: (progress: number, data?: UploadProgressData) => void) { + this.onProgress = callback; + } + /** + * 打印信息 + */ + info(msg: string) { + console.log(msg); + } + /** + * 打印错误 + */ + error(msg: string) { + console.error(msg); + } + /** + * 打印警告 + */ + warn(msg: string) { + console.warn(msg); + } +} diff --git a/src/query/query-upload/core/upload.ts b/src/query/query-upload/core/upload.ts new file mode 100644 index 0000000..d545a3d --- /dev/null +++ b/src/query/query-upload/core/upload.ts @@ -0,0 +1,113 @@ +import { randomId } from '../utils/random-id.ts'; +import type { UploadOpts } from './upload-chunk.ts'; +type ConvertOpts = { + appKey?: string; + version?: string; + username?: string; + directory?: string; + /** + * 文件大小限制 + */ + maxSize?: number; + /** + * 文件数量限制 + */ + maxCount?: number; + /** + * 是否不检查应用文件, 默认 true,默认不检测 + */ + noCheckAppFiles?: boolean; +}; + +export const uploadFiles = async (files: File[], opts: ConvertOpts, opts2: UploadOpts) => { + const { directory, appKey, version, username, noCheckAppFiles = true } = opts; + const { uploadProgress, createEventSource, baseUrl = '', token, FormDataFn } = opts2 || {}; + const length = files.length; + const maxSize = opts.maxSize || 20 * 1024 * 1024; // 20MB + const totalSize = files.reduce((acc, file) => acc + file.size, 0); + if (totalSize > maxSize) { + const maxSizeMB = maxSize / 1024 / 1024; + uploadProgress?.error('有文件大小不能超过' + maxSizeMB + 'MB'); + return; + } + const maxCount = opts.maxCount || 10; + if (length > maxCount) { + uploadProgress?.error(`最多只能上传${maxCount}个文件`); + return; + } + uploadProgress?.info(`上传中,共${length}个文件`); + return new Promise((resolve, reject) => { + const formData = new FormDataFn(); + const webkitRelativePath = files[0]?.webkitRelativePath; + const keepDirectory = webkitRelativePath !== ''; + const root = keepDirectory ? webkitRelativePath.split('/')[0] : ''; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (keepDirectory) { + // relativePath 去除第一级 + const webkitRelativePath = file.webkitRelativePath.replace(root + '/', ''); + formData.append('file', file, webkitRelativePath); // 保留文件夹路径 + } else { + formData.append('file', files[i], files[i].name); + } + } + if (directory) { + formData.append('directory', directory); + } + if (appKey && version) { + formData.append('appKey', appKey); + formData.append('version', version); + } + if (username) { + formData.append('username', username); + } + const searchParams = new URLSearchParams(); + const taskId = randomId(); + searchParams.set('taskId', taskId); + + if (noCheckAppFiles) { + searchParams.set('noCheckAppFiles', '1'); + } + const eventSource = new EventSource('/api/s1/events?taskId=' + taskId); + + uploadProgress?.start('上传中...'); + eventSource.onopen = async function (event) { + const res = await fetch('/api/s1/resources/upload?' + searchParams.toString(), { + method: 'POST', + body: formData, + headers: { + 'task-id': taskId, + Authorization: `Bearer ${token}`, + }, + }).then((response) => response.json()); + + console.log('upload success', res); + fetch('/api/s1/events/close?taskId=' + taskId); + eventSource.close(); + uploadProgress?.done(); + resolve(res); + }; + // 监听服务器推送的进度更新 + eventSource.onmessage = function (event) { + console.log('Progress update:', event.data); + const parseIfJson = (data: string) => { + try { + return JSON.parse(data); + } catch (e) { + return data; + } + }; + const receivedData = parseIfJson(event.data); + if (typeof receivedData === 'string') return; + const progress = Number(receivedData.progress); + const progressFixed = progress.toFixed(2); + console.log('progress', progress); + uploadProgress?.set(progress, { ...receivedData, taskId, progressFixed }); + }; + + eventSource.onerror = function (event) { + console.log('eventSource.onerror', event); + reject(event); + }; + }); +}; diff --git a/src/query/query-upload/query-upload-browser.ts b/src/query/query-upload/query-upload-browser.ts new file mode 100644 index 0000000..16e502c --- /dev/null +++ b/src/query/query-upload/query-upload-browser.ts @@ -0,0 +1,51 @@ +import { UploadProgress, UploadProgressData } from './core/upload-progress.ts'; +import { uploadFileChunked } from './core/upload-chunk.ts'; +import { toFile, uploadFiles, randomId } from './query-upload.ts'; + +export { toFile, randomId }; +export { uploadFiles, uploadFileChunked, UploadProgress }; + +type UploadFileProps = { + onStart?: () => void; + onDone?: () => void; + onProgress?: (progress: number, data: UploadProgressData) => void; + onSuccess?: (res: any) => void; + onError?: (err: any) => void; + token?: string; +}; +export type ConvertOpts = { + appKey?: string; + version?: string; + username?: string; + directory?: string; + isPublic?: boolean; + filename?: string; + /** + * 是否不检查应用文件, 默认 true,默认不检测 + */ + noCheckAppFiles?: boolean; +}; + +export const uploadChunk = async (file: File, opts: ConvertOpts, props?: UploadFileProps) => { + const uploadProgress = new UploadProgress({ + onStart: function () { + props?.onStart?.(); + }, + onDone: () => { + props?.onDone?.(); + }, + onProgress: (progress, data) => { + props?.onProgress?.(progress, data!); + }, + }); + const result = await uploadFileChunked(file, opts, { + uploadProgress, + token: props?.token!, + createEventSource: (url: string, searchParams: URLSearchParams) => { + return new EventSource(url + '?' + searchParams.toString()); + }, + FormDataFn: FormData, + }); + + return result; +}; diff --git a/src/query/query-upload/query-upload-node.ts b/src/query/query-upload/query-upload-node.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/query/query-upload/query-upload.ts b/src/query/query-upload/query-upload.ts new file mode 100644 index 0000000..547d20b --- /dev/null +++ b/src/query/query-upload/query-upload.ts @@ -0,0 +1,11 @@ +import { uploadFiles } from './core/upload.ts'; + +import { uploadFileChunked } from './core/upload-chunk.ts'; +import { UploadProgress } from './core/upload-progress.ts'; + +export { uploadFiles, uploadFileChunked, UploadProgress }; + +export * from './utils/to-file.ts'; +export { randomId } from './utils/random-id.ts'; + +export { filterFiles } from './utils/filter-files.ts'; diff --git a/src/query/query-upload/utils/filter-files.ts b/src/query/query-upload/utils/filter-files.ts new file mode 100644 index 0000000..71ab8f0 --- /dev/null +++ b/src/query/query-upload/utils/filter-files.ts @@ -0,0 +1,23 @@ +/** + * 过滤文件, 过滤 .DS_Store, node_modules, 以.开头的文件, 过滤 __开头的文件 + * @param files + * @returns + */ +export const filterFiles = (files: File[]) => { + files = files.filter((file) => { + if (file.webkitRelativePath.startsWith('__MACOSX')) { + return false; + } + // 过滤node_modules + if (file.webkitRelativePath.includes('node_modules')) { + return false; + } + // 过滤文件 .DS_Store + if (file.name === '.DS_Store') { + return false; + } + // 过滤以.开头的文件 + return !file.name.startsWith('.'); + }); + return files; +}; diff --git a/src/query/query-upload/utils/index.ts b/src/query/query-upload/utils/index.ts new file mode 100644 index 0000000..86bd60c --- /dev/null +++ b/src/query/query-upload/utils/index.ts @@ -0,0 +1,3 @@ +export * from './to-file.ts'; +export * from './filter-files.ts'; +export * from './random-id.ts'; diff --git a/src/query/query-upload/utils/random-id.ts b/src/query/query-upload/utils/random-id.ts new file mode 100644 index 0000000..c36e4f0 --- /dev/null +++ b/src/query/query-upload/utils/random-id.ts @@ -0,0 +1,3 @@ +export const randomId = () => { + return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); +}; \ No newline at end of file diff --git a/src/query/query-upload/utils/to-file.ts b/src/query/query-upload/utils/to-file.ts new file mode 100644 index 0000000..1072e24 --- /dev/null +++ b/src/query/query-upload/utils/to-file.ts @@ -0,0 +1,105 @@ +const getFileExtension = (filename: string) => { + return filename.split('.').pop(); +}; +const getFileType = (extension: string) => { + switch (extension) { + case 'js': + return 'text/javascript'; + case 'css': + return 'text/css'; + case 'html': + return 'text/html'; + case 'json': + return 'application/json'; + case 'png': + return 'image/png'; + case 'jpg': + return 'image/jpeg'; + case 'jpeg': + return 'image/jpeg'; + case 'gif': + return 'image/gif'; + case 'svg': + return 'image/svg+xml'; + case 'webp': + return 'image/webp'; + case 'ico': + return 'image/x-icon'; + default: + return 'text/plain'; + } +}; +const checkIsBase64 = (content: string) => { + return content.startsWith('data:'); +}; +/** + * 获取文件的目录和文件名 + * @param filename 文件名 + * @returns 目录和文件名 + */ +export const getDirectoryAndName = (filename: string) => { + if (!filename) { + return null; + } + if (filename.startsWith('.')) { + return null; + } else { + filename = filename.replace(/^\/+/, ''); // Remove all leading slashes + } + const hasDirectory = filename.includes('/'); + if (!hasDirectory) { + return { directory: '', name: filename }; + } + const parts = filename.split('/'); + const name = parts.pop()!; // Get the last part as the file name + const directory = parts.join('/'); // Join the remaining parts as the directory + return { directory, name }; +}; +/** + * 把字符串转为文件流,并返回文件流,根据filename的扩展名,自动设置文件类型. + * 当不是文本类型,自动需要把base64的字符串转为blob + * @param content 字符串 + * @param filename 文件名 + * @returns 文件流 + */ +export const toFile = (content: string, filename: string) => { + // 如果文件名是 a/d/a.js 格式的,则需要把d作为目录,a.js作为文件名 + const directoryAndName = getDirectoryAndName(filename); + if (!directoryAndName) { + throw new Error('Invalid filename'); + } + const { name } = directoryAndName; + const extension = getFileExtension(name); + if (!extension) { + throw new Error('Invalid filename'); + } + const isBase64 = checkIsBase64(content); + const type = getFileType(extension); + + if (isBase64) { + // Decode base64 string + const base64Data = content.split(',')[1]; // Remove the data URL prefix + const byteCharacters = atob(base64Data); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { type }); + return new File([blob], filename, { type }); + } else { + const blob = new Blob([content], { type }); + return new File([blob], filename, { type }); + } +}; + +/** + * 把字符串转为文本文件 + * @param content 字符串 + * @param filename 文件名 + * @returns 文件流 + */ +export const toTextFile = (content: string = 'keep directory exist', filename: string = 'keep.txt') => { + const file = toFile(content, filename); + return file; +};