@nextclaw/ui 0.15.5 → 0.15.6

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 (147) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/assets/api-Dyb5rgh0.js +13 -0
  3. package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
  4. package/dist/assets/{appearance-settings-page-80fLa4-9.js → appearance-settings-page-DFm3BbEb.js} +1 -1
  5. package/dist/assets/{book-open-CN7Shpij.js → book-open-L6LCuqOr.js} +1 -1
  6. package/dist/assets/{channels-list-page-R7M6pzp6.js → channels-list-page-DOaPKNJA.js} +2 -2
  7. package/dist/assets/{chat-page-152YuwfU.js → chat-page-Cw9uDhD_.js} +1 -1
  8. package/dist/assets/{config-split-page-BlfvfmTm.js → config-split-page-CqlqjnCK.js} +1 -1
  9. package/dist/assets/{confirm-dialog-Dt8Kdbgp.js → confirm-dialog-bAp8NU93.js} +1 -1
  10. package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
  11. package/dist/assets/{dist-D5ycbwCo.js → dist-CrtbGLlh.js} +1 -1
  12. package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
  13. package/dist/assets/{doc-browser-C9u7N9vH.js → doc-browser-BmnQa30-.js} +1 -1
  14. package/dist/assets/{doc-browser-context-CCdW9TJf.js → doc-browser-context-BlpUUFrR.js} +1 -1
  15. package/dist/assets/{download-CyPgglKr.js → download-BhZZNXD8.js} +1 -1
  16. package/dist/assets/{external-link-BahW1sl3.js → external-link-B9Uoq5J7.js} +1 -1
  17. package/dist/assets/{eye-D8gGXMkD.js → eye-K5knauEM.js} +1 -1
  18. package/dist/assets/{folder-DGWqFDvE.js → folder-BIYZZWJv.js} +1 -1
  19. package/dist/assets/{form-actions-D2imZLnU.js → form-actions-CKprWFeM.js} +1 -1
  20. package/dist/assets/index-BVn702m4.css +1 -0
  21. package/dist/assets/{index-CT0iYlxV.js → index-CFxJ1Fkg.js} +2 -2
  22. package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
  23. package/dist/assets/{logo-badge-CuPetu-v.js → logo-badge-BQ-XFZod.js} +1 -1
  24. package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
  25. package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
  26. package/dist/assets/{model-config-page-DiJcvBML.js → model-config-page-BbWbW_qM.js} +1 -1
  27. package/dist/assets/plus-TmFdwO3P.js +1 -0
  28. package/dist/assets/{popover-Btp7KVc3.js → popover-CX9zFsuc.js} +1 -1
  29. package/dist/assets/{provider-scoped-model-input-DZMtoAJG.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
  30. package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
  31. package/dist/assets/{react-BY-U3h3t.js → react-DsgudwpT.js} +1 -1
  32. package/dist/assets/{refresh-cw-Cme5qFej.js → refresh-cw-kXdvbhFf.js} +1 -1
  33. package/dist/assets/remote-B9F2uCk9.js +1 -0
  34. package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
  35. package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
  36. package/dist/assets/{save-DNg2yJTS.js → save-C9qpz2_2.js} +1 -1
  37. package/dist/assets/{search-3ykfrAFt.js → search-_kEi29qo.js} +1 -1
  38. package/dist/assets/{search-config-page-DrVKa_Ls.js → search-config-page-H7ug8f4D.js} +1 -1
  39. package/dist/assets/{secrets-config-page-B39uQ5kF.js → secrets-config-page-C1_6rINu.js} +2 -2
  40. package/dist/assets/security-config-Cr2ERKLu.js +1 -0
  41. package/dist/assets/{select-B5EecXJt.js → select-MJKhKAWR.js} +1 -1
  42. package/dist/assets/{setting-row-Qv1Ps79v.js → setting-row-DA3tJuew.js} +1 -1
  43. package/dist/assets/{settings-2-D-3um9Az.js → settings-2-D_y7zXJz.js} +1 -1
  44. package/dist/assets/{skeleton-BgkzQd4U.js → skeleton-0uTLUhm9.js} +1 -1
  45. package/dist/assets/{tabs-BVD8gp3y.js → tabs-h44XPocd.js} +1 -1
  46. package/dist/assets/{tag-chip-0qn-f3Bo.js → tag-chip-bRZ04q3C.js} +1 -1
  47. package/dist/assets/{trash-2-cnsMppRC.js → trash-2-BRpSE-Wt.js} +1 -1
  48. package/dist/assets/use-config-CwDEAl6f.js +1 -0
  49. package/dist/assets/{use-confirm-dialog-C_o0eQeH.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
  50. package/dist/assets/{use-viewport-layout-BWav4KCL.js → use-viewport-layout-CqgIMDQL.js} +1 -1
  51. package/dist/assets/x-DdEDL6ju.js +1 -0
  52. package/dist/index.html +29 -29
  53. package/package.json +8 -8
  54. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
  55. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
  56. package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
  57. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
  58. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
  59. package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
  60. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
  61. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
  62. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
  63. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +8 -9
  64. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
  65. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
  66. package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
  67. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
  68. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
  69. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
  70. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +23 -0
  71. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
  72. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
  73. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +4 -1
  74. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
  75. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
  76. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
  77. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
  78. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
  79. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +1 -0
  80. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
  81. package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
  82. package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
  83. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
  84. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
  85. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
  86. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
  87. package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
  88. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
  89. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
  90. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
  91. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
  92. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
  93. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
  94. package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
  95. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
  96. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
  97. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
  98. package/src/features/settings/components/config/provider-form.tsx +0 -4
  99. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
  100. package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
  101. package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
  102. package/src/features/system-status/index.ts +9 -0
  103. package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
  104. package/src/index.css +10 -16
  105. package/src/shared/components/common/__tests__/brand-header.test.tsx +110 -3
  106. package/src/shared/components/common/brand-header.tsx +121 -35
  107. package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
  108. package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
  109. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
  110. package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
  111. package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
  112. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
  113. package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
  114. package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
  115. package/src/shared/hooks/use-projects.ts +28 -0
  116. package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
  117. package/src/shared/hooks/use-server-path-search.ts +40 -0
  118. package/src/shared/lib/api/index.ts +1 -0
  119. package/src/shared/lib/api/projects/project.utils.ts +14 -0
  120. package/src/shared/lib/api/types.ts +27 -0
  121. package/src/shared/lib/api/utils/server-path.utils.ts +21 -1
  122. package/src/shared/lib/i18n/locales/en-US/chat.json +35 -2
  123. package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
  124. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  125. package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
  126. package/src/shared/lib/i18n/locales/zh-CN/chat.json +35 -2
  127. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
  128. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  129. package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
  130. package/dist/assets/api-IlX_CbwU.js +0 -13
  131. package/dist/assets/app-presenter-provider-Bbl_MVbg.js +0 -106
  132. package/dist/assets/desktop-update-config-DreMJu9z.js +0 -1
  133. package/dist/assets/doc-browser-DJQ5XPsi2.js +0 -1
  134. package/dist/assets/index-0av6WO9-.css +0 -1
  135. package/dist/assets/loader-circle-DtKek-qG.js +0 -1
  136. package/dist/assets/mcp-marketplace-page-4qMwFDpz.js +0 -1
  137. package/dist/assets/mcp-marketplace-page-BbxN5aZF.js +0 -9
  138. package/dist/assets/plus-BwgE-Zwf.js +0 -1
  139. package/dist/assets/providers-config-page-DfiWveEF.js +0 -1
  140. package/dist/assets/remote-BaVECEZw.js +0 -1
  141. package/dist/assets/rotate-cw-CMRZxIkH.js +0 -1
  142. package/dist/assets/runtime-config-page-wygwvMB4.js +0 -1
  143. package/dist/assets/security-config-BPzet76z.js +0 -1
  144. package/dist/assets/use-config-D6yx0lb0.js +0 -1
  145. package/dist/assets/x-B3o1dWR5.js +0 -1
  146. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
  147. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
@@ -0,0 +1,58 @@
1
+ import { Button } from '@/shared/components/ui/button';
2
+ import { DialogFooter } from '@/shared/components/ui/dialog';
3
+ import { Input } from '@/shared/components/ui/input';
4
+ import { Label } from '@/shared/components/ui/label';
5
+ import { t } from '@/shared/lib/i18n';
6
+
7
+ type ServerPathPickerFooterProps = {
8
+ confirmLabel: string;
9
+ disabled: boolean;
10
+ hint?: string;
11
+ isSaving: boolean;
12
+ onCancel: () => void;
13
+ onSelectedPathChange: (path: string) => void;
14
+ pathLabel: string;
15
+ pathPlaceholder?: string;
16
+ selectedPath: string;
17
+ };
18
+
19
+ export function ServerPathPickerFooter({
20
+ confirmLabel,
21
+ disabled,
22
+ hint,
23
+ isSaving,
24
+ onCancel,
25
+ onSelectedPathChange,
26
+ pathLabel,
27
+ pathPlaceholder,
28
+ selectedPath,
29
+ }: ServerPathPickerFooterProps) {
30
+ return (
31
+ <div className="min-w-0 border-t border-border bg-muted/20 px-4 py-3">
32
+ <div className="flex min-w-0 items-center gap-3">
33
+ <Label htmlFor="server-path-picker-selection" className="shrink-0 text-sm">
34
+ {pathLabel}
35
+ </Label>
36
+ <Input
37
+ id="server-path-picker-selection"
38
+ value={selectedPath}
39
+ onChange={(event) => onSelectedPathChange(event.target.value)}
40
+ placeholder={pathPlaceholder}
41
+ disabled={isSaving}
42
+ className="h-9 min-w-0 flex-1 bg-background"
43
+ />
44
+ <DialogFooter className="shrink-0 flex-row gap-2">
45
+ <Button type="button" variant="outline" onClick={onCancel} disabled={isSaving}>
46
+ {t('cancel')}
47
+ </Button>
48
+ <Button type="submit" disabled={disabled}>
49
+ {isSaving ? t('saving') : confirmLabel}
50
+ </Button>
51
+ </DialogFooter>
52
+ </div>
53
+ {hint ? (
54
+ <p className="mt-2 text-xs leading-relaxed text-muted-foreground">{hint}</p>
55
+ ) : null}
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,148 @@
1
+ import {
2
+ AppWindow,
3
+ Cloud,
4
+ Download,
5
+ FileText,
6
+ FolderKanban,
7
+ HardDrive,
8
+ Home,
9
+ Monitor,
10
+ Star,
11
+ } from 'lucide-react';
12
+ import type { ServerPathLocationView } from '@/shared/lib/api';
13
+ import { cn } from '@/shared/lib/utils';
14
+ import { t } from '@/shared/lib/i18n';
15
+
16
+ type ServerPathPickerLocationItem = {
17
+ icon: 'applications' | 'desktop' | 'documents' | 'downloads' | 'home' | 'icloud-drive' | 'root' | 'volumes' | 'workspace';
18
+ label: string;
19
+ path: string;
20
+ };
21
+
22
+ type ServerPathPickerLocationsProps = {
23
+ currentPath: string;
24
+ defaultWorkspacePath?: string | null;
25
+ disabled: boolean;
26
+ homePath?: string | null;
27
+ locations: readonly ServerPathLocationView[];
28
+ rootPath?: string | null;
29
+ onNavigate: (path: string) => void;
30
+ };
31
+
32
+ const locationIcons = {
33
+ applications: AppWindow,
34
+ desktop: Monitor,
35
+ documents: FileText,
36
+ downloads: Download,
37
+ home: Home,
38
+ 'icloud-drive': Cloud,
39
+ workspace: FolderKanban,
40
+ root: HardDrive,
41
+ volumes: HardDrive,
42
+ };
43
+
44
+ const locationLabelKeys: Record<ServerPathLocationView['kind'], string> = {
45
+ applications: 'pathPickerApplications',
46
+ desktop: 'pathPickerDesktop',
47
+ documents: 'pathPickerDocuments',
48
+ downloads: 'pathPickerDownloads',
49
+ 'icloud-drive': 'pathPickerICloudDrive',
50
+ volumes: 'pathPickerVolumes',
51
+ };
52
+
53
+ export function ServerPathPickerLocations({
54
+ currentPath,
55
+ defaultWorkspacePath,
56
+ disabled,
57
+ homePath,
58
+ locations,
59
+ rootPath,
60
+ onNavigate,
61
+ }: ServerPathPickerLocationsProps) {
62
+ const favoriteItems: ServerPathPickerLocationItem[] = [];
63
+ const locationItems: ServerPathPickerLocationItem[] = [];
64
+ const knownPaths = new Set<string>();
65
+ const appendLocation = (
66
+ target: ServerPathPickerLocationItem[],
67
+ item: ServerPathPickerLocationItem,
68
+ ) => {
69
+ const path = item.path.trim();
70
+ if (!path || knownPaths.has(path)) {
71
+ return;
72
+ }
73
+ knownPaths.add(path);
74
+ target.push({ ...item, path });
75
+ };
76
+
77
+ if (defaultWorkspacePath) {
78
+ appendLocation(favoriteItems, {
79
+ icon: 'workspace',
80
+ label: t('pathPickerDefaultWorkspace'),
81
+ path: defaultWorkspacePath,
82
+ });
83
+ }
84
+ if (homePath) {
85
+ appendLocation(favoriteItems, {
86
+ icon: 'home',
87
+ label: t('homeDirectory'),
88
+ path: homePath,
89
+ });
90
+ }
91
+ for (const location of locations) {
92
+ appendLocation(location.kind === 'volumes' ? locationItems : favoriteItems, {
93
+ icon: location.kind,
94
+ label: t(locationLabelKeys[location.kind]),
95
+ path: location.path,
96
+ });
97
+ }
98
+ if (rootPath) {
99
+ appendLocation(locationItems, {
100
+ icon: 'root',
101
+ label: t('pathPickerFileSystem'),
102
+ path: rootPath,
103
+ });
104
+ }
105
+
106
+ const renderItems = (items: ServerPathPickerLocationItem[]) => items.map((item) => {
107
+ const Icon = locationIcons[item.icon];
108
+ const active = currentPath === item.path;
109
+ return (
110
+ <button
111
+ key={item.path}
112
+ type="button"
113
+ className={cn(
114
+ 'flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
115
+ active ? 'bg-primary/12 text-primary' : 'text-foreground hover:bg-accent',
116
+ )}
117
+ aria-current={active ? 'location' : undefined}
118
+ onClick={() => onNavigate(item.path)}
119
+ disabled={disabled}
120
+ >
121
+ <Icon className={cn('h-4 w-4 shrink-0', active ? 'text-primary' : 'text-amber-500')} />
122
+ <span className="truncate">{item.label}</span>
123
+ </button>
124
+ );
125
+ });
126
+
127
+ return (
128
+ <aside className="hidden min-h-0 w-48 shrink-0 overflow-y-auto border-r border-border bg-muted/20 p-2 sm:block">
129
+ <div className="mb-1 flex items-center gap-2 px-2 py-1.5 text-xs font-medium text-muted-foreground">
130
+ <Star className="h-3.5 w-3.5" />
131
+ {t('pathPickerQuickAccess')}
132
+ </div>
133
+ <nav aria-label={t('pathPickerQuickAccess')} className="space-y-0.5">
134
+ {renderItems(favoriteItems)}
135
+ </nav>
136
+ {locationItems.length > 0 ? (
137
+ <>
138
+ <div className="mb-1 mt-3 px-2 py-1.5 text-xs font-medium text-muted-foreground">
139
+ {t('pathPickerLocations')}
140
+ </div>
141
+ <nav aria-label={t('pathPickerLocations')} className="space-y-0.5">
142
+ {renderItems(locationItems)}
143
+ </nav>
144
+ </>
145
+ ) : null}
146
+ </aside>
147
+ );
148
+ }
@@ -0,0 +1,198 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ ArrowLeft,
4
+ ArrowRight,
5
+ ChevronRight,
6
+ Folder,
7
+ FolderUp,
8
+ Pencil,
9
+ RefreshCcw,
10
+ Search,
11
+ } from 'lucide-react';
12
+ import type { ServerPathBreadcrumbView } from '@/shared/lib/api';
13
+ import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
14
+ import { Input } from '@/shared/components/ui/input';
15
+ import { t } from '@/shared/lib/i18n';
16
+
17
+ type ServerPathPickerNavigationBarProps = {
18
+ addressPath: string;
19
+ breadcrumbs: readonly ServerPathBreadcrumbView[];
20
+ canGoBack: boolean;
21
+ canGoForward: boolean;
22
+ canGoUp: boolean;
23
+ disabled: boolean;
24
+ onBack: () => void;
25
+ onForward: () => void;
26
+ onNavigate: (path: string) => void;
27
+ onRefresh: () => void;
28
+ onSearchChange: (value: string) => void;
29
+ onUp: () => void;
30
+ searchText: string;
31
+ };
32
+
33
+ export function ServerPathPickerNavigationBar({
34
+ addressPath,
35
+ breadcrumbs,
36
+ canGoBack,
37
+ canGoForward,
38
+ canGoUp,
39
+ disabled,
40
+ onBack,
41
+ onForward,
42
+ onNavigate,
43
+ onRefresh,
44
+ onSearchChange,
45
+ onUp,
46
+ searchText,
47
+ }: ServerPathPickerNavigationBarProps) {
48
+ const [editingAddress, setEditingAddress] = useState(false);
49
+ const [addressDraft, setAddressDraft] = useState('');
50
+ const addressBreadcrumbs =
51
+ breadcrumbs.length > 5
52
+ ? [breadcrumbs[0], ...breadcrumbs.slice(-4)]
53
+ : breadcrumbs;
54
+ const breadcrumbsCollapsed = addressBreadcrumbs.length < breadcrumbs.length;
55
+
56
+ const beginAddressEdit = () => {
57
+ if (disabled) {
58
+ return;
59
+ }
60
+ setAddressDraft(addressPath);
61
+ setEditingAddress(true);
62
+ };
63
+
64
+ const cancelAddressEdit = () => {
65
+ setAddressDraft('');
66
+ setEditingAddress(false);
67
+ };
68
+
69
+ const openAddress = () => {
70
+ const nextPath = addressDraft.trim();
71
+ if (!nextPath) {
72
+ return;
73
+ }
74
+ setEditingAddress(false);
75
+ onNavigate(nextPath);
76
+ };
77
+
78
+ return (
79
+ <div className="flex min-w-0 items-center gap-1.5 overflow-hidden border-b border-border bg-muted/25 px-3 py-2">
80
+ <IconActionButton
81
+ icon={<ArrowLeft className="h-4 w-4" />}
82
+ label={t('pathPickerBack')}
83
+ size="lg"
84
+ onClick={onBack}
85
+ disabled={!canGoBack || disabled}
86
+ />
87
+ <IconActionButton
88
+ icon={<ArrowRight className="h-4 w-4" />}
89
+ label={t('pathPickerForward')}
90
+ size="lg"
91
+ onClick={onForward}
92
+ disabled={!canGoForward || disabled}
93
+ />
94
+ <IconActionButton
95
+ icon={<FolderUp className="h-4 w-4" />}
96
+ label={t('parentDirectory')}
97
+ size="lg"
98
+ onClick={onUp}
99
+ disabled={!canGoUp || disabled}
100
+ />
101
+
102
+ <div className="ml-1 flex h-9 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-border bg-background shadow-sm focus-within:ring-1 focus-within:ring-primary/50">
103
+ {editingAddress ? (
104
+ <Input
105
+ value={addressDraft}
106
+ onChange={(event) => setAddressDraft(event.target.value)}
107
+ onBlur={cancelAddressEdit}
108
+ onKeyDown={(event) => {
109
+ if (event.key === 'Enter') {
110
+ event.preventDefault();
111
+ openAddress();
112
+ }
113
+ if (event.key === 'Escape') {
114
+ event.preventDefault();
115
+ cancelAddressEdit();
116
+ }
117
+ }}
118
+ aria-label={t('pathPickerAddressLabel')}
119
+ className="h-full rounded-none border-0 bg-transparent px-3 shadow-none focus-visible:ring-0"
120
+ autoFocus
121
+ />
122
+ ) : (
123
+ <>
124
+ <Folder className="ml-3 h-4 w-4 shrink-0 text-amber-500" />
125
+ <div
126
+ className="flex min-w-0 flex-1 items-center overflow-hidden pl-2"
127
+ onDoubleClick={beginAddressEdit}
128
+ >
129
+ {addressBreadcrumbs.length > 0 ? (
130
+ addressBreadcrumbs.map((breadcrumb, index) => (
131
+ <div key={breadcrumb.path} className="flex min-w-0 items-center">
132
+ {index > 0 ? (
133
+ <>
134
+ <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
135
+ {breadcrumbsCollapsed && index === 1 ? (
136
+ <>
137
+ <button
138
+ type="button"
139
+ className="rounded px-1.5 py-1 text-sm text-muted-foreground hover:bg-accent"
140
+ aria-label={t('pathPickerShowFullAddress')}
141
+ onClick={beginAddressEdit}
142
+ >
143
+
144
+ </button>
145
+ <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
146
+ </>
147
+ ) : null}
148
+ </>
149
+ ) : null}
150
+ <button
151
+ type="button"
152
+ className="max-w-40 truncate rounded px-1.5 py-1 text-sm text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
153
+ onClick={() => onNavigate(breadcrumb.path)}
154
+ disabled={disabled}
155
+ >
156
+ {breadcrumb.label}
157
+ </button>
158
+ </div>
159
+ ))
160
+ ) : (
161
+ <span className="truncate text-sm text-foreground">{addressPath}</span>
162
+ )}
163
+ <span className="h-full min-w-2 flex-1 cursor-text" aria-hidden="true" />
164
+ <IconActionButton
165
+ icon={<Pencil className="h-3.5 w-3.5" />}
166
+ label={t('pathPickerEditAddress')}
167
+ size="md"
168
+ tooltipSide="bottom"
169
+ onClick={beginAddressEdit}
170
+ disabled={disabled}
171
+ className="mr-1"
172
+ />
173
+ </div>
174
+ </>
175
+ )}
176
+ </div>
177
+
178
+ <IconActionButton
179
+ icon={<RefreshCcw className="h-4 w-4" />}
180
+ label={t('chatRefresh')}
181
+ size="lg"
182
+ onClick={onRefresh}
183
+ disabled={disabled}
184
+ />
185
+
186
+ <div className="relative hidden w-56 shrink-0 sm:block">
187
+ <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
188
+ <Input
189
+ value={searchText}
190
+ onChange={(event) => onSearchChange(event.target.value)}
191
+ placeholder={t('pathPickerSearchPlaceholder')}
192
+ disabled={disabled}
193
+ className="h-9 rounded-md bg-background pl-9"
194
+ />
195
+ </div>
196
+ </div>
197
+ );
198
+ }
@@ -0,0 +1,59 @@
1
+ import type { KeyboardEvent } from 'react';
2
+ import { Button } from '@/shared/components/ui/button';
3
+ import { Input } from '@/shared/components/ui/input';
4
+ import { t } from '@/shared/lib/i18n';
5
+
6
+ type ServerPathPickerNewFolderProps = {
7
+ errorMessage: string | null;
8
+ isCreating: boolean;
9
+ name: string;
10
+ onCancel: () => void;
11
+ onCreate: () => void;
12
+ onNameChange: (name: string) => void;
13
+ };
14
+
15
+ export function ServerPathPickerNewFolder({
16
+ errorMessage,
17
+ isCreating,
18
+ name,
19
+ onCancel,
20
+ onCreate,
21
+ onNameChange,
22
+ }: ServerPathPickerNewFolderProps) {
23
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
24
+ if (event.key === 'Enter') {
25
+ event.preventDefault();
26
+ onCreate();
27
+ }
28
+ if (event.key === 'Escape') {
29
+ onCancel();
30
+ }
31
+ };
32
+
33
+ return (
34
+ <div className="border-b border-gray-200 bg-white px-3 py-2">
35
+ <div className="flex gap-2">
36
+ <Input
37
+ value={name}
38
+ onChange={(event) => onNameChange(event.target.value)}
39
+ onKeyDown={handleKeyDown}
40
+ placeholder={t('pathPickerNewFolderPlaceholder')}
41
+ aria-label={t('pathPickerNewFolderName')}
42
+ autoFocus
43
+ disabled={isCreating}
44
+ />
45
+ <Button type="button" onClick={onCreate} disabled={!name.trim() || isCreating}>
46
+ {isCreating ? t('saving') : t('pathPickerCreateFolder')}
47
+ </Button>
48
+ <Button type="button" variant="outline" onClick={onCancel} disabled={isCreating}>
49
+ {t('cancel')}
50
+ </Button>
51
+ </div>
52
+ {errorMessage ? (
53
+ <p role="alert" className="mt-2 text-sm text-destructive">
54
+ {t('pathPickerCreateFolderFailed')}: {errorMessage}
55
+ </p>
56
+ ) : null}
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,25 @@
1
+ import { FolderPlus } from 'lucide-react';
2
+ import { Button } from '@/shared/components/ui/button';
3
+ import { t } from '@/shared/lib/i18n';
4
+
5
+ type ServerPathPickerToolbarProps = {
6
+ disabled: boolean;
7
+ onNewFolder: () => void;
8
+ };
9
+
10
+ export function ServerPathPickerToolbar({
11
+ disabled,
12
+ onNewFolder,
13
+ }: ServerPathPickerToolbarProps) {
14
+ return (
15
+ <div className="flex items-center border-b border-border bg-background px-3 py-1.5">
16
+ <Button type="button" variant="ghost" size="sm" onClick={onNewFolder} disabled={disabled}>
17
+ <FolderPlus className="mr-1 h-4 w-4" />
18
+ {t('pathPickerNewFolder')}
19
+ </Button>
20
+ <span className="ml-auto text-xs text-muted-foreground">
21
+ {t('pathPickerDoubleClickHint')}
22
+ </span>
23
+ </div>
24
+ );
25
+ }
@@ -0,0 +1,28 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { toast } from "sonner";
3
+ import { createProject, fetchProjects } from "@/shared/lib/api";
4
+ import { t } from "@/shared/lib/i18n";
5
+
6
+ const PROJECTS_QUERY_KEY = ["projects"] as const;
7
+
8
+ export function useProjects() {
9
+ return useQuery({
10
+ queryKey: PROJECTS_QUERY_KEY,
11
+ queryFn: fetchProjects,
12
+ staleTime: 30_000,
13
+ });
14
+ }
15
+
16
+ export function useCreateProject() {
17
+ const queryClient = useQueryClient();
18
+ return useMutation({
19
+ mutationFn: createProject,
20
+ onSuccess: async () => {
21
+ await queryClient.invalidateQueries({ queryKey: PROJECTS_QUERY_KEY });
22
+ toast.success(t("chatProjectCreated"));
23
+ },
24
+ onError: (error: Error) => {
25
+ toast.error(`${t("chatProjectCreateFailed")}: ${error.message}`);
26
+ },
27
+ });
28
+ }
@@ -0,0 +1,6 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { createServerPathDirectory } from '@/shared/lib/api';
3
+
4
+ export function useServerPathCreateDirectory() {
5
+ return useMutation({ mutationFn: createServerPathDirectory });
6
+ }
@@ -0,0 +1,40 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { fetchServerPathSearch } from '@/shared/lib/api';
3
+
4
+ export function buildServerPathSearchQueryKey(params: {
5
+ basePath: string;
6
+ query?: string | null;
7
+ limit?: number | null;
8
+ }) {
9
+ return [
10
+ 'server-path-search',
11
+ params.basePath.trim(),
12
+ params.query?.trim() ?? '',
13
+ params.limit ?? null,
14
+ ] as const;
15
+ }
16
+
17
+ export function useServerPathSearch(params: {
18
+ basePath: string;
19
+ query?: string | null;
20
+ limit?: number | null;
21
+ enabled?: boolean;
22
+ }) {
23
+ const { enabled, limit } = params;
24
+ const basePath = params.basePath.trim();
25
+ const query = params.query?.trim() ?? '';
26
+ return useQuery({
27
+ queryKey: buildServerPathSearchQueryKey({
28
+ basePath,
29
+ query,
30
+ limit,
31
+ }),
32
+ queryFn: () => fetchServerPathSearch({
33
+ basePath,
34
+ query,
35
+ limit,
36
+ }),
37
+ enabled: (enabled ?? true) && Boolean(basePath),
38
+ staleTime: 5_000,
39
+ });
40
+ }
@@ -10,6 +10,7 @@ export * from './utils/marketplace.utils';
10
10
  export * from './utils/mcp-marketplace.utils';
11
11
  export * from './utils/ncp-attachments.utils';
12
12
  export * from './utils/ncp-session.utils';
13
+ export * from './projects/project.utils';
13
14
  export * from './preferences/preference.utils';
14
15
  export * from './preferences/preference.types';
15
16
  export * from './preferences/preference-keys.config';
@@ -0,0 +1,14 @@
1
+ import { nextclawClient } from "@/shared/lib/api/managers/client.manager";
2
+ import type {
3
+ ProjectCreateRequest,
4
+ ProjectListView,
5
+ ProjectView,
6
+ } from "@nextclaw/client-sdk";
7
+
8
+ export async function fetchProjects(): Promise<ProjectListView> {
9
+ return await nextclawClient.projects.list();
10
+ }
11
+
12
+ export async function createProject(input: ProjectCreateRequest): Promise<ProjectView> {
13
+ return await nextclawClient.projects.create(input);
14
+ }
@@ -1,5 +1,6 @@
1
1
  import type { NcpSessionStatus } from '@nextclaw/ncp';
2
2
  import type { RuntimeEntryView, NcpSessionSummaryView } from './ncp-session.types';
3
+ export type { ProjectCreateRequest, ProjectListView, ProjectTemplateView, ProjectView } from '@nextclaw/client-sdk';
3
4
  export type { SessionEntryView, RuntimeEntryView, SessionTypeIconView, SessionMessageView, SessionEventView, NcpSessionSummaryView, NcpSessionsListView, NcpMessageView, NcpSessionMessagesView, SessionContextWindowView } from './ncp-session.types';
4
5
 
5
6
  // API Types - matching backend response format
@@ -359,14 +360,40 @@ export type ServerPathEntryView = { name: string; path: string; kind: "directory
359
360
 
360
361
  export type ServerPathBreadcrumbView = { label: string; path: string };
361
362
 
363
+ export type ServerPathLocationView = {
364
+ kind: "desktop" | "documents" | "downloads" | "icloud-drive" | "applications" | "volumes";
365
+ path: string;
366
+ };
367
+
362
368
  export type ServerPathBrowseView = {
363
369
  currentPath: string;
364
370
  parentPath: string | null;
365
371
  homePath: string;
366
372
  breadcrumbs: ServerPathBreadcrumbView[];
367
373
  entries: ServerPathEntryView[];
374
+ locations: ServerPathLocationView[];
368
375
  };
369
376
 
377
+ export type ServerPathSearchEntryView = {
378
+ name: string;
379
+ path: string;
380
+ relativePath: string;
381
+ parentRelativePath: string;
382
+ kind: "directory" | "file";
383
+ hidden: boolean;
384
+ };
385
+
386
+ export type ServerPathSearchView = {
387
+ basePath: string;
388
+ query: string;
389
+ entries: ServerPathSearchEntryView[];
390
+ truncated: boolean;
391
+ };
392
+
393
+ export type ServerPathDirectoryCreateRequest = { parentPath: string; name: string };
394
+
395
+ export type ServerPathDirectoryCreateView = { path: string };
396
+
370
397
  export type ServerPathReadView = { requestedPath: string; resolvedPath: string; kind: "text" | "markdown" | "binary"; sizeBytes: number; startLine?: number; truncated: boolean; text?: string; languageHint?: string | null };
371
398
 
372
399
  export type PanelAppEntryView = {
@@ -1,6 +1,12 @@
1
1
  import { API_BASE } from '@/shared/lib/api/api-base';
2
2
  import { nextclawClient } from '@/shared/lib/api/managers/client.manager';
3
- import type { ServerPathBrowseView, ServerPathReadView } from '@/shared/lib/api/types';
3
+ import type {
4
+ ServerPathBrowseView,
5
+ ServerPathDirectoryCreateRequest,
6
+ ServerPathDirectoryCreateView,
7
+ ServerPathReadView,
8
+ ServerPathSearchView,
9
+ } from '@/shared/lib/api/types';
4
10
 
5
11
  const SERVER_PATH_CONTENT_BASE_PATH = '/api/server-paths/content';
6
12
 
@@ -12,6 +18,12 @@ export async function fetchServerPathBrowse(params?: {
12
18
  return await nextclawClient.serverPaths.browse(params);
13
19
  }
14
20
 
21
+ export async function createServerPathDirectory(
22
+ input: ServerPathDirectoryCreateRequest,
23
+ ): Promise<ServerPathDirectoryCreateView> {
24
+ return await nextclawClient.serverPaths.createDirectory(input);
25
+ }
26
+
15
27
  export async function fetchServerPathRead(params: {
16
28
  path: string;
17
29
  basePath?: string | null;
@@ -20,6 +32,14 @@ export async function fetchServerPathRead(params: {
20
32
  return await nextclawClient.serverPaths.read(params);
21
33
  }
22
34
 
35
+ export async function fetchServerPathSearch(params: {
36
+ basePath: string;
37
+ query?: string | null;
38
+ limit?: number | null;
39
+ }): Promise<ServerPathSearchView> {
40
+ return await nextclawClient.serverPaths.search(params);
41
+ }
42
+
23
43
  export function buildServerPathContentUrl(path: string): string;
24
44
  export function buildServerPathContentUrl(
25
45
  path: string,