Merge branch '3.1.0-preview' into memory-preview

This commit is contained in:
Wenzheng Jiang 2025-09-24 17:58:54 +09:00 committed by GitHub
commit 61d423fd5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1716 additions and 911 deletions

View file

@ -1,7 +1,7 @@
{
"id": "copilot",
"name": "Copilot",
"version": "3.0.2",
"version": "3.0.3",
"minAppVersion": "0.15.0",
"description": "Your AI Copilot: Chat with Your Second Brain, Learn Faster, Work Smarter.",
"author": "Logan Yang",

584
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "obsidian-copilot",
"version": "3.0.2",
"version": "3.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-copilot",
"version": "3.0.2",
"version": "3.0.3",
"license": "AGPL-3.0",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@ -5506,18 +5506,18 @@
}
},
"node_modules/@radix-ui/react-dropdown-menu": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz",
"integrity": "sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==",
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
"integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
"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-id": "1.1.0",
"@radix-ui/react-menu": "2.1.4",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-controllable-state": "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-id": "1.1.1",
"@radix-ui/react-menu": "2.1.16",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
@ -5535,15 +5535,15 @@
}
},
"node_modules/@radix-ui/react-dropdown-menu/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-dropdown-menu/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": "*",
@ -5555,13 +5555,46 @@
}
}
},
"node_modules/@radix-ui/react-dropdown-menu/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-dropdown-menu/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-dropdown-menu/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.1"
"@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-dropdown-menu/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": "*",
@ -5578,13 +5611,14 @@
}
}
},
"node_modules/@radix-ui/react-dropdown-menu/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-dropdown-menu/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": "*",
@ -5596,6 +5630,21 @@
}
}
},
"node_modules/@radix-ui/react-dropdown-menu/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-focus-guards": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
@ -5675,29 +5724,29 @@
}
},
"node_modules/@radix-ui/react-menu": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz",
"integrity": "sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==",
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
"integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "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-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-roving-focus": "1.1.1",
"@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-use-callback-ref": "1.1.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "^2.6.1"
"@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-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-roving-focus": "1.1.11",
"@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.6.3"
},
"peerDependencies": {
"@types/react": "*",
@ -5715,18 +5764,18 @@
}
},
"node_modules/@radix-ui/react-menu/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-menu/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": "*",
@ -5744,15 +5793,15 @@
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
"integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
"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.1",
"@radix-ui/react-slot": "1.1.1"
"@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": "*",
@ -5770,9 +5819,39 @@
}
},
"node_modules/@radix-ui/react-menu/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-menu/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-menu/node_modules/@radix-ui/react-direction": {
"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==",
"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": "*",
@ -5785,16 +5864,16 @@
}
},
"node_modules/@radix-ui/react-menu/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": "*",
@ -5811,15 +5890,30 @@
}
}
},
"node_modules/@radix-ui/react-menu/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-menu/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": "*",
@ -5836,22 +5930,40 @@
}
}
},
"node_modules/@radix-ui/react-menu/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-menu/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": "*",
@ -5869,13 +5981,37 @@
}
},
"node_modules/@radix-ui/react-menu/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-menu/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": "*",
@ -5893,12 +6029,12 @@
}
},
"node_modules/@radix-ui/react-menu/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": "*",
@ -5915,13 +6051,28 @@
}
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"node_modules/@radix-ui/react-menu/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-menu/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-compose-refs": "1.1.1"
"@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@ -5933,17 +6084,74 @@
}
}
},
"node_modules/@radix-ui/react-menu/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-menu/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-menu/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-menu/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-menu/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"
@ -6423,20 +6631,20 @@
}
},
"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",
"integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
"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.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-id": "1.1.0",
"@radix-ui/react-primitive": "2.0.1",
"@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": "*",
@ -6454,21 +6662,21 @@
}
},
"node_modules/@radix-ui/react-roving-focus/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-roving-focus/node_modules/@radix-ui/react-collection": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
"integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
"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.1",
"@radix-ui/react-slot": "1.1.1"
"@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": "*",
@ -6486,9 +6694,9 @@
}
},
"node_modules/@radix-ui/react-roving-focus/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": "*",
@ -6500,13 +6708,61 @@
}
}
},
"node_modules/@radix-ui/react-roving-focus/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-roving-focus/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-roving-focus/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-roving-focus/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.1"
"@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-roving-focus/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": "*",
@ -6523,13 +6779,29 @@
}
}
},
"node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": {
"node_modules/@radix-ui/react-roving-focus/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-roving-focus/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": "*",
@ -6541,6 +6813,21 @@
}
}
},
"node_modules/@radix-ui/react-roving-focus/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-scroll-area": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz",
@ -21540,9 +21827,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 +21839,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": {

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-copilot",
"version": "3.0.2",
"version": "3.0.3",
"description": "Your AI Copilot: Chat with Your Second Brain, Learn Faster, Work Smarter.",
"main": "main.js",
"scripts": {

View file

@ -96,7 +96,8 @@ More content
const dashHeading = "Content\nSources -\n[^1]: [[Doc]]";
expect(hasExistingCitations(dashHeading)).toBe(true);
const summary = "<details><summary>Sources</summary>List</details>";
const summary =
'<details><summary class="copilot-sources__summary">Sources</summary>List</details>';
expect(hasExistingCitations(summary)).toBe(true);
});
@ -188,9 +189,10 @@ More content
[^2]: [[Document 2]]`;
const result = processInlineCitations(content, true);
expect(result).toContain("Sources</summary>");
expect(result).toContain("Document 1");
expect(result).toContain("Document 2");
expect(result).toContain("copilot-sources__summary");
expect(result).toContain('copilot-sources__index">[1]');
expect(result).toContain('copilot-sources__text">[[Document 1]]');
expect(result).toContain('copilot-sources__text">[[Document 2]]');
});
it("should use simple expandable list when disabled", () => {
@ -201,9 +203,10 @@ More content
- [[Document 2]]`;
const result = processInlineCitations(content, false);
expect(result).toContain("Sources</summary>");
expect(result).toContain("Document 1");
expect(result).toContain("Document 2");
expect(result).toContain("copilot-sources__summary");
expect(result).toContain('copilot-sources__index">[1]');
expect(result).toContain('copilot-sources__text">[[Document 1]]');
expect(result).toContain('copilot-sources__text">[[Document 2]]');
});
it("should return unchanged content when no sources section", () => {
@ -350,10 +353,18 @@ More content
// [^2] (third mention) -> [3] -> should map to [[2024-03-26]]
// [^18] (fourth mention) -> [4] -> should map to [[2024-04-08]]
expect(result).toContain("<strong>[1]</strong> [[2024-03-13]]"); // [^9] -> [1]
expect(result).toContain("<strong>[2]</strong> [[2024-03-18]]"); // [^1] -> [2]
expect(result).toContain("<strong>[3]</strong> [[2024-03-26]]"); // [^2] -> [3]
expect(result).toContain("<strong>[4]</strong> [[2024-04-08]]"); // [^18] -> [4]
expect(result).toContain(
'<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[2024-03-13]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[2024-03-18]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[3]</span><span class="copilot-sources__text">[[2024-03-26]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[4]</span><span class="copilot-sources__text">[[2024-04-08]]</span>'
);
// Verify the citations in text are renumbered correctly
expect(result).toContain("Dolce Arts Studio [1]"); // [^9] -> [1]
@ -380,10 +391,14 @@ More content
const result = processInlineCitations(content, true);
// Should consolidate the 3 "How to Make Wealth" entries into 1
expect(result).toContain("<strong>[1]</strong> [[How to Make Wealth]]");
expect(result).toContain("<strong>[2]</strong> [[Superlinear Returns]]");
expect(result).not.toContain("<strong>[3]</strong>");
expect(result).not.toContain("<strong>[4]</strong>");
expect(result).toContain(
'<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[How to Make Wealth]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[Superlinear Returns]]</span>'
);
expect(result).not.toContain('copilot-sources__index">[3]');
expect(result).not.toContain('copilot-sources__index">[4]');
// Verify periods after citations are removed
expect(result).not.toContain("[1].");
@ -419,10 +434,16 @@ More content
// [^22] (fourth mention) -> [4] -> [[Document B]] (should consolidate with [^14])
// After consolidation, should only have 3 unique sources:
expect(result).toContain("<strong>[1]</strong> [[Document B]]"); // [^14] and [^22] consolidated
expect(result).toContain("<strong>[2]</strong> [[Document C]]"); // [^17]
expect(result).toContain("<strong>[3]</strong> [[Document A]]"); // [^3]
expect(result).not.toContain("<strong>[4]</strong>");
expect(result).toContain(
'<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[Document B]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[Document C]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[3]</span><span class="copilot-sources__text">[[Document A]]</span>'
);
expect(result).not.toContain('copilot-sources__index">[4]');
// Verify citations in text point to correct consolidated sources
expect(result).toContain("references [1] and also [2]"); // [^14]->[1], [^17]->[2]
@ -447,8 +468,12 @@ More content
// [^7] (first mention) -> [1] -> [[Superlinear Returns]]
// [^8] (second mention) -> [2] -> [[How to Do Great Work]]
expect(result).toContain("<strong>[1]</strong> [[Superlinear Returns]]"); // [^7] -> [1]
expect(result).toContain("<strong>[2]</strong> [[How to Do Great Work]]"); // [^8] -> [2]
expect(result).toContain(
'<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[Superlinear Returns]]</span>'
);
expect(result).toContain(
'<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[How to Do Great Work]]</span>'
);
// CRITICAL: Both citations in text must be converted (not partial like [4][^8])
expect(result).toContain("thresholds). [1][2]"); // [^7][^8] -> [1][2]

View file

@ -151,7 +151,7 @@ export function hasExistingCitations(response: string): boolean {
const content = response || "";
const hasMarkdownHeading = /(^|\n)\s*#{1,6}\s*Sources\b/i.test(content);
const hasPlainLabel = /(^|\n)\s*Sources\s*(?:[:-]\s*)?(\n|$)/i.test(content);
const hasSummaryTag = /<summary>\s*Sources\s*<\/summary>/i.test(content);
const hasSummaryTag = /<summary[^>]*>\s*Sources\s*<\/summary>/i.test(content);
// More robust detection: look for ANY line starting with [^digits]:
const hasFootnoteDefinitions = /(^|\n)\s*\[\^\d+\]:\s*/.test(content);
return hasMarkdownHeading || hasPlainLabel || hasSummaryTag || hasFootnoteDefinitions;
@ -403,6 +403,93 @@ export function updateCitationsForConsolidation(
});
}
interface SourcesDisplayItem {
index: number;
html: string;
}
/**
* Escapes HTML-sensitive characters to avoid unintended markup injection.
*/
function escapeHtml(value: string): string {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
/**
* Type guard that filters out nullish values when mapping optional entries.
*/
function isNonNull<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
/**
* Converts a normalized sources block into display-ready list items.
*/
function parseSimpleSources(sourcesBlock: string): SourcesDisplayItem[] {
const lines = sourcesBlock.split("\n");
const items: SourcesDisplayItem[] = [];
lines.forEach((rawLine) => {
const trimmed = rawLine.trim();
if (!trimmed) {
return;
}
const content = trimmed
.replace(/^<li>/i, "")
.replace(/<\/li>$/i, "")
.replace(/^[-*]\s*/, "")
.trim();
if (!content) {
return;
}
const markdownLink = content.match(/\[([^\]]+)\]\(([^)]+)\)/);
const wikiLink = content.match(/\[\[(.*?)\]\]/);
let html: string;
if (markdownLink) {
html = `<a href="${markdownLink[2]}">${markdownLink[1]}</a>`;
} else if (wikiLink) {
html = `[[${wikiLink[1]}]]`;
} else {
html = escapeHtml(content);
}
items.push({ index: items.length + 1, html });
});
return items;
}
/**
* Appends a styled, collapsible sources list to the main message content.
*/
function buildSourcesDetails(mainContent: string, items: SourcesDisplayItem[]): string {
if (items.length === 0) {
return mainContent;
}
const listItems = items
.map(
({ index, html }) =>
`<li class="copilot-sources__item"><span class="copilot-sources__index">[${index}]</span><span class="copilot-sources__text">${html}</span></li>`
)
.join("\n");
return (
`${mainContent}\n\n<br/>\n` +
`<details class="copilot-sources"><summary class="copilot-sources__summary">Sources</summary>\n` +
`<ul class="copilot-sources__list">\n${listItems}\n</ul>\n</details>`
);
}
/**
* Main function to process inline citations in content.
* Uses settings to determine if inline citations should be processed.
@ -416,46 +503,15 @@ export function processInlineCitations(content: string, useInlineCitations: bool
// If inline citations are disabled, use simple expandable sources list
if (!useInlineCitations) {
const sourceLinks = sourcesBlock
.split("\n")
.map((line) => {
const match = line.match(/- \[\[(.*?)\]\]/);
if (match) {
return `<li>[[${match[1]}]]</li>`;
}
return line;
})
.join("\n");
return (
mainContent +
"\n\n<br/>\n<details><summary>Sources</summary>\n<ul>\n" +
sourceLinks +
"\n</ul>\n</details>"
);
const simpleItems = parseSimpleSources(sourcesBlock);
return buildSourcesDetails(mainContent, simpleItems);
}
// Process inline citations
const footnoteLines = parseFootnoteDefinitions(sourcesBlock);
if (footnoteLines.length === 0) {
// Not footnote format, use simple sources list
const sourceLinks = sourcesBlock
.split("\n")
.map((line) => {
const match = line.match(/- \[\[(.*?)\]\]/);
if (match) {
return `<li>[[${match[1]}]]</li>`;
}
return line;
})
.join("\n");
return (
mainContent +
"\n\n<br/>\n<details><summary>Sources</summary>\n<ul>\n" +
sourceLinks +
"\n</ul>\n</details>"
);
const simpleItems = parseSimpleSources(sourcesBlock);
return buildSourcesDetails(mainContent, simpleItems);
}
// Process footnote-style citations
@ -471,31 +527,16 @@ export function processInlineCitations(content: string, useInlineCitations: bool
items = uniqueItems;
}
// Build sources list maintaining proper citation mapping
// The old code used .filter().map() which destroyed the mapping by renumbering
// Now we build the list respecting the citation numbers used in the text
const maxCitationNum = Math.max(
...Array.from(citationMap.values()).concat(
consolidationMap.size > 0 ? Array.from(consolidationMap.values()) : []
)
);
const detailedItems = items
.map<SourcesDisplayItem | null>((item, index) => {
if (!item) {
return null;
}
return { index: index + 1, html: item };
})
.filter(isNonNull);
const sourcesList: string[] = [];
for (let i = 1; i <= maxCitationNum; i++) {
const item = items[i - 1];
if (item) {
sourcesList.push(`<li><strong>[${i}]</strong> ${item}</li>`);
}
}
const formattedSourcesList = sourcesList.join("\n");
return (
mainContent +
"\n\n<br/>\n<details><summary>Sources</summary>\n<ul>\n" +
formattedSourcesList +
"\n</ul>\n</details>"
);
return buildSourcesDetails(mainContent, detailedItems);
}
// ===== SOURCE CATALOG UTILITIES =====

View file

@ -561,9 +561,8 @@ const Chat: React.FC<ChatProps> = ({
safeSet.setCurrentAiMessage("");
setContextNotes([]);
clearSelectedTextContexts();
// Only modify includeActiveNote if in a non-COPILOT_PLUS_CHAIN mode
// In COPILOT_PLUS_CHAIN mode, respect the settings.includeActiveNoteAsContext value
if (selectedChain !== ChainType.COPILOT_PLUS_CHAIN) {
// Respect the includeActiveNote setting for all non-project chains
if (selectedChain === ChainType.PROJECT_CHAIN) {
setIncludeActiveNote(false);
} else {
setIncludeActiveNote(settings.includeActiveNoteAsContext);
@ -604,11 +603,10 @@ const Chat: React.FC<ChatProps> = ({
useEffect(() => {
if (settings.includeActiveNoteAsContext !== undefined) {
// Only apply the setting if not in Project mode
if (selectedChain === ChainType.COPILOT_PLUS_CHAIN) {
setIncludeActiveNote(settings.includeActiveNoteAsContext);
} else {
// In other modes, always disable including active note
if (selectedChain === ChainType.PROJECT_CHAIN) {
setIncludeActiveNote(false);
} else {
setIncludeActiveNote(settings.includeActiveNoteAsContext);
}
}
}, [settings.includeActiveNoteAsContext, selectedChain]);

View file

@ -2,7 +2,7 @@ import { ChatButtons } from "@/components/chat-components/ChatButtons";
import { ToolCallBanner } from "@/components/chat-components/ToolCallBanner";
import { SourcesModal } from "@/components/modals/SourcesModal";
import { Badge } from "@/components/ui/badge";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { USER_SENDER } from "@/constants";
import { cn } from "@/lib/utils";
import { parseToolCallMarkers } from "@/LLMProviders/chainRunner/utils/toolCallParser";
@ -10,7 +10,6 @@ import { processInlineCitations } from "@/LLMProviders/chainRunner/utils/citatio
import { useSettingsValue } from "@/settings/model";
import { ChatMessage } from "@/types/message";
import { cleanMessageForCopy, insertIntoEditor } from "@/utils";
import { Bot, User } from "lucide-react";
import { App, Component, MarkdownRenderer, MarkdownView, TFile } from "obsidian";
import React, { useCallback, useEffect, useRef, useState } from "react";
import ReactDOM, { Root } from "react-dom/client";
@ -66,24 +65,18 @@ function MessageContext({ context }: { context: ChatMessage["context"] }) {
return (
<div className="tw-flex tw-flex-wrap tw-gap-2">
{context.notes.map((note, index) => (
<Tooltip key={`${index}-${note.path}`}>
<TooltipTrigger asChild>
<Badge variant="secondary">
<span className="tw-max-w-40 tw-truncate">{note.basename}</span>
</Badge>
</TooltipTrigger>
<TooltipContent>{note.path}</TooltipContent>
</Tooltip>
<HelpTooltip key={`${index}-${note.path}`} content={note.path} side="top">
<Badge variant="secondary">
<span className="tw-max-w-40 tw-truncate">{note.basename}</span>
</Badge>
</HelpTooltip>
))}
{context.urls.map((url, index) => (
<Tooltip key={`${index}-${url}`}>
<TooltipTrigger asChild>
<Badge variant="secondary">
<span className="tw-max-w-40 tw-truncate">{url}</span>
</Badge>
</TooltipTrigger>
<TooltipContent>{url}</TooltipContent>
</Tooltip>
<HelpTooltip key={`${index}-${url}`} content={url} side="top">
<Badge variant="secondary">
<span className="tw-max-w-40 tw-truncate">{url}</span>
</Badge>
</HelpTooltip>
))}
</div>
);
@ -644,14 +637,16 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
<div className="tw-my-1 tw-flex tw-w-full tw-flex-col">
<div
className={cn(
"tw-group tw-mx-2 tw-flex tw-gap-2 tw-rounded-md tw-p-2",
"tw-group tw-mx-2 tw-rounded-md tw-p-2",
message.sender === USER_SENDER && "tw-border tw-border-solid tw-border-border"
)}
style={
message.sender === USER_SENDER
? { backgroundColor: "var(--background-modifier-hover)" }
: undefined
}
>
<div className="tw-w-6 tw-shrink-0">
{message.sender === USER_SENDER ? <User /> : <Bot />}
</div>
<div className="tw-flex tw-max-w-full tw-grow tw-flex-col tw-gap-2 tw-overflow-hidden">
<div className="tw-flex tw-max-w-full tw-flex-col tw-gap-2 tw-overflow-hidden">
{!isEditing && <MessageContext context={message.context} />}
<div className="message-content">{renderMessageContent()}</div>

View file

@ -10,7 +10,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { SearchBar } from "@/components/ui/SearchBar";
import { cn } from "@/lib/utils";
import { logError } from "@/logger";
@ -20,7 +20,6 @@ import {
ChevronUp,
Edit2,
Folder,
Info,
MessageSquare,
Plus,
Search,
@ -30,6 +29,7 @@ import {
import { App, Notice } from "obsidian";
import React, { memo, useEffect, useMemo, useState } from "react";
import { filterProjects } from "@/utils/projectUtils";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
function ProjectItem({
project,
@ -236,7 +236,7 @@ export const ProjectList = memo(
<div className="tw-flex tw-flex-col">
{showChatInput && selectedProject ? (
<div className="tw-flex tw-items-center tw-justify-between tw-px-2 tw-py-3">
<div className="tw-flex tw-items-center tw-gap-2">
<div className="tw-flex tw-min-w-0 tw-items-center tw-gap-2">
<span className="tw-font-semibold tw-text-normal">Projects</span>
<Select
value={selectedProject.name}
@ -247,31 +247,31 @@ export const ProjectList = memo(
}
}}
>
<SelectTrigger className="tw-w-[200px]">
<SelectTrigger className="tw-truncate">
<SelectValue>
<div className="tw-flex tw-items-center tw-gap-2">
<Folder className="tw-size-4 tw-text-accent/70" />
<span>{selectedProject.name}</span>
<div className="tw-flex tw-min-w-0 tw-items-center tw-gap-2">
<Folder className="tw-size-4 tw-shrink-0 tw-text-accent/70" />
<span className="tw-flex-1 tw-truncate">{selectedProject.name}</span>
</div>
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectContent className="tw-truncate">
{projects.map((project) => (
<SelectItem
key={project.name}
value={project.name}
className="tw-flex tw-items-center tw-gap-2"
>
<div className="tw-flex tw-items-center tw-gap-2">
<Folder className="tw-size-4" />
{project.name}
<div className="tw-flex tw-min-w-0 tw-items-center tw-gap-2">
<Folder className="tw-size-4 tw-shrink-0" />
<span className="tw-truncate">{project.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="tw-flex tw-items-center tw-gap-2">
<div className="tw-ml-1 tw-flex tw-items-center tw-gap-2">
<Button
variant="secondary"
onClick={() => handleEditProject(selectedProject)}
@ -307,14 +307,11 @@ export const ProjectList = memo(
<div className="tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-3">
<div className="tw-flex tw-flex-1 tw-items-center tw-gap-2">
<span className="tw-font-semibold tw-text-normal">Projects</span>
<Tooltip>
<TooltipTrigger asChild>
<Info className="tw-size-4 tw-text-muted" />
</TooltipTrigger>
<TooltipContent side="bottom" className="tw-w-64">
Manage your projects with different contexts and configurations.
</TooltipContent>
</Tooltip>
<HelpTooltip
content="Manage your projects with different contexts and configurations."
contentClassName="tw-w-64"
buttonClassName="tw-size-4 tw-text-muted"
/>
</div>
<div className="tw-flex tw-items-center tw-gap-2">
<Button className="tw-px-2" variant="secondary" onClick={handleAddProject}>

View file

@ -2,7 +2,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { useActiveFile } from "@/hooks/useActiveFile";
import { cn } from "@/lib/utils";
import {
@ -17,13 +17,13 @@ import {
ChevronUp,
FileInput,
FileOutput,
Info,
PlusCircle,
RefreshCcw,
TriangleAlert,
} from "lucide-react";
import { Notice, TFile } from "obsidian";
import React, { memo, useCallback, useEffect, useState } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
function useRelevantNotes(refresher: number) {
const [relevantNotes, setRelevantNotes] = useState<RelevantNoteEntry[]>([]);
@ -47,6 +47,7 @@ function useRelevantNotes(refresher: number) {
setRelevantNotes([]);
}
}
fetchNotes();
}, [activeFile?.path, refresher]);
@ -57,6 +58,7 @@ function useHasIndex(notePath: string, refresher: number) {
const [hasIndex, setHasIndex] = useState(true);
useEffect(() => {
if (!notePath) return;
async function fetchHasIndex() {
try {
const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
@ -66,6 +68,7 @@ function useHasIndex(notePath: string, refresher: number) {
setHasIndex(false);
}
}
fetchHasIndex();
}, [notePath, refresher]);
return hasIndex;
@ -290,22 +293,16 @@ export const RelevantNotes = memo(
<div className="tw-flex tw-items-center tw-justify-between tw-pb-2 tw-pl-1">
<div className="tw-flex tw-flex-1 tw-items-center tw-gap-2">
<span className="tw-font-semibold tw-text-normal">Relevant Notes</span>
<Tooltip>
<TooltipTrigger asChild>
<Info className="tw-size-4 tw-text-muted" />
</TooltipTrigger>
<TooltipContent side="bottom" className="tw-w-64">
Relevance is a combination of semantic similarity and links.
</TooltipContent>
</Tooltip>
<HelpTooltip
content="Relevance is a combination of semantic similarity and links."
contentClassName="tw-w-64"
buttonClassName="tw-size-4 tw-text-muted"
/>
{!hasIndex && (
<Tooltip>
<TooltipTrigger asChild>
<TriangleAlert className="tw-size-4 tw-text-warning" />
</TooltipTrigger>
<TooltipContent side="bottom">Note has not been indexed</TooltipContent>
</Tooltip>
<HelpTooltip content="Note has not been indexed" side="bottom">
<TriangleAlert className="tw-size-4 tw-text-warning" />
</HelpTooltip>
)}
</div>
<div className="tw-flex tw-items-center">

View file

@ -1,17 +1,18 @@
import { useChainType } from "@/aiParams";
import { ChainType } from "@/chainFactory";
import { Button } from "@/components/ui/button";
import { Card, CardTitle, CardContent, CardHeader } from "@/components/ui/card";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { VAULT_VECTOR_STORE_STRATEGY } from "@/constants";
import { useSettingsValue } from "@/settings/model";
import { PlusCircle, TriangleAlert } from "lucide-react";
import React, { useMemo } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
interface NotePrompt {
title: string;
prompts: string[];
}
const SUGGESTED_PROMPTS: Record<string, NotePrompt> = {
activeNote: {
title: "Active Note Insights",

View file

@ -8,15 +8,14 @@ import { getModelDisplayWithIcons } from "@/components/ui/model-display";
import { ObsidianNativeSelect } from "@/components/ui/obsidian-native-select";
import { SettingSlider } from "@/components/ui/setting-slider";
import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { DEFAULT_MODEL_SETTING } from "@/constants";
import { getDecodedPatterns } from "@/search/searchUtils";
import { getModelKeyFromModel, useSettingsValue } from "@/settings/model";
import { checkModelApiKey, err2String, randomUUID } from "@/utils";
import { HelpCircle } from "lucide-react";
import { App, Modal, Notice } from "obsidian";
import React, { useState } from "react";
import { createRoot, Root } from "react-dom/client";
import { HelpTooltip } from "@/components/ui/help-tooltip";
interface AddProjectModalContentProps {
initialProject?: ProjectConfig;
@ -259,31 +258,27 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
label={
<div className="tw-flex tw-items-center tw-gap-2">
<span>File Context</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4 tw-text-muted" />
</TooltipTrigger>
<TooltipContent>
<div className="tw-max-w-80">
<strong>Supported File Types:</strong>
<br />
<strong> Documents:</strong> pdf, doc, docx, ppt, pptx, epub, txt, rtf and
many more
<br />
<strong> Images:</strong> jpg, png, svg, gif, bmp, webp, tiff
<br />
<strong> Spreadsheets:</strong> xlsx, xls, csv, numbers
<br />
<br />
Non-markdown files are converted to markdown in the background.
<br />
<strong>Rate limit:</strong> 50 files or 100MB per 3 hours, whichever is
reached first.
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HelpTooltip
buttonClassName="tw-size-4 tw-text-muted"
content={
<div className="tw-max-w-80">
<strong>Supported File Types:</strong>
<br />
<strong> Documents:</strong> pdf, doc, docx, ppt, pptx, epub, txt, rtf and
many more
<br />
<strong> Images:</strong> jpg, png, svg, gif, bmp, webp, tiff
<br />
<strong> Spreadsheets:</strong> xlsx, xls, csv, numbers
<br />
<br />
Non-markdown files are converted to markdown in the background.
<br />
<strong>Rate limit:</strong> 50 files or 100MB per 3 hours, whichever is
reached first.
</div>
}
/>
</div>
}
description="Define patterns to include specific files, folders or tags (specified in the note property) in the project context."

View file

@ -8,7 +8,6 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen
import { ScrollArea } from "@/components/ui/scroll-area";
import { SearchBar } from "@/components/ui/SearchBar";
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import {
createPatternSettingsValue,
@ -25,15 +24,15 @@ import {
FileText,
FileVideo,
FolderIcon,
HelpCircle,
Plus,
PlusCircle,
TagIcon,
XIcon,
} from "lucide-react";
import { App, Modal, Notice, TFile } from "obsidian";
import { App, Modal, Notice, Platform, TFile } from "obsidian";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createRoot, Root } from "react-dom/client";
import { HelpTooltip } from "@/components/ui/help-tooltip";
function FileIcon({ extension, size = "tw-size-4" }: { extension: string; size?: string }) {
const ext = extension.toLowerCase().replace("*.", "");
@ -78,18 +77,13 @@ const SectionHeader: React.FC<SectionHeaderProps> = ({
<IconComponent className={`tw-mr-2 tw-size-4 ${iconColorClassName}`} />
<h3 className={`tw-text-sm tw-font-semibold ${iconColorClassName}`}>{title}</h3>
{tooltip && (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-ml-2 tw-size-4 tw-text-muted" />
</TooltipTrigger>
<TooltipContent>
<div className="tw-max-w-80">{tooltip}</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HelpTooltip
buttonClassName="tw-ml-2 tw-size-4 tw-text-muted"
content={<div className="tw-max-w-80">{tooltip}</div>}
/>
)}
</div>
<Button
variant="ghost"
size="fit"
@ -309,6 +303,7 @@ function isCategoryItem(item: DisplayItem): item is CategoryItem {
}
function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageProps) {
const isMobile = Platform.isMobile;
const { inclusions: inclusionPatterns, exclusions: exclusionPatterns } = useMemo(() => {
return getMatchingPatterns({
inclusions: initialProject?.contextSource.inclusions,
@ -997,7 +992,7 @@ function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageP
<div className="tw-flex tw-h-full tw-flex-col">
<ResizablePanelGroup direction="horizontal" className="tw-flex-1">
{/* Left Sidebar - Navigation */}
<ResizablePanel defaultSize={30} minSize={20} maxSize={40}>
<ResizablePanel defaultSize={isMobile ? 35 : 30} minSize={20} maxSize={40}>
<div className="tw-flex tw-h-full tw-flex-col">
{/* Header */}
<div className="tw-border-b tw-p-4">
@ -1094,7 +1089,7 @@ function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageP
)}
onClick={groupHandlers.click.ignoreFiles}
>
Ignore Files ({ignoreItems.files.size})
Files ({ignoreItems.files.size})
</div>
</div>
</div>
@ -1105,7 +1100,7 @@ function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageP
<ResizableHandle withHandle />
{/* Right Content Area */}
<ResizablePanel defaultSize={70}>
<ResizablePanel defaultSize={isMobile ? 65 : 70}>
<div className="tw-flex tw-h-full tw-flex-col">
{/* Header */}
<div className="tw-border-b tw-p-4">

View file

@ -0,0 +1,91 @@
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpCircle } from "lucide-react";
import React, { useState } from "react";
import { Platform } from "obsidian";
interface TooltipProps {
content: React.ReactNode;
children?: React.ReactNode;
side?: "top" | "right" | "bottom" | "left";
delayDuration?: number;
contentClassName?: string;
buttonClassName?: string;
}
/**
* A versatile tooltip component that works on both desktop and mobile devices.
* On desktop, it shows on hover. On mobile, it shows on click.
*
* Can be used in two modes:
* 1. Help mode (default): Shows a help icon with tooltip
* 2. Wrapper mode (with children): Wraps any component with tooltip functionality
*/
export const HelpTooltip: React.FC<TooltipProps> = ({
content,
children,
side = "bottom",
delayDuration = 0,
contentClassName,
buttonClassName,
}) => {
const isMobile = Platform.isMobile;
const [showTooltip, setShowTooltip] = useState(false);
const isClickingRef = React.useRef(false);
const handleTouchStart = () => {
if (isMobile) {
isClickingRef.current = true;
}
};
const handleClick = () => {
if (isMobile) {
setShowTooltip(!showTooltip);
// Reset the flag after a brief delay
setTimeout(() => {
isClickingRef.current = false;
}, 100);
}
};
return (
<TooltipProvider delayDuration={delayDuration}>
<Tooltip
open={showTooltip}
onOpenChange={(open) => {
// Ignore onOpenChange events on mobile when we're handling a click
if (isMobile && isClickingRef.current) {
return;
}
setShowTooltip(open);
}}
>
<TooltipTrigger asChild>
{children ? (
<div
onClick={handleClick}
onTouchStart={handleTouchStart}
className="tw-cursor-pointer"
>
{children}
</div>
) : (
<Button
variant="ghost"
size="sm"
onClick={handleClick}
onTouchStart={handleTouchStart}
className={`tw-inline-flex tw-size-6 tw-items-center tw-justify-center tw-p-0 hover:tw-bg-transparent hover:tw-text-normal ${buttonClassName || ""}`}
>
<HelpCircle className="tw-size-4" />
</Button>
)}
</TooltipTrigger>
<TooltipContent side={side} className={contentClassName}>
{content}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};

View file

@ -0,0 +1,211 @@
import React, { useState } from "react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { ChevronDown, ChevronRight, GripVertical, MoreVertical } from "lucide-react";
export interface MobileCardDropdownAction<T = any> {
icon: React.ReactNode;
label: string;
onClick: (item: T) => void;
variant?: "default" | "destructive";
}
export interface MobileCardProps<T extends object> {
id: string;
item: T;
title: string;
subtitle?: string;
badge?: React.ReactNode;
icon?: React.ReactNode;
isDraggable?: boolean;
isExpandable?: boolean;
defaultExpanded?: boolean;
expandedContent?: React.ReactNode;
primaryAction?: {
icon: React.ReactNode;
onClick: (item: T) => void;
tooltip?: string;
};
dropdownActions?: Array<MobileCardDropdownAction<T>>;
containerRef?: React.RefObject<HTMLDivElement>;
className?: string;
onExpandToggle?: (expanded: boolean) => void;
}
export function MobileCard<T extends object>({
id,
item,
title,
subtitle,
badge,
icon,
isDraggable = false,
isExpandable = false,
defaultExpanded = false,
expandedContent,
primaryAction,
dropdownActions = [],
containerRef,
className,
onExpandToggle,
}: MobileCardProps<T>) {
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
disabled: !isDraggable,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const handleExpandToggle = () => {
if (isExpandable) {
const newExpanded = !isExpanded;
setIsExpanded(newExpanded);
onExpandToggle?.(newExpanded);
}
};
const showDropdownMenu = dropdownActions.length > 0;
return (
<Card
ref={setNodeRef}
style={style}
className={cn(
"tw-mb-2",
isDragging && [
"tw-opacity-90",
"tw-shadow-lg",
"tw-border-accent/50",
"tw-relative",
"tw-z-[9999]",
"tw-bg-primary",
"tw-rounded-lg",
"tw-transform-gpu",
],
isDraggable && "tw-touch-manipulation",
className
)}
>
<CardHeader className="tw-p-3">
<div className="tw-flex tw-items-center tw-justify-between">
{/* Drag handle */}
{isDraggable && (
<div
className="tw-mr-2 tw-cursor-grab tw-touch-none active:tw-cursor-grabbing"
{...attributes}
{...listeners}
>
<GripVertical className="tw-size-4" />
</div>
)}
{/* Main content area */}
<div
className="tw-flex-1 tw-touch-auto"
onClick={isExpandable ? handleExpandToggle : undefined}
style={{ cursor: isExpandable ? "pointer" : "default" }}
>
<div className="tw-flex tw-items-center tw-gap-2">
{/* Expand/collapse icon */}
{isExpandable && (
<div className="tw-flex tw-size-3 tw-items-center tw-justify-center">
{isExpanded ? (
<ChevronDown className="tw-size-3 tw-stroke-[7]" />
) : (
<ChevronRight className="tw-size-3 tw-stroke-[7]" />
)}
</div>
)}
{/* Custom icon */}
{icon && <div className="tw-flex tw-items-center tw-justify-center">{icon}</div>}
{/* Title and subtitle area */}
<div className="tw-min-w-0 tw-flex-1">
<div className="tw-break-words tw-font-medium tw-leading-relaxed">
{title}
{badge && <span className="tw-ml-1 tw-inline-flex tw-items-center">{badge}</span>}
</div>
{subtitle && (
<div className="tw-flex tw-items-center tw-gap-2">
<span className="tw-bg-secondary tw-text-sm tw-text-muted">{subtitle}</span>
</div>
)}
</div>
</div>
</div>
{/* Action buttons */}
<div className="tw-flex tw-items-center tw-gap-2">
{primaryAction && (
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
primaryAction.onClick(item);
}}
title={primaryAction.tooltip}
>
{primaryAction.icon}
</Button>
)}
{showDropdownMenu && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreVertical className="tw-size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" container={containerRef?.current}>
{dropdownActions.map((action, index) => (
<DropdownMenuItem
key={index}
onClick={(e) => {
e.stopPropagation();
action.onClick(item);
}}
className={cn(action.variant === "destructive" && "tw-text-error")}
>
<span className="tw-mr-2 tw-flex tw-size-4 tw-items-center tw-justify-center">
{action.icon}
</span>
{action.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
</CardHeader>
{/* Expandable content */}
{isExpandable && (
<div
className={cn(
"tw-transition-all tw-duration-300 tw-ease-in-out",
isExpanded ? "tw-max-h-96 tw-opacity-100" : "tw-max-h-0 tw-overflow-hidden tw-opacity-0"
)}
>
<CardContent className="tw-p-3 tw-pt-0">{expandedContent}</CardContent>
</div>
)}
</Card>
);
}

View file

@ -9,7 +9,8 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { HelpCircle, Zap, ZapOff } from "lucide-react";
import { Zap, ZapOff } from "lucide-react";
import { HelpTooltip } from "@/components/ui/help-tooltip";
interface BaseParameterControlProps {
label: string;
@ -83,14 +84,10 @@ export function ParameterControl(props: ParameterControlProps) {
{label}
</span>
{helpText && (
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4 tw-text-muted" />
</TooltipTrigger>
<TooltipContent side="bottom">
<div className="tw-max-w-[300px]">{helpText}</div>
</TooltipContent>
</Tooltip>
<HelpTooltip
buttonClassName="tw-size-4 tw-text-muted"
content={<div className="tw-max-w-[300px]">{helpText}</div>}
/>
)}
</div>
<div className={`tw-flex tw-items-center ${optional ? "tw-gap-3" : "tw-gap-0"}`}>

View file

@ -33,17 +33,16 @@ export const TabItem: React.FC<TabItemProps> = ({ tab, isSelected, onClick, isFi
"tw-whitespace-nowrap",
"tw-text-sm",
"tw-border tw-border-solid tw-border-border",
"tw-rounded-b-[2px] tw-rounded-t-md",
"tw-rounded-b-[2px] tw-rounded-t-sm",
"tw-bg-primary",
"tw-transition-all tw-duration-300 tw-ease-in-out",
"hover:tw-border-interactive-accent",
isSelected && [
"!tw-bg-interactive-accent",
"tw-text-on-accent",
"!tw-px-3",
"!tw-max-w-full",
"tw-transition-all tw-duration-300 tw-ease-in-out",
"tw-delay-200",
"tw-delay-100",
],
"lg:tw-max-w-32",
"md:tw-max-w-32"

View file

@ -153,6 +153,9 @@ export enum ChatModels {
OPENROUTER_GEMINI_2_5_FLASH = "google/gemini-2.5-flash",
OPENROUTER_GEMINI_2_5_PRO = "google/gemini-2.5-pro",
OPENROUTER_GEMINI_2_5_FLASH_LITE = "google/gemini-2.5-flash-lite",
OPENROUTER_GPT_41 = "openai/gpt-4.1",
OPENROUTER_GPT_41_MINI = "openai/gpt-4.1-mini",
OPENROUTER_GROK_4_FAST_FREE = "x-ai/grok-4-fast:free",
}
// Model Providers
@ -223,6 +226,32 @@ export const BUILTIN_CHAT_MODELS: CustomModel[] = [
projectEnabled: true,
capabilities: [ModelCapability.VISION],
},
{
name: ChatModels.OPENROUTER_GPT_41,
provider: ChatModelProviders.OPENROUTERAI,
enabled: true,
isBuiltIn: true,
core: false,
projectEnabled: true,
capabilities: [ModelCapability.VISION],
},
{
name: ChatModels.OPENROUTER_GPT_41_MINI,
provider: ChatModelProviders.OPENROUTERAI,
enabled: true,
isBuiltIn: true,
core: false,
projectEnabled: true,
capabilities: [ModelCapability.VISION],
},
{
name: ChatModels.OPENROUTER_GROK_4_FAST_FREE,
provider: ChatModelProviders.OPENROUTERAI,
enabled: true,
isBuiltIn: true,
core: false,
projectEnabled: true,
},
{
name: ChatModels.GPT_5,
provider: ChatModelProviders.OPENAI,

View file

@ -287,11 +287,11 @@ describe("Agent Prompt Integration Test - Direct Model Testing", () => {
<title>test.md</title>
<content>
New York City, USA - The city that never sleeps, New York buzzes with energy, towering skyscrapers, cultural diversity, and endless ambition. From Broadway to Wall Street, its a global symbol of dreams and hustle.
Tokyo, Japan - A dazzling blend of futuristic tech and centuries-old tradition, Tokyo moves fast yet bows deep. Neon lights, tranquil shrines, and sushi perfection coexist in this mesmerizing metropolis.
Paris, France - Romantic and refined, Paris is a living museum of art, fashion, and gastronomy. Every street corner whispers history, and every café terrace invites you to linger.
London, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.
</content>
</active_note>
@ -301,13 +301,9 @@ describe("Agent Prompt Integration Test - Direct Model Testing", () => {
toolName: "replaceInFile",
// Check if args.diff contains the correct search text
argumentValidator: (args) => {
expect(args).toEqual(
expect.objectContaining({
path: "test.md",
diff: expect.stringContaining(
`------- SEARCH\nLondon, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.\n=======`
),
})
expect(args.path).toBe("test.md");
expect(args.diff).toContain(
`------- SEARCH\nLondon, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.\n=======`
);
},
mockedReturnValue: "File updated successfully",
@ -323,11 +319,11 @@ describe("Agent Prompt Integration Test - Direct Model Testing", () => {
<title>test.md</title>
<content>
New York City, USA - The city that never sleeps, New York buzzes with energy, towering skyscrapers, cultural diversity, and endless ambition. From Broadway to Wall Street, its a global symbol of dreams and hustle.
Tokyo, Japan - A dazzling blend of futuristic tech and centuries-old tradition, Tokyo moves fast yet bows deep. Neon lights, tranquil shrines, and sushi perfection coexist in this mesmerizing metropolis.
Paris, France - Romantic and refined, Paris is a living museum of art, fashion, and gastronomy. Every street corner whispers history, and every café terrace invites you to linger.
London, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.
</content>
</active_note>

View file

@ -1,6 +1,7 @@
import { getChainType } from "@/aiParams";
import { ChainType } from "@/chainFactory";
import { getSettings } from "@/settings/model";
import { logInfo } from "@/logger";
import { getSettings, subscribeToSettingsChange } from "@/settings/model";
import { App, MarkdownView, Platform, TAbstractFile, TFile } from "obsidian";
import { DBOperations } from "./dbOperations";
import { IndexOperations } from "./indexOperations";
@ -12,24 +13,65 @@ export class IndexEventHandler {
private debounceTimer: number | null = null;
private lastActiveFile: TFile | null = null;
private lastActiveFileMtime: number | null = null;
private listenersActive = false;
constructor(
private app: App,
private indexOps: IndexOperations,
private dbOps: DBOperations
) {
this.initializeEventListeners();
this.syncEventListeners();
subscribeToSettingsChange(() => {
this.syncEventListeners();
});
}
private initializeEventListeners() {
if (getSettings().debug) {
console.log("Copilot Plus: Initializing event listeners");
/**
* Determine whether indexing-related events should be processed based on current settings.
*
* @returns {boolean} True when semantic search indexing should run.
*/
private shouldHandleEvents(): boolean {
return getSettings().enableSemanticSearchV3;
}
/**
* Ensure event listeners are registered only when semantic search indexing is enabled.
*/
private syncEventListeners(): void {
const shouldListen = this.shouldHandleEvents();
if (shouldListen && !this.listenersActive) {
logInfo("Copilot Plus: Initializing semantic index event listeners");
this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange);
this.app.vault.on("delete", this.handleFileDelete);
this.listenersActive = true;
} else if (!shouldListen && this.listenersActive) {
this.teardownEventListeners();
}
this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange);
this.app.vault.on("delete", this.handleFileDelete);
}
/**
* Remove indexing event listeners and reset any pending timers or cached state.
*/
private teardownEventListeners(): void {
if (!this.listenersActive) {
return;
}
this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange);
this.app.vault.off("delete", this.handleFileDelete);
if (this.debounceTimer !== null) {
window.clearTimeout(this.debounceTimer);
this.debounceTimer = null;
}
this.lastActiveFile = null;
this.lastActiveFileMtime = null;
this.listenersActive = false;
}
private handleActiveLeafChange = async (leaf: any) => {
if (!this.shouldHandleEvents()) {
return;
}
if (Platform.isMobile && getSettings().disableIndexOnMobile) {
return;
}
@ -73,6 +115,9 @@ export class IndexEventHandler {
};
private debouncedReindexFile = (file: TFile) => {
if (!this.shouldHandleEvents()) {
return;
}
if (this.debounceTimer !== null) {
window.clearTimeout(this.debounceTimer);
}
@ -87,28 +132,19 @@ export class IndexEventHandler {
};
private handleFileDelete = async (file: TAbstractFile) => {
if (!this.shouldHandleEvents()) {
return;
}
if (file instanceof TFile) {
await this.dbOps.removeDocs(file.path);
}
};
public cleanup() {
if (this.debounceTimer !== null) {
window.clearTimeout(this.debounceTimer);
}
this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange);
this.app.vault.off("delete", this.handleFileDelete);
this.teardownEventListeners();
}
public unload() {
if (this.debounceTimer !== null) {
window.clearTimeout(this.debounceTimer);
}
// Clean up file tracking
this.lastActiveFile = null;
this.lastActiveFileMtime = null;
this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange);
this.app.vault.off("delete", this.handleFileDelete);
this.teardownEventListeners();
}
}

View file

@ -49,7 +49,7 @@ const SettingsContent: React.FC = () => {
return (
<div className="tw-flex tw-flex-col">
<div className="tw-inline-flex tw-rounded-lg">
<div className="tw-flex tw-flex-wrap tw-rounded-lg">
{tabs.map((tab, index) => (
<TabItem
key={tab.id}

View file

@ -3,14 +3,14 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { getModelDisplayWithIcons } from "@/components/ui/model-display";
import { SettingItem } from "@/components/ui/setting-item";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { DEFAULT_OPEN_AREA, PLUS_UTM_MEDIUMS } from "@/constants";
import { cn } from "@/lib/utils";
import { createPlusPageUrl } from "@/plusUtils";
import { getModelKeyFromModel, updateSetting, useSettingsValue } from "@/settings/model";
import { PlusSettings } from "@/settings/v2/components/PlusSettings";
import { checkModelApiKey, formatDateTime } from "@/utils";
import { HelpCircle, Key, Loader2 } from "lucide-react";
import { Key, Loader2 } from "lucide-react";
import { Notice } from "obsidian";
import React, { useState } from "react";
import { ApiKeyDialog } from "./ApiKeyDialog";
@ -104,12 +104,9 @@ export const BasicSettings: React.FC = () => {
<span className="tw-leading-none">
Configure API keys for different AI providers
</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
<HelpTooltip
content={
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
<div className="tw-text-sm tw-font-medium tw-text-accent">
API key required for chat and QA features
</div>
@ -117,9 +114,9 @@ export const BasicSettings: React.FC = () => {
To enable chat and QA functionality, please provide an API key from your
selected provider.
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
/>
</div>
}
>
@ -141,12 +138,9 @@ export const BasicSettings: React.FC = () => {
description={
<div className="tw-flex tw-items-center tw-gap-1.5">
<span className="tw-leading-none">Select the Chat model to use</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
<HelpTooltip
content={
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
<div className="tw-text-sm tw-font-medium tw-text-accent">
Default model is OpenRouter Gemini 2.5 Flash
</div>
@ -154,9 +148,9 @@ export const BasicSettings: React.FC = () => {
Set your OpenRouter API key in &apos;API keys&apos; to use this model, or
select a different model from another provider.
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
/>
</div>
}
value={defaultModelActivated ? settings.defaultModelKey : "Select Model"}
@ -188,12 +182,9 @@ export const BasicSettings: React.FC = () => {
description={
<div className="tw-flex tw-items-center tw-gap-1.5">
<span className="tw-leading-none">Select the default chat mode</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
<HelpTooltip
content={
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
<ul className="tw-pl-4 tw-text-sm tw-text-muted">
<li>
<strong>Chat:</strong> Regular chat mode for general conversations and
@ -218,9 +209,9 @@ export const BasicSettings: React.FC = () => {
for more details.
</li>
</ul>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
/>
</div>
}
value={settings.defaultChainType}
@ -269,12 +260,9 @@ export const BasicSettings: React.FC = () => {
<span className="tw-leading-none">
Customize the format of saved conversation note names.
</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
<HelpTooltip
content={
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
<div className="tw-text-sm tw-font-medium tw-text-accent">
Note: All the following variables must be included in the template.
</div>
@ -298,9 +286,9 @@ export const BasicSettings: React.FC = () => {
20250114_153232__polish_this_article_[[Readme]]
</i>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
/>
</div>
}
>

View file

@ -1,7 +1,8 @@
import { useCustomCommands } from "@/commands/state";
import React, { useMemo, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Copy, GripVertical, Info, Lightbulb, PenLine, Plus, Trash2 } from "lucide-react";
import React, { useMemo } from "react";
import { useCustomCommands } from "@/commands/state";
import { MobileCard, MobileCardDropdownAction } from "@/components/ui/mobile-card";
import { Copy, GripVertical, Lightbulb, PenLine, Plus, Trash2 } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import {
@ -12,10 +13,6 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
import { logError } from "@/logger";
import { updateSetting, useSettingsValue } from "@/settings/model";
import { PromptSortStrategy } from "@/types";
import {
closestCenter,
DndContext,
@ -33,6 +30,10 @@ import {
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { cn } from "@/lib/utils";
import { updateSetting, useSettingsValue } from "@/settings/model";
import { PromptSortStrategy } from "@/types";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { EMPTY_COMMAND } from "@/commands/constants";
import { CustomCommandManager } from "@/commands/customCommandManager";
import { CustomCommandSettingsModal } from "@/commands/CustomCommandSettingsModal";
@ -45,9 +46,124 @@ import { generateDefaultCommands } from "@/commands/migrator";
import { CustomCommand } from "@/commands/type";
import { ConfirmModal } from "@/components/modals/ConfirmModal";
import { SettingItem } from "@/components/ui/setting-item";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Notice } from "obsidian";
const MobileCommandCard: React.FC<{
command: CustomCommand;
commands: CustomCommand[];
onUpdate: (newCommand: CustomCommand, prevCommandTitle: string) => void;
onRemove: (command: CustomCommand) => void;
onCopy: (command: CustomCommand) => void;
containerRef: React.RefObject<HTMLDivElement>;
}> = ({ command, commands, onUpdate, onRemove, onCopy, containerRef }) => {
const handleEdit = (cmd: CustomCommand) => {
const modal = new CustomCommandSettingsModal(app, commands, cmd, async (updatedCommand) => {
await onUpdate(updatedCommand, cmd.title);
});
modal.open();
};
const dropdownActions: MobileCardDropdownAction<CustomCommand>[] = [
{
icon: <PenLine className="tw-size-4" />,
label: "Edit",
onClick: handleEdit,
},
{
icon: <Copy className="tw-size-4" />,
label: "Copy",
onClick: onCopy,
},
{
icon: <Trash2 className="tw-size-4" />,
label: "Delete",
onClick: (cmd) => {
new ConfirmModal(
app,
() => onRemove(cmd),
`Are you sure you want to delete the command "${cmd.title}"? This will permanently remove the command file and cannot be undone.`,
"Delete Command",
"Delete",
"Cancel"
).open();
},
variant: "destructive",
},
];
const expandedContent = (
<div className="tw-flex tw-flex-wrap tw-justify-around">
<div className="tw-flex tw-items-center tw-justify-between tw-gap-2">
<div className="tw-flex tw-items-center tw-gap-1">
<span className="tw-text-sm tw-font-medium">In Menu</span>
<HelpTooltip
content={
<div className="tw-max-w-xs tw-text-xs">
If enabled, the command will be available in the context menu when you right-click
in the editor.
</div>
}
/>
</div>
<Checkbox
checked={command.showInContextMenu}
onCheckedChange={(checked) => {
onUpdate(
{
...command,
showInContextMenu: checked === true,
},
command.title
);
}}
/>
</div>
<div className="tw-flex tw-items-center tw-justify-between tw-gap-2">
<div className="tw-flex tw-items-center tw-gap-1">
<span className="tw-text-sm tw-font-medium">In Slash</span>
<HelpTooltip
content={
<div className="tw-max-w-xs tw-text-xs">
If enabled, the command will be available as a slash command in the chat.
</div>
}
/>
</div>
<Checkbox
checked={command.showInSlashMenu}
onCheckedChange={(checked) =>
onUpdate(
{
...command,
showInSlashMenu: checked === true,
},
command.title
)
}
/>
</div>
</div>
);
return (
<MobileCard
id={command.title}
item={command}
title={command.title}
isDraggable
isExpandable
expandedContent={expandedContent}
primaryAction={{
icon: <PenLine className="tw-size-4" />,
onClick: handleEdit,
tooltip: "Edit Command",
}}
dropdownActions={dropdownActions}
containerRef={containerRef}
/>
);
};
const SortableTableRow: React.FC<{
command: CustomCommand;
commands: CustomCommand[];
@ -169,6 +285,7 @@ export const CommandSettings: React.FC = () => {
}, [rawCommands]);
const settings = useSettingsValue();
const containerRef = useRef<HTMLDivElement>(null);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
@ -241,8 +358,40 @@ export const CommandSettings: React.FC = () => {
await CustomCommandManager.getInstance().reorderCommands(newCommands);
};
// Mobile view rendering
const renderMobileView = () => (
<div className="tw-relative md:tw-hidden">
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext
items={commands.map((command) => command.title)}
strategy={verticalListSortingStrategy}
>
<div className="tw-space-y-2">
{commands.length === 0 ? (
<div className="tw-rounded-lg tw-border tw-border-border tw-bg-primary tw-p-8 tw-text-center tw-text-muted">
No custom prompt files found.
</div>
) : (
commands.map((command) => (
<MobileCommandCard
key={command.title}
command={command}
commands={commands}
onUpdate={handleUpdate}
onRemove={handleRemove}
onCopy={handleCopy}
containerRef={containerRef}
/>
))
)}
</div>
</SortableContext>
</DndContext>
</div>
);
return (
<div className="tw-space-y-4">
<div className="tw-space-y-4" ref={containerRef}>
<section>
<div className="tw-mb-4 tw-flex tw-flex-col tw-gap-2">
<div className="tw-text-xl tw-font-bold">Custom Commands</div>
@ -296,7 +445,7 @@ export const CommandSettings: React.FC = () => {
</div>
<div className="tw-flex tw-flex-col tw-gap-4">
<div className="tw-flex tw-w-full tw-justify-between">
<div className="tw-flex tw-w-full tw-justify-between tw-gap-2 md:tw-justify-end">
<div>
<Button
variant="secondary"
@ -309,7 +458,7 @@ export const CommandSettings: React.FC = () => {
).open()
}
>
Generate Default Commands
Generate Default
</Button>
</div>
<Button
@ -330,82 +479,83 @@ export const CommandSettings: React.FC = () => {
modal.open();
}}
>
<Plus className="tw-size-4" />
Add Command
<Plus className="tw-size-2 md:tw-size-4" />
Add Cmd
</Button>
</div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<Table>
<TableHeader>
<TableRow>
<TableHead className="tw-w-10"></TableHead>
<TableHead>Name</TableHead>
<TableHead className="tw-w-24 tw-text-center">
<div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
In Menu
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-max-w-xs tw-text-xs">
If enabled, the command will be available in the context menu when you
right-click in the editor.
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</TableHead>
<TableHead className="tw-w-28 tw-text-center">
<div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
Slash Cmd
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-max-w-xs tw-text-xs">
If enabled, the command will be available as a slash command in the
chat.
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</TableHead>
<TableHead className="tw-w-32 tw-text-center">Actions</TableHead>
</TableRow>
</TableHeader>
<SortableContext
items={commands.map((command) => command.title)}
strategy={verticalListSortingStrategy}
>
<TableBody>
{commands.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="tw-py-8 tw-text-center tw-text-muted">
No custom prompt files found.
</TableCell>
</TableRow>
) : (
commands.map((command) => (
<SortableTableRow
key={command.title}
command={command}
commands={commands}
onUpdate={handleUpdate}
onRemove={handleRemove}
onCopy={handleCopy}
/>
))
)}
</TableBody>
</SortableContext>
</Table>
</DndContext>
{/* Desktop view */}
<div className="tw-hidden md:tw-block">
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<Table>
<TableHeader>
<TableRow>
<TableHead className="tw-w-10"></TableHead>
<TableHead>Name</TableHead>
<TableHead className="tw-w-24 tw-text-center">
<div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
In Menu
<HelpTooltip
content={
<div className="tw-max-w-xs tw-text-xs">
If enabled, the command will be available in the context menu when you
right-click in the editor.
</div>
}
/>
</div>
</TableHead>
<TableHead className="tw-w-28 tw-text-center">
<div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
Slash Cmd
<HelpTooltip
content={
<div className="tw-max-w-xs tw-text-xs">
If enabled, the command will be available as a slash command in the
chat.
</div>
}
/>
</div>
</TableHead>
<TableHead className="tw-w-32 tw-text-center">Actions</TableHead>
</TableRow>
</TableHeader>
<SortableContext
items={commands.map((command) => command.title)}
strategy={verticalListSortingStrategy}
>
<TableBody>
{commands.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="tw-py-8 tw-text-center tw-text-muted">
No custom prompt files found.
</TableCell>
</TableRow>
) : (
commands.map((command) => (
<SortableTableRow
key={command.title}
command={command}
commands={commands}
onUpdate={handleUpdate}
onRemove={handleRemove}
onCopy={handleCopy}
/>
))
)}
</TableBody>
</SortableContext>
</Table>
</DndContext>
</div>
{/* Mobile view */}
{renderMobileView()}
</div>
</section>
</div>

View file

@ -10,12 +10,12 @@ import {
SelectValue,
} from "@/components/ui/select";
import { SettingItem } from "@/components/ui/setting-item";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { AUTOCOMPLETE_CONFIG } from "@/constants";
import { cn } from "@/lib/utils";
import { logError } from "@/logger";
import { updateSetting, useSettingsValue } from "@/settings/model";
import { HelpCircle, RefreshCw } from "lucide-react";
import { RefreshCw } from "lucide-react";
import { Notice } from "obsidian";
import React, { useState } from "react";
import { ToolSettingsSection } from "./ToolSettingsSection";
@ -192,19 +192,16 @@ export const CopilotPlusSettings: React.FC = () => {
<span className="tw-leading-none">
The key used to accept autocomplete suggestions
</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
<HelpTooltip
content={
<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
<div className="tw-text-sm tw-text-muted">
Select the key you want to use for accepting suggestions. Default is
&quot;Tab&quot;.
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
/>
</div>
}
>

View file

@ -19,7 +19,7 @@ import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
import { ChevronDown, HelpCircle, Loader2 } from "lucide-react";
import { ChevronDown, Loader2 } from "lucide-react";
import {
Dialog,
DialogContent,
@ -36,7 +36,7 @@ import {
} from "@/components/ui/select";
import { PasswordInput } from "@/components/ui/password-input";
import { Checkbox } from "@/components/ui/checkbox";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { FormField } from "@/components/ui/form-field";
interface FormErrors {
@ -422,25 +422,21 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
label={
<div className="tw-flex tw-items-center tw-gap-1.5">
<span className="tw-leading-none">Display Name</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
<div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
<div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
<div className="tw-text-[12px]">
Example:
<li>Direct-Paid:Ds-r1</li>
<li>OpenRouter-Paid:Ds-r1</li>
<li>Perplexity-Paid:lg</li>
</div>
<HelpTooltip
content={
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
<div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
<div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
<div className="tw-text-[12px]">
Example:
<li>Direct-Paid:Ds-r1</li>
<li>OpenRouter-Paid:Ds-r1</li>
<li>Perplexity-Paid:lg</li>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
contentClassName="tw-max-w-96"
/>
</div>
}
>
@ -501,18 +497,14 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
label={
<div className="tw-flex tw-items-center tw-gap-1.5">
<span className="tw-leading-none">Model Capabilities</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
<div className="tw-text-sm tw-text-muted">
Only used to display model capabilities, does not affect model functionality
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HelpTooltip
content={
<div className="tw-text-sm tw-text-muted">
Only used to display model capabilities, does not affect model functionality
</div>
}
contentClassName="tw-max-w-96"
/>
</div>
}
>
@ -532,16 +524,11 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
});
}}
/>
<Label htmlFor={id} className="tw-text-sm">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<span>{label}</span>
</TooltipTrigger>
<TooltipContent side="bottom">{description}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<HelpTooltip content={description}>
<Label htmlFor={id} className="tw-text-sm">
{label}
</Label>
</HelpTooltip>
</div>
))}
</div>
@ -550,39 +537,35 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
{renderProviderSpecificFields()}
</div>
<div className="tw-flex tw-items-center tw-justify-end tw-gap-4">
<div className="tw-flex tw-items-center tw-justify-between tw-gap-4">
<div className="tw-flex tw-items-center tw-gap-2">
<Checkbox
id="enable-cors"
checked={model.enableCors || false}
onCheckedChange={(checked: boolean) => setModel({ ...model, enableCors: checked })}
/>
<Label htmlFor="enable-cors" className="tw-text-sm">
<div className="tw-flex tw-items-center tw-gap-1.5">
<span>Enable CORS</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
<div className="tw-text-sm tw-text-muted">
Only check this option when prompted that CORS is needed
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Label htmlFor="enable-cors">
<div className="tw-flex tw-items-center tw-gap-0.5">
<span className="tw-text-xs md:tw-text-sm">CORS</span>
<HelpTooltip
content={
<div className="tw-text-sm tw-text-muted">
Only check this option when prompted that CORS is needed
</div>
}
contentClassName="tw-max-w-96"
/>
</div>
</Label>
</div>
<div className="tw-flex tw-gap-2">
<div className="tw-flex tw-gap-2 tw-text-xs md:tw-text-sm">
<Button variant="secondary" onClick={handleAdd} disabled={isButtonDisabled()}>
Add Model
</Button>
<Button variant="secondary" onClick={handleVerify} disabled={isButtonDisabled()}>
{isVerifying ? (
<>
<Loader2 className="tw-mr-2 tw-size-4 tw-animate-spin" />
<Loader2 className="tw-mr-2 tw-size-2 tw-animate-spin md:tw-size-4 " />
Verify
</>
) : (

View file

@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import {
DEFAULT_MODEL_SETTING,
MODEL_CAPABILITIES,
@ -17,7 +17,6 @@ import {
} from "@/constants";
import { getSettings } from "@/settings/model";
import { debounce, getProviderInfo, getProviderLabel } from "@/utils";
import { HelpCircle } from "lucide-react";
import { App, Modal, Platform } from "obsidian";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { createRoot, Root } from "react-dom/client";
@ -143,25 +142,21 @@ export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
label={
<div className="tw-flex tw-items-center tw-gap-1.5">
<span className="tw-leading-none">Display Name</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
<div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
<div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
<div className="tw-text-[12px]">
Example:
<li>Direct-Paid:Ds-r1</li>
<li>OpenRouter-Paid:Ds-r1</li>
<li>Perplexity-Paid:lg</li>
</div>
<HelpTooltip
content={
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
<div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
<div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
<div className="tw-text-[12px]">
Example:
<li>Direct-Paid:Ds-r1</li>
<li>OpenRouter-Paid:Ds-r1</li>
<li>Perplexity-Paid:lg</li>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
}
contentClassName="tw-max-w-96"
/>
</div>
}
>
@ -207,19 +202,14 @@ export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
label={
<div className="tw-flex tw-items-center tw-gap-1.5">
<span className="tw-leading-none">Model Capabilities</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent align="start" className="tw-max-w-96" side="bottom">
<div className="tw-text-sm tw-text-muted">
Only used to display model capabilities, does not affect model
functionality
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HelpTooltip
content={
<div className="tw-text-sm tw-text-muted">
Only used to display model capabilities, does not affect model functionality
</div>
}
contentClassName="tw-max-w-96"
/>
</div>
}
>
@ -237,16 +227,11 @@ export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
handleLocalUpdate("capabilities", value);
}}
/>
<Label htmlFor={id} className="tw-text-sm">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<span>{label}</span>
</TooltipTrigger>
<TooltipContent side="bottom">{description}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<HelpTooltip content={description}>
<Label htmlFor={id} className="tw-text-sm">
{label}
</Label>
</HelpTooltip>
</div>
))}
</div>

View file

@ -143,7 +143,6 @@ export const ModelSettings: React.FC = () => {
return (
<div className="tw-space-y-4">
<section>
<div className="tw-mb-3 tw-text-xl tw-font-bold">Chat Models</div>
<ModelTable
models={settings.activeModels}
onEdit={(model) => handleEditModel(model)}
@ -153,7 +152,7 @@ export const ModelSettings: React.FC = () => {
onUpdateModel={handleTableUpdate}
onReorderModels={(newModels) => handleModelReorder(newModels)}
onRefresh={handleRefreshChatModels}
title="Chat Model"
title="Chat Models"
/>
{/* model add dialog */}
@ -184,7 +183,6 @@ export const ModelSettings: React.FC = () => {
</section>
<section>
<div className="tw-mb-3 tw-text-xl tw-font-bold">Embedding Models</div>
<ModelTable
models={settings.activeEmbeddingModels}
onEdit={(model) => handleEditModel(model, true)}
@ -194,7 +192,7 @@ export const ModelSettings: React.FC = () => {
onUpdateModel={handleEmbeddingModelUpdate}
onReorderModels={(newModels) => handleModelReorder(newModels, true)}
onRefresh={handleRefreshEmbeddingModels}
title="Embedding Model"
title="Embedding Models"
/>
{/* Embedding model add dialog */}

View file

@ -1,7 +1,7 @@
import { CustomModel } from "@/aiParams";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { MobileCard, MobileCardDropdownAction } from "@/components/ui/mobile-card";
import { ModelCapabilityIcons } from "@/components/ui/model-display";
import {
Table,
@ -11,7 +11,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { MODEL_CAPABILITIES, ModelCapability } from "@/constants";
import { cn } from "@/lib/utils";
import { getModelKeyFromModel } from "@/settings/model";
@ -34,8 +34,6 @@ import {
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
ChevronDown,
ChevronRight,
Copy,
Eye,
Globe,
@ -49,14 +47,13 @@ import {
RefreshCw,
Trash2,
} from "lucide-react";
import React, { ForwardRefExoticComponent, RefAttributes } from "react";
import React, { ForwardRefExoticComponent, RefAttributes, useRef } from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useContainerContext } from "@/settings/v2/components/ContainerContext";
const CAPABILITY_ICONS: Record<
ModelCapability,
@ -89,34 +86,60 @@ const CAPABILITY_ORDER = [
ModelCapability.WEB_SEARCH,
] as const;
interface ModelTableHeaderProps {
title: string;
onRefresh?: () => void;
onAdd: () => void;
}
/**
* Renders the model table header with a title and aligned action buttons.
*/
const ModelTableHeader: React.FC<ModelTableHeaderProps> = ({ title, onRefresh, onAdd }) => (
<div className="tw-mb-3 tw-flex tw-flex-col tw-gap-2 md:tw-flex-row md:tw-items-center md:tw-justify-between">
<h3 className="tw-text-xl tw-font-bold">{title}</h3>
<div className="tw-flex tw-flex-col tw-gap-2 sm:tw-flex-row sm:tw-items-center sm:tw-justify-end">
{onRefresh && (
<Button
onClick={onRefresh}
variant="secondary"
className="tw-flex tw-items-center tw-gap-2"
>
<RefreshCw className="tw-size-2 md:tw-size-4" />
Refresh Built-ins
</Button>
)}
<Button onClick={onAdd} variant="default" className="tw-flex tw-items-center tw-gap-2">
<Plus className="tw-size-2 md:tw-size-4" />
Add Model
</Button>
</div>
</div>
);
const renderCapabilities = (model: CustomModel) => {
return (
<TooltipProvider delayDuration={0}>
<div className="tw-mx-auto tw-grid tw-w-16 tw-grid-cols-3 tw-gap-1">
{CAPABILITY_ORDER.map((capability) => {
const config = CAPABILITY_ICONS[capability];
if (!config) return <div key={capability} className="tw-w-4" />;
<div className="tw-mx-auto tw-grid tw-w-16 tw-grid-cols-3 tw-gap-1">
{CAPABILITY_ORDER.map((capability) => {
const config = CAPABILITY_ICONS[capability];
if (!config) return <div key={capability} className="tw-w-4" />;
const Icon = config.icon;
const hasCapability = model.capabilities?.includes(capability);
const Icon = config.icon;
const hasCapability = model.capabilities?.includes(capability);
return hasCapability ? (
<Tooltip key={capability}>
<TooltipTrigger asChild>
<div className="tw-flex tw-items-center tw-justify-center">
<Icon className={cn("tw-size-4", config.color)} />
</div>
</TooltipTrigger>
<TooltipContent side="bottom">{config.tooltip}</TooltipContent>
</Tooltip>
) : (
<div key={capability} className="tw-flex tw-items-center tw-justify-center">
<div className="tw-size-4" />
return hasCapability ? (
<HelpTooltip key={capability} content={config.tooltip} side="bottom">
<div className="tw-flex tw-items-center tw-justify-center">
<Icon className={cn("tw-size-4", config.color)} />
</div>
);
})}
</div>
</TooltipProvider>
</HelpTooltip>
) : (
<div key={capability} className="tw-flex tw-items-center tw-justify-center">
<div className="tw-size-4" />
</div>
);
})}
</div>
);
};
@ -127,6 +150,7 @@ interface ModelCardProps {
onDelete: (modelKey: string) => void;
onUpdateModel: (model: CustomModel) => void;
id: string;
containerRef: React.RefObject<HTMLDivElement>;
}
const ModelCard: React.FC<ModelCardProps> = ({
@ -136,169 +160,82 @@ const ModelCard: React.FC<ModelCardProps> = ({
onDelete,
onUpdateModel,
id,
containerRef,
}) => {
const [isExpanded, setIsExpanded] = React.useState(false);
const container = useContainerContext();
const dropdownActions: MobileCardDropdownAction<CustomModel>[] = [];
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
disabled: model.core,
});
if (onEdit) {
dropdownActions.push({
icon: <PencilLine className="tw-size-4" />,
label: "Edit",
onClick: onEdit,
});
}
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
if (onCopy && !model.core) {
dropdownActions.push({
icon: <Copy className="tw-size-4" />,
label: "Copy",
onClick: onCopy,
});
}
if (!model.core) {
dropdownActions.push({
icon: <Trash2 className="tw-size-4" />,
label: "Delete",
onClick: () => onDelete(getModelKeyFromModel(model)),
variant: "destructive",
});
}
const expandedContent = (
<div className="tw-flex tw-justify-around">
{!model.isEmbeddingModel && (
<div className="tw-flex tw-items-center tw-gap-2">
<span className="tw-text-sm">Enabled</span>
<Checkbox
checked={model.enabled}
onCheckedChange={(checked: boolean) => onUpdateModel({ ...model, enabled: checked })}
/>
</div>
)}
<div className="tw-flex tw-items-center tw-gap-2">
<span className="tw-text-sm">CORS</span>
<Checkbox
checked={model.enableCors}
onCheckedChange={(checked: boolean) => onUpdateModel({ ...model, enableCors: checked })}
/>
</div>
</div>
);
return (
<Card
ref={setNodeRef}
style={style}
className={cn(
"tw-mb-2",
isDragging && [
"tw-opacity-90",
"tw-shadow-lg",
"tw-border-accent/50",
"tw-relative",
"tw-z-[9999]",
"tw-bg-primary",
"tw-rounded-lg",
"tw-transform-gpu",
],
!model.core && "tw-touch-none"
)}
>
<CardHeader className="tw-p-3">
<div className="tw-flex tw-items-center tw-justify-between">
{!model.core && (
<div
className="tw-mr-2 tw-cursor-grab tw-touch-none active:tw-cursor-grabbing"
{...attributes}
{...listeners}
>
<GripVertical className="tw-size-4" />
</div>
)}
<div className="tw-flex-1 tw-cursor-pointer" onClick={() => setIsExpanded(!isExpanded)}>
<div className="tw-flex tw-items-center tw-gap-2">
<div className="tw-flex tw-size-3 tw-items-center tw-justify-center">
{isExpanded ? (
<ChevronDown className="tw-size-3 tw-stroke-[7]" />
) : (
<ChevronRight className="tw-size-3 tw-stroke-[7]" />
)}
</div>
<div>
<div className="tw-flex tw-items-center tw-gap-1">
<span className="tw-font-medium">{model.displayName || model.name}</span>
{model.capabilities && model.capabilities.length > 0 && (
<ModelCapabilityIcons capabilities={model.capabilities} iconSize={14} />
)}
</div>
<div className="tw-flex tw-items-center tw-gap-2">
<span className="tw-bg-secondary tw-text-sm tw-text-muted">
{getProviderLabel(model.provider, model)}
</span>
</div>
</div>
</div>
</div>
<div className="tw-flex tw-items-center tw-gap-2">
{onEdit && (
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
onEdit(model);
}}
>
<Pencil className="tw-size-4" />
</Button>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreVertical className="tw-size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" container={container}>
{onEdit && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onEdit(model);
}}
>
<PencilLine className="tw-mr-2 tw-size-4" />
Edit
</DropdownMenuItem>
)}
{onCopy && !model.core && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onCopy(model);
}}
>
<Copy className="tw-mr-2 tw-size-4" />
Copy
</DropdownMenuItem>
)}
{!model.core && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onDelete(getModelKeyFromModel(model));
}}
className="tw-text-error"
>
<Trash2 className="tw-mr-2 tw-size-4" />
Delete
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</CardHeader>
<div
className={cn(
"tw-transition-all tw-duration-300 tw-ease-in-out",
isExpanded ? "tw-max-h-20 tw-opacity-100" : "tw-max-h-0 tw-overflow-hidden tw-opacity-0"
)}
>
<CardContent className="tw-p-3 tw-pt-0">
<div className="tw-flex tw-justify-around">
{!model.isEmbeddingModel && (
<div className="tw-flex tw-items-center tw-gap-2">
<span className="tw-text-sm">Enabled</span>
<Checkbox
checked={model.enabled}
onCheckedChange={(checked: boolean) =>
onUpdateModel({ ...model, enabled: checked })
}
/>
</div>
)}
<div className="tw-flex tw-items-center tw-gap-2">
<span className="tw-text-sm">CORS</span>
<Checkbox
checked={model.enableCors}
onCheckedChange={(checked: boolean) =>
onUpdateModel({ ...model, enableCors: checked })
}
/>
</div>
</div>
</CardContent>
</div>
</Card>
<MobileCard
id={id}
item={model}
title={model.displayName || model.name}
subtitle={getProviderLabel(model.provider, model)}
badge={
model.capabilities && model.capabilities.length > 0 ? (
<ModelCapabilityIcons capabilities={model.capabilities} iconSize={14} />
) : undefined
}
isDraggable={!model.core}
isExpandable
expandedContent={expandedContent}
primaryAction={
onEdit
? {
icon: <Pencil className="tw-size-4" />,
onClick: onEdit,
tooltip: "Edit Model",
}
: undefined
}
dropdownActions={dropdownActions}
containerRef={containerRef}
/>
);
};
@ -309,12 +246,12 @@ const DesktopSortableTableRow: React.FC<{
onDelete: (modelKey: string) => void;
onUpdateModel: (model: CustomModel) => void;
isEmbeddingModel: boolean;
}> = ({ model, onEdit, onCopy, onDelete, onUpdateModel, isEmbeddingModel }) => {
containerRef: React.RefObject<HTMLDivElement>;
}> = ({ model, onEdit, onCopy, onDelete, onUpdateModel, isEmbeddingModel, containerRef }) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: getModelKeyFromModel(model),
disabled: model.core,
});
const container = useContainerContext();
const style = {
transform: CSS.Transform.toString(transform),
@ -387,7 +324,7 @@ const DesktopSortableTableRow: React.FC<{
<MoreVertical className="tw-size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" container={container}>
<DropdownMenuContent align="end" container={containerRef.current}>
{onEdit && (
<DropdownMenuItem onClick={() => onEdit(model)}>
<PencilLine className="tw-mr-2 tw-size-4" />
@ -452,6 +389,8 @@ export const ModelTable: React.FC<ModelTableProps> = ({
})
);
const containerRef = useRef<HTMLDivElement>(null);
// Find the index of the first non-core model
const firstDraggableIndex = models.findIndex((model) => !model.core);
@ -483,7 +422,7 @@ export const ModelTable: React.FC<ModelTableProps> = ({
return {
...transform,
x: 0,
y: currentIndex * rowHeight,
y: 0,
};
}
}
@ -529,16 +468,25 @@ export const ModelTable: React.FC<ModelTableProps> = ({
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[createDragModifier(true)]}
autoScroll={{
enabled: true,
acceleration: 10,
threshold: {
x: 0,
y: 0.2,
},
}}
>
<SortableContext
items={models.map((model) => getModelKeyFromModel(model))}
strategy={verticalListSortingStrategy}
>
<div className="tw-relative tw-space-y-2">
<div className="tw-relative tw-touch-auto tw-space-y-2 tw-overflow-auto tw-pb-2">
{models.map((model) => (
<ModelCard
key={getModelKeyFromModel(model)}
id={getModelKeyFromModel(model)}
containerRef={containerRef}
model={model}
onEdit={onEdit}
onCopy={onCopy}
@ -553,7 +501,8 @@ export const ModelTable: React.FC<ModelTableProps> = ({
);
return (
<div className="tw-mb-4">
<div ref={containerRef} className="tw-mb-4">
<ModelTableHeader title={title} onRefresh={onRefresh} onAdd={onAdd} />
{/* Desktop view */}
<div className="tw-hidden md:tw-block">
<DndContext
@ -583,6 +532,7 @@ export const ModelTable: React.FC<ModelTableProps> = ({
{models.map((model) => (
<DesktopSortableTableRow
key={getModelKeyFromModel(model)}
containerRef={containerRef}
model={model}
onEdit={onEdit}
onCopy={onCopy}
@ -600,23 +550,6 @@ export const ModelTable: React.FC<ModelTableProps> = ({
{/* Mobile view */}
{renderMobileView()}
<div className="tw-mt-4 tw-flex tw-justify-end tw-gap-2">
{onRefresh && (
<Button
onClick={onRefresh}
variant="secondary"
className="tw-flex tw-items-center tw-gap-2"
>
<RefreshCw className="tw-size-4" />
Refresh Built-in Models
</Button>
)}
<Button onClick={onAdd} variant="secondary" className="tw-flex tw-items-center tw-gap-2">
<Plus className="tw-size-4" />
Add Custom Model
</Button>
</div>
</div>
);
};

View file

@ -31,8 +31,12 @@ export function PlusSettings() {
<div className="tw-flex tw-flex-col tw-gap-2 tw-text-sm tw-text-muted">
<div>
Copilot Plus takes your Obsidian experience to the next level with cutting-edge AI
capabilities. This premium tier unlocks advanced features, including chat context, PDF and
image support, web search integration, exclusive chat and embedding models, and much more.
capabilities. This premium tier unlocks advanced features:{" "}
<strong>
{" "}
including chat context, PDF and image support, web search integration, exclusive chat
and embedding models, and much more.
</strong>
</div>
<div>
Copilot Plus is evolving fast, with new features and improvements rolling out regularly.
@ -62,12 +66,16 @@ export function PlusSettings() {
new CopilotPlusWelcomeModal(app).open();
}
}}
className="tw-min-w-20"
className="tw-min-w-10 tw-text-xs md:tw-text-sm"
>
{isChecking ? <Loader2 className="tw-size-4 tw-animate-spin" /> : "Apply"}
{isChecking ? <Loader2 className="tw-size-2 tw-animate-spin md:tw-size-4" /> : "Apply"}
</Button>
<Button variant="secondary" onClick={() => navigateToPlusPage(PLUS_UTM_MEDIUMS.SETTINGS)}>
Join Now <ExternalLink className="tw-size-4" />
<Button
className="tw-text-xs md:tw-text-sm"
variant="secondary"
onClick={() => navigateToPlusPage(PLUS_UTM_MEDIUMS.SETTINGS)}
>
Join Now <ExternalLink className="tw-size-2 md:tw-size-4" />
</Button>
</div>
<div className="tw-text-error">{error}</div>

View file

@ -4,10 +4,9 @@ import { SemanticSearchToggleModal } from "@/components/modals/SemanticSearchTog
import { Button } from "@/components/ui/button";
import { getModelDisplayWithIcons } from "@/components/ui/model-display";
import { SettingItem } from "@/components/ui/setting-item";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { HelpTooltip } from "@/components/ui/help-tooltip";
import { VAULT_VECTOR_STORE_STRATEGIES } from "@/constants";
import { getModelKeyFromModel, updateSetting, useSettingsValue } from "@/settings/model";
import { HelpCircle } from "lucide-react";
import { Notice } from "obsidian";
import React from "react";
@ -83,25 +82,20 @@ export const QASettings: React.FC = () => {
Powers Semantic Vault Search and Relevant Notes. Enable Semantic Search to use
it.
</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
<div className="tw-pt-2 tw-text-sm tw-text-muted">
This model converts text into vector representations, essential for
semantic search and Question Answering (QA) functionality. Changing the
embedding model will:
</div>
<ul className="tw-pl-4 tw-text-sm tw-text-muted">
<li>Require rebuilding your vault&#39;s vector index</li>
<li>Affect semantic search quality</li>
<li>Impact Question Answering feature performance</li>
</ul>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HelpTooltip
content={<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
<div className="tw-pt-2 tw-text-sm tw-text-muted">
This model converts text into vector representations, essential for
semantic search and Question Answering (QA) functionality. Changing the
embedding model will:
</div>
<ul className="tw-pl-4 tw-text-sm tw-text-muted">
<li>Require rebuilding your vault&#39;s vector index</li>
<li>Affect semantic search quality</li>
<li>Impact Question Answering feature performance</li>
</ul>
</div>}
/>
</div>
</div>
}
@ -123,51 +117,46 @@ export const QASettings: React.FC = () => {
<span className="tw-leading-none">
Decide when you want the vault to be indexed.
</span>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="tw-size-4" />
</TooltipTrigger>
<TooltipContent>
<div className="tw-space-y-2 tw-py-2">
<div className="tw-space-y-1">
<div className="tw-text-sm tw-text-muted">
Choose when to index your vault:
</div>
<ul className="tw-list-disc tw-space-y-1 tw-pl-2 tw-text-sm">
<li>
<div className="tw-flex tw-items-center tw-gap-1">
<strong className="tw-inline-block tw-whitespace-nowrap">
NEVER:
</strong>
<span>Manual indexing via command or refresh only</span>
</div>
</li>
<li>
<div className="tw-flex tw-items-center tw-gap-1">
<strong className="tw-inline-block tw-whitespace-nowrap">
ON STARTUP:
</strong>
<span>Index updates when plugin loads or reloads</span>
</div>
</li>
<li>
<div className="tw-flex tw-items-center tw-gap-1">
<strong className="tw-inline-block tw-whitespace-nowrap">
ON MODE SWITCH:
</strong>
<span>Updates when entering QA mode (Recommended)</span>
</div>
</li>
</ul>
<HelpTooltip
content={
<div className="tw-space-y-2 tw-py-2">
<div className="tw-space-y-1">
<div className="tw-text-sm tw-text-muted">
Choose when to index your vault:
</div>
<p className="tw-text-sm tw-text-callout-warning">
Warning: Cost implications for large vaults with paid models
</p>
<ul className="tw-list-disc tw-space-y-1 tw-pl-2 tw-text-sm">
<li>
<div className="tw-flex tw-items-center tw-gap-1">
<strong className="tw-inline-block tw-whitespace-nowrap">
NEVER:
</strong>
<span>Manual indexing via command or refresh only</span>
</div>
</li>
<li>
<div className="tw-flex tw-items-center tw-gap-1">
<strong className="tw-inline-block tw-whitespace-nowrap">
ON STARTUP:
</strong>
<span>Index updates when plugin loads or reloads</span>
</div>
</li>
<li>
<div className="tw-flex tw-items-center tw-gap-1">
<strong className="tw-inline-block tw-whitespace-nowrap">
ON MODE SWITCH:
</strong>
<span>Updates when entering QA mode (Recommended)</span>
</div>
</li>
</ul>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<p className="tw-text-sm tw-text-callout-warning">
Warning: Cost implications for large vaults with paid models
</p>
</div>
}
/>
</div>
}
value={settings.indexVaultToVectorStore}

View file

@ -881,3 +881,85 @@ If your plugin does not need CSS, delete this file.
.workspace-leaf-content[data-type="copilot-chat-view"] .view-content {
padding-bottom: max(var(--safe-area-inset-bottom), var(--size-4-8)) !important;
}
/* Collapsible sources styling */
.copilot-sources {
margin-top: var(--size-4-1);
padding: calc(var(--size-4-2) / 2) var(--size-4-2);
border-radius: var(--radius-s);
border: 1px solid color-mix(in srgb, var(--background-modifier-border) 70%, transparent);
background-color: color-mix(
in srgb,
var(--background-primary) 94%,
var(--background-modifier-border) 6%
);
width: min(100%, 32rem);
}
.copilot-sources__summary {
font-size: var(--font-ui-smaller);
color: color-mix(in srgb, var(--text-muted) 90%, transparent);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--size-4-2);
padding: calc(var(--size-4-2) / 4) 0;
padding-left: var(--size-4-2);
padding-right: var(--size-4-2);
margin: 0;
}
.copilot-sources__summary::-webkit-details-marker {
color: color-mix(in srgb, var(--text-muted) 80%, transparent);
}
.copilot-sources__summary::after {
content: ">";
font-size: var(--font-ui-smaller);
color: color-mix(in srgb, var(--text-muted) 70%, transparent);
transition: transform 0.15s ease;
}
.copilot-sources[open] .copilot-sources__summary {
margin-bottom: calc(var(--size-4-2) / 2);
}
.copilot-sources[open] .copilot-sources__summary::after {
transform: rotate(90deg);
}
.copilot-sources__list {
list-style: none;
margin: 0;
padding: 0;
padding-left: var(--size-4-2);
padding-right: var(--size-4-2);
display: flex;
flex-direction: column;
gap: calc(var(--size-4-2) / 2);
font-size: var(--font-ui-smaller);
color: color-mix(in srgb, var(--text-muted) 85%, transparent);
}
.copilot-sources__item {
display: flex;
gap: var(--size-4-2);
align-items: baseline;
}
.copilot-sources__index {
color: color-mix(in srgb, var(--text-muted) 65%, transparent);
font-weight: var(--font-medium);
min-width: var(--size-4-3);
text-align: right;
}
.copilot-sources__text {
color: color-mix(in srgb, var(--text-muted) 90%, transparent);
line-height: 1.4;
}
.copilot-sources__text a {
color: var(--text-accent);
}

View file

@ -92,5 +92,6 @@
"2.9.5": "0.15.0",
"3.0.0": "0.15.0",
"3.0.1": "0.15.0",
"3.0.2": "0.15.0"
"3.0.2": "0.15.0",
"3.0.3": "0.15.0"
}