mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2026-01-04 06:04:21 +01:00
feat: add unifySameTitleSession option for unify resume messages
This commit is contained in:
@@ -36,7 +36,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: projectQueryConfig(projectId).queryKey,
|
||||
});
|
||||
}, [config.hideNoUserMessageSession]);
|
||||
}, [config.hideNoUserMessageSession, config.unifySameTitleSession]);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
@@ -78,7 +78,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => {
|
||||
</h2>
|
||||
|
||||
{/* Filter Controls */}
|
||||
<div className="mb-6 p-4 bg-muted/50 rounded-lg">
|
||||
<div className="mb-6 p-4 bg-muted/50 rounded-lg space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={checkboxId}
|
||||
@@ -103,6 +103,32 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => {
|
||||
<p className="text-xs text-muted-foreground mt-1 ml-6">
|
||||
Only show sessions that contain user commands or messages
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`${checkboxId}-unify`}
|
||||
checked={config?.unifySameTitleSession}
|
||||
onCheckedChange={async () => {
|
||||
updateConfig({
|
||||
...config,
|
||||
unifySameTitleSession: !config?.unifySameTitleSession,
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: configQueryConfig.queryKey,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${checkboxId}-unify`}
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Unify sessions with same title
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 ml-6">
|
||||
Show only the latest session when multiple sessions have the same
|
||||
title
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{sessions.length === 0 ? (
|
||||
|
||||
@@ -62,7 +62,10 @@ export const ConversationItem: FC<{
|
||||
if (conversation.type === "user") {
|
||||
const userConversationJsx =
|
||||
typeof conversation.message.content === "string" ? (
|
||||
<UserConversationContent content={conversation.message.content} />
|
||||
<UserConversationContent
|
||||
content={conversation.message.content}
|
||||
id={`message-${conversation.uuid}`}
|
||||
/>
|
||||
) : (
|
||||
<ul className="w-full" id={`message-${conversation.uuid}`}>
|
||||
{conversation.message.content.map((content) => (
|
||||
|
||||
@@ -14,19 +14,23 @@ import { UserTextContent } from "./UserTextContent";
|
||||
|
||||
export const UserConversationContent: FC<{
|
||||
content: UserMessageContent;
|
||||
}> = ({ content }) => {
|
||||
id?: string;
|
||||
}> = ({ content, id }) => {
|
||||
if (typeof content === "string") {
|
||||
return <UserTextContent text={content} />;
|
||||
return <UserTextContent text={content} id={id} />;
|
||||
}
|
||||
|
||||
if (content.type === "text") {
|
||||
return <UserTextContent text={content.text} />;
|
||||
return <UserTextContent text={content.text} id={id} />;
|
||||
}
|
||||
|
||||
if (content.type === "image") {
|
||||
if (content.source.type === "base64") {
|
||||
return (
|
||||
<Card className="border-purple-200 bg-purple-50/50 dark:border-purple-800 dark:bg-purple-950/20">
|
||||
<Card
|
||||
className="border-purple-200 bg-purple-50/50 dark:border-purple-800 dark:bg-purple-950/20"
|
||||
id={id}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<ImageIcon className="h-4 w-4 text-purple-600 dark:text-purple-400" />
|
||||
@@ -56,7 +60,10 @@ export const UserConversationContent: FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="border-red-200 bg-red-50/50 dark:border-red-800 dark:bg-red-950/20">
|
||||
<Card
|
||||
className="border-red-200 bg-red-50/50 dark:border-red-800 dark:bg-red-950/20"
|
||||
id={id}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-red-600 dark:text-red-400" />
|
||||
|
||||
@@ -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 (
|
||||
<Card className="border-green-200 bg-green-50/50 dark:border-green-800 dark:bg-green-950/20 gap-2 py-3 mb-3">
|
||||
<Card
|
||||
className="border-green-200 bg-green-50/50 dark:border-green-800 dark:bg-green-950/20 gap-2 py-3 mb-3"
|
||||
id={id}
|
||||
>
|
||||
<CardHeader className="py-0 px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Terminal className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
|
||||
@@ -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<typeof configSchema>;
|
||||
|
||||
@@ -20,6 +20,7 @@ export const configMiddleware = createMiddleware<HonoContext>(
|
||||
"ccv-config",
|
||||
JSON.stringify({
|
||||
hideNoUserMessageSession: true,
|
||||
unifySameTitleSession: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user