@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.
- package/CHANGELOG.md +21 -0
- package/dist/assets/{api-BxPNDp30.js → api-FK7tikI1.js} +3 -3
- package/dist/assets/appearance-settings-page-BYbvl2wj.js +1 -0
- package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CocdcIko.js} +2 -2
- package/dist/assets/chat-page-C5E-LLVd.js +1 -0
- package/dist/assets/{config-split-page-BVWVDbvG.js → config-split-page-CA_a0AJ5.js} +1 -1
- package/dist/assets/{confirm-dialog-mA2iVzxh.js → confirm-dialog-CxZfenjK.js} +1 -1
- package/dist/assets/{desktop-update-config-DyQb1LXr.js → desktop-update-config-BEKC8Ly9.js} +1 -1
- package/dist/assets/{dist-B4Pf5X3Q.js → dist-BH7W5yCi.js} +1 -1
- package/dist/assets/{dist-DHEWkGWX.js → dist-CaYdVVeO.js} +1 -1
- package/dist/assets/doc-browser-Cl2ldE3f.js +1 -0
- package/dist/assets/doc-browser-DXCSd63E.js +1 -0
- package/dist/assets/{doc-browser-context-CuGI6PQc.js → doc-browser-context-FDPs3gJf.js} +1 -1
- package/dist/assets/{form-actions-BOfIyG3O.js → form-actions-0c0aw_bl.js} +1 -1
- package/dist/assets/i18n-provider-DkITJNz8.js +1 -0
- package/dist/assets/index-CQ2LcI2p.css +1 -0
- package/dist/assets/index-CrdfvBVg.js +107 -0
- package/dist/assets/loader-circle-DHtUxCIA.js +1 -0
- package/dist/assets/{mcp-marketplace-page-ofDqbOIF.js → mcp-marketplace-page-Bc_jI5iH.js} +2 -2
- package/dist/assets/mcp-marketplace-page-CzCieD9r.js +1 -0
- package/dist/assets/{model-config-page-D73Tu3jW.js → model-config-page-C4jw-pcj.js} +1 -1
- package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-CVy0_kVl.js} +1 -1
- package/dist/assets/plus-CG1RwZQq.js +1 -0
- package/dist/assets/{provider-scoped-model-input-BaJJJYMX.js → provider-scoped-model-input-CmH4BFvc.js} +1 -1
- package/dist/assets/{providers-config-page-BL6IRVBm.js → providers-config-page-CJQjwzaw.js} +1 -1
- package/dist/assets/{react-BC_orpAj.js → react-DjFBVA0n.js} +1 -1
- package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-j2bl79Ea.js} +1 -1
- package/dist/assets/remote-BIlpD3yL.js +1 -0
- package/dist/assets/rotate-cw-bKG8zH8M.js +1 -0
- package/dist/assets/runtime-config-page-B_x2Glz5.js +1 -0
- package/dist/assets/{save-Cnr71JON.js → save-BKNrtK81.js} +1 -1
- package/dist/assets/{search-CCX11X2m.js → search-CkOm65W_.js} +1 -1
- package/dist/assets/{search-config-page-BB6d5gT7.js → search-config-page-BaIijtmO.js} +1 -1
- package/dist/assets/{secrets-config-page-DYsDeT9K.js → secrets-config-page-CJjtdFAl.js} +2 -2
- package/dist/assets/security-config-DB8apj09.js +1 -0
- package/dist/assets/{select-DvwBu99K.js → select-Ea7ffKCE.js} +1 -1
- package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-CtyN3m_V.js} +1 -1
- package/dist/assets/skeleton-Bt6zxcAB.js +1 -0
- package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-C9l4xsKp.js} +1 -1
- package/dist/assets/x-CowtCu9Z.js +1 -0
- package/dist/index.html +20 -20
- package/package.json +8 -8
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +30 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +573 -90
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +389 -288
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +149 -94
- package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +411 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +88 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +335 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
- package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
- package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
- package/src/features/chat/managers/chat-thread.manager.ts +132 -128
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +20 -40
- package/src/features/chat/stores/chat-thread.store.ts +47 -74
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
- package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
- package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
- package/src/shared/lib/api/managers/client.manager.ts +31 -17
- package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
- package/src/shared/lib/api/types.ts +145 -116
- package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
- package/src/shared/lib/i18n/locales/en-US/chat.json +42 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +42 -0
- package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
- package/dist/assets/chat-page-BuFDmG5_.js +0 -1
- package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
- package/dist/assets/doc-browser-eMUagyBV.js +0 -1
- package/dist/assets/index-BjBdWHV1.js +0 -107
- package/dist/assets/index-DmpmjAGQ.css +0 -1
- package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
- package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
- package/dist/assets/plus-DVU5RLCS.js +0 -1
- package/dist/assets/remote-T24O58lH.js +0 -1
- package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
- package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
- package/dist/assets/security-config-CVPgqL7G.js +0 -1
- package/dist/assets/skeleton-C5tWata3.js +0 -1
- package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
- package/dist/assets/x-BEWSqeux.js +0 -1
|
@@ -8,26 +8,40 @@ import {
|
|
|
8
8
|
type MouseEvent,
|
|
9
9
|
type ReactElement,
|
|
10
10
|
type ReactNode,
|
|
11
|
-
} from
|
|
12
|
-
import { createPortal } from
|
|
13
|
-
import { cn } from
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { createPortal } from 'react-dom';
|
|
13
|
+
import { cn } from '@/shared/lib/utils';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
type ContextMenuItemBase = {
|
|
16
16
|
key: string;
|
|
17
17
|
label: string;
|
|
18
18
|
icon?: ReactNode;
|
|
19
|
+
destructive?: boolean;
|
|
19
20
|
disabled?: boolean;
|
|
20
21
|
restoreFocus?: boolean;
|
|
21
|
-
onSelect: () => void;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
export type ContextMenuItem = ContextMenuItemBase &
|
|
25
|
+
(
|
|
26
|
+
| {
|
|
27
|
+
download?: string;
|
|
28
|
+
href: string;
|
|
29
|
+
onSelect?: () => void;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
download?: never;
|
|
33
|
+
href?: never;
|
|
34
|
+
onSelect: () => void;
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
24
38
|
export type ContextMenuGroup = {
|
|
25
39
|
key: string;
|
|
26
40
|
items: readonly ContextMenuItem[];
|
|
27
41
|
};
|
|
28
42
|
|
|
29
43
|
type ContextMenuPosition = {
|
|
30
|
-
align:
|
|
44
|
+
align: 'start' | 'end';
|
|
31
45
|
x: number;
|
|
32
46
|
y: number;
|
|
33
47
|
trigger: HTMLElement;
|
|
@@ -61,9 +75,7 @@ function ContextMenuSurface({
|
|
|
61
75
|
return;
|
|
62
76
|
}
|
|
63
77
|
const rect = menu.getBoundingClientRect();
|
|
64
|
-
const preferredLeft = position.align ===
|
|
65
|
-
? position.x - rect.width
|
|
66
|
-
: position.x;
|
|
78
|
+
const preferredLeft = position.align === 'end' ? position.x - rect.width : position.x;
|
|
67
79
|
const left = Math.max(
|
|
68
80
|
CONTEXT_MENU_EDGE_GAP,
|
|
69
81
|
Math.min(preferredLeft, window.innerWidth - rect.width - CONTEXT_MENU_EDGE_GAP),
|
|
@@ -74,22 +86,16 @@ function ContextMenuSurface({
|
|
|
74
86
|
);
|
|
75
87
|
menu.style.left = `${left}px`;
|
|
76
88
|
menu.style.top = `${top}px`;
|
|
77
|
-
menu.querySelector<
|
|
89
|
+
menu.querySelector<HTMLElement>('[role="menuitem"]:not(:disabled)')?.focus();
|
|
78
90
|
}, [position]);
|
|
79
91
|
|
|
80
92
|
const moveFocus = (direction: 1 | -1) => {
|
|
81
|
-
const items = Array.from(
|
|
82
|
-
menuRef.current?.querySelectorAll<HTMLButtonElement>(
|
|
83
|
-
'[role="menuitem"]:not(:disabled)',
|
|
84
|
-
) ?? [],
|
|
85
|
-
);
|
|
93
|
+
const items = Array.from(menuRef.current?.querySelectorAll<HTMLElement>('[role="menuitem"]:not(:disabled)') ?? []);
|
|
86
94
|
if (items.length === 0) {
|
|
87
95
|
return;
|
|
88
96
|
}
|
|
89
|
-
const currentIndex = items.indexOf(document.activeElement as
|
|
90
|
-
const nextIndex = currentIndex < 0
|
|
91
|
-
? 0
|
|
92
|
-
: (currentIndex + direction + items.length) % items.length;
|
|
97
|
+
const currentIndex = items.indexOf(document.activeElement as HTMLElement);
|
|
98
|
+
const nextIndex = currentIndex < 0 ? 0 : (currentIndex + direction + items.length) % items.length;
|
|
93
99
|
items[nextIndex]?.focus();
|
|
94
100
|
};
|
|
95
101
|
|
|
@@ -107,50 +113,78 @@ function ContextMenuSurface({
|
|
|
107
113
|
style={{ left: position.x, top: position.y }}
|
|
108
114
|
onPointerDown={(event) => event.stopPropagation()}
|
|
109
115
|
onKeyDown={(event) => {
|
|
110
|
-
if (event.key ===
|
|
116
|
+
if (event.key === 'Escape') {
|
|
111
117
|
event.preventDefault();
|
|
112
118
|
onClose(true);
|
|
113
|
-
} else if (event.key ===
|
|
119
|
+
} else if (event.key === 'ArrowDown') {
|
|
114
120
|
event.preventDefault();
|
|
115
121
|
moveFocus(1);
|
|
116
|
-
} else if (event.key ===
|
|
122
|
+
} else if (event.key === 'ArrowUp') {
|
|
117
123
|
event.preventDefault();
|
|
118
124
|
moveFocus(-1);
|
|
119
|
-
} else if (event.key ===
|
|
125
|
+
} else if (event.key === 'Home' || event.key === 'End') {
|
|
120
126
|
event.preventDefault();
|
|
121
|
-
const items = menuRef.current?.querySelectorAll<
|
|
122
|
-
|
|
123
|
-
);
|
|
124
|
-
items?.[event.key === "Home" ? 0 : items.length - 1]?.focus();
|
|
127
|
+
const items = menuRef.current?.querySelectorAll<HTMLElement>('[role="menuitem"]:not(:disabled)');
|
|
128
|
+
items?.[event.key === 'Home' ? 0 : items.length - 1]?.focus();
|
|
125
129
|
}
|
|
126
130
|
}}
|
|
127
131
|
>
|
|
128
132
|
{groups.map((group, groupIndex) => (
|
|
129
133
|
<div key={group.key} role="group">
|
|
130
134
|
{groupIndex > 0 ? <div className="my-1 h-px bg-border" /> : null}
|
|
131
|
-
{group.items.map((item) =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
135
|
+
{group.items.map((item) => {
|
|
136
|
+
const itemClassName = cn(
|
|
137
|
+
'flex h-8 w-full items-center gap-2 rounded-lg px-2.5 text-left text-[13px] font-medium outline-none transition-colors',
|
|
138
|
+
item.destructive
|
|
139
|
+
? 'text-destructive hover:bg-destructive/10 focus-visible:bg-destructive/10'
|
|
140
|
+
: 'text-foreground hover:bg-muted focus-visible:bg-muted',
|
|
141
|
+
'disabled:pointer-events-none disabled:opacity-45',
|
|
142
|
+
);
|
|
143
|
+
const content = (
|
|
144
|
+
<>
|
|
145
|
+
{item.icon ? (
|
|
146
|
+
<span
|
|
147
|
+
className={cn(
|
|
148
|
+
'inline-flex h-4 w-4 shrink-0 items-center justify-center',
|
|
149
|
+
item.destructive ? 'text-destructive' : 'text-muted-foreground',
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
{item.icon}
|
|
153
|
+
</span>
|
|
154
|
+
) : null}
|
|
155
|
+
<span className="min-w-0 flex-1 truncate">{item.label}</span>
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
return item.href ? (
|
|
159
|
+
<a
|
|
160
|
+
key={item.key}
|
|
161
|
+
role="menuitem"
|
|
162
|
+
className={itemClassName}
|
|
163
|
+
href={item.href}
|
|
164
|
+
download={item.download}
|
|
165
|
+
onClick={() => {
|
|
166
|
+
item.onSelect?.();
|
|
167
|
+
onClose(false);
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
{content}
|
|
171
|
+
</a>
|
|
172
|
+
) : (
|
|
173
|
+
<button
|
|
174
|
+
key={item.key}
|
|
175
|
+
type="button"
|
|
176
|
+
role="menuitem"
|
|
177
|
+
disabled={item.disabled}
|
|
178
|
+
className={itemClassName}
|
|
179
|
+
onClick={() => {
|
|
180
|
+
item.onSelect?.();
|
|
181
|
+
onClose(item.restoreFocus !== false);
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{content}
|
|
185
|
+
</button>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
154
188
|
</div>
|
|
155
189
|
))}
|
|
156
190
|
</div>
|
|
@@ -165,7 +199,7 @@ export function ContextMenu({
|
|
|
165
199
|
label,
|
|
166
200
|
}: {
|
|
167
201
|
children: ReactElement<{
|
|
168
|
-
|
|
202
|
+
'data-context-menu-open'?: string;
|
|
169
203
|
onContextMenu?: (event: MouseEvent) => void;
|
|
170
204
|
}>;
|
|
171
205
|
groups: readonly ContextMenuGroup[];
|
|
@@ -191,7 +225,7 @@ export function ContextMenu({
|
|
|
191
225
|
}
|
|
192
226
|
const bounds = trigger.getBoundingClientRect();
|
|
193
227
|
setPosition({
|
|
194
|
-
align:
|
|
228
|
+
align: 'end',
|
|
195
229
|
x: bounds.right,
|
|
196
230
|
y: bounds.bottom,
|
|
197
231
|
trigger,
|
|
@@ -199,7 +233,7 @@ export function ContextMenu({
|
|
|
199
233
|
};
|
|
200
234
|
|
|
201
235
|
const child = cloneElement(children, {
|
|
202
|
-
|
|
236
|
+
'data-context-menu-open': position ? '' : undefined,
|
|
203
237
|
onContextMenu: (event: MouseEvent) => {
|
|
204
238
|
children.props.onContextMenu?.(event);
|
|
205
239
|
if (event.defaultPrevented || visibleGroups.length === 0) {
|
|
@@ -209,7 +243,7 @@ export function ContextMenu({
|
|
|
209
243
|
const trigger = event.currentTarget as HTMLElement;
|
|
210
244
|
const bounds = trigger.getBoundingClientRect();
|
|
211
245
|
setPosition({
|
|
212
|
-
align:
|
|
246
|
+
align: 'start',
|
|
213
247
|
x: event.clientX || bounds.left,
|
|
214
248
|
y: event.clientY || bounds.bottom,
|
|
215
249
|
trigger,
|
|
@@ -218,17 +252,10 @@ export function ContextMenu({
|
|
|
218
252
|
});
|
|
219
253
|
|
|
220
254
|
return (
|
|
221
|
-
<ContextMenuControllerContext.Provider
|
|
222
|
-
value={{ isOpen: Boolean(position), toggleFromButton }}
|
|
223
|
-
>
|
|
255
|
+
<ContextMenuControllerContext.Provider value={{ isOpen: Boolean(position), toggleFromButton }}>
|
|
224
256
|
{child}
|
|
225
257
|
{position ? (
|
|
226
|
-
<ContextMenuSurface
|
|
227
|
-
groups={visibleGroups}
|
|
228
|
-
label={label}
|
|
229
|
-
position={position}
|
|
230
|
-
onClose={closeMenu}
|
|
231
|
-
/>
|
|
258
|
+
<ContextMenuSurface groups={visibleGroups} label={label} position={position} onClose={closeMenu} />
|
|
232
259
|
) : null}
|
|
233
260
|
</ContextMenuControllerContext.Provider>
|
|
234
261
|
);
|
|
@@ -238,19 +265,19 @@ export function ContextMenuTrigger({
|
|
|
238
265
|
children,
|
|
239
266
|
}: {
|
|
240
267
|
children: ReactElement<{
|
|
241
|
-
|
|
242
|
-
|
|
268
|
+
'aria-expanded'?: boolean;
|
|
269
|
+
'aria-haspopup'?: 'menu';
|
|
243
270
|
onClick?: (event: MouseEvent) => void;
|
|
244
271
|
}>;
|
|
245
272
|
}) {
|
|
246
273
|
const controller = useContext(ContextMenuControllerContext);
|
|
247
274
|
if (!controller) {
|
|
248
|
-
throw new Error(
|
|
275
|
+
throw new Error('ContextMenuTrigger must be rendered inside ContextMenu');
|
|
249
276
|
}
|
|
250
277
|
|
|
251
278
|
return cloneElement(children, {
|
|
252
|
-
|
|
253
|
-
|
|
279
|
+
'aria-expanded': controller.isOpen,
|
|
280
|
+
'aria-haspopup': 'menu',
|
|
254
281
|
onClick: (event: MouseEvent) => {
|
|
255
282
|
children.props.onClick?.(event);
|
|
256
283
|
if (event.defaultPrevented) {
|
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NextClawClient,
|
|
3
|
+
NextClawClientError,
|
|
3
4
|
type NextClawRealtimeEvent,
|
|
4
5
|
type NextClawQueryParams,
|
|
5
6
|
type NextClawTransport,
|
|
6
7
|
type NextClawTransportRequestInput,
|
|
7
|
-
type NextClawTransportUploadInput
|
|
8
|
+
type NextClawTransportUploadInput,
|
|
8
9
|
} from '@nextclaw/client-sdk';
|
|
9
10
|
import { API_BASE } from '@/shared/lib/api/api-base';
|
|
10
11
|
import { appClient } from '@/shared/lib/transport';
|
|
11
12
|
import type { ApiResponse } from '@/shared/lib/api/types';
|
|
12
13
|
|
|
13
14
|
const nextclawUiTransport: NextClawTransport = {
|
|
14
|
-
request: async <T>({
|
|
15
|
+
request: async <T>({
|
|
16
|
+
body,
|
|
17
|
+
headers,
|
|
18
|
+
method,
|
|
19
|
+
path,
|
|
20
|
+
query,
|
|
21
|
+
signal,
|
|
22
|
+
timeoutMs,
|
|
23
|
+
}: NextClawTransportRequestInput): Promise<T> => {
|
|
15
24
|
return await appClient.request({
|
|
16
25
|
method,
|
|
17
26
|
path: appendQueryToPath(path, serializeQuery(query)),
|
|
18
27
|
...(body !== undefined ? { body } : {}),
|
|
19
28
|
...(headers ? { headers } : {}),
|
|
20
29
|
...(signal ? { signal } : {}),
|
|
21
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {})
|
|
30
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
22
31
|
});
|
|
23
32
|
},
|
|
24
33
|
upload: async <T>({ formData, headers, path, signal }: NextClawTransportUploadInput): Promise<T> => {
|
|
@@ -27,25 +36,30 @@ const nextclawUiTransport: NextClawTransport = {
|
|
|
27
36
|
body: formData,
|
|
28
37
|
credentials: 'include',
|
|
29
38
|
...(headers ? { headers } : {}),
|
|
30
|
-
...(signal ? { signal } : {})
|
|
39
|
+
...(signal ? { signal } : {}),
|
|
31
40
|
});
|
|
32
41
|
const payload = (await response.json()) as ApiResponse<unknown>;
|
|
33
42
|
if (!response.ok || !payload.ok) {
|
|
34
|
-
throw new
|
|
43
|
+
throw new NextClawClientError({
|
|
44
|
+
message: readApiErrorMessage(payload, `Upload failed for ${path}`),
|
|
45
|
+
status: response.status,
|
|
46
|
+
code: !payload.ok ? payload.error.code : undefined,
|
|
47
|
+
details: !payload.ok ? payload.error.details : undefined,
|
|
48
|
+
});
|
|
35
49
|
}
|
|
36
50
|
return payload.data as T;
|
|
37
51
|
},
|
|
38
|
-
subscribe: (handler: (event: NextClawRealtimeEvent) => void) => appClient.subscribe(handler)
|
|
52
|
+
subscribe: (handler: (event: NextClawRealtimeEvent) => void) => appClient.subscribe(handler),
|
|
39
53
|
};
|
|
40
54
|
|
|
41
55
|
export const nextclawClient = new NextClawClient({
|
|
42
56
|
baseUrl: API_BASE,
|
|
43
|
-
transport: nextclawUiTransport
|
|
57
|
+
transport: nextclawUiTransport,
|
|
44
58
|
});
|
|
45
59
|
|
|
46
60
|
export async function requestApiResponse<T>(
|
|
47
61
|
endpoint: string,
|
|
48
|
-
options: RequestInit & { timeoutMs?: number } = {}
|
|
62
|
+
options: RequestInit & { timeoutMs?: number } = {},
|
|
49
63
|
): Promise<ApiResponse<T>> {
|
|
50
64
|
const method = (options.method || 'GET').toUpperCase();
|
|
51
65
|
try {
|
|
@@ -53,11 +67,11 @@ export async function requestApiResponse<T>(
|
|
|
53
67
|
method: method as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
54
68
|
path: endpoint,
|
|
55
69
|
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
56
|
-
...(options.body !== undefined ? { body: parseRequestBody(options.body) } : {})
|
|
70
|
+
...(options.body !== undefined ? { body: parseRequestBody(options.body) } : {}),
|
|
57
71
|
});
|
|
58
72
|
return {
|
|
59
73
|
ok: true,
|
|
60
|
-
data
|
|
74
|
+
data,
|
|
61
75
|
};
|
|
62
76
|
} catch (error) {
|
|
63
77
|
return {
|
|
@@ -67,9 +81,9 @@ export async function requestApiResponse<T>(
|
|
|
67
81
|
message: error instanceof Error ? error.message : String(error),
|
|
68
82
|
details: {
|
|
69
83
|
method,
|
|
70
|
-
endpoint
|
|
71
|
-
}
|
|
72
|
-
}
|
|
84
|
+
endpoint,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
73
87
|
};
|
|
74
88
|
}
|
|
75
89
|
}
|
|
@@ -80,17 +94,17 @@ export const api = {
|
|
|
80
94
|
put: <T>(path: string, body: unknown) =>
|
|
81
95
|
requestApiResponse<T>(path, {
|
|
82
96
|
method: 'PUT',
|
|
83
|
-
body: JSON.stringify(body)
|
|
97
|
+
body: JSON.stringify(body),
|
|
84
98
|
}),
|
|
85
99
|
post: <T>(path: string, body: unknown) =>
|
|
86
100
|
requestApiResponse<T>(path, {
|
|
87
101
|
method: 'POST',
|
|
88
|
-
body: JSON.stringify(body)
|
|
102
|
+
body: JSON.stringify(body),
|
|
89
103
|
}),
|
|
90
104
|
delete: <T>(path: string) =>
|
|
91
105
|
requestApiResponse<T>(path, {
|
|
92
|
-
method: 'DELETE'
|
|
93
|
-
})
|
|
106
|
+
method: 'DELETE',
|
|
107
|
+
}),
|
|
94
108
|
};
|
|
95
109
|
|
|
96
110
|
function parseRequestBody(body: BodyInit | null | undefined): unknown {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type ServerPathEntryView = {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
kind: 'directory' | 'file';
|
|
5
|
+
hidden: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ServerPathBreadcrumbView = { label: string; path: string };
|
|
9
|
+
|
|
10
|
+
export type ServerPathLocationView = {
|
|
11
|
+
kind: 'desktop' | 'documents' | 'downloads' | 'icloud-drive' | 'applications' | 'volumes';
|
|
12
|
+
path: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ServerPathBrowseView = {
|
|
16
|
+
currentPath: string;
|
|
17
|
+
parentPath: string | null;
|
|
18
|
+
homePath: string;
|
|
19
|
+
breadcrumbs: ServerPathBreadcrumbView[];
|
|
20
|
+
entries: ServerPathEntryView[];
|
|
21
|
+
locations: ServerPathLocationView[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ServerPathSearchEntryView = {
|
|
25
|
+
name: string;
|
|
26
|
+
path: string;
|
|
27
|
+
relativePath: string;
|
|
28
|
+
parentRelativePath: string;
|
|
29
|
+
kind: 'directory' | 'file';
|
|
30
|
+
hidden: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ServerPathSearchView = {
|
|
34
|
+
basePath: string;
|
|
35
|
+
query: string;
|
|
36
|
+
entries: ServerPathSearchEntryView[];
|
|
37
|
+
truncated: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ServerPathDirectoryCreateRequest = { basePath?: string; parentPath: string; name: string };
|
|
41
|
+
export type ServerPathDirectoryCreateView = { path: string };
|
|
42
|
+
export type ServerPathFileCreateRequest = { basePath: string; parentPath: string; name: string };
|
|
43
|
+
export type ServerPathFileCreateView = { name: string; path: string; kind: 'file' };
|
|
44
|
+
export type ServerPathEntryRenameRequest = { basePath: string; path: string; name: string };
|
|
45
|
+
export type ServerPathEntryRenameView = {
|
|
46
|
+
oldPath: string;
|
|
47
|
+
path: string;
|
|
48
|
+
name: string;
|
|
49
|
+
kind: 'directory' | 'file';
|
|
50
|
+
};
|
|
51
|
+
export type ServerPathEntryDeleteView = { path: string; kind: 'directory' | 'file' };
|
|
52
|
+
export type ServerPathFilesUploadView = {
|
|
53
|
+
files: Array<{ name: string; path: string; sizeBytes: number }>;
|
|
54
|
+
overwritten: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type ServerPathReadView = {
|
|
58
|
+
requestedPath: string;
|
|
59
|
+
resolvedPath: string;
|
|
60
|
+
kind: 'text' | 'markdown' | 'binary';
|
|
61
|
+
sizeBytes: number;
|
|
62
|
+
startLine?: number;
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
text?: string;
|
|
65
|
+
languageHint?: string | null;
|
|
66
|
+
};
|