From 8d7561d58cf6e6373258970182bba2c9136d53f1 Mon Sep 17 00:00:00 2001 From: wyh Date: Fri, 29 Aug 2025 14:01:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9A=82=E5=AD=98.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 685 +++++++++++++----- package.json | 8 +- src/components/AddContextModal.tsx | 408 +++++++++++ src/components/Chat.tsx | 15 +- .../chat-components/ChatContextMenu.tsx | 37 +- src/components/chat-components/ChatInput.tsx | 6 + .../chat-components/ContextControl.tsx | 105 ++- src/utils/addContextUtils.ts | 163 +++++ 8 files changed, 1225 insertions(+), 202 deletions(-) create mode 100644 src/components/AddContextModal.tsx create mode 100644 src/utils/addContextUtils.ts diff --git a/package-lock.json b/package-lock.json index 767d5f54..0e860bdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,20 +26,20 @@ "@langchain/openai": "^0.6.6", "@langchain/xai": "^0.0.2", "@orama/orama": "^3.0.0-rc-2", - "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.7", "@tabler/icons-react": "^2.14.0", "async-mutex": "^0.5.0", @@ -5023,19 +5023,19 @@ } }, "node_modules/@radix-ui/react-checkbox": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz", - "integrity": "sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -5053,15 +5053,15 @@ } }, "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -5073,13 +5073,29 @@ } } }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", - "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.1" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -5096,13 +5112,85 @@ } } }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", - "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -5959,26 +6047,26 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz", - "integrity": "sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.3", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.1", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.1", - "@radix-ui/react-portal": "1.1.3", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-slot": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "^2.6.1" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", @@ -5996,18 +6084,18 @@ } }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", - "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.1" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -6025,9 +6113,24 @@ } }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6040,16 +6143,16 @@ } }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", - "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -6066,15 +6169,30 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", - "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-use-callback-ref": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -6091,22 +6209,40 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", - "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -6124,13 +6260,37 @@ } }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", - "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -6148,12 +6308,12 @@ } }, "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", - "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.1" + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -6170,13 +6330,29 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", - "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -6188,17 +6364,92 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", - "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.2" + "use-sidecar": "^1.1.3" }, "engines": { "node": ">=10" @@ -6542,17 +6793,17 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz", - "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.2", + "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -6579,9 +6830,9 @@ "license": "MIT" }, "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", - "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": { @@ -6630,9 +6881,9 @@ } }, "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -7105,19 +7356,19 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz", - "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-roving-focus": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -7135,21 +7386,21 @@ } }, "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", - "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", - "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-slot": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -7167,9 +7418,9 @@ } }, "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -7181,13 +7432,85 @@ } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -7205,20 +7528,20 @@ } }, "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", - "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-collection": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -7235,13 +7558,29 @@ } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -7253,6 +7592,21 @@ } } }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", @@ -21540,9 +21894,10 @@ } }, "node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" @@ -21551,8 +21906,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { diff --git a/package.json b/package.json index 6bb13cc6..8b8f8ae1 100644 --- a/package.json +++ b/package.json @@ -93,20 +93,20 @@ "@langchain/openai": "^0.6.6", "@langchain/xai": "^0.0.2", "@orama/orama": "^3.0.0-rc-2", - "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.7", "@tabler/icons-react": "^2.14.0", "async-mutex": "^0.5.0", diff --git a/src/components/AddContextModal.tsx b/src/components/AddContextModal.tsx new file mode 100644 index 00000000..98b79e75 --- /dev/null +++ b/src/components/AddContextModal.tsx @@ -0,0 +1,408 @@ +import React, { useMemo, useState, useEffect } from "react"; +import { App, TFile } from "obsidian"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Checkbox } from "@/components/ui/checkbox"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { SearchBar } from "@/components/ui/SearchBar"; +import { FileText, Folder, Info, Trash2, X } from "lucide-react"; +import { ChainType } from "@/chainFactory"; +import { TruncatedText } from "@/components/TruncatedText"; +import { getFolders, getMarkdownFiles, getOtherFiles } from "@/utils/addContextUtils"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +interface AddContextModalProps { + app: App; + chainType: ChainType; + excludeNotePaths: string[]; + activeNote: TFile | null; + contextNotes: TFile[]; // Currently selected notes + contextFolders: string[]; // Currently selected folders + onNoteSelect: (note: TFile | string) => void; // Support files and folders + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; +} + +export function AddContextModal({ + app, + chainType, + excludeNotePaths, + activeNote, + contextNotes, + contextFolders, + onNoteSelect, + isOpen, + onClose, + children, +}: AddContextModalProps) { + const [searchQuery, setSearchQuery] = useState(""); + const [activeTab, setActiveTab] = useState("markdown"); + const [selectedFiles, setSelectedFiles] = useState([]); + + // Initialize selected files with current context when modal opens + useEffect(() => { + if (isOpen) { + const contextFilePaths = contextNotes.map((note) => note.path); + const initialSelected = [...contextFilePaths, ...contextFolders]; + console.log("initialSelected", initialSelected); + setSelectedFiles(initialSelected); + } + }, [isOpen, contextNotes, contextFolders]); + + const allFiles = useMemo(() => { + // Don't exclude context files - we want to show them as selected + const contextNotePaths = contextNotes.map((note) => note.path); + const currentExcludePaths = excludeNotePaths.filter((path) => !contextNotePaths.includes(path)); + + const markdownFiles = getMarkdownFiles(app, chainType, currentExcludePaths, activeNote); + const folders = getFolders(app); + const otherFiles = getOtherFiles(app, chainType, currentExcludePaths, activeNote); + + return [...markdownFiles, ...folders, ...otherFiles]; + }, [app, chainType, excludeNotePaths, activeNote, contextNotes]); + + const filteredFiles = useMemo(() => { + let files = allFiles; + + // Filter by type + files = files.filter((file) => { + if (activeTab === "markdown") { + return file.type === "markdown"; + } else if (activeTab === "folder") { + return file.type === "folder"; + } else if (activeTab === "other") { + return file.type === "other"; + } + return false; + }); + + // Filter by search query + if (searchQuery) { + files = files.filter( + (file) => + file.name.toLowerCase().includes(searchQuery.toLowerCase()) || + file.path.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + + return files; + }, [allFiles, activeTab, searchQuery]); + + const getFilteredCounts = useMemo(() => { + let searchFilteredFiles = allFiles; + + // Filter only by search queries, not by type. + if (searchQuery) { + searchFilteredFiles = allFiles.filter( + (file) => + file.name.toLowerCase().includes(searchQuery.toLowerCase()) || + file.path.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + + return { + markdown: searchFilteredFiles.filter((f) => f.type === "markdown").length, + folder: searchFilteredFiles.filter((f) => f.type === "folder").length, + other: searchFilteredFiles.filter((f) => f.type === "other").length, + }; + }, [allFiles, searchQuery]); + + const getFileIcon = (type: string) => { + switch (type) { + case "markdown": + return ; + case "folder": + return ; + default: + return ; + } + }; + + const toggleFileSelection = (fileId: string) => { + if (selectedFiles.includes(fileId)) { + setSelectedFiles(selectedFiles.filter((id) => id !== fileId)); + } else { + setSelectedFiles([...selectedFiles, fileId]); + } + }; + + const clearAllSelections = () => { + // Remove all currently selected files and folders from context + selectedFiles.forEach((fileId) => { + // Check if it's a folder + if (contextFolders.includes(fileId)) { + // Remove folder from context + onNoteSelect(`REMOVE_FOLDER:${fileId}`); + } else { + // Remove file from context + onNoteSelect(`REMOVE_FILE:${fileId}`); + } + }); + + // Clear selection state and close modal + setSelectedFiles([]); + onClose(); + }; + + // Processing file added to context + const handleAddToContext = () => { + const contextFilePaths = contextNotes.map((note) => note.path); + const initialSelected = [...contextFilePaths, ...contextFolders]; + + // Files/folders to add (newly selected) + const toAdd = selectedFiles.filter((fileId) => !initialSelected.includes(fileId)); + // Files/folders to remove (previously selected but now unselected) + const toRemove = initialSelected.filter((fileId) => !selectedFiles.includes(fileId)); + + // Add new selections + toAdd.forEach((fileId) => { + const file = allFiles.find((f) => f.id === fileId); + if (file) { + if (file.type === "folder") { + // For folders, pass the folder path + onNoteSelect(file.path); + } else { + // For files, pass the TFile + const tFile = app.vault.getAbstractFileByPath(file.path); + if (tFile instanceof TFile) { + onNoteSelect(tFile); + } + } + } + }); + + // Remove unselected items by calling their respective remove handlers + toRemove.forEach((fileId) => { + // Check if it's a folder + if (contextFolders.includes(fileId)) { + // This is a folder - we need a way to remove it + // For now, we'll call onNoteSelect with a special format to indicate removal + onNoteSelect(`REMOVE_FOLDER:${fileId}`); + } else { + // This is a file - we need a way to remove it + onNoteSelect(`REMOVE_FILE:${fileId}`); + } + }); + + setSelectedFiles([]); + onClose(); + }; + + // Get statistical information + /* + const getStats = () => { + const markdownCount = selectedFiles.filter( + (id) => allFiles.find((f) => f.id === id)?.type === "markdown" + ).length; + const folderCount = selectedFiles.filter( + (id) => allFiles.find((f) => f.id === id)?.type === "folder" + ).length; + const otherCount = selectedFiles.filter( + (id) => allFiles.find((f) => f.id === id)?.type === "other" + ).length; + + return { markdownCount, folderCount, otherCount }; + }; +*/ + + /* const stats = getStats();*/ + + return ( + + {children} + +
+
+
Add Context
+ {selectedFiles.length > 0 && ( + + )} +
+ +
+
+ +
+ + {/* 选择统计 */} + {/* + {selectedFiles.length > 0 && ( +
+ Selected: + {stats.markdownCount > 0 && ( + + {stats.markdownCount} MD + + )} + {stats.folderCount > 0 && ( + + {stats.folderCount} Folders + + )} + {stats.otherCount > 0 && ( + + {stats.otherCount} Other + + )} +
+ )} +*/} + + + + + + MD ({getFilteredCounts.markdown}) + + +
+ + Folder ({getFilteredCounts.folder}) + + + +   + + + + + Adding folders will only include files in the current folder + + +
+
+ + + Other ({getFilteredCounts.other}) + +
+ + + +
+ {filteredFiles.length === 0 ? ( +
+ {searchQuery + ? "No files found matching your search." + : "No files available."} +
+ ) : ( + filteredFiles.map((file) => { + const isSelected = selectedFiles.includes(file.id); + return ( +
toggleFileSelection(file.id)} + > + toggleFileSelection(file.id)} + onClick={(e) => e.stopPropagation()} + className="tw-size-3" + /> + +
+
{getFileIcon(file.type)}
+
+
+ + {file.name} + {file.isActive && ( + + (Current) + + )} + {file.extension === "pdf" && ( + + (PDF) + + )} + {file.extension === "canvas" && ( + + (Canvas) + + )} + +
+ + + {file.path} + +
+
+ + {isSelected && ( +
+ +
+ )} +
+ ); + }) + )} +
+
+
+
+
+ +
+
+ {selectedFiles.length} file{selectedFiles.length !== 1 ? "s" : ""} selected +
+
+ + +
+
+
+
+
+ ); +} diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index 7ec012c2..91fb647f 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -36,6 +36,7 @@ import { updateSetting, useSettingsValue } from "@/settings/model"; import { ChatUIState } from "@/state/ChatUIState"; import { FileParserManager } from "@/tools/FileParserManager"; import { err2String, isPlusChain } from "@/utils"; +import { getFilesInFolder } from "@/utils/addContextUtils"; import { Buffer } from "buffer"; import { Notice, TFile } from "obsidian"; import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; @@ -78,6 +79,7 @@ const Chat: React.FC = ({ const [loading, setLoading] = useState(false); const [loadingMessage, setLoadingMessage] = useState(LOADING_MESSAGES.DEFAULT); const [contextNotes, setContextNotes] = useState([]); + const [contextFolders, setContextFolders] = useState([]); const [includeActiveNote, setIncludeActiveNote] = useState(false); const [selectedImages, setSelectedImages] = useState([]); const [showChatUI, setShowChatUI] = useState(false); @@ -192,7 +194,15 @@ const Chat: React.FC = ({ } // Prepare context notes and deduplicate by path - const allNotes = [...(passedContextNotes || []), ...contextNotes]; + + // Get files from context folders + const folderFiles: TFile[] = []; + contextFolders.forEach((folderPath) => { + const filesInFolder = getFilesInFolder(app, folderPath, currentChain); + folderFiles.push(...filesInFolder); + }); + + const allNotes = [...(passedContextNotes || []), ...contextNotes, ...folderFiles]; const notes = allNotes.filter( (note, index, array) => array.findIndex((n) => n.path === note.path) === index ); @@ -549,6 +559,7 @@ const Chat: React.FC = ({ // Additional UI state reset specific to this component safeSet.setCurrentAiMessage(""); setContextNotes([]); + setContextFolders([]); clearSelectedTextContexts(); // Only modify includeActiveNote if in a non-COPILOT_PLUS_CHAIN mode // In COPILOT_PLUS_CHAIN mode, respect the settings.includeActiveNoteAsContext value @@ -652,6 +663,8 @@ const Chat: React.FC = ({ app={app} contextNotes={contextNotes} setContextNotes={setContextNotes} + contextFolders={contextFolders} + setContextFolders={setContextFolders} includeActiveNote={includeActiveNote} setIncludeActiveNote={setIncludeActiveNote} mention={mention} diff --git a/src/components/chat-components/ChatContextMenu.tsx b/src/components/chat-components/ChatContextMenu.tsx index c54f7852..f2ff19a8 100644 --- a/src/components/chat-components/ChatContextMenu.tsx +++ b/src/components/chat-components/ChatContextMenu.tsx @@ -1,4 +1,4 @@ -import { AlertCircle, CheckCircle, CircleDashed, Loader2, Plus, X } from "lucide-react"; +import { AlertCircle, CheckCircle, CircleDashed, Loader2, Plus, X, Folder } from "lucide-react"; import { TFile } from "obsidian"; import React from "react"; import { Button } from "@/components/ui/button"; @@ -12,10 +12,12 @@ import { useProjectContextStatus } from "@/hooks/useProjectContextStatus"; interface ChatContextMenuProps { activeNote: TFile | null; contextNotes: TFile[]; + contextFolders: string[]; contextUrls: string[]; selectedTextContexts?: SelectedTextContext[]; onAddContext: () => void; onRemoveContext: (path: string) => void; + onRemoveFolder: (path: string) => void; onRemoveUrl: (url: string) => void; onRemoveSelectedText?: (id: string) => void; showProgressCard: () => void; @@ -70,6 +72,33 @@ function ContextUrl({ url, onRemoveUrl }: { url: string; onRemoveUrl: (url: stri ); } +function ContextFolder({ + folderPath, + onRemoveFolder, +}: { + folderPath: string; + onRemoveFolder: (path: string) => void; +}) { + const folderName = folderPath.split("/").pop() || folderPath; + return ( + +
+ + {folderName} +
+ +
+ ); +} + function ContextSelection({ selectedText, onRemoveSelectedText, @@ -104,10 +133,12 @@ function ContextSelection({ export const ChatContextMenu: React.FC = ({ activeNote, contextNotes, + contextFolders, contextUrls, selectedTextContexts = [], onAddContext, onRemoveContext, + onRemoveFolder, onRemoveUrl, onRemoveSelectedText, showProgressCard, @@ -133,6 +164,7 @@ export const ChatContextMenu: React.FC = ({ const hasContext = uniqueNotes.length > 0 || + contextFolders.length > 0 || uniqueUrls.length > 0 || selectedTextContexts.length > 0 || !!activeNote; @@ -181,6 +213,9 @@ export const ChatContextMenu: React.FC = ({ onRemoveContext={onRemoveContext} /> ))} + {contextFolders.map((folderPath) => ( + + ))} {uniqueUrls.map((url) => ( ))} diff --git a/src/components/chat-components/ChatInput.tsx b/src/components/chat-components/ChatInput.tsx index 489661e0..eacd016e 100644 --- a/src/components/chat-components/ChatInput.tsx +++ b/src/components/chat-components/ChatInput.tsx @@ -64,6 +64,8 @@ interface ChatInputProps { app: App; contextNotes: TFile[]; setContextNotes: React.Dispatch>; + contextFolders: string[]; + setContextFolders: React.Dispatch>; includeActiveNote: boolean; setIncludeActiveNote: (include: boolean) => void; mention: Mention; @@ -87,6 +89,8 @@ const ChatInput = forwardRef<{ focus: () => void }, ChatInputProps>( app, contextNotes, setContextNotes, + contextFolders, + setContextFolders, includeActiveNote, setIncludeActiveNote, mention, @@ -520,6 +524,8 @@ const ChatInput = forwardRef<{ focus: () => void }, ChatInputProps>( excludeNotePaths={excludeNotePaths} contextNotes={contextNotes} setContextNotes={setContextNotes} + contextFolders={contextFolders} + setContextFolders={setContextFolders} includeActiveNote={includeActiveNote} setIncludeActiveNote={setIncludeActiveNote} activeNote={currentActiveNote} diff --git a/src/components/chat-components/ContextControl.tsx b/src/components/chat-components/ContextControl.tsx index dc5e7f96..5a08c06a 100644 --- a/src/components/chat-components/ContextControl.tsx +++ b/src/components/chat-components/ContextControl.tsx @@ -1,10 +1,8 @@ -import { App } from "obsidian"; -import React from "react"; - +import { App, TFile } from "obsidian"; +import React, { useState } from "react"; import { useChainType } from "@/aiParams"; -import { AddContextNoteModal } from "@/components/modals/AddContextNoteModal"; +import { AddContextModal } from "@/components/AddContextModal"; import { SelectedTextContext } from "@/types/message"; -import { TFile } from "obsidian"; import { ChatContextMenu } from "./ChatContextMenu"; interface ChatControlsProps { @@ -12,6 +10,8 @@ interface ChatControlsProps { excludeNotePaths: string[]; contextNotes: TFile[]; setContextNotes: React.Dispatch>; + contextFolders: string[]; + setContextFolders: React.Dispatch>; includeActiveNote: boolean; setIncludeActiveNote: React.Dispatch>; activeNote: TFile | null; @@ -27,6 +27,8 @@ const ContextControl: React.FC = ({ excludeNotePaths, contextNotes, setContextNotes, + contextFolders, + setContextFolders, includeActiveNote, setIncludeActiveNote, activeNote, @@ -37,22 +39,42 @@ const ContextControl: React.FC = ({ showProgressCard, }) => { const [selectedChain] = useChainType(); + const [isAddContextModalOpen, setIsAddContextModalOpen] = useState(false); const handleAddContext = () => { - new AddContextNoteModal({ - app, - onNoteSelect: (note) => { - if (activeNote && note.path === activeNote.path) { - setIncludeActiveNote(true); - // Remove the note from contextNotes if it exists there - setContextNotes((prev) => prev.filter((n) => n.path !== note.path)); - } else { - // Add wasAddedManually flag to distinguish from reference-added notes - setContextNotes((prev) => [...prev, Object.assign(note, { wasAddedManually: true })]); - } - }, - excludeNotePaths, - chainType: selectedChain, - }).open(); + setIsAddContextModalOpen(true); + }; + + // Callback for handling file/folder selection + const handleNoteSelect = (noteOrPath: TFile | string) => { + if (typeof noteOrPath === "string") { + // Handle special removal commands from AddContextModal + if (noteOrPath.startsWith("REMOVE_FOLDER:")) { + const folderPath = noteOrPath.replace("REMOVE_FOLDER:", ""); + handleRemoveFolder(folderPath); + return; + } else if (noteOrPath.startsWith("REMOVE_FILE:")) { + const filePath = noteOrPath.replace("REMOVE_FILE:", ""); + handleRemoveContext(filePath); + return; + } + + // Processing folder paths + const folderPath = noteOrPath; + if (!contextFolders.includes(folderPath)) { + setContextFolders((prev) => [...prev, folderPath]); + } + } else { + // Processing file + const note = noteOrPath; + if (activeNote && note.path === activeNote.path) { + setIncludeActiveNote(true); + // Remove the note from contextNotes if it exists there + setContextNotes((prev) => prev.filter((n) => n.path !== note.path)); + } else { + // Add wasAddedManually flag to distinguish from reference-added notes + setContextNotes((prev) => [...prev, Object.assign(note, { wasAddedManually: true })]); + } + } }; const handleRemoveContext = (path: string) => { @@ -73,20 +95,41 @@ const ContextControl: React.FC = ({ } }; + const handleRemoveFolder = (folderPath: string) => { + setContextFolders((prev) => prev.filter((path) => path !== folderPath)); + }; + // Context menu is now available for all chain types return ( - + <> + + setIsAddContextModalOpen(false)} + > +
+ + ); }; diff --git a/src/utils/addContextUtils.ts b/src/utils/addContextUtils.ts new file mode 100644 index 00000000..ff8cc0a8 --- /dev/null +++ b/src/utils/addContextUtils.ts @@ -0,0 +1,163 @@ +import { ChainType } from "@/chainFactory"; +import { App, TFile } from "obsidian"; +import { isAllowedFileForChainContext } from "@/utils"; +import { extractAppIgnoreSettings, shouldIndexFile } from "@/search/searchUtils"; + +interface FileItem { + id: string; + name: string; + type: "markdown" | "folder" | "other"; + path: string; + extension?: string; + isActive?: boolean; +} + +export function getMarkdownFiles( + app: App, + chainType: ChainType, + excludeNotePaths: string[], + activeNote: TFile | null +): FileItem[] { + const recentFiles = app.workspace + .getLastOpenFiles() + .map((filePath) => app.vault.getAbstractFileByPath(filePath)) + .filter( + (file): file is TFile => + file instanceof TFile && + (file.extension === "md" || file.extension === "canvas") && + isAllowedFileForChainContext(file, chainType) && + !excludeNotePaths.includes(file.path) && + file.path !== activeNote?.path + ); + + // Get all other files + const allFiles = app.vault + .getFiles() + .filter( + (file) => + (file.extension === "md" || file.extension === "canvas") && + isAllowedFileForChainContext(file, chainType) + ); + + const otherFiles = allFiles.filter( + (file) => + !recentFiles.some((recent) => recent.path === file.path) && + !excludeNotePaths.includes(file.path) && + file.path !== activeNote?.path + ); + + // Combine active files (if they exist and are permitted) + const activeNoteArray = + activeNote && + (activeNote.extension === "md" || activeNote.extension === "canvas") && + isAllowedFileForChainContext(activeNote, chainType) + ? [activeNote] + : []; + + const allOrderedFiles = [...activeNoteArray, ...recentFiles, ...otherFiles]; + + return allOrderedFiles.map((file, index) => ({ + id: file.path, + name: file.basename, + type: "markdown", + path: file.path, + extension: file.extension, + isActive: file.path === activeNote?.path, + })); +} + +export function getFolders(app: App): FileItem[] { + const folderSet = new Set(); + const ignoredFolders = extractAppIgnoreSettings(app); + + // Get all loaded files + app.vault.getAllLoadedFiles().forEach((file) => { + if (file.parent?.path && file.parent.path !== "/") { + // Check if the folder is ignored + const shouldInclude = !ignoredFolders.some( + (ignored) => file.parent!.path === ignored || file.parent!.path.startsWith(ignored + "/") + ); + + if (shouldInclude) { + folderSet.add(file.parent.path); + } + } + }); + + return Array.from(folderSet).map((folderPath) => ({ + id: folderPath, + name: folderPath.split("/").pop() || folderPath, + type: "folder", + path: folderPath, + })); +} + +export function getOtherFiles( + app: App, + chainType: ChainType, + excludeNotePaths: string[], + activeNote: TFile | null +): FileItem[] { + console.log(excludeNotePaths); + const allFiles = app.vault + .getFiles() + .filter( + (file) => + file.extension !== "md" && + file.extension !== "canvas" && + isAllowedFileForChainContext(file, chainType) && + !excludeNotePaths.includes(file.path) && + file.path !== activeNote?.path + ); + + return allFiles.map((file) => ({ + id: file.path, + name: file.basename, + type: "other", + path: file.path, + extension: file.extension, + isActive: false, + })); +} + +/** + * Get all files in a specific folder path + * @param app - Obsidian App instance + * @param folderPath - The folder path to search in + * @param chainType - Optional chain type for permission filtering + * @param excludeNotePaths - Optional array of file paths to exclude + * @returns Array of FileItem objects representing files in the folder + */ +export function getFilesInFolder( + app: App, + folderPath: string, + chainType?: ChainType, + excludeNotePaths: string[] = [] +): TFile[] { + // Construct PatternCategory object for folder pattern + const folderPattern = { + folderPatterns: [folderPath], + }; + + // Get all files and apply filtering + const allFiles = app.vault.getFiles().filter((file) => { + // Check if file is in the specified folder + const isInFolder = shouldIndexFile(file, folderPattern, null, true); + if (!isInFolder) return false; + + // Check file permissions (if chainType is provided) + if (chainType && !isAllowedFileForChainContext(file, chainType)) { + return false; + } + + // Exclude specified file paths + if (excludeNotePaths.includes(file.path)) { + return false; + } + + return true; + }); + + // Convert to FileItem format + return allFiles; +}