From bfa19a6e85b9a6afa03f91c3fdbc82057b2fad2a Mon Sep 17 00:00:00 2001 From: d-kimsuon Date: Sat, 30 Aug 2025 03:18:42 +0900 Subject: [PATCH] chore: format files --- package.json | 2 +- src/app/components/MarkdownContent.tsx | 4 +- .../[projectId]/components/ProjectPage.tsx | 43 ++----- src/app/projects/[projectId]/loading.tsx | 40 ------- .../services/firstCommandToTitle.ts | 17 +++ .../[projectId]/services/parseCommandXml.ts | 12 +- .../components/SessionPageContent.tsx | 54 +++++++++ .../AssistantConversationContent.tsx | 21 ++-- .../conversationList/ConversationItem.tsx | 6 +- .../conversationList/ConversationList.tsx | 25 ++-- .../MetaConversationContent.tsx | 2 +- .../SummaryConversationContent.tsx | 2 +- .../SystemConversationContent.tsx | 2 +- .../conversationList/TextContent.tsx | 13 +- .../UserConversationContent.tsx | 9 +- .../{useConversations.ts => useSession.ts} | 9 +- ...nversationsQuery.ts => useSessionQuery.ts} | 2 +- .../sessions/[sessionId]/loading.tsx | 34 ------ .../[projectId]/sessions/[sessionId]/page.tsx | 33 +----- src/app/projects/components/ProjectList.tsx | 16 +-- src/components/ui/alert.tsx | 22 ++-- src/components/ui/avatar.tsx | 20 ++-- src/components/ui/badge.tsx | 18 +-- src/components/ui/button.tsx | 20 ++-- src/components/ui/card.tsx | 26 ++-- src/components/ui/collapsible.tsx | 12 +- src/components/ui/hover-card.tsx | 18 +-- src/components/ui/skeleton.tsx | 6 +- .../content/ImageContentSchema.ts | 20 ++-- .../content/TextContentSchema.ts | 12 +- .../content/ThinkingContentSchema.ts | 14 ++- .../content/ToolResultContentSchema.ts | 29 ++--- .../content/ToolUseContentSchema.ts | 16 +-- .../entry/AssistantEntrySchema.ts | 10 +- .../entry/BaseEntrySchema.ts | 38 +++--- .../entry/SummaryEntrySchema.ts | 14 ++- .../entry/SystemEntrySchema.ts | 10 +- .../entry/UserEntrySchema.ts | 10 +- src/lib/conversation-schema/index.ts | 14 +-- .../message/AssistantMessageSchema.ts | 68 ++++++----- .../message/UserMessageSchema.ts | 32 +++-- .../tool/CommonToolSchema.ts | 112 ++++++++++-------- .../tool/StructuredPatchSchema.ts | 18 +-- .../conversation-schema/tool/TodoSchema.ts | 24 ++-- src/lib/conversation-schema/tool/index.ts | 2 +- src/server/hono/route.ts | 6 +- src/server/service/parseCommandXml.ts | 16 +-- src/server/service/project/getProject.ts | 4 +- src/server/service/project/getProjectMeta.ts | 42 ++++--- src/server/service/project/getProjects.ts | 9 +- src/server/service/session/getSession.ts | 4 +- src/server/service/session/getSessionMeta.ts | 58 +++++---- src/server/service/session/getSessions.ts | 7 +- src/server/service/types.ts | 3 +- 54 files changed, 539 insertions(+), 541 deletions(-) delete mode 100644 src/app/projects/[projectId]/loading.tsx create mode 100644 src/app/projects/[projectId]/services/firstCommandToTitle.ts create mode 100644 src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx rename src/app/projects/[projectId]/sessions/[sessionId]/hooks/{useConversations.ts => useSession.ts} (79%) rename src/app/projects/[projectId]/sessions/[sessionId]/hooks/{useConversationsQuery.ts => useSessionQuery.ts} (85%) delete mode 100644 src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx diff --git a/package.json b/package.json index e3801a0..7a2d8dd 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint:biome-lint": "biome check .", "fix": "run-s 'fix:*'", "fix:biome-format": "biome format --write .", - "fix:biome-lint": "biome check --write .", + "fix:biome-lint": "biome check --write --unsafe .", "typecheck": "tsc --noEmit", "test": "vitest --run", "test:watch": "vitest" diff --git a/src/app/components/MarkdownContent.tsx b/src/app/components/MarkdownContent.tsx index 4b81705..f1c51c6 100644 --- a/src/app/components/MarkdownContent.tsx +++ b/src/app/components/MarkdownContent.tsx @@ -1,10 +1,10 @@ "use client"; +import type { FC } from "react"; import Markdown from "react-markdown"; -import remarkGfm from "remark-gfm"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; -import type { FC } from "react"; +import remarkGfm from "remark-gfm"; interface MarkdownContentProps { content: string; diff --git a/src/app/projects/[projectId]/components/ProjectPage.tsx b/src/app/projects/[projectId]/components/ProjectPage.tsx index 54d7eb1..94785bf 100644 --- a/src/app/projects/[projectId]/components/ProjectPage.tsx +++ b/src/app/projects/[projectId]/components/ProjectPage.tsx @@ -1,9 +1,8 @@ "use client"; -import { ArrowLeftIcon } from "lucide-react"; +import { ArrowLeftIcon, FolderIcon, MessageSquareIcon } from "lucide-react"; import Link from "next/link"; -import { FolderIcon, MessageSquareIcon } from "lucide-react"; - +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -11,11 +10,9 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; - -import { useProject } from "../hooks/useProject"; import { pagesPath } from "../../../../lib/$path"; -import { parseCommandXml } from "../../../../server/service/parseCommandXml"; +import { useProject } from "../hooks/useProject"; +import { firstCommandToTitle } from "../services/firstCommandToTitle"; export const ProjectPageContent = ({ projectId }: { projectId: string }) => { const { @@ -78,31 +75,9 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => { - {session.meta.firstContent - ? (() => { - const parsed = parseCommandXml( - session.meta.firstContent - ); - if (parsed.kind === "command") { - return ( - - {parsed.commandName} {parsed.commandArgs} - - ); - } - if (parsed.kind === "local-command-1") { - return ( - - {parsed.commandName} {parsed.commandMessage} - - ); - } - if (parsed.kind === "local-command-2") { - return {parsed.stdout}; - } - return {session.meta.firstContent}; - })() - : ""} + {session.meta.firstCommand !== null + ? firstCommandToTitle(session.meta.firstCommand) + : session.id} @@ -117,7 +92,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => { Last modified:{" "} {session.meta.lastModifiedAt ? new Date( - session.meta.lastModifiedAt + session.meta.lastModifiedAt, ).toLocaleDateString() : ""}

@@ -129,7 +104,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => { + +
+ +

+ {session.meta.firstCommand !== null + ? firstCommandToTitle(session.meta.firstCommand) + : sessionId} +

+
+

+ Session ID: {sessionId} +

+ + +
+ +
+ + ); +}; diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/AssistantConversationContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/AssistantConversationContent.tsx index 3b67987..178bd78 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/AssistantConversationContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/AssistantConversationContent.tsx @@ -1,12 +1,6 @@ -import { ToolResultContent } from "@/lib/conversation-schema/content/ToolResultContentSchema"; -import { AssistantMessageContent } from "@/lib/conversation-schema/message/AssistantMessageSchema"; -import { FC } from "react"; - -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; +import { ChevronDown, FileText, Lightbulb, Settings } from "lucide-react"; +import type { FC } from "react"; +import { Badge } from "@/components/ui/badge"; import { Card, CardContent, @@ -14,8 +8,13 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { ChevronDown, Lightbulb, Settings, FileText } from "lucide-react"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import type { ToolResultContent } from "@/lib/conversation-schema/content/ToolResultContentSchema"; +import type { AssistantMessageContent } from "@/lib/conversation-schema/message/AssistantMessageSchema"; import { MarkdownContent } from "../../../../../../components/MarkdownContent"; export const AssistantConversationContent: FC<{ 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 baba2b1..06db794 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationItem.tsx @@ -1,11 +1,11 @@ +import type { FC } from "react"; import type { Conversation } from "@/lib/conversation-schema"; import type { ToolResultContent } from "@/lib/conversation-schema/content/ToolResultContentSchema"; -import type { FC } from "react"; -import { UserConversationContent } from "./UserConversationContent"; import { AssistantConversationContent } from "./AssistantConversationContent"; import { MetaConversationContent } from "./MetaConversationContent"; -import { SystemConversationContent } from "./SystemConversationContent"; import { SummaryConversationContent } from "./SummaryConversationContent"; +import { SystemConversationContent } from "./SystemConversationContent"; +import { UserConversationContent } from "./UserConversationContent"; export const ConversationItem: FC<{ conversation: Conversation; diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx index 82f04b9..f623adc 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/ConversationList.tsx @@ -1,8 +1,8 @@ "use client"; -import type { Conversation } from "@/lib/conversation-schema"; import type { FC } from "react"; -import { useConversations } from "../../hooks/useConversations"; +import type { Conversation } from "@/lib/conversation-schema"; +import type { ToolResultContent } from "@/lib/conversation-schema/content/ToolResultContentSchema"; import { ConversationItem } from "./ConversationItem"; const getConversationKey = (conversation: Conversation) => { @@ -26,19 +26,14 @@ const getConversationKey = (conversation: Conversation) => { }; type ConversationListProps = { - projectId: string; - sessionId: string; + conversations: Conversation[]; + getToolResult: (toolUseId: string) => ToolResultContent | undefined; }; export const ConversationList: FC = ({ - projectId, - sessionId, + conversations, + getToolResult, }) => { - const { conversations, getToolResult } = useConversations( - projectId, - sessionId - ); - return (
    {conversations.flatMap((conversation) => { @@ -60,8 +55,8 @@ export const ConversationList: FC = ({ conversation.type === "user" ? "justify-end" : conversation.type === "assistant" - ? "justify-start" - : "justify-center" + ? "justify-start" + : "justify-center" }`} key={getConversationKey(conversation)} > @@ -70,8 +65,8 @@ export const ConversationList: FC = ({ conversation.type === "user" ? "w-[90%]" : conversation.type === "assistant" - ? "w-[90%]" - : "w-[100%]" + ? "w-[90%]" + : "w-[100%]" }`} > {elm} diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/MetaConversationContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/MetaConversationContent.tsx index 69152b0..e87882a 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/MetaConversationContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/MetaConversationContent.tsx @@ -1,10 +1,10 @@ +import { ChevronDown } from "lucide-react"; import type { FC, PropsWithChildren } from "react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { ChevronDown } from "lucide-react"; export const MetaConversationContent: FC = ({ children, diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SummaryConversationContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SummaryConversationContent.tsx index 6e0ce93..6c00c10 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SummaryConversationContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SummaryConversationContent.tsx @@ -1,10 +1,10 @@ +import { ChevronDown } from "lucide-react"; import type { FC, PropsWithChildren } from "react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { ChevronDown } from "lucide-react"; export const SummaryConversationContent: FC = ({ children, diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SystemConversationContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SystemConversationContent.tsx index 75a79ee..66f637f 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SystemConversationContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/SystemConversationContent.tsx @@ -1,10 +1,10 @@ +import { ChevronDown } from "lucide-react"; import type { FC, PropsWithChildren } from "react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { ChevronDown } from "lucide-react"; export const SystemConversationContent: FC = ({ children, diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/TextContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/TextContent.tsx index b610405..94fc345 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/TextContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/TextContent.tsx @@ -1,9 +1,8 @@ -import type { FC } from "react"; - -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; import { Terminal } from "lucide-react"; +import type { FC } from "react"; import { parseCommandXml } from "@/app/projects/[projectId]/services/parseCommandXml"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { MarkdownContent } from "../../../../../../components/MarkdownContent"; export const TextContent: FC<{ text: string }> = ({ text }) => { @@ -11,7 +10,7 @@ export const TextContent: FC<{ text: string }> = ({ text }) => { if (parsed.kind === "command") { return ( - +
    @@ -46,7 +45,7 @@ export const TextContent: FC<{ text: string }> = ({ text }) => { if (parsed.kind === "local-command-1") { return ( - +
    @@ -62,7 +61,7 @@ export const TextContent: FC<{ text: string }> = ({ text }) => { if (parsed.kind === "local-command-2") { return ( - +
    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 fb6c8c9..5774235 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserConversationContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/conversationList/UserConversationContent.tsx @@ -1,5 +1,7 @@ -import type { UserMessageContent } from "@/lib/conversation-schema/message/UserMessageSchema"; +import { AlertCircle, Image as ImageIcon } from "lucide-react"; +import Image from "next/image"; import type { FC } from "react"; +import { Badge } from "@/components/ui/badge"; import { Card, CardContent, @@ -7,8 +9,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Image as ImageIcon, AlertCircle } from "lucide-react"; +import type { UserMessageContent } from "@/lib/conversation-schema/message/UserMessageSchema"; import { TextContent } from "./TextContent"; export const UserConversationContent: FC<{ @@ -43,7 +44,7 @@ export const UserConversationContent: FC<{
    - User uploaded content { - const query = useConversationsQuery(projectId, sessionId); +export const useSession = (projectId: string, sessionId: string) => { + const query = useSessionQuery(projectId, sessionId); const toolResultMap = useMemo(() => { const entries = query.data.session.conversations.flatMap((conversation) => { @@ -34,10 +34,11 @@ export const useConversations = (projectId: string, sessionId: string) => { (toolUseId: string) => { return toolResultMap.get(toolUseId); }, - [toolResultMap] + [toolResultMap], ); return { + session: query.data.session, conversations: query.data.session.conversations, getToolResult, }; diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useConversationsQuery.ts b/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSessionQuery.ts similarity index 85% rename from src/app/projects/[projectId]/sessions/[sessionId]/hooks/useConversationsQuery.ts rename to src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSessionQuery.ts index 2be0dd5..4929c4b 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useConversationsQuery.ts +++ b/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSessionQuery.ts @@ -1,7 +1,7 @@ import { useSuspenseQuery } from "@tanstack/react-query"; import { honoClient } from "../../../../../../lib/api/client"; -export const useConversationsQuery = (projectId: string, sessionId: string) => { +export const useSessionQuery = (projectId: string, sessionId: string) => { return useSuspenseQuery({ queryKey: ["conversations", sessionId], queryFn: async () => { diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx deleted file mode 100644 index 9de175b..0000000 --- a/src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Card, CardContent, CardHeader } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; - -export default function SessionLoading() { - return ( -
    -
    - -
    - - -
    - -
    - -
    - - - - - - - - - -
    - -
    -
    -
    -
    -
    - ); -} \ No newline at end of file diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx index 23385e5..bebdb95 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx @@ -1,8 +1,5 @@ -import Link from "next/link"; -import { ArrowLeftIcon, MessageSquareIcon } from "lucide-react"; -import { Button } from "@/components/ui/button"; import type { Metadata } from "next"; -import { ConversationList } from "./components/conversationList/ConversationList"; +import { SessionPageContent } from "./components/SessionPageContent"; type PageParams = { projectId: string; @@ -28,31 +25,5 @@ interface SessionPageProps { export default async function SessionPage({ params }: SessionPageProps) { const { projectId, sessionId } = await params; - return ( -
    -
    - - -
    - -

    Conversation Session

    -
    -

    - Session ID: {sessionId} -

    -
    - -
    - -
    -
    - ); + return ; } diff --git a/src/app/projects/components/ProjectList.tsx b/src/app/projects/components/ProjectList.tsx index 8047692..f6763d7 100644 --- a/src/app/projects/components/ProjectList.tsx +++ b/src/app/projects/components/ProjectList.tsx @@ -1,16 +1,16 @@ "use client"; -import type { FC } from "react"; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, -} from "@/components/ui/card"; import { FolderIcon } from "lucide-react"; import Link from "next/link"; +import type { FC } from "react"; import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { useProjects } from "../hooks/useProjects"; export const ProjectList: FC = () => { diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx index 1421354..ef36696 100644 --- a/src/components/ui/alert.tsx +++ b/src/components/ui/alert.tsx @@ -1,7 +1,7 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const alertVariants = cva( "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", @@ -16,8 +16,8 @@ const alertVariants = cva( defaultVariants: { variant: "default", }, - } -) + }, +); function Alert({ className, @@ -31,7 +31,7 @@ function Alert({ className={cn(alertVariants({ variant }), className)} {...props} /> - ) + ); } function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { @@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { data-slot="alert-title" className={cn( "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", - className + className, )} {...props} /> - ) + ); } function AlertDescription({ @@ -56,11 +56,11 @@ function AlertDescription({ data-slot="alert-description" className={cn( "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed", - className + className, )} {...props} /> - ) + ); } -export { Alert, AlertTitle, AlertDescription } +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 71e428b..11c21d9 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -1,9 +1,9 @@ -"use client" +"use client"; -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" +import * as AvatarPrimitive from "@radix-ui/react-avatar"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Avatar({ className, @@ -14,11 +14,11 @@ function Avatar({ data-slot="avatar" className={cn( "relative flex size-8 shrink-0 overflow-hidden rounded-full", - className + className, )} {...props} /> - ) + ); } function AvatarImage({ @@ -31,7 +31,7 @@ function AvatarImage({ className={cn("aspect-square size-full", className)} {...props} /> - ) + ); } function AvatarFallback({ @@ -43,11 +43,11 @@ function AvatarFallback({ data-slot="avatar-fallback" className={cn( "bg-muted flex size-full items-center justify-center rounded-full", - className + className, )} {...props} /> - ) + ); } -export { Avatar, AvatarImage, AvatarFallback } +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 0205413..d9ebd4a 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -1,8 +1,8 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const badgeVariants = cva( "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", @@ -22,8 +22,8 @@ const badgeVariants = cva( defaultVariants: { variant: "default", }, - } -) + }, +); function Badge({ className, @@ -32,7 +32,7 @@ function Badge({ ...props }: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span" + const Comp = asChild ? Slot : "span"; return ( - ) + ); } -export { Badge, badgeVariants } +export { Badge, badgeVariants }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index a2df8dc..10a0797 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,8 +1,8 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", @@ -32,8 +32,8 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); function Button({ className, @@ -43,9 +43,9 @@ function Button({ ...props }: React.ComponentProps<"button"> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index d05bbc6..13631ed 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Card({ className, ...props }: React.ComponentProps<"div">) { return ( @@ -8,11 +8,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) { data-slot="card" className={cn( "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", - className + className, )} {...props} /> - ) + ); } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { @@ -21,11 +21,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-header" className={cn( "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", - className + className, )} {...props} /> - ) + ); } function CardTitle({ className, ...props }: React.ComponentProps<"div">) { @@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) { className={cn("leading-none font-semibold", className)} {...props} /> - ) + ); } function CardDescription({ className, ...props }: React.ComponentProps<"div">) { @@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) { className={cn("text-muted-foreground text-sm", className)} {...props} /> - ) + ); } function CardAction({ className, ...props }: React.ComponentProps<"div">) { @@ -54,11 +54,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-action" className={cn( "col-start-2 row-span-2 row-start-1 self-start justify-self-end", - className + className, )} {...props} /> - ) + ); } function CardContent({ className, ...props }: React.ComponentProps<"div">) { @@ -68,7 +68,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) { className={cn("px-6", className)} {...props} /> - ) + ); } function CardFooter({ className, ...props }: React.ComponentProps<"div">) { @@ -78,7 +78,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) { className={cn("flex items-center px-6 [.border-t]:pt-6", className)} {...props} /> - ) + ); } export { @@ -89,4 +89,4 @@ export { CardAction, CardDescription, CardContent, -} +}; diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx index ae9fad0..90935c6 100644 --- a/src/components/ui/collapsible.tsx +++ b/src/components/ui/collapsible.tsx @@ -1,11 +1,11 @@ -"use client" +"use client"; -import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; function Collapsible({ ...props }: React.ComponentProps) { - return + return ; } function CollapsibleTrigger({ @@ -16,7 +16,7 @@ function CollapsibleTrigger({ data-slot="collapsible-trigger" {...props} /> - ) + ); } function CollapsibleContent({ @@ -27,7 +27,7 @@ function CollapsibleContent({ data-slot="collapsible-content" {...props} /> - ) + ); } -export { Collapsible, CollapsibleTrigger, CollapsibleContent } +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx index e754186..0ef6109 100644 --- a/src/components/ui/hover-card.tsx +++ b/src/components/ui/hover-card.tsx @@ -1,14 +1,14 @@ -"use client" +"use client"; -import * as React from "react" -import * as HoverCardPrimitive from "@radix-ui/react-hover-card" +import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; +import type * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function HoverCard({ ...props }: React.ComponentProps) { - return + return ; } function HoverCardTrigger({ @@ -16,7 +16,7 @@ function HoverCardTrigger({ }: React.ComponentProps) { return ( - ) + ); } function HoverCardContent({ @@ -33,12 +33,12 @@ function HoverCardContent({ sideOffset={sideOffset} className={cn( "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", - className + className, )} {...props} /> - ) + ); } -export { HoverCard, HoverCardTrigger, HoverCardContent } +export { HoverCard, HoverCardTrigger, HoverCardContent }; diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx index 32ea0ef..0168998 100644 --- a/src/components/ui/skeleton.tsx +++ b/src/components/ui/skeleton.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Skeleton({ className, ...props }: React.ComponentProps<"div">) { return ( @@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) { className={cn("bg-accent animate-pulse rounded-md", className)} {...props} /> - ) + ); } -export { Skeleton } +export { Skeleton }; diff --git a/src/lib/conversation-schema/content/ImageContentSchema.ts b/src/lib/conversation-schema/content/ImageContentSchema.ts index a08df21..92fa1ac 100644 --- a/src/lib/conversation-schema/content/ImageContentSchema.ts +++ b/src/lib/conversation-schema/content/ImageContentSchema.ts @@ -1,10 +1,12 @@ -import { z } from 'zod' +import { z } from "zod"; -export const ImageContentSchema = z.object({ - type: z.literal('image'), - source: z.object({ - type: z.literal('base64'), - data: z.string(), - media_type: z.enum(['image/png']), - }), -}).strict() +export const ImageContentSchema = z + .object({ + type: z.literal("image"), + source: z.object({ + type: z.literal("base64"), + data: z.string(), + media_type: z.enum(["image/png"]), + }), + }) + .strict(); diff --git a/src/lib/conversation-schema/content/TextContentSchema.ts b/src/lib/conversation-schema/content/TextContentSchema.ts index 46eb68b..d2f0739 100644 --- a/src/lib/conversation-schema/content/TextContentSchema.ts +++ b/src/lib/conversation-schema/content/TextContentSchema.ts @@ -1,6 +1,8 @@ -import { z } from 'zod' +import { z } from "zod"; -export const TextContentSchema = z.object({ - type: z.literal('text'), - text: z.string(), -}).strict() +export const TextContentSchema = z + .object({ + type: z.literal("text"), + text: z.string(), + }) + .strict(); diff --git a/src/lib/conversation-schema/content/ThinkingContentSchema.ts b/src/lib/conversation-schema/content/ThinkingContentSchema.ts index b77bb1d..601e4a5 100644 --- a/src/lib/conversation-schema/content/ThinkingContentSchema.ts +++ b/src/lib/conversation-schema/content/ThinkingContentSchema.ts @@ -1,7 +1,9 @@ -import { z } from 'zod' +import { z } from "zod"; -export const ThinkingContentSchema = z.object({ - type: z.literal('thinking'), - thinking: z.string(), - signature: z.string().optional(), -}).strict() +export const ThinkingContentSchema = z + .object({ + type: z.literal("thinking"), + thinking: z.string(), + signature: z.string().optional(), + }) + .strict(); diff --git a/src/lib/conversation-schema/content/ToolResultContentSchema.ts b/src/lib/conversation-schema/content/ToolResultContentSchema.ts index 0fe2a49..78ccd07 100644 --- a/src/lib/conversation-schema/content/ToolResultContentSchema.ts +++ b/src/lib/conversation-schema/content/ToolResultContentSchema.ts @@ -1,16 +1,17 @@ -import { z } from 'zod' -import { TextContentSchema } from './TextContentSchema' -import { ImageContentSchema } from './ImageContentSchema' +import { z } from "zod"; +import { ImageContentSchema } from "./ImageContentSchema"; +import { TextContentSchema } from "./TextContentSchema"; -export const ToolResultContentSchema = z.object({ - type: z.literal('tool_result'), - tool_use_id: z.string(), - content: z.union([z.string(), z.array(z.union([ - TextContentSchema, - ImageContentSchema - ]))]), - is_error: z.boolean().optional(), -}).strict() +export const ToolResultContentSchema = z + .object({ + type: z.literal("tool_result"), + tool_use_id: z.string(), + content: z.union([ + z.string(), + z.array(z.union([TextContentSchema, ImageContentSchema])), + ]), + is_error: z.boolean().optional(), + }) + .strict(); - -export type ToolResultContent = z.infer +export type ToolResultContent = z.infer; diff --git a/src/lib/conversation-schema/content/ToolUseContentSchema.ts b/src/lib/conversation-schema/content/ToolUseContentSchema.ts index 799285c..9852c69 100644 --- a/src/lib/conversation-schema/content/ToolUseContentSchema.ts +++ b/src/lib/conversation-schema/content/ToolUseContentSchema.ts @@ -1,8 +1,10 @@ -import { z } from 'zod' +import { z } from "zod"; -export const ToolUseContentSchema = z.object({ - type: z.literal('tool_use'), - id: z.string(), - name: z.string(), - input: z.record(z.string(), z.unknown()), -}).strict() +export const ToolUseContentSchema = z + .object({ + type: z.literal("tool_use"), + id: z.string(), + name: z.string(), + input: z.record(z.string(), z.unknown()), + }) + .strict(); diff --git a/src/lib/conversation-schema/entry/AssistantEntrySchema.ts b/src/lib/conversation-schema/entry/AssistantEntrySchema.ts index d4130fc..80200a4 100644 --- a/src/lib/conversation-schema/entry/AssistantEntrySchema.ts +++ b/src/lib/conversation-schema/entry/AssistantEntrySchema.ts @@ -1,10 +1,10 @@ -import { z } from 'zod' -import { AssistantMessageSchema } from '../message/AssistantMessageSchema' -import { BaseEntrySchema } from './BaseEntrySchema' +import { z } from "zod"; +import { AssistantMessageSchema } from "../message/AssistantMessageSchema"; +import { BaseEntrySchema } from "./BaseEntrySchema"; export const AssistantEntrySchema = BaseEntrySchema.extend({ // discriminator - type: z.literal('assistant'), + type: z.literal("assistant"), // required message: AssistantMessageSchema, @@ -12,4 +12,4 @@ export const AssistantEntrySchema = BaseEntrySchema.extend({ // optional requestId: z.string().optional(), isApiErrorMessage: z.boolean().optional(), -}).strict() +}).strict(); diff --git a/src/lib/conversation-schema/entry/BaseEntrySchema.ts b/src/lib/conversation-schema/entry/BaseEntrySchema.ts index 12d1498..e97aec1 100644 --- a/src/lib/conversation-schema/entry/BaseEntrySchema.ts +++ b/src/lib/conversation-schema/entry/BaseEntrySchema.ts @@ -1,21 +1,23 @@ -import { z } from 'zod' +import { z } from "zod"; -export const BaseEntrySchema = z.object({ - // required - isSidechain: z.boolean(), - userType: z.enum(['external']), - cwd: z.string(), - sessionId: z.string(), - version: z.string(), - uuid: z.uuid(), - timestamp: z.string(), +export const BaseEntrySchema = z + .object({ + // required + isSidechain: z.boolean(), + userType: z.enum(["external"]), + cwd: z.string(), + sessionId: z.string(), + version: z.string(), + uuid: z.uuid(), + timestamp: z.string(), - // nullable - parentUuid: z.uuid().nullable(), + // nullable + parentUuid: z.uuid().nullable(), - // optional - isMeta: z.boolean().optional(), - toolUseResult: z.unknown().optional(), // スキーマがツールごとに異なりすぎるし利用もしなそうなので unknown - gitBranch: z.string().optional(), - isCompactSummary: z.boolean().optional(), -}).strict() + // optional + isMeta: z.boolean().optional(), + toolUseResult: z.unknown().optional(), // スキーマがツールごとに異なりすぎるし利用もしなそうなので unknown + gitBranch: z.string().optional(), + isCompactSummary: z.boolean().optional(), + }) + .strict(); diff --git a/src/lib/conversation-schema/entry/SummaryEntrySchema.ts b/src/lib/conversation-schema/entry/SummaryEntrySchema.ts index 7c798d1..1fac63c 100644 --- a/src/lib/conversation-schema/entry/SummaryEntrySchema.ts +++ b/src/lib/conversation-schema/entry/SummaryEntrySchema.ts @@ -1,7 +1,9 @@ -import { z } from 'zod' +import { z } from "zod"; -export const SummaryEntrySchema = z.object({ - type: z.literal('summary'), - summary: z.string(), - leafUuid: z.string().uuid(), -}).strict() +export const SummaryEntrySchema = z + .object({ + type: z.literal("summary"), + summary: z.string(), + leafUuid: z.string().uuid(), + }) + .strict(); diff --git a/src/lib/conversation-schema/entry/SystemEntrySchema.ts b/src/lib/conversation-schema/entry/SystemEntrySchema.ts index a232731..d6fa9eb 100644 --- a/src/lib/conversation-schema/entry/SystemEntrySchema.ts +++ b/src/lib/conversation-schema/entry/SystemEntrySchema.ts @@ -1,12 +1,12 @@ -import { z } from 'zod' -import { BaseEntrySchema } from './BaseEntrySchema' +import { z } from "zod"; +import { BaseEntrySchema } from "./BaseEntrySchema"; export const SystemEntrySchema = BaseEntrySchema.extend({ // discriminator - type: z.literal('system'), + type: z.literal("system"), // required content: z.string(), toolUseID: z.string(), - level: z.enum(['info']), -}).strict() + level: z.enum(["info"]), +}).strict(); diff --git a/src/lib/conversation-schema/entry/UserEntrySchema.ts b/src/lib/conversation-schema/entry/UserEntrySchema.ts index 56f1f13..c2e5794 100644 --- a/src/lib/conversation-schema/entry/UserEntrySchema.ts +++ b/src/lib/conversation-schema/entry/UserEntrySchema.ts @@ -1,11 +1,11 @@ -import { z } from 'zod' -import { UserMessageSchema } from '../message/UserMessageSchema' -import { BaseEntrySchema } from './BaseEntrySchema' +import { z } from "zod"; +import { UserMessageSchema } from "../message/UserMessageSchema"; +import { BaseEntrySchema } from "./BaseEntrySchema"; export const UserEntrySchema = BaseEntrySchema.extend({ // discriminator - type: z.literal('user'), + type: z.literal("user"), // required message: UserMessageSchema, -}).strict() +}).strict(); diff --git a/src/lib/conversation-schema/index.ts b/src/lib/conversation-schema/index.ts index 1f533b9..9224a8c 100644 --- a/src/lib/conversation-schema/index.ts +++ b/src/lib/conversation-schema/index.ts @@ -1,14 +1,14 @@ -import { z } from 'zod' -import { UserEntrySchema } from './entry/UserEntrySchema' -import { AssistantEntrySchema } from './entry/AssistantEntrySchema' -import { SummaryEntrySchema } from './entry/SummaryEntrySchema' -import { SystemEntrySchema } from './entry/SystemEntrySchema' +import { z } from "zod"; +import { AssistantEntrySchema } from "./entry/AssistantEntrySchema"; +import { SummaryEntrySchema } from "./entry/SummaryEntrySchema"; +import { SystemEntrySchema } from "./entry/SystemEntrySchema"; +import { UserEntrySchema } from "./entry/UserEntrySchema"; export const ConversationSchema = z.union([ UserEntrySchema, AssistantEntrySchema, SummaryEntrySchema, SystemEntrySchema, -]) +]); -export type Conversation = z.infer +export type Conversation = z.infer; diff --git a/src/lib/conversation-schema/message/AssistantMessageSchema.ts b/src/lib/conversation-schema/message/AssistantMessageSchema.ts index de16f6d..39105a1 100644 --- a/src/lib/conversation-schema/message/AssistantMessageSchema.ts +++ b/src/lib/conversation-schema/message/AssistantMessageSchema.ts @@ -1,38 +1,46 @@ -import { z } from 'zod' -import { ThinkingContentSchema } from '../content/ThinkingContentSchema' -import { TextContentSchema } from '../content/TextContentSchema' -import { ToolUseContentSchema } from '../content/ToolUseContentSchema' -import { ToolResultContentSchema } from '../content/ToolResultContentSchema' +import { z } from "zod"; +import { TextContentSchema } from "../content/TextContentSchema"; +import { ThinkingContentSchema } from "../content/ThinkingContentSchema"; +import { ToolResultContentSchema } from "../content/ToolResultContentSchema"; +import { ToolUseContentSchema } from "../content/ToolUseContentSchema"; const AssistantMessageContentSchema = z.union([ ThinkingContentSchema, TextContentSchema, ToolUseContentSchema, ToolResultContentSchema, -]) +]); -export type AssistantMessageContent = z.infer +export type AssistantMessageContent = z.infer< + typeof AssistantMessageContentSchema +>; -export const AssistantMessageSchema = z.object({ - id: z.string(), - type: z.literal('message'), - role: z.literal('assistant'), - model: z.string(), - content: z.array(AssistantMessageContentSchema), - stop_reason: z.string().nullable(), - stop_sequence: z.string().nullable(), - usage: z.object({ - input_tokens: z.number(), - cache_creation_input_tokens: z.number().optional(), - cache_read_input_tokens: z.number().optional(), - cache_creation: z.object({ - ephemeral_5m_input_tokens: z.number(), - ephemeral_1h_input_tokens: z.number(), - }).optional(), - output_tokens: z.number(), - service_tier: z.string().nullable().optional(), - server_tool_use: z.object({ - web_search_requests: z.number(), - }).optional(), - }), -}).strict() +export const AssistantMessageSchema = z + .object({ + id: z.string(), + type: z.literal("message"), + role: z.literal("assistant"), + model: z.string(), + content: z.array(AssistantMessageContentSchema), + stop_reason: z.string().nullable(), + stop_sequence: z.string().nullable(), + usage: z.object({ + input_tokens: z.number(), + cache_creation_input_tokens: z.number().optional(), + cache_read_input_tokens: z.number().optional(), + cache_creation: z + .object({ + ephemeral_5m_input_tokens: z.number(), + ephemeral_1h_input_tokens: z.number(), + }) + .optional(), + output_tokens: z.number(), + service_tier: z.string().nullable().optional(), + server_tool_use: z + .object({ + web_search_requests: z.number(), + }) + .optional(), + }), + }) + .strict(); diff --git a/src/lib/conversation-schema/message/UserMessageSchema.ts b/src/lib/conversation-schema/message/UserMessageSchema.ts index 675e496..85239c7 100644 --- a/src/lib/conversation-schema/message/UserMessageSchema.ts +++ b/src/lib/conversation-schema/message/UserMessageSchema.ts @@ -1,25 +1,23 @@ -import { z } from 'zod' -import { ToolResultContentSchema } from '../content/ToolResultContentSchema' -import { TextContentSchema } from '../content/TextContentSchema' -import { ImageContentSchema } from '../content/ImageContentSchema' - +import { z } from "zod"; +import { ImageContentSchema } from "../content/ImageContentSchema"; +import { TextContentSchema } from "../content/TextContentSchema"; +import { ToolResultContentSchema } from "../content/ToolResultContentSchema"; const UserMessageContentSchema = z.union([ z.string(), TextContentSchema, ToolResultContentSchema, - ImageContentSchema -]) + ImageContentSchema, +]); -export type UserMessageContent = z.infer +export type UserMessageContent = z.infer; -export const UserMessageSchema = z.object({ - role: z.literal('user'), - content: z.union([ - z.string(), - z.array(z.union([ +export const UserMessageSchema = z + .object({ + role: z.literal("user"), + content: z.union([ z.string(), - UserMessageContentSchema - ])) - ]), -}).strict() + z.array(z.union([z.string(), UserMessageContentSchema])), + ]), + }) + .strict(); diff --git a/src/lib/conversation-schema/tool/CommonToolSchema.ts b/src/lib/conversation-schema/tool/CommonToolSchema.ts index 132982f..ffcf75b 100644 --- a/src/lib/conversation-schema/tool/CommonToolSchema.ts +++ b/src/lib/conversation-schema/tool/CommonToolSchema.ts @@ -1,59 +1,71 @@ -import { z } from 'zod' -import { StructuredPatchSchema } from './StructuredPatchSchema' +import { z } from "zod"; +import { StructuredPatchSchema } from "./StructuredPatchSchema"; export const CommonToolResultSchema = z.union([ - z.object({ - stdout: z.string(), - stderr: z.string(), - interrupted: z.boolean(), - isImage: z.boolean(), - }).strict(), + z + .object({ + stdout: z.string(), + stderr: z.string(), + interrupted: z.boolean(), + isImage: z.boolean(), + }) + .strict(), // create - z.object({ - type: z.literal('create'), - filePath: z.string(), - content: z.string(), - structuredPatch: z.array(StructuredPatchSchema) - }).strict(), - - // update - z.object({ - filePath: z.string(), - oldString: z.string(), - newString: z.string(), - originalFile: z.string(), - userModified: z.boolean(), - replaceAll: z.boolean(), - structuredPatch: z.array(StructuredPatchSchema) - }).strict(), - - // search? - z.object({ - filenames: z.array(z.string()), - durationMs: z.number(), - numFiles: z.number(), - truncated: z.boolean(), - }).strict(), - - // text - z.object({ - type: z.literal('text'), - file: z.object({ + z + .object({ + type: z.literal("create"), filePath: z.string(), content: z.string(), - numLines: z.number(), - startLine: z.number(), - totalLines: z.number(), + structuredPatch: z.array(StructuredPatchSchema), }) - }).strict(), + .strict(), + + // update + z + .object({ + filePath: z.string(), + oldString: z.string(), + newString: z.string(), + originalFile: z.string(), + userModified: z.boolean(), + replaceAll: z.boolean(), + structuredPatch: z.array(StructuredPatchSchema), + }) + .strict(), + + // search? + z + .object({ + filenames: z.array(z.string()), + durationMs: z.number(), + numFiles: z.number(), + truncated: z.boolean(), + }) + .strict(), + + // text + z + .object({ + type: z.literal("text"), + file: z.object({ + filePath: z.string(), + content: z.string(), + numLines: z.number(), + startLine: z.number(), + totalLines: z.number(), + }), + }) + .strict(), // content - z.object({ - mode: z.literal('content'), - numFiles: z.number(), - filenames: z.array(z.string()), - content: z.string(), - numLines: z.number(), - }).strict(), -]) + z + .object({ + mode: z.literal("content"), + numFiles: z.number(), + filenames: z.array(z.string()), + content: z.string(), + numLines: z.number(), + }) + .strict(), +]); diff --git a/src/lib/conversation-schema/tool/StructuredPatchSchema.ts b/src/lib/conversation-schema/tool/StructuredPatchSchema.ts index 8922a65..5db20c8 100644 --- a/src/lib/conversation-schema/tool/StructuredPatchSchema.ts +++ b/src/lib/conversation-schema/tool/StructuredPatchSchema.ts @@ -1,9 +1,11 @@ -import { z } from 'zod' +import { z } from "zod"; -export const StructuredPatchSchema = z.object({ - oldStart: z.number(), - oldLines: z.number(), - newStart: z.number(), - newLines: z.number(), - lines: z.array(z.string()), -}).strict() +export const StructuredPatchSchema = z + .object({ + oldStart: z.number(), + oldLines: z.number(), + newStart: z.number(), + newLines: z.number(), + lines: z.array(z.string()), + }) + .strict(); diff --git a/src/lib/conversation-schema/tool/TodoSchema.ts b/src/lib/conversation-schema/tool/TodoSchema.ts index 61e16b4..914eac3 100644 --- a/src/lib/conversation-schema/tool/TodoSchema.ts +++ b/src/lib/conversation-schema/tool/TodoSchema.ts @@ -1,13 +1,17 @@ import z from "zod"; -const TodoSchema = z.object({ - content: z.string(), - status: z.enum(['pending', 'in_progress', 'completed']), - priority: z.enum(['low', 'medium', 'high']), - id: z.string(), -}).strict() +const TodoSchema = z + .object({ + content: z.string(), + status: z.enum(["pending", "in_progress", "completed"]), + priority: z.enum(["low", "medium", "high"]), + id: z.string(), + }) + .strict(); -export const TodoToolResultSchema = z.object({ - oldTodos: z.array(TodoSchema).optional(), - newTodos: z.array(TodoSchema).optional(), -}).strict() +export const TodoToolResultSchema = z + .object({ + oldTodos: z.array(TodoSchema).optional(), + newTodos: z.array(TodoSchema).optional(), + }) + .strict(); diff --git a/src/lib/conversation-schema/tool/index.ts b/src/lib/conversation-schema/tool/index.ts index 48d2fa5..305d24d 100644 --- a/src/lib/conversation-schema/tool/index.ts +++ b/src/lib/conversation-schema/tool/index.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { TodoToolResultSchema } from "./TodoSchema"; import { CommonToolResultSchema } from "./CommonToolSchema"; +import { TodoToolResultSchema } from "./TodoSchema"; export const ToolUseResultSchema = z.union([ z.string(), diff --git a/src/server/hono/route.ts b/src/server/hono/route.ts index 70b590b..fc501aa 100644 --- a/src/server/hono/route.ts +++ b/src/server/hono/route.ts @@ -1,8 +1,8 @@ -import type { HonoAppType } from "./app"; -import { getProjects } from "../service/project/getProjects"; import { getProject } from "../service/project/getProject"; -import { getSessions } from "../service/session/getSessions"; +import { getProjects } from "../service/project/getProjects"; import { getSession } from "../service/session/getSession"; +import { getSessions } from "../service/session/getSessions"; +import type { HonoAppType } from "./app"; export const routes = (app: HonoAppType) => { return app diff --git a/src/server/service/parseCommandXml.ts b/src/server/service/parseCommandXml.ts index d00388a..4b936be 100644 --- a/src/server/service/parseCommandXml.ts +++ b/src/server/service/parseCommandXml.ts @@ -7,9 +7,7 @@ const matchSchema = z.object({ content: z.string(), }); -export const parseCommandXml = ( - content: string -): +export type ParsedCommand = | { kind: "command"; commandName: string; @@ -28,7 +26,9 @@ export const parseCommandXml = ( | { kind: "text"; content: string; - } => { + }; + +export const parseCommandXml = (content: string): ParsedCommand => { const matches = Array.from(content.matchAll(regExp)) .map((match) => matchSchema.safeParse(match.groups)) .filter((result) => result.success) @@ -42,16 +42,16 @@ export const parseCommandXml = ( } const commandName = matches.find( - (match) => match.tag === "command-name" + (match) => match.tag === "command-name", )?.content; const commandArgs = matches.find( - (match) => match.tag === "command-args" + (match) => match.tag === "command-args", )?.content; const commandMessage = matches.find( - (match) => match.tag === "command-message" + (match) => match.tag === "command-message", )?.content; const localCommandStdout = matches.find( - (match) => match.tag === "local-command-stdout" + (match) => match.tag === "local-command-stdout", )?.content; switch (true) { diff --git a/src/server/service/project/getProject.ts b/src/server/service/project/getProject.ts index 09b9e24..4af646d 100644 --- a/src/server/service/project/getProject.ts +++ b/src/server/service/project/getProject.ts @@ -1,11 +1,11 @@ import { existsSync } from "node:fs"; import type { Project } from "../types"; -import { decodeProjectId } from "./id"; import { getProjectMeta } from "./getProjectMeta"; +import { decodeProjectId } from "./id"; export const getProject = async ( - projectId: string + projectId: string, ): Promise<{ project: Project }> => { const fullPath = decodeProjectId(projectId); if (!existsSync(fullPath)) { diff --git a/src/server/service/project/getProjectMeta.ts b/src/server/service/project/getProjectMeta.ts index 5713616..aeb8618 100644 --- a/src/server/service/project/getProjectMeta.ts +++ b/src/server/service/project/getProjectMeta.ts @@ -1,13 +1,18 @@ import { statSync } from "node:fs"; import { readdir, readFile } from "node:fs/promises"; -import { basename, dirname, resolve } from "node:path"; +import { basename, resolve } from "node:path"; import { parseJsonl } from "../parseJsonl"; import type { ProjectMeta } from "../types"; -const projectMetaCache = new Map(); +const projectPathCache = new Map(); + +const extractProjectPathFromJsonl = async (filePath: string) => { + const cached = projectPathCache.get(filePath); + if (cached !== undefined) { + return cached; + } -const extractMetaFromJsonl = async (filePath: string) => { const content = await readFile(filePath, "utf-8"); const lines = content.split("\n"); @@ -25,19 +30,16 @@ const extractMetaFromJsonl = async (filePath: string) => { break; } - return { - cwd, - } as const; + if (cwd !== null) { + projectPathCache.set(filePath, cwd); + } + + return cwd; }; export const getProjectMeta = async ( - claudeProjectPath: string + claudeProjectPath: string, ): Promise => { - const cached = projectMetaCache.get(claudeProjectPath); - if (cached !== undefined) { - return cached; - } - const dirents = await readdir(claudeProjectPath, { withFileTypes: true }); const files = dirents .filter((d) => d.isFile() && d.name.endsWith(".jsonl")) @@ -46,7 +48,7 @@ export const getProjectMeta = async ( ({ fullPath: resolve(d.parentPath, d.name), stats: statSync(resolve(d.parentPath, d.name)), - } as const) + }) as const, ) .toSorted((a, b) => { return a.stats.ctime.getTime() - b.stats.ctime.getTime(); @@ -54,30 +56,26 @@ export const getProjectMeta = async ( const lastModifiedUnixTime = files.at(-1)?.stats.ctime.getTime(); - let cwd: string | null = null; + let projectPath: string | null = null; for (const file of files) { - const result = await extractMetaFromJsonl(file.fullPath); + projectPath = await extractProjectPathFromJsonl(file.fullPath); - if (result.cwd === null) { + if (projectPath === null) { continue; } - cwd = result.cwd; - break; } const projectMeta: ProjectMeta = { - projectName: cwd ? basename(cwd) : null, - projectPath: cwd, + projectName: projectPath ? basename(projectPath) : null, + projectPath, lastModifiedAt: lastModifiedUnixTime ? new Date(lastModifiedUnixTime) : null, sessionCount: files.length, }; - projectMetaCache.set(claudeProjectPath, projectMeta); - return projectMeta; }; diff --git a/src/server/service/project/getProjects.ts b/src/server/service/project/getProjects.ts index 731c5cf..7b75553 100644 --- a/src/server/service/project/getProjects.ts +++ b/src/server/service/project/getProjects.ts @@ -3,8 +3,8 @@ import { resolve } from "node:path"; import { claudeProjectPath } from "../paths"; import type { Project } from "../types"; -import { encodeProjectId } from "./id"; import { getProjectMeta } from "./getProjectMeta"; +import { encodeProjectId } from "./id"; export const getProjects = async (): Promise<{ projects: Project[] }> => { const dirents = await readdir(claudeProjectPath, { withFileTypes: true }); @@ -24,6 +24,11 @@ export const getProjects = async (): Promise<{ projects: Project[] }> => { ); return { - projects, + projects: projects.sort((a, b) => { + return ( + (b.meta.lastModifiedAt?.getTime() ?? 0) - + (a.meta.lastModifiedAt?.getTime() ?? 0) + ); + }), }; }; diff --git a/src/server/service/session/getSession.ts b/src/server/service/session/getSession.ts index 509f078..7c7e612 100644 --- a/src/server/service/session/getSession.ts +++ b/src/server/service/session/getSession.ts @@ -1,13 +1,13 @@ import { readFile } from "node:fs/promises"; -import { decodeProjectId } from "../project/id"; import { resolve } from "node:path"; import { parseJsonl } from "../parseJsonl"; +import { decodeProjectId } from "../project/id"; import type { SessionDetail } from "../types"; import { getSessionMeta } from "./getSessionMeta"; export const getSession = async ( projectId: string, - sessionId: string + sessionId: string, ): Promise<{ session: SessionDetail; }> => { diff --git a/src/server/service/session/getSessionMeta.ts b/src/server/service/session/getSessionMeta.ts index bbc371c..3862b1d 100644 --- a/src/server/service/session/getSessionMeta.ts +++ b/src/server/service/session/getSessionMeta.ts @@ -1,26 +1,21 @@ import { statSync } from "node:fs"; import { readFile } from "node:fs/promises"; - -import { parseJsonl } from "../parseJsonl"; import type { Conversation } from "../../../lib/conversation-schema"; +import { type ParsedCommand, parseCommandXml } from "../parseCommandXml"; +import { parseJsonl } from "../parseJsonl"; import type { SessionMeta } from "../types"; -const sessionMetaCache = new Map(); +const firstCommandCache = new Map(); -export const getSessionMeta = async ( - jsonlFilePath: string -): Promise => { - const cached = sessionMetaCache.get(jsonlFilePath); +const getFirstCommand = ( + jsonlFilePath: string, + lines: string[], +): ParsedCommand | null => { + const cached = firstCommandCache.get(jsonlFilePath); if (cached !== undefined) { return cached; } - const stats = statSync(jsonlFilePath); - const lastModifiedUnixTime = stats.ctime.getTime(); - - const content = await readFile(jsonlFilePath, "utf-8"); - const lines = content.split("\n"); - let firstUserMessage: Conversation | null = null; for (const line of lines) { @@ -35,12 +30,10 @@ export const getSessionMeta = async ( break; } - const sessionMeta: SessionMeta = { - messageCount: lines.length, - firstContent: - firstUserMessage === null - ? null - : typeof firstUserMessage.message.content === "string" + const firstMessageText = + firstUserMessage === null + ? null + : typeof firstUserMessage.message.content === "string" ? firstUserMessage.message.content : (() => { const firstContent = firstUserMessage.message.content.at(0); @@ -48,13 +41,34 @@ export const getSessionMeta = async ( if (typeof firstContent === "string") return firstContent; if (firstContent.type === "text") return firstContent.text; return null; - })(), + })(); + + const firstCommand = + firstMessageText === null ? null : parseCommandXml(firstMessageText); + + if (firstCommand !== null) { + firstCommandCache.set(jsonlFilePath, firstCommand); + } + + return firstCommand; +}; + +export const getSessionMeta = async ( + jsonlFilePath: string, +): Promise => { + const stats = statSync(jsonlFilePath); + const lastModifiedUnixTime = stats.ctime.getTime(); + + const content = await readFile(jsonlFilePath, "utf-8"); + const lines = content.split("\n"); + + const sessionMeta: SessionMeta = { + messageCount: lines.length, + firstCommand: getFirstCommand(jsonlFilePath, lines), lastModifiedAt: lastModifiedUnixTime ? new Date(lastModifiedUnixTime) : null, }; - sessionMetaCache.set(jsonlFilePath, sessionMeta); - return sessionMeta; }; diff --git a/src/server/service/session/getSessions.ts b/src/server/service/session/getSessions.ts index 0c8f2da..6b24043 100644 --- a/src/server/service/session/getSessions.ts +++ b/src/server/service/session/getSessions.ts @@ -26,6 +26,11 @@ export const getSessions = async ( ); return { - sessions, + sessions: sessions.sort((a, b) => { + return ( + (b.meta.lastModifiedAt?.getTime() ?? 0) - + (a.meta.lastModifiedAt?.getTime() ?? 0) + ); + }), }; }; diff --git a/src/server/service/types.ts b/src/server/service/types.ts index 9e120fc..f32c4c9 100644 --- a/src/server/service/types.ts +++ b/src/server/service/types.ts @@ -1,4 +1,5 @@ import type { Conversation } from "../../lib/conversation-schema"; +import type { ParsedCommand } from "./parseCommandXml"; export type Project = { id: string; @@ -21,7 +22,7 @@ export type Session = { export type SessionMeta = { messageCount: number; - firstContent: string | null; + firstCommand: ParsedCommand | null; lastModifiedAt: Date | null; };