diff --git a/src/app/projects/[projectId]/components/ProjectPage.tsx b/src/app/projects/[projectId]/components/ProjectPage.tsx index 3303be0..d0d1bbe 100644 --- a/src/app/projects/[projectId]/components/ProjectPage.tsx +++ b/src/app/projects/[projectId]/components/ProjectPage.tsx @@ -36,7 +36,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => { void queryClient.invalidateQueries({ queryKey: projectQueryConfig(projectId).queryKey, }); - }, [config.hideNoUserMessageSession]); + }, [config.hideNoUserMessageSession, config.unifySameTitleSession]); return (
@@ -78,7 +78,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => { {/* Filter Controls */} -
+
{

Only show sessions that contain user commands or messages

+ +
+ { + updateConfig({ + ...config, + unifySameTitleSession: !config?.unifySameTitleSession, + }); + await queryClient.invalidateQueries({ + queryKey: configQueryConfig.queryKey, + }); + }} + /> + +
+

+ Show only the latest session when multiple sessions have the same + title +

{sessions.length === 0 ? ( diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx index 97114d7..7f7033b 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx @@ -62,7 +62,10 @@ export const ConversationItem: FC<{ if (conversation.type === "user") { const userConversationJsx = typeof conversation.message.content === "string" ? ( - + ) : (
    {conversation.message.content.map((content) => ( diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserConversationContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserConversationContent.tsx index eed3bed..8d9d7c0 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserConversationContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserConversationContent.tsx @@ -14,19 +14,23 @@ import { UserTextContent } from "./UserTextContent"; export const UserConversationContent: FC<{ content: UserMessageContent; -}> = ({ content }) => { + id?: string; +}> = ({ content, id }) => { if (typeof content === "string") { - return ; + return ; } if (content.type === "text") { - return ; + return ; } if (content.type === "image") { if (content.source.type === "base64") { return ( - +
    @@ -56,7 +60,10 @@ export const UserConversationContent: FC<{ } return ( - +
    diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserTextContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserTextContent.tsx index 8e81aa1..1396322 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserTextContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserTextContent.tsx @@ -6,12 +6,18 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { parseCommandXml } from "../../../../../../../server/service/parseCommandXml"; import { MarkdownContent } from "../../../../../../components/MarkdownContent"; -export const UserTextContent: FC<{ text: string }> = ({ text }) => { +export const UserTextContent: FC<{ text: string; id?: string }> = ({ + text, + id, +}) => { const parsed = parseCommandXml(text); if (parsed.kind === "command") { return ( - +
    diff --git a/src/server/config/config.ts b/src/server/config/config.ts index 1c74bc2..26563ed 100644 --- a/src/server/config/config.ts +++ b/src/server/config/config.ts @@ -2,6 +2,7 @@ import z from "zod"; export const configSchema = z.object({ hideNoUserMessageSession: z.boolean().optional().default(true), + unifySameTitleSession: z.boolean().optional().default(true), }); export type Config = z.infer; diff --git a/src/server/hono/middleware/config.middleware.ts b/src/server/hono/middleware/config.middleware.ts index 067984a..ed099db 100644 --- a/src/server/hono/middleware/config.middleware.ts +++ b/src/server/hono/middleware/config.middleware.ts @@ -20,6 +20,7 @@ export const configMiddleware = createMiddleware( "ccv-config", JSON.stringify({ hideNoUserMessageSession: true, + unifySameTitleSession: true, }), ); } diff --git a/src/server/hono/route.ts b/src/server/hono/route.ts index 195f5d5..3ae8424 100644 --- a/src/server/hono/route.ts +++ b/src/server/hono/route.ts @@ -52,14 +52,74 @@ export const routes = (app: HonoAppType) => { const [{ project }, { sessions }] = await Promise.all([ getProject(projectId), - getSessions(projectId).then(({ sessions }) => ({ - sessions: sessions.filter((session) => { - if (c.get("config").hideNoUserMessageSession) { + getSessions(projectId).then(({ sessions }) => { + let filteredSessions = sessions; + + // Filter sessions based on hideNoUserMessageSession setting + if (c.get("config").hideNoUserMessageSession) { + filteredSessions = filteredSessions.filter((session) => { return session.meta.firstCommand !== null; + }); + } + + // Unify sessions with same title if unifySameTitleSession is enabled + if (c.get("config").unifySameTitleSession) { + const sessionMap = new Map< + string, + (typeof filteredSessions)[0] + >(); + + for (const session of filteredSessions) { + // Generate title for comparison + const title = + session.meta.firstCommand !== null + ? (() => { + const cmd = session.meta.firstCommand; + switch (cmd.kind) { + case "command": + return cmd.commandArgs === undefined + ? cmd.commandName + : `${cmd.commandName} ${cmd.commandArgs}`; + case "local-command": + return cmd.stdout; + case "text": + return cmd.content; + default: + return session.id; + } + })() + : session.id; + + const existingSession = sessionMap.get(title); + if (existingSession) { + // Keep the session with the latest modification date + if ( + session.meta.lastModifiedAt && + existingSession.meta.lastModifiedAt + ) { + if ( + new Date(session.meta.lastModifiedAt) > + new Date(existingSession.meta.lastModifiedAt) + ) { + sessionMap.set(title, session); + } + } else if ( + session.meta.lastModifiedAt && + !existingSession.meta.lastModifiedAt + ) { + sessionMap.set(title, session); + } + // If no modification dates, keep the existing one + } else { + sessionMap.set(title, session); + } } - return true; - }), - })), + + filteredSessions = Array.from(sessionMap.values()); + } + + return { sessions: filteredSessions }; + }), ] as const); return c.json({ project, sessions });