@workbench-kit/shell-react 0.0.2-prototype.0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +49 -0
- package/src/chat-ai-mock-proposals.ts +39 -0
- package/src/chat-command-input.ts +52 -0
- package/src/chat-command-policy.ts +54 -0
- package/src/chat-command-surface.tsx +155 -0
- package/src/chat-view-data.ts +20 -0
- package/src/chat-view.tsx +220 -0
- package/src/command-inspector-surface.tsx +46 -0
- package/src/commands-view-data.ts +17 -0
- package/src/commands-view.tsx +43 -0
- package/src/createCommandWorkspaceExplorerPort.ts +86 -0
- package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
- package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
- package/src/devtools/index.ts +11 -0
- package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
- package/src/devtools/workbench-devtools-snapshot.ts +140 -0
- package/src/devtools/workbench-devtools.css +82 -0
- package/src/editor-area-dnd.ts +69 -0
- package/src/editor-area-model.ts +71 -0
- package/src/editor-area.css +454 -0
- package/src/editor-area.tsx +676 -0
- package/src/editor-host-surface.tsx +634 -0
- package/src/editor-pane-visibility.ts +130 -0
- package/src/editor-resource.ts +36 -0
- package/src/editor-state-storage.ts +193 -0
- package/src/editor-tab-context-menu.ts +118 -0
- package/src/editor-view-providers.tsx +552 -0
- package/src/editor-workspace-file-availability.ts +15 -0
- package/src/editor-workspace-reconcile.tsx +28 -0
- package/src/explorer-context-menu.ts +114 -0
- package/src/explorer-reveal.ts +112 -0
- package/src/explorer-view-data.ts +58 -0
- package/src/explorer-view.tsx +224 -0
- package/src/extension-context-menu.ts +52 -0
- package/src/extension-management-model.ts +313 -0
- package/src/extensions-view-data.ts +19 -0
- package/src/extensions-view.tsx +38 -0
- package/src/field-remap-demo.tsx +18 -0
- package/src/field-remap-editor-surface.tsx +36 -0
- package/src/field-remap-flow-adapter.ts +240 -0
- package/src/field-remap-flow.tsx +369 -0
- package/src/field-remap-graph.tsx +559 -0
- package/src/field-remap-panel.tsx +175 -0
- package/src/field-remap-samples.ts +352 -0
- package/src/field-remap-tree.tsx +404 -0
- package/src/field-remap-view-data.ts +16 -0
- package/src/field-remap-view.css +676 -0
- package/src/field-remap-view.tsx +86 -0
- package/src/index.ts +199 -0
- package/src/jdw-lab-view-data.ts +25 -0
- package/src/jdw-lab-view.css +21 -0
- package/src/jdw-lab-view.tsx +79 -0
- package/src/jdw-widget-form-view.tsx +65 -0
- package/src/jdw-widget-preview-view.tsx +51 -0
- package/src/json-form-source-patch.ts +323 -0
- package/src/jsonata-transform.ts +36 -0
- package/src/keybinding-management-settings.tsx +16 -0
- package/src/keybinding-overrides-storage.ts +57 -0
- package/src/management-palette-commands.ts +38 -0
- package/src/management-settings-ids.ts +12 -0
- package/src/management-settings.tsx +75 -0
- package/src/preference-settings-storage.ts +58 -0
- package/src/provider.tsx +573 -0
- package/src/search-view-data.ts +15 -0
- package/src/search-view.tsx +62 -0
- package/src/shell-model.tsx +149 -0
- package/src/shell-secondary-actions.tsx +59 -0
- package/src/shell-settings-constants.ts +9 -0
- package/src/shell-settings.tsx +599 -0
- package/src/shell-titlebar-layout-controls.tsx +4 -0
- package/src/shell-view-host.tsx +199 -0
- package/src/shell.tsx +649 -0
- package/src/table-mapping-adapter.ts +59 -0
- package/src/use-active-workspace-path.ts +15 -0
- package/src/use-command-management.ts +191 -0
- package/src/use-context-key-revision.ts +18 -0
- package/src/use-editor.ts +107 -0
- package/src/use-extension-management.ts +193 -0
- package/src/use-keybinding-management.ts +130 -0
- package/src/use-persisted-workbench-appearance.ts +33 -0
- package/src/use-workbench-chat-command-proposals.ts +194 -0
- package/src/use-workbench-command-descriptors.ts +41 -0
- package/src/workbench-appearance-storage.ts +115 -0
- package/src/workbench-command-host.tsx +243 -0
- package/src/workbench-command-palette.ts +208 -0
- package/src/workbench-keybinding-bridge.ts +54 -0
- package/src/workbench-layout-storage.ts +145 -0
- package/src/workbench-profile-modal.tsx +118 -0
- package/src/workbench-shell-command-registration.ts +58 -0
- package/src/workbench-startup-gate.tsx +136 -0
- package/src/workbench-status-sections.ts +43 -0
- package/src/workbench-user-commands.ts +46 -0
- package/src/workspace-view-state.ts +35 -0
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workbench-kit/shell-react",
|
|
3
|
+
"version": "0.0.2-prototype.0.2.6",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"!src/**/*.test.ts",
|
|
12
|
+
"!src/**/*.test.tsx",
|
|
13
|
+
"!src/**/*.stories.ts",
|
|
14
|
+
"!src/**/*.stories.tsx"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@radix-ui/react-select": "^2.3.4",
|
|
18
|
+
"@radix-ui/react-slot": "^1.3.0",
|
|
19
|
+
"@xyflow/react": "^12.11.2",
|
|
20
|
+
"jsonata": "^1.8.7",
|
|
21
|
+
"react-table-mapping": "^1.0.3",
|
|
22
|
+
"@workbench-kit/react": "0.0.2-prototype.0.2.6",
|
|
23
|
+
"@workbench-kit/workbench-config": "0.0.2-prototype.0.2.6",
|
|
24
|
+
"@workbench-kit/tokens": "0.0.2-prototype.0.2.6",
|
|
25
|
+
"@workbench-kit/field-remap": "0.0.2-prototype.0.2.6",
|
|
26
|
+
"@workbench-kit/workspace": "0.0.2-prototype.0.2.6",
|
|
27
|
+
"@workbench-kit/platform": "0.0.2-prototype.0.2.6",
|
|
28
|
+
"@workbench-kit/workbench-core": "0.0.2-prototype.0.2.6"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"react-dom": "^19.0.0"
|
|
33
|
+
},
|
|
34
|
+
"description": "React shell and host assembly for Workbench Kit.",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public",
|
|
37
|
+
"tag": "prototype",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/NewChoBo/workbench-kit.git",
|
|
43
|
+
"directory": "packages/shell-react"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "vitest run --config vitest.config.ts src",
|
|
47
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WorkbenchChatCommandProposalInput } from './use-workbench-chat-command-proposals.js';
|
|
2
|
+
|
|
3
|
+
const MOCK_AI_COMMAND_PROPOSALS: readonly WorkbenchChatCommandProposalInput[] = [
|
|
4
|
+
{
|
|
5
|
+
commandId: 'workbench.showActivity.explorer',
|
|
6
|
+
description: 'Focus the Explorer activity in the primary sidebar.',
|
|
7
|
+
id: 'mock-proposal-show-explorer',
|
|
8
|
+
label: 'Show Explorer',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
commandId: 'workbench.togglePrimarySidebar',
|
|
12
|
+
description: 'Toggle the primary sidebar visibility.',
|
|
13
|
+
id: 'mock-proposal-toggle-sidebar',
|
|
14
|
+
label: 'Toggle Primary Sidebar',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function createMockAiChatCommandProposals(
|
|
19
|
+
userMessage: string,
|
|
20
|
+
): readonly WorkbenchChatCommandProposalInput[] {
|
|
21
|
+
const normalized = userMessage.trim().toLocaleLowerCase();
|
|
22
|
+
if (!normalized || normalized === 'help') {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return MOCK_AI_COMMAND_PROPOSALS;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createMockAiChatResponseContent(userMessage: string) {
|
|
30
|
+
const proposals = createMockAiChatCommandProposals(userMessage);
|
|
31
|
+
if (!proposals.length) {
|
|
32
|
+
return `Received: ${userMessage}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return [
|
|
36
|
+
`I can help with "${userMessage.trim()}".`,
|
|
37
|
+
'Review the suggested commands below. Safe commands run automatically; workspace changes require approval.',
|
|
38
|
+
].join('\n\n');
|
|
39
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type WorkbenchChatCommandInputParseResult =
|
|
2
|
+
| { type: 'none' }
|
|
3
|
+
| { type: 'unknown'; token: string }
|
|
4
|
+
| { commandId: string; message: string; type: 'invalid-arguments' }
|
|
5
|
+
| { args: readonly unknown[]; commandId: string; type: 'run' };
|
|
6
|
+
|
|
7
|
+
export function parseWorkbenchChatCommandInput(
|
|
8
|
+
input: string,
|
|
9
|
+
isValidCommandId: (id: string) => boolean,
|
|
10
|
+
): WorkbenchChatCommandInputParseResult {
|
|
11
|
+
const trimmed = input.trim();
|
|
12
|
+
if (!trimmed.startsWith('/')) {
|
|
13
|
+
return { type: 'none' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const [rawToken] = trimmed.slice(1).split(/\s+/, 1);
|
|
17
|
+
if (!rawToken) {
|
|
18
|
+
return { token: '', type: 'unknown' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!isValidCommandId(rawToken)) {
|
|
22
|
+
return { token: rawToken, type: 'unknown' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const argumentText = getSlashCommandArgumentText(trimmed, rawToken);
|
|
26
|
+
if (!argumentText) {
|
|
27
|
+
return {
|
|
28
|
+
args: [],
|
|
29
|
+
commandId: rawToken,
|
|
30
|
+
type: 'run',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
args: [JSON.parse(argumentText) as unknown],
|
|
37
|
+
commandId: rawToken,
|
|
38
|
+
type: 'run',
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
commandId: rawToken,
|
|
43
|
+
message:
|
|
44
|
+
'Command arguments must be valid JSON. Use /command.id {"key":"value"} or /command.id "value".',
|
|
45
|
+
type: 'invalid-arguments',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getSlashCommandArgumentText(trimmedInput: string, commandId: string) {
|
|
51
|
+
return trimmedInput.slice(1 + commandId.length).trim();
|
|
52
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResolveWorkbenchCommandExecutionPolicyInput,
|
|
3
|
+
WorkbenchCommandExecutionPolicy,
|
|
4
|
+
} from '@workbench-kit/react/workbench';
|
|
5
|
+
import type { WorkbenchStorageReader } from '@workbench-kit/workbench-core';
|
|
6
|
+
|
|
7
|
+
import { readPersistedLocalPreferences } from './preference-settings-storage.js';
|
|
8
|
+
|
|
9
|
+
export const WORKBENCH_AI_CHAT_COMMAND_DEFAULT_POLICY_KEY = 'workbench.chat.aiCommandDefaultPolicy';
|
|
10
|
+
|
|
11
|
+
function isWorkbenchCommandExecutionPolicy(
|
|
12
|
+
value: unknown,
|
|
13
|
+
): value is WorkbenchCommandExecutionPolicy {
|
|
14
|
+
return value === 'auto-allow' || value === 'approval-required' || value === 'auto-deny';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readWorkbenchAiChatCommandPolicyInput(
|
|
18
|
+
storageKey?: string,
|
|
19
|
+
storage?: WorkbenchStorageReader,
|
|
20
|
+
): ResolveWorkbenchCommandExecutionPolicyInput {
|
|
21
|
+
const preferences = readPersistedLocalPreferences(storageKey, storage);
|
|
22
|
+
const configuredDefault = preferences[WORKBENCH_AI_CHAT_COMMAND_DEFAULT_POLICY_KEY];
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
defaultPolicy: isWorkbenchCommandExecutionPolicy(configuredDefault)
|
|
26
|
+
? configuredDefault
|
|
27
|
+
: undefined,
|
|
28
|
+
mutatingDefaultPolicy: 'approval-required',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createWorkbenchAiChatCommandPolicyById(
|
|
33
|
+
entries: Readonly<Record<string, WorkbenchCommandExecutionPolicy>>,
|
|
34
|
+
): ResolveWorkbenchCommandExecutionPolicyInput {
|
|
35
|
+
return {
|
|
36
|
+
policyByCommandId: entries,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function mergeWorkbenchAiChatCommandPolicyInput(
|
|
41
|
+
...inputs: readonly ResolveWorkbenchCommandExecutionPolicyInput[]
|
|
42
|
+
): ResolveWorkbenchCommandExecutionPolicyInput {
|
|
43
|
+
return inputs.reduce<ResolveWorkbenchCommandExecutionPolicyInput>(
|
|
44
|
+
(merged, input) => ({
|
|
45
|
+
defaultPolicy: input.defaultPolicy ?? merged.defaultPolicy,
|
|
46
|
+
mutatingDefaultPolicy: input.mutatingDefaultPolicy ?? merged.mutatingDefaultPolicy,
|
|
47
|
+
policyByCommandId: {
|
|
48
|
+
...merged.policyByCommandId,
|
|
49
|
+
...input.policyByCommandId,
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
{},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, type KeyboardEvent } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
WorkbenchCommandSuggest,
|
|
4
|
+
isWorkbenchCommandRunnable,
|
|
5
|
+
useSlashCommandSuggest,
|
|
6
|
+
type WorkbenchCommandDescriptor,
|
|
7
|
+
} from '@workbench-kit/react/workbench';
|
|
8
|
+
|
|
9
|
+
import { parseWorkbenchChatCommandInput } from './chat-command-input.js';
|
|
10
|
+
import { useWorkbench } from './provider.js';
|
|
11
|
+
import { useWorkbenchCommandDescriptors } from './use-workbench-command-descriptors.js';
|
|
12
|
+
|
|
13
|
+
export interface WorkbenchChatCommandRunResult {
|
|
14
|
+
commandId: string;
|
|
15
|
+
label?: string | undefined;
|
|
16
|
+
message?: string | undefined;
|
|
17
|
+
status: 'error' | 'success';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WorkbenchChatCommandSurfaceOptions {
|
|
21
|
+
additionalCommands?: readonly WorkbenchCommandDescriptor[] | undefined;
|
|
22
|
+
onCommandResult?: ((result: WorkbenchChatCommandRunResult) => void) | undefined;
|
|
23
|
+
onValueChange: (value: string) => void;
|
|
24
|
+
value: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useWorkbenchChatCommandSurface({
|
|
28
|
+
additionalCommands,
|
|
29
|
+
onCommandResult,
|
|
30
|
+
onValueChange,
|
|
31
|
+
value,
|
|
32
|
+
}: WorkbenchChatCommandSurfaceOptions) {
|
|
33
|
+
const { executeCommand } = useWorkbench();
|
|
34
|
+
const composerRef = useRef<HTMLTextAreaElement>(null);
|
|
35
|
+
const commands = useWorkbenchCommandDescriptors(additionalCommands);
|
|
36
|
+
const commandIds = useMemo(() => new Set(commands.map((command) => command.id)), [commands]);
|
|
37
|
+
const suggest = useSlashCommandSuggest({ commands, value });
|
|
38
|
+
|
|
39
|
+
const runCommand = useCallback(
|
|
40
|
+
async (commandId: string, args: readonly unknown[] = []) => {
|
|
41
|
+
const command = commands.find((candidate) => candidate.id === commandId);
|
|
42
|
+
if (command && !isWorkbenchCommandRunnable(command)) {
|
|
43
|
+
onCommandResult?.({
|
|
44
|
+
commandId,
|
|
45
|
+
label: command.label,
|
|
46
|
+
message: command.disabledReason ?? 'Command is currently unavailable.',
|
|
47
|
+
status: 'error',
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onValueChange('');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await executeCommand(commandId, ...args);
|
|
56
|
+
onCommandResult?.({
|
|
57
|
+
commandId,
|
|
58
|
+
label: command?.label,
|
|
59
|
+
status: 'success',
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
onCommandResult?.({
|
|
63
|
+
commandId,
|
|
64
|
+
label: command?.label,
|
|
65
|
+
message: getCommandErrorMessage(error),
|
|
66
|
+
status: 'error',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
[commands, executeCommand, onCommandResult, onValueChange],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const runInputAsCommand = useCallback(
|
|
74
|
+
(message: string) => {
|
|
75
|
+
const parsed = parseWorkbenchChatCommandInput(message, (commandId) =>
|
|
76
|
+
commandIds.has(commandId),
|
|
77
|
+
);
|
|
78
|
+
if (parsed.type === 'none') {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (parsed.type === 'unknown') {
|
|
83
|
+
onValueChange('');
|
|
84
|
+
onCommandResult?.({
|
|
85
|
+
commandId: parsed.token || '/',
|
|
86
|
+
message: parsed.token ? `Unknown command: ${parsed.token}` : 'Type a command after /.',
|
|
87
|
+
status: 'error',
|
|
88
|
+
});
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (parsed.type === 'invalid-arguments') {
|
|
93
|
+
onValueChange('');
|
|
94
|
+
onCommandResult?.({
|
|
95
|
+
commandId: parsed.commandId,
|
|
96
|
+
message: parsed.message,
|
|
97
|
+
status: 'error',
|
|
98
|
+
});
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void runCommand(parsed.commandId, parsed.args);
|
|
103
|
+
return true;
|
|
104
|
+
},
|
|
105
|
+
[commandIds, onCommandResult, onValueChange, runCommand],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const handleKeyDown = useCallback(
|
|
109
|
+
(event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
110
|
+
suggest.handleKeyDown(event, (commandId) => {
|
|
111
|
+
void runCommand(commandId);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
[runCommand, suggest],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const openCommandSuggest = useCallback(() => {
|
|
118
|
+
if (!value.trimStart().startsWith('/')) {
|
|
119
|
+
onValueChange('/');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
window.requestAnimationFrame(() => {
|
|
123
|
+
composerRef.current?.focus();
|
|
124
|
+
});
|
|
125
|
+
}, [onValueChange, value]);
|
|
126
|
+
|
|
127
|
+
const commandSuggestPopover = (
|
|
128
|
+
<WorkbenchCommandSuggest
|
|
129
|
+
activeCommandId={suggest.activeCommandId}
|
|
130
|
+
commands={suggest.suggestedCommands}
|
|
131
|
+
query={suggest.commandQuery}
|
|
132
|
+
visible={suggest.isOpen}
|
|
133
|
+
onActiveCommandChange={suggest.setActiveCommandByKey}
|
|
134
|
+
onRunCommand={(command) => {
|
|
135
|
+
void runCommand(command.id);
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
commandSuggestPopover,
|
|
142
|
+
composerRef,
|
|
143
|
+
onCommandClick: openCommandSuggest,
|
|
144
|
+
onKeyDown: handleKeyDown,
|
|
145
|
+
runInputAsCommand,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getCommandErrorMessage(error: unknown) {
|
|
150
|
+
if (error instanceof Error) {
|
|
151
|
+
return error.message;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return String(error);
|
|
155
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const BUILTIN_CHAT_VIEW_RENDER_KIND = 'workbench-kit.builtin.chat.view' as const;
|
|
2
|
+
|
|
3
|
+
export type BuiltinChatViewMode = 'aiChat' | 'chatting';
|
|
4
|
+
|
|
5
|
+
export interface BuiltinChatViewRenderData {
|
|
6
|
+
readonly kind: typeof BUILTIN_CHAT_VIEW_RENDER_KIND;
|
|
7
|
+
readonly mode: BuiltinChatViewMode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isBuiltinChatViewRenderData(value: unknown): value is BuiltinChatViewRenderData {
|
|
11
|
+
if (typeof value !== 'object' || value === null) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const candidate = value as Partial<BuiltinChatViewRenderData>;
|
|
16
|
+
return (
|
|
17
|
+
candidate.kind === BUILTIN_CHAT_VIEW_RENDER_KIND &&
|
|
18
|
+
(candidate.mode === 'chatting' || candidate.mode === 'aiChat')
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { ChatPanel, type ChatMessage } from '@workbench-kit/react/workbench/chat';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createMockAiChatCommandProposals,
|
|
6
|
+
createMockAiChatResponseContent,
|
|
7
|
+
} from './chat-ai-mock-proposals.js';
|
|
8
|
+
import {
|
|
9
|
+
readWorkbenchAiChatCommandPolicyInput,
|
|
10
|
+
mergeWorkbenchAiChatCommandPolicyInput,
|
|
11
|
+
} from './chat-command-policy.js';
|
|
12
|
+
import {
|
|
13
|
+
useWorkbenchChatCommandSurface,
|
|
14
|
+
type WorkbenchChatCommandRunResult,
|
|
15
|
+
} from './chat-command-surface.js';
|
|
16
|
+
import { type BuiltinChatViewMode, type BuiltinChatViewRenderData } from './chat-view-data.js';
|
|
17
|
+
import { useWorkbenchChatCommandProposals } from './use-workbench-chat-command-proposals.js';
|
|
18
|
+
import { useWorkbenchCommandDescriptors } from './use-workbench-command-descriptors.js';
|
|
19
|
+
|
|
20
|
+
export type { BuiltinChatViewMode, BuiltinChatViewRenderData };
|
|
21
|
+
export { BUILTIN_CHAT_VIEW_RENDER_KIND, isBuiltinChatViewRenderData } from './chat-view-data.js';
|
|
22
|
+
|
|
23
|
+
type BuiltinChatRuntimeStatus = 'idle' | 'running' | 'error';
|
|
24
|
+
|
|
25
|
+
const initialChattingMessages: readonly ChatMessage[] = [
|
|
26
|
+
{
|
|
27
|
+
content: 'Share updates here while working in the workspace.',
|
|
28
|
+
createdAt: '2026-06-18T14:29:00.000Z',
|
|
29
|
+
id: 'workbench-chatting-intro',
|
|
30
|
+
label: 'Alex',
|
|
31
|
+
source: 'assistant',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const initialAiChatMessages: readonly ChatMessage[] = [
|
|
36
|
+
{
|
|
37
|
+
content: 'Ask about the workspace or run a command with `/command.id`.',
|
|
38
|
+
createdAt: '2026-06-18T14:29:00.000Z',
|
|
39
|
+
id: 'workbench-ai-chat-intro',
|
|
40
|
+
label: 'Assistant',
|
|
41
|
+
source: 'assistant',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export function BuiltinChatView({ mode }: { mode: BuiltinChatViewMode }) {
|
|
46
|
+
if (mode === 'aiChat') {
|
|
47
|
+
return <BuiltinAiChatView />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return <BuiltinChattingView />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createChatCommandFeedbackMessage(result: WorkbenchChatCommandRunResult): ChatMessage {
|
|
54
|
+
const title = result.label ?? result.commandId;
|
|
55
|
+
const status =
|
|
56
|
+
result.status === 'success' ? `Ran command \`${title}\`.` : `Command failed: \`${title}\`.`;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
content: result.message ? `${status}\n\n${result.message}` : status,
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
id: `chat-command-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
62
|
+
label: 'Workbench',
|
|
63
|
+
source: 'assistant',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createChatMessageId(prefix: string) {
|
|
68
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function BuiltinChattingView() {
|
|
72
|
+
const [draft, setDraft] = useState('');
|
|
73
|
+
const [messages, setMessages] = useState<ChatMessage[]>(() => [...initialChattingMessages]);
|
|
74
|
+
const appendCommandResult = useCallback((result: WorkbenchChatCommandRunResult) => {
|
|
75
|
+
setMessages((currentMessages) => [
|
|
76
|
+
...currentMessages,
|
|
77
|
+
createChatCommandFeedbackMessage(result),
|
|
78
|
+
]);
|
|
79
|
+
}, []);
|
|
80
|
+
const chatCommands = useWorkbenchChatCommandSurface({
|
|
81
|
+
onCommandResult: appendCommandResult,
|
|
82
|
+
onValueChange: setDraft,
|
|
83
|
+
value: draft,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="workbench-chat-view">
|
|
88
|
+
<ChatPanel
|
|
89
|
+
assistantLabel="Alex"
|
|
90
|
+
commandLabel="Show commands"
|
|
91
|
+
commandSuggestPopover={chatCommands.commandSuggestPopover}
|
|
92
|
+
composerRef={chatCommands.composerRef}
|
|
93
|
+
emptyLabel="Start a conversation with your team."
|
|
94
|
+
messageLayout="peer"
|
|
95
|
+
messages={messages}
|
|
96
|
+
placeholder="Message your team"
|
|
97
|
+
title="Chat"
|
|
98
|
+
userLabel="Jay"
|
|
99
|
+
value={draft}
|
|
100
|
+
onCommandClick={chatCommands.onCommandClick}
|
|
101
|
+
onKeyDown={chatCommands.onKeyDown}
|
|
102
|
+
onSubmit={(message) => {
|
|
103
|
+
if (chatCommands.runInputAsCommand(message)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setDraft('');
|
|
108
|
+
setMessages((currentMessages) => [
|
|
109
|
+
...currentMessages,
|
|
110
|
+
{
|
|
111
|
+
content: message,
|
|
112
|
+
createdAt: new Date().toISOString(),
|
|
113
|
+
id: createChatMessageId('chatting-user'),
|
|
114
|
+
source: 'user',
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
}}
|
|
118
|
+
onValueChange={setDraft}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function BuiltinAiChatView() {
|
|
125
|
+
const [draft, setDraft] = useState('');
|
|
126
|
+
const [messages, setMessages] = useState<ChatMessage[]>(() => [...initialAiChatMessages]);
|
|
127
|
+
const [runtimeStatus, setRuntimeStatus] = useState<BuiltinChatRuntimeStatus>('idle');
|
|
128
|
+
const commands = useWorkbenchCommandDescriptors();
|
|
129
|
+
const policyInput = useMemo(
|
|
130
|
+
() =>
|
|
131
|
+
mergeWorkbenchAiChatCommandPolicyInput(readWorkbenchAiChatCommandPolicyInput(), {
|
|
132
|
+
policyByCommandId: {
|
|
133
|
+
'workbench.togglePrimarySidebar': 'approval-required',
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
136
|
+
[],
|
|
137
|
+
);
|
|
138
|
+
const appendCommandResult = useCallback((result: WorkbenchChatCommandRunResult) => {
|
|
139
|
+
setMessages((currentMessages) => [
|
|
140
|
+
...currentMessages,
|
|
141
|
+
createChatCommandFeedbackMessage(result),
|
|
142
|
+
]);
|
|
143
|
+
}, []);
|
|
144
|
+
const chatCommands = useWorkbenchChatCommandSurface({
|
|
145
|
+
onCommandResult: appendCommandResult,
|
|
146
|
+
onValueChange: setDraft,
|
|
147
|
+
value: draft,
|
|
148
|
+
});
|
|
149
|
+
const { createProposalHandlers, enrichMessageProposals, processAutoPolicies } =
|
|
150
|
+
useWorkbenchChatCommandProposals({
|
|
151
|
+
commands,
|
|
152
|
+
onCommandResult: appendCommandResult,
|
|
153
|
+
policyInput,
|
|
154
|
+
});
|
|
155
|
+
const proposalHandlers = useMemo(
|
|
156
|
+
() => createProposalHandlers(setMessages),
|
|
157
|
+
[createProposalHandlers],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div className="workbench-chat-view">
|
|
162
|
+
<ChatPanel
|
|
163
|
+
assistantLabel="Assistant"
|
|
164
|
+
commandLabel="Show commands"
|
|
165
|
+
commandSuggestPopover={chatCommands.commandSuggestPopover}
|
|
166
|
+
composerRef={chatCommands.composerRef}
|
|
167
|
+
disabled={runtimeStatus === 'error'}
|
|
168
|
+
emptyLabel="Ask about this workspace."
|
|
169
|
+
isRunning={runtimeStatus === 'running'}
|
|
170
|
+
messageLayout="assistant"
|
|
171
|
+
messages={messages}
|
|
172
|
+
placeholder="Ask about this workspace"
|
|
173
|
+
showTools
|
|
174
|
+
title="AI Chat"
|
|
175
|
+
value={draft}
|
|
176
|
+
onCancel={() => setRuntimeStatus('idle')}
|
|
177
|
+
onCommandClick={chatCommands.onCommandClick}
|
|
178
|
+
onCommandProposalAllow={proposalHandlers.onProposalAllow}
|
|
179
|
+
onCommandProposalDeny={proposalHandlers.onProposalDeny}
|
|
180
|
+
onKeyDown={chatCommands.onKeyDown}
|
|
181
|
+
onSubmit={(message) => {
|
|
182
|
+
if (chatCommands.runInputAsCommand(message)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setDraft('');
|
|
187
|
+
setRuntimeStatus('running');
|
|
188
|
+
|
|
189
|
+
const assistantMessageId = createChatMessageId('ai-chat-assistant');
|
|
190
|
+
const rawProposals = createMockAiChatCommandProposals(message);
|
|
191
|
+
const commandProposalsForMessage = enrichMessageProposals(rawProposals);
|
|
192
|
+
const assistantMessage: ChatMessage = {
|
|
193
|
+
commandProposals: commandProposalsForMessage.length
|
|
194
|
+
? commandProposalsForMessage
|
|
195
|
+
: undefined,
|
|
196
|
+
content: createMockAiChatResponseContent(message),
|
|
197
|
+
createdAt: new Date().toISOString(),
|
|
198
|
+
id: assistantMessageId,
|
|
199
|
+
label: 'Assistant',
|
|
200
|
+
source: 'assistant',
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
setMessages((currentMessages) => [
|
|
204
|
+
...currentMessages,
|
|
205
|
+
{
|
|
206
|
+
content: message,
|
|
207
|
+
createdAt: new Date().toISOString(),
|
|
208
|
+
id: createChatMessageId('ai-chat-user'),
|
|
209
|
+
source: 'user',
|
|
210
|
+
},
|
|
211
|
+
assistantMessage,
|
|
212
|
+
]);
|
|
213
|
+
processAutoPolicies(setMessages, assistantMessageId, commandProposalsForMessage);
|
|
214
|
+
setRuntimeStatus('idle');
|
|
215
|
+
}}
|
|
216
|
+
onValueChange={setDraft}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ScrollArea } from '@workbench-kit/react/primitives';
|
|
2
|
+
import {
|
|
3
|
+
CommandInspectorPanel,
|
|
4
|
+
findCommandManagementEntry,
|
|
5
|
+
} from '@workbench-kit/react/workbench/management';
|
|
6
|
+
|
|
7
|
+
import { useCommandManagementModel } from './use-command-management.js';
|
|
8
|
+
|
|
9
|
+
export interface CommandInspectorSurfaceProps {
|
|
10
|
+
commandId: string;
|
|
11
|
+
resourceUri: string;
|
|
12
|
+
tabId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function CommandInspectorSurface({
|
|
16
|
+
commandId,
|
|
17
|
+
resourceUri,
|
|
18
|
+
tabId,
|
|
19
|
+
}: CommandInspectorSurfaceProps) {
|
|
20
|
+
const { groups, lastRun, runCommand } = useCommandManagementModel();
|
|
21
|
+
const entry = findCommandManagementEntry(groups, commandId);
|
|
22
|
+
|
|
23
|
+
if (!entry) {
|
|
24
|
+
return (
|
|
25
|
+
<section
|
|
26
|
+
aria-label="Command inspector"
|
|
27
|
+
className="workbench-command-inspector-surface"
|
|
28
|
+
data-editor-host-id={tabId}
|
|
29
|
+
data-resource-uri={resourceUri}
|
|
30
|
+
>
|
|
31
|
+
<div className="workbench-command-inspector-surface__missing" role="alert">
|
|
32
|
+
<p className="workbench-command-inspector-surface__missing-title">Command not found</p>
|
|
33
|
+
<p className="workbench-command-inspector-surface__missing-message">
|
|
34
|
+
No registry entry matches <code>{commandId}</code>.
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ScrollArea className="workbench-command-inspector-surface" orientation="vertical">
|
|
43
|
+
<CommandInspectorPanel entry={entry} lastRun={lastRun} onRunCommand={runCommand} />
|
|
44
|
+
</ScrollArea>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const BUILTIN_COMMANDS_VIEW_RENDER_KIND = 'workbench-kit.builtin.commands.view' as const;
|
|
2
|
+
export const BUILTIN_COMMANDS_VIEW_CONTAINER_ID = 'commands' as const;
|
|
3
|
+
|
|
4
|
+
export interface BuiltinCommandsViewRenderData {
|
|
5
|
+
readonly kind: typeof BUILTIN_COMMANDS_VIEW_RENDER_KIND;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isBuiltinCommandsViewRenderData(
|
|
9
|
+
value: unknown,
|
|
10
|
+
): value is BuiltinCommandsViewRenderData {
|
|
11
|
+
return (
|
|
12
|
+
typeof value === 'object' &&
|
|
13
|
+
value !== null &&
|
|
14
|
+
'kind' in value &&
|
|
15
|
+
(value as BuiltinCommandsViewRenderData).kind === BUILTIN_COMMANDS_VIEW_RENDER_KIND
|
|
16
|
+
);
|
|
17
|
+
}
|