@nextclaw/ui 0.22.5 → 0.24.0-beta.0

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/assets/{api-aEu9cEI8.js → api-CoqPRKmF.js} +5 -5
  3. package/dist/assets/{appearance-settings-page-z4DaHc_E.js → appearance-settings-page-CPoFZLw8.js} +1 -1
  4. package/dist/assets/{book-open-DYRapW_h.js → book-open-jcUt0rr9.js} +1 -1
  5. package/dist/assets/{channels-list-page-x07sGqsT.js → channels-list-page-anMJAkfK.js} +2 -2
  6. package/dist/assets/{config-split-page-DJDp86Ne.js → config-split-page-AcuFnbIC.js} +1 -1
  7. package/dist/assets/{confirm-dialog-BALTnP33.js → confirm-dialog-B-atCfC5.js} +1 -1
  8. package/dist/assets/{createLucideIcon-CgM0rnsa.js → createLucideIcon-Blz0FzrV.js} +1 -1
  9. package/dist/assets/{desktop-capabilities-page-4-v3wKLV.js → desktop-capabilities-page-8lKF_Ob-.js} +1 -1
  10. package/dist/assets/desktop-update-config-DABXDpbj.js +1 -0
  11. package/dist/assets/{dist-BjYvUhPL.js → dist-D8kcEpTf.js} +1 -1
  12. package/dist/assets/{dist-N9zc5bkM.js → dist-eH-_j8yU.js} +1 -1
  13. package/dist/assets/doc-browser-DJEMr7Zk.js +1 -0
  14. package/dist/assets/{doc-browser-DwRLokCI.js → doc-browser-DzXRehlW.js} +1 -1
  15. package/dist/assets/{doc-browser-context-BDuypObS.js → doc-browser-context-BlbUcg2J.js} +1 -1
  16. package/dist/assets/{extensions-DFQXmSwG.js → extensions-mquwAJ2Z.js} +1 -1
  17. package/dist/assets/{form-actions-CzGmrPAv.js → form-actions-CaZDGEmB.js} +1 -1
  18. package/dist/assets/index-DyR5iR0O.css +1 -0
  19. package/dist/assets/{index-zuqzAvNx.js → index-XxXj8WDS.js} +10 -10
  20. package/dist/assets/loader-circle-0qPOOYQ5.js +1 -0
  21. package/dist/assets/{mcp-marketplace-page-COUopIOc.js → mcp-marketplace-page-gFtgyueL.js} +1 -1
  22. package/dist/assets/mcp-marketplace-page-uanamJ6L.js +1 -0
  23. package/dist/assets/{model-config-page-g9_-hZqZ.js → model-config-page-hFBpeRAO.js} +1 -1
  24. package/dist/assets/{navigation-link-Ci4Z7Phi.js → navigation-link-Hb5Z0UxZ.js} +1 -1
  25. package/dist/assets/plus-CYOvNvcV.js +1 -0
  26. package/dist/assets/privacy-settings-page-C18zo3ox.js +1 -0
  27. package/dist/assets/projects-C8Gm9RS8.js +1 -0
  28. package/dist/assets/providers-config-page-YU0-AOXJ.js +1 -0
  29. package/dist/assets/react-DF-kP7iN.js +8 -0
  30. package/dist/assets/{refresh-cw-C-3_thgr.js → refresh-cw-ZHKfE3yx.js} +1 -1
  31. package/dist/assets/remote-B_utkBra.js +1 -0
  32. package/dist/assets/{rotate-cw-DSFTq_yu.js → rotate-cw-CLhOXGwE.js} +1 -1
  33. package/dist/assets/runtime-config-page-Dmy1A_0Y.js +1 -0
  34. package/dist/assets/{save-CpPr78_S.js → save-CWuuZs77.js} +1 -1
  35. package/dist/assets/{search-msqk2-rd.js → search-DcmsKAn2.js} +1 -1
  36. package/dist/assets/{search-config-page-BqoDbKpM.js → search-config-page-CxBacx6W.js} +1 -1
  37. package/dist/assets/{secrets-config-page-GsahsEVw.js → secrets-config-page-B4py8-KR.js} +2 -2
  38. package/dist/assets/security-config-CTP4IQWw.js +1 -0
  39. package/dist/assets/{select-DPKCWajZ.js → select-BEr0Iy_f.js} +1 -1
  40. package/dist/assets/skeleton-B2NHluWf.js +1 -0
  41. package/dist/assets/{tag-chip-ClBWeQ4p.js → tag-chip-ByqDkn6g.js} +1 -1
  42. package/dist/assets/x-CENkFyK5.js +1 -0
  43. package/dist/index.html +21 -21
  44. package/package.json +6 -6
  45. package/src/app/configs/__tests__/app-navigation.config.test.ts +20 -0
  46. package/src/app/configs/app-navigation.config.ts +10 -0
  47. package/src/app/index.tsx +8 -1
  48. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +29 -0
  49. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +4 -2
  50. package/src/features/chat/components/layout/__tests__/chat-page-shell.test.tsx +21 -0
  51. package/src/features/chat/components/layout/chat-page-shell.tsx +19 -4
  52. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +1 -1
  53. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +0 -1
  54. package/src/features/chat/features/conversation/utils/__tests__/session-message-composer.utils.test.ts +11 -4
  55. package/src/features/chat/features/conversation/utils/__tests__/session-queued-input.utils.test.ts +0 -1
  56. package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +10 -11
  57. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +1 -0
  58. package/src/features/chat/features/message/hooks/use-chat-message-actions.tsx +0 -2
  59. package/src/features/chat/features/runtime/utils/chat-runtime.utils.ts +0 -14
  60. package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +70 -22
  61. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +79 -32
  62. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +2 -0
  63. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +0 -6
  64. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +2 -0
  65. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +1 -0
  66. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +27 -0
  67. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +2 -14
  68. package/src/features/chat/types/chat-run.types.ts +0 -1
  69. package/src/features/panel-apps/managers/__tests__/panel-app-bridge.manager.test.ts +51 -0
  70. package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +37 -2
  71. package/src/features/projects/AGENTS.md +6 -0
  72. package/src/features/projects/components/project-agreement.tsx +92 -0
  73. package/src/features/projects/components/project-artifacts.test.tsx +77 -0
  74. package/src/features/projects/components/project-artifacts.tsx +188 -0
  75. package/src/features/projects/components/project-overview.tsx +150 -0
  76. package/src/features/projects/components/project-requests.tsx +57 -0
  77. package/src/features/projects/components/project-runs.tsx +60 -0
  78. package/src/features/projects/components/project-section.tsx +60 -0
  79. package/src/features/projects/components/project-skills.tsx +53 -0
  80. package/src/features/projects/components/project-work-items.test.tsx +78 -0
  81. package/src/features/projects/components/project-work-items.tsx +272 -0
  82. package/src/features/projects/hooks/use-project-observation.test.tsx +93 -0
  83. package/src/features/projects/hooks/use-project-observation.ts +85 -0
  84. package/src/features/projects/index.ts +2 -0
  85. package/src/features/projects/pages/project-home-page.tsx +253 -0
  86. package/src/features/projects/pages/projects-page.test.tsx +366 -0
  87. package/src/features/projects/presenters/project-home.presenter.test.ts +74 -0
  88. package/src/features/projects/presenters/project-home.presenter.ts +55 -0
  89. package/src/features/projects/utils/project-artifact-view.utils.test.ts +63 -0
  90. package/src/features/projects/utils/project-artifact-view.utils.ts +62 -0
  91. package/src/features/projects/utils/project-request-response.utils.test.ts +44 -0
  92. package/src/features/projects/utils/project-request-response.utils.ts +39 -0
  93. package/src/shared/lib/i18n/index.ts +6 -2
  94. package/src/shared/lib/i18n/locales/en-US/projects.json +90 -0
  95. package/src/shared/lib/i18n/locales/zh-CN/projects.json +90 -0
  96. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +6 -0
  97. package/src/shared/lib/ui-document-title/index.ts +1 -0
  98. package/dist/assets/desktop-update-config-Cl-_XHqU.js +0 -1
  99. package/dist/assets/doc-browser-JlXVLNRe.js +0 -1
  100. package/dist/assets/index-CwCTiMM9.css +0 -1
  101. package/dist/assets/loader-circle-D9etMT9f.js +0 -1
  102. package/dist/assets/mcp-marketplace-page-DxPPmYqh.js +0 -1
  103. package/dist/assets/plus-DKGD04AO.js +0 -1
  104. package/dist/assets/privacy-settings-page-pNhQQT4r.js +0 -1
  105. package/dist/assets/providers-config-page-DftW_6KU.js +0 -1
  106. package/dist/assets/react-CVFx225w.js +0 -8
  107. package/dist/assets/remote-DS2TkMPm.js +0 -1
  108. package/dist/assets/runtime-config-page-Cd9sOYvn.js +0 -1
  109. package/dist/assets/security-config-BnHQyrtG.js +0 -1
  110. package/dist/assets/skeleton-BsR5rwB7.js +0 -1
  111. package/dist/assets/x-Dp5GAFmP.js +0 -1
@@ -0,0 +1,85 @@
1
+ import { useEffect } from "react";
2
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
3
+ import { NcpEventType, type NcpEndpointEvent } from "@nextclaw/ncp";
4
+ import { eventKeys, type ProjectObservationSnapshot } from "@nextclaw/client-sdk";
5
+ import { nextclawClient } from "@/shared/lib/api";
6
+
7
+ export const projectObservationQueryKey = (projectId: string) => [
8
+ "project-observation",
9
+ projectId,
10
+ ] as const;
11
+
12
+ const REFRESHING_NCP_EVENTS = new Set<NcpEventType>([
13
+ NcpEventType.MessageTextEnd,
14
+ NcpEventType.MessageTextDelta,
15
+ NcpEventType.MessageCompleted,
16
+ NcpEventType.MessageFailed,
17
+ NcpEventType.MessageAbort,
18
+ NcpEventType.RunStarted,
19
+ NcpEventType.RunFinished,
20
+ NcpEventType.RunError,
21
+ ]);
22
+
23
+ const readNcpEventSessionId = (event: NcpEndpointEvent): string | null => {
24
+ if (!("payload" in event) || !event.payload || typeof event.payload !== "object" || !("sessionId" in event.payload)) {
25
+ return null;
26
+ }
27
+ const { sessionId } = event.payload;
28
+ return typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : null;
29
+ };
30
+
31
+ const readProjectRoot = (metadata: Record<string, unknown> | undefined): string | null => {
32
+ const value = metadata?.project_root ?? metadata?.projectRoot;
33
+ return typeof value === "string" && value.trim() ? value.trim() : null;
34
+ };
35
+
36
+ export function useProjectObservation(projectId: string | null, projectRoot: string | null) {
37
+ const queryClient = useQueryClient();
38
+ useEffect(() => {
39
+ if (!projectId || !projectRoot) return;
40
+ const queryKey = projectObservationQueryKey(projectId);
41
+ let refreshTimer: ReturnType<typeof setTimeout> | null = null;
42
+ const scheduleRefresh = () => {
43
+ if (refreshTimer) return;
44
+ refreshTimer = setTimeout(() => {
45
+ refreshTimer = null;
46
+ void queryClient.invalidateQueries({ queryKey, exact: true });
47
+ }, 100);
48
+ };
49
+ const hasObservedSession = (sessionId: string): boolean => {
50
+ const snapshot = queryClient.getQueryData<ProjectObservationSnapshot>(queryKey);
51
+ return Boolean(
52
+ snapshot?.runs?.some((run) => run.sessionId === sessionId)
53
+ || snapshot?.workItems.some((item) => item.reference.sessionId === sessionId)
54
+ || snapshot?.activity.some((activity) => activity.reference.sessionId === sessionId),
55
+ );
56
+ };
57
+ const unsubscribers = [
58
+ nextclawClient.eventBus.on(eventKeys.ncpEvent, (event) => {
59
+ if (!REFRESHING_NCP_EVENTS.has(event.type)) return;
60
+ const sessionId = readNcpEventSessionId(event);
61
+ if (sessionId && hasObservedSession(sessionId)) scheduleRefresh();
62
+ }),
63
+ nextclawClient.eventBus.on(eventKeys.sessionRunStatus, ({ sessionKey }) => {
64
+ if (hasObservedSession(sessionKey)) scheduleRefresh();
65
+ }),
66
+ nextclawClient.eventBus.on(eventKeys.sessionSummaryUpsert, ({ summary }) => {
67
+ if (readProjectRoot(summary.metadata) === projectRoot) scheduleRefresh();
68
+ }),
69
+ nextclawClient.eventBus.on(eventKeys.sessionSummaryDelete, ({ sessionKey }) => {
70
+ if (hasObservedSession(sessionKey)) scheduleRefresh();
71
+ }),
72
+ ];
73
+ return () => {
74
+ if (refreshTimer) clearTimeout(refreshTimer);
75
+ for (const unsubscribe of unsubscribers) unsubscribe();
76
+ };
77
+ }, [projectId, projectRoot, queryClient]);
78
+
79
+ return useQuery({
80
+ queryKey: projectObservationQueryKey(projectId ?? ""),
81
+ enabled: Boolean(projectId),
82
+ staleTime: 5_000,
83
+ queryFn: async () => await nextclawClient.projects.getObservation(projectId!),
84
+ });
85
+ }
@@ -0,0 +1,2 @@
1
+ /** Public UI boundary for Projects. */
2
+ export { ProjectsPage } from "./pages/project-home-page";
@@ -0,0 +1,253 @@
1
+ import { useState } from "react";
2
+ import { useNavigate, useParams } from "react-router-dom";
3
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
4
+ import type {
5
+ ObservedRequest,
6
+ ProjectObservationSnapshot,
7
+ } from "@nextclaw/client-sdk";
8
+ import { toast } from "sonner";
9
+ import { useProjects } from "@/shared/hooks/use-projects";
10
+ import { t } from "@/shared/lib/i18n";
11
+ import {
12
+ Tabs,
13
+ TabsContent,
14
+ TabsList,
15
+ TabsTrigger,
16
+ } from "@/shared/components/ui/tabs";
17
+ import { ProjectAgreement } from "@/features/projects/components/project-agreement";
18
+ import { ProjectArtifacts } from "@/features/projects/components/project-artifacts";
19
+ import { ProjectOverview } from "@/features/projects/components/project-overview";
20
+ import { ProjectRequests } from "@/features/projects/components/project-requests";
21
+ import { ProjectSkills } from "@/features/projects/components/project-skills";
22
+ import { ProjectWorkItems } from "@/features/projects/components/project-work-items";
23
+ import { ChatConversationWorkspaceSection } from "@/features/chat/components/conversation/chat-conversation-workspace-section";
24
+ import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
25
+ import {
26
+ projectObservationQueryKey,
27
+ useProjectObservation,
28
+ } from "@/features/projects/hooks/use-project-observation";
29
+ import {
30
+ getOpenProjectRequests,
31
+ isProjectHomeTab,
32
+ type ProjectHomeTab,
33
+ } from "@/features/projects/presenters/project-home.presenter";
34
+ import {
35
+ sendProjectRequestResponse,
36
+ type ProjectRequestDecision,
37
+ } from "@/features/projects/utils/project-request-response.utils";
38
+
39
+ const PROJECT_TABS: ProjectHomeTab[] = [
40
+ "overview",
41
+ "work",
42
+ "artifacts",
43
+ "skills",
44
+ "agreement",
45
+ ];
46
+
47
+ function shouldOfferObservationSetup(
48
+ sources: ProjectObservationSnapshot["sources"],
49
+ ): boolean {
50
+ const config = sources.find((source) => source.id === "config");
51
+ return !config || config.status !== "available";
52
+ }
53
+
54
+ function hasObservedProjectSessions(
55
+ sources: ProjectObservationSnapshot["sources"],
56
+ ): boolean {
57
+ return (sources.find((source) => source.id === "sessions")?.itemCount ?? 0) > 0;
58
+ }
59
+
60
+ export function ProjectsPage() {
61
+ const { projectId, tab: tabParam } = useParams<{ projectId?: string; tab?: string }>();
62
+ const navigate = useNavigate();
63
+ const [pendingRequestId, setPendingRequestId] = useState<string | null>(null);
64
+ const queryClient = useQueryClient();
65
+ const presenter = usePresenter();
66
+ const projects = useProjects();
67
+ const registered = projects.data?.projects ?? [];
68
+ const selectedProject = registered.find((project) => project.id === projectId) ?? null;
69
+ const tab: ProjectHomeTab | null = isProjectHomeTab(tabParam) ? tabParam : null;
70
+ const observation = useProjectObservation(
71
+ selectedProject?.id ?? null,
72
+ selectedProject?.rootPath ?? null,
73
+ );
74
+ const response = useMutation({
75
+ mutationFn: sendProjectRequestResponse,
76
+ onMutate: (input) => setPendingRequestId(input.requestId),
77
+ onSuccess: async () => {
78
+ if (selectedProject) {
79
+ await queryClient.invalidateQueries({
80
+ queryKey: projectObservationQueryKey(selectedProject.id),
81
+ });
82
+ }
83
+ toast.success(t("projectsResponseSent"));
84
+ },
85
+ onError: (error) =>
86
+ toast.error(
87
+ `${t("projectsResponseFailed")}: ${error instanceof Error ? error.message : String(error)}`,
88
+ ),
89
+ onSettled: () => setPendingRequestId(null),
90
+ });
91
+
92
+ const reply = (
93
+ request: ObservedRequest,
94
+ decision: ProjectRequestDecision,
95
+ ) => {
96
+ if (!request.reference.sessionId) return;
97
+ response.mutate({
98
+ requestId: request.id,
99
+ sessionId: request.reference.sessionId,
100
+ decision,
101
+ prompt: request.prompt,
102
+ });
103
+ };
104
+
105
+ if (projects.isLoading) {
106
+ return (
107
+ <main className="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
108
+ {t("projectsLoading")}
109
+ </main>
110
+ );
111
+ }
112
+ if (projects.isError) {
113
+ return (
114
+ <main className="p-6 text-sm text-destructive">
115
+ {t("projectsLoadFailed")}: {projects.error.message}
116
+ </main>
117
+ );
118
+ }
119
+ if (registered.length === 0) {
120
+ return (
121
+ <main className="p-6 text-sm text-muted-foreground">
122
+ {t("projectsEmpty")}
123
+ </main>
124
+ );
125
+ }
126
+ if (!selectedProject || !tab) {
127
+ return (
128
+ <main className="p-6 text-sm text-muted-foreground">
129
+ {t("projectsChoose")}
130
+ </main>
131
+ );
132
+ }
133
+
134
+ const snapshot = observation.data;
135
+ const startObservationSetup = () =>
136
+ presenter.chatSessionListManager.createSession({
137
+ projectRoot: selectedProject.rootPath,
138
+ prompt: t("projectsObservationSetupPrompt"),
139
+ });
140
+ const startProjectWork = () =>
141
+ presenter.chatSessionListManager.createSession({
142
+ projectRoot: selectedProject.rootPath,
143
+ prompt: t("projectsStartProjectWorkPrompt"),
144
+ });
145
+ const openProjectFile = (path: string, label: string) =>
146
+ presenter.chatThreadManager.openFilePreview({
147
+ path,
148
+ label,
149
+ viewMode: "preview",
150
+ previewViewer: "rendered",
151
+ });
152
+ return (
153
+ <>
154
+ <main className="h-full min-w-0 flex-1 overflow-y-auto p-4 md:p-6">
155
+ <div className="mx-auto max-w-6xl space-y-4">
156
+ <header className="flex flex-wrap items-center gap-3">
157
+ <h1 className="text-xl font-semibold">{t("projectsTitle")}</h1>
158
+ </header>
159
+
160
+ {observation.isLoading ? (
161
+ <div className="rounded-xl border border-border/60 p-5 text-sm text-muted-foreground">
162
+ {t("projectsLoading")}
163
+ </div>
164
+ ) : null}
165
+ {observation.isError ? (
166
+ <div className="rounded-xl border border-destructive/40 bg-destructive/5 p-5 text-sm text-destructive">
167
+ {t("projectsLoadFailed")}: {observation.error.message}
168
+ </div>
169
+ ) : null}
170
+ {snapshot ? (
171
+ <>
172
+ <section className="rounded-2xl border border-border/60 bg-card p-4">
173
+ <h2 className="text-lg font-semibold">{snapshot.project.name}</h2>
174
+ {snapshot.project.summary ? (
175
+ <p className="mt-1 text-sm text-muted-foreground">
176
+ {snapshot.project.summary}
177
+ </p>
178
+ ) : null}
179
+ <p className="mt-2 break-all text-xs text-muted-foreground">
180
+ {snapshot.project.rootPath}
181
+ </p>
182
+ </section>
183
+ <ProjectRequests
184
+ requests={getOpenProjectRequests(snapshot)}
185
+ pendingRequestId={pendingRequestId}
186
+ onReply={reply}
187
+ />
188
+ <Tabs
189
+ value={tab}
190
+ onValueChange={(value) =>
191
+ navigate(`/projects/${encodeURIComponent(selectedProject.id)}/${value}`)
192
+ }
193
+ >
194
+ <div className="min-w-0 overflow-x-auto pb-1">
195
+ <TabsList className="w-max">
196
+ {PROJECT_TABS.map((key) => (
197
+ <TabsTrigger key={key} value={key}>
198
+ {t(`projects${key[0].toUpperCase()}${key.slice(1)}`)}
199
+ </TabsTrigger>
200
+ ))}
201
+ </TabsList>
202
+ </div>
203
+ <TabsContent value="overview" className="mt-4">
204
+ <ProjectOverview
205
+ hasProjectSessions={hasObservedProjectSessions(snapshot.sources)}
206
+ snapshot={snapshot}
207
+ shouldOfferObservationSetup={shouldOfferObservationSetup(snapshot.sources)}
208
+ onOpenFile={openProjectFile}
209
+ onStartObservationSetup={startObservationSetup}
210
+ onStartProjectWork={startProjectWork}
211
+ />
212
+ </TabsContent>
213
+ <TabsContent value="work" className="mt-4">
214
+ <ProjectWorkItems
215
+ hasProjectSessions={hasObservedProjectSessions(snapshot.sources)}
216
+ snapshot={snapshot}
217
+ shouldOfferObservationSetup={shouldOfferObservationSetup(snapshot.sources)}
218
+ onStartObservationSetup={startObservationSetup}
219
+ onStartProjectWork={startProjectWork}
220
+ />
221
+ </TabsContent>
222
+ <TabsContent value="artifacts" className="mt-4">
223
+ <ProjectArtifacts snapshot={snapshot} onOpenFile={openProjectFile} />
224
+ </TabsContent>
225
+ <TabsContent value="skills" className="mt-4">
226
+ <ProjectSkills
227
+ snapshot={snapshot}
228
+ onOpen={(skill) =>
229
+ presenter.chatThreadManager.openFilePreview({
230
+ path: skill.path,
231
+ label: skill.name,
232
+ viewMode: "preview",
233
+ previewViewer: "rendered",
234
+ })
235
+ }
236
+ />
237
+ </TabsContent>
238
+ <TabsContent value="agreement" className="mt-4">
239
+ <ProjectAgreement snapshot={snapshot} onOpenFile={openProjectFile} />
240
+ </TabsContent>
241
+ </Tabs>
242
+ </>
243
+ ) : null}
244
+ </div>
245
+ </main>
246
+ <ChatConversationWorkspaceSection
247
+ layoutMode="desktop"
248
+ sessionKey={null}
249
+ projectRoot={selectedProject.rootPath}
250
+ />
251
+ </>
252
+ );
253
+ }