feat: Support project context preview. (#1551)

* feat: Support project context preview.
* feat: add test file.
* fix: Fix state update issues and remove extension section.
This commit is contained in:
Emt-lin 2025-06-25 03:35:48 +08:00 committed by GitHub
parent b33f5cf321
commit 4539338dfe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 2325 additions and 210 deletions

615
package-lock.json generated
View file

@ -32,13 +32,14 @@
"@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-scroll-area": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.6",
"@radix-ui/react-tooltip": "^1.2.7",
"@tabler/icons-react": "^2.14.0",
"async-mutex": "^0.5.0",
"axios": "^1.3.4",
@ -65,6 +66,7 @@
"react-dom": "^18.2.0",
"react-dropzone": "^14.3.5",
"react-markdown": "^9.0.1",
"react-resizable-panels": "^3.0.2",
"react-syntax-highlighter": "^15.5.0",
"sse": "github:mpetazzoni/sse.js",
"tailwind-merge": "^2.5.5",
@ -5175,6 +5177,24 @@
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
@ -6254,6 +6274,24 @@
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
@ -6374,20 +6412,20 @@
}
},
"node_modules/@radix-ui/react-scroll-area": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz",
"integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==",
"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==",
"license": "MIT",
"dependencies": {
"@radix-ui/number": "1.1.0",
"@radix-ui/primitive": "1.1.1",
"@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-presence": "1.1.2",
"@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/number": "1.1.1",
"@radix-ui/primitive": "1.1.2",
"@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-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@ -6404,16 +6442,22 @@
}
}
},
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": {
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
"integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
"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==",
"license": "MIT"
},
"node_modules/@radix-ui/react-scroll-area/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": "*",
@ -6425,13 +6469,44 @@
}
}
},
"node_modules/@radix-ui/react-scroll-area/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-scroll-area/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-scroll-area/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-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==",
"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": "*",
@ -6448,14 +6523,49 @@
}
}
},
"node_modules/@radix-ui/react-scroll-area/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-scroll-area/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-scroll-area/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-scroll-area/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"
@ -6509,6 +6619,24 @@
}
}
},
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
},
"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-select/node_modules/@radix-ui/react-visually-hidden": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz",
@ -6532,6 +6660,52 @@
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
"integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slider": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.1.tgz",
@ -6566,12 +6740,12 @@
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0"
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
@ -6583,6 +6757,21 @@
}
}
},
"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-switch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz",
@ -6762,23 +6951,23 @@
}
},
"node_modules/@radix-ui/react-tooltip": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz",
"integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz",
"integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==",
"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-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",
"@radix-ui/react-visually-hidden": "1.1.1"
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.10",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-popper": "1.2.7",
"@radix-ui/react-portal": "1.1.9",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-visually-hidden": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
@ -6796,18 +6985,18 @@
}
},
"node_modules/@radix-ui/react-tooltip/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.2",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-tooltip/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": "*",
@ -6825,9 +7014,24 @@
}
},
"node_modules/@radix-ui/react-tooltip/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-tooltip/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": "*",
@ -6840,16 +7044,16 @@
}
},
"node_modules/@radix-ui/react-tooltip/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.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
"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.2",
"@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": "*",
@ -6866,22 +7070,40 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/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-tooltip/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.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
"integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==",
"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": "*",
@ -6899,13 +7121,37 @@
}
},
"node_modules/@radix-ui/react-tooltip/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-tooltip/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==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@ -6923,12 +7169,12 @@
}
},
"node_modules/@radix-ui/react-tooltip/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": "*",
@ -6945,13 +7191,29 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"node_modules/@radix-ui/react-tooltip/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-tooltip/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": "*",
@ -6963,6 +7225,81 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/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-tooltip/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/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-tooltip/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-tooltip/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-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@ -6994,6 +7331,39 @@
}
}
},
"node_modules/@radix-ui/react-use-effect-event": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
@ -7075,12 +7445,12 @@
}
},
"node_modules/@radix-ui/react-visually-hidden": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
"integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
"integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.1"
"@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
@ -7097,28 +7467,13 @@
}
}
},
"node_modules/@radix-ui/react-visually-hidden/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==",
"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-visually-hidden/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": "*",
@ -7135,24 +7490,6 @@
}
}
},
"node_modules/@radix-ui/react-visually-hidden/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==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/rect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
@ -18688,6 +19025,16 @@
}
}
},
"node_modules/react-resizable-panels": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.2.tgz",
"integrity": "sha512-j4RNII75fnHkLnbsTb5G5YsDvJsSEZrJK2XSF2z0Tc2jIonYlIVir/Yh/5LvcUFCfs1HqrMAoiBFmIrRjC4XnA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",

View file

@ -99,13 +99,14 @@
"@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-scroll-area": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.6",
"@radix-ui/react-tooltip": "^1.2.7",
"@tabler/icons-react": "^2.14.0",
"async-mutex": "^0.5.0",
"axios": "^1.3.4",
@ -132,6 +133,7 @@
"react-dom": "^18.2.0",
"react-dropzone": "^14.3.5",
"react-markdown": "^9.0.1",
"react-resizable-panels": "^3.0.2",
"react-syntax-highlighter": "^15.5.0",
"sse": "github:mpetazzoni/sse.js",
"tailwind-merge": "^2.5.5",

View file

@ -1,5 +1,5 @@
import { ProjectConfig, setCurrentProject } from "@/aiParams";
import { AddProjectModal } from "@/components/modals/AddProjectModal";
import { AddProjectModal } from "@/components/modals/project/AddProjectModal";
import { ConfirmModal } from "@/components/modals/ConfirmModal";
import { Button } from "@/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
@ -11,6 +11,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { SearchBar } from "@/components/ui/SearchBar";
import { cn } from "@/lib/utils";
import { logError } from "@/logger";
import { updateSetting } from "@/settings/model";
@ -25,12 +26,10 @@ import {
Search,
Trash2,
X,
XCircle,
} from "lucide-react";
import { App, Notice } from "obsidian";
import React, { memo, useEffect, useMemo, useState } from "react";
import { filterProjects } from "@/utils/projectUtils";
import { Input } from "../ui/input";
function ProjectItem({
project,
@ -356,23 +355,11 @@ export const ProjectList = memo(
{projects.length > 0 && (
<div className="tw-px-4 tw-pb-2 tw-pt-3">
<div className="tw-relative">
<Input
type="text"
placeholder="Search projects..."
<SearchBar
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onChange={setSearchQuery}
placeholder="Search projects..."
/>
{searchQuery && (
<Button
variant={"secondary"}
onClick={() => setSearchQuery("")}
className="tw-absolute tw-right-8 tw-top-1/2 tw-size-4 -tw-translate-y-1/2 tw-transform tw-rounded-full tw-p-0 tw-transition-colors"
aria-label="Clear search"
>
<XCircle className="tw-size-4 tw-text-muted/60 hover:tw-text-accent-hover" />
</Button>
)}
<Search className="tw-absolute tw-right-3 tw-top-1/2 tw-size-4 -tw-translate-y-1/2 tw-transform tw-text-muted" />
</div>
</div>
)}

View file

@ -25,6 +25,8 @@ function ExtensionInputModalContent({
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
validateAndConfirm(extension);
}
};

View file

@ -4,8 +4,11 @@ import React, { useState } from "react";
import { createRoot, Root } from "react-dom/client";
import {
categorizePatterns,
getDecodedPatterns,
createPatternSettingsValue,
getDecodedPatterns,
getExtensionPattern,
getFilePattern,
getTagPattern,
} from "@/search/searchUtils";
import { File, FileText, Folder, Tag, Wrench, X } from "lucide-react";
import {
@ -148,7 +151,7 @@ function PatternMatchingModalContent({
<DropdownMenuItem
onSelect={() => {
new TagSearchModal(app, (tag) => {
const tagPattern = `#${tag}`;
const tagPattern = getTagPattern(tag);
if (tagPatterns.includes(tagPattern)) {
return;
}
@ -185,7 +188,7 @@ function PatternMatchingModalContent({
new AddContextNoteModal({
app,
onNoteSelect: (note) => {
const notePattern = `[[${note.basename}]]`;
const notePattern = getFilePattern(note);
if (notePatterns.includes(notePattern)) {
return;
}
@ -206,7 +209,7 @@ function PatternMatchingModalContent({
<DropdownMenuItem
onSelect={() => {
new ExtensionInputModal(app, (extension) => {
const extensionPattern = `*.${extension}`;
const extensionPattern = getExtensionPattern(extension);
if (extensionPatterns.includes(extensionPattern)) {
return;
}

View file

@ -10,6 +10,9 @@ import {
categorizePatterns,
createPatternSettingsValue,
getDecodedPatterns,
getExtensionPattern,
getFilePattern,
getTagPattern,
} from "@/search/searchUtils";
import { File, FileText, Folder, Tag, Wrench, X } from "lucide-react";
import { App, Modal, TFile } from "obsidian";
@ -148,7 +151,7 @@ function ProjectPatternMatchingModalContent({
<DropdownMenuItem
onSelect={() => {
new TagSearchModal(app, (tag) => {
const tagPattern = `#${tag}`;
const tagPattern = getTagPattern(tag);
if (tagPatterns.includes(tagPattern)) {
return;
}
@ -185,7 +188,7 @@ function ProjectPatternMatchingModalContent({
new ProjectFileSelectModal({
app,
onFileSelect: (file: TFile) => {
const filePattern = `[[${file.basename}]]`;
const filePattern = getFilePattern(file);
if (notePatterns.includes(filePattern)) {
return;
}
@ -206,7 +209,7 @@ function ProjectPatternMatchingModalContent({
<DropdownMenuItem
onSelect={() => {
new ExtensionInputModal(app, (extension) => {
const extensionPattern = `*.${extension}`;
const extensionPattern = getExtensionPattern(extension);
if (extensionPatterns.includes(extensionPattern)) {
return;
}

View file

@ -1,5 +1,4 @@
import { ProjectConfig } from "@/aiParams";
import { ProjectPatternMatchingModal } from "@/components/modals/ProjectPatternMatchingModal";
import { Button } from "@/components/ui/button";
import { FormField } from "@/components/ui/form-field";
import { Input } from "@/components/ui/input";
@ -14,6 +13,9 @@ import { HelpCircle } from "lucide-react";
import { App, Modal, Notice } from "obsidian";
import React, { useState } from "react";
import { createRoot, Root } from "react-dom/client";
import { ContextManageModal } from "@/components/modals/project/context-manage-modal";
import { TruncatedText } from "@/components/TruncatedText";
import { getDecodedPatterns } from "@/search/searchUtils";
interface AddProjectModalContentProps {
initialProject?: ProjectConfig;
@ -31,7 +33,7 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
inclusions: false,
});
const [formData, setFormData] = useState<Partial<ProjectConfig>>(
const [formData, setFormData] = useState<ProjectConfig>(
initialProject || {
id: randomUUID(),
name: "",
@ -53,6 +55,23 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
}
);
const showContext = getDecodedPatterns(
formData.contextSource.inclusions || formData.contextSource.exclusions || "nothing"
)
.reverse()
.join(",");
const handleEditProjectContext = (originP: ProjectConfig) => {
const modal = new ContextManageModal(
app,
async (updatedProject: ProjectConfig) => {
setFormData(updatedProject);
},
originP
);
modal.open();
};
const isFormValid = () => {
return formData.name && formData.projectModelKey;
};
@ -130,7 +149,9 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
return (
<div className="tw-flex tw-flex-col tw-gap-2 tw-p-4">
<div className="tw-mb-2 tw-text-xl tw-font-bold tw-text-normal">Add New Project</div>
<div className="tw-mb-2 tw-text-xl tw-font-bold tw-text-normal">
{initialProject ? "Edit Project" : "New Project"}
</div>
<div className="tw-flex tw-flex-col tw-gap-2">
<FormField
@ -236,7 +257,7 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
<FormField
label={
<div className="tw-flex tw-items-center tw-gap-2">
<span>Inclusions</span>
<span>Project Context</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
@ -261,53 +282,19 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
description="Define patterns to include specific files or folders in the project context"
>
<div className="tw-flex tw-items-center tw-gap-2">
<div className="tw-flex-1 tw-text-xs tw-text-muted">
{formData.contextSource?.inclusions?.trim()
? "Patterns configured"
: "No patterns configured"}
<div className="tw-flex tw-flex-1 tw-flex-row">
<span className="tw-text-sm">Patterns configure</span>
<TruncatedText className="tw-max-w-[100px] tw-text-sm tw-text-accent">
{showContext}
</TruncatedText>
</div>
<Button
variant="secondary"
onClick={() =>
new ProjectPatternMatchingModal(
app,
(value: string) => {
handleInputChange("contextSource.inclusions", value);
},
formData.contextSource?.inclusions || "",
"Manage Inclusions"
).open()
}
onClick={() => {
handleEditProjectContext(formData);
}}
>
Manage Patterns
</Button>
</div>
</FormField>
<FormField
label="Exclusions"
description="Exclude specific files or patterns from the included folders above"
>
<div className="tw-flex tw-items-center tw-gap-2">
<div className="tw-flex-1 tw-text-xs tw-text-muted">
{formData.contextSource?.exclusions?.trim()
? "Patterns configured"
: "No patterns configured"}
</div>
<Button
variant="secondary"
onClick={() =>
new ProjectPatternMatchingModal(
app,
(value: string) => {
handleInputChange("contextSource.exclusions", value);
},
formData.contextSource?.exclusions || "",
"Manage Exclusions"
).open()
}
>
Manage Patterns
Manage Context
</Button>
</div>
</FormField>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
import React from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { XCircle, Search } from "lucide-react";
interface SearchBarProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export const SearchBar: React.FC<SearchBarProps> = ({
value,
onChange,
placeholder = "Search...",
}) => {
return (
<div className="tw-relative">
<Input
type="text"
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
className="tw-pr-10" // Add padding to prevent text overlap with icons
/>
{value && (
<Button
variant={"secondary"}
onClick={() => onChange("")}
className="tw-absolute tw-right-8 tw-top-1/2 tw-size-4 -tw-translate-y-1/2 tw-transform tw-rounded-full tw-p-0 tw-transition-colors"
aria-label="Clear search"
>
<XCircle className="tw-size-4 tw-text-muted/60 hover:tw-text-accent-hover" />
</Button>
)}
<Search className="tw-absolute tw-right-3 tw-top-1/2 tw-size-4 -tw-translate-y-1/2 tw-transform tw-text-muted" />
</div>
);
};

View file

@ -0,0 +1,45 @@
import * as React from "react";
import { GripVertical } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "@/lib/utils";
const ResizablePanelGroup = ({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
<ResizablePrimitive.PanelGroup
className={cn(
"tw-flex tw-size-full data-[panel-group-direction=vertical]:tw-flex-col",
className
)}
{...props}
/>
);
const ResizablePanel = ResizablePrimitive.Panel;
const ResizableHandle = ({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean;
}) => (
// tw-bg-border -> --divider-color: var(--background-modifier-border);
<ResizablePrimitive.PanelResizeHandle
className={cn(
"tw-relative tw-flex tw-w-px tw-items-center tw-justify-center tw-bg-[var(--background-modifier-border)] after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-1 after:tw--translate-x-1/2 focus-visible:tw-outline-none focus-visible:tw-ring-1 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-1 data-[panel-group-direction=vertical]:tw-h-px data-[panel-group-direction=vertical]:tw-w-full data-[panel-group-direction=vertical]:after:tw-left-0 data-[panel-group-direction=vertical]:after:tw-h-1 data-[panel-group-direction=vertical]:after:tw-w-full data-[panel-group-direction=vertical]:after:tw--translate-y-1/2 data-[panel-group-direction=vertical]:after:tw-translate-x-0 [&[data-panel-group-direction=vertical]>div]:tw-rotate-90",
className
)}
{...props}
>
{withHandle && (
<div className="tw-z-sidedock tw-flex tw-h-4 tw-w-3 tw-items-center tw-justify-center tw-rounded-sm tw-border tw-border-solid tw-border-border">
<GripVertical className="tw-size-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
);
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };

View file

@ -0,0 +1,48 @@
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils";
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("tw-relative tw-overflow-hidden", className)}
{...props}
>
{/*[&>div:first-child]:!tw-blockfor override the first child div style's (display: table)*/}
<ScrollAreaPrimitive.Viewport className="tw-size-full tw-rounded-[inherit] [&>div:first-child]:!tw-block">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"tw-flex tw-touch-none tw-select-none tw-transition-colors",
orientation === "vertical" &&
"tw-h-full tw-w-2.5 tw-border-l tw-border-l-transparent tw-p-[1px]",
orientation === "horizontal" &&
"tw-h-2.5 tw-flex-col tw-border-t tw-border-t-transparent tw-p-[1px]",
className
)}
{...props}
>
{/* tw-bg-border -> --divider-color: var(--background-modifier-border); */}
<ScrollAreaPrimitive.ScrollAreaThumb className="tw-relative tw-flex-1 tw-rounded-full tw-bg-[var(--background-modifier-border)]" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar };

View file

@ -0,0 +1,27 @@
"use client";
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
// tw-bg-border -> --divider-color: var(--background-modifier-border);
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"tw-shrink-0 tw-bg-[var(--background-modifier-border)]",
orientation === "horizontal" ? "tw-h-[1px] tw-w-full" : "tw-h-full tw-w-[1px]",
className
)}
{...props}
/>
));
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator };

View file

@ -5,7 +5,7 @@ import { getTagsFromNote, stripHash } from "@/utils";
import { Embeddings } from "@langchain/core/embeddings";
import { App, TFile } from "obsidian";
interface PatternCategory {
export interface PatternCategory {
tagPatterns?: string[];
extensionPatterns?: string[];
folderPatterns?: string[];
@ -331,3 +331,15 @@ export function extractAppIgnoreSettings(app: App): string[] {
return appIgnoreFolders;
}
export function getTagPattern(tag: string): string {
return `#${tag}`;
}
export function getFilePattern(file: TFile): string {
return `[[${file.basename}]]`;
}
export function getExtensionPattern(extension: string): string {
return `*.${extension}`;
}

View file

@ -0,0 +1,546 @@
import { TFile } from "obsidian";
import {
createPatternSettingsValue,
getFilePattern,
shouldIndexFile,
PatternCategory,
} from "@/search/searchUtils";
// Mock dependencies
jest.mock("obsidian", () => ({
TFile: class TFile {
path: string;
basename: string;
extension: string;
constructor(path: string = "") {
this.path = path;
const parts = path.split("/");
const filename = parts[parts.length - 1];
this.basename = filename.replace(/\.[^/.]+$/, "");
this.extension = filename.split(".").pop() || "";
}
},
}));
jest.mock("@/search/searchUtils", () => ({
getMatchingPatterns: jest.fn(),
shouldIndexFile: jest.fn(),
getFilePattern: jest.fn(),
createPatternSettingsValue: jest.fn(),
categorizePatterns: jest.fn(),
getDecodedPatterns: jest.fn(),
}));
// Mock types and interfaces
interface GroupItem {
id: string;
name: string;
isIgnored?: boolean;
}
interface GroupListItem {
tags: Record<string, Array<GroupItem>>;
folders: Record<string, Array<GroupItem>>;
extensions: Record<string, Array<GroupItem>>;
notes: Array<GroupItem>;
}
interface IgnoreItems {
files: Set<TFile>;
}
// Test helper functions
const createTestFile = (path: string): TFile => {
const file = new TFile();
file.path = path;
const parts = path.split("/");
const filename = parts[parts.length - 1];
file.basename = filename.replace(/\.[^/.]+$/, "");
file.extension = filename.split(".").pop() || "";
return file;
};
const createMockPatternCategory = (overrides: Partial<PatternCategory> = {}): PatternCategory => ({
tagPatterns: [],
folderPatterns: [],
extensionPatterns: [],
notePatterns: [],
...overrides,
});
// Implementation of the functions to test
const createAndPopulateGroupList = (
appFiles: TFile[],
inclusionPatterns: PatternCategory | null,
exclusionPatterns: PatternCategory | null
): GroupListItem => {
const projectAllFiles = appFiles.filter((file) =>
shouldIndexFile(file, inclusionPatterns, exclusionPatterns)
);
// Initialize groups
const tags: Record<string, Array<GroupItem>> = {};
const folders: Record<string, Array<GroupItem>> = {};
const extensions: Record<string, Array<GroupItem>> = {};
const notes: Array<GroupItem> = [];
(inclusionPatterns?.tagPatterns ?? []).forEach((tag) => {
tags[tag] = [];
});
(inclusionPatterns?.folderPatterns ?? []).forEach((folder) => {
folders[folder] = [];
});
(inclusionPatterns?.extensionPatterns ?? []).forEach((extension) => {
extensions[extension] = [];
});
// Populate with matching files
projectAllFiles.forEach((file) => {
const groupItem: GroupItem = {
id: file.path,
name: file.basename,
};
// Add to notes array
notes.push(groupItem);
// Add to appropriate groups based on patterns
(inclusionPatterns?.tagPatterns ?? []).forEach((tag) => {
if (tags[tag]) {
tags[tag].push(groupItem);
}
});
(inclusionPatterns?.folderPatterns ?? []).forEach((folder) => {
if (folders[folder] && file.path.includes(folder)) {
folders[folder].push(groupItem);
}
});
(inclusionPatterns?.extensionPatterns ?? []).forEach((extension) => {
if (extensions[extension] && file.extension === extension.replace("*.", "")) {
extensions[extension].push(groupItem);
}
});
});
return { tags, folders, extensions, notes };
};
const convertGroupListToInclusions = (list: GroupListItem, appFiles: TFile[]): string => {
const tagPatterns = Object.keys(list.tags);
const folderPatterns = Object.keys(list.folders);
const extensionPatterns = Object.keys(list.extensions);
const notePatterns = list.notes
.map((note) => {
const file = appFiles.find((file) => file.path === note.id);
if (file) {
return getFilePattern(file);
}
})
.filter(Boolean) as string[];
return createPatternSettingsValue({
tagPatterns,
folderPatterns,
extensionPatterns,
notePatterns,
});
};
const convertDeletedItemsToExclusions = (items: IgnoreItems): string => {
const notePatterns = Array.from(items.files).map((file) => getFilePattern(file));
return createPatternSettingsValue({ notePatterns }) || "";
};
describe("Context Manage Modal Functions", () => {
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
(shouldIndexFile as jest.Mock).mockReturnValue(true);
(getFilePattern as jest.Mock).mockImplementation((file: TFile) => `[[${file.basename}]]`);
(createPatternSettingsValue as jest.Mock).mockImplementation(
({ tagPatterns = [], folderPatterns = [], extensionPatterns = [], notePatterns = [] }) => {
const patterns = [...tagPatterns, ...folderPatterns, ...extensionPatterns, ...notePatterns];
return patterns.join(",");
}
);
});
describe("createAndPopulateGroupList", () => {
it("Should create empty GroupList when there are no files", () => {
const result = createAndPopulateGroupList([], null, null);
expect(result).toEqual({
tags: {},
folders: {},
extensions: {},
notes: [],
});
});
it("Should correctly filter files based on inclusion and exclusion patterns", () => {
const files = [
createTestFile("test1.md"),
createTestFile("test2.pdf"),
createTestFile("test3.txt"),
];
(shouldIndexFile as jest.Mock)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
const inclusionPatterns = createMockPatternCategory();
const exclusionPatterns = createMockPatternCategory();
const result = createAndPopulateGroupList(files, inclusionPatterns, exclusionPatterns);
expect(shouldIndexFile).toHaveBeenCalledTimes(3);
expect(result.notes).toHaveLength(2);
expect(result.notes[0].id).toBe("test1.md");
expect(result.notes[1].id).toBe("test3.txt");
});
it("Should initialize groups based on inclusion patterns", () => {
const files = [createTestFile("test.md")];
const inclusionPatterns = createMockPatternCategory({
tagPatterns: ["#important", "#work"],
folderPatterns: ["docs", "projects"],
extensionPatterns: ["*.md", "*.pdf"],
});
const result = createAndPopulateGroupList(files, inclusionPatterns, null);
expect(Object.keys(result.tags)).toEqual(["#important", "#work"]);
expect(Object.keys(result.folders)).toEqual(["docs", "projects"]);
expect(Object.keys(result.extensions)).toEqual(["*.md", "*.pdf"]);
});
it("Should add files to appropriate groups", () => {
const files = [createTestFile("docs/test.md"), createTestFile("projects/work.pdf")];
const inclusionPatterns = createMockPatternCategory({
folderPatterns: ["docs", "projects"],
extensionPatterns: ["*.md", "*.pdf"],
});
const result = createAndPopulateGroupList(files, inclusionPatterns, null);
expect(result.folders["docs"]).toHaveLength(1);
expect(result.folders["docs"][0].name).toBe("test");
expect(result.folders["projects"]).toHaveLength(1);
expect(result.folders["projects"][0].name).toBe("work");
});
it("Should handle duplicate patterns", () => {
const files = [createTestFile("test.md")];
const inclusionPatterns = createMockPatternCategory({
tagPatterns: ["#tag", "#tag", "#tag"],
folderPatterns: ["folder", "folder"],
});
const result = createAndPopulateGroupList(files, inclusionPatterns, null);
expect(Object.keys(result.tags)).toEqual(["#tag"]);
expect(Object.keys(result.folders)).toEqual(["folder"]);
});
});
describe("convertGroupListToInclusions", () => {
it("Should convert empty GroupList to empty string", () => {
const emptyGroupList: GroupListItem = {
tags: {},
folders: {},
extensions: {},
notes: [],
};
const result = convertGroupListToInclusions(emptyGroupList, []);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
tagPatterns: [],
folderPatterns: [],
extensionPatterns: [],
notePatterns: [],
});
expect(result).toBe("");
});
it("Should correctly extract all pattern types", () => {
const groupList: GroupListItem = {
tags: { "#important": [], "#work": [] },
folders: { docs: [], projects: [] },
extensions: { "*.md": [], "*.pdf": [] },
notes: [
{ id: "note1.md", name: "note1" },
{ id: "note2.md", name: "note2" },
],
};
const appFiles = [createTestFile("note1.md"), createTestFile("note2.md")];
convertGroupListToInclusions(groupList, appFiles);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
tagPatterns: ["#important", "#work"],
folderPatterns: ["docs", "projects"],
extensionPatterns: ["*.md", "*.pdf"],
notePatterns: ["[[note1]]", "[[note2]]"],
});
});
it("Should handle notes without corresponding files", () => {
const groupList: GroupListItem = {
tags: {},
folders: {},
extensions: {},
notes: [
{ id: "missing.md", name: "missing" },
{ id: "existing.md", name: "existing" },
],
};
const appFiles = [createTestFile("existing.md")];
convertGroupListToInclusions(groupList, appFiles);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
tagPatterns: [],
folderPatterns: [],
extensionPatterns: [],
notePatterns: ["[[existing]]"],
});
});
});
describe("convertDeletedItemsToExclusions", () => {
it("Should convert empty IgnoreItems to empty string", () => {
const emptyIgnoreItems: IgnoreItems = {
files: new Set(),
};
const result = convertDeletedItemsToExclusions(emptyIgnoreItems);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
notePatterns: [],
});
expect(result).toBe("");
});
it("Should correctly convert ignored files to exclusion patterns", () => {
const file1 = createTestFile("ignore1.md");
const file2 = createTestFile("ignore2.pdf");
const ignoreItems: IgnoreItems = {
files: new Set([file1, file2]),
};
convertDeletedItemsToExclusions(ignoreItems);
expect(getFilePattern).toHaveBeenCalledWith(file1);
expect(getFilePattern).toHaveBeenCalledWith(file2);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
notePatterns: ["[[ignore1]]", "[[ignore2]]"],
});
});
it("Should handle a single ignored file", () => {
const file = createTestFile("single.md");
const ignoreItems: IgnoreItems = {
files: new Set([file]),
};
convertDeletedItemsToExclusions(ignoreItems);
expect(getFilePattern).toHaveBeenCalledWith(file);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
notePatterns: ["[[single]]"],
});
});
it("Should handle case when createPatternSettingsValue returns undefined", () => {
(createPatternSettingsValue as jest.Mock).mockReturnValueOnce(undefined);
const file = createTestFile("test.md");
const ignoreItems: IgnoreItems = {
files: new Set([file]),
};
const result = convertDeletedItemsToExclusions(ignoreItems);
expect(result).toBe("");
});
});
// Boundary conditions and error handling tests
describe("Boundary Condition Tests", () => {
describe("createAndPopulateGroupList", () => {
it("Should handle null inclusion patterns", () => {
const files = [createTestFile("test.md")];
const result = createAndPopulateGroupList(files, null, null);
expect(result.tags).toEqual({});
expect(result.folders).toEqual({});
expect(result.extensions).toEqual({});
expect(result.notes).toHaveLength(1);
});
it("Should handle file paths with special characters", () => {
const files = [
createTestFile("folder with spaces/file-with-dashes.md"),
createTestFile("中文文件夹/中文文件.md"),
createTestFile("folder/file@#$%.md"),
];
const result = createAndPopulateGroupList(files, null, null);
expect(result.notes).toHaveLength(3);
expect(result.notes[0].name).toBe("file-with-dashes");
expect(result.notes[1].name).toBe("中文文件");
expect(result.notes[2].name).toBe("file@#$%");
});
});
describe("convertGroupListToInclusions", () => {
it("Should handle large number of patterns", () => {
const tags: Record<string, Array<GroupItem>> = {};
const folders: Record<string, Array<GroupItem>> = {};
const extensions: Record<string, Array<GroupItem>> = {};
// Create many patterns
for (let i = 0; i < 100; i++) {
tags[`#tag${i}`] = [];
folders[`folder${i}`] = [];
extensions[`*.ext${i}`] = [];
}
const groupList: GroupListItem = { tags, folders, extensions, notes: [] };
convertGroupListToInclusions(groupList, []);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
tagPatterns: expect.arrayContaining([`#tag0`, `#tag99`]),
folderPatterns: expect.arrayContaining([`folder0`, `folder99`]),
extensionPatterns: expect.arrayContaining([`*.ext0`, `*.ext99`]),
notePatterns: [],
});
});
it("Should handle invalid file paths in notes", () => {
const groupList: GroupListItem = {
tags: {},
folders: {},
extensions: {},
notes: [
{ id: "", name: "" },
{ id: " ", name: " " },
{ id: "valid.md", name: "valid" },
],
};
const appFiles = [createTestFile("valid.md")];
convertGroupListToInclusions(groupList, appFiles);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
tagPatterns: [],
folderPatterns: [],
extensionPatterns: [],
notePatterns: ["[[valid]]"],
});
});
});
describe("convertDeletedItemsToExclusions", () => {
it("Should handle large number of ignored files", () => {
const files = new Set<TFile>();
for (let i = 0; i < 1000; i++) {
files.add(createTestFile(`file${i}.md`));
}
const ignoreItems: IgnoreItems = { files };
convertDeletedItemsToExclusions(ignoreItems);
expect(getFilePattern).toHaveBeenCalledTimes(1000);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
notePatterns: expect.arrayContaining(["[[file0]]", "[[file999]]"]),
});
});
it("Should handle filenames with special characters", () => {
const files = [
createTestFile("file with spaces.md"),
createTestFile("file-with-dashes.md"),
createTestFile("file_with_underscores.md"),
createTestFile("file@#$%.md"),
];
const ignoreItems: IgnoreItems = { files: new Set(files) };
convertDeletedItemsToExclusions(ignoreItems);
expect(getFilePattern).toHaveBeenCalledTimes(4);
files.forEach((file) => {
expect(getFilePattern).toHaveBeenCalledWith(file);
});
});
});
});
// Integration tests
describe("Integration Tests", () => {
it("Should correctly handle the complete workflow", () => {
// 1. Create initial file list
const appFiles = [
createTestFile("docs/readme.md"),
createTestFile("src/main.ts"),
createTestFile("tests/test.spec.ts"),
createTestFile("config.json"),
];
// 2. Set inclusion patterns
const inclusionPatterns = createMockPatternCategory({
folderPatterns: ["docs", "src"],
extensionPatterns: ["*.md", "*.ts"],
});
// 3. Create GroupList
const groupList = createAndPopulateGroupList(appFiles, inclusionPatterns, null);
// 4. Convert to inclusions
const inclusions = convertGroupListToInclusions(groupList, appFiles);
// 5. Create ignore items and convert to exclusions
const ignoreItems: IgnoreItems = {
files: new Set([createTestFile("config.json")]),
};
const exclusions = convertDeletedItemsToExclusions(ignoreItems);
// Verify results
expect(groupList.folders).toHaveProperty("docs");
expect(groupList.folders).toHaveProperty("src");
expect(Object.prototype.hasOwnProperty.call(groupList.extensions, "*.md")).toBe(true);
expect(Object.prototype.hasOwnProperty.call(groupList.extensions, "*.ts")).toBe(true);
expect(createPatternSettingsValue).toHaveBeenCalledWith({
tagPatterns: [],
folderPatterns: ["docs", "src"],
extensionPatterns: ["*.md", "*.ts"],
notePatterns: expect.any(Array),
});
expect(createPatternSettingsValue).toHaveBeenCalledWith({
notePatterns: ["[[config]]"],
});
// Verify inclusions and exclusions variables
// Since createPatternSettingsValue is mocked to return all patterns as comma-separated list
expect(inclusions).toContain("docs");
expect(inclusions).toContain("src");
expect(inclusions).toContain("*.md");
expect(inclusions).toContain("*.ts");
expect(exclusions).toBe("[[config]]");
});
});
});

View file

@ -40,6 +40,14 @@ module.exports = {
green: "var(--color-green)",
blue: "var(--color-blue)",
},
"context-manager": {
blue: "var(--color-blue)",
green: "var(--color-green)",
red: "var(--color-red)",
yellow: "var(--color-yellow)",
orange: "var(--color-orange)",
purple: "var(--color-purple)",
},
},
backgroundColor: {
inherit: colors.inherit,