@nextclaw/ui 0.15.3 → 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 (184) hide show
  1. package/CHANGELOG.md +21 -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-C3mJGwiy.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-CU0kzzIP.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-BvCp_0lS.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-CILd1vAP.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-ByJqcPTP.js → save-BABzyrjB.js} +1 -1
  43. package/dist/assets/{search-DVIV6pfD.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-CH5shGg_.js → settings-2-D8WWcBnw.js} +1 -1
  50. package/dist/assets/{skeleton-DeX1FKw7.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 +11 -6
  61. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +41 -0
  62. package/src/features/chat/components/chat-popover-content.tsx +26 -0
  63. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
  64. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
  65. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
  66. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
  67. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
  68. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
  69. package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
  70. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  71. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
  72. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +3 -14
  73. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
  74. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +69 -54
  75. package/src/features/chat/features/message/components/chat-message-list.container.tsx +16 -1
  76. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
  77. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +37 -0
  78. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +4 -3
  79. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
  80. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
  81. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
  82. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  83. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +3 -3
  84. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -3
  85. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
  86. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +403 -106
  87. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
  88. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
  89. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
  90. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +9 -3
  91. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +7 -5
  92. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +57 -177
  93. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
  94. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +56 -6
  95. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +29 -7
  96. package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
  97. package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
  98. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
  99. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
  100. package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
  101. package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
  102. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
  103. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
  104. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +53 -10
  105. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +68 -2
  106. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +17 -0
  107. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +63 -0
  108. package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
  109. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
  110. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +120 -38
  111. package/src/features/chat/index.ts +1 -0
  112. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +76 -0
  113. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +44 -60
  114. package/src/features/chat/managers/chat-thread.manager.ts +123 -126
  115. package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
  116. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +78 -1
  117. package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
  118. package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
  119. package/src/features/chat/stores/chat-thread.store.ts +70 -13
  120. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
  121. package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
  122. package/src/index.css +36 -2
  123. package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
  124. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
  125. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
  126. package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
  127. package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
  128. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
  129. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
  130. package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
  131. package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
  132. package/src/shared/components/file-type-icon.tsx +272 -0
  133. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
  134. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
  135. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
  136. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
  137. package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
  138. package/src/shared/lib/api/utils/server-path.utils.ts +19 -20
  139. package/src/shared/lib/i18n/locales/en-US/chat.json +17 -1
  140. package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
  141. package/src/shared/lib/i18n/locales/zh-CN/chat.json +17 -1
  142. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
  143. package/dist/assets/api-iNBpppzj.js +0 -13
  144. package/dist/assets/appearance-settings-page-DMffQFR3.js +0 -1
  145. package/dist/assets/channels-list-page-CCtp0BRS.js +0 -8
  146. package/dist/assets/chat-page-H5a0dWvO.js +0 -1
  147. package/dist/assets/config-split-page-CrOJgB-Y.js +0 -1
  148. package/dist/assets/confirm-dialog-Chv50USy.js +0 -5
  149. package/dist/assets/desktop-update-config-KzTuXRf_.js +0 -1
  150. package/dist/assets/dist-BunzmC4B.js +0 -1
  151. package/dist/assets/dist-ChmGwvtK.js +0 -1
  152. package/dist/assets/doc-browser-DNRL4Zvd.js +0 -1
  153. package/dist/assets/doc-browser-Dg0pmkOg.js +0 -1
  154. package/dist/assets/doc-browser-context-BHY-0FSD.js +0 -1
  155. package/dist/assets/es2015-DQZ5JWwK.js +0 -41
  156. package/dist/assets/i18n-provider-Cu8XyZb_.js +0 -1
  157. package/dist/assets/index-CzfBdvK1.css +0 -1
  158. package/dist/assets/index-DGqHne1Q.js +0 -104
  159. package/dist/assets/loader-circle-CDSFuzmk.js +0 -1
  160. package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +0 -9
  161. package/dist/assets/mcp-marketplace-page-D6idOTLp.js +0 -1
  162. package/dist/assets/model-config-page-bNNo-XyG.js +0 -1
  163. package/dist/assets/plus-D4DsdLN-.js +0 -1
  164. package/dist/assets/provider-scoped-model-input-IlgQCsoT.js +0 -1
  165. package/dist/assets/providers-config-page-wLo6CAJw.js +0 -1
  166. package/dist/assets/react-BPIsQIlO.js +0 -8
  167. package/dist/assets/remote-uKEYp7AR.js +0 -1
  168. package/dist/assets/rotate-cw-wPFPg1kH.js +0 -1
  169. package/dist/assets/runtime-config-page-DDn1sP4G.js +0 -1
  170. package/dist/assets/search-config-page-B-Cf4iiB.js +0 -1
  171. package/dist/assets/secrets-config-page-DPKsY8Iw.js +0 -3
  172. package/dist/assets/security-config-e9SSBlKr.js +0 -1
  173. package/dist/assets/select-BdgczSNw.js +0 -1
  174. package/dist/assets/setting-row-BkTVUTKx.js +0 -1
  175. package/dist/assets/side-dock-BsBvcJ9i.js +0 -3
  176. package/dist/assets/switch-Cv8Ekb3x.js +0 -1
  177. package/dist/assets/tag-chip-RB9uKcoC.js +0 -1
  178. package/dist/assets/tooltip-oQlEN-l6.js +0 -1
  179. package/dist/assets/use-config-l3W7y8Qz.js +0 -1
  180. package/dist/assets/use-confirm-dialog-BAxaVOJI.js +0 -1
  181. package/dist/assets/x-BBMffEN0.js +0 -1
  182. /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-fDrYfl0l.js} +0 -0
  183. /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-zZvY9U63.js} +0 -0
  184. /package/dist/assets/{middleware-BILWbJE4.js → middleware-BLMHv9YF.js} +0 -0
@@ -2,7 +2,11 @@ import {
2
2
  AlarmClock,
3
3
  ArrowLeft,
4
4
  ArrowRight,
5
- FileCode2,
5
+ Code2,
6
+ Eye,
7
+ FolderTree,
8
+ GitBranch,
9
+ LayoutDashboard,
6
10
  Maximize2,
7
11
  MessageSquarePlus,
8
12
  MessageSquareText,
@@ -12,6 +16,7 @@ import {
12
16
  } from "lucide-react";
13
17
  import type { WorkspaceTabViewModel } from "@/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils";
14
18
  import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
19
+ import { FileTypeIcon } from "@/shared/components/file-type-icon";
15
20
  import {
16
21
  CompactTabStrip,
17
22
  type CompactTabStripAction,
@@ -19,13 +24,29 @@ import {
19
24
  } from "@/shared/components/ui/tab-strip/compact-tab-strip";
20
25
  import { t } from "@/shared/lib/i18n";
21
26
 
22
- function WorkspaceTabIcon({ agentId, kind }: Pick<WorkspaceTabViewModel, "agentId" | "kind">) {
27
+ function WorkspaceTabIcon({
28
+ agentId,
29
+ fileName,
30
+ kind,
31
+ }: Pick<WorkspaceTabViewModel, "agentId" | "fileName" | "kind">) {
32
+ if (kind === "overview") {
33
+ return <LayoutDashboard className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
34
+ }
35
+
36
+ if (kind === "child-sessions") {
37
+ return <GitBranch className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
38
+ }
39
+
40
+ if (kind === "project-files") {
41
+ return <FolderTree className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
42
+ }
43
+
23
44
  if (kind === "cron") {
24
45
  return <AlarmClock className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
25
46
  }
26
47
 
27
48
  if (kind === "file") {
28
- return <FileCode2 className="h-3.5 w-3.5 shrink-0 text-gray-400" />;
49
+ return <FileTypeIcon fileName={fileName ?? ""} size="compact" />;
29
50
  }
30
51
 
31
52
  if (kind === "side-chat-draft") {
@@ -47,11 +68,17 @@ function buildCompactWorkspaceTabs(
47
68
  return tabs.map((tab) => ({
48
69
  key: tab.key,
49
70
  label: tab.title,
71
+ labelClassName: tab.isRenderedPreview ? "italic font-normal" : undefined,
50
72
  active: tab.active,
51
73
  tooltip: tab.tooltip,
52
- leadingIcon: <WorkspaceTabIcon kind={tab.kind} agentId={tab.agentId} />,
53
- badge:
54
- tab.kind === "file" && tab.viewMode === "diff" ? (
74
+ leadingIcon: (
75
+ <WorkspaceTabIcon
76
+ kind={tab.kind}
77
+ agentId={tab.agentId}
78
+ fileName={tab.fileName}
79
+ />
80
+ ),
81
+ badge: tab.kind === "file" && tab.viewMode === "diff" ? (
55
82
  <span className="shrink-0 rounded border border-amber-200 bg-amber-50 px-1 py-0 text-[9px] font-medium uppercase tracking-[0.08em] text-amber-700">
56
83
  {t("chatWorkspaceDiff")}
57
84
  </span>
@@ -61,6 +88,29 @@ function buildCompactWorkspaceTabs(
61
88
  closePlacement: "leading-hover",
62
89
  onSelect: tab.onSelect,
63
90
  onClose: tab.onClose,
91
+ menuLabel: t("chatWorkspaceFileMoreActions"),
92
+ menuActions: tab.kind === "file"
93
+ ? [
94
+ ...(tab.alternateViewerAction
95
+ ? [{
96
+ key: `viewer:${tab.alternateViewerAction.viewer}`,
97
+ icon: tab.alternateViewerAction.viewer === "rendered"
98
+ ? <Eye className="h-3.5 w-3.5" />
99
+ : <Code2 className="h-3.5 w-3.5" />,
100
+ label: tab.alternateViewerAction.label,
101
+ onClick: tab.alternateViewerAction.onSelect,
102
+ }]
103
+ : []),
104
+ ...(tab.onClose
105
+ ? [{
106
+ key: "close",
107
+ icon: <X className="h-3.5 w-3.5" />,
108
+ label: t("chatWorkspaceCloseFile"),
109
+ onClick: tab.onClose,
110
+ }]
111
+ : []),
112
+ ]
113
+ : undefined,
64
114
  }));
65
115
  }
66
116
 
@@ -3,8 +3,14 @@ import { useQueryClient } from "@tanstack/react-query";
3
3
  import type { CronJobView } from "@/shared/lib/api";
4
4
  import { useNcpChildSessionTabsView } from "@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view";
5
5
  import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
6
+ import {
7
+ CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
8
+ CHAT_WORKSPACE_PANEL_MAX_WIDTH,
9
+ CHAT_WORKSPACE_PANEL_MIN_WIDTH,
10
+ } from "@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils";
6
11
  import type {
7
12
  ChatChildSessionTab,
13
+ ChatWorkspacePanelKind,
8
14
  ChatWorkspaceSideChatDraft,
9
15
  ChatWorkspaceNavigationEntry,
10
16
  ChatWorkspaceFileTab,
@@ -35,10 +41,11 @@ type ChatSessionWorkspacePanelProps = {
35
41
  activeWorkspaceFileKey: string | null;
36
42
  workspaceNavigationHistory?: readonly ChatWorkspaceNavigationEntry[];
37
43
  workspaceNavigationHistoryIndex?: number;
38
- activePanelKind?: "child-session" | "side-chat-draft" | "file" | "cron" | null;
44
+ activePanelKind?: ChatWorkspacePanelKind | null;
39
45
  sessionCronJobs?: readonly CronJobView[];
40
46
  sessionProjectRoot: string | null;
41
47
  sessionWorkingDir: string | null;
48
+ workspacePanelWidth?: number;
42
49
  displayMode?: "docked" | "overlay";
43
50
  };
44
51
 
@@ -55,6 +62,7 @@ export function ChatSessionWorkspacePanel({
55
62
  sessionCronJobs = [],
56
63
  sessionProjectRoot,
57
64
  sessionWorkingDir,
65
+ workspacePanelWidth = CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
58
66
  displayMode = "docked",
59
67
  }: ChatSessionWorkspacePanelProps) {
60
68
  const presenter = usePresenter();
@@ -72,7 +80,6 @@ export function ChatSessionWorkspacePanel({
72
80
  activePanelKind,
73
81
  childSessionTabs: resolvedChildTabs,
74
82
  workspaceFileTabs,
75
- sessionCronJobCount: sessionCronJobs.length,
76
83
  });
77
84
  const workspaceHistory = {
78
85
  entries: workspaceNavigationHistory,
@@ -89,12 +96,24 @@ export function ChatSessionWorkspacePanel({
89
96
  resolvedChildTabs,
90
97
  activeSideChatDraft,
91
98
  workspaceFileTabs,
92
- sessionCronJobCount: sessionCronJobs.length,
93
99
  activeSelection,
94
100
  optimisticReadAtBySessionKey,
95
101
  onSelectSession: presenter.chatThreadManager.selectChildSessionDetail,
96
102
  onSelectFile: presenter.chatThreadManager.selectWorkspaceFile,
103
+ onOpenFileViewer: presenter.chatThreadManager.openWorkspaceFileViewer,
97
104
  onCloseFile: presenter.chatThreadManager.closeWorkspaceFile,
105
+ onSelectOverview: () => {
106
+ if (sessionKey)
107
+ presenter.chatThreadManager.openWorkspaceOverview(sessionKey);
108
+ },
109
+ onSelectChildSessions: () => {
110
+ if (sessionKey)
111
+ presenter.chatThreadManager.openChildSessions(sessionKey);
112
+ },
113
+ onSelectProjectFiles: () => {
114
+ if (sessionKey)
115
+ presenter.chatThreadManager.openProjectFiles(sessionKey);
116
+ },
98
117
  onSelectCronJobs: () => {
99
118
  if (sessionKey)
100
119
  presenter.chatThreadManager.openSessionCronPanel(sessionKey);
@@ -108,7 +127,6 @@ export function ChatSessionWorkspacePanel({
108
127
  resolvedChildTabs,
109
128
  sessionKey,
110
129
  workspaceFileTabs,
111
- sessionCronJobs.length,
112
130
  ],
113
131
  );
114
132
 
@@ -150,9 +168,11 @@ export function ChatSessionWorkspacePanel({
150
168
  : "hidden border-gray-200/70 bg-white/95 backdrop-blur-sm md:flex",
151
169
  isContainerMaximized ? "shadow-xl" : null,
152
170
  )}
153
- defaultWidth={480}
154
- minWidth={360}
155
- maxWidth={860}
171
+ defaultWidth={workspacePanelWidth}
172
+ width={workspacePanelWidth}
173
+ minWidth={CHAT_WORKSPACE_PANEL_MIN_WIDTH}
174
+ maxWidth={CHAT_WORKSPACE_PANEL_MAX_WIDTH}
175
+ onWidthCommit={presenter.chatThreadManager.setWorkspacePanelWidth}
156
176
  overlay={isOverlayPanel}
157
177
  overlayScope={isContainerMaximized ? "container" : "viewport"}
158
178
  >
@@ -175,7 +195,9 @@ export function ChatSessionWorkspacePanel({
175
195
  <div className="flex min-h-0 flex-1 flex-col bg-white">
176
196
  <ChatSessionWorkspacePanelContent
177
197
  activeSelection={activeSelection}
198
+ childSessionTabs={resolvedChildTabs}
178
199
  filePreviewRefreshVersion={filePreviewRefreshVersion}
200
+ sessionKey={sessionKey}
179
201
  sessionCronJobs={sessionCronJobs}
180
202
  sessionProjectRoot={sessionProjectRoot}
181
203
  sessionWorkingDir={sessionWorkingDir}
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ type WorkspaceFileBufferState =
4
+ | { contentUrl: string; status: "ready"; data: ArrayBuffer }
5
+ | { contentUrl: string; status: "error"; data: null };
6
+
7
+ export function useWorkspaceFileBuffer(contentUrl: string): {
8
+ status: "loading" | "ready" | "error";
9
+ data: ArrayBuffer | null;
10
+ } {
11
+ const [state, setState] = useState<WorkspaceFileBufferState | null>(null);
12
+ const currentState = state?.contentUrl === contentUrl ? state : null;
13
+
14
+ useEffect(() => {
15
+ const abortController = new AbortController();
16
+ void fetch(contentUrl, { signal: abortController.signal })
17
+ .then((response) => {
18
+ if (!response.ok) {
19
+ throw new Error(`Unable to load document (${response.status}).`);
20
+ }
21
+ return response.arrayBuffer();
22
+ })
23
+ .then((data) => {
24
+ if (!abortController.signal.aborted) {
25
+ setState({ contentUrl, status: "ready", data });
26
+ }
27
+ })
28
+ .catch((error: unknown) => {
29
+ if (!abortController.signal.aborted) {
30
+ console.error("Failed to load workspace document", error);
31
+ setState({ contentUrl, status: "error", data: null });
32
+ }
33
+ });
34
+
35
+ return () => abortController.abort();
36
+ }, [contentUrl]);
37
+
38
+ return {
39
+ status: currentState?.status ?? "loading",
40
+ data: currentState?.data ?? null,
41
+ };
42
+ }
@@ -0,0 +1,18 @@
1
+ import { t } from "@/shared/lib/i18n";
2
+
3
+ export function WorkspaceDocumentPreviewState({
4
+ status,
5
+ }: {
6
+ status: "loading" | "ready" | "error";
7
+ }) {
8
+ if (status === "ready") {
9
+ return null;
10
+ }
11
+ return (
12
+ <div className="absolute inset-0 z-10 flex items-center justify-center bg-background/90 px-6 text-center text-sm text-muted-foreground">
13
+ {status === "loading"
14
+ ? t("chatWorkspaceLoadingPreview")
15
+ : t("chatWorkspacePreviewFailed")}
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,89 @@
1
+ .workspace-docx-preview {
2
+ container-type: inline-size;
3
+ }
4
+
5
+ .workspace-docx-preview .docx-wrapper {
6
+ gap: 1rem;
7
+ padding: 0 !important;
8
+ padding-bottom: 0 !important;
9
+ background: transparent !important;
10
+ }
11
+
12
+ .workspace-docx-preview .docx-wrapper > section.docx {
13
+ margin: 0 auto 1rem !important;
14
+ border: 1px solid rgb(0 0 0 / 8%);
15
+ border-radius: 4px;
16
+ box-shadow: 0 4px 18px rgb(0 0 0 / 12%) !important;
17
+ }
18
+
19
+ .workspace-docx-preview--reflow .docx-wrapper > section.docx {
20
+ width: 100% !important;
21
+ min-height: auto !important;
22
+ padding: clamp(1.5rem, 6cqw, 3.5rem) !important;
23
+ overflow: visible !important;
24
+ }
25
+
26
+ .workspace-docx-preview--reflow .docx-wrapper > section.docx > article {
27
+ min-width: 0;
28
+ }
29
+
30
+ .workspace-docx-preview--reflow .docx p {
31
+ line-height: 1.6 !important;
32
+ }
33
+
34
+ .workspace-docx-preview--reflow .docx p.docx_heading1 span {
35
+ min-height: 0 !important;
36
+ font-size: clamp(1.5rem, 6cqw, 2rem) !important;
37
+ line-height: 1.25 !important;
38
+ }
39
+
40
+ .workspace-docx-preview--reflow .docx p.docx_heading2 span {
41
+ min-height: 0 !important;
42
+ font-size: clamp(1.125rem, 4.5cqw, 1.375rem) !important;
43
+ line-height: 1.35 !important;
44
+ }
45
+
46
+ .workspace-docx-preview--reflow .docx table {
47
+ width: 100% !important;
48
+ margin: 0.75rem 0 1.25rem;
49
+ table-layout: auto !important;
50
+ }
51
+
52
+ .workspace-docx-preview--reflow .docx col {
53
+ width: auto !important;
54
+ }
55
+
56
+ .workspace-docx-preview--reflow .docx table td,
57
+ .workspace-docx-preview--reflow .docx table th {
58
+ padding: 0.45rem 0.55rem !important;
59
+ border-bottom: 1px solid rgb(0 0 0 / 9%);
60
+ }
61
+
62
+ .workspace-docx-preview--reflow .docx table tr:first-child > td,
63
+ .workspace-docx-preview--reflow .docx table tr:first-child > th {
64
+ background: rgb(0 0 0 / 4%);
65
+ font-weight: 600;
66
+ }
67
+
68
+ .workspace-docx-preview--reflow .docx table p {
69
+ min-height: 0;
70
+ margin: 0 !important;
71
+ }
72
+
73
+ .workspace-docx-preview--reflow .workspace-docx-wide-table {
74
+ max-width: 100%;
75
+ margin: 0.75rem 0 1.25rem;
76
+ overflow-x: auto;
77
+ scrollbar-width: thin;
78
+ }
79
+
80
+ .workspace-docx-preview--reflow .workspace-docx-wide-table table {
81
+ min-width: 36rem;
82
+ margin: 0;
83
+ font-size: 0.875rem;
84
+ }
85
+
86
+ .workspace-docx-preview--reflow .workspace-docx-wide-table td,
87
+ .workspace-docx-preview--reflow .workspace-docx-wide-table th {
88
+ white-space: nowrap;
89
+ }
@@ -0,0 +1,87 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useWorkspaceFileBuffer } from "./use-workspace-file-buffer";
3
+ import { WorkspaceDocumentPreviewState } from "./workspace-document-preview-state";
4
+ import "./workspace-docx-preview.css";
5
+
6
+ function applyResponsiveDocxFallback(container: HTMLDivElement): void {
7
+ const pages = Array.from(
8
+ container.querySelectorAll<HTMLElement>(".docx-wrapper > section.docx"),
9
+ );
10
+ const needsFallback = pages.some(
11
+ (page) => !page.style.width && !page.style.padding,
12
+ );
13
+ container.classList.toggle("workspace-docx-preview--reflow", needsFallback);
14
+ if (!needsFallback) {
15
+ return;
16
+ }
17
+ for (const table of container.querySelectorAll<HTMLTableElement>("table")) {
18
+ if (table.querySelectorAll("col").length < 5) {
19
+ continue;
20
+ }
21
+ const scrollContainer = document.createElement("div");
22
+ scrollContainer.className = "workspace-docx-wide-table";
23
+ table.before(scrollContainer);
24
+ scrollContainer.append(table);
25
+ }
26
+ }
27
+
28
+ export function WorkspaceDocxPreview({ contentUrl }: { contentUrl: string }) {
29
+ const containerRef = useRef<HTMLDivElement | null>(null);
30
+ const fileBuffer = useWorkspaceFileBuffer(contentUrl);
31
+ const [renderState, setRenderState] = useState<{
32
+ contentUrl: string;
33
+ status: "ready" | "error";
34
+ } | null>(null);
35
+ const renderStatus =
36
+ renderState?.contentUrl === contentUrl ? renderState.status : "loading";
37
+ const status =
38
+ fileBuffer.status === "ready" ? renderStatus : fileBuffer.status;
39
+
40
+ useEffect(() => {
41
+ const container = containerRef.current;
42
+ if (!container || !fileBuffer.data) {
43
+ return undefined;
44
+ }
45
+ let disposed = false;
46
+ container.replaceChildren();
47
+ void import("docx-preview")
48
+ .then(async (docxPreview) => {
49
+ await docxPreview.renderAsync(
50
+ fileBuffer.data as ArrayBuffer,
51
+ container,
52
+ container,
53
+ {
54
+ breakPages: true,
55
+ inWrapper: true,
56
+ ignoreLastRenderedPageBreak: false,
57
+ },
58
+ );
59
+ if (!disposed) {
60
+ applyResponsiveDocxFallback(container);
61
+ setRenderState({ contentUrl, status: "ready" });
62
+ }
63
+ })
64
+ .catch((error: unknown) => {
65
+ if (!disposed) {
66
+ console.error("Failed to render DOCX preview", error);
67
+ setRenderState({ contentUrl, status: "error" });
68
+ }
69
+ });
70
+
71
+ return () => {
72
+ disposed = true;
73
+ container.replaceChildren();
74
+ };
75
+ }, [contentUrl, fileBuffer.data]);
76
+
77
+ return (
78
+ <div className="relative h-full overflow-auto bg-muted/40 p-4 custom-scrollbar">
79
+ <WorkspaceDocumentPreviewState status={status} />
80
+ <div
81
+ ref={containerRef}
82
+ className="workspace-docx-preview min-h-full"
83
+ data-testid="workspace-content-docx"
84
+ />
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,82 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useWorkspaceFileBuffer } from "./use-workspace-file-buffer";
3
+ import { WorkspaceDocumentPreviewState } from "./workspace-document-preview-state";
4
+
5
+ export function WorkspacePresentationPreview({
6
+ contentUrl,
7
+ }: {
8
+ contentUrl: string;
9
+ }) {
10
+ const containerRef = useRef<HTMLDivElement | null>(null);
11
+ const fileBuffer = useWorkspaceFileBuffer(contentUrl);
12
+ const [renderState, setRenderState] = useState<{
13
+ contentUrl: string;
14
+ status: "ready" | "error";
15
+ } | null>(null);
16
+ const renderStatus =
17
+ renderState?.contentUrl === contentUrl ? renderState.status : "loading";
18
+ const status =
19
+ fileBuffer.status === "ready" ? renderStatus : fileBuffer.status;
20
+
21
+ useEffect(() => {
22
+ const container = containerRef.current;
23
+ if (!container || !fileBuffer.data) {
24
+ return undefined;
25
+ }
26
+ const abortController = new AbortController();
27
+ let viewer: { destroy: () => void } | null = null;
28
+ container.replaceChildren();
29
+ void import("@aiden0z/pptx-renderer")
30
+ .then(async ({ PptxViewer, RECOMMENDED_ZIP_LIMITS }) => {
31
+ viewer = await PptxViewer.open(
32
+ fileBuffer.data as ArrayBuffer,
33
+ container,
34
+ {
35
+ renderMode: "list",
36
+ fitMode: "contain",
37
+ lazySlides: true,
38
+ lazyMedia: true,
39
+ pdfjs: false,
40
+ scrollContainer: container,
41
+ signal: abortController.signal,
42
+ zipLimits: RECOMMENDED_ZIP_LIMITS,
43
+ listOptions: {
44
+ windowed: true,
45
+ batchSize: 8,
46
+ initialSlides: 4,
47
+ overscanViewport: 1.5,
48
+ },
49
+ },
50
+ );
51
+ if (abortController.signal.aborted) {
52
+ viewer.destroy();
53
+ viewer = null;
54
+ return;
55
+ }
56
+ setRenderState({ contentUrl, status: "ready" });
57
+ })
58
+ .catch((error: unknown) => {
59
+ if (!abortController.signal.aborted) {
60
+ console.error("Failed to render presentation preview", error);
61
+ setRenderState({ contentUrl, status: "error" });
62
+ }
63
+ });
64
+
65
+ return () => {
66
+ abortController.abort();
67
+ viewer?.destroy();
68
+ container.replaceChildren();
69
+ };
70
+ }, [contentUrl, fileBuffer.data]);
71
+
72
+ return (
73
+ <div className="relative h-full bg-muted/40">
74
+ <WorkspaceDocumentPreviewState status={status} />
75
+ <div
76
+ ref={containerRef}
77
+ className="h-full overflow-auto p-4 custom-scrollbar"
78
+ data-testid="workspace-content-presentation"
79
+ />
80
+ </div>
81
+ );
82
+ }