Skip to content

Commit

Permalink
feat: allow deleting images in the explorer (#321)
Browse files Browse the repository at this point in the history
## Motivation

<!-- List motivation and changes here -->

## Issues closed

<!-- List closed issues here -->
  • Loading branch information
pixelass authored May 27, 2024
1 parent 503ce12 commit cdd558b
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 27 deletions.
42 changes: 21 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 51 additions & 2 deletions src/client/pages/[locale]/apps/explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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) {
Expand Down Expand Up @@ -103,7 +107,7 @@ export function PreviewIcon({ item }: { item: VectorStoreResponse }) {
export default function Page(_properties: InferGetStaticPropsType<typeof getStaticProps>) {
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(
Expand Down Expand Up @@ -182,12 +186,57 @@ export default function Page(_properties: InferGetStaticPropsType<typeof getStat
{group.map(item => (
<ListItem
key={item.id}
slotProps={{
startAction: {
className: "delete--button-wrapper",
sx: {
top: 0,
right: 0,
left: "unset",
transform: "unset",
opacity: 0,
"&:focus-within": {
opacity: 1,
},
},
},
}}
startAction={
item.payload.type === "image" ? (
<TooltipButton
label={t("labels:delete")}
onClick={() => {
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,
}
);
}}
>
<DeleteForeverIcon />
</TooltipButton>
) : undefined
}
sx={{
"--focus-outline-offset": "-2px",
"&:hover .delete--button-wrapper, &:focus-within .delete--button-wrapper":
{
opacity: 1,
},
}}
>
<ListItemButton
// Disabled={item.payload.type !== "app"}
sx={{
flexDirection: "column",
width: 128,
Expand Down
19 changes: 19 additions & 0 deletions src/electron/helpers/ipc/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import path from "node:path";
import { USER_LANGUAGE_KEY } from "@captn/utils/constants";
import { ipcMain } from "electron";

import { logError, logger } from "@/electron/services/logger";
import { VectorStore } from "@/electron/services/vector-store";
import { keyStore, userStore } from "@/electron/stores";
import { sendToAllWindows } from "@/electron/stores/watchers";
import { clone, lfs } from "@/electron/utils/git";
import { getCaptainData } from "@/electron/utils/path-helpers";
import { readFilesRecursively } from "@/electron/utils/read-files-recursively";
import { buildKey } from "@/shared/build-key";
import { VECTOR_STORE_COLLECTION } from "@/shared/constants";
import { ID } from "@/shared/enums";

ipcMain.on(USER_LANGUAGE_KEY, (_event, language) => {
Expand Down Expand Up @@ -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}`,
});
}
});
22 changes: 22 additions & 0 deletions src/electron/helpers/services/vector-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<QdrantClient["delete"]>} A promise that resolves with the delete results.
*/
public async delete(collectionName: string, id: string): ReturnType<QdrantClient["delete"]> {
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.
Expand Down

0 comments on commit cdd558b

Please sign in to comment.