From 82eaaaf113c01fbdc682efe7869d121eeb0e96cd Mon Sep 17 00:00:00 2001 From: Zero Liu Date: Mon, 30 Dec 2024 13:55:30 -0800 Subject: [PATCH] Relevant note new UI (#981) * Relevant note new UI * Make Relevant Notes section a sticky head * Test distribution * Adjust similarity threshold * Adjust relevance sorting * Make refresh reindex * Address latest comments --- package-lock.json | 736 +++++++++++++++++- package.json | 5 +- src/components/Chat.tsx | 5 +- .../chat-components/ChatMessages.tsx | 95 ++- .../chat-components/RelevantNotes.tsx | 310 ++++++++ .../chat-components/SuggestedPrompts.tsx | 130 ++-- src/components/ui/badge.tsx | 33 + src/components/ui/card.tsx | 55 ++ src/components/ui/collapsible.tsx | 9 + src/components/ui/popover.tsx | 31 + src/components/ui/tooltip.tsx | 30 + src/constants.ts | 1 + src/lib/utils.ts | 20 +- src/search/dbOperations.ts | 8 + src/search/findRelevantNotes.ts | 26 +- src/search/vectorStoreManager.ts | 16 +- src/settings/components/GeneralSettings.tsx | 7 + src/settings/model.ts | 1 + src/styles/tailwind.css | 23 +- tailwind.config.js | 30 +- 20 files changed, 1373 insertions(+), 198 deletions(-) create mode 100644 src/components/chat-components/RelevantNotes.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/tooltip.tsx diff --git a/package-lock.json b/package-lock.json index db24a2a2..c3d8cb60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ "@langchain/groq": "^0.1.2", "@langchain/openai": "^0.3.14", "@orama/orama": "^3.0.0-rc-2", + "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-tooltip": "^1.1.3", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-tooltip": "^1.1.6", "@tabler/icons-react": "^2.14.0", "axios": "^1.3.4", "chrono-node": "^2.7.7", @@ -48,6 +50,7 @@ }, "devDependencies": { "@langchain/ollama": "^0.1.1", + "@tailwindcss/container-queries": "^0.1.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/crypto-js": "^4.1.1", @@ -4177,6 +4180,122 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz", + "integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==", + "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-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-collapsible/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==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-collapsible/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==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "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-collapsible/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -4406,6 +4525,285 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz", + "integrity": "sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "^2.6.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/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==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", + "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "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" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-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==", + "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" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-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==", + "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" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -4582,22 +4980,23 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", - "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz", + "integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==", + "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" + "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -4614,6 +5013,198 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", + "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "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" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-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==", + "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" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -4711,11 +5302,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz", + "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==", + "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", @@ -4732,6 +5324,62 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "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-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", @@ -5799,6 +6447,16 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@tailwindcss/container-queries": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz", + "integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.2.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz", @@ -10274,14 +10932,6 @@ "node": ">= 0.4" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -15335,19 +15985,20 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.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" }, "peerDependenciesMeta": { "@types/react": { @@ -15356,20 +16007,20 @@ } }, "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", - "invariant": "^2.2.4", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.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": { @@ -17194,9 +17845,10 @@ } }, "node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -17204,8 +17856,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { diff --git a/package.json b/package.json index 822a2c6b..0761ac5f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "license": "AGPL-3.0", "devDependencies": { "@langchain/ollama": "^0.1.1", + "@tailwindcss/container-queries": "^0.1.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/crypto-js": "^4.1.1", @@ -77,8 +78,10 @@ "@langchain/groq": "^0.1.2", "@langchain/openai": "^0.3.14", "@orama/orama": "^3.0.0-rc-2", + "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-tooltip": "^1.1.3", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-tooltip": "^1.1.6", "@tabler/icons-react": "^2.14.0", "axios": "^1.3.4", "chrono-node": "^2.7.7", diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index 2da23d9d..50782054 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -620,9 +620,10 @@ ${chatContent}`; onRegenerate={handleRegenerate} onEdit={handleEdit} onDelete={handleDelete} - onSelectSuggestedPrompt={(prompt) => { - setInputMessage(prompt); + onInsertToChat={(prompt) => { + setInputMessage((prev) => `${prev} ${prompt} `); }} + onReplaceChat={setInputMessage} />
void; onEdit: (messageIndex: number, newMessage: string) => void; onDelete: (messageIndex: number) => void; - onSelectSuggestedPrompt: (prompt: string) => void; + onInsertToChat: (prompt: string) => void; + onReplaceChat: (prompt: string) => void; } const ChatMessages: React.FC = ({ @@ -27,12 +30,14 @@ const ChatMessages: React.FC = ({ onRegenerate, onEdit, onDelete, - onSelectSuggestedPrompt, + onInsertToChat, + onReplaceChat, }) => { const [loadingDots, setLoadingDots] = useState(""); + const settings = useSettingsValue(); const scrollToBottom = () => { - const chatMessagesContainer = document.querySelector(".chat-messages"); + const chatMessagesContainer = document.querySelector("[data-testid='chat-messages']"); if (chatMessagesContainer) { chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight; } @@ -58,8 +63,15 @@ const ChatMessages: React.FC = ({ if (!chatHistory.filter((message) => message.isVisible).length && !currentAiMessage) { return ( -
- +
+ {settings.showRelevantNotes && ( + + )} + {settings.showSuggestedPrompts && }
); } @@ -69,38 +81,51 @@ const ChatMessages: React.FC = ({ }; return ( -
- {chatHistory.map( - (message, index) => - message.isVisible && ( - { - onInsertAtCursor(message.message); - }} - onRegenerate={() => onRegenerate(index)} - onEdit={(newMessage) => onEdit(index, newMessage)} - onDelete={() => onDelete(index)} - /> - ) - )} - {(currentAiMessage || loading) && ( - {}} +
+ {settings.showRelevantNotes && ( + )} +
+ {chatHistory.map( + (message, index) => + message.isVisible && ( + { + onInsertAtCursor(message.message); + }} + onRegenerate={() => onRegenerate(index)} + onEdit={(newMessage) => onEdit(index, newMessage)} + onDelete={() => onDelete(index)} + /> + ) + )} + {(currentAiMessage || loading) && ( + {}} + /> + )} +
); }; diff --git a/src/components/chat-components/RelevantNotes.tsx b/src/components/chat-components/RelevantNotes.tsx new file mode 100644 index 00000000..63eb3202 --- /dev/null +++ b/src/components/chat-components/RelevantNotes.tsx @@ -0,0 +1,310 @@ +import { Badge } from "@/components/ui/badge"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { + findRelevantNotes, + getSimilarityCategory, + RelevantNoteEntry, +} from "@/search/findRelevantNotes"; +import VectorStoreManager from "@/search/vectorStoreManager"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { + ArrowRight, + ChevronDown, + ChevronUp, + Info, + PlusCircle, + RefreshCcw, + TriangleAlert, +} from "lucide-react"; +import React, { forwardRef, useEffect, useState } from "react"; +import { Notice, TFile } from "obsidian"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; + +function useRelevantNotes(refresher: number) { + const [relevantNotes, setRelevantNotes] = useState([]); + const activeFile = app.workspace.getActiveFile(); + + useEffect(() => { + async function fetchNotes() { + if (!activeFile?.path) return; + const db = await VectorStoreManager.getInstance().getDb(); + const notes = await findRelevantNotes({ db, filePath: activeFile.path }); + setRelevantNotes(notes); + } + fetchNotes(); + }, [activeFile?.path, refresher]); + + return relevantNotes; +} + +function useHasIndex(notePath: string, refresher: number) { + const [hasIndex, setHasIndex] = useState(true); + useEffect(() => { + if (!notePath) return; + async function fetchHasIndex() { + const hasIndex = await VectorStoreManager.getInstance().hasIndex(notePath); + setHasIndex(hasIndex); + } + fetchHasIndex(); + }, [notePath, refresher]); + return hasIndex; +} + +const ExplainerBadge = forwardRef( + ({ children, ...props }, ref) => { + return ( +
+ + {children} + +
+ ); + } +); + +function inSameFolder(path1: string, path2: string) { + const folder1 = path1.split("/").slice(0, -1).join("/"); + const folder2 = path2.split("/").slice(0, -1).join("/"); + return folder1 === folder2; +} + +function SimilarityBadge({ score }: { score: number }) { + const category = getSimilarityCategory(score); + let text = "🔴 Low Similarity"; + if (category === 2) text = "🟠 Medium Similarity"; + if (category === 3) text = "🟢 High Similarity"; + return ( + + + {text} + + + Similarity Score: {Math.round(score * 100).toFixed(0)}% + + + ); +} + +function Metadata({ note }: { note: RelevantNoteEntry }) { + return ( +
+ {note.metadata.similarityScore != null && ( + + )} + {note.metadata.hasOutgoingLinks && Outgoing Links} + {note.metadata.hasBacklinks && Backlinks} +
+ ); +} + +function RelevantNote({ + showPath = false, + note, + onAddToChat, + onNavigateToNote, +}: { + showPath: boolean; + note: RelevantNoteEntry; + onAddToChat: () => void; + onNavigateToNote: () => void; +}) { + return ( +
+
+ + {note.document.title} + + {showPath && ( + + {note.document.path} + + )} +
+ +
+
+
+ + + + + Add to Chat + +
+
+ ); +} + +function RelevantNotePopover({ + note, + onAddToChat, + onNavigateToNote, + children, +}: { + note: RelevantNoteEntry; + onAddToChat: () => void; + onNavigateToNote: () => void; + children: React.ReactNode; +}) { + return ( + + {children} + + {note.document.title} + {note.document.path} + +
+ + +
+
+
+ ); +} + +export function RelevantNotes({ + className, + onInsertToChat, + defaultOpen = false, +}: { + className?: string; + onInsertToChat: (prompt: string) => void; + defaultOpen?: boolean; +}) { + const [refresher, setRefresher] = useState(0); + const [isOpen, setIsOpen] = useState(defaultOpen); + const relevantNotes = useRelevantNotes(refresher); + const activeFile = app.workspace.getActiveFile(); + const hasIndex = useHasIndex(activeFile?.path ?? "", refresher); + const navigateToNote = (notePath: string) => { + const file = app.vault.getAbstractFileByPath(notePath); + if (file instanceof TFile) { + app.workspace.getLeaf(false).openFile(file); + } + }; + const addToChat = (prompt: string) => { + onInsertToChat(`[[${prompt}]]`); + }; + const refreshIndex = async () => { + if (activeFile) { + await VectorStoreManager.getInstance().reindexFile(activeFile); + new Notice(`Reindexed ${activeFile.name}`); + setRefresher(refresher + 1); + } + }; + return ( +
+ +
+
+ Relevant Notes + + + + + + Relevance is a combination of semantic similarity and links. + + + + {!hasIndex && ( + + + + + Note has not been indexed + + )} +
+
+ + + + + Reindex Current Note + + {relevantNotes.length > 0 && ( + + + + )} +
+
+ {relevantNotes.length === 0 && ( +
+ No relevant notes found +
+ )} + {!isOpen && relevantNotes.length > 0 && ( +
+ {relevantNotes.map((note) => ( + addToChat(note.document.title)} + onNavigateToNote={() => navigateToNote(note.document.path)} + > + + + {note.document.title} + + + + ))} +
+ )} + +
+ {relevantNotes.map((note) => ( + addToChat(note.document.title)} + onNavigateToNote={() => navigateToNote(note.document.path)} + /> + ))} +
+
+
+
+ ); +} diff --git a/src/components/chat-components/SuggestedPrompts.tsx b/src/components/chat-components/SuggestedPrompts.tsx index 375b793d..1ee58326 100644 --- a/src/components/chat-components/SuggestedPrompts.tsx +++ b/src/components/chat-components/SuggestedPrompts.tsx @@ -1,7 +1,10 @@ import { useChainType } from "@/aiParams"; import { ChainType } from "@/chainFactory"; +import { Card, CardTitle, CardContent, CardHeader } from "@/components/ui/card"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { VAULT_VECTOR_STORE_STRATEGY } from "@/constants"; import { useSettingsValue } from "@/settings/model"; +import { PlusCircle, TriangleAlert } from "lucide-react"; import React, { useMemo } from "react"; interface NotePrompt { @@ -89,97 +92,58 @@ export const SuggestedPrompts: React.FC = ({ onClick }) = const prompts = useMemo(() => getRandomPrompt(chainType), [chainType]); const settings = useSettingsValue(); const indexVaultToVectorStore = settings.indexVaultToVectorStore as VAULT_VECTOR_STORE_STRATEGY; - const showSuggestedPrompts = settings.showSuggestedPrompts; - - if (!showSuggestedPrompts) { - return null; - } return ( -
-
-
-

- Suggested Prompts -

-
+
+ + + Suggested Prompts + + +
{prompts.map((prompt, i) => ( - +
+
{prompt.title}
+
{prompt.text}
+
+ + + + + Add to Chat + +
))}
- {chainType === ChainType.VAULT_QA_CHAIN && ( -

- Please note that this is a retrieval-based QA. Questions should contain keywords and - concepts that exist literally in your vault -

- )} - {chainType === ChainType.VAULT_QA_CHAIN && - indexVaultToVectorStore === VAULT_VECTOR_STORE_STRATEGY.NEVER && ( -

- ⚠️ Your auto-index strategy is set to NEVER. Before proceeding, click the{" "} - Refresh Index button below or run - the{" "} - - Copilot command: Index (refresh) vault for QA - {" "} - to update the index. -

- )} + + + {chainType === ChainType.VAULT_QA_CHAIN && ( +
+ Please note that this is a retrieval-based QA. Questions should contain keywords and + concepts that exist literally in your vault
-
+ )} + {chainType === ChainType.VAULT_QA_CHAIN && + indexVaultToVectorStore === VAULT_VECTOR_STORE_STRATEGY.NEVER && ( +
+
+ Your auto-index strategy is set to NEVER. + Before proceeding, click the Refresh Index button + below or run the{" "} + Copilot command: Index (refresh) vault for QA to + update the index. +
+
+ )}
); }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 00000000..6155d568 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-md px-2.5 py-0.5 text-normal text-ui-smaller transition-colors focus:outline-none", + { + variants: { + variant: { + default: "bg-primary-alt shadow", + secondary: "bg-secondary-alt", + destructive: "bg-modifier-error shadow", + outline: "border border-border border-solid", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +const Badge = React.forwardRef( + ({ className, variant, ...props }, ref) => ( +
+ ) +); + +export { Badge, badgeVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 00000000..1999a4ce --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardFooter.displayName = "CardFooter"; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 00000000..5c28cbcc --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000..470ef84f --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 00000000..dd94b410 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/src/constants.ts b/src/constants.ts index 51d2a868..a22a6dde 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -278,6 +278,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = { embeddingRequestsPerSecond: 10, disableIndexOnMobile: true, showSuggestedPrompts: true, + showRelevantNotes: true, numPartitions: 1, enabledCommands: { [COMMAND_IDS.FIX_GRAMMAR]: { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a5ef1935..14a8c1db 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,22 @@ import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { extendTailwindMerge } from "tailwind-merge"; +const customTwMerge = extendTailwindMerge({ + extend: { + classGroups: { + "text-color": ["text-muted", "text-success", "text-warning", "text-error", "text-accent"], + "font-size": [ + "text-smallest", + "text-smaller", + "text-small", + "text-ui-smaller", + "text-ui-small", + "text-ui-medium", + "text-ui-larger", + ], + }, + }, +}); export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return customTwMerge(clsx(inputs)); } diff --git a/src/search/dbOperations.ts b/src/search/dbOperations.ts index a9ddae1f..73a6b26c 100644 --- a/src/search/dbOperations.ts +++ b/src/search/dbOperations.ts @@ -550,4 +550,12 @@ export class DBOperations { throw new CustomError("Failed to check if database is empty."); } } + + public async hasIndex(notePath: string): Promise { + if (!this.oramaDb) { + return false; + } + const docs = await DBOperations.getDocsByPath(this.oramaDb, notePath); + return docs !== undefined && docs.length > 0; + } } diff --git a/src/search/findRelevantNotes.ts b/src/search/findRelevantNotes.ts index 4ceb4152..b00986bf 100644 --- a/src/search/findRelevantNotes.ts +++ b/src/search/findRelevantNotes.ts @@ -4,7 +4,7 @@ import { getSettings } from "@/settings/model"; import { InternalTypedDocument, Orama, Result } from "@orama/orama"; import { TFile } from "obsidian"; -const MIN_SIMILARITY_SCORE = 0.3; +const MIN_SIMILARITY_SCORE = 0.4; const MAX_K = 20; const ORIGINAL_WEIGHT = 0.7; const LINKS_WEIGHT = 0.3; @@ -186,7 +186,18 @@ export async function findRelevantNotes({ const similarityScoreMap = await calculateSimilarityScore({ db, filePath }); const noteLinks = getNoteLinks(file); const mergedScoreMap = mergeScoreMaps(similarityScoreMap, noteLinks); - const sortedHits = Array.from(mergedScoreMap.entries()).sort((a, b) => b[1] - a[1]); + const sortedHits = Array.from(mergedScoreMap.entries()).sort((a, b) => { + const aPath = a[0]; + const bPath = b[0]; + const aCategory = getSimilarityCategory(similarityScoreMap.get(aPath) ?? 0); + const bCategory = getSimilarityCategory(similarityScoreMap.get(bPath) ?? 0); + + if (aCategory !== bCategory) { + return bCategory - aCategory; + } + + return b[1] - a[1]; + }); return sortedHits .map(([path, score]) => { const file = app.vault.getAbstractFileByPath(path); @@ -208,3 +219,14 @@ export async function findRelevantNotes({ }) .filter((entry) => entry !== null); } + +/** + * Gets the similarity category for the given score. + * @param score - The score to get the similarity category for. + * @returns The similarity category. 1 is low, 2 is medium, 3 is high. + */ +export function getSimilarityCategory(score: number): number { + if (score > 0.7) return 3; + if (score > 0.55) return 2; + return 1; +} diff --git a/src/search/vectorStoreManager.ts b/src/search/vectorStoreManager.ts index cedac767..d7f11c78 100644 --- a/src/search/vectorStoreManager.ts +++ b/src/search/vectorStoreManager.ts @@ -2,7 +2,7 @@ import { CustomError } from "@/error"; import EmbeddingsManager from "@/LLMProviders/embeddingManager"; import { CopilotSettings, getSettings, subscribeToSettingsChange } from "@/settings/model"; import { Orama } from "@orama/orama"; -import { Notice, Platform } from "obsidian"; +import { Notice, Platform, TFile } from "obsidian"; import { DBOperations } from "./dbOperations"; import { IndexEventHandler } from "./indexEventHandler"; import { IndexOperations } from "./indexOperations"; @@ -94,6 +94,10 @@ export default class VectorStoreManager { } } + private async waitForInitialization(): Promise { + await this.initializationPromise; + } + public async indexVaultToVectorStore(overwrite?: boolean): Promise { await this.waitForInitialization(); if (Platform.isMobile && getSettings().disableIndexOnMobile) { @@ -123,8 +127,9 @@ export default class VectorStoreManager { return this.dbOps.isIndexEmpty(); } - public async waitForInitialization(): Promise { - await this.initializationPromise; + public async hasIndex(notePath: string): Promise { + await this.waitForInitialization(); + return this.dbOps.hasIndex(notePath); } public onunload(): void { @@ -145,4 +150,9 @@ export default class VectorStoreManager { } return db; } + + public async reindexFile(file: TFile): Promise { + await this.waitForInitialization(); + await this.indexOps.reindexFile(file); + } } diff --git a/src/settings/components/GeneralSettings.tsx b/src/settings/components/GeneralSettings.tsx index 6d3055de..02f77301 100644 --- a/src/settings/components/GeneralSettings.tsx +++ b/src/settings/components/GeneralSettings.tsx @@ -105,6 +105,13 @@ const GeneralSettings: React.FC = () => { value={settings.showSuggestedPrompts} onChange={(value) => updateSetting("showSuggestedPrompts", value)} /> + updateSetting("showRelevantNotes", value)} + /> +

Open Plugin In

diff --git a/src/settings/model.ts b/src/settings/model.ts index 9681f190..eeccd351 100644 --- a/src/settings/model.ts +++ b/src/settings/model.ts @@ -58,6 +58,7 @@ export interface CopilotSettings { defaultOpenArea: DEFAULT_OPEN_AREA; disableIndexOnMobile: boolean; showSuggestedPrompts: boolean; + showRelevantNotes: boolean; numPartitions: number; } diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index 8331d5a1..474d0738 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -11,6 +11,11 @@ If your plugin does not need CSS, delete this file. */ +/* Popover needs to render on top of the workspace-leaf-resize-handle */ +[data-radix-popper-content-wrapper] { + z-index: 9999 !important; +} + .button-container { display: flex; justify-content: space-between; @@ -457,24 +462,6 @@ If your plugin does not need CSS, delete this file. height: 12px; } -.chat-messages { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - flex: 1; - overflow-y: auto; - padding: 8px; - width: 100%; - word-wrap: break-word; - overflow-wrap: break-word; - font-size: calc(var(--font-text-size) - 2px); - box-sizing: border-box; - scroll-behavior: smooth; - margin-top: auto; - user-select: text; -} - .chat-message-container { display: flex; flex-direction: column; diff --git a/tailwind.config.js b/tailwind.config.js index 8db2c1f4..504610d5 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,18 +3,22 @@ import tailwindcssAnimate from "tailwindcss-animate"; import { colorOpacityPlugin } from "./src/lib/plugins/colorOpacityPlugin"; import colors from "tailwindcss/colors"; +import containerQueries from "@tailwindcss/container-queries"; /** @type {import("tailwindcss").Config} */ module.exports = { content: ["./src/**/*.{js,ts,jsx,tsx}", "./src/styles/tailwind.css"], darkMode: ["class"], - plugins: [tailwindcssAnimate, colorOpacityPlugin], + plugins: [tailwindcssAnimate, colorOpacityPlugin, containerQueries], corePlugins: { preflight: false, }, // https://github.com/tailwindlabs/tailwindcss/blob/main/stubs/config.full.js theme: { textColor: { + inherit: colors.inherit, + current: colors.current, + transparent: colors.transparent, normal: "var(--text-normal)", muted: "var(--text-muted)", faint: "var(--text-faint)", @@ -27,10 +31,12 @@ module.exports = { "accent-hover": "var(--text-accent-hover)", selection: "var(--text-selection)", "highlight-bg": "var(--text-highlight-bg)", - bold: "var(--bold-color)", - italic: "var(--italic-color)", }, backgroundColor: { + inherit: colors.inherit, + current: colors.current, + transparent: colors.transparent, + background: "var(--background-primary)", primary: "var(--background-primary)", "primary-alt": "var(--background-primary-alt)", secondary: "var(--background-secondary)", @@ -50,8 +56,18 @@ module.exports = { DEFAULT: "var(--dropdown-background)", }, }, + interactive: { + normal: "var(--interactive-normal)", + hover: "var(--interactive-hover)", + accent: "var(--interactive-accent)", + "accent-hsl": "hsl(var(--interactive-accent-hsl),)", + "accent-hover": "var(--interactive-accent-hover)", + }, }, borderColor: { + inherit: colors.inherit, + current: colors.current, + transparent: colors.transparent, border: "var(--background-modifier-border)", "border-hover": "var(--background-modifier-border-hover)", "border-focus": "var(--background-modifier-border-focus)", @@ -97,13 +113,7 @@ module.exports = { 0: "rgba(var(--mono-rgb-0),)", 100: "rgba(var(--mono-rgb-100),)", }, - interactive: { - normal: "var(--interactive-normal)", - hover: "var(--interactive-hover)", - accent: "var(--interactive-accent)", - "accent-hsl": "hsl(var(--interactive-accent-hsl),)", - "accent-hover": "var(--interactive-accent-hover)", - }, + caret: "var(--caret-color)", icon: { DEFAULT: "var(--icon-color)",