@nextclaw/ui 0.15.24 → 0.15.25

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 (94) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/assets/{api-BxPNDp30.js → api-FK7tikI1.js} +3 -3
  3. package/dist/assets/appearance-settings-page-BYbvl2wj.js +1 -0
  4. package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CocdcIko.js} +2 -2
  5. package/dist/assets/chat-page-C5E-LLVd.js +1 -0
  6. package/dist/assets/{config-split-page-BVWVDbvG.js → config-split-page-CA_a0AJ5.js} +1 -1
  7. package/dist/assets/{confirm-dialog-mA2iVzxh.js → confirm-dialog-CxZfenjK.js} +1 -1
  8. package/dist/assets/{desktop-update-config-DyQb1LXr.js → desktop-update-config-BEKC8Ly9.js} +1 -1
  9. package/dist/assets/{dist-B4Pf5X3Q.js → dist-BH7W5yCi.js} +1 -1
  10. package/dist/assets/{dist-DHEWkGWX.js → dist-CaYdVVeO.js} +1 -1
  11. package/dist/assets/doc-browser-Cl2ldE3f.js +1 -0
  12. package/dist/assets/doc-browser-DXCSd63E.js +1 -0
  13. package/dist/assets/{doc-browser-context-CuGI6PQc.js → doc-browser-context-FDPs3gJf.js} +1 -1
  14. package/dist/assets/{form-actions-BOfIyG3O.js → form-actions-0c0aw_bl.js} +1 -1
  15. package/dist/assets/i18n-provider-DkITJNz8.js +1 -0
  16. package/dist/assets/index-CQ2LcI2p.css +1 -0
  17. package/dist/assets/index-CrdfvBVg.js +107 -0
  18. package/dist/assets/loader-circle-DHtUxCIA.js +1 -0
  19. package/dist/assets/{mcp-marketplace-page-ofDqbOIF.js → mcp-marketplace-page-Bc_jI5iH.js} +2 -2
  20. package/dist/assets/mcp-marketplace-page-CzCieD9r.js +1 -0
  21. package/dist/assets/{model-config-page-D73Tu3jW.js → model-config-page-C4jw-pcj.js} +1 -1
  22. package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-CVy0_kVl.js} +1 -1
  23. package/dist/assets/plus-CG1RwZQq.js +1 -0
  24. package/dist/assets/{provider-scoped-model-input-BaJJJYMX.js → provider-scoped-model-input-CmH4BFvc.js} +1 -1
  25. package/dist/assets/{providers-config-page-BL6IRVBm.js → providers-config-page-CJQjwzaw.js} +1 -1
  26. package/dist/assets/{react-BC_orpAj.js → react-DjFBVA0n.js} +1 -1
  27. package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-j2bl79Ea.js} +1 -1
  28. package/dist/assets/remote-BIlpD3yL.js +1 -0
  29. package/dist/assets/rotate-cw-bKG8zH8M.js +1 -0
  30. package/dist/assets/runtime-config-page-B_x2Glz5.js +1 -0
  31. package/dist/assets/{save-Cnr71JON.js → save-BKNrtK81.js} +1 -1
  32. package/dist/assets/{search-CCX11X2m.js → search-CkOm65W_.js} +1 -1
  33. package/dist/assets/{search-config-page-BB6d5gT7.js → search-config-page-BaIijtmO.js} +1 -1
  34. package/dist/assets/{secrets-config-page-DYsDeT9K.js → secrets-config-page-CJjtdFAl.js} +2 -2
  35. package/dist/assets/security-config-DB8apj09.js +1 -0
  36. package/dist/assets/{select-DvwBu99K.js → select-Ea7ffKCE.js} +1 -1
  37. package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-CtyN3m_V.js} +1 -1
  38. package/dist/assets/skeleton-Bt6zxcAB.js +1 -0
  39. package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-C9l4xsKp.js} +1 -1
  40. package/dist/assets/x-CowtCu9Z.js +1 -0
  41. package/dist/index.html +20 -20
  42. package/package.json +8 -8
  43. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +30 -1
  44. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -1
  45. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
  46. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
  47. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
  48. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +573 -90
  49. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +389 -288
  50. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
  51. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
  52. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +149 -94
  53. package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
  54. package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
  55. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +411 -0
  56. package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
  57. package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
  58. package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +88 -0
  59. package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +335 -0
  60. package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
  61. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
  62. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
  63. package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
  64. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
  65. package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
  66. package/src/features/chat/managers/chat-thread.manager.ts +132 -128
  67. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +20 -40
  68. package/src/features/chat/stores/chat-thread.store.ts +47 -74
  69. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
  70. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
  71. package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
  72. package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
  73. package/src/shared/lib/api/managers/client.manager.ts +31 -17
  74. package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
  75. package/src/shared/lib/api/types.ts +145 -116
  76. package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
  77. package/src/shared/lib/i18n/locales/en-US/chat.json +42 -0
  78. package/src/shared/lib/i18n/locales/zh-CN/chat.json +42 -0
  79. package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
  80. package/dist/assets/chat-page-BuFDmG5_.js +0 -1
  81. package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
  82. package/dist/assets/doc-browser-eMUagyBV.js +0 -1
  83. package/dist/assets/index-BjBdWHV1.js +0 -107
  84. package/dist/assets/index-DmpmjAGQ.css +0 -1
  85. package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
  86. package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
  87. package/dist/assets/plus-DVU5RLCS.js +0 -1
  88. package/dist/assets/remote-T24O58lH.js +0 -1
  89. package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
  90. package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
  91. package/dist/assets/security-config-CVPgqL7G.js +0 -1
  92. package/dist/assets/skeleton-C5tWata3.js +0 -1
  93. package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
  94. package/dist/assets/x-BEWSqeux.js +0 -1
@@ -11,15 +11,12 @@ export function createWorkspaceFileTab(
11
11
  return null;
12
12
  }
13
13
  const normalizedParentSessionKey = parentSessionKey?.trim() || null;
14
- const previewViewer = action.viewMode === 'preview'
15
- ? normalizeWorkspaceFilePreviewViewer(
16
- path,
17
- action.previewViewer ?? (action.line ? 'source' : undefined),
18
- )
19
- : action.previewViewer ?? null;
20
- const viewIdentity = action.viewMode === 'preview' && previewViewer === 'rendered'
21
- ? 'preview:rendered'
22
- : action.viewMode;
14
+ const previewViewer =
15
+ action.viewMode === 'preview'
16
+ ? normalizeWorkspaceFilePreviewViewer(path, action.previewViewer ?? (action.line ? 'source' : undefined))
17
+ : (action.previewViewer ?? null);
18
+ const viewIdentity =
19
+ action.viewMode === 'preview' && previewViewer === 'rendered' ? 'preview:rendered' : action.viewMode;
23
20
  return {
24
21
  key: `${normalizedParentSessionKey ?? 'draft'}::${viewIdentity}::${path}`,
25
22
  parentSessionKey: normalizedParentSessionKey,
@@ -53,28 +50,32 @@ export function upsertWorkspaceFileTab(
53
50
  nextTabs[existingIndex] = { ...tabs[existingIndex], ...nextTab };
54
51
  return nextTabs;
55
52
  }
56
- const adjacentIndex = adjacentToKey
57
- ? tabs.findIndex((tab) => tab.key === adjacentToKey)
58
- : -1;
53
+ const adjacentIndex = adjacentToKey ? tabs.findIndex((tab) => tab.key === adjacentToKey) : -1;
59
54
  if (adjacentIndex === -1) {
60
55
  return [nextTab, ...tabs];
61
56
  }
62
- return [
63
- ...tabs.slice(0, adjacentIndex + 1),
64
- nextTab,
65
- ...tabs.slice(adjacentIndex + 1),
66
- ];
57
+ return [...tabs.slice(0, adjacentIndex + 1), nextTab, ...tabs.slice(adjacentIndex + 1)];
67
58
  }
68
59
 
69
- export function reparentWorkspaceFileTab(
60
+ export function replaceWorkspaceFileTabPath(
70
61
  tab: ChatWorkspaceFileTab,
71
- parentSessionKey: string,
62
+ path: string,
63
+ label?: string | null,
72
64
  ): ChatWorkspaceFileTab {
65
+ const viewIdentity =
66
+ tab.viewMode === 'preview' && tab.previewViewer === 'rendered' ? 'preview:rendered' : tab.viewMode;
67
+ return {
68
+ ...tab,
69
+ key: `${tab.parentSessionKey ?? 'draft'}::${viewIdentity}::${path}`,
70
+ path,
71
+ label: label?.trim() || tab.label,
72
+ };
73
+ }
74
+
75
+ export function reparentWorkspaceFileTab(tab: ChatWorkspaceFileTab, parentSessionKey: string): ChatWorkspaceFileTab {
73
76
  const normalizedParentSessionKey = parentSessionKey.trim();
74
77
  const identitySeparatorIndex = tab.key.indexOf('::');
75
- const tabIdentity = identitySeparatorIndex >= 0
76
- ? tab.key.slice(identitySeparatorIndex)
77
- : `::${tab.key}`;
78
+ const tabIdentity = identitySeparatorIndex >= 0 ? tab.key.slice(identitySeparatorIndex) : `::${tab.key}`;
78
79
  return {
79
80
  ...tab,
80
81
  key: `${normalizedParentSessionKey}${tabIdentity}`,
@@ -2,12 +2,21 @@ export const CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH = 480;
2
2
  export const CHAT_WORKSPACE_PANEL_MIN_WIDTH = 360;
3
3
  export const CHAT_WORKSPACE_PANEL_MAX_WIDTH = 860;
4
4
 
5
+ export const CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH = 224;
6
+ export const CHAT_WORKSPACE_EXPLORER_MIN_WIDTH = 192;
7
+ export const CHAT_WORKSPACE_EXPLORER_MAX_WIDTH = 400;
8
+ export const CHAT_WORKSPACE_EXPLORER_COMPACT_THRESHOLD = 560;
9
+
5
10
  export function normalizeChatWorkspacePanelWidth(value: unknown): number {
6
11
  if (typeof value !== 'number' || !Number.isFinite(value)) {
7
12
  return CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH;
8
13
  }
9
- return Math.max(
10
- CHAT_WORKSPACE_PANEL_MIN_WIDTH,
11
- Math.min(CHAT_WORKSPACE_PANEL_MAX_WIDTH, value),
12
- );
14
+ return Math.max(CHAT_WORKSPACE_PANEL_MIN_WIDTH, Math.min(CHAT_WORKSPACE_PANEL_MAX_WIDTH, value));
15
+ }
16
+
17
+ export function normalizeChatWorkspaceExplorerWidth(value: unknown): number {
18
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
19
+ return CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH;
20
+ }
21
+ return Math.max(CHAT_WORKSPACE_EXPLORER_MIN_WIDTH, Math.min(CHAT_WORKSPACE_EXPLORER_MAX_WIDTH, value));
13
22
  }
@@ -1,129 +1,155 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { ChatComposerIntentManager } from "@/features/chat/managers/chat-composer-intent.manager";
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { ChatComposerIntentManager } from '@/features/chat/managers/chat-composer-intent.manager';
3
3
 
4
- describe("ChatComposerIntentManager", () => {
5
- it("delivers a file reference only to the targeted composer", () => {
4
+ describe('ChatComposerIntentManager', () => {
5
+ it('delivers a file reference only to the targeted composer', () => {
6
6
  const manager = new ChatComposerIntentManager();
7
7
  const draftListener = vi.fn();
8
8
  const sessionListener = vi.fn();
9
9
  manager.subscribe(null, draftListener);
10
- manager.subscribe("session-1", sessionListener);
10
+ manager.subscribe('session-1', sessionListener);
11
11
 
12
12
  manager.requestFileReference({
13
13
  targetSessionKey: null,
14
- tokenKey: "docs/guide.md",
15
- label: "guide.md",
14
+ tokenKey: 'docs/guide.md',
15
+ label: 'guide.md',
16
16
  });
17
17
 
18
18
  expect(draftListener).toHaveBeenCalledWith(
19
19
  expect.objectContaining({
20
20
  targetSessionKey: null,
21
- tokenKey: "docs/guide.md",
22
- label: "guide.md",
21
+ tokenKey: 'docs/guide.md',
22
+ label: 'guide.md',
23
23
  }),
24
24
  );
25
25
  expect(sessionListener).not.toHaveBeenCalled();
26
26
  });
27
27
 
28
- it("keeps an intent pending until its matching composer mounts", () => {
28
+ it('keeps an intent pending until its matching composer mounts', () => {
29
29
  const manager = new ChatComposerIntentManager();
30
30
  manager.requestFileReference({
31
- targetSessionKey: " session-1 ",
32
- tokenKey: " src/index.ts ",
33
- label: " index.ts ",
31
+ targetSessionKey: ' session-1 ',
32
+ tokenKey: ' src/index.ts ',
33
+ label: ' index.ts ',
34
34
  });
35
35
 
36
36
  expect(manager.consumePending(null)).toBeNull();
37
- expect(manager.consumePending("session-1")).toMatchObject({
38
- targetSessionKey: "session-1",
39
- tokenKey: "src/index.ts",
40
- label: "index.ts",
37
+ expect(manager.consumePending('session-1')).toMatchObject({
38
+ targetSessionKey: 'session-1',
39
+ tokenKey: 'src/index.ts',
40
+ label: 'index.ts',
41
41
  });
42
- expect(manager.consumePending("session-1")).toBeNull();
42
+ expect(manager.consumePending('session-1')).toBeNull();
43
43
  });
44
44
 
45
- it("ignores incomplete file references", () => {
45
+ it('publishes a directory reference with the workspace directory kind', () => {
46
+ const manager = new ChatComposerIntentManager();
47
+ const listener = vi.fn();
48
+ manager.subscribe('session-1', listener);
49
+
50
+ manager.requestDirectoryReference({
51
+ targetSessionKey: 'session-1',
52
+ tokenKey: 'docs/designs',
53
+ label: 'designs',
54
+ });
55
+
56
+ expect(listener).toHaveBeenCalledWith(
57
+ expect.objectContaining({
58
+ kind: 'workspace_directory',
59
+ tokenKey: 'docs/designs',
60
+ label: 'designs',
61
+ }),
62
+ );
63
+ });
64
+
65
+ it('ignores incomplete file references', () => {
46
66
  const manager = new ChatComposerIntentManager();
47
67
  const listener = vi.fn();
48
68
  manager.subscribe(null, listener);
49
69
 
50
70
  manager.requestFileReference({
51
71
  targetSessionKey: null,
52
- tokenKey: " ",
53
- label: "README.md",
72
+ tokenKey: ' ',
73
+ label: 'README.md',
54
74
  });
55
75
 
56
76
  expect(listener).not.toHaveBeenCalled();
57
77
  expect(manager.consumePending(null)).toBeNull();
58
78
  });
59
79
 
60
- it("publishes a source-aware excerpt with a stable content identity", () => {
80
+ it('publishes a source-aware excerpt with a stable content identity', () => {
61
81
  const manager = new ChatComposerIntentManager();
62
82
  const listener = vi.fn();
63
- manager.subscribe("session-1", listener);
83
+ manager.subscribe('session-1', listener);
64
84
 
65
85
  manager.requestExcerptReference({
66
- targetSessionKey: "session-1",
67
- path: "docs/guide.md",
68
- label: "guide.md",
69
- excerpt: "selected text",
86
+ targetSessionKey: 'session-1',
87
+ path: 'docs/guide.md',
88
+ label: 'guide.md',
89
+ excerpt: 'selected text',
70
90
  startLine: 12,
71
91
  endLine: 13,
72
92
  });
73
93
 
74
- expect(listener).toHaveBeenCalledWith(expect.objectContaining({
75
- kind: "workspace_excerpt",
76
- path: "docs/guide.md",
77
- label: "guide.md",
78
- excerpt: "selected text",
79
- startLine: 12,
80
- endLine: 13,
81
- tokenKey: expect.stringMatching(/^docs\/guide\.md#excerpt-/),
82
- }));
94
+ expect(listener).toHaveBeenCalledWith(
95
+ expect.objectContaining({
96
+ kind: 'workspace_excerpt',
97
+ path: 'docs/guide.md',
98
+ label: 'guide.md',
99
+ excerpt: 'selected text',
100
+ startLine: 12,
101
+ endLine: 13,
102
+ tokenKey: expect.stringMatching(/^docs\/guide\.md#excerpt-/),
103
+ }),
104
+ );
83
105
  });
84
106
 
85
- it("delivers an excerpt to the unmaterialized new-chat composer", () => {
107
+ it('delivers an excerpt to the unmaterialized new-chat composer', () => {
86
108
  const manager = new ChatComposerIntentManager();
87
109
  const draftListener = vi.fn();
88
110
  manager.subscribe(null, draftListener);
89
111
 
90
112
  manager.requestExcerptReference({
91
113
  targetSessionKey: null,
92
- path: "package.json",
93
- label: "package.json",
114
+ path: 'package.json',
115
+ label: 'package.json',
94
116
  excerpt: '"name": "nextclaw"',
95
117
  startLine: 2,
96
118
  endLine: 2,
97
119
  });
98
120
 
99
- expect(draftListener).toHaveBeenCalledWith(expect.objectContaining({
100
- targetSessionKey: null,
101
- kind: "workspace_excerpt",
102
- path: "package.json",
103
- excerpt: '"name": "nextclaw"',
104
- }));
121
+ expect(draftListener).toHaveBeenCalledWith(
122
+ expect.objectContaining({
123
+ targetSessionKey: null,
124
+ kind: 'workspace_excerpt',
125
+ path: 'package.json',
126
+ excerpt: '"name": "nextclaw"',
127
+ }),
128
+ );
105
129
  });
106
130
 
107
- it("publishes a conversation excerpt with its source message identity", () => {
131
+ it('publishes a conversation excerpt with its source message identity', () => {
108
132
  const manager = new ChatComposerIntentManager();
109
133
  const listener = vi.fn();
110
- manager.subscribe("session-1", listener);
134
+ manager.subscribe('session-1', listener);
111
135
 
112
136
  manager.requestConversationExcerptReference({
113
- targetSessionKey: "session-1",
114
- messageId: "assistant-message-1",
115
- role: "assistant",
116
- label: "AI reply",
117
- excerpt: "Keep the visible tag concise.",
137
+ targetSessionKey: 'session-1',
138
+ messageId: 'assistant-message-1',
139
+ role: 'assistant',
140
+ label: 'AI reply',
141
+ excerpt: 'Keep the visible tag concise.',
118
142
  });
119
143
 
120
- expect(listener).toHaveBeenCalledWith(expect.objectContaining({
121
- kind: "conversation_excerpt",
122
- messageId: "assistant-message-1",
123
- role: "assistant",
124
- label: "AI reply",
125
- excerpt: "Keep the visible tag concise.",
126
- tokenKey: expect.stringMatching(/^assistant-message-1#excerpt-/),
127
- }));
144
+ expect(listener).toHaveBeenCalledWith(
145
+ expect.objectContaining({
146
+ kind: 'conversation_excerpt',
147
+ messageId: 'assistant-message-1',
148
+ role: 'assistant',
149
+ label: 'AI reply',
150
+ excerpt: 'Keep the visible tag concise.',
151
+ tokenKey: expect.stringMatching(/^assistant-message-1#excerpt-/),
152
+ }),
153
+ );
128
154
  });
129
155
  });