diff --git a/apps/webapp/app/components/common/styled-markdown.tsx b/apps/webapp/app/components/common/styled-markdown.tsx
index b398b26..3b33881 100644
--- a/apps/webapp/app/components/common/styled-markdown.tsx
+++ b/apps/webapp/app/components/common/styled-markdown.tsx
@@ -1,5 +1,4 @@
-import ReactMarkdown from "react-markdown";
-import type { Components } from "react-markdown";
+import ReactMarkdown, {type Components } from "react-markdown";
import { cn } from "~/lib/utils";
const markdownComponents: Components = {
diff --git a/apps/webapp/app/components/spaces/space-episode-actions.tsx b/apps/webapp/app/components/spaces/space-episode-actions.tsx
new file mode 100644
index 0000000..f27c8fd
--- /dev/null
+++ b/apps/webapp/app/components/spaces/space-episode-actions.tsx
@@ -0,0 +1,112 @@
+import { EllipsisVertical, Trash } from "lucide-react";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "../ui/dropdown-menu";
+import { Button } from "../ui/button";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "../ui/alert-dialog";
+import { useEffect, useState } from "react";
+import { useFetcher, useNavigate } from "@remix-run/react";
+import { toast } from "~/hooks/use-toast";
+
+interface SpaceEpisodeActionsProps {
+ episodeId: string;
+ spaceId: string;
+}
+
+export const SpaceEpisodeActions = ({
+ episodeId,
+ spaceId,
+}: SpaceEpisodeActionsProps) => {
+ const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
+ const removeFetcher = useFetcher();
+ const navigate = useNavigate();
+
+ const handleRemove = () => {
+ removeFetcher.submit(
+ {
+ episodeIds: JSON.stringify([episodeId]),
+ spaceId,
+ action: "remove",
+ },
+ {
+ method: "post",
+ action: "/api/v1/episodes/assign-space",
+ encType: "application/json",
+ },
+ );
+ setRemoveDialogOpen(false);
+ };
+
+ useEffect(() => {
+ if (removeFetcher.state === "idle" && removeFetcher.data) {
+ if (removeFetcher.data.success) {
+ toast({
+ title: "Success",
+ description: "Episode removed from space",
+ });
+ // Reload the page to refresh the episode list
+ navigate(".", { replace: true });
+ } else {
+ toast({
+ title: "Error",
+ description: removeFetcher.data.error || "Failed to remove episode",
+ variant: "destructive",
+ });
+ }
+ }
+ }, [removeFetcher.state, removeFetcher.data, navigate]);
+
+ return (
+ <>
+
+
+
+
+
+ e.stopPropagation()}>
+ setRemoveDialogOpen(true)}>
+
+
+
+
+
+
+ e.stopPropagation()}>
+
+ Remove from space
+
+ Are you sure you want to remove this episode from the space? This
+ will not delete the episode itself.
+
+
+
+ Cancel
+
+ Remove
+
+
+
+
+ >
+ );
+};
diff --git a/apps/webapp/app/components/spaces/space-episode-card.tsx b/apps/webapp/app/components/spaces/space-episode-card.tsx
index 4c6e704..2272ceb 100644
--- a/apps/webapp/app/components/spaces/space-episode-card.tsx
+++ b/apps/webapp/app/components/spaces/space-episode-card.tsx
@@ -5,6 +5,7 @@ import { cn } from "~/lib/utils";
import { useNavigate } from "@remix-run/react";
import Markdown from "react-markdown";
import { StyledMarkdown } from "../common/styled-markdown";
+import { SpaceEpisodeActions } from "./space-episode-actions";
export interface Episode {
uuid: string;
@@ -20,9 +21,10 @@ export interface Episode {
interface SpaceFactCardProps {
episode: Episode;
+ spaceId: string;
}
-export function SpaceEpisodeCard({ episode }: SpaceFactCardProps) {
+export function SpaceEpisodeCard({ episode, spaceId }: SpaceFactCardProps) {
const navigate = useNavigate();
const formatDate = (date: Date | string) => {
const d = new Date(date);
@@ -62,6 +64,7 @@ export function SpaceEpisodeCard({ episode }: SpaceFactCardProps) {
{formatDate(episode.validAt)}
+
diff --git a/apps/webapp/app/components/spaces/space-episodes-list.tsx b/apps/webapp/app/components/spaces/space-episodes-list.tsx
index c26265f..4e18a3e 100644
--- a/apps/webapp/app/components/spaces/space-episodes-list.tsx
+++ b/apps/webapp/app/components/spaces/space-episodes-list.tsx
@@ -18,12 +18,14 @@ interface SpaceEpisodesListProps {
loadMore: () => void;
isLoading: boolean;
height?: number;
+ spaceId: string;
}
function EpisodeItemRenderer(
props: ListRowProps,
episodes: Episode[],
cache: CellMeasurerCache,
+ spaceId: string,
) {
const { index, key, style, parent } = props;
const episode = episodes[index];
@@ -37,7 +39,7 @@ function EpisodeItemRenderer(
rowIndex={index}
>
-
+
);
@@ -48,6 +50,7 @@ export function SpaceEpisodesList({
hasMore,
loadMore,
isLoading,
+ spaceId,
}: SpaceEpisodesListProps) {
// Create a CellMeasurerCache instance using useRef to prevent recreation
const cacheRef = useRef(null);
@@ -91,7 +94,7 @@ export function SpaceEpisodesList({
};
const rowRenderer = (props: ListRowProps) => {
- return EpisodeItemRenderer(props, episodes, cache);
+ return EpisodeItemRenderer(props, episodes, cache, spaceId);
};
const rowHeight = ({ index }: Index) => {
diff --git a/apps/webapp/app/routes/home.space.$spaceId.episodes.tsx b/apps/webapp/app/routes/home.space.$spaceId.episodes.tsx
index d5a8894..6017db1 100644
--- a/apps/webapp/app/routes/home.space.$spaceId.episodes.tsx
+++ b/apps/webapp/app/routes/home.space.$spaceId.episodes.tsx
@@ -37,7 +37,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
export default function Episodes() {
- const { episodes } = useLoaderData();
+ const { episodes, space } = useLoaderData();
const [selectedValidDate, setSelectedValidDate] = useState<
string | undefined
>();
@@ -98,6 +98,7 @@ export default function Episodes() {
hasMore={false} // TODO: Implement real pagination
loadMore={loadMore}
isLoading={false}
+ spaceId={space.id}
/>
)}
diff --git a/apps/webapp/app/services/space.server.ts b/apps/webapp/app/services/space.server.ts
index b993174..10afc43 100644
--- a/apps/webapp/app/services/space.server.ts
+++ b/apps/webapp/app/services/space.server.ts
@@ -236,10 +236,6 @@ export class SpaceService {
throw new Error("Space not found");
}
- if (space.name === "Profile") {
- throw new Error("Cannot reset Profile space");
- }
-
// Delete all relationships in Neo4j (episodes, statements, etc.)
await deleteSpace(spaceId, userId);
diff --git a/apps/webapp/app/trigger/spaces/space-summary.ts b/apps/webapp/app/trigger/spaces/space-summary.ts
index 64d3c1c..3f95831 100644
--- a/apps/webapp/app/trigger/spaces/space-summary.ts
+++ b/apps/webapp/app/trigger/spaces/space-summary.ts
@@ -198,7 +198,7 @@ async function generateSpaceSummary(
if (
episodeDifference < CONFIG.summaryEpisodeThreshold ||
- lastSummaryEpisodeCount === 0
+ lastSummaryEpisodeCount !== 0
) {
logger.info(
`Skipping summary generation for space ${spaceId}: only ${episodeDifference} new episodes (threshold: ${CONFIG.summaryEpisodeThreshold})`,