@workbench-kit/react 0.0.1-prototype.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/package.json +62 -0
- package/src/index.ts +43 -0
- package/src/layout/Panel.tsx +27 -0
- package/src/layout/SideBarViewFrame.tsx +328 -0
- package/src/modal/ConfirmDialog.tsx +53 -0
- package/src/modal/Modal.tsx +110 -0
- package/src/overlay/ContextMenu.tsx +145 -0
- package/src/primitives/Badge.tsx +12 -0
- package/src/primitives/Button.tsx +14 -0
- package/src/primitives/Checkbox.tsx +15 -0
- package/src/primitives/EmptyState.tsx +26 -0
- package/src/primitives/Field.tsx +38 -0
- package/src/primitives/IconButton.tsx +32 -0
- package/src/primitives/Select.tsx +12 -0
- package/src/primitives/TextInput.tsx +24 -0
- package/src/primitives/Toolbar.tsx +8 -0
- package/src/styles.css +1762 -0
- package/src/utils/cx.ts +3 -0
- package/src/workbench/ActivityBar.tsx +52 -0
- package/src/workbench/SplitView.tsx +136 -0
- package/src/workbench/StatusBar.tsx +123 -0
- package/src/workbench/WorkbenchShell.tsx +70 -0
- package/src/workbench/WorkbenchStandaloneShell.tsx +291 -0
- package/src/workbench/chat/ChatComposer.tsx +148 -0
- package/src/workbench/chat/ChatMessageItem.tsx +35 -0
- package/src/workbench/chat/ChatMessageList.tsx +49 -0
- package/src/workbench/chat/ChatPanel.tsx +55 -0
- package/src/workbench/chat/index.ts +9 -0
- package/src/workbench/chat/types.ts +10 -0
- package/src/workbench/commands.ts +490 -0
- package/src/workbench/index.ts +98 -0
- package/src/workbench/settings/WorkbenchSettingsModal.tsx +188 -0
- package/src/workbench/settings/WorkbenchSettingsNav.tsx +41 -0
- package/src/workbench/settings/WorkbenchSettingsSection.tsx +30 -0
- package/src/workbench/settings/index.ts +7 -0
- package/src/workbench/settings/types.ts +16 -0
- package/src/workbench/shellState.ts +203 -0
- package/src/workbench/standalone.ts +94 -0
- package/src/workbench/workspace/WorkspaceEditor.tsx +226 -0
- package/src/workbench/workspace/WorkspaceEditorPanel.tsx +353 -0
- package/src/workbench/workspace/WorkspaceExplorer.tsx +524 -0
- package/src/workbench/workspace/WorkspaceFileIcon.tsx +149 -0
- package/src/workbench/workspace/WorkspaceHighlightedText.tsx +22 -0
- package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -0
- package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -0
- package/src/workbench/workspace/index.ts +98 -0
- package/src/workbench/workspace/path.ts +10 -0
- package/src/workbench/workspace/search.ts +6 -0
- package/src/workbench/workspace/tree.ts +1 -0
- package/src/workbench/workspace/types.ts +10 -0
- package/src/workbench/workspace/useVirtualWorkspace.ts +98 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useLayoutEffect,
|
|
3
|
+
useRef,
|
|
4
|
+
type KeyboardEvent,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
type TextareaHTMLAttributes,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { cx } from '../../utils/cx';
|
|
9
|
+
|
|
10
|
+
export interface ChatComposerProps extends Omit<
|
|
11
|
+
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
12
|
+
'onChange' | 'onSubmit' | 'value'
|
|
13
|
+
> {
|
|
14
|
+
cancelLabel?: string;
|
|
15
|
+
commandLabel?: string;
|
|
16
|
+
contextLabel?: string;
|
|
17
|
+
isRunning?: boolean;
|
|
18
|
+
onCancel?: () => void;
|
|
19
|
+
onSubmit: (message: string) => void;
|
|
20
|
+
onValueChange: (value: string) => void;
|
|
21
|
+
showTools?: boolean;
|
|
22
|
+
submitLabel?: string;
|
|
23
|
+
toolbarStart?: ReactNode;
|
|
24
|
+
value: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ChatComposer({
|
|
28
|
+
cancelLabel = 'Stop response',
|
|
29
|
+
className,
|
|
30
|
+
commandLabel = 'Open commands',
|
|
31
|
+
contextLabel = 'Add context',
|
|
32
|
+
disabled,
|
|
33
|
+
isRunning = false,
|
|
34
|
+
onCancel,
|
|
35
|
+
onSubmit,
|
|
36
|
+
onValueChange,
|
|
37
|
+
placeholder = 'Type a message...',
|
|
38
|
+
showTools = true,
|
|
39
|
+
submitLabel = 'Send message',
|
|
40
|
+
toolbarStart,
|
|
41
|
+
value,
|
|
42
|
+
...props
|
|
43
|
+
}: ChatComposerProps) {
|
|
44
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
45
|
+
|
|
46
|
+
const resizeTextarea = () => {
|
|
47
|
+
const element = textareaRef.current;
|
|
48
|
+
if (!element) return;
|
|
49
|
+
|
|
50
|
+
element.style.height = 'auto';
|
|
51
|
+
element.style.height = `${Math.min(element.scrollHeight, 160)}px`;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
resizeTextarea();
|
|
56
|
+
}, [value]);
|
|
57
|
+
|
|
58
|
+
const handleSubmit = () => {
|
|
59
|
+
const trimmed = value.trim();
|
|
60
|
+
if (!trimmed || disabled || isRunning) return;
|
|
61
|
+
|
|
62
|
+
onSubmit(trimmed);
|
|
63
|
+
window.requestAnimationFrame(() => {
|
|
64
|
+
if (!textareaRef.current) return;
|
|
65
|
+
|
|
66
|
+
textareaRef.current.style.height = 'auto';
|
|
67
|
+
textareaRef.current.focus();
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
72
|
+
props.onKeyDown?.(event);
|
|
73
|
+
if (event.defaultPrevented) return;
|
|
74
|
+
|
|
75
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
handleSubmit();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="composer">
|
|
83
|
+
<div className="composer__box">
|
|
84
|
+
<textarea
|
|
85
|
+
ref={textareaRef}
|
|
86
|
+
{...props}
|
|
87
|
+
className={cx('composer__textarea', className)}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
placeholder={placeholder}
|
|
90
|
+
rows={1}
|
|
91
|
+
value={value}
|
|
92
|
+
onChange={(event) => onValueChange(event.currentTarget.value)}
|
|
93
|
+
onInput={resizeTextarea}
|
|
94
|
+
onKeyDown={handleKeyDown}
|
|
95
|
+
/>
|
|
96
|
+
<div className="composer__toolbar">
|
|
97
|
+
<div className="composer__toolbar-left">
|
|
98
|
+
{toolbarStart}
|
|
99
|
+
{showTools ? (
|
|
100
|
+
<>
|
|
101
|
+
<button
|
|
102
|
+
aria-label={contextLabel}
|
|
103
|
+
className="composer__tool-btn"
|
|
104
|
+
title={contextLabel}
|
|
105
|
+
type="button"
|
|
106
|
+
>
|
|
107
|
+
<i className="codicon codicon-add" />
|
|
108
|
+
</button>
|
|
109
|
+
<button
|
|
110
|
+
aria-label={commandLabel}
|
|
111
|
+
className="composer__tool-btn"
|
|
112
|
+
title={commandLabel}
|
|
113
|
+
type="button"
|
|
114
|
+
>
|
|
115
|
+
<i className="codicon codicon-terminal" />
|
|
116
|
+
</button>
|
|
117
|
+
</>
|
|
118
|
+
) : null}
|
|
119
|
+
</div>
|
|
120
|
+
<div className="composer__toolbar-right">
|
|
121
|
+
{isRunning ? (
|
|
122
|
+
<button
|
|
123
|
+
aria-label={cancelLabel}
|
|
124
|
+
className="composer__send-btn composer__send-btn--cancel"
|
|
125
|
+
title={cancelLabel}
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={onCancel}
|
|
128
|
+
>
|
|
129
|
+
<i className="codicon codicon-stop-circle" />
|
|
130
|
+
</button>
|
|
131
|
+
) : (
|
|
132
|
+
<button
|
|
133
|
+
aria-label={submitLabel}
|
|
134
|
+
className="composer__send-btn"
|
|
135
|
+
disabled={disabled || !value.trim()}
|
|
136
|
+
title={submitLabel}
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={handleSubmit}
|
|
139
|
+
>
|
|
140
|
+
<i className="codicon codicon-send" />
|
|
141
|
+
</button>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Markdown from 'react-markdown';
|
|
2
|
+
import type { ChatMessage } from './types';
|
|
3
|
+
|
|
4
|
+
export interface ChatMessageItemProps {
|
|
5
|
+
assistantLabel?: string;
|
|
6
|
+
isStreaming?: boolean;
|
|
7
|
+
message: ChatMessage;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ChatMessageItem({
|
|
11
|
+
assistantLabel = 'Assistant',
|
|
12
|
+
isStreaming = false,
|
|
13
|
+
message,
|
|
14
|
+
}: ChatMessageItemProps) {
|
|
15
|
+
if (message.source === 'user') {
|
|
16
|
+
return (
|
|
17
|
+
<div className="message message--user">
|
|
18
|
+
<div className="message__bubble">{message.content}</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="message message--assistant">
|
|
25
|
+
<div className="message__label message__label--assistant">
|
|
26
|
+
<i className="codicon codicon-sparkle message__label-icon" />
|
|
27
|
+
{message.label ?? assistantLabel}
|
|
28
|
+
</div>
|
|
29
|
+
<div className="md-content">
|
|
30
|
+
<Markdown>{message.content}</Markdown>
|
|
31
|
+
{isStreaming ? <span aria-hidden="true" className="message__cursor" /> : null}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { SideBarScrollSpacer } from '../../layout/SideBarViewFrame';
|
|
3
|
+
import { ChatMessageItem } from './ChatMessageItem';
|
|
4
|
+
import type { ChatMessage } from './types';
|
|
5
|
+
|
|
6
|
+
export interface ChatMessageListProps {
|
|
7
|
+
assistantLabel?: string;
|
|
8
|
+
emptyLabel?: string;
|
|
9
|
+
isStreaming?: boolean;
|
|
10
|
+
messages: ChatMessage[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ChatMessageList({
|
|
14
|
+
assistantLabel,
|
|
15
|
+
emptyLabel = 'How can I help?',
|
|
16
|
+
isStreaming = false,
|
|
17
|
+
messages,
|
|
18
|
+
}: ChatMessageListProps) {
|
|
19
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
bottomRef.current?.scrollIntoView({ behavior: 'auto', block: 'end' });
|
|
23
|
+
}, [isStreaming, messages.length]);
|
|
24
|
+
|
|
25
|
+
if (messages.length === 0) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="message-empty">
|
|
28
|
+
<i className="codicon codicon-sparkle" />
|
|
29
|
+
<span>{emptyLabel}</span>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="message-list">
|
|
36
|
+
{messages.map((message, index) => (
|
|
37
|
+
<ChatMessageItem
|
|
38
|
+
key={message.id}
|
|
39
|
+
assistantLabel={assistantLabel}
|
|
40
|
+
isStreaming={
|
|
41
|
+
isStreaming && index === messages.length - 1 && message.source === 'assistant'
|
|
42
|
+
}
|
|
43
|
+
message={message}
|
|
44
|
+
/>
|
|
45
|
+
))}
|
|
46
|
+
<SideBarScrollSpacer ref={bottomRef} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SideBarViewFrame } from '../../layout/SideBarViewFrame';
|
|
2
|
+
import { ChatComposer, type ChatComposerProps } from './ChatComposer';
|
|
3
|
+
import { ChatMessageList, type ChatMessageListProps } from './ChatMessageList';
|
|
4
|
+
|
|
5
|
+
export interface ChatPanelProps
|
|
6
|
+
extends
|
|
7
|
+
ChatMessageListProps,
|
|
8
|
+
Pick<
|
|
9
|
+
ChatComposerProps,
|
|
10
|
+
| 'disabled'
|
|
11
|
+
| 'isRunning'
|
|
12
|
+
| 'onCancel'
|
|
13
|
+
| 'onSubmit'
|
|
14
|
+
| 'onValueChange'
|
|
15
|
+
| 'placeholder'
|
|
16
|
+
| 'showTools'
|
|
17
|
+
| 'value'
|
|
18
|
+
> {
|
|
19
|
+
title?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ChatPanel({
|
|
23
|
+
title = 'Chat',
|
|
24
|
+
value,
|
|
25
|
+
onValueChange,
|
|
26
|
+
onSubmit,
|
|
27
|
+
onCancel,
|
|
28
|
+
placeholder,
|
|
29
|
+
disabled,
|
|
30
|
+
isRunning,
|
|
31
|
+
showTools,
|
|
32
|
+
...messageListProps
|
|
33
|
+
}: ChatPanelProps) {
|
|
34
|
+
return (
|
|
35
|
+
<SideBarViewFrame
|
|
36
|
+
className="chat-side-bar-view"
|
|
37
|
+
footer={
|
|
38
|
+
<ChatComposer
|
|
39
|
+
disabled={disabled}
|
|
40
|
+
isRunning={isRunning}
|
|
41
|
+
placeholder={placeholder}
|
|
42
|
+
showTools={showTools}
|
|
43
|
+
value={value}
|
|
44
|
+
onCancel={onCancel}
|
|
45
|
+
onSubmit={onSubmit}
|
|
46
|
+
onValueChange={onValueChange}
|
|
47
|
+
/>
|
|
48
|
+
}
|
|
49
|
+
footerPlacement="overlay"
|
|
50
|
+
title={title}
|
|
51
|
+
>
|
|
52
|
+
<ChatMessageList isStreaming={isRunning} {...messageListProps} />
|
|
53
|
+
</SideBarViewFrame>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ChatComposer } from './ChatComposer';
|
|
2
|
+
export type { ChatComposerProps } from './ChatComposer';
|
|
3
|
+
export { ChatMessageItem } from './ChatMessageItem';
|
|
4
|
+
export type { ChatMessageItemProps } from './ChatMessageItem';
|
|
5
|
+
export { ChatMessageList } from './ChatMessageList';
|
|
6
|
+
export type { ChatMessageListProps } from './ChatMessageList';
|
|
7
|
+
export { ChatPanel } from './ChatPanel';
|
|
8
|
+
export type { ChatPanelProps } from './ChatPanel';
|
|
9
|
+
export type { ChatMessage, ChatMessageSource } from './types';
|