mirror of
https://github.com/aljazceru/claude-code-viewer.git
synced 2026-01-21 14:34:21 +01:00
perf: refactor sse handleing
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { honoClient } from "../lib/api/client";
|
||||
import { fileCompletionQuery } from "../lib/api/queries";
|
||||
|
||||
export type FileCompletionEntry = {
|
||||
name: string;
|
||||
@@ -19,21 +19,8 @@ export const useFileCompletion = (
|
||||
enabled = true,
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: ["file-completion", projectId, basePath],
|
||||
queryFn: async (): Promise<FileCompletionResult> => {
|
||||
const response = await honoClient.api.projects[":projectId"][
|
||||
"file-completion"
|
||||
].$get({
|
||||
param: { projectId },
|
||||
query: { basePath },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch file completion");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
queryKey: fileCompletionQuery(projectId, basePath).queryKey,
|
||||
queryFn: fileCompletionQuery(projectId, basePath).queryFn,
|
||||
enabled: enabled && !!projectId,
|
||||
staleTime: 1000 * 60 * 5, // 5分間キャッシュ
|
||||
});
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { aliveTasksAtom } from "../app/projects/[projectId]/sessions/[sessionId]/store/aliveTasksAtom";
|
||||
import { projetsQueryConfig } from "../app/projects/hooks/useProjects";
|
||||
import { honoClient } from "../lib/api/client";
|
||||
import type { SSEEvent } from "../server/service/events/types";
|
||||
|
||||
type ParsedEvent = {
|
||||
event: string;
|
||||
data: SSEEvent;
|
||||
id: string;
|
||||
};
|
||||
|
||||
const parseSSEEvent = (text: string): ParsedEvent => {
|
||||
const lines = text.split("\n");
|
||||
const eventIndex = lines.findIndex((line) => line.startsWith("event:"));
|
||||
const dataIndex = lines.findIndex((line) => line.startsWith("data:"));
|
||||
const idIndex = lines.findIndex((line) => line.startsWith("id:"));
|
||||
|
||||
const endIndex = (index: number) => {
|
||||
const targets = [eventIndex, dataIndex, idIndex, lines.length].filter(
|
||||
(current) => current > index,
|
||||
);
|
||||
return Math.min(...targets);
|
||||
};
|
||||
|
||||
if (eventIndex === -1 || dataIndex === -1 || idIndex === -1) {
|
||||
console.error("failed", text);
|
||||
throw new Error("Failed to parse SSE event");
|
||||
}
|
||||
|
||||
const event = lines.slice(eventIndex, endIndex(eventIndex)).join("\n");
|
||||
const data = lines.slice(dataIndex, endIndex(dataIndex)).join("\n");
|
||||
const id = lines.slice(idIndex, endIndex(idIndex)).join("\n");
|
||||
|
||||
return {
|
||||
id: id.slice("id:".length).trim(),
|
||||
event: event.slice("event:".length).trim(),
|
||||
data: JSON.parse(
|
||||
data.slice(data.indexOf("{"), data.lastIndexOf("}") + 1),
|
||||
) as SSEEvent,
|
||||
};
|
||||
};
|
||||
|
||||
const parseSSEEvents = (text: string): ParsedEvent[] => {
|
||||
const eventTexts = text
|
||||
.split("\n\n")
|
||||
.filter((eventText) => eventText.length > 0);
|
||||
|
||||
return eventTexts.map((eventText) => parseSSEEvent(eventText));
|
||||
};
|
||||
|
||||
let isInitialized = false;
|
||||
|
||||
export const useServerEvents = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const setAliveTasks = useSetAtom(aliveTasksAtom);
|
||||
|
||||
const listener = useCallback(async () => {
|
||||
console.log("listening to events");
|
||||
const response = await honoClient.api.events.state_changes.$get();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch events");
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new Error("Failed to get reader");
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
const events = parseSSEEvents(decoder.decode(value));
|
||||
|
||||
for (const event of events) {
|
||||
console.log("data", event);
|
||||
|
||||
if (event.data.type === "project_changed") {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: projetsQueryConfig.queryKey,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.data.type === "session_changed") {
|
||||
await queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
}
|
||||
|
||||
if (event.data.type === "task_changed") {
|
||||
setAliveTasks(event.data.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [queryClient, setAliveTasks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized === false) {
|
||||
void listener()
|
||||
.then(() => {
|
||||
console.log("registered events listener");
|
||||
isInitialized = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("failed to register events listener", error);
|
||||
isInitialized = true;
|
||||
});
|
||||
}
|
||||
}, [listener]);
|
||||
};
|
||||
Reference in New Issue
Block a user