@nextclaw/ui 0.15.23 → 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 +38 -0
- package/dist/assets/{api-Glw2OGXK.js → api-FK7tikI1.js} +4 -4
- package/dist/assets/appearance-settings-page-BYbvl2wj.js +1 -0
- package/dist/assets/{channels-list-page-C7E4Sxwd.js → channels-list-page-CocdcIko.js} +2 -2
- package/dist/assets/{chat-page-BT6dNI-4.js → chat-page-C5E-LLVd.js} +1 -1
- package/dist/assets/config-split-page-CA_a0AJ5.js +1 -0
- package/dist/assets/confirm-dialog-CxZfenjK.js +5 -0
- package/dist/assets/desktop-update-config-BEKC8Ly9.js +1 -0
- package/dist/assets/{dist-dv_SUR54.js → dist-BH7W5yCi.js} +1 -1
- package/dist/assets/{dist-CtLyEJeO.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-BIBjkwfV.js → doc-browser-context-FDPs3gJf.js} +1 -1
- package/dist/assets/form-actions-0c0aw_bl.js +1 -0
- 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-Bc_jI5iH.js +9 -0
- package/dist/assets/mcp-marketplace-page-CzCieD9r.js +1 -0
- package/dist/assets/model-config-page-C4jw-pcj.js +1 -0
- package/dist/assets/{navigation-link-CB0OQbnY.js → navigation-link-CVy0_kVl.js} +2 -2
- package/dist/assets/plus-CG1RwZQq.js +1 -0
- package/dist/assets/provider-scoped-model-input-CmH4BFvc.js +1 -0
- package/dist/assets/providers-config-page-CJQjwzaw.js +1 -0
- package/dist/assets/react-DjFBVA0n.js +8 -0
- package/dist/assets/{refresh-cw-Ba0LZou4.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-DTOAvqzt.js → save-BKNrtK81.js} +1 -1
- package/dist/assets/{search-D4X5DNMe.js → search-CkOm65W_.js} +1 -1
- package/dist/assets/{search-config-page-Cr6254K5.js → search-config-page-BaIijtmO.js} +1 -1
- package/dist/assets/{secrets-config-page-W2Hx332d.js → secrets-config-page-CJjtdFAl.js} +2 -2
- package/dist/assets/security-config-DB8apj09.js +1 -0
- package/dist/assets/{select-CDv0g2Pj.js → select-Ea7ffKCE.js} +2 -2
- package/dist/assets/{settings-2-CoxzNABp.js → settings-2-CtyN3m_V.js} +1 -1
- package/dist/assets/skeleton-Bt6zxcAB.js +1 -0
- package/dist/assets/{tag-chip-CjkZEoS2.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/conversation/hooks/__tests__/use-session-provider-model-catalog.test.tsx +54 -2
- package/src/features/chat/features/conversation/hooks/use-chat-composer-reference-intent.ts +10 -1
- package/src/features/chat/features/conversation/hooks/use-session-provider-model-catalog.ts +76 -1
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +6 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +29 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +12 -3
- package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
- package/src/features/chat/features/input/utils/chat-conversation-excerpt-token.utils.ts +59 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +42 -16
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +50 -14
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +40 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +5 -0
- 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/__tests__/workspace-text-selection-menu.test.tsx +11 -4
- 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/components/workspace-text-selection-menu.tsx +17 -195
- 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 +94 -45
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
- package/src/features/chat/managers/chat-composer-intent.manager.ts +83 -34
- 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/features/chat/types/chat-input-bar.types.ts +3 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +1 -0
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +0 -9
- 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 +49 -1
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +49 -1
- package/dist/assets/appearance-settings-page-Bl_W_9m7.js +0 -1
- package/dist/assets/config-split-page-D5BgtKrm.js +0 -1
- package/dist/assets/confirm-dialog-Bf907Azu.js +0 -5
- package/dist/assets/desktop-update-config-BwuyBPJb.js +0 -1
- package/dist/assets/doc-browser-C23NDGh5.js +0 -1
- package/dist/assets/doc-browser-DlOOhBxI.js +0 -1
- package/dist/assets/form-actions-DAzi68Cn.js +0 -1
- package/dist/assets/index-BLNg2yg0.css +0 -1
- package/dist/assets/index-D3EKjEyK.js +0 -107
- package/dist/assets/loader-circle-BSg5u0r4.js +0 -1
- package/dist/assets/mcp-marketplace-page-ByhvQQj5.js +0 -9
- package/dist/assets/mcp-marketplace-page-CytXwnUs.js +0 -1
- package/dist/assets/model-config-page-bgEx3MjQ.js +0 -1
- package/dist/assets/plus-CNjWdLhZ.js +0 -1
- package/dist/assets/provider-scoped-model-input-8F6JMd2n.js +0 -1
- package/dist/assets/providers-config-page-CxpTjR81.js +0 -1
- package/dist/assets/react-Bk2ydNTe.js +0 -8
- package/dist/assets/remote-Dv38PuMV.js +0 -1
- package/dist/assets/rotate-cw-IkYVk9JX.js +0 -1
- package/dist/assets/runtime-config-page-C0ByyUVr.js +0 -1
- package/dist/assets/security-config-1E8OF4kR.js +0 -1
- package/dist/assets/skeleton-DW018fJv.js +0 -1
- package/dist/assets/use-confirm-dialog-CavtY1vJ.js +0 -1
- package/dist/assets/x-PTXYTEwz.js +0 -1
package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { Fragment, useState } from
|
|
2
|
-
import type { ChatFileOpenActionViewModel } from
|
|
3
|
-
import { ChevronRight, Folder, FolderTree } from
|
|
4
|
-
import { Popover, PopoverTrigger } from
|
|
5
|
-
import { FileTypeIcon } from
|
|
6
|
-
import { ChatPopoverContent } from
|
|
7
|
-
import { WorkspaceBreadcrumbBrowser } from
|
|
1
|
+
import { Fragment, useLayoutEffect, useRef, useState, type ReactNode } from 'react';
|
|
2
|
+
import type { ChatFileOpenActionViewModel } from '@nextclaw/agent-chat-ui';
|
|
3
|
+
import { ChevronRight, Folder, FolderTree } from 'lucide-react';
|
|
4
|
+
import { Popover, PopoverTrigger } from '@/shared/components/ui/popover';
|
|
5
|
+
import { FileTypeIcon } from '@/shared/components/file-type-icon';
|
|
6
|
+
import { ChatPopoverContent } from '@/features/chat/components/chat-popover-content';
|
|
7
|
+
import { WorkspaceBreadcrumbBrowser } from './chat-session-workspace-file-breadcrumb-browser';
|
|
8
8
|
import type {
|
|
9
9
|
WorkspaceFileBreadcrumbSegmentViewModel,
|
|
10
10
|
WorkspaceFileBreadcrumbViewModel,
|
|
11
|
-
} from
|
|
12
|
-
import { t } from
|
|
13
|
-
import { cn } from
|
|
11
|
+
} from '@/shared/lib/session-project';
|
|
12
|
+
import { t } from '@/shared/lib/i18n';
|
|
13
|
+
import { cn } from '@/shared/lib/utils';
|
|
14
14
|
|
|
15
15
|
function WorkspaceBreadcrumbWarningChip({ value }: { value: string }) {
|
|
16
16
|
return (
|
|
17
|
-
<span
|
|
18
|
-
className="inline-flex h-5 items-center rounded-sm border border-amber-200 bg-amber-50 px-1.5 text-[10px] font-medium leading-none text-amber-700"
|
|
19
|
-
>
|
|
17
|
+
<span className="inline-flex h-5 items-center rounded-sm border border-amber-200 bg-amber-50 px-1.5 text-[10px] font-medium leading-none text-amber-700">
|
|
20
18
|
{value}
|
|
21
19
|
</span>
|
|
22
20
|
);
|
|
@@ -43,20 +41,20 @@ function WorkspaceBreadcrumbSegmentButton({
|
|
|
43
41
|
<PopoverTrigger asChild>
|
|
44
42
|
<button
|
|
45
43
|
type="button"
|
|
46
|
-
aria-current={segment.isCurrent ?
|
|
44
|
+
aria-current={segment.isCurrent ? 'page' : undefined}
|
|
47
45
|
className={cn(
|
|
48
|
-
|
|
49
|
-
segment.kind ===
|
|
50
|
-
?
|
|
46
|
+
'inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-sm px-1 text-[11px] leading-none transition-colors hover:bg-gray-200/80 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
|
|
47
|
+
segment.kind === 'workspace'
|
|
48
|
+
? 'bg-primary/8 text-primary hover:bg-primary/12'
|
|
51
49
|
: segment.isCurrent
|
|
52
|
-
?
|
|
53
|
-
:
|
|
50
|
+
? 'bg-gray-200/70 text-gray-900'
|
|
51
|
+
: 'text-gray-500',
|
|
54
52
|
)}
|
|
55
53
|
disabled={!segment.browsePath}
|
|
56
54
|
>
|
|
57
|
-
{segment.kind ===
|
|
55
|
+
{segment.kind === 'workspace' ? (
|
|
58
56
|
<FolderTree className="h-3 w-3 shrink-0" />
|
|
59
|
-
) : segment.kind ===
|
|
57
|
+
) : segment.kind === 'directory' && segment.isCurrent ? (
|
|
60
58
|
<Folder className="h-3 w-3 shrink-0" />
|
|
61
59
|
) : segment.isCurrent ? (
|
|
62
60
|
<FileTypeIcon fileName={segment.label} size="compact" />
|
|
@@ -64,11 +62,7 @@ function WorkspaceBreadcrumbSegmentButton({
|
|
|
64
62
|
<span>{segment.label}</span>
|
|
65
63
|
</button>
|
|
66
64
|
</PopoverTrigger>
|
|
67
|
-
<ChatPopoverContent
|
|
68
|
-
data-testid="workspace-breadcrumb-popover"
|
|
69
|
-
className="w-[22rem] rounded-md p-0"
|
|
70
|
-
align="start"
|
|
71
|
-
>
|
|
65
|
+
<ChatPopoverContent data-testid="workspace-breadcrumb-popover" className="w-[22rem] rounded-md p-0" align="start">
|
|
72
66
|
<WorkspaceBreadcrumbBrowser
|
|
73
67
|
browsePath={browsePath}
|
|
74
68
|
onBrowsePathChange={setBrowsePath}
|
|
@@ -82,28 +76,55 @@ function WorkspaceBreadcrumbSegmentButton({
|
|
|
82
76
|
|
|
83
77
|
export function ChatSessionWorkspaceFileBreadcrumbs({
|
|
84
78
|
breadcrumb,
|
|
79
|
+
leading,
|
|
85
80
|
onFileOpen,
|
|
86
81
|
}: {
|
|
87
82
|
breadcrumb: WorkspaceFileBreadcrumbViewModel;
|
|
83
|
+
leading?: ReactNode;
|
|
88
84
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
89
85
|
}) {
|
|
86
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
87
|
+
|
|
88
|
+
useLayoutEffect(() => {
|
|
89
|
+
const scroll = scrollRef.current;
|
|
90
|
+
if (!scroll) return;
|
|
91
|
+
const current = scroll.querySelector<HTMLElement>('[aria-current="page"]');
|
|
92
|
+
if (!current) return;
|
|
93
|
+
|
|
94
|
+
const revealCurrentSegment = () => {
|
|
95
|
+
const scrollRect = scroll.getBoundingClientRect();
|
|
96
|
+
const currentRect = current.getBoundingClientRect();
|
|
97
|
+
if (currentRect.right > scrollRect.right) {
|
|
98
|
+
scroll.scrollLeft += currentRect.right - scrollRect.right;
|
|
99
|
+
} else if (currentRect.left < scrollRect.left) {
|
|
100
|
+
scroll.scrollLeft -= scrollRect.left - currentRect.left;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
revealCurrentSegment();
|
|
105
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
106
|
+
const observer = new ResizeObserver(revealCurrentSegment);
|
|
107
|
+
observer.observe(scroll);
|
|
108
|
+
observer.observe(current);
|
|
109
|
+
return () => observer.disconnect();
|
|
110
|
+
}, [breadcrumb.fullPath]);
|
|
111
|
+
|
|
90
112
|
return (
|
|
91
113
|
<div
|
|
92
114
|
data-testid="workspace-file-breadcrumbs"
|
|
93
115
|
title={breadcrumb.fullPath}
|
|
94
|
-
className="
|
|
116
|
+
className="flex min-w-0 items-center border-b border-gray-200/80 bg-gray-50/55"
|
|
95
117
|
>
|
|
118
|
+
{leading ? <div className="flex h-full shrink-0 items-center pl-1 pr-0.5">{leading}</div> : null}
|
|
96
119
|
<div
|
|
120
|
+
ref={scrollRef}
|
|
97
121
|
data-testid="workspace-file-breadcrumb-scroll"
|
|
98
|
-
className="flex min-w-
|
|
122
|
+
className="workspace-horizontal-scrollbar flex min-w-0 flex-1 items-center gap-2.5 overflow-x-auto overflow-y-hidden px-3 py-1.5"
|
|
99
123
|
>
|
|
100
124
|
<div className="flex min-w-0 flex-1 items-center gap-1 pr-1">
|
|
101
125
|
{breadcrumb.segments.map((segment, index) => (
|
|
102
126
|
<Fragment key={segment.key}>
|
|
103
|
-
<WorkspaceBreadcrumbSegmentButton
|
|
104
|
-
segment={segment}
|
|
105
|
-
onFileOpen={onFileOpen}
|
|
106
|
-
/>
|
|
127
|
+
<WorkspaceBreadcrumbSegmentButton segment={segment} onFileOpen={onFileOpen} />
|
|
107
128
|
{index < breadcrumb.segments.length - 1 ? (
|
|
108
129
|
<ChevronRight className="h-3 w-3 shrink-0 text-gray-300" />
|
|
109
130
|
) : null}
|
|
@@ -113,9 +134,7 @@ export function ChatSessionWorkspaceFileBreadcrumbs({
|
|
|
113
134
|
|
|
114
135
|
{breadcrumb.truncated ? (
|
|
115
136
|
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1">
|
|
116
|
-
<WorkspaceBreadcrumbWarningChip
|
|
117
|
-
value={t("chatWorkspacePreviewTruncated")}
|
|
118
|
-
/>
|
|
137
|
+
<WorkspaceBreadcrumbWarningChip value={t('chatWorkspacePreviewTruncated')} />
|
|
119
138
|
</div>
|
|
120
139
|
) : null}
|
|
121
140
|
</div>
|
package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx
CHANGED
|
@@ -1,56 +1,44 @@
|
|
|
1
|
-
import { useMemo } from
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "@nextclaw/agent-chat-ui";
|
|
11
|
-
import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
|
|
12
|
-
import { ChatSessionWorkspaceDirectoryBrowser } from "./chat-session-workspace-directory-browser";
|
|
13
|
-
import { ChatSessionWorkspaceFileBreadcrumbs } from "./chat-session-workspace-file-breadcrumbs";
|
|
14
|
-
import { WorkspaceTextSelectionMenu } from "./workspace-text-selection-menu";
|
|
15
|
-
import type { WorkspaceTextExcerpt } from "@/features/chat/features/workspace/utils/workspace-text-excerpt.utils";
|
|
1
|
+
import { useMemo, type ReactNode } from 'react';
|
|
2
|
+
import type { ChatFileOpenActionViewModel, ChatFileOperationBlockViewModel } from '@nextclaw/agent-chat-ui';
|
|
3
|
+
import { NextClawClientError } from '@nextclaw/client-sdk';
|
|
4
|
+
import { ChatMessageMarkdown, FileOperationCodeSurface } from '@nextclaw/agent-chat-ui';
|
|
5
|
+
import type { ChatWorkspaceFileTab } from '@/features/chat/stores/chat-thread.store';
|
|
6
|
+
import { ChatSessionWorkspaceDirectoryBrowser } from './chat-session-workspace-directory-browser';
|
|
7
|
+
import { ChatSessionWorkspaceFileBreadcrumbs } from './chat-session-workspace-file-breadcrumbs';
|
|
8
|
+
import { WorkspaceTextSelectionMenu } from './workspace-text-selection-menu';
|
|
9
|
+
import type { WorkspaceTextExcerpt } from '@/features/chat/features/workspace/utils/workspace-text-excerpt.utils';
|
|
16
10
|
import {
|
|
17
11
|
resolveWorkspaceFileContentKind,
|
|
18
12
|
WorkspaceFileContentPreview,
|
|
19
13
|
type WorkspaceFileContentKind,
|
|
20
|
-
} from
|
|
21
|
-
import { useServerPathBrowse } from
|
|
22
|
-
import { useServerPathRead } from
|
|
23
|
-
import { buildServerPathContentUrl } from
|
|
14
|
+
} from './workspace-file-content-preview';
|
|
15
|
+
import { useServerPathBrowse } from '@/shared/hooks/use-server-path-browse';
|
|
16
|
+
import { useServerPathRead } from '@/shared/hooks/use-server-path-read';
|
|
17
|
+
import { buildServerPathContentUrl } from '@/shared/lib/api';
|
|
24
18
|
import {
|
|
25
19
|
buildLineDiff,
|
|
26
20
|
buildPreviewLines,
|
|
27
|
-
} from
|
|
28
|
-
import { t } from
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from "@/shared/lib/session-project";
|
|
33
|
-
import { cn } from "@/shared/lib/utils";
|
|
34
|
-
import { resolveWorkspaceFileViewer } from "@/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils";
|
|
21
|
+
} from '@/features/chat/features/message/utils/file-operation/line-builder.utils';
|
|
22
|
+
import { t } from '@/shared/lib/i18n';
|
|
23
|
+
import { buildWorkspaceFileBreadcrumb, resolveWorkspaceRelativePath } from '@/shared/lib/session-project';
|
|
24
|
+
import { cn } from '@/shared/lib/utils';
|
|
25
|
+
import { resolveWorkspaceFileViewer } from '@/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils';
|
|
35
26
|
|
|
36
27
|
function inferPreviewKind(params: {
|
|
37
28
|
path: string;
|
|
38
|
-
serverKind?:
|
|
39
|
-
}):
|
|
29
|
+
serverKind?: 'text' | 'markdown' | 'binary';
|
|
30
|
+
}): 'text' | 'markdown' | 'binary' {
|
|
40
31
|
if (params.serverKind) {
|
|
41
32
|
return params.serverKind;
|
|
42
33
|
}
|
|
43
|
-
return /\.mdx?$/i.test(params.path) ?
|
|
34
|
+
return /\.mdx?$/i.test(params.path) ? 'markdown' : 'text';
|
|
44
35
|
}
|
|
45
36
|
|
|
46
|
-
function appendPreviewRefreshVersion(
|
|
47
|
-
url: string,
|
|
48
|
-
refreshVersion: number,
|
|
49
|
-
): string {
|
|
37
|
+
function appendPreviewRefreshVersion(url: string, refreshVersion: number): string {
|
|
50
38
|
if (refreshVersion <= 0) {
|
|
51
39
|
return url;
|
|
52
40
|
}
|
|
53
|
-
return `${url}${url.includes(
|
|
41
|
+
return `${url}${url.includes('?') ? '&' : '?'}refresh=${refreshVersion}`;
|
|
54
42
|
}
|
|
55
43
|
|
|
56
44
|
function buildPreviewBlock(params: {
|
|
@@ -64,10 +52,10 @@ function buildPreviewBlock(params: {
|
|
|
64
52
|
return {
|
|
65
53
|
key: `preview:${path}`,
|
|
66
54
|
path,
|
|
67
|
-
display:
|
|
55
|
+
display: 'preview',
|
|
68
56
|
lines: buildPreviewLines({
|
|
69
57
|
text,
|
|
70
|
-
kind:
|
|
58
|
+
kind: 'context',
|
|
71
59
|
oldStartLine: normalizedStartLine,
|
|
72
60
|
newStartLine: normalizedStartLine,
|
|
73
61
|
}),
|
|
@@ -78,25 +66,19 @@ function buildPreviewBlock(params: {
|
|
|
78
66
|
};
|
|
79
67
|
}
|
|
80
68
|
|
|
81
|
-
function buildDiffBlock(
|
|
82
|
-
file: ChatWorkspaceFileTab,
|
|
83
|
-
): ChatFileOperationBlockViewModel | null {
|
|
69
|
+
function buildDiffBlock(file: ChatWorkspaceFileTab): ChatFileOperationBlockViewModel | null {
|
|
84
70
|
if (Array.isArray(file.fullLines) && file.fullLines.length > 0) {
|
|
85
71
|
return {
|
|
86
72
|
key: `diff:${file.key}`,
|
|
87
73
|
path: file.path,
|
|
88
|
-
display:
|
|
74
|
+
display: 'diff',
|
|
89
75
|
lines: file.fullLines,
|
|
90
76
|
fullLines: file.fullLines,
|
|
91
77
|
...(file.beforeText ? { beforeText: file.beforeText } : {}),
|
|
92
78
|
...(file.afterText ? { afterText: file.afterText } : {}),
|
|
93
79
|
...(file.patchText ? { patchText: file.patchText } : {}),
|
|
94
|
-
...(typeof file.oldStartLine ===
|
|
95
|
-
|
|
96
|
-
: {}),
|
|
97
|
-
...(typeof file.newStartLine === "number"
|
|
98
|
-
? { newStartLine: file.newStartLine }
|
|
99
|
-
: {}),
|
|
80
|
+
...(typeof file.oldStartLine === 'number' ? { oldStartLine: file.oldStartLine } : {}),
|
|
81
|
+
...(typeof file.newStartLine === 'number' ? { newStartLine: file.newStartLine } : {}),
|
|
100
82
|
};
|
|
101
83
|
}
|
|
102
84
|
|
|
@@ -105,8 +87,8 @@ function buildDiffBlock(
|
|
|
105
87
|
}
|
|
106
88
|
|
|
107
89
|
const lines = buildLineDiff({
|
|
108
|
-
beforeText: file.beforeText ??
|
|
109
|
-
afterText: file.afterText ??
|
|
90
|
+
beforeText: file.beforeText ?? '',
|
|
91
|
+
afterText: file.afterText ?? '',
|
|
110
92
|
oldStartLine: file.oldStartLine ?? undefined,
|
|
111
93
|
newStartLine: file.newStartLine ?? undefined,
|
|
112
94
|
});
|
|
@@ -114,32 +96,22 @@ function buildDiffBlock(
|
|
|
114
96
|
return {
|
|
115
97
|
key: `diff:${file.key}`,
|
|
116
98
|
path: file.path,
|
|
117
|
-
display:
|
|
99
|
+
display: 'diff',
|
|
118
100
|
lines,
|
|
119
101
|
fullLines: lines,
|
|
120
102
|
...(file.beforeText ? { beforeText: file.beforeText } : {}),
|
|
121
103
|
...(file.afterText ? { afterText: file.afterText } : {}),
|
|
122
|
-
...(typeof file.oldStartLine ===
|
|
123
|
-
|
|
124
|
-
: {}),
|
|
125
|
-
...(typeof file.newStartLine === "number"
|
|
126
|
-
? { newStartLine: file.newStartLine }
|
|
127
|
-
: {}),
|
|
104
|
+
...(typeof file.oldStartLine === 'number' ? { oldStartLine: file.oldStartLine } : {}),
|
|
105
|
+
...(typeof file.newStartLine === 'number' ? { newStartLine: file.newStartLine } : {}),
|
|
128
106
|
};
|
|
129
107
|
}
|
|
130
108
|
|
|
131
|
-
function WorkspaceFilePreviewStatus({
|
|
132
|
-
text,
|
|
133
|
-
tone = "muted",
|
|
134
|
-
}: {
|
|
135
|
-
text: string;
|
|
136
|
-
tone?: "muted" | "error";
|
|
137
|
-
}) {
|
|
109
|
+
function WorkspaceFilePreviewStatus({ text, tone = 'muted' }: { text: string; tone?: 'muted' | 'error' }) {
|
|
138
110
|
return (
|
|
139
111
|
<div
|
|
140
112
|
className={cn(
|
|
141
|
-
|
|
142
|
-
tone ===
|
|
113
|
+
'flex h-full items-center justify-center px-6 text-center text-sm',
|
|
114
|
+
tone === 'error' ? 'text-rose-600' : 'text-gray-500',
|
|
143
115
|
)}
|
|
144
116
|
>
|
|
145
117
|
{text}
|
|
@@ -148,19 +120,14 @@ function WorkspaceFilePreviewStatus({
|
|
|
148
120
|
}
|
|
149
121
|
|
|
150
122
|
export function resolveWorkspacePreviewErrorText(error: unknown): string {
|
|
151
|
-
return error instanceof NextClawClientError &&
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
: t("chatWorkspacePreviewFailed");
|
|
123
|
+
return error instanceof NextClawClientError && error.code === 'SERVER_PATH_NOT_FOUND'
|
|
124
|
+
? t('chatWorkspacePreviewNotFound')
|
|
125
|
+
: t('chatWorkspacePreviewFailed');
|
|
155
126
|
}
|
|
156
127
|
|
|
157
|
-
function WorkspaceDiffBody({
|
|
158
|
-
diffBlock,
|
|
159
|
-
}: {
|
|
160
|
-
diffBlock: ChatFileOperationBlockViewModel | null;
|
|
161
|
-
}) {
|
|
128
|
+
function WorkspaceDiffBody({ diffBlock }: { diffBlock: ChatFileOperationBlockViewModel | null }) {
|
|
162
129
|
if (!diffBlock) {
|
|
163
|
-
return <WorkspaceFilePreviewStatus text={t(
|
|
130
|
+
return <WorkspaceFilePreviewStatus text={t('chatWorkspaceDiffEmpty')} />;
|
|
164
131
|
}
|
|
165
132
|
return <WorkspaceCodeSurface block={diffBlock} />;
|
|
166
133
|
}
|
|
@@ -176,12 +143,7 @@ function WorkspaceCodeSurface({
|
|
|
176
143
|
}) {
|
|
177
144
|
return (
|
|
178
145
|
<div className="h-full overflow-auto custom-scrollbar bg-white">
|
|
179
|
-
<FileOperationCodeSurface
|
|
180
|
-
block={block}
|
|
181
|
-
layout="workspace"
|
|
182
|
-
targetColumn={targetColumn}
|
|
183
|
-
targetLine={targetLine}
|
|
184
|
-
/>
|
|
146
|
+
<FileOperationCodeSurface block={block} layout="workspace" targetColumn={targetColumn} targetLine={targetLine} />
|
|
185
147
|
</div>
|
|
186
148
|
);
|
|
187
149
|
}
|
|
@@ -206,14 +168,14 @@ function WorkspacePreviewBody({
|
|
|
206
168
|
contentUrl: string | null;
|
|
207
169
|
contentUrlKind: WorkspaceFileContentKind | null;
|
|
208
170
|
contentLabel: string;
|
|
209
|
-
contentParams: ChatWorkspaceFileTab[
|
|
171
|
+
contentParams: ChatWorkspaceFileTab['params'];
|
|
210
172
|
directoryQuery: ReturnType<typeof useServerPathBrowse> | null | undefined;
|
|
211
173
|
fileBasePath: string | null;
|
|
212
174
|
onFileOpen: (action: ChatFileOpenActionViewModel) => void;
|
|
213
175
|
onHtmlContentHeightChange?: (height: number) => void;
|
|
214
176
|
previewBlock: ChatFileOperationBlockViewModel | null;
|
|
215
|
-
previewKind:
|
|
216
|
-
previewViewer:
|
|
177
|
+
previewKind: 'text' | 'markdown' | 'binary';
|
|
178
|
+
previewViewer: 'source' | 'rendered' | null;
|
|
217
179
|
previewQuery: ReturnType<typeof useServerPathRead> | null | undefined;
|
|
218
180
|
previewText: string | null;
|
|
219
181
|
targetColumn?: number | null;
|
|
@@ -242,65 +204,47 @@ function WorkspacePreviewBody({
|
|
|
242
204
|
}
|
|
243
205
|
|
|
244
206
|
if (directoryQuery?.isLoading && previewQuery?.error && !previewBlock) {
|
|
245
|
-
return (
|
|
246
|
-
<WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingDirectory")} />
|
|
247
|
-
);
|
|
207
|
+
return <WorkspaceFilePreviewStatus text={t('chatWorkspaceLoadingDirectory')} />;
|
|
248
208
|
}
|
|
249
209
|
|
|
250
210
|
if ((directoryQuery?.isLoading || previewQuery?.isLoading) && !previewBlock) {
|
|
251
|
-
return (
|
|
252
|
-
<WorkspaceFilePreviewStatus text={t("chatWorkspaceLoadingPreview")} />
|
|
253
|
-
);
|
|
211
|
+
return <WorkspaceFilePreviewStatus text={t('chatWorkspaceLoadingPreview')} />;
|
|
254
212
|
}
|
|
255
213
|
|
|
256
|
-
if (previewQuery?.data?.kind ===
|
|
257
|
-
return (
|
|
258
|
-
<WorkspaceFilePreviewStatus text={t("chatWorkspacePreviewUnsupported")} />
|
|
259
|
-
);
|
|
214
|
+
if (previewQuery?.data?.kind === 'binary') {
|
|
215
|
+
return <WorkspaceFilePreviewStatus text={t('chatWorkspacePreviewUnsupported')} />;
|
|
260
216
|
}
|
|
261
217
|
|
|
262
218
|
if (previewQuery?.error && !previewBlock) {
|
|
263
|
-
return (
|
|
264
|
-
<WorkspaceFilePreviewStatus
|
|
265
|
-
tone="error"
|
|
266
|
-
text={resolveWorkspacePreviewErrorText(previewQuery.error)}
|
|
267
|
-
/>
|
|
268
|
-
);
|
|
219
|
+
return <WorkspaceFilePreviewStatus tone="error" text={resolveWorkspacePreviewErrorText(previewQuery.error)} />;
|
|
269
220
|
}
|
|
270
221
|
|
|
271
|
-
if (previewKind ===
|
|
222
|
+
if (previewKind === 'markdown' && previewViewer !== 'source' && previewText) {
|
|
272
223
|
return (
|
|
273
224
|
<div className="h-full overflow-auto custom-scrollbar px-5 py-4">
|
|
274
225
|
<ChatMessageMarkdown
|
|
275
226
|
text={previewText}
|
|
276
227
|
role="assistant"
|
|
277
228
|
texts={{
|
|
278
|
-
copyCodeLabel: t(
|
|
279
|
-
copiedCodeLabel: t(
|
|
229
|
+
copyCodeLabel: t('chatCodeCopy'),
|
|
230
|
+
copiedCodeLabel: t('chatCodeCopied'),
|
|
280
231
|
}}
|
|
281
232
|
onFileOpen={onFileOpen}
|
|
282
|
-
resolveFileContentUrl={(action) =>
|
|
283
|
-
buildServerPathContentUrl(action.path, fileBasePath)
|
|
284
|
-
}
|
|
233
|
+
resolveFileContentUrl={(action) => buildServerPathContentUrl(action.path, fileBasePath)}
|
|
285
234
|
/>
|
|
286
235
|
</div>
|
|
287
236
|
);
|
|
288
237
|
}
|
|
289
238
|
|
|
290
239
|
if (previewBlock) {
|
|
291
|
-
return
|
|
292
|
-
<WorkspaceCodeSurface
|
|
293
|
-
block={previewBlock}
|
|
294
|
-
targetColumn={targetColumn}
|
|
295
|
-
targetLine={targetLine}
|
|
296
|
-
/>
|
|
297
|
-
);
|
|
240
|
+
return <WorkspaceCodeSurface block={previewBlock} targetColumn={targetColumn} targetLine={targetLine} />;
|
|
298
241
|
}
|
|
299
242
|
|
|
300
|
-
return <WorkspaceFilePreviewStatus text={t(
|
|
243
|
+
return <WorkspaceFilePreviewStatus text={t('chatWorkspacePreviewEmpty')} />;
|
|
301
244
|
}
|
|
302
245
|
|
|
303
246
|
type ChatSessionWorkspaceFilePreviewProps = {
|
|
247
|
+
breadcrumbLeading?: ReactNode;
|
|
304
248
|
file: ChatWorkspaceFileTab;
|
|
305
249
|
refreshVersion?: number;
|
|
306
250
|
sessionProjectRoot: string | null;
|
|
@@ -312,6 +256,7 @@ type ChatSessionWorkspaceFilePreviewProps = {
|
|
|
312
256
|
};
|
|
313
257
|
|
|
314
258
|
export function ChatSessionWorkspaceFilePreview({
|
|
259
|
+
breadcrumbLeading,
|
|
315
260
|
file,
|
|
316
261
|
refreshVersion = 0,
|
|
317
262
|
sessionProjectRoot,
|
|
@@ -321,7 +266,7 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
321
266
|
onFileOpen,
|
|
322
267
|
onTextExcerptAdd,
|
|
323
268
|
}: ChatSessionWorkspaceFilePreviewProps) {
|
|
324
|
-
const isPreviewMode = file.viewMode ===
|
|
269
|
+
const isPreviewMode = file.viewMode === 'preview';
|
|
325
270
|
const suppliedContentUrl = file.contentUrl?.trim() || null;
|
|
326
271
|
const usesServerPath = isPreviewMode && !suppliedContentUrl;
|
|
327
272
|
const previewQuery = useServerPathRead({
|
|
@@ -336,13 +281,8 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
336
281
|
includeFiles: true,
|
|
337
282
|
enabled: usesServerPath,
|
|
338
283
|
});
|
|
339
|
-
const diffBlock = useMemo(
|
|
340
|
-
|
|
341
|
-
[file],
|
|
342
|
-
);
|
|
343
|
-
const previewText = isPreviewMode
|
|
344
|
-
? (previewQuery?.data?.text ?? file.rawText ?? null)
|
|
345
|
-
: null;
|
|
284
|
+
const diffBlock = useMemo(() => (file.viewMode === 'diff' ? buildDiffBlock(file) : null), [file]);
|
|
285
|
+
const previewText = isPreviewMode ? (previewQuery?.data?.text ?? file.rawText ?? null) : null;
|
|
346
286
|
const previewKind = inferPreviewKind({
|
|
347
287
|
path: previewQuery?.data?.resolvedPath ?? file.path,
|
|
348
288
|
serverKind: previewQuery?.data?.kind,
|
|
@@ -353,37 +293,27 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
353
293
|
mimeType: file.mimeType,
|
|
354
294
|
})
|
|
355
295
|
: null;
|
|
356
|
-
const resolvedPath =
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
file.path;
|
|
360
|
-
const previewPathKind = directoryQuery?.data ? "directory" : "file";
|
|
361
|
-
const previewViewer = resolveWorkspaceFileViewer(
|
|
362
|
-
resolvedPath,
|
|
363
|
-
file.previewViewer,
|
|
364
|
-
);
|
|
296
|
+
const resolvedPath = directoryQuery?.data?.currentPath ?? previewQuery?.data?.resolvedPath ?? file.path;
|
|
297
|
+
const previewPathKind = directoryQuery?.data ? 'directory' : 'file';
|
|
298
|
+
const previewViewer = resolveWorkspaceFileViewer(resolvedPath, file.previewViewer);
|
|
365
299
|
const localContentKind = resolveWorkspaceFileContentKind({
|
|
366
300
|
path: resolvedPath,
|
|
367
301
|
});
|
|
368
|
-
const localContentUrlCandidate = buildServerPathContentUrl(
|
|
369
|
-
file.path,
|
|
370
|
-
sessionWorkingDir,
|
|
371
|
-
);
|
|
302
|
+
const localContentUrlCandidate = buildServerPathContentUrl(file.path, sessionWorkingDir);
|
|
372
303
|
const shouldRenderLocalContent = Boolean(
|
|
373
304
|
!suppliedContentUrl &&
|
|
374
305
|
isPreviewMode &&
|
|
375
306
|
localContentUrlCandidate &&
|
|
376
|
-
file.previewViewer !==
|
|
377
|
-
localContentKind !==
|
|
378
|
-
(localContentKind !==
|
|
307
|
+
file.previewViewer !== 'source' &&
|
|
308
|
+
localContentKind !== 'other' &&
|
|
309
|
+
(localContentKind !== 'html' || previewViewer === 'rendered'),
|
|
379
310
|
);
|
|
380
311
|
const localContentUrl =
|
|
381
312
|
shouldRenderLocalContent && localContentUrlCandidate
|
|
382
313
|
? appendPreviewRefreshVersion(localContentUrlCandidate, refreshVersion)
|
|
383
314
|
: null;
|
|
384
315
|
const contentUrl = suppliedContentUrl ?? localContentUrl;
|
|
385
|
-
const contentUrlKind =
|
|
386
|
-
suppliedContentKind ?? (localContentUrl ? localContentKind : null);
|
|
316
|
+
const contentUrlKind = suppliedContentKind ?? (localContentUrl ? localContentKind : null);
|
|
387
317
|
const previewBlock = useMemo(() => {
|
|
388
318
|
if (!isPreviewMode || !previewText || contentUrl) {
|
|
389
319
|
return null;
|
|
@@ -403,13 +333,13 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
403
333
|
previewQuery?.data?.startLine,
|
|
404
334
|
previewText,
|
|
405
335
|
]);
|
|
406
|
-
const isTextPreviewTruncated =
|
|
407
|
-
!contentUrl && Boolean(previewQuery?.data?.truncated);
|
|
336
|
+
const isTextPreviewTruncated = !contentUrl && Boolean(previewQuery?.data?.truncated);
|
|
408
337
|
const breadcrumbBasePath = sessionProjectRoot ?? sessionWorkingDir;
|
|
409
|
-
const excerptPath =
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
338
|
+
const excerptPath =
|
|
339
|
+
resolveWorkspaceRelativePath({
|
|
340
|
+
path: resolvedPath,
|
|
341
|
+
sessionProjectRoot: breadcrumbBasePath,
|
|
342
|
+
}) ?? resolvedPath.trim();
|
|
413
343
|
const breadcrumb = useMemo(
|
|
414
344
|
() =>
|
|
415
345
|
buildWorkspaceFileBreadcrumb({
|
|
@@ -445,16 +375,17 @@ export function ChatSessionWorkspaceFilePreview({
|
|
|
445
375
|
{showBreadcrumbs ? (
|
|
446
376
|
<ChatSessionWorkspaceFileBreadcrumbs
|
|
447
377
|
breadcrumb={breadcrumb}
|
|
378
|
+
leading={breadcrumbLeading}
|
|
448
379
|
onFileOpen={onFileOpen}
|
|
449
380
|
/>
|
|
450
381
|
) : null}
|
|
451
382
|
|
|
452
383
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
453
|
-
{file.viewMode ===
|
|
384
|
+
{file.viewMode === 'diff' ? (
|
|
454
385
|
<WorkspaceDiffBody diffBlock={diffBlock} />
|
|
455
386
|
) : excerptPath && previewText ? (
|
|
456
387
|
<WorkspaceTextSelectionMenu
|
|
457
|
-
fileLabel={file.label?.trim() || excerptPath.split(
|
|
388
|
+
fileLabel={file.label?.trim() || excerptPath.split('/').at(-1) || excerptPath}
|
|
458
389
|
filePath={excerptPath}
|
|
459
390
|
sourceStartLine={previewQuery?.data?.startLine ?? 1}
|
|
460
391
|
sourceText={previewText}
|