diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx index 3a7e180..800dd72 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageContent.tsx @@ -1,97 +1,15 @@ -import { Trans } from "@lingui/react"; -import { useMutation } from "@tanstack/react-query"; -import { - GitCompareIcon, - LoaderIcon, - MenuIcon, - PauseIcon, - XIcon, -} from "lucide-react"; import type { FC } from "react"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { PermissionDialog } from "@/components/PermissionDialog"; -import { Button } from "@/components/ui/button"; -import { usePermissionRequests } from "@/hooks/usePermissionRequests"; -import { useTaskNotifications } from "@/hooks/useTaskNotifications"; -import { Badge } from "../../../../../../components/ui/badge"; -import { honoClient } from "../../../../../../lib/api/client"; -import { useProject } from "../../../hooks/useProject"; -import { firstUserMessageToTitle } from "../../../services/firstCommandToTitle"; -import { useSession } from "../hooks/useSession"; -import { useSessionProcess } from "../hooks/useSessionProcess"; -import { ConversationList } from "./conversationList/ConversationList"; -import { DiffModal } from "./diffModal"; -import { ContinueChat } from "./resumeChat/ContinueChat"; -import { ResumeChat } from "./resumeChat/ResumeChat"; +import { Suspense, useState } from "react"; + +import { Loading } from "@/components/Loading"; +import { SessionPageMain } from "./SessionPageMain"; import { SessionSidebar } from "./sessionSidebar/SessionSidebar"; export const SessionPageContent: FC<{ projectId: string; sessionId: string; }> = ({ projectId, sessionId }) => { - const { session, conversations, getToolResult } = useSession( - projectId, - sessionId, - ); - const { data: projectData } = useProject(projectId); - // biome-ignore lint/style/noNonNullAssertion: useSuspenseInfiniteQuery guarantees at least one page - const project = projectData.pages[0]!.project; - - const abortTask = useMutation({ - mutationFn: async (sessionProcessId: string) => { - const response = await honoClient.api.cc["session-processes"][ - ":sessionProcessId" - ].abort.$post({ - param: { sessionProcessId }, - json: { projectId }, - }); - - if (!response.ok) { - throw new Error(response.statusText); - } - - return response.json(); - }, - }); - const sessionProcess = useSessionProcess(); - - const { currentPermissionRequest, isDialogOpen, onPermissionResponse } = - usePermissionRequests(); - - const relatedSessionProcess = useMemo( - () => sessionProcess.getSessionProcess(sessionId), - [sessionProcess, sessionId], - ); - - // Set up task completion notifications - useTaskNotifications(relatedSessionProcess?.status === "running"); - - const [previousConversationLength, setPreviousConversationLength] = - useState(0); const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); - const [isDiffModalOpen, setIsDiffModalOpen] = useState(false); - const scrollContainerRef = useRef(null); - - // 自動スクロール処理 - useEffect(() => { - if ( - relatedSessionProcess?.status === "running" && - conversations.length !== previousConversationLength - ) { - setPreviousConversationLength(conversations.length); - const scrollContainer = scrollContainerRef.current; - if (scrollContainer) { - scrollContainer.scrollTo({ - top: scrollContainer.scrollHeight, - behavior: "smooth", - }); - } - } - }, [ - conversations, - relatedSessionProcess?.status, - previousConversationLength, - ]); return (
@@ -101,178 +19,13 @@ export const SessionPageContent: FC<{ isMobileOpen={isMobileSidebarOpen} onMobileOpenChange={setIsMobileSidebarOpen} /> - -
-
-
-
- -

- {session.meta.firstUserMessage !== null - ? firstUserMessageToTitle(session.meta.firstUserMessage) - : sessionId} -

-
- -
- {project?.claudeProjectPath && ( - - {project.meta.projectPath ?? project.claudeProjectPath} - - )} - - {sessionId} - -
- - {relatedSessionProcess?.status === "running" && ( -
- -
-

- -

-
-
-
-
- -
- )} - - {relatedSessionProcess?.status === "paused" && ( -
- -
-

- -

-
- -
- )} -
-
- -
-
- - - {relatedSessionProcess?.status === "running" && ( -
-
-
- -
-
-

- -

-
-
- )} - - {relatedSessionProcess !== undefined ? ( - - ) : ( - - )} -
-
-
- - {/* Fixed Diff Button */} - - - {/* Diff Modal */} - - - {/* Permission Dialog */} - + }> + +
); }; diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageMain.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageMain.tsx new file mode 100644 index 0000000..83f47b0 --- /dev/null +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/SessionPageMain.tsx @@ -0,0 +1,269 @@ +import { Trans } from "@lingui/react"; +import { useMutation } from "@tanstack/react-query"; +import { + GitCompareIcon, + LoaderIcon, + MenuIcon, + PauseIcon, + XIcon, +} from "lucide-react"; +import { type FC, useEffect, useMemo, useRef, useState } from "react"; + +import { PermissionDialog } from "@/components/PermissionDialog"; +import { Button } from "@/components/ui/button"; +import { usePermissionRequests } from "@/hooks/usePermissionRequests"; +import { useTaskNotifications } from "@/hooks/useTaskNotifications"; +import { Badge } from "../../../../../../components/ui/badge"; +import { honoClient } from "../../../../../../lib/api/client"; +import { useProject } from "../../../hooks/useProject"; +import { firstUserMessageToTitle } from "../../../services/firstCommandToTitle"; +import { useSession } from "../hooks/useSession"; +import { useSessionProcess } from "../hooks/useSessionProcess"; +import { ConversationList } from "./conversationList/ConversationList"; +import { DiffModal } from "./diffModal"; +import { ContinueChat } from "./resumeChat/ContinueChat"; +import { ResumeChat } from "./resumeChat/ResumeChat"; + +export const SessionPageMain: FC<{ + projectId: string; + sessionId: string; + setIsMobileSidebarOpen: (open: boolean) => void; +}> = ({ projectId, sessionId, setIsMobileSidebarOpen }) => { + const { session, conversations, getToolResult } = useSession( + projectId, + sessionId, + ); + const { data: projectData } = useProject(projectId); + // biome-ignore lint/style/noNonNullAssertion: useSuspenseInfiniteQuery guarantees at least one page + const project = projectData.pages[0]!.project; + const { currentPermissionRequest, isDialogOpen, onPermissionResponse } = + usePermissionRequests(); + const [isDiffModalOpen, setIsDiffModalOpen] = useState(false); + + const abortTask = useMutation({ + mutationFn: async (sessionProcessId: string) => { + const response = await honoClient.api.cc["session-processes"][ + ":sessionProcessId" + ].abort.$post({ + param: { sessionProcessId }, + json: { projectId }, + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + return response.json(); + }, + }); + const sessionProcess = useSessionProcess(); + + const relatedSessionProcess = useMemo( + () => sessionProcess.getSessionProcess(sessionId), + [sessionProcess, sessionId], + ); + + // Set up task completion notifications + useTaskNotifications(relatedSessionProcess?.status === "running"); + + const [previousConversationLength, setPreviousConversationLength] = + useState(0); + const scrollContainerRef = useRef(null); + + // 自動スクロール処理 + useEffect(() => { + if ( + relatedSessionProcess?.status === "running" && + conversations.length !== previousConversationLength + ) { + setPreviousConversationLength(conversations.length); + const scrollContainer = scrollContainerRef.current; + if (scrollContainer) { + scrollContainer.scrollTo({ + top: scrollContainer.scrollHeight, + behavior: "smooth", + }); + } + } + }, [ + conversations, + relatedSessionProcess?.status, + previousConversationLength, + ]); + + return ( + <> +
+
+
+
+ +

+ {session.meta.firstUserMessage !== null + ? firstUserMessageToTitle(session.meta.firstUserMessage) + : sessionId} +

+
+ +
+ {project?.claudeProjectPath && ( + + {project.meta.projectPath ?? project.claudeProjectPath} + + )} + + {sessionId} + +
+ + {relatedSessionProcess?.status === "running" && ( +
+ +
+

+ +

+
+
+
+
+ +
+ )} + + {relatedSessionProcess?.status === "paused" && ( +
+ +
+

+ +

+
+ +
+ )} +
+
+ +
+
+ + + {relatedSessionProcess?.status === "running" && ( +
+
+
+ +
+
+

+ +

+
+
+ )} + + {relatedSessionProcess !== undefined ? ( + + ) : ( + + )} +
+
+
+ + {/* Fixed Diff Button */} + + + {/* Diff Modal */} + + + {/* Permission Dialog */} + + + ); +}; diff --git a/src/components/scheduler/CronExpressionBuilder.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/scheduler/CronExpressionBuilder.tsx similarity index 100% rename from src/components/scheduler/CronExpressionBuilder.tsx rename to src/app/projects/[projectId]/sessions/[sessionId]/components/scheduler/CronExpressionBuilder.tsx diff --git a/src/components/scheduler/SchedulerJobDialog.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/scheduler/SchedulerJobDialog.tsx similarity index 100% rename from src/components/scheduler/SchedulerJobDialog.tsx rename to src/app/projects/[projectId]/sessions/[sessionId]/components/scheduler/SchedulerJobDialog.tsx diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/McpTab.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/McpTab.tsx index 9ca241c..6929bef 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/McpTab.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/McpTab.tsx @@ -3,6 +3,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 { Loading } from "../../../../../../../components/Loading"; import { mcpListQuery } from "../../../../../../../lib/api/queries"; export const McpTab: FC<{ projectId: string }> = ({ projectId }) => { @@ -13,9 +14,14 @@ export const McpTab: FC<{ projectId: string }> = ({ projectId }) => { data: mcpData, isLoading, error, + isFetching, } = useQuery({ queryKey: mcpListQuery(projectId).queryKey, queryFn: mcpListQuery(projectId).queryFn, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchInterval: false, }); const handleReload = () => { @@ -36,11 +42,11 @@ export const McpTab: FC<{ projectId: string }> = ({ projectId }) => { variant="ghost" size="sm" className="h-7 w-7 p-0" - disabled={isLoading} + disabled={isLoading || isFetching} title={i18n._("Reload MCP servers")} > @@ -50,7 +56,7 @@ export const McpTab: FC<{ projectId: string }> = ({ projectId }) => { {isLoading && (
- +
)} diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/MobileSidebar.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/MobileSidebar.tsx index 1e431a8..2be4a47 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/MobileSidebar.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/MobileSidebar.tsx @@ -21,7 +21,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; -import { useProject } from "../../../../hooks/useProject"; import { McpTab } from "./McpTab"; import { SessionsTab } from "./SessionsTab"; @@ -39,13 +38,6 @@ export const MobileSidebar: FC = ({ onClose, }) => { const { i18n } = useLingui(); - const { - data: projectData, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - } = useProject(projectId); - const sessions = projectData.pages.flatMap((page) => page.sessions); const [activeTab, setActiveTab] = useState< "sessions" | "mcp" | "settings" | "system-info" >("sessions"); @@ -93,15 +85,8 @@ export const MobileSidebar: FC = ({ case "sessions": return ( ({ - ...session, - lastModifiedAt: new Date(session.lastModifiedAt), - }))} currentSessionId={currentSessionId} projectId={projectId} - hasNextPage={hasNextPage} - isFetchingNextPage={isFetchingNextPage} - onLoadMore={() => fetchNextPage()} isMobile={true} /> ); diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/SchedulerTab.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/SchedulerTab.tsx index 9015321..e877614 100644 --- a/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/SchedulerTab.tsx +++ b/src/app/projects/[projectId]/sessions/[sessionId]/components/sessionSidebar/SchedulerTab.tsx @@ -2,7 +2,6 @@ import { Trans, useLingui } from "@lingui/react"; import { EditIcon, PlusIcon, RefreshCwIcon, TrashIcon } from "lucide-react"; import { type FC, useState } from "react"; import { toast } from "sonner"; -import { SchedulerJobDialog } from "@/components/scheduler/SchedulerJobDialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -21,13 +20,21 @@ import { useSchedulerJobs, useUpdateSchedulerJob, } from "@/hooks/useScheduler"; +import { Loading } from "../../../../../../../components/Loading"; +import { SchedulerJobDialog } from "../scheduler/SchedulerJobDialog"; export const SchedulerTab: FC<{ projectId: string; sessionId: string }> = ({ projectId, sessionId, }) => { const { i18n } = useLingui(); - const { data: jobs, isLoading, error, refetch } = useSchedulerJobs(); + const { + data: jobs, + isLoading, + isFetching, + error, + refetch, + } = useSchedulerJobs(); const createJob = useCreateSchedulerJob(); const updateJob = useUpdateSchedulerJob(); const deleteJob = useDeleteSchedulerJob(); @@ -164,11 +171,11 @@ export const SchedulerTab: FC<{ projectId: string; sessionId: string }> = ({ variant="ghost" size="sm" className="h-7 w-7 p-0" - disabled={isLoading} + disabled={isLoading || isFetching} title={i18n._({ id: "common.reload", message: "Reload" })} >