@nextclaw/ui 0.15.2 → 0.15.4

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 (212) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/aiden0z-pptx-renderer.es-Bd99vut2.js +94 -0
  3. package/dist/assets/api-BOXOrpcJ.js +13 -0
  4. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +106 -0
  5. package/dist/assets/app-presenter-provider-D3BuuOyZ.css +1 -0
  6. package/dist/assets/appearance-settings-page-fLIJbx1C.js +1 -0
  7. package/dist/assets/{book-open-Ba2f7YiG.js → book-open-DWUjS6aW.js} +1 -1
  8. package/dist/assets/channels-list-page-CvCTJraa.js +8 -0
  9. package/dist/assets/chat-page-B42UUIFR.js +1 -0
  10. package/dist/assets/chunk-DseTPa7n.js +1 -0
  11. package/dist/assets/config-split-page-DBgt9S8b.js +1 -0
  12. package/dist/assets/confirm-dialog-GSXIvoRU.js +5 -0
  13. package/dist/assets/desktop-update-config-7bmNeWIC.js +1 -0
  14. package/dist/assets/dist-DC7f4TJH.js +41 -0
  15. package/dist/assets/doc-browser-CgdoLUT92.js +1 -0
  16. package/dist/assets/doc-browser-D0monHjG.js +1 -0
  17. package/dist/assets/doc-browser-context-CGwEOhBy.js +1 -0
  18. package/dist/assets/docx-preview-eFH9DRsS.js +20 -0
  19. package/dist/assets/download-DiP9zx-J.js +1 -0
  20. package/dist/assets/{external-link-DxuPaDoK.js → external-link-C6ZeSPef.js} +1 -1
  21. package/dist/assets/eye-suvKmtwn.js +1 -0
  22. package/dist/assets/folder-CMELbQGG.js +1 -0
  23. package/dist/assets/{form-actions-BztL7P_T.js → form-actions-CUAzpNe_.js} +1 -1
  24. package/dist/assets/index-BFeNDQ4R.css +1 -0
  25. package/dist/assets/index-D_6U-M94.js +2 -0
  26. package/dist/assets/jszip.min-Das2IAnb.js +2 -0
  27. package/dist/assets/loader-circle-tYHStefV.js +1 -0
  28. package/dist/assets/logo-badge-Cu5UGH_4.js +1 -0
  29. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +9 -0
  30. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +1 -0
  31. package/dist/assets/model-config-page-CWP6pyBm.js +1 -0
  32. package/dist/assets/plus-tywizkeY.js +1 -0
  33. package/dist/assets/popover-DImguRok.js +1 -0
  34. package/dist/assets/provider-models-C_yOh6DE.js +1 -0
  35. package/dist/assets/provider-scoped-model-input-CE7p4SbJ.js +1 -0
  36. package/dist/assets/providers-config-page-BKPo8SjH.js +1 -0
  37. package/dist/assets/react-5sjAT_6d.js +8 -0
  38. package/dist/assets/{refresh-cw-eT8BF-8B.js → refresh-cw-CHLBzWWj.js} +1 -1
  39. package/dist/assets/remote-DXJryUxB.js +1 -0
  40. package/dist/assets/rotate-cw-BefO5brU.js +1 -0
  41. package/dist/assets/runtime-config-page-1Axv4QGu.js +1 -0
  42. package/dist/assets/{save-Cb2g0BOi.js → save-BABzyrjB.js} +1 -1
  43. package/dist/assets/{search-BcHjwdsb.js → search-BTqxG3ZG.js} +1 -1
  44. package/dist/assets/search-config-page-BkKKmxmS.js +1 -0
  45. package/dist/assets/secrets-config-page-CWJMyu-k.js +3 -0
  46. package/dist/assets/security-config-DeQUIXfi.js +1 -0
  47. package/dist/assets/select-TYZDBNmq.js +1 -0
  48. package/dist/assets/setting-row-DR23Z_5I.js +1 -0
  49. package/dist/assets/{settings-2-2DWauOjY.js → settings-2-D8WWcBnw.js} +1 -1
  50. package/dist/assets/{skeleton-P5oM14E1.js → skeleton-CloHeg0S.js} +1 -1
  51. package/dist/assets/tabs-CX-ckWoo.js +1 -0
  52. package/dist/assets/tag-chip-mCc23Rhb.js +1 -0
  53. package/dist/assets/trash-2-SAW1Z4Po.js +1 -0
  54. package/dist/assets/use-config-BucdaK23.js +1 -0
  55. package/dist/assets/use-confirm-dialog-C95F6sT1.js +1 -0
  56. package/dist/assets/use-viewport-layout-C6m3Nte7.js +1 -0
  57. package/dist/assets/x-DtCPHwCu.js +1 -0
  58. package/dist/assets/xlsx-B6zVt1RK.js +64 -0
  59. package/dist/index.html +34 -28
  60. package/package.json +14 -9
  61. package/src/app/components/layout/sidebar-items.tsx +3 -3
  62. package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
  63. package/src/app/components/layout/sidebar.tsx +1 -1
  64. package/src/app/styles/design-system.css +17 -4
  65. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +41 -0
  66. package/src/features/chat/components/chat-popover-content.tsx +26 -0
  67. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
  68. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
  69. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
  70. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
  71. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
  72. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
  73. package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
  74. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  75. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
  76. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +4 -15
  77. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
  78. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +69 -54
  79. package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
  80. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +62 -1
  81. package/src/features/chat/features/message/components/chat-message-list.container.tsx +63 -2
  82. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
  83. package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +11 -1
  84. package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +23 -0
  85. package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +5 -0
  86. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +138 -0
  87. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +37 -0
  88. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -4
  89. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
  90. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
  91. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
  92. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  93. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +5 -5
  94. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +4 -4
  95. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +4 -4
  96. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +463 -99
  97. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
  98. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
  99. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
  100. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +11 -5
  101. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +8 -6
  102. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +99 -67
  103. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
  104. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +56 -6
  105. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +29 -7
  106. package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
  107. package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
  108. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
  109. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
  110. package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
  111. package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
  112. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
  113. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
  114. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +53 -10
  115. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +68 -2
  116. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +21 -0
  117. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +63 -0
  118. package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
  119. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
  120. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +120 -38
  121. package/src/features/chat/index.ts +1 -0
  122. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +76 -0
  123. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +44 -60
  124. package/src/features/chat/managers/chat-thread.manager.ts +123 -124
  125. package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
  126. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +78 -1
  127. package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
  128. package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
  129. package/src/features/chat/stores/chat-thread.store.ts +73 -13
  130. package/src/features/cron/components/cron-config.tsx +63 -80
  131. package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +44 -6
  132. package/src/features/marketplace/components/marketplace-detail-doc.tsx +8 -4
  133. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  134. package/src/features/marketplace/hooks/use-marketplace-detail-doc-entry.ts +84 -0
  135. package/src/features/panel-apps/components/panel-apps-list.tsx +2 -2
  136. package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
  137. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
  138. package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
  139. package/src/features/side-dock/components/side-dock.tsx +3 -3
  140. package/src/index.css +275 -15
  141. package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
  142. package/src/shared/components/actions/action-link.tsx +1 -1
  143. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
  144. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
  145. package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
  146. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
  147. package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
  148. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
  149. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
  150. package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
  151. package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
  152. package/src/shared/components/file-type-icon.tsx +272 -0
  153. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
  154. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
  155. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  156. package/src/shared/components/ui/button.tsx +1 -1
  157. package/src/shared/components/ui/dialog.tsx +1 -1
  158. package/src/shared/components/ui/input.tsx +1 -1
  159. package/src/shared/components/ui/select.tsx +1 -1
  160. package/src/shared/components/ui/switch.tsx +1 -1
  161. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
  162. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
  163. package/src/shared/components/ui/tabs.tsx +1 -1
  164. package/src/shared/components/ui/textarea.tsx +1 -1
  165. package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
  166. package/src/shared/lib/api/utils/server-path.utils.ts +19 -20
  167. package/src/shared/lib/i18n/locales/en-US/chat.json +99 -0
  168. package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
  169. package/src/shared/lib/i18n/locales/zh-CN/chat.json +99 -0
  170. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
  171. package/dist/assets/api-B7Qigvtm.js +0 -13
  172. package/dist/assets/appearance-settings-page-CuE0Iiqj.js +0 -1
  173. package/dist/assets/channels-list-page-CTsM6TcP.js +0 -8
  174. package/dist/assets/chat-page-C_5dcuE3.js +0 -1
  175. package/dist/assets/config-split-page-Fbb4PtTf.js +0 -1
  176. package/dist/assets/confirm-dialog-D2EG3nqE.js +0 -5
  177. package/dist/assets/desktop-update-config-BGR3HUi1.js +0 -1
  178. package/dist/assets/dist-7JTjycxE.js +0 -1
  179. package/dist/assets/dist-wXLhk4hl.js +0 -1
  180. package/dist/assets/doc-browser-bHyXnxlj.js +0 -1
  181. package/dist/assets/doc-browser-context-CYAGm5d5.js +0 -1
  182. package/dist/assets/doc-browser-gTrQ3jCF.js +0 -1
  183. package/dist/assets/es2015-j1SRCIX5.js +0 -41
  184. package/dist/assets/i18n-provider-B4oMxOFx.js +0 -1
  185. package/dist/assets/index-CvrvblgX.css +0 -1
  186. package/dist/assets/index-Obd4JYU2.js +0 -104
  187. package/dist/assets/loader-circle-CdzgK4eH.js +0 -1
  188. package/dist/assets/mcp-marketplace-page-Bt7CuOoq.js +0 -9
  189. package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +0 -1
  190. package/dist/assets/model-config-page-DeKk9gvE.js +0 -1
  191. package/dist/assets/plus-Bx7wQbhG.js +0 -1
  192. package/dist/assets/provider-scoped-model-input-DsH2LF1q.js +0 -1
  193. package/dist/assets/providers-config-page-BFnGp68F.js +0 -1
  194. package/dist/assets/react-UirzUuST.js +0 -8
  195. package/dist/assets/remote-CDkHZtA8.js +0 -1
  196. package/dist/assets/rotate-cw-1JqxHTuw.js +0 -1
  197. package/dist/assets/runtime-config-page-BelAqiNG.js +0 -1
  198. package/dist/assets/search-config-page-zPm6--Ig.js +0 -1
  199. package/dist/assets/secrets-config-page-Cxyp-KuQ.js +0 -3
  200. package/dist/assets/security-config-B_gCq9fn.js +0 -1
  201. package/dist/assets/select-CdSEOwVM.js +0 -1
  202. package/dist/assets/setting-row-CTAtGexr.js +0 -1
  203. package/dist/assets/side-dock-DPvakJ0a.js +0 -3
  204. package/dist/assets/switch-BU_UEULb.js +0 -1
  205. package/dist/assets/tag-chip-D-WsKoT1.js +0 -1
  206. package/dist/assets/tooltip-CjkOaHW7.js +0 -1
  207. package/dist/assets/use-config--hx5oxQn.js +0 -1
  208. package/dist/assets/use-confirm-dialog-CD9a_SK1.js +0 -1
  209. package/dist/assets/x-DT5x45JU.js +0 -1
  210. /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-fDrYfl0l.js} +0 -0
  211. /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-zZvY9U63.js} +0 -0
  212. /package/dist/assets/{middleware-BILWbJE4.js → middleware-BLMHv9YF.js} +0 -0
@@ -1,12 +1,24 @@
1
1
  import type { ResolvedChildSessionTab } from "@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view";
2
2
  import { shouldShowUnreadSessionIndicator } from "@/features/chat/stores/chat-session-list.store";
3
3
  import type {
4
+ ChatWorkspacePanelKind,
4
5
  ChatWorkspaceFileTab,
5
6
  ChatWorkspaceSideChatDraft,
6
7
  } from "@/features/chat/stores/chat-thread.store";
7
8
  import { t } from "@/shared/lib/i18n";
9
+ import {
10
+ resolveAlternateWorkspaceFileViewer,
11
+ resolveWorkspaceFileViewer,
12
+ type ChatWorkspaceFileViewer,
13
+ } from "@/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils";
8
14
 
9
15
  export type WorkspaceSelection =
16
+ | {
17
+ kind: "overview";
18
+ }
19
+ | {
20
+ kind: "child-sessions";
21
+ }
10
22
  | {
11
23
  kind: "child-session";
12
24
  tab: ResolvedChildSessionTab;
@@ -15,6 +27,9 @@ export type WorkspaceSelection =
15
27
  kind: "side-chat-draft";
16
28
  draft: ChatWorkspaceSideChatDraft;
17
29
  }
30
+ | {
31
+ kind: "project-files";
32
+ }
18
33
  | {
19
34
  kind: "file";
20
35
  file: ChatWorkspaceFileTab;
@@ -25,13 +40,20 @@ export type WorkspaceSelection =
25
40
 
26
41
  export type WorkspaceTabViewModel = {
27
42
  key: string;
28
- kind: "child-session" | "side-chat-draft" | "file" | "cron";
43
+ kind: ChatWorkspacePanelKind;
29
44
  title: string;
30
45
  tooltip: string;
31
46
  active: boolean;
32
47
  agentId?: string | null;
48
+ fileName?: string | null;
33
49
  showUnreadDot?: boolean;
34
50
  viewMode?: "preview" | "diff";
51
+ isRenderedPreview?: boolean;
52
+ alternateViewerAction?: {
53
+ label: string;
54
+ viewer: ChatWorkspaceFileViewer;
55
+ onSelect: () => void;
56
+ } | null;
35
57
  onSelect: () => void;
36
58
  onClose?: () => void;
37
59
  };
@@ -42,13 +64,12 @@ export function readWorkspaceFileTitle(file: ChatWorkspaceFileTab): string {
42
64
  }
43
65
 
44
66
  export function resolveWorkspaceSelection(params: {
45
- activePanelKind?: "child-session" | "side-chat-draft" | "file" | "cron" | null;
67
+ activePanelKind?: ChatWorkspacePanelKind | null;
46
68
  activeChildSessionKey: string | null;
47
69
  activeSideChatDraft: ChatWorkspaceSideChatDraft | null;
48
70
  activeWorkspaceFileKey: string | null;
49
71
  childSessionTabs: ResolvedChildSessionTab[];
50
72
  workspaceFileTabs: readonly ChatWorkspaceFileTab[];
51
- sessionCronJobCount: number;
52
73
  }): WorkspaceSelection | null {
53
74
  const {
54
75
  activePanelKind,
@@ -57,10 +78,21 @@ export function resolveWorkspaceSelection(params: {
57
78
  activeWorkspaceFileKey,
58
79
  childSessionTabs,
59
80
  workspaceFileTabs,
60
- sessionCronJobCount,
61
81
  } = params;
62
82
 
63
- if (activePanelKind === "cron" && sessionCronJobCount > 0) {
83
+ if (activePanelKind === "overview") {
84
+ return { kind: "overview" };
85
+ }
86
+
87
+ if (activePanelKind === "child-sessions") {
88
+ return { kind: "child-sessions" };
89
+ }
90
+
91
+ if (activePanelKind === "project-files") {
92
+ return { kind: "project-files" };
93
+ }
94
+
95
+ if (activePanelKind === "cron") {
64
96
  return { kind: "cron" };
65
97
  }
66
98
 
@@ -116,10 +148,6 @@ export function resolveWorkspaceSelection(params: {
116
148
  };
117
149
  }
118
150
 
119
- if (sessionCronJobCount > 0) {
120
- return { kind: "cron" };
121
- }
122
-
123
151
  return null;
124
152
  }
125
153
 
@@ -127,27 +155,68 @@ export function buildWorkspaceTabsViewModel(params: {
127
155
  resolvedChildTabs: ResolvedChildSessionTab[];
128
156
  activeSideChatDraft: ChatWorkspaceSideChatDraft | null;
129
157
  workspaceFileTabs: readonly ChatWorkspaceFileTab[];
130
- sessionCronJobCount: number;
131
158
  activeSelection: WorkspaceSelection | null;
132
159
  optimisticReadAtBySessionKey: Record<string, string>;
133
160
  onSelectSession: (sessionKey: string) => void;
134
161
  onSelectFile: (fileKey: string) => void;
162
+ onOpenFileViewer: (fileKey: string, viewer: ChatWorkspaceFileViewer) => void;
135
163
  onCloseFile: (fileKey: string) => void;
164
+ onSelectOverview: () => void;
165
+ onSelectChildSessions: () => void;
166
+ onSelectProjectFiles: () => void;
136
167
  onSelectCronJobs: () => void;
137
168
  }): WorkspaceTabViewModel[] {
138
169
  const {
139
170
  activeSideChatDraft,
140
171
  resolvedChildTabs,
141
172
  workspaceFileTabs,
142
- sessionCronJobCount,
143
173
  activeSelection,
144
174
  optimisticReadAtBySessionKey,
145
175
  onSelectSession,
146
176
  onSelectFile,
177
+ onOpenFileViewer,
147
178
  onCloseFile,
179
+ onSelectOverview,
180
+ onSelectChildSessions,
181
+ onSelectProjectFiles,
148
182
  onSelectCronJobs,
149
183
  } = params;
150
184
 
185
+ const workspacePages = [
186
+ {
187
+ key: "overview",
188
+ kind: "overview" as const,
189
+ title: t("chatWorkspaceOverview"),
190
+ tooltip: t("chatWorkspaceOverview"),
191
+ active: activeSelection?.kind === "overview",
192
+ onSelect: onSelectOverview,
193
+ },
194
+ {
195
+ key: "child-sessions",
196
+ kind: "child-sessions" as const,
197
+ title: t("chatWorkspaceChildSessions"),
198
+ tooltip: t("chatWorkspaceChildSessions"),
199
+ active: activeSelection?.kind === "child-sessions",
200
+ onSelect: onSelectChildSessions,
201
+ },
202
+ {
203
+ key: "cron:session",
204
+ kind: "cron" as const,
205
+ title: t("chatWorkspaceSessionCronJobs"),
206
+ tooltip: t("chatWorkspaceSessionCronJobs"),
207
+ active: activeSelection?.kind === "cron",
208
+ onSelect: onSelectCronJobs,
209
+ },
210
+ {
211
+ key: "project-files",
212
+ kind: "project-files" as const,
213
+ title: t("chatWorkspaceProjectFiles"),
214
+ tooltip: t("chatWorkspaceProjectFiles"),
215
+ active: activeSelection?.kind === "project-files",
216
+ onSelect: onSelectProjectFiles,
217
+ },
218
+ ];
219
+
151
220
  const sideChatDraftTabs = activeSideChatDraft
152
221
  ? [
153
222
  {
@@ -193,31 +262,44 @@ export function buildWorkspaceTabsViewModel(params: {
193
262
  };
194
263
  });
195
264
 
196
- const fileTabs = workspaceFileTabs.map((file) => ({
197
- key: `file:${file.key}`,
198
- kind: "file" as const,
199
- title: readWorkspaceFileTitle(file),
200
- tooltip: file.path,
201
- viewMode: file.viewMode,
202
- active:
203
- activeSelection?.kind === "file" && activeSelection.file.key === file.key,
204
- onSelect: () => onSelectFile(file.key),
205
- onClose: () => onCloseFile(file.key),
206
- }));
207
-
208
- const cronTab =
209
- sessionCronJobCount > 0
210
- ? [
211
- {
212
- key: "cron:session",
213
- kind: "cron" as const,
214
- title: t("chatWorkspaceSessionCronJobs"),
215
- tooltip: t("chatWorkspaceSessionCronJobs"),
216
- active: activeSelection?.kind === "cron",
217
- onSelect: onSelectCronJobs,
218
- },
219
- ]
220
- : [];
221
-
222
- return [...sideChatDraftTabs, ...childTabs, ...fileTabs, ...cronTab];
265
+ const fileTabs = workspaceFileTabs.map((file) => {
266
+ const viewer = file.viewMode === "preview"
267
+ ? resolveWorkspaceFileViewer(file.path, file.previewViewer)
268
+ : null;
269
+ const alternateViewer = file.viewMode === "preview"
270
+ ? resolveAlternateWorkspaceFileViewer(file.path, file.previewViewer)
271
+ : null;
272
+ const fileTitle = readWorkspaceFileTitle(file);
273
+ return {
274
+ key: `file:${file.key}`,
275
+ kind: "file" as const,
276
+ title: viewer === "rendered"
277
+ ? `${t("chatWorkspacePreview")}: ${fileTitle}`
278
+ : fileTitle,
279
+ tooltip: file.path,
280
+ fileName: file.path,
281
+ viewMode: file.viewMode,
282
+ isRenderedPreview: viewer === "rendered",
283
+ alternateViewerAction: alternateViewer
284
+ ? {
285
+ label: alternateViewer === "rendered"
286
+ ? t("chatWorkspaceOpenPreview")
287
+ : t("chatWorkspaceOpenSource"),
288
+ viewer: alternateViewer,
289
+ onSelect: () => onOpenFileViewer(file.key, alternateViewer),
290
+ }
291
+ : null,
292
+ active:
293
+ activeSelection?.kind === "file" && activeSelection.file.key === file.key,
294
+ onSelect: () => onSelectFile(file.key),
295
+ onClose: () => onCloseFile(file.key),
296
+ };
297
+ });
298
+
299
+ return [
300
+ ...workspacePages,
301
+ ...sideChatDraftTabs,
302
+ ...childTabs,
303
+ ...fileTabs,
304
+ ];
223
305
  }
@@ -6,6 +6,7 @@ export { ChatPresenter } from "./presenters/chat.presenter";
6
6
  export { ChatDraftIntentManager } from "./managers/chat-draft-intent.manager";
7
7
  export { useChatSessionListStore } from "./stores/chat-session-list.store";
8
8
  export { useChatThreadStore } from "./stores/chat-thread.store";
9
+ export { useChatMessageLayoutStore } from "./stores/chat-message-layout.store";
9
10
  export { useNcpChatSessionTypes } from "./features/session-type/hooks/use-ncp-chat-session-types";
10
11
  export {
11
12
  buildSessionTypeOptions,
@@ -0,0 +1,76 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { ChatThreadManager } from '@/features/chat/managers/chat-thread.manager';
3
+ import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
4
+ import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
5
+
6
+ function createUiManager(): ConstructorParameters<typeof ChatThreadManager>[0] {
7
+ return {
8
+ goToSession: vi.fn(),
9
+ } as unknown as ConstructorParameters<typeof ChatThreadManager>[0];
10
+ }
11
+
12
+ describe('ChatThreadManager workspace pages', () => {
13
+ beforeEach(() => {
14
+ useChatSessionListStore.setState({
15
+ snapshot: {
16
+ ...useChatSessionListStore.getState().snapshot,
17
+ selectedSessionKey: 'parent-session-1',
18
+ },
19
+ });
20
+ useChatThreadStore.setState({
21
+ snapshot: {
22
+ ...useChatThreadStore.getState().snapshot,
23
+ workspacePanelParentKey: null,
24
+ activeWorkspacePanelKind: null,
25
+ activeChildSessionKey: null,
26
+ activeWorkspaceFileKey: null,
27
+ workspaceNavigationHistory: [],
28
+ workspaceNavigationHistoryIndex: 0,
29
+ },
30
+ });
31
+ });
32
+
33
+ it('opens the stable pages as navigable session pages', () => {
34
+ const onWorkspacePanelOpened = vi.fn();
35
+ const manager = new ChatThreadManager(
36
+ createUiManager(),
37
+ {} as ConstructorParameters<typeof ChatThreadManager>[1],
38
+ onWorkspacePanelOpened,
39
+ );
40
+
41
+ manager.openWorkspaceOverview('parent-session-1');
42
+ manager.openChildSessions('parent-session-1');
43
+ manager.openProjectFiles('parent-session-1');
44
+
45
+ expect(useChatThreadStore.getState().snapshot).toMatchObject({
46
+ workspacePanelParentKey: 'parent-session-1',
47
+ activeWorkspacePanelKind: 'project-files',
48
+ workspaceNavigationHistory: [
49
+ { kind: 'overview' },
50
+ { kind: 'child-sessions' },
51
+ { kind: 'project-files' },
52
+ ],
53
+ workspaceNavigationHistoryIndex: 2,
54
+ });
55
+ expect(onWorkspacePanelOpened).toHaveBeenCalledTimes(3);
56
+ });
57
+
58
+ it('toggles the current session workspace from the stable header action', () => {
59
+ const manager = new ChatThreadManager(
60
+ createUiManager(),
61
+ {} as ConstructorParameters<typeof ChatThreadManager>[1],
62
+ );
63
+
64
+ manager.toggleWorkspacePanel('parent-session-1');
65
+ expect(useChatThreadStore.getState().snapshot).toMatchObject({
66
+ workspacePanelParentKey: 'parent-session-1',
67
+ activeWorkspacePanelKind: 'overview',
68
+ });
69
+
70
+ manager.toggleWorkspacePanel('parent-session-1');
71
+ expect(useChatThreadStore.getState().snapshot).toMatchObject({
72
+ workspacePanelParentKey: null,
73
+ activeWorkspacePanelKind: null,
74
+ });
75
+ });
76
+ });
@@ -315,7 +315,7 @@ describe('ChatThreadManager', () => {
315
315
  expect(useChatThreadStore.getState().snapshot.workspaceFileTabs).toEqual(
316
316
  expect.arrayContaining([
317
317
  expect.objectContaining({
318
- key: 'parent-session-1::preview::README.md',
318
+ key: 'parent-session-1::preview:rendered::README.md',
319
319
  path: 'README.md',
320
320
  viewMode: 'preview',
321
321
  }),
@@ -331,7 +331,31 @@ describe('ChatThreadManager', () => {
331
331
  });
332
332
 
333
333
  describe('ChatThreadManager workspace file tabs', () => {
334
- it('opens HTML previews in source mode when the viewer is omitted or auto', () => {
334
+ it.each([
335
+ { path: 'demo.html', previewViewer: undefined, expectedViewer: 'source' },
336
+ { path: 'auto.html', previewViewer: 'auto' as const, expectedViewer: 'source' },
337
+ { path: 'README.md', previewViewer: undefined, expectedViewer: 'rendered' },
338
+ { path: 'report.docx', previewViewer: 'auto' as const, expectedViewer: 'auto' },
339
+ ])('normalizes the viewer for $path', ({ path, previewViewer, expectedViewer }) => {
340
+ const manager = new ChatThreadManager(
341
+ createUiManager(),
342
+ {} as ConstructorParameters<typeof ChatThreadManager>[1],
343
+ );
344
+
345
+ manager.openFilePreview({
346
+ path,
347
+ label: path,
348
+ viewMode: 'preview',
349
+ previewViewer,
350
+ });
351
+
352
+ expect(useChatThreadStore.getState().snapshot.workspaceFileTabs[0]).toMatchObject({
353
+ path,
354
+ previewViewer: expectedViewer,
355
+ });
356
+ });
357
+
358
+ it('opens source and rendered previews for the same file as adjacent workspace tabs', () => {
335
359
  const manager = new ChatThreadManager(
336
360
  createUiManager(),
337
361
  {} as ConstructorParameters<typeof ChatThreadManager>[1],
@@ -341,10 +365,12 @@ describe('ChatThreadManager workspace file tabs', () => {
341
365
  path: 'demo.html',
342
366
  label: 'demo.html',
343
367
  viewMode: 'preview',
368
+ previewViewer: 'source',
344
369
  });
370
+ manager.openWorkspaceFileViewer('parent-session-1::preview::demo.html');
345
371
 
346
372
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
347
- activeWorkspaceFileKey: 'parent-session-1::preview::demo.html',
373
+ activeWorkspaceFileKey: 'parent-session-1::preview:rendered::demo.html',
348
374
  workspaceFileTabs: [
349
375
  {
350
376
  key: 'parent-session-1::preview::demo.html',
@@ -352,67 +378,25 @@ describe('ChatThreadManager workspace file tabs', () => {
352
378
  viewMode: 'preview',
353
379
  previewViewer: 'source',
354
380
  },
355
- ],
356
- });
357
-
358
- manager.openFilePreview({
359
- path: 'auto.html',
360
- label: 'auto.html',
361
- viewMode: 'preview',
362
- previewViewer: 'auto',
363
- });
364
-
365
- expect(useChatThreadStore.getState().snapshot).toMatchObject({
366
- activeWorkspaceFileKey: 'parent-session-1::preview::auto.html',
367
- workspaceFileTabs: [
368
381
  {
369
- key: 'parent-session-1::preview::auto.html',
370
- path: 'auto.html',
382
+ key: 'parent-session-1::preview:rendered::demo.html',
383
+ path: 'demo.html',
371
384
  viewMode: 'preview',
372
- previewViewer: 'source',
385
+ previewViewer: 'rendered',
373
386
  },
374
- expect.objectContaining({
375
- key: 'parent-session-1::preview::demo.html',
376
- }),
377
387
  ],
378
388
  });
379
- });
380
389
 
381
- it('keeps source and rendered previews for the same file as separate workspace tabs', () => {
382
- const manager = new ChatThreadManager(
383
- createUiManager(),
384
- {} as ConstructorParameters<typeof ChatThreadManager>[1],
385
- );
386
-
387
- manager.openFilePreview({
388
- path: 'demo.html',
389
- label: 'demo.html',
390
- viewMode: 'preview',
391
- previewViewer: 'source',
392
- });
393
- manager.openFilePreview({
394
- path: 'demo.html',
395
- label: 'demo.html',
396
- viewMode: 'preview',
397
- previewViewer: 'rendered',
398
- });
390
+ manager.openWorkspaceFileViewer('parent-session-1::preview::demo.html');
391
+ expect(useChatThreadStore.getState().snapshot.workspaceFileTabs).toHaveLength(2);
399
392
 
393
+ manager.openWorkspaceFileViewer('parent-session-1::preview:rendered::demo.html');
400
394
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
401
- activeWorkspaceFileKey: 'parent-session-1::preview:rendered::demo.html',
402
- workspaceFileTabs: [
403
- {
404
- key: 'parent-session-1::preview:rendered::demo.html',
405
- path: 'demo.html',
406
- viewMode: 'preview',
407
- previewViewer: 'rendered',
408
- },
409
- {
410
- key: 'parent-session-1::preview::demo.html',
411
- path: 'demo.html',
412
- viewMode: 'preview',
413
- previewViewer: 'source',
414
- },
415
- ],
395
+ activeWorkspaceFileKey: 'parent-session-1::preview::demo.html',
396
+ workspaceFileTabs: expect.arrayContaining([
397
+ expect.objectContaining({ previewViewer: 'source' }),
398
+ expect.objectContaining({ previewViewer: 'rendered' }),
399
+ ]),
416
400
  });
417
401
  });
418
402
  });
@@ -443,14 +427,14 @@ describe('ChatThreadManager workspace navigation', () => {
443
427
 
444
428
  expect(useChatThreadStore.getState().snapshot.workspaceNavigationHistory).toEqual([
445
429
  { kind: 'child-session', key: 'child-session-1' },
446
- { kind: 'file', key: 'parent-session-1::preview::README.md' },
430
+ { kind: 'file', key: 'parent-session-1::preview:rendered::README.md' },
447
431
  { kind: 'cron' },
448
432
  ]);
449
433
 
450
434
  manager.goBackWorkspacePanel();
451
435
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
452
436
  activeWorkspacePanelKind: 'file',
453
- activeWorkspaceFileKey: 'parent-session-1::preview::README.md',
437
+ activeWorkspaceFileKey: 'parent-session-1::preview:rendered::README.md',
454
438
  workspaceNavigationHistoryIndex: 1,
455
439
  });
456
440
 
@@ -464,7 +448,7 @@ describe('ChatThreadManager workspace navigation', () => {
464
448
  manager.goForwardWorkspacePanel();
465
449
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
466
450
  activeWorkspacePanelKind: 'file',
467
- activeWorkspaceFileKey: 'parent-session-1::preview::README.md',
451
+ activeWorkspaceFileKey: 'parent-session-1::preview:rendered::README.md',
468
452
  workspaceNavigationHistoryIndex: 1,
469
453
  });
470
454
  });
@@ -520,7 +504,7 @@ describe('ChatThreadManager workspace navigation', () => {
520
504
  label: 'README.md',
521
505
  viewMode: 'preview',
522
506
  });
523
- manager.closeWorkspaceFile('parent-session-1::preview::README.md');
507
+ manager.closeWorkspaceFile('parent-session-1::preview:rendered::README.md');
524
508
 
525
509
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
526
510
  activeWorkspacePanelKind: 'child-session',