@mcp-b/react-components 0.14.2 → 0.16.0
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/dist/AgentActivity-BNUNrP9O.d.ts +36 -0
- package/dist/{AgentChat-DQkqt82e.d.ts → AgentChat-CrlpbvQJ.d.ts} +58 -6
- package/dist/{CodeBlock-DUh9WOGo.d.ts → CodeBlock-BRajW4QU.d.ts} +1 -1
- package/dist/{CodeBlock-BUFXG8HW.js → CodeBlock-BmB4Xaqq.js} +15 -4
- package/dist/{FileTree-mpEIcfzA.js → FileTree-CkVbIMWV.js} +82 -89
- package/dist/Message-BkMdE3bS.js +375 -0
- package/dist/{ModelSelector-CPCpFAUt.js → ModelSelector-SlJNgwKY.js} +2 -2
- package/dist/{PromptInput-ClNqBiIw.d.ts → PromptInput-BWTuXMOD.d.ts} +123 -5
- package/dist/ResponsiveSidebarShell-Dd9DCcBT.d.ts +67 -0
- package/dist/{Select-Dl_vF_Bt.d.ts → Select-BeEsefzh.d.ts} +12 -2
- package/dist/{ThinkChat-BPcehdbP.js → ThinkChat-CzC-zNGA.js} +24 -36
- package/dist/{ThinkChatActivity-BmwuIigx.d.ts → ThinkChatActivity-BFQnb2ct.d.ts} +3 -10
- package/dist/ThinkChatActivitySummary-BPmu9a-c.d.ts +39 -0
- package/dist/{ThinkChatToolParts-CDEjqS6A.d.ts → ThinkChatToolParts-CSzVwMXf.d.ts} +13 -3
- package/dist/ThinkExecutionRun-ChhWhOoU.d.ts +30 -0
- package/dist/Toggle-CAAlNl-M.d.ts +69 -0
- package/dist/ToggleGroup-8hPv-b69.d.ts +73 -0
- package/dist/Workspace-D39LxGvP.d.ts +62 -0
- package/dist/WorkspaceFile-DOZkPwzu.d.ts +106 -0
- package/dist/WorkspaceFileView-rSN_yT0L.js +744 -0
- package/dist/WorkspaceLink-DIPbXrUM.js +337 -0
- package/dist/WorkspaceLink-DK1DWMHe.d.ts +89 -0
- package/dist/components/Activity.d.ts +27 -1
- package/dist/components/Activity.js +33 -15
- package/dist/components/AgentActivity.d.ts +2 -0
- package/dist/components/AgentActivity.js +45 -0
- package/dist/components/AgentActivityPreview.d.ts +46 -0
- package/dist/components/AgentActivityPreview.js +94 -0
- package/dist/components/AgentAttention.d.ts +73 -0
- package/dist/components/AgentAttention.js +85 -0
- package/dist/components/AgentChat.d.ts +2 -2
- package/dist/components/AgentChat.js +224 -200
- package/dist/components/AgentChatMessageEditor.d.ts +29 -0
- package/dist/components/AgentChatMessageEditor.js +92 -0
- package/dist/components/AgentChatTurnError.d.ts +17 -0
- package/dist/components/AgentChatTurnError.js +40 -0
- package/dist/components/AgentConnection.d.ts +12 -17
- package/dist/components/AgentConnection.js +99 -91
- package/dist/components/AppFrame.d.ts +24 -0
- package/dist/components/AppFrame.js +25 -0
- package/dist/components/AppTopBar.d.ts +71 -0
- package/dist/components/AppTopBar.js +82 -0
- package/dist/components/Artifact.js +1 -1
- package/dist/components/BrowserLiveView.js +1 -1
- package/dist/components/BrowserReplay.js +1 -1
- package/dist/components/BrowserRunPanel.js +1 -1
- package/dist/components/Card.d.ts +3 -0
- package/dist/components/Card.js +2 -1
- package/dist/components/ChainOfThought.d.ts +1 -1
- package/dist/components/ChainOfThought.js +1 -1
- package/dist/components/ChatGptSignInScreen.js +2 -2
- package/dist/components/Checkbox.js +5 -13
- package/dist/components/CheckboxGroup.d.ts +58 -2
- package/dist/components/CheckboxGroup.js +59 -1
- package/dist/components/CodeBlock.d.ts +1 -1
- package/dist/components/CodeBlock.js +1 -1
- package/dist/components/CodexModelSelector.js +1 -1
- package/dist/components/Commit.js +2 -2
- package/dist/components/ContextMenu.js +1 -1
- package/dist/components/Conversation.js +1 -1
- package/dist/components/DescriptionList.d.ts +12 -0
- package/dist/components/DescriptionList.js +11 -0
- package/dist/components/DescriptionListParts.d.ts +37 -0
- package/dist/components/DescriptionListParts.js +35 -0
- package/dist/components/EnvironmentVariables.js +2 -2
- package/dist/components/Fieldset.d.ts +13 -1
- package/dist/components/Fieldset.js +11 -2
- package/dist/components/FileTree.js +1 -1
- package/dist/components/IconButton.d.ts +33 -0
- package/dist/components/IconButton.js +27 -0
- package/dist/components/McpConnectionRequest.d.ts +44 -0
- package/dist/components/McpConnectionRequest.js +122 -0
- package/dist/components/McpServerPicker.d.ts +48 -23
- package/dist/components/McpServerPicker.js +238 -189
- package/dist/components/Menubar.js +1 -1
- package/dist/components/Message.d.ts +1 -0
- package/dist/components/Message.js +1 -285
- package/dist/components/MessageScroller.js +1 -1
- package/dist/components/ModelSelector.js +1 -1
- package/dist/components/Pricing.d.ts +37 -0
- package/dist/components/Pricing.js +17 -0
- package/dist/components/PricingBillingToggle.d.ts +23 -0
- package/dist/components/PricingBillingToggle.js +86 -0
- package/dist/components/PricingConfirmationDialog.d.ts +25 -0
- package/dist/components/PricingConfirmationDialog.js +57 -0
- package/dist/components/PricingFAQ.d.ts +18 -0
- package/dist/components/PricingFAQ.js +27 -0
- package/dist/components/PricingPage.d.ts +2 -38
- package/dist/components/PricingPage.js +39 -359
- package/dist/components/PricingPlanCard.d.ts +22 -0
- package/dist/components/PricingPlanCard.js +109 -0
- package/dist/components/PricingUsageCalculator.d.ts +22 -0
- package/dist/components/PricingUsageCalculator.js +96 -0
- package/dist/components/PromptInput.d.ts +2 -2
- package/dist/components/PromptInput.js +210 -15
- package/dist/components/Queue.js +1 -1
- package/dist/components/Reasoning.d.ts +2 -1
- package/dist/components/Reasoning.js +14 -29
- package/dist/components/ResizableSidePanel.d.ts +57 -0
- package/dist/components/ResizableSidePanel.js +151 -0
- package/dist/components/ResponsiveSidebarShell.d.ts +2 -0
- package/dist/components/ResponsiveSidebarShell.js +129 -0
- package/dist/components/Select.d.ts +2 -2
- package/dist/components/Select.js +11 -2
- package/dist/components/Slider.d.ts +13 -1
- package/dist/components/Slider.js +11 -0
- package/dist/components/Sources.js +1 -1
- package/dist/components/Stepper.d.ts +4 -0
- package/dist/components/Stepper.js +5 -3
- package/dist/components/SubagentRun.d.ts +18 -19
- package/dist/components/SubagentRun.js +25 -18
- package/dist/components/SubagentRunIndicator.d.ts +22 -0
- package/dist/components/SubagentRunIndicator.js +33 -0
- package/dist/components/SubagentRunList.d.ts +25 -0
- package/dist/components/SubagentRunList.js +26 -0
- package/dist/components/SubagentRunSelector.d.ts +47 -0
- package/dist/components/SubagentRunSelector.js +55 -0
- package/dist/components/Task.js +1 -1
- package/dist/components/Terminal.js +2 -2
- package/dist/components/TestResults.js +1 -1
- package/dist/components/Textarea.d.ts +21 -0
- package/dist/components/Textarea.js +19 -0
- package/dist/components/ThemeModeControl.d.ts +24 -0
- package/dist/components/ThemeModeControl.js +52 -0
- package/dist/components/ThemeProvider.d.ts +38 -0
- package/dist/components/ThemeProvider.js +80 -0
- package/dist/components/ThinkChat.d.ts +29 -6
- package/dist/components/ThinkChat.js +7 -4
- package/dist/components/ThinkChatActivity.d.ts +3 -2
- package/dist/components/ThinkChatActivity.js +12 -318
- package/dist/components/ThinkChatActivitySummary.d.ts +2 -0
- package/dist/components/ThinkChatActivitySummary.js +421 -0
- package/dist/components/ThinkChatToolPanels.d.ts +7 -5
- package/dist/components/ThinkChatToolPanels.js +70 -8
- package/dist/components/ThinkChatToolParts.d.ts +2 -2
- package/dist/components/ThinkChatToolParts.js +36 -10
- package/dist/components/ThinkExecutionDetail.d.ts +20 -0
- package/dist/components/ThinkExecutionDetail.js +80 -0
- package/dist/components/ThinkExecutionRun.d.ts +2 -0
- package/dist/components/ThinkExecutionRun.js +72 -0
- package/dist/components/ThinkExecutionStatus.d.ts +22 -0
- package/dist/components/ThinkExecutionStatus.js +36 -0
- package/dist/components/ThinkExecutionWorkbench.d.ts +7 -0
- package/dist/components/ThinkExecutionWorkbench.js +69 -225
- package/dist/components/ThinkFullApp.d.ts +58 -14
- package/dist/components/ThinkFullApp.js +129 -160
- package/dist/components/ThinkFullAppViewSwitch.d.ts +31 -0
- package/dist/components/ThinkFullAppViewSwitch.js +34 -0
- package/dist/components/ThinkRoutine.d.ts +36 -0
- package/dist/components/ThinkRoutine.js +15 -0
- package/dist/components/ThinkRoutineBrowser.d.ts +25 -0
- package/dist/components/ThinkRoutineBrowser.js +156 -0
- package/dist/components/ThinkRoutineDetail.d.ts +24 -0
- package/dist/components/ThinkRoutineDetail.js +102 -0
- package/dist/components/ThinkRoutineForm.d.ts +32 -0
- package/dist/components/ThinkRoutineForm.js +151 -0
- package/dist/components/ThinkRoutineLibrary.d.ts +74 -0
- package/dist/components/ThinkRoutineLibrary.js +178 -0
- package/dist/components/ThinkRoutineRuns.d.ts +24 -0
- package/dist/components/ThinkRoutineRuns.js +68 -0
- package/dist/components/ThinkThreadPicker.d.ts +18 -1
- package/dist/components/ThinkThreadPicker.js +51 -5
- package/dist/components/ThinkThreadShell.d.ts +12 -9
- package/dist/components/ThinkThreadShell.js +17 -137
- package/dist/components/Toggle.d.ts +1 -68
- package/dist/components/ToggleGroup.d.ts +1 -72
- package/dist/components/Tooltip.d.ts +3 -1
- package/dist/components/Tooltip.js +4 -2
- package/dist/components/WebPreview.js +1 -1
- package/dist/components/Workspace.d.ts +2 -0
- package/dist/components/Workspace.js +424 -0
- package/dist/components/WorkspaceBrowser.d.ts +32 -0
- package/dist/components/WorkspaceBrowser.js +103 -0
- package/dist/components/WorkspaceExplorer.d.ts +12 -79
- package/dist/components/WorkspaceExplorer.js +116 -330
- package/dist/components/WorkspaceFile.d.ts +2 -0
- package/dist/components/WorkspaceFile.js +195 -0
- package/dist/components/WorkspaceFileView.d.ts +26 -0
- package/dist/components/WorkspaceFileView.js +2 -0
- package/dist/components/WorkspaceHeader.d.ts +35 -0
- package/dist/components/WorkspaceHeader.js +51 -0
- package/dist/components/WorkspaceLink.d.ts +2 -0
- package/dist/components/WorkspaceLink.js +2 -0
- package/dist/components/icons.d.ts +2 -2
- package/dist/components/icons.js +2 -2
- package/dist/{copy-button-DUMLYdsk.js → copy-button-Dejn5heL.js} +1 -1
- package/dist/icons-CpfUer49.js +85 -0
- package/dist/{icons-CUCz_Sft.d.ts → icons-GQd1xF4e.d.ts} +51 -1
- package/dist/styles/activity-header.css +75 -0
- package/dist/styles/activity.css +49 -63
- package/dist/styles/agent-activity-preview-overlay.css +80 -0
- package/dist/styles/agent-activity-preview.css +235 -0
- package/dist/styles/agent-attention.css +122 -0
- package/dist/styles/agent-chat.css +133 -4
- package/dist/styles/app-frame.css +15 -0
- package/dist/styles/app-top-bar.css +100 -0
- package/dist/styles/avatar.css +0 -1
- package/dist/styles/browser-control-overlay.css +184 -0
- package/dist/styles/card.css +21 -0
- package/dist/styles/checkbox-group.css +69 -0
- package/dist/styles/description-list.css +50 -0
- package/dist/styles/fieldset.css +11 -0
- package/dist/styles/index.css +14 -2
- package/dist/styles/mcp-connection-request.css +60 -0
- package/dist/styles/mcp-server-picker.css +95 -68
- package/dist/styles/message.css +4 -43
- package/dist/styles/pricing-page.css +21 -27
- package/dist/styles/prompt-input.css +225 -32
- package/dist/styles/reasoning.css +1 -55
- package/dist/styles/recipes.css +23 -15
- package/dist/styles/resizable-side-panel.css +66 -0
- package/dist/styles/responsive-sidebar-shell.css +169 -0
- package/dist/styles/select.css +10 -0
- package/dist/styles/stepper.css +13 -0
- package/dist/styles/streamdown.css +0 -4
- package/dist/styles/subagent-run.css +55 -38
- package/dist/styles/textarea.css +20 -0
- package/dist/styles/theme-mode-control.css +72 -0
- package/dist/styles/think-execution-status.css +49 -0
- package/dist/styles/think-execution-workbench.css +8 -72
- package/dist/styles/think-full-app.css +104 -131
- package/dist/styles/think-routine-library.css +491 -0
- package/dist/styles/think-thread-picker.css +47 -3
- package/dist/styles/toggle-group.css +7 -0
- package/dist/styles/workspace-explorer.css +511 -64
- package/dist/utils/mcp-server-catalog.d.ts +82 -78
- package/dist/utils/mcp-server-catalog.js +64 -41
- package/dist/utils/structured-code.d.ts +1 -1
- package/dist/utils/theme.d.ts +22 -0
- package/dist/utils/theme.js +55 -0
- package/package.json +34 -28
- package/dist/icons-Ba9THQKz.js +0 -64
- package/dist/styles/think-resize-handle.css +0 -33
- package/dist/styles/think-thread-shell.css +0 -219
|
@@ -1,73 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { ToggleGroup as ToggleGroup$1 } from "@base-ui/react/toggle-group";
|
|
4
|
-
import { Toggle } from "@base-ui/react/toggle";
|
|
5
|
-
|
|
6
|
-
//#region src/components/ToggleGroup.d.ts
|
|
7
|
-
type ToggleGroupSize = ToggleVariantsOptions["size"] | "md";
|
|
8
|
-
interface ToggleGroupProps extends Omit<React.ComponentPropsWithRef<typeof ToggleGroup$1>, "className"> {
|
|
9
|
-
className?: string;
|
|
10
|
-
variant?: ToggleVariantsOptions["variant"];
|
|
11
|
-
/**
|
|
12
|
-
* The size of the toggle group.
|
|
13
|
-
* @default 'md'
|
|
14
|
-
*/
|
|
15
|
-
size?: ToggleGroupSize;
|
|
16
|
-
spacing?: number;
|
|
17
|
-
}
|
|
18
|
-
interface ToggleGroupItemProps extends Omit<React.ComponentPropsWithRef<typeof Toggle>, "className">, ToggleVariantsOptions {}
|
|
19
|
-
/**
|
|
20
|
-
* A toggle group component for selecting one or multiple options.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```tsx
|
|
24
|
-
* // Single selection (defaultValue is an array)
|
|
25
|
-
* <ToggleGroup defaultValue={["center"]}>
|
|
26
|
-
* <ToggleGroupItem value="left">Left</ToggleGroupItem>
|
|
27
|
-
* <ToggleGroupItem value="center">Center</ToggleGroupItem>
|
|
28
|
-
* <ToggleGroupItem value="right">Right</ToggleGroupItem>
|
|
29
|
-
* </ToggleGroup>
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```tsx
|
|
34
|
-
* // Multiple selection
|
|
35
|
-
* <ToggleGroup multiple defaultValue={["bold", "italic"]}>
|
|
36
|
-
* <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
|
37
|
-
* <ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
|
38
|
-
* <ToggleGroupItem value="underline">Underline</ToggleGroupItem>
|
|
39
|
-
* </ToggleGroup>
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @see {@link https://base-ui.com/react/components/toggle-group | Base UI Toggle Group}
|
|
43
|
-
*/
|
|
44
|
-
declare function ToggleGroupRoot({
|
|
45
|
-
size,
|
|
46
|
-
spacing,
|
|
47
|
-
variant,
|
|
48
|
-
className,
|
|
49
|
-
children,
|
|
50
|
-
style,
|
|
51
|
-
ref,
|
|
52
|
-
...props
|
|
53
|
-
}: ToggleGroupProps & {
|
|
54
|
-
ref?: React.Ref<HTMLDivElement>;
|
|
55
|
-
}): React.JSX.Element;
|
|
56
|
-
/**
|
|
57
|
-
* A single toggle item within a ToggleGroup.
|
|
58
|
-
*/
|
|
59
|
-
declare function ToggleGroupItem({
|
|
60
|
-
className,
|
|
61
|
-
variant,
|
|
62
|
-
size,
|
|
63
|
-
ref,
|
|
64
|
-
...props
|
|
65
|
-
}: ToggleGroupItemProps & {
|
|
66
|
-
ref?: React.Ref<HTMLButtonElement>;
|
|
67
|
-
}): React.JSX.Element;
|
|
68
|
-
declare const ToggleGroup: {
|
|
69
|
-
Root: typeof ToggleGroupRoot;
|
|
70
|
-
Item: typeof ToggleGroupItem;
|
|
71
|
-
};
|
|
72
|
-
//#endregion
|
|
1
|
+
import { n as ToggleGroupItemProps, r as ToggleGroupProps, t as ToggleGroup } from "../ToggleGroup-8hPv-b69.js";
|
|
73
2
|
export { ToggleGroup, ToggleGroupItemProps, ToggleGroupProps };
|
|
@@ -49,6 +49,7 @@ declare function TooltipTrigger(props: TooltipTriggerProps): React.JSX.Element;
|
|
|
49
49
|
declare function TooltipPopup({
|
|
50
50
|
className,
|
|
51
51
|
ref,
|
|
52
|
+
role,
|
|
52
53
|
...props
|
|
53
54
|
}: TooltipPopupProps & {
|
|
54
55
|
ref?: React.Ref<HTMLDivElement>;
|
|
@@ -73,7 +74,8 @@ declare function TooltipArrow({
|
|
|
73
74
|
ref?: React.Ref<HTMLDivElement>;
|
|
74
75
|
}): React.JSX.Element;
|
|
75
76
|
/**
|
|
76
|
-
* An accessible tooltip assembled from explicit Base UI parts.
|
|
77
|
+
* An accessible tooltip assembled from explicit Base UI parts. Popup defaults
|
|
78
|
+
* to `role="tooltip"` so assistive technology can identify revealed help text.
|
|
77
79
|
*/
|
|
78
80
|
declare const Tooltip: {
|
|
79
81
|
createHandle: typeof Tooltip$1.createHandle;
|
|
@@ -36,7 +36,7 @@ function TooltipTrigger(props) {
|
|
|
36
36
|
...props
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
function TooltipPopup({ className = "", ref, ...props }) {
|
|
39
|
+
function TooltipPopup({ className = "", ref, role = "tooltip", ...props }) {
|
|
40
40
|
const classes = [
|
|
41
41
|
"tooltip__popup",
|
|
42
42
|
"base-ui-popup",
|
|
@@ -44,6 +44,7 @@ function TooltipPopup({ className = "", ref, ...props }) {
|
|
|
44
44
|
].filter(Boolean).join(" ");
|
|
45
45
|
return /* @__PURE__ */ jsx(Tooltip$1.Popup, {
|
|
46
46
|
ref,
|
|
47
|
+
role,
|
|
47
48
|
"data-slot": "tooltip-content",
|
|
48
49
|
className: classes,
|
|
49
50
|
...props
|
|
@@ -73,7 +74,8 @@ function TooltipArrow({ className = "", ref, ...props }) {
|
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
/**
|
|
76
|
-
* An accessible tooltip assembled from explicit Base UI parts.
|
|
77
|
+
* An accessible tooltip assembled from explicit Base UI parts. Popup defaults
|
|
78
|
+
* to `role="tooltip"` so assistive technology can identify revealed help text.
|
|
77
79
|
*/
|
|
78
80
|
const Tooltip = {
|
|
79
81
|
createHandle: Tooltip$1.createHandle,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as cx } from "../class-names-9UtZwb0J.js";
|
|
2
|
-
import {
|
|
2
|
+
import { L as RefreshIcon, n as ArrowLeftIcon, r as ArrowRightIcon, s as ChevronDownIcon } from "../icons-CpfUer49.js";
|
|
3
3
|
import { Button } from "./Button.js";
|
|
4
4
|
import { Tooltip } from "./Tooltip.js";
|
|
5
5
|
import { Badge } from "./Badge.js";
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { getWorkspaceBasename, inferWorkspaceMedia } from "./WorkspaceFile.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
//#region src/components/Workspace.ts
|
|
4
|
+
const DEFAULT_MAX_INLINE_ASSET_BYTES = 32 * 1024 * 1024;
|
|
5
|
+
const WORKSPACE_DIRECTORY_PAGE_SIZE = 1e3;
|
|
6
|
+
const EMPTY_ENTRIES_BY_DIRECTORY = Object.freeze({});
|
|
7
|
+
const EMPTY_ENTRY_BY_PATH = /* @__PURE__ */ new Map();
|
|
8
|
+
const EMPTY_PATHS = /* @__PURE__ */ new Set();
|
|
9
|
+
function toErrorMessage(error) {
|
|
10
|
+
return error instanceof Error ? error.message : String(error);
|
|
11
|
+
}
|
|
12
|
+
function normalizeWorkspacePath(path) {
|
|
13
|
+
if (!path.startsWith("/") || path.includes("\0")) return null;
|
|
14
|
+
const segments = [];
|
|
15
|
+
for (const segment of path.split("/")) {
|
|
16
|
+
if (!segment || segment === ".") continue;
|
|
17
|
+
if (segment === "..") segments.pop();
|
|
18
|
+
else segments.push(segment);
|
|
19
|
+
}
|
|
20
|
+
return `/${segments.join("/")}`;
|
|
21
|
+
}
|
|
22
|
+
function isPathWithinWorkspaceRoot(path, root) {
|
|
23
|
+
const normalizedPath = normalizeWorkspacePath(path);
|
|
24
|
+
const normalizedRoot = normalizeWorkspacePath(root);
|
|
25
|
+
if (!normalizedPath || !normalizedRoot) return false;
|
|
26
|
+
return normalizedRoot === "/" || normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}/`);
|
|
27
|
+
}
|
|
28
|
+
function getWorkspaceParentPath(path) {
|
|
29
|
+
const separatorIndex = path.lastIndexOf("/");
|
|
30
|
+
return separatorIndex <= 0 ? "/" : path.slice(0, separatorIndex);
|
|
31
|
+
}
|
|
32
|
+
function getAssetDataSize(asset) {
|
|
33
|
+
if (asset.kind === "url") return void 0;
|
|
34
|
+
if (asset.data instanceof ArrayBuffer || asset.data instanceof Uint8Array) return asset.data.byteLength;
|
|
35
|
+
return asset.data.size;
|
|
36
|
+
}
|
|
37
|
+
function normalizeWorkspaceAsset(asset, path, inferredMedia, info) {
|
|
38
|
+
const media = asset.mimeType?.trim() ? inferWorkspaceMedia(path, asset.mimeType) : inferredMedia;
|
|
39
|
+
if (!media) return null;
|
|
40
|
+
const metadata = {
|
|
41
|
+
mimeType: media.mimeType,
|
|
42
|
+
size: asset.size ?? getAssetDataSize(asset) ?? info.size
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
asset: asset.kind === "url" ? {
|
|
46
|
+
...asset,
|
|
47
|
+
...metadata
|
|
48
|
+
} : {
|
|
49
|
+
...asset,
|
|
50
|
+
...metadata
|
|
51
|
+
},
|
|
52
|
+
media: inferWorkspaceMedia(path, media.mimeType) ?? media
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function readWorkspaceAssetBytes(source, path, info, maxInlineAssetBytes) {
|
|
56
|
+
if (info.size > maxInlineAssetBytes) {
|
|
57
|
+
const maximumMiB = Math.floor(maxInlineAssetBytes / (1024 * 1024));
|
|
58
|
+
throw new Error(`This file is too large for the built-in byte preview (${maximumMiB.toLocaleString()} MiB maximum). Provide resolveFileAsset with a URL instead.`);
|
|
59
|
+
}
|
|
60
|
+
const data = await source.readFileBytes(path);
|
|
61
|
+
if (data && data.byteLength > maxInlineAssetBytes) throw new Error("The returned bytes exceed the built-in preview limit. Provide resolveFileAsset with a URL instead.");
|
|
62
|
+
return data ? {
|
|
63
|
+
kind: "data",
|
|
64
|
+
data
|
|
65
|
+
} : null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Headless state for browsing a Think workspace: lazy directory loading,
|
|
69
|
+
* cached file reads, expansion/selection, and refresh. Feed the result to
|
|
70
|
+
* `WorkspaceExplorer`, or compose `WorkspaceBrowser` and `WorkspaceFileView`.
|
|
71
|
+
*/
|
|
72
|
+
function useWorkspace({ source, root = "/", resolveFileAsset, maxInlineAssetBytes = DEFAULT_MAX_INLINE_ASSET_BYTES }) {
|
|
73
|
+
const [entriesByDirectory, setEntriesByDirectory] = React.useState({});
|
|
74
|
+
const [filesByPath, setFilesByPath] = React.useState({});
|
|
75
|
+
const [expandedPaths, setExpandedPathsState] = React.useState(/* @__PURE__ */ new Set());
|
|
76
|
+
const [selectedPath, setSelectedPath] = React.useState(null);
|
|
77
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
78
|
+
const [loadingPaths, setLoadingPaths] = React.useState(/* @__PURE__ */ new Set());
|
|
79
|
+
const [directoryErrorsByPath, setDirectoryErrorsByPath] = React.useState({});
|
|
80
|
+
const [info, setInfo] = React.useState(null);
|
|
81
|
+
const [activeAssetFile, setActiveAssetFile] = React.useState(null);
|
|
82
|
+
const [stateGeneration, setStateGeneration] = React.useState(0);
|
|
83
|
+
const loadedDirectoriesRef = React.useRef(/* @__PURE__ */ new Set());
|
|
84
|
+
const directoryRequestVersionsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
85
|
+
const nextDirectoryRequestVersionRef = React.useRef(0);
|
|
86
|
+
const nextInfoRequestVersionRef = React.useRef(0);
|
|
87
|
+
const refreshRequestVersionRef = React.useRef(0);
|
|
88
|
+
const loadingOperationVersionRef = React.useRef(0);
|
|
89
|
+
const selectedPathRef = React.useRef(null);
|
|
90
|
+
const fileRequestRef = React.useRef(null);
|
|
91
|
+
const resolveFileAssetRef = React.useRef(resolveFileAsset);
|
|
92
|
+
resolveFileAssetRef.current = resolveFileAsset;
|
|
93
|
+
const maxInlineAssetBytesRef = React.useRef(DEFAULT_MAX_INLINE_ASSET_BYTES);
|
|
94
|
+
maxInlineAssetBytesRef.current = Number.isFinite(maxInlineAssetBytes) && maxInlineAssetBytes >= 0 ? maxInlineAssetBytes : DEFAULT_MAX_INLINE_ASSET_BYTES;
|
|
95
|
+
const sourceIdentityRef = React.useRef({
|
|
96
|
+
source,
|
|
97
|
+
root,
|
|
98
|
+
generation: 0
|
|
99
|
+
});
|
|
100
|
+
if (sourceIdentityRef.current.source !== source || sourceIdentityRef.current.root !== root) sourceIdentityRef.current = {
|
|
101
|
+
source,
|
|
102
|
+
root,
|
|
103
|
+
generation: sourceIdentityRef.current.generation + 1
|
|
104
|
+
};
|
|
105
|
+
const entryByPath = React.useMemo(() => {
|
|
106
|
+
const map = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const entries of Object.values(entriesByDirectory)) for (const entry of entries) map.set(entry.path, entry);
|
|
108
|
+
return map;
|
|
109
|
+
}, [entriesByDirectory]);
|
|
110
|
+
const markLoading = React.useCallback((path, loading) => {
|
|
111
|
+
setLoadingPaths((current) => {
|
|
112
|
+
const next = new Set(current);
|
|
113
|
+
if (loading) next.add(path);
|
|
114
|
+
else next.delete(path);
|
|
115
|
+
return next;
|
|
116
|
+
});
|
|
117
|
+
}, []);
|
|
118
|
+
const setDirectoryError = React.useCallback((path, message) => {
|
|
119
|
+
setDirectoryErrorsByPath((current) => {
|
|
120
|
+
if (message !== null) {
|
|
121
|
+
if (current[path] === message) return current;
|
|
122
|
+
return {
|
|
123
|
+
...current,
|
|
124
|
+
[path]: message
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (!Object.hasOwn(current, path)) return current;
|
|
128
|
+
const next = { ...current };
|
|
129
|
+
delete next[path];
|
|
130
|
+
return next;
|
|
131
|
+
});
|
|
132
|
+
}, []);
|
|
133
|
+
const activateAssetFile = React.useCallback((file) => {
|
|
134
|
+
setFilesByPath((current) => {
|
|
135
|
+
if (!Object.hasOwn(current, file.path)) return current;
|
|
136
|
+
const next = { ...current };
|
|
137
|
+
delete next[file.path];
|
|
138
|
+
return next;
|
|
139
|
+
});
|
|
140
|
+
setActiveAssetFile(file);
|
|
141
|
+
}, []);
|
|
142
|
+
const cacheTextFile = React.useCallback((file) => {
|
|
143
|
+
setActiveAssetFile((current) => current?.path === file.path ? null : current);
|
|
144
|
+
setFilesByPath((current) => ({
|
|
145
|
+
...current,
|
|
146
|
+
[file.path]: file
|
|
147
|
+
}));
|
|
148
|
+
}, []);
|
|
149
|
+
const cancelFileLoad = React.useCallback(() => {
|
|
150
|
+
const request = fileRequestRef.current;
|
|
151
|
+
if (!request) return;
|
|
152
|
+
request.controller.abort();
|
|
153
|
+
fileRequestRef.current = null;
|
|
154
|
+
markLoading(request.path, false);
|
|
155
|
+
}, [markLoading]);
|
|
156
|
+
const loadDirectory = React.useCallback(async (path, { force = false } = {}) => {
|
|
157
|
+
if (!force && loadedDirectoriesRef.current.has(path)) return;
|
|
158
|
+
const identity = sourceIdentityRef.current;
|
|
159
|
+
if (identity.source !== source || identity.root !== root) return;
|
|
160
|
+
const generation = identity.generation;
|
|
161
|
+
const requestVersion = nextDirectoryRequestVersionRef.current + 1;
|
|
162
|
+
nextDirectoryRequestVersionRef.current = requestVersion;
|
|
163
|
+
directoryRequestVersionsRef.current.set(path, requestVersion);
|
|
164
|
+
const requestIsCurrent = () => generation === sourceIdentityRef.current.generation && directoryRequestVersionsRef.current.get(path) === requestVersion;
|
|
165
|
+
markLoading(path, true);
|
|
166
|
+
try {
|
|
167
|
+
const entries = [];
|
|
168
|
+
let offset = 0;
|
|
169
|
+
for (;;) {
|
|
170
|
+
const page = await source.readDir(path, {
|
|
171
|
+
limit: WORKSPACE_DIRECTORY_PAGE_SIZE,
|
|
172
|
+
offset
|
|
173
|
+
});
|
|
174
|
+
if (!requestIsCurrent()) return;
|
|
175
|
+
entries.push(...page);
|
|
176
|
+
if (page.length < WORKSPACE_DIRECTORY_PAGE_SIZE) break;
|
|
177
|
+
offset += page.length;
|
|
178
|
+
}
|
|
179
|
+
loadedDirectoriesRef.current.add(path);
|
|
180
|
+
setEntriesByDirectory((current) => ({
|
|
181
|
+
...current,
|
|
182
|
+
[path]: entries
|
|
183
|
+
}));
|
|
184
|
+
setDirectoryError(path, null);
|
|
185
|
+
return entries;
|
|
186
|
+
} catch (listError) {
|
|
187
|
+
if (!requestIsCurrent()) return;
|
|
188
|
+
setDirectoryError(path, toErrorMessage(listError));
|
|
189
|
+
return null;
|
|
190
|
+
} finally {
|
|
191
|
+
if (requestIsCurrent()) markLoading(path, false);
|
|
192
|
+
}
|
|
193
|
+
}, [
|
|
194
|
+
source,
|
|
195
|
+
root,
|
|
196
|
+
markLoading,
|
|
197
|
+
setDirectoryError
|
|
198
|
+
]);
|
|
199
|
+
const loadFile = React.useCallback(async (path, { force = false, refreshRequestVersion } = {}) => {
|
|
200
|
+
const listedFileInfo = entryByPath.get(path) ?? null;
|
|
201
|
+
const cachedFile = filesByPath[path];
|
|
202
|
+
if (!force && cachedFile && !inferWorkspaceMedia(path, listedFileInfo?.mimeType)) return;
|
|
203
|
+
const identity = sourceIdentityRef.current;
|
|
204
|
+
if (identity.source !== source || identity.root !== root) return;
|
|
205
|
+
const generation = identity.generation;
|
|
206
|
+
cancelFileLoad();
|
|
207
|
+
const controller = new AbortController();
|
|
208
|
+
const request = {
|
|
209
|
+
path,
|
|
210
|
+
controller
|
|
211
|
+
};
|
|
212
|
+
fileRequestRef.current = request;
|
|
213
|
+
markLoading(path, true);
|
|
214
|
+
let fileInfo = listedFileInfo;
|
|
215
|
+
let media = inferWorkspaceMedia(path, fileInfo?.mimeType);
|
|
216
|
+
const requestIsCurrent = () => !controller.signal.aborted && generation === sourceIdentityRef.current.generation && selectedPathRef.current === path && (refreshRequestVersion === void 0 || refreshRequestVersionRef.current === refreshRequestVersion);
|
|
217
|
+
try {
|
|
218
|
+
if (!isPathWithinWorkspaceRoot(path, root)) throw new Error("This file is outside the configured workspace root.");
|
|
219
|
+
const resolvedInfo = await source.stat(path);
|
|
220
|
+
if (!requestIsCurrent()) return;
|
|
221
|
+
if (!resolvedInfo) {
|
|
222
|
+
fileInfo = null;
|
|
223
|
+
throw new Error("File metadata is unavailable because source.stat() returned null.");
|
|
224
|
+
}
|
|
225
|
+
fileInfo = resolvedInfo;
|
|
226
|
+
const readPath = normalizeWorkspacePath(resolvedInfo.path);
|
|
227
|
+
if (!readPath) throw new Error("The workspace source returned an invalid file path from stat().");
|
|
228
|
+
if (readPath !== resolvedInfo.path) throw new Error("The workspace source returned a non-canonical file path from stat().");
|
|
229
|
+
if (!isPathWithinWorkspaceRoot(readPath, root)) throw new Error("This file's resolved path is outside the configured workspace root.");
|
|
230
|
+
media = inferWorkspaceMedia(readPath, resolvedInfo.mimeType);
|
|
231
|
+
const resolver = resolveFileAssetRef.current;
|
|
232
|
+
const resolvedAsset = resolver ? await resolver({
|
|
233
|
+
path: readPath,
|
|
234
|
+
requestedPath: path,
|
|
235
|
+
root: normalizeWorkspacePath(root) ?? root,
|
|
236
|
+
info: resolvedInfo,
|
|
237
|
+
signal: controller.signal
|
|
238
|
+
}) : null;
|
|
239
|
+
if (!requestIsCurrent()) return;
|
|
240
|
+
if (resolvedAsset) {
|
|
241
|
+
const normalized = normalizeWorkspaceAsset(resolvedAsset, readPath, media, resolvedInfo);
|
|
242
|
+
if (!normalized) throw new Error("The host returned an asset without a supported image, PDF, audio, or video MIME type.");
|
|
243
|
+
media = normalized.media;
|
|
244
|
+
activateAssetFile({
|
|
245
|
+
path,
|
|
246
|
+
name: getWorkspaceBasename(path),
|
|
247
|
+
content: null,
|
|
248
|
+
asset: normalized.asset,
|
|
249
|
+
info: resolvedInfo,
|
|
250
|
+
error: null
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (media) {
|
|
255
|
+
const byteAsset = await readWorkspaceAssetBytes(source, readPath, resolvedInfo, maxInlineAssetBytesRef.current);
|
|
256
|
+
if (!requestIsCurrent()) return;
|
|
257
|
+
const normalized = byteAsset ? normalizeWorkspaceAsset(byteAsset, readPath, media, resolvedInfo) : null;
|
|
258
|
+
activateAssetFile({
|
|
259
|
+
path,
|
|
260
|
+
name: getWorkspaceBasename(path),
|
|
261
|
+
content: null,
|
|
262
|
+
asset: normalized?.asset ?? null,
|
|
263
|
+
info: resolvedInfo,
|
|
264
|
+
error: null
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const content = await source.readFile(readPath);
|
|
269
|
+
if (!requestIsCurrent()) return;
|
|
270
|
+
cacheTextFile({
|
|
271
|
+
path,
|
|
272
|
+
name: getWorkspaceBasename(path),
|
|
273
|
+
content,
|
|
274
|
+
asset: null,
|
|
275
|
+
info: resolvedInfo,
|
|
276
|
+
error: null
|
|
277
|
+
});
|
|
278
|
+
} catch (readError) {
|
|
279
|
+
if (!requestIsCurrent()) return;
|
|
280
|
+
const failedFile = {
|
|
281
|
+
path,
|
|
282
|
+
name: getWorkspaceBasename(path),
|
|
283
|
+
content: null,
|
|
284
|
+
asset: null,
|
|
285
|
+
info: fileInfo,
|
|
286
|
+
error: toErrorMessage(readError)
|
|
287
|
+
};
|
|
288
|
+
if (media) activateAssetFile(failedFile);
|
|
289
|
+
else cacheTextFile(failedFile);
|
|
290
|
+
} finally {
|
|
291
|
+
if (fileRequestRef.current === request && generation === sourceIdentityRef.current.generation) {
|
|
292
|
+
fileRequestRef.current = null;
|
|
293
|
+
markLoading(path, false);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}, [
|
|
297
|
+
activateAssetFile,
|
|
298
|
+
cacheTextFile,
|
|
299
|
+
cancelFileLoad,
|
|
300
|
+
entryByPath,
|
|
301
|
+
filesByPath,
|
|
302
|
+
markLoading,
|
|
303
|
+
root,
|
|
304
|
+
source
|
|
305
|
+
]);
|
|
306
|
+
const loadInfo = React.useCallback(async () => {
|
|
307
|
+
if (!("getWorkspaceInfo" in source)) return;
|
|
308
|
+
if (typeof source.getWorkspaceInfo !== "function") return;
|
|
309
|
+
const identity = sourceIdentityRef.current;
|
|
310
|
+
if (identity.source !== source || identity.root !== root) return;
|
|
311
|
+
const generation = identity.generation;
|
|
312
|
+
const requestVersion = nextInfoRequestVersionRef.current + 1;
|
|
313
|
+
nextInfoRequestVersionRef.current = requestVersion;
|
|
314
|
+
const requestIsCurrent = () => generation === sourceIdentityRef.current.generation && nextInfoRequestVersionRef.current === requestVersion;
|
|
315
|
+
try {
|
|
316
|
+
const nextInfo = await source.getWorkspaceInfo();
|
|
317
|
+
if (requestIsCurrent()) setInfo(nextInfo);
|
|
318
|
+
} catch {
|
|
319
|
+
if (requestIsCurrent()) setInfo(null);
|
|
320
|
+
}
|
|
321
|
+
}, [source, root]);
|
|
322
|
+
React.useEffect(() => {
|
|
323
|
+
const generation = sourceIdentityRef.current.generation;
|
|
324
|
+
const loadingOperationVersion = loadingOperationVersionRef.current + 1;
|
|
325
|
+
loadingOperationVersionRef.current = loadingOperationVersion;
|
|
326
|
+
refreshRequestVersionRef.current += 1;
|
|
327
|
+
cancelFileLoad();
|
|
328
|
+
setEntriesByDirectory({});
|
|
329
|
+
setFilesByPath({});
|
|
330
|
+
setActiveAssetFile(null);
|
|
331
|
+
setExpandedPathsState(/* @__PURE__ */ new Set());
|
|
332
|
+
setSelectedPath(null);
|
|
333
|
+
selectedPathRef.current = null;
|
|
334
|
+
setLoadingPaths(/* @__PURE__ */ new Set());
|
|
335
|
+
setDirectoryErrorsByPath({});
|
|
336
|
+
setInfo(null);
|
|
337
|
+
setStateGeneration(generation);
|
|
338
|
+
loadedDirectoriesRef.current = /* @__PURE__ */ new Set();
|
|
339
|
+
directoryRequestVersionsRef.current = /* @__PURE__ */ new Map();
|
|
340
|
+
setIsLoading(true);
|
|
341
|
+
Promise.all([loadDirectory(root, { force: true }), loadInfo()]).finally(() => {
|
|
342
|
+
if (generation === sourceIdentityRef.current.generation && loadingOperationVersionRef.current === loadingOperationVersion) setIsLoading(false);
|
|
343
|
+
});
|
|
344
|
+
}, [
|
|
345
|
+
cancelFileLoad,
|
|
346
|
+
loadDirectory,
|
|
347
|
+
loadInfo,
|
|
348
|
+
root
|
|
349
|
+
]);
|
|
350
|
+
React.useEffect(() => () => {
|
|
351
|
+
fileRequestRef.current?.controller.abort();
|
|
352
|
+
}, []);
|
|
353
|
+
const setExpandedPaths = React.useCallback((next) => {
|
|
354
|
+
setExpandedPathsState((current) => {
|
|
355
|
+
for (const path of next) if (!current.has(path)) loadDirectory(path);
|
|
356
|
+
return next;
|
|
357
|
+
});
|
|
358
|
+
}, [loadDirectory]);
|
|
359
|
+
const selectFile = React.useCallback((path) => {
|
|
360
|
+
cancelFileLoad();
|
|
361
|
+
selectedPathRef.current = path;
|
|
362
|
+
setActiveAssetFile(null);
|
|
363
|
+
setSelectedPath(path);
|
|
364
|
+
if (path !== null) loadFile(path);
|
|
365
|
+
}, [cancelFileLoad, loadFile]);
|
|
366
|
+
const refresh = React.useCallback(() => {
|
|
367
|
+
(async () => {
|
|
368
|
+
const identity = sourceIdentityRef.current;
|
|
369
|
+
if (identity.source !== source || identity.root !== root) return;
|
|
370
|
+
const generation = identity.generation;
|
|
371
|
+
const refreshRequestVersion = refreshRequestVersionRef.current + 1;
|
|
372
|
+
refreshRequestVersionRef.current = refreshRequestVersion;
|
|
373
|
+
const loadingOperationVersion = loadingOperationVersionRef.current + 1;
|
|
374
|
+
loadingOperationVersionRef.current = loadingOperationVersion;
|
|
375
|
+
const requestIsCurrent = () => generation === sourceIdentityRef.current.generation && refreshRequestVersionRef.current === refreshRequestVersion;
|
|
376
|
+
setIsLoading(true);
|
|
377
|
+
try {
|
|
378
|
+
const directoryPaths = [.../* @__PURE__ */ new Set([root, ...expandedPaths])];
|
|
379
|
+
const [refreshedListings] = await Promise.all([Promise.all(directoryPaths.map((path) => loadDirectory(path, { force: true }))), loadInfo()]);
|
|
380
|
+
if (!requestIsCurrent()) return;
|
|
381
|
+
const refreshedSelectedPath = selectedPathRef.current;
|
|
382
|
+
if (refreshedSelectedPath) {
|
|
383
|
+
const selectedDirectoryIndex = directoryPaths.indexOf(getWorkspaceParentPath(refreshedSelectedPath));
|
|
384
|
+
const selectedDirectoryListing = refreshedListings[selectedDirectoryIndex];
|
|
385
|
+
if (selectedDirectoryIndex >= 0 && (selectedDirectoryListing === null || selectedDirectoryListing === void 0)) return;
|
|
386
|
+
await loadFile(refreshedSelectedPath, {
|
|
387
|
+
force: true,
|
|
388
|
+
refreshRequestVersion
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
} finally {
|
|
392
|
+
if (requestIsCurrent() && loadingOperationVersionRef.current === loadingOperationVersion) setIsLoading(false);
|
|
393
|
+
}
|
|
394
|
+
})();
|
|
395
|
+
}, [
|
|
396
|
+
expandedPaths,
|
|
397
|
+
loadDirectory,
|
|
398
|
+
loadFile,
|
|
399
|
+
loadInfo,
|
|
400
|
+
root,
|
|
401
|
+
source
|
|
402
|
+
]);
|
|
403
|
+
const stateIsCurrent = stateGeneration === sourceIdentityRef.current.generation;
|
|
404
|
+
const directoryError = [...new Set(Object.values(directoryErrorsByPath))].join("; ") || null;
|
|
405
|
+
const visibleSelectedPath = stateIsCurrent ? selectedPath : null;
|
|
406
|
+
const selectedFile = visibleSelectedPath ? activeAssetFile?.path === visibleSelectedPath ? activeAssetFile : filesByPath[visibleSelectedPath] ?? null : null;
|
|
407
|
+
return {
|
|
408
|
+
root,
|
|
409
|
+
entriesByDirectory: stateIsCurrent ? entriesByDirectory : EMPTY_ENTRIES_BY_DIRECTORY,
|
|
410
|
+
entryByPath: stateIsCurrent ? entryByPath : EMPTY_ENTRY_BY_PATH,
|
|
411
|
+
expandedPaths: stateIsCurrent ? expandedPaths : EMPTY_PATHS,
|
|
412
|
+
setExpandedPaths,
|
|
413
|
+
selectedPath: visibleSelectedPath,
|
|
414
|
+
selectFile,
|
|
415
|
+
selectedFile,
|
|
416
|
+
isLoading: !stateIsCurrent || isLoading,
|
|
417
|
+
loadingPaths: stateIsCurrent ? loadingPaths : EMPTY_PATHS,
|
|
418
|
+
error: stateIsCurrent ? directoryError : null,
|
|
419
|
+
info: stateIsCurrent ? info : null,
|
|
420
|
+
refresh
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
//#endregion
|
|
424
|
+
export { useWorkspace };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { i as WorkspaceState } from "../Workspace-D39LxGvP.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/components/WorkspaceBrowser.d.ts
|
|
5
|
+
interface WorkspaceBrowserProps {
|
|
6
|
+
workspace: WorkspaceState;
|
|
7
|
+
title?: React.ReactNode;
|
|
8
|
+
/** Leading workspace control, such as a host-owned Back to chat button. */
|
|
9
|
+
leading?: React.ReactNode;
|
|
10
|
+
actions?: React.ReactNode;
|
|
11
|
+
showFilter?: boolean;
|
|
12
|
+
filterPlaceholder?: string;
|
|
13
|
+
emptyState?: React.ReactNode;
|
|
14
|
+
onSelectFile?: (path: string) => void;
|
|
15
|
+
onBack?: () => void;
|
|
16
|
+
onForward?: () => void;
|
|
17
|
+
}
|
|
18
|
+
/** Workspace tree and filter surface, usable independently from the file preview. */
|
|
19
|
+
declare function WorkspaceBrowser({
|
|
20
|
+
workspace,
|
|
21
|
+
title,
|
|
22
|
+
leading,
|
|
23
|
+
actions,
|
|
24
|
+
showFilter,
|
|
25
|
+
filterPlaceholder,
|
|
26
|
+
emptyState,
|
|
27
|
+
onSelectFile,
|
|
28
|
+
onBack,
|
|
29
|
+
onForward
|
|
30
|
+
}: WorkspaceBrowserProps): React.JSX.Element;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { WorkspaceBrowser, WorkspaceBrowserProps };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { R as SearchIcon } from "../icons-CpfUer49.js";
|
|
2
|
+
import { Field } from "./Field.js";
|
|
3
|
+
import { i as FileTreeFolder, r as FileTreeFile, t as FileTree } from "../FileTree-CkVbIMWV.js";
|
|
4
|
+
import { WorkspaceHeader } from "./WorkspaceHeader.js";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/WorkspaceBrowser.tsx
|
|
8
|
+
function compareFilteredWorkspaceFiles(left, right) {
|
|
9
|
+
return left.path.localeCompare(right.path) || left.name.localeCompare(right.name);
|
|
10
|
+
}
|
|
11
|
+
/** Workspace tree and filter surface, usable independently from the file preview. */
|
|
12
|
+
function WorkspaceBrowser({ workspace, title = "Workspace", leading, actions, showFilter = true, filterPlaceholder = "Filter files...", emptyState, onSelectFile, onBack, onForward }) {
|
|
13
|
+
const [filter, setFilter] = React.useState("");
|
|
14
|
+
const filterQuery = filter.trim().toLowerCase();
|
|
15
|
+
const { root, entriesByDirectory, entryByPath, expandedPaths, setExpandedPaths, selectedPath, selectFile, isLoading, error, info, refresh } = workspace;
|
|
16
|
+
const handleSelect = React.useCallback((path) => {
|
|
17
|
+
if (entryByPath.get(path)?.type === "directory") return;
|
|
18
|
+
selectFile(path);
|
|
19
|
+
onSelectFile?.(path);
|
|
20
|
+
}, [
|
|
21
|
+
entryByPath,
|
|
22
|
+
onSelectFile,
|
|
23
|
+
selectFile
|
|
24
|
+
]);
|
|
25
|
+
const filteredFiles = React.useMemo(() => {
|
|
26
|
+
if (!filterQuery) return [];
|
|
27
|
+
return [...entryByPath.values()].filter((entry) => entry.type !== "directory" && entry.path.toLowerCase().includes(filterQuery)).sort(compareFilteredWorkspaceFiles);
|
|
28
|
+
}, [entryByPath, filterQuery]);
|
|
29
|
+
const renderEntry = (entry) => {
|
|
30
|
+
if (entry.type === "directory") return /* @__PURE__ */ jsx(FileTreeFolder, {
|
|
31
|
+
path: entry.path,
|
|
32
|
+
name: entry.name,
|
|
33
|
+
children: (entriesByDirectory[entry.path] ?? []).map(renderEntry)
|
|
34
|
+
}, entry.path);
|
|
35
|
+
return /* @__PURE__ */ jsx(FileTreeFile, {
|
|
36
|
+
path: entry.path,
|
|
37
|
+
name: entry.name
|
|
38
|
+
}, entry.path);
|
|
39
|
+
};
|
|
40
|
+
const rootEntries = entriesByDirectory[root] ?? [];
|
|
41
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(WorkspaceHeader, {
|
|
42
|
+
title,
|
|
43
|
+
fileCount: info?.fileCount,
|
|
44
|
+
leading,
|
|
45
|
+
actions,
|
|
46
|
+
isRefreshing: isLoading,
|
|
47
|
+
onBack,
|
|
48
|
+
onForward,
|
|
49
|
+
onRefresh: refresh
|
|
50
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
51
|
+
className: "workspace-explorer__body",
|
|
52
|
+
"data-workspace-browser-focus-target": "",
|
|
53
|
+
role: "region",
|
|
54
|
+
"aria-label": "Workspace files",
|
|
55
|
+
tabIndex: -1,
|
|
56
|
+
children: [showFilter ? /* @__PURE__ */ jsxs(Field.Root, {
|
|
57
|
+
className: "workspace-explorer__filter",
|
|
58
|
+
name: "workspace-filter",
|
|
59
|
+
children: [/* @__PURE__ */ jsx(Field.Label, {
|
|
60
|
+
className: "workspace-explorer__filter-label",
|
|
61
|
+
children: "Filter workspace files"
|
|
62
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
63
|
+
className: "workspace-explorer__filter-control",
|
|
64
|
+
children: [/* @__PURE__ */ jsx(SearchIcon, { "aria-hidden": "true" }), /* @__PURE__ */ jsx(Field.Control, {
|
|
65
|
+
type: "search",
|
|
66
|
+
value: filter,
|
|
67
|
+
onChange: (event) => setFilter(event.currentTarget.value),
|
|
68
|
+
placeholder: filterPlaceholder,
|
|
69
|
+
autoComplete: "off"
|
|
70
|
+
})]
|
|
71
|
+
})]
|
|
72
|
+
}) : null, error ? /* @__PURE__ */ jsx("div", {
|
|
73
|
+
className: "workspace-explorer__empty",
|
|
74
|
+
role: "alert",
|
|
75
|
+
children: error
|
|
76
|
+
}) : filterQuery ? filteredFiles.length > 0 ? /* @__PURE__ */ jsx(FileTree, {
|
|
77
|
+
className: "workspace-explorer__tree",
|
|
78
|
+
selectedPath,
|
|
79
|
+
onSelect: handleSelect,
|
|
80
|
+
"aria-label": "Workspace files",
|
|
81
|
+
children: filteredFiles.map((entry) => /* @__PURE__ */ jsx(FileTreeFile, {
|
|
82
|
+
path: entry.path,
|
|
83
|
+
name: entry.path
|
|
84
|
+
}, entry.path))
|
|
85
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
86
|
+
className: "workspace-explorer__empty",
|
|
87
|
+
children: "No matching files"
|
|
88
|
+
}) : rootEntries.length > 0 ? /* @__PURE__ */ jsx(FileTree, {
|
|
89
|
+
className: "workspace-explorer__tree",
|
|
90
|
+
expanded: new Set(expandedPaths),
|
|
91
|
+
onExpandedChange: setExpandedPaths,
|
|
92
|
+
selectedPath,
|
|
93
|
+
onSelect: handleSelect,
|
|
94
|
+
"aria-label": "Workspace files",
|
|
95
|
+
children: rootEntries.map(renderEntry)
|
|
96
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
97
|
+
className: "workspace-explorer__empty",
|
|
98
|
+
children: isLoading ? "Loading workspace..." : emptyState ?? "No workspace files"
|
|
99
|
+
})]
|
|
100
|
+
})] });
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { WorkspaceBrowser };
|