mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2026-01-09 00:24:22 +01:00
perf: refactor sse handleing
This commit is contained in:
33
src/app/components/SSEEventListeners.tsx
Normal file
33
src/app/components/SSEEventListeners.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useSetAtom } from "jotai";
|
||||
import type { FC, PropsWithChildren } from "react";
|
||||
import { projectDetailQuery, sessionDetailQuery } from "../../lib/api/queries";
|
||||
import { useServerEventListener } from "../../lib/sse/hook/useServerEventListener";
|
||||
import { aliveTasksAtom } from "../projects/[projectId]/sessions/[sessionId]/store/aliveTasksAtom";
|
||||
|
||||
export const SSEEventListeners: FC<PropsWithChildren> = ({ children }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const setAliveTasks = useSetAtom(aliveTasksAtom);
|
||||
|
||||
useServerEventListener("sessionListChanged", async (event) => {
|
||||
// invalidate session list
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: projectDetailQuery(event.projectId).queryKey,
|
||||
});
|
||||
});
|
||||
|
||||
useServerEventListener("sessionChanged", async (event) => {
|
||||
// invalidate session detail
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: sessionDetailQuery(event.projectId, event.sessionId).queryKey,
|
||||
});
|
||||
});
|
||||
|
||||
useServerEventListener("taskChanged", async (event) => {
|
||||
setAliveTasks(event.aliveTasks);
|
||||
});
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useServerEvents } from "@/hooks/useServerEvents";
|
||||
|
||||
interface ServerEventsProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ServerEventsProvider({ children }: ServerEventsProviderProps) {
|
||||
useServerEvents();
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -5,21 +5,15 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import { useCallback } from "react";
|
||||
import { honoClient } from "../../lib/api/client";
|
||||
import { configQuery } from "../../lib/api/queries";
|
||||
import type { Config } from "../../server/config/config";
|
||||
|
||||
export const configQueryConfig = {
|
||||
queryKey: ["config"],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.config.$get();
|
||||
return await response.json();
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const useConfig = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data } = useSuspenseQuery({
|
||||
...configQueryConfig,
|
||||
queryKey: configQuery.queryKey,
|
||||
queryFn: configQuery.queryFn,
|
||||
});
|
||||
const updateConfigMutation = useMutation({
|
||||
mutationFn: async (config: Config) => {
|
||||
@@ -30,7 +24,7 @@ export const useConfig = () => {
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: configQueryConfig.queryKey,
|
||||
queryKey: configQuery.queryKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
|
||||
import { Toaster } from "../components/ui/sonner";
|
||||
import { QueryClientProviderWrapper } from "../lib/api/QueryClientProviderWrapper";
|
||||
import { SSEProvider } from "../lib/sse/components/SSEProvider";
|
||||
import { RootErrorBoundary } from "./components/RootErrorBoundary";
|
||||
import { ServerEventsProvider } from "./components/ServerEventsProvider";
|
||||
|
||||
import "./globals.css";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { configQueryConfig } from "./hooks/useConfig";
|
||||
import { configQuery } from "../lib/api/queries";
|
||||
import { SSEEventListeners } from "./components/SSEEventListeners";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const fetchCache = "force-no-store";
|
||||
@@ -35,7 +37,8 @@ export default async function RootLayout({
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
...configQueryConfig,
|
||||
queryKey: configQuery.queryKey,
|
||||
queryFn: configQuery.queryFn,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -45,7 +48,9 @@ export default async function RootLayout({
|
||||
>
|
||||
<RootErrorBoundary>
|
||||
<QueryClientProviderWrapper>
|
||||
<ServerEventsProvider>{children}</ServerEventsProvider>
|
||||
<SSEProvider>
|
||||
<SSEEventListeners>{children}</SSEEventListeners>
|
||||
</SSEProvider>
|
||||
</QueryClientProviderWrapper>
|
||||
</RootErrorBoundary>
|
||||
<Toaster position="top-right" />
|
||||
|
||||
@@ -25,8 +25,9 @@ import {
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { projectDetailQuery } from "../../../../lib/api/queries";
|
||||
import { useConfig } from "../../../hooks/useConfig";
|
||||
import { projectQueryConfig, useProject } from "../hooks/useProject";
|
||||
import { useProject } from "../hooks/useProject";
|
||||
import { firstCommandToTitle } from "../services/firstCommandToTitle";
|
||||
import { NewChatModal } from "./newChat/NewChatModal";
|
||||
|
||||
@@ -41,7 +42,7 @@ export const ProjectPageContent = ({ projectId }: { projectId: string }) => {
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: invalidate when config changed
|
||||
useEffect(() => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: projectQueryConfig(projectId).queryKey,
|
||||
queryKey: projectDetailQuery(projectId).queryKey,
|
||||
});
|
||||
}, [config.hideNoUserMessageSession, config.unifySameTitleSession]);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
} from "../../../../../components/ui/collapsible";
|
||||
import { honoClient } from "../../../../../lib/api/client";
|
||||
import { claudeCommandsQuery } from "../../../../../lib/api/queries";
|
||||
import { cn } from "../../../../../lib/utils";
|
||||
|
||||
type CommandCompletionProps = {
|
||||
@@ -40,18 +40,8 @@ export const CommandCompletion = forwardRef<
|
||||
|
||||
// コマンドリストを取得
|
||||
const { data: commandData } = useQuery({
|
||||
queryKey: ["claude-commands", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.projects[":projectId"][
|
||||
"claude-commands"
|
||||
].$get({
|
||||
param: { projectId },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch commands");
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
queryKey: claudeCommandsQuery(projectId).queryKey,
|
||||
queryFn: claudeCommandsQuery(projectId).queryFn,
|
||||
staleTime: 1000 * 60 * 5, // 5分間キャッシュ
|
||||
});
|
||||
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { honoClient } from "../../../../lib/api/client";
|
||||
|
||||
export const projectQueryConfig = (projectId: string) =>
|
||||
({
|
||||
queryKey: ["projects", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.projects[":projectId"].$get({
|
||||
param: { projectId },
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
},
|
||||
}) as const;
|
||||
import { projectDetailQuery } from "../../../../lib/api/queries";
|
||||
|
||||
export const useProject = (projectId: string) => {
|
||||
return useSuspenseQuery({
|
||||
...projectQueryConfig(projectId),
|
||||
queryKey: projectDetailQuery(projectId).queryKey,
|
||||
queryFn: projectDetailQuery(projectId).queryFn,
|
||||
refetchOnReconnect: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { projectDetailQuery } from "../../../lib/api/queries";
|
||||
import { ProjectPageContent } from "./components/ProjectPage";
|
||||
import { projectQueryConfig } from "./hooks/useProject";
|
||||
|
||||
interface ProjectPageProps {
|
||||
params: Promise<{ projectId: string }>;
|
||||
@@ -16,7 +16,8 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
...projectQueryConfig(projectId),
|
||||
queryKey: projectDetailQuery(projectId).queryKey,
|
||||
queryFn: projectDetailQuery(projectId).queryFn,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { RefreshCwIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { honoClient } from "@/lib/api/client";
|
||||
import { mcpListQuery } from "../../../../../../../lib/api/queries";
|
||||
|
||||
export const McpTab: FC = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -14,18 +14,12 @@ export const McpTab: FC = () => {
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["mcp", "list"],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.mcp.list.$get();
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch MCP servers");
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
queryKey: mcpListQuery.queryKey,
|
||||
queryFn: mcpListQuery.queryFn,
|
||||
});
|
||||
|
||||
const handleReload = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["mcp", "list"] });
|
||||
queryClient.invalidateQueries({ queryKey: mcpListQuery.queryKey });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { useMemo } from "react";
|
||||
import { honoClient } from "../../../../../../lib/api/client";
|
||||
import { aliveTasksQuery } from "../../../../../../lib/api/queries";
|
||||
import { aliveTasksAtom } from "../store/aliveTasksAtom";
|
||||
|
||||
export const useAliveTask = (sessionId: string) => {
|
||||
const [aliveTasks, setAliveTasks] = useAtom(aliveTasksAtom);
|
||||
|
||||
useQuery({
|
||||
queryKey: ["aliveTasks"],
|
||||
queryKey: aliveTasksQuery.queryKey,
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.tasks.alive.$get({});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setAliveTasks(data.aliveTasks);
|
||||
return response.json();
|
||||
const { aliveTasks } = await aliveTasksQuery.queryFn();
|
||||
setAliveTasks(aliveTasks);
|
||||
return aliveTasks;
|
||||
},
|
||||
refetchOnReconnect: true,
|
||||
});
|
||||
|
||||
@@ -1,42 +1,22 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { honoClient } from "@/lib/api/client";
|
||||
import {
|
||||
gitBranchesQuery,
|
||||
gitCommitsQuery,
|
||||
} from "../../../../../../lib/api/queries";
|
||||
|
||||
export const useGitBranches = (projectId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["git", "branches", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.projects[
|
||||
":projectId"
|
||||
].git.branches.$get({
|
||||
param: { projectId },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch branches: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
queryKey: gitBranchesQuery(projectId).queryKey,
|
||||
queryFn: gitBranchesQuery(projectId).queryFn,
|
||||
staleTime: 30000, // 30 seconds
|
||||
});
|
||||
};
|
||||
|
||||
export const useGitCommits = (projectId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["git", "commits", projectId],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.projects[
|
||||
":projectId"
|
||||
].git.commits.$get({
|
||||
param: { projectId },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch commits: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
queryKey: gitCommitsQuery(projectId).queryKey,
|
||||
queryFn: gitCommitsQuery(projectId).queryFn,
|
||||
staleTime: 30000, // 30 seconds
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { honoClient } from "../../../../../../lib/api/client";
|
||||
|
||||
export const sessionQueryConfig = (projectId: string, sessionId: string) =>
|
||||
({
|
||||
queryKey: ["sessions", sessionId],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.projects[":projectId"].sessions[
|
||||
":sessionId"
|
||||
].$get({
|
||||
param: {
|
||||
projectId,
|
||||
sessionId,
|
||||
},
|
||||
});
|
||||
|
||||
return response.json();
|
||||
},
|
||||
}) as const;
|
||||
import { sessionDetailQuery } from "../../../../../../lib/api/queries";
|
||||
|
||||
export const useSessionQuery = (projectId: string, sessionId: string) => {
|
||||
return useSuspenseQuery({
|
||||
...sessionQueryConfig(projectId, sessionId),
|
||||
queryKey: sessionDetailQuery(projectId, sessionId).queryKey,
|
||||
queryFn: sessionDetailQuery(projectId, sessionId).queryFn,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import type { Metadata } from "next";
|
||||
import { projectQueryConfig } from "../../hooks/useProject";
|
||||
import {
|
||||
projectDetailQuery,
|
||||
sessionDetailQuery,
|
||||
} from "../../../../../lib/api/queries";
|
||||
import { SessionPageContent } from "./components/SessionPageContent";
|
||||
import { sessionQueryConfig } from "./hooks/useSessionQuery";
|
||||
|
||||
type PageParams = {
|
||||
projectId: string;
|
||||
@@ -19,11 +21,12 @@ export async function generateMetadata({
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
...sessionQueryConfig(projectId, sessionId),
|
||||
...sessionDetailQuery(projectId, sessionId),
|
||||
});
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
...projectQueryConfig(projectId),
|
||||
queryKey: projectDetailQuery(projectId).queryKey,
|
||||
queryFn: projectDetailQuery(projectId).queryFn,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
import { useProjects } from "../hooks/useProjects";
|
||||
|
||||
export const ProjectList: FC = () => {
|
||||
const { data: projects } = useProjects();
|
||||
const {
|
||||
data: { projects },
|
||||
} = useProjects();
|
||||
|
||||
if (projects.length === 0) {
|
||||
<Card>
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { honoClient } from "../../../lib/api/client";
|
||||
|
||||
export const projetsQueryConfig = {
|
||||
queryKey: ["projects"],
|
||||
queryFn: async () => {
|
||||
const response = await honoClient.api.projects.$get();
|
||||
const { projects } = await response.json();
|
||||
return projects;
|
||||
},
|
||||
} as const;
|
||||
import { projectListQuery } from "../../../lib/api/queries";
|
||||
|
||||
export const useProjects = () => {
|
||||
return useSuspenseQuery({
|
||||
queryKey: projetsQueryConfig.queryKey,
|
||||
queryFn: projetsQueryConfig.queryFn,
|
||||
queryKey: projectListQuery.queryKey,
|
||||
queryFn: projectListQuery.queryFn,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { HistoryIcon } from "lucide-react";
|
||||
import { projectListQuery } from "../../lib/api/queries";
|
||||
import { ProjectList } from "./components/ProjectList";
|
||||
import { projetsQueryConfig } from "./hooks/useProjects";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const fetchCache = "force-no-store";
|
||||
@@ -10,8 +10,8 @@ export default async function ProjectsPage() {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: projetsQueryConfig.queryKey,
|
||||
queryFn: projetsQueryConfig.queryFn,
|
||||
queryKey: projectListQuery.queryKey,
|
||||
queryFn: projectListQuery.queryFn,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user