conductor-oss 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/node_modules/@conductor-oss/plugin-agent-amp/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-ccr/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-claude-code/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-codex/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-cursor-cli/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-droid/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-gemini/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-github-copilot/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-opencode/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-qwen-code/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-mcp-server/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-notifier-desktop/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-notifier-discord/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-runtime-tmux/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-scm-github/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-terminal-web/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-tracker-github/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-webhook/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-workspace-worktree/package.json +1 -1
- package/package.json +21 -21
- package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/packages/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/standalone/packages/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/packages/web/.next/routes-manifest.json +8 -0
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route/build-manifest.json +11 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js +10 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.map +5 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.nft.json +1 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/keys/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/send/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/730ea_web__next-internal_server_app_api_sessions_[id]_feed_route_actions_d88aaa25.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__346d9484._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__53385c40._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__98c6a6f3._.js → [root-of-the-server]__6c10c03b._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__9726f664._.js → [root-of-the-server]__12eb9005._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__6622b514._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__869d9ac0._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__9dc23e5a._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__b388693f._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_b0abbdd9._.js → _20a4007d._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b6d31783._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_0973acf3._.js → _b88bcf2c._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_bf11b471._.js → node_modules_@clerk_nextjs_dist_esm_app-router_78af9fdf._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_c4bad84a._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_f2ebd7a9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_79316445._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_a078c137._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_app_page_tsx_cd282e82._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{packages_web_src_components_dd296b40._.js → packages_web_src_components_3809c507._.js} +1 -1
- package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
- package/web/.next/standalone/packages/web/.next/static/chunks/1e67fbc3874d3f51.js +1 -0
- package/web/.next/{static/chunks/e88fa6d41d743b9e.js → standalone/packages/web/.next/static/chunks/28dd6ef2af62b509.js} +2 -2
- package/web/.next/standalone/packages/web/.next/static/chunks/2b2a24dff50e7dc9.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/483eb2824f5282c7.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/695b7cb206c6dadd.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/860d84e1f09476a4.css +3 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{86bce89c37cf6212.js → c959976264f14eba.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/{4c566fd1e4a92935.js → d9d05e7b540400af.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/e862e73b22fe29c2.js +1 -0
- package/web/.next/{static/chunks/719697e99b51d55b.js → standalone/packages/web/.next/static/chunks/f5d9ad0f62ede339.js} +1 -1
- package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/feed/route.ts +97 -0
- package/web/.next/standalone/packages/web/src/app/page.tsx +3 -4555
- package/web/.next/standalone/packages/web/src/app/sessions/[id]/page.tsx +2 -115
- package/web/.next/standalone/packages/web/src/components/layout/AppShell.tsx +62 -2
- package/web/.next/standalone/packages/web/src/components/layout/TopBar.tsx +13 -11
- package/web/.next/standalone/packages/web/src/components/layout/WorkspaceSidebarPanel.tsx +68 -10
- package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +4571 -0
- package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx +296 -0
- package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +125 -0
- package/web/.next/standalone/packages/web/src/hooks/useSessionFeed.ts +17 -13
- package/web/.next/standalone/packages/web/src/hooks/useSessions.ts +37 -7
- package/web/.next/static/chunks/1e67fbc3874d3f51.js +1 -0
- package/web/.next/{standalone/packages/web/.next/static/chunks/e88fa6d41d743b9e.js → static/chunks/28dd6ef2af62b509.js} +2 -2
- package/web/.next/static/chunks/2b2a24dff50e7dc9.js +1 -0
- package/web/.next/static/chunks/483eb2824f5282c7.js +1 -0
- package/web/.next/static/chunks/695b7cb206c6dadd.js +1 -0
- package/web/.next/static/chunks/860d84e1f09476a4.css +3 -0
- package/web/.next/static/chunks/{86bce89c37cf6212.js → c959976264f14eba.js} +1 -1
- package/web/.next/static/chunks/{4c566fd1e4a92935.js → d9d05e7b540400af.js} +1 -1
- package/web/.next/static/chunks/e862e73b22fe29c2.js +1 -0
- package/web/.next/{standalone/packages/web/.next/static/chunks/719697e99b51d55b.js → static/chunks/f5d9ad0f62ede339.js} +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_8a444735._.js +0 -3
- package/web/.next/standalone/packages/web/.next/static/chunks/1867dba46fcc022e.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/19d7d4e2cfe3261a.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/ab8ef6c7dfc78082.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/b5e2d1ef92e508a0.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/e8283780c26eaa91.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/fe557eb4039d8a8d.css +0 -3
- package/web/.next/static/chunks/1867dba46fcc022e.js +0 -1
- package/web/.next/static/chunks/19d7d4e2cfe3261a.js +0 -1
- package/web/.next/static/chunks/ab8ef6c7dfc78082.js +0 -1
- package/web/.next/static/chunks/b5e2d1ef92e508a0.js +0 -1
- package/web/.next/static/chunks/e8283780c26eaa91.js +0 -1
- package/web/.next/static/chunks/fe557eb4039d8a8d.css +0 -3
- /package/web/.next/standalone/packages/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_buildManifest.js +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_ssgManifest.js +0 -0
- /package/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_buildManifest.js +0 -0
- /package/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_ssgManifest.js +0 -0
package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import {
|
|
5
|
+
ArrowRight,
|
|
6
|
+
FolderGit2,
|
|
7
|
+
FolderKanban,
|
|
8
|
+
GitBranch,
|
|
9
|
+
Layers3,
|
|
10
|
+
Sparkles,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { Button } from "@/components/ui/Button";
|
|
13
|
+
import { Badge } from "@/components/ui/Badge";
|
|
14
|
+
import { Card, CardContent, CardHeader } from "@/components/ui/Card";
|
|
15
|
+
import { getAttentionLevel, type DashboardSession } from "@/lib/types";
|
|
16
|
+
import type { ConfigProject } from "@/hooks/useConfig";
|
|
17
|
+
|
|
18
|
+
type WorkspaceView = "chat" | "board";
|
|
19
|
+
|
|
20
|
+
interface WorkspaceOverviewProps {
|
|
21
|
+
projects: ConfigProject[];
|
|
22
|
+
sessions: DashboardSession[];
|
|
23
|
+
selectedProjectId: string | null;
|
|
24
|
+
workspaceView: WorkspaceView;
|
|
25
|
+
agentCount: number;
|
|
26
|
+
onCreateWorkspace: () => void;
|
|
27
|
+
onSelectProject: (projectId: string | null) => void;
|
|
28
|
+
onSelectSession: (sessionId: string) => void;
|
|
29
|
+
onShowView: (view: WorkspaceView) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type ProjectSummary = {
|
|
33
|
+
id: string;
|
|
34
|
+
description: string | null;
|
|
35
|
+
branch: string;
|
|
36
|
+
totalSessions: number;
|
|
37
|
+
activeSessions: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function formatRelativeTime(isoDate: string): string {
|
|
41
|
+
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
42
|
+
if (!Number.isFinite(diffMs) || diffMs < 60_000) return "Updated now";
|
|
43
|
+
const minutes = Math.floor(diffMs / 60_000);
|
|
44
|
+
if (minutes < 60) return `Updated ${minutes}m ago`;
|
|
45
|
+
const hours = Math.floor(minutes / 60);
|
|
46
|
+
if (hours < 24) return `Updated ${hours}h ago`;
|
|
47
|
+
const days = Math.floor(hours / 24);
|
|
48
|
+
return `Updated ${days}d ago`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getStatusTone(session: DashboardSession): "success" | "warning" | "error" | "outline" {
|
|
52
|
+
const level = getAttentionLevel(session);
|
|
53
|
+
if (level === "merge") return "success";
|
|
54
|
+
if (level === "review" || level === "respond") return "warning";
|
|
55
|
+
if (session.status === "errored" || session.status === "killed") return "error";
|
|
56
|
+
return "outline";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getStatusLabel(session: DashboardSession): string {
|
|
60
|
+
const level = getAttentionLevel(session);
|
|
61
|
+
if (level === "merge") return "Ready";
|
|
62
|
+
if (level === "respond") return "Needs input";
|
|
63
|
+
if (level === "review") return "Review";
|
|
64
|
+
if (level === "pending") return "Pending";
|
|
65
|
+
if (level === "done") return "Done";
|
|
66
|
+
return "Running";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function WorkspaceOverview({
|
|
70
|
+
projects,
|
|
71
|
+
sessions,
|
|
72
|
+
selectedProjectId,
|
|
73
|
+
workspaceView,
|
|
74
|
+
agentCount,
|
|
75
|
+
onCreateWorkspace,
|
|
76
|
+
onSelectProject,
|
|
77
|
+
onSelectSession,
|
|
78
|
+
onShowView,
|
|
79
|
+
}: WorkspaceOverviewProps) {
|
|
80
|
+
const visibleSessions = useMemo(
|
|
81
|
+
() => sessions.filter((session) => session.status !== "archived"),
|
|
82
|
+
[sessions],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const projectSummaries = useMemo<ProjectSummary[]>(() => {
|
|
86
|
+
return projects
|
|
87
|
+
.map((project) => {
|
|
88
|
+
const projectSessions = visibleSessions.filter((session) => session.projectId === project.id);
|
|
89
|
+
const activeSessions = projectSessions.filter((session) => getAttentionLevel(session) !== "done").length;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
id: project.id,
|
|
93
|
+
description: project.description,
|
|
94
|
+
branch: project.defaultBranch || "main",
|
|
95
|
+
totalSessions: projectSessions.length,
|
|
96
|
+
activeSessions,
|
|
97
|
+
};
|
|
98
|
+
})
|
|
99
|
+
.sort((left, right) => right.activeSessions - left.activeSessions || right.totalSessions - left.totalSessions || left.id.localeCompare(right.id));
|
|
100
|
+
}, [projects, visibleSessions]);
|
|
101
|
+
|
|
102
|
+
const recentSessions = useMemo(() => {
|
|
103
|
+
return [...visibleSessions]
|
|
104
|
+
.sort((left, right) => new Date(right.lastActivityAt).getTime() - new Date(left.lastActivityAt).getTime())
|
|
105
|
+
.slice(0, 5);
|
|
106
|
+
}, [visibleSessions]);
|
|
107
|
+
|
|
108
|
+
const activeSessions = visibleSessions.filter((session) => getAttentionLevel(session) !== "done").length;
|
|
109
|
+
const needsAttention = visibleSessions.filter((session) => {
|
|
110
|
+
const level = getAttentionLevel(session);
|
|
111
|
+
return level === "merge" || level === "respond" || level === "review";
|
|
112
|
+
}).length;
|
|
113
|
+
const mergeReady = visibleSessions.filter((session) => getAttentionLevel(session) === "merge").length;
|
|
114
|
+
const selectedProject = projectSummaries.find((project) => project.id === selectedProjectId) ?? null;
|
|
115
|
+
|
|
116
|
+
const statCards = [
|
|
117
|
+
{ label: "Projects", value: String(projects.length), icon: FolderGit2 },
|
|
118
|
+
{ label: "Active sessions", value: String(activeSessions), icon: Layers3 },
|
|
119
|
+
{ label: "Need attention", value: String(needsAttention), icon: Sparkles },
|
|
120
|
+
{ label: "Merge ready", value: String(mergeReady), icon: GitBranch },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="border-b border-[var(--vk-border)] bg-[linear-gradient(180deg,rgba(255,255,255,0.03),rgba(255,255,255,0))]">
|
|
125
|
+
<div className="mx-auto flex w-full max-w-[1440px] flex-col gap-4 px-3 py-3 sm:px-4">
|
|
126
|
+
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
127
|
+
<div className="max-w-3xl">
|
|
128
|
+
<p className="text-[11px] uppercase tracking-[0.12em] text-[var(--vk-text-muted)]">
|
|
129
|
+
Frontend Control Surface
|
|
130
|
+
</p>
|
|
131
|
+
<h1 className="mt-1 text-[24px] font-semibold leading-[1.05] tracking-[-0.04em] text-[var(--vk-text-strong)] sm:text-[30px]">
|
|
132
|
+
Operate workspaces, sessions, and agents from one surface.
|
|
133
|
+
</h1>
|
|
134
|
+
<p className="mt-2 max-w-2xl text-[13px] leading-6 text-[var(--vk-text-muted)]">
|
|
135
|
+
The current design language stays intact, but the workspace entrypoint now exposes status, recent activity,
|
|
136
|
+
and project context without forcing you into a blank composer first.
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
141
|
+
<div className="inline-flex rounded-[6px] border border-[var(--vk-border)] p-1">
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
onClick={() => onShowView("chat")}
|
|
145
|
+
className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
|
|
146
|
+
workspaceView === "chat"
|
|
147
|
+
? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
|
|
148
|
+
: "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
|
|
149
|
+
}`}
|
|
150
|
+
>
|
|
151
|
+
Chat launchpad
|
|
152
|
+
</button>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
onClick={() => onShowView("board")}
|
|
156
|
+
className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
|
|
157
|
+
workspaceView === "board"
|
|
158
|
+
? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
|
|
159
|
+
: "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
|
|
160
|
+
}`}
|
|
161
|
+
>
|
|
162
|
+
Board view
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<Button variant="outline" size="md" onClick={onCreateWorkspace}>
|
|
167
|
+
Add workspace
|
|
168
|
+
</Button>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
173
|
+
{statCards.map(({ label, value, icon: Icon }) => (
|
|
174
|
+
<Card key={label} className="bg-[color:color-mix(in_srgb,var(--vk-bg-panel)_86%,transparent)]">
|
|
175
|
+
<CardContent className="flex items-center gap-3 py-3">
|
|
176
|
+
<span className="inline-flex h-10 w-10 items-center justify-center rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-normal)]">
|
|
177
|
+
<Icon className="h-4 w-4" />
|
|
178
|
+
</span>
|
|
179
|
+
<div>
|
|
180
|
+
<p className="text-[11px] uppercase tracking-[0.08em] text-[var(--vk-text-muted)]">{label}</p>
|
|
181
|
+
<p className="mt-1 text-[22px] font-semibold text-[var(--vk-text-strong)]">{value}</p>
|
|
182
|
+
</div>
|
|
183
|
+
</CardContent>
|
|
184
|
+
</Card>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div className="grid gap-3 xl:grid-cols-[1.35fr_0.95fr]">
|
|
189
|
+
<Card>
|
|
190
|
+
<CardHeader className="justify-between">
|
|
191
|
+
<div>
|
|
192
|
+
<p className="text-[14px] font-semibold text-[var(--vk-text-strong)]">Recent sessions</p>
|
|
193
|
+
<p className="text-[12px] text-[var(--vk-text-muted)]">Jump back into active work without hunting through the sidebar.</p>
|
|
194
|
+
</div>
|
|
195
|
+
<Badge variant="outline">{visibleSessions.length}</Badge>
|
|
196
|
+
</CardHeader>
|
|
197
|
+
<CardContent className="space-y-2">
|
|
198
|
+
{recentSessions.length === 0 ? (
|
|
199
|
+
<p className="text-[13px] text-[var(--vk-text-muted)]">No sessions yet. Create one from the launchpad below.</p>
|
|
200
|
+
) : recentSessions.map((session) => (
|
|
201
|
+
<button
|
|
202
|
+
key={session.id}
|
|
203
|
+
type="button"
|
|
204
|
+
onClick={() => onSelectSession(session.id)}
|
|
205
|
+
className="flex w-full items-center gap-3 rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] px-3 py-3 text-left transition-colors hover:bg-[var(--vk-bg-hover)]"
|
|
206
|
+
>
|
|
207
|
+
<div className="min-w-0 flex-1">
|
|
208
|
+
<div className="flex items-center gap-2">
|
|
209
|
+
<p className="truncate text-[14px] font-medium text-[var(--vk-text-strong)]">
|
|
210
|
+
{session.summary?.trim() || session.projectId}
|
|
211
|
+
</p>
|
|
212
|
+
<Badge variant={getStatusTone(session)}>{getStatusLabel(session)}</Badge>
|
|
213
|
+
</div>
|
|
214
|
+
<p className="mt-1 truncate text-[12px] text-[var(--vk-text-muted)]">
|
|
215
|
+
{session.projectId}
|
|
216
|
+
{session.branch ? ` · ${session.branch}` : ""}
|
|
217
|
+
{session.issueId ? ` · ${session.issueId}` : ""}
|
|
218
|
+
</p>
|
|
219
|
+
</div>
|
|
220
|
+
<div className="shrink-0 text-right">
|
|
221
|
+
<p className="text-[11px] text-[var(--vk-text-muted)]">{formatRelativeTime(session.lastActivityAt)}</p>
|
|
222
|
+
<ArrowRight className="ml-auto mt-2 h-4 w-4 text-[var(--vk-text-muted)]" />
|
|
223
|
+
</div>
|
|
224
|
+
</button>
|
|
225
|
+
))}
|
|
226
|
+
</CardContent>
|
|
227
|
+
</Card>
|
|
228
|
+
|
|
229
|
+
<Card>
|
|
230
|
+
<CardHeader className="justify-between">
|
|
231
|
+
<div>
|
|
232
|
+
<p className="text-[14px] font-semibold text-[var(--vk-text-strong)]">Project focus</p>
|
|
233
|
+
<p className="text-[12px] text-[var(--vk-text-muted)]">
|
|
234
|
+
{selectedProject ? `Selected: ${selectedProject.id}` : "Select a project to scope new work."}
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
<Badge variant="outline">{agentCount} agents</Badge>
|
|
238
|
+
</CardHeader>
|
|
239
|
+
<CardContent className="space-y-2">
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
onClick={() => onSelectProject(null)}
|
|
243
|
+
className={`flex w-full items-center justify-between rounded-[6px] border px-3 py-2 text-left ${
|
|
244
|
+
selectedProjectId === null
|
|
245
|
+
? "border-[var(--vk-border)] bg-[var(--vk-bg-hover)] text-[var(--vk-text-strong)]"
|
|
246
|
+
: "border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
|
|
247
|
+
}`}
|
|
248
|
+
>
|
|
249
|
+
<span>All projects</span>
|
|
250
|
+
<Badge variant="outline">{visibleSessions.length}</Badge>
|
|
251
|
+
</button>
|
|
252
|
+
|
|
253
|
+
{projectSummaries.length === 0 ? (
|
|
254
|
+
<p className="text-[13px] text-[var(--vk-text-muted)]">No configured projects yet.</p>
|
|
255
|
+
) : projectSummaries.slice(0, 6).map((project) => (
|
|
256
|
+
<button
|
|
257
|
+
key={project.id}
|
|
258
|
+
type="button"
|
|
259
|
+
onClick={() => onSelectProject(project.id)}
|
|
260
|
+
className={`flex w-full items-start justify-between gap-3 rounded-[6px] border px-3 py-2.5 text-left ${
|
|
261
|
+
selectedProjectId === project.id
|
|
262
|
+
? "border-[var(--vk-border)] bg-[var(--vk-bg-hover)]"
|
|
263
|
+
: "border-[var(--vk-border)] bg-[var(--vk-bg-main)] hover:bg-[var(--vk-bg-hover)]"
|
|
264
|
+
}`}
|
|
265
|
+
>
|
|
266
|
+
<div className="min-w-0">
|
|
267
|
+
<p className="truncate text-[13px] font-medium text-[var(--vk-text-strong)]">{project.id}</p>
|
|
268
|
+
<p className="truncate text-[12px] text-[var(--vk-text-muted)]">
|
|
269
|
+
{project.description?.trim() || `Default branch: ${project.branch}`}
|
|
270
|
+
</p>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="shrink-0 text-right">
|
|
273
|
+
<p className="text-[12px] text-[var(--vk-text-strong)]">{project.activeSessions} active</p>
|
|
274
|
+
<p className="text-[11px] text-[var(--vk-text-muted)]">{project.totalSessions} total</p>
|
|
275
|
+
</div>
|
|
276
|
+
</button>
|
|
277
|
+
))}
|
|
278
|
+
|
|
279
|
+
{selectedProject ? (
|
|
280
|
+
<div className="rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] px-3 py-3">
|
|
281
|
+
<div className="flex items-center gap-2">
|
|
282
|
+
<FolderKanban className="h-4 w-4 text-[var(--vk-text-muted)]" />
|
|
283
|
+
<p className="text-[13px] font-medium text-[var(--vk-text-strong)]">{selectedProject.id}</p>
|
|
284
|
+
</div>
|
|
285
|
+
<p className="mt-2 text-[12px] leading-5 text-[var(--vk-text-muted)]">
|
|
286
|
+
{selectedProject.description?.trim() || "This project is ready to launch work from chat or board mode."}
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
289
|
+
) : null}
|
|
290
|
+
</CardContent>
|
|
291
|
+
</Card>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useParams, useRouter } from "next/navigation";
|
|
5
|
+
import { AppShell } from "@/components/layout/AppShell";
|
|
6
|
+
import { TopBar } from "@/components/layout/TopBar";
|
|
7
|
+
import { WorkspaceSidebarPanel } from "@/components/layout/WorkspaceSidebarPanel";
|
|
8
|
+
import { SessionDetail } from "@/components/sessions/SessionDetail";
|
|
9
|
+
import { useConfig } from "@/hooks/useConfig";
|
|
10
|
+
import { useSessions } from "@/hooks/useSessions";
|
|
11
|
+
import type { DashboardSession } from "@/lib/types";
|
|
12
|
+
|
|
13
|
+
export default function SessionPageClient() {
|
|
14
|
+
const params = useParams<{ id: string }>();
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const { projects } = useConfig();
|
|
17
|
+
const { sessions, refresh } = useSessions();
|
|
18
|
+
const dashboardSessions = sessions as unknown as DashboardSession[];
|
|
19
|
+
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
20
|
+
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (typeof window === "undefined") return;
|
|
24
|
+
if (window.innerWidth < 1024) {
|
|
25
|
+
setSidebarOpen(false);
|
|
26
|
+
}
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const selectedSession = useMemo(
|
|
30
|
+
() => dashboardSessions.find((session) => session.id === params.id) ?? null,
|
|
31
|
+
[dashboardSessions, params.id],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (selectedSession?.projectId) {
|
|
36
|
+
setSelectedProjectId(selectedSession.projectId);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!selectedProjectId && projects.length > 0) {
|
|
40
|
+
setSelectedProjectId(projects[0]?.id ?? null);
|
|
41
|
+
}
|
|
42
|
+
}, [projects, selectedProjectId, selectedSession?.projectId]);
|
|
43
|
+
|
|
44
|
+
const toggleSidebar = () => setSidebarOpen((prev) => !prev);
|
|
45
|
+
|
|
46
|
+
const closeSidebarOnMobile = () => {
|
|
47
|
+
if (typeof window !== "undefined" && window.innerWidth < 1024) {
|
|
48
|
+
setSidebarOpen(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
async function handleArchiveSession(sessionId: string) {
|
|
53
|
+
let res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/archive`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
});
|
|
56
|
+
let data = (await res.json().catch(() => null)) as
|
|
57
|
+
| { ok?: boolean; error?: string }
|
|
58
|
+
| null;
|
|
59
|
+
|
|
60
|
+
if (res.status === 404) {
|
|
61
|
+
res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/actions`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "Content-Type": "application/json" },
|
|
64
|
+
body: JSON.stringify({ action: "archive" }),
|
|
65
|
+
});
|
|
66
|
+
data = (await res.json().catch(() => null)) as
|
|
67
|
+
| { ok?: boolean; error?: string }
|
|
68
|
+
| null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
throw new Error(data?.error ?? `Failed to archive session: ${res.status}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (sessionId === params.id) {
|
|
76
|
+
router.push("/");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await refresh();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!params.id) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="flex h-dvh min-h-[100dvh] items-center justify-center">
|
|
86
|
+
<span className="text-[13px] text-[var(--text-muted)]">No session ID provided</span>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<AppShell
|
|
93
|
+
sidebarOpen={sidebarOpen}
|
|
94
|
+
onToggleSidebar={toggleSidebar}
|
|
95
|
+
sidebar={
|
|
96
|
+
<WorkspaceSidebarPanel
|
|
97
|
+
orgLabel="conductor-oss"
|
|
98
|
+
projects={projects}
|
|
99
|
+
selectedProjectId={selectedProjectId}
|
|
100
|
+
onSelectProject={(projectId) => {
|
|
101
|
+
setSelectedProjectId(projectId);
|
|
102
|
+
}}
|
|
103
|
+
sessions={dashboardSessions}
|
|
104
|
+
selectedSessionId={params.id}
|
|
105
|
+
onSelectSession={(sessionId) => {
|
|
106
|
+
router.push(`/sessions/${encodeURIComponent(sessionId)}?tab=chat`);
|
|
107
|
+
closeSidebarOnMobile();
|
|
108
|
+
}}
|
|
109
|
+
onArchiveSession={handleArchiveSession}
|
|
110
|
+
onCreateWorkspace={() => {
|
|
111
|
+
router.push("/");
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
}
|
|
115
|
+
>
|
|
116
|
+
<TopBar
|
|
117
|
+
session={selectedSession}
|
|
118
|
+
fallbackTitle={selectedSession?.projectId ?? "Session"}
|
|
119
|
+
/>
|
|
120
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
121
|
+
<SessionDetail sessionId={params.id} />
|
|
122
|
+
</div>
|
|
123
|
+
</AppShell>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -4,9 +4,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
4
4
|
import type { NormalizedChatEntry } from "@/lib/chatFeed";
|
|
5
5
|
import { subscribeToSnapshotEvents } from "@/lib/liveEvents";
|
|
6
6
|
import { TERMINAL_STATUSES, type SSESnapshotEvent } from "@/lib/types";
|
|
7
|
-
const ACTIVE_POLL_INTERVAL_MS =
|
|
8
|
-
const HIDDEN_POLL_INTERVAL_MS =
|
|
9
|
-
const TERMINAL_POLL_INTERVAL_MS =
|
|
7
|
+
const ACTIVE_POLL_INTERVAL_MS = 4_000;
|
|
8
|
+
const HIDDEN_POLL_INTERVAL_MS = 15_000;
|
|
9
|
+
const TERMINAL_POLL_INTERVAL_MS = 30_000;
|
|
10
10
|
|
|
11
11
|
interface SessionFeedResponse {
|
|
12
12
|
entries?: NormalizedChatEntry[];
|
|
@@ -209,26 +209,29 @@ export function useSessionFeed(sessionId: string | null | undefined): UseSession
|
|
|
209
209
|
window.addEventListener("focus", handleWindowFocus);
|
|
210
210
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
211
211
|
|
|
212
|
-
let
|
|
212
|
+
let pollTimeoutId: number | null = null;
|
|
213
|
+
|
|
214
|
+
const stopPolling = () => {
|
|
215
|
+
if (pollTimeoutId === null) return;
|
|
216
|
+
window.clearTimeout(pollTimeoutId);
|
|
217
|
+
pollTimeoutId = null;
|
|
218
|
+
};
|
|
213
219
|
|
|
214
220
|
const startPolling = () => {
|
|
215
|
-
|
|
221
|
+
stopPolling();
|
|
216
222
|
const interval = terminalRef.current
|
|
217
223
|
? TERMINAL_POLL_INTERVAL_MS
|
|
218
224
|
: document.visibilityState === "visible"
|
|
219
225
|
? ACTIVE_POLL_INTERVAL_MS
|
|
220
226
|
: HIDDEN_POLL_INTERVAL_MS;
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
pollTimeoutId = window.setTimeout(async () => {
|
|
228
|
+
await refresh();
|
|
229
|
+
if (mountedRef.current) {
|
|
230
|
+
startPolling();
|
|
231
|
+
}
|
|
223
232
|
}, interval);
|
|
224
233
|
};
|
|
225
234
|
|
|
226
|
-
const stopPolling = () => {
|
|
227
|
-
if (intervalId === null) return;
|
|
228
|
-
window.clearInterval(intervalId);
|
|
229
|
-
intervalId = null;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
235
|
startPolling();
|
|
233
236
|
const unsubscribe = subscribeToSnapshotEvents((payload: SSESnapshotEvent) => {
|
|
234
237
|
if (!mountedRef.current) return;
|
|
@@ -247,6 +250,7 @@ export function useSessionFeed(sessionId: string | null | undefined): UseSession
|
|
|
247
250
|
}
|
|
248
251
|
snapshotSignatureRef.current = signature;
|
|
249
252
|
void refresh();
|
|
253
|
+
startPolling();
|
|
250
254
|
});
|
|
251
255
|
|
|
252
256
|
return () => {
|
|
@@ -18,6 +18,9 @@ interface UseSessionsReturn {
|
|
|
18
18
|
refresh: () => Promise<void>;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const ACTIVE_FALLBACK_POLL_INTERVAL_MS = 15_000;
|
|
22
|
+
const HIDDEN_FALLBACK_POLL_INTERVAL_MS = 45_000;
|
|
23
|
+
|
|
21
24
|
function sessionsEqual(left: Session[], right: Session[]): boolean {
|
|
22
25
|
if (left === right) return true;
|
|
23
26
|
if (left.length !== right.length) return false;
|
|
@@ -90,19 +93,28 @@ export function useSessions(projectId?: string | null): UseSessionsReturn {
|
|
|
90
93
|
|
|
91
94
|
let pollingId: number | null = null;
|
|
92
95
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}, 3000);
|
|
98
|
-
};
|
|
96
|
+
const getPollDelay = () =>
|
|
97
|
+
document.visibilityState === "visible"
|
|
98
|
+
? ACTIVE_FALLBACK_POLL_INTERVAL_MS
|
|
99
|
+
: HIDDEN_FALLBACK_POLL_INTERVAL_MS;
|
|
99
100
|
|
|
100
101
|
const stopPolling = () => {
|
|
101
102
|
if (pollingId === null) return;
|
|
102
|
-
window.
|
|
103
|
+
window.clearTimeout(pollingId);
|
|
103
104
|
pollingId = null;
|
|
104
105
|
};
|
|
105
106
|
|
|
107
|
+
const startPolling = () => {
|
|
108
|
+
stopPolling();
|
|
109
|
+
pollingId = window.setTimeout(async () => {
|
|
110
|
+
if (!mountedRef.current) return;
|
|
111
|
+
await fetchSessions();
|
|
112
|
+
if (mountedRef.current) {
|
|
113
|
+
startPolling();
|
|
114
|
+
}
|
|
115
|
+
}, getPollDelay());
|
|
116
|
+
};
|
|
117
|
+
|
|
106
118
|
startPolling();
|
|
107
119
|
const unsubscribe = subscribeToSnapshotEvents((data: SSESnapshotEvent) => {
|
|
108
120
|
if (!mountedRef.current) return;
|
|
@@ -112,10 +124,28 @@ export function useSessions(projectId?: string | null): UseSessionsReturn {
|
|
|
112
124
|
setLoading(false);
|
|
113
125
|
});
|
|
114
126
|
|
|
127
|
+
const refresh = () => {
|
|
128
|
+
void fetchSessions();
|
|
129
|
+
startPolling();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const handleVisibilityChange = () => {
|
|
133
|
+
if (document.visibilityState === "visible") {
|
|
134
|
+
refresh();
|
|
135
|
+
} else {
|
|
136
|
+
startPolling();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
window.addEventListener("focus", refresh);
|
|
141
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
142
|
+
|
|
115
143
|
return () => {
|
|
116
144
|
mountedRef.current = false;
|
|
117
145
|
unsubscribe();
|
|
118
146
|
stopPolling();
|
|
147
|
+
window.removeEventListener("focus", refresh);
|
|
148
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
119
149
|
};
|
|
120
150
|
}, [applySessions, fetchSessions, projectId]);
|
|
121
151
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,69971,e=>{"use strict";var c=e.i(95187);let r=(0,c.createServerReference)("00c0a601f70ee423bfba0ca90a896e1583105820a8",c.callServer,void 0,c.findSourceMapURL,"createOrReadKeylessAction");e.s(["createOrReadKeylessAction",()=>r])},23151,e=>{"use strict";e.s([],29135),e.i(29135);var c=e.i(95187);let r=(0,c.createServerReference)("401ec4cb0b784bfb9223c4bd7b67f802fc28f88168",c.callServer,void 0,c.findSourceMapURL,"syncKeylessConfigAction");var t=e.i(69971);let s=(0,c.createServerReference)("006b576f68969d56679602f2efafd04f16e06733df",c.callServer,void 0,c.findSourceMapURL,"deleteKeylessAction");var i=e.i(51688);e.s(["createOrReadKeylessAction",()=>t.createOrReadKeylessAction,"deleteKeylessAction",()=>s,"detectKeylessEnvDriftAction",()=>i.detectKeylessEnvDriftAction,"syncKeylessConfigAction",()=>r],23151)}]);
|