diff --git a/package-lock.json b/package-lock.json index e1710175e..82ffc3998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,10 @@ "@ai-sdk/google": "^0.0.15", "@ai-sdk/mistral": "^0.0.12", "@ai-sdk/openai": "^0.0.13", - "@captn/joy": "^0.38.0", - "@captn/react": "^0.38.0", - "@captn/theme": "^0.38.0", - "@captn/utils": "^0.38.0", + "@captn/joy": "^0.39.0", + "@captn/react": "^0.39.0", + "@captn/theme": "^0.39.0", + "@captn/utils": "^0.39.0", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@dnd-kit/core": "^6.1.0", @@ -2461,14 +2461,14 @@ "license": "MIT" }, "node_modules/@captn/joy": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@captn/joy/-/joy-0.38.0.tgz", - "integrity": "sha512-154pw6dGHWO05qdIVxCLGcip9NNhW4GnJE9tW2hC1IqhsvZBxQHiP5ztHItxAeocze8qGlq+OJ+XY+jVOWpGxQ==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@captn/joy/-/joy-0.39.0.tgz", + "integrity": "sha512-x5aLJ+03GqpcoBYc4A/2AftoXvnT1omKVn/+yWMo1xjLlF7USP8Jd1oJfOk5uDfERLAxyimTLVRgcXrk9xPidQ==", "dev": true, "dependencies": { - "@captn/react": "^0.38.0", - "@captn/theme": "^0.38.0", - "@captn/utils": "^0.38.0", + "@captn/react": "^0.39.0", + "@captn/theme": "^0.39.0", + "@captn/utils": "^0.39.0", "@mui/base": "5.0.0-beta.37", "@mui/icons-material": "5.15.11", "@mui/material": "5.15.11", @@ -2583,12 +2583,12 @@ } }, "node_modules/@captn/react": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@captn/react/-/react-0.38.0.tgz", - "integrity": "sha512-sy5NQZkWZMeE78E48pUiOzMzkefO5WLMV9VjKKWUUGfjMhPmqojff4/V2KVWc2JlFhQp8vm7IV6cPAtl19JA6A==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@captn/react/-/react-0.39.0.tgz", + "integrity": "sha512-8qPUNxR5srahLmjUAIxuEP9S2Rnz2s+eFgOX1kUoS5cEDZeTUET+7YUOmjki8/fQjdyC3oCkMTSHVDLFI2jE5w==", "dev": true, "dependencies": { - "@captn/utils": "^0.38.0", + "@captn/utils": "^0.39.0", "dot-prop": "^8.0.2", "lodash.isequal": "^4.5.0", "swr": "^2.2.5", @@ -2601,12 +2601,12 @@ } }, "node_modules/@captn/theme": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@captn/theme/-/theme-0.38.0.tgz", - "integrity": "sha512-cSlgiFxZ+rX9KkKr2O3i+Jy0x8VTsV3UjPJ2CHgUdykoZCqu4WAKtKDDFG+QbgXA1T10L/SJQHI750aKDRc4rA==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@captn/theme/-/theme-0.39.0.tgz", + "integrity": "sha512-n3h8WShMtiDcjM2f+d8ArJX2/lc/hRphTV6VAaQquLJeEIop+oqTmUTg31RYt8uo6WZli4aMEY4FSU3jH0fXBA==", "dev": true, "dependencies": { - "@captn/utils": "^0.38.0" + "@captn/utils": "^0.39.0" }, "peerDependencies": { "@emotion/react": ">=11.11.3", @@ -2616,9 +2616,9 @@ } }, "node_modules/@captn/utils": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/@captn/utils/-/utils-0.38.0.tgz", - "integrity": "sha512-1nRCTQ31lCgp4nW3BDZjn5Y0G1E/S2lmlQVjL2KHjxHBWhjI80I4hfqE7e+eCS/0faWdXGBpKaClYTv1YIntQA==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@captn/utils/-/utils-0.39.0.tgz", + "integrity": "sha512-sfOd5eN9Kmcyi+SPX2FaZO28BiWZsga7X866A2mDosb0I09AtIVAVJTtBSAqueGUb50q0yHlFJwMQf62MmoKcA==", "dev": true, "dependencies": { "@qdrant/js-client-rest": "^1.8.1", diff --git a/package.json b/package.json index d162e3898..58c1859e7 100644 --- a/package.json +++ b/package.json @@ -56,10 +56,10 @@ "@ai-sdk/google": "^0.0.15", "@ai-sdk/mistral": "^0.0.12", "@ai-sdk/openai": "^0.0.13", - "@captn/joy": "^0.38.0", - "@captn/react": "^0.38.0", - "@captn/theme": "^0.38.0", - "@captn/utils": "^0.38.0", + "@captn/joy": "^0.39.0", + "@captn/react": "^0.39.0", + "@captn/theme": "^0.39.0", + "@captn/utils": "^0.39.0", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@dnd-kit/core": "^6.1.0", diff --git a/src/client/pages/[locale]/apps/explorer.tsx b/src/client/pages/[locale]/apps/explorer.tsx index bec27779e..11b1a889d 100644 --- a/src/client/pages/[locale]/apps/explorer.tsx +++ b/src/client/pages/[locale]/apps/explorer.tsx @@ -4,6 +4,7 @@ import { TitleBar } from "@captn/joy/title-bar"; import { useVectorStore } from "@captn/react/use-vector-store"; import { localFile } from "@captn/utils/string"; import type { VectorStoreResponse } from "@captn/utils/types"; +import DeleteForeverIcon from "@mui/icons-material/DeleteForever"; import FolderIcon from "@mui/icons-material/Folder"; import SearchIcon from "@mui/icons-material/Search"; import Box from "@mui/joy/Box"; @@ -22,11 +23,14 @@ import { useTranslation } from "next-i18next"; import { Fragment, useMemo, useState } from "react"; import { FileIcon, defaultStyles } from "react-file-icon"; +import { TooltipButton } from "@/client/apps/shared/components"; import { DynamicIcon } from "@/client/atoms/icons/dynamic"; import { handleCaptainAction } from "@/client/ions/handlers/action"; import { useThirdPartyAppsPath } from "@/client/ions/hooks/third-party-apps-path"; import { makeStaticProperties } from "@/client/ions/i18n/get-static"; import { getContrastColor } from "@/client/ions/utils/color"; +import { buildKey } from "@/shared/build-key"; +import { ID } from "@/shared/enums"; export function PreviewIcon({ item }: { item: VectorStoreResponse }) { switch (item.payload.type) { @@ -103,7 +107,7 @@ export function PreviewIcon({ item }: { item: VectorStoreResponse }) { export default function Page(_properties: InferGetStaticPropsType) { const { t } = useTranslation(["common", "labels"]); const [query, setQuery] = useState(""); - const { data } = useVectorStore(query, { limit: 100, score_threshold: 0.2 }); + const { data, mutate } = useVectorStore(query, { limit: 100, score_threshold: 0.2 }); const uniqueData = useMemo(() => uniqBy(data, "payload.id"), [data]); const groups = useMemo( @@ -182,12 +186,57 @@ export default function Page(_properties: InferGetStaticPropsType ( { + mutate(previousState => + previousState.filter( + entry => + entry.id !== item.id + ) + ); + window.ipc.send( + buildKey([ID.FILE], { + suffix: ":delete-forever", + }), + { + id: item.payload.id, + filePath: + item.payload.filePath, + } + ); + }} + > + + + ) : undefined + } sx={{ "--focus-outline-offset": "-2px", + "&:hover .delete--button-wrapper, &:focus-within .delete--button-wrapper": + { + opacity: 1, + }, }} > { @@ -117,3 +120,19 @@ ipcMain.on( event.sender.send(buildKey([ID.STORY], { suffix: ":all" }), parsedFiles); } ); + +ipcMain.on(buildKey([ID.FILE], { suffix: ":delete-forever" }), async (event, { id, filePath }) => { + try { + logger.log(`deleting ${id}`); + const instance = VectorStore.getInstance; + await instance.delete(VECTOR_STORE_COLLECTION, id); + await fsp.rm(filePath); + event.sender.send(buildKey([ID.FILE], { suffix: ":delete-forever" }), true); + logger.log(`deleted ${id}`); + } catch (error) { + logError(error); + event.sender.send(buildKey([ID.FILE], { suffix: ":delete-error" }), { + error: `Could not delete ${id}`, + }); + } +}); diff --git a/src/electron/helpers/services/vector-store.ts b/src/electron/helpers/services/vector-store.ts index 8b2e11624..bab87189a 100644 --- a/src/electron/helpers/services/vector-store.ts +++ b/src/electron/helpers/services/vector-store.ts @@ -280,6 +280,28 @@ export class VectorStore { return this.client!.scroll(collectionName, options); } + /** + * Deletes all points related to a given entry id in the specified collection. + * + * @param {string} collectionName - The name of the collection to search in. + * @param {string} id - The id in the payload. + * @returns {ReturnType} A promise that resolves with the delete results. + */ + public async delete(collectionName: string, id: string): ReturnType { + return this.client!.delete(collectionName, { + filter: { + must: [ + { + key: "id", + match: { + value: id, + }, + }, + ], + }, + }); + } + /** * Ensures the existence of the specified collection. If the collection does not exist, * it can be automatically created with a default configuration based on the embeddings model.