Eduards commited on
Commit
9792e93
·
unverified ·
2 Parent(s): 95776af 84fad81

Merge pull request #361 from qwikode/feature/mobile-friendly

Browse files
app/components/chat/APIKeyManager.tsx CHANGED
@@ -10,11 +10,7 @@ interface APIKeyManagerProps {
10
  labelForGetApiKey?: string;
11
  }
12
 
13
- export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
14
- provider,
15
- apiKey,
16
- setApiKey,
17
- }) => {
18
  const [isEditing, setIsEditing] = useState(false);
19
  const [tempKey, setTempKey] = useState(apiKey);
20
 
@@ -24,15 +20,29 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
24
  };
25
 
26
  return (
27
- <div className="flex items-center gap-2 mt-2 mb-2">
28
- <span className="text-sm text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  {isEditing ? (
30
- <>
31
  <input
32
  type="password"
33
  value={tempKey}
 
34
  onChange={(e) => setTempKey(e.target.value)}
35
- className="flex-1 p-1 text-sm rounded border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus"
36
  />
37
  <IconButton onClick={handleSave} title="Save API Key">
38
  <div className="i-ph:check" />
@@ -40,20 +50,15 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({
40
  <IconButton onClick={() => setIsEditing(false)} title="Cancel">
41
  <div className="i-ph:x" />
42
  </IconButton>
43
- </>
44
  ) : (
45
  <>
46
- <span className="flex-1 text-sm text-bolt-elements-textPrimary">
47
- {apiKey ? '••••••••' : 'Not set (will still work if set in .env file)'}
48
- </span>
49
- <IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
50
- <div className="i-ph:pencil-simple" />
51
- </IconButton>
52
-
53
- {provider?.getApiKeyLink && <IconButton onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
54
- <span className="mr-2">{provider?.labelForGetApiKey || 'Get API Key'}</span>
55
- <div className={provider?.icon || "i-ph:key"} />
56
- </IconButton>}
57
  </>
58
  )}
59
  </div>
 
10
  labelForGetApiKey?: string;
11
  }
12
 
13
+ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey, setApiKey }) => {
 
 
 
 
14
  const [isEditing, setIsEditing] = useState(false);
15
  const [tempKey, setTempKey] = useState(apiKey);
16
 
 
20
  };
21
 
22
  return (
23
+ <div className="flex items-start sm:items-center mt-2 mb-2 flex-col sm:flex-row">
24
+ <div>
25
+ <span className="text-sm text-bolt-elements-textSecondary">{provider?.name} API Key:</span>
26
+ {!isEditing && (
27
+ <div className="flex items-center mb-4">
28
+ <span className="flex-1 text-xs text-bolt-elements-textPrimary mr-2">
29
+ {apiKey ? '••••••••' : 'Not set (will still work if set in .env file)'}
30
+ </span>
31
+ <IconButton onClick={() => setIsEditing(true)} title="Edit API Key">
32
+ <div className="i-ph:pencil-simple" />
33
+ </IconButton>
34
+ </div>
35
+ )}
36
+ </div>
37
+
38
  {isEditing ? (
39
+ <div className="flex items-center gap-3 mt-2">
40
  <input
41
  type="password"
42
  value={tempKey}
43
+ placeholder="Your API Key"
44
  onChange={(e) => setTempKey(e.target.value)}
45
+ className="flex-1 px-2 py-1 text-xs lg:text-sm rounded border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus"
46
  />
47
  <IconButton onClick={handleSave} title="Save API Key">
48
  <div className="i-ph:check" />
 
50
  <IconButton onClick={() => setIsEditing(false)} title="Cancel">
51
  <div className="i-ph:x" />
52
  </IconButton>
53
+ </div>
54
  ) : (
55
  <>
56
+ {provider?.getApiKeyLink && (
57
+ <IconButton className="ml-auto" onClick={() => window.open(provider?.getApiKeyLink)} title="Edit API Key">
58
+ <span className="mr-2 text-xs lg:text-sm">{provider?.labelForGetApiKey || 'Get API Key'}</span>
59
+ <div className={provider?.icon || 'i-ph:key'} />
60
+ </IconButton>
61
+ )}
 
 
 
 
 
62
  </>
63
  )}
64
  </div>
app/components/chat/BaseChat.tsx CHANGED
@@ -27,9 +27,9 @@ const EXAMPLE_PROMPTS = [
27
 
28
  const providerList = PROVIDER_LIST;
29
 
30
- const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList }) => {
31
  return (
32
- <div className="mb-2 flex gap-2">
33
  <select
34
  value={provider?.name}
35
  onChange={(e) => {
@@ -49,8 +49,7 @@ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, prov
49
  key={provider?.name}
50
  value={model}
51
  onChange={(e) => setModel(e.target.value)}
52
- style={{ maxWidth: '70%' }}
53
- className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all"
54
  >
55
  {[...modelList]
56
  .filter((e) => e.provider == provider?.name && e.name)
@@ -157,25 +156,25 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
157
  ref={ref}
158
  className={classNames(
159
  styles.BaseChat,
160
- 'relative flex h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',
161
  )}
162
  data-chat-visible={showChat}
163
  >
164
  <ClientOnly>{() => <Menu />}</ClientOnly>
165
- <div ref={scrollRef} className="flex overflow-y-auto w-full h-full">
166
- <div className={classNames(styles.Chat, 'flex flex-col flex-grow min-w-[var(--chat-min-width)] h-full')}>
167
  {!chatStarted && (
168
- <div id="intro" className="mt-[26vh] max-w-chat mx-auto text-center">
169
- <h1 className="text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
170
  Where ideas begin
171
  </h1>
172
- <p className="text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
173
  Bring ideas to life in seconds or get help on existing projects.
174
  </p>
175
  </div>
176
  )}
177
  <div
178
- className={classNames('pt-6 px-6', {
179
  'h-full flex flex-col': chatStarted,
180
  })}
181
  >
@@ -184,7 +183,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
184
  return chatStarted ? (
185
  <Messages
186
  ref={messageRef}
187
- className="flex flex-col w-full flex-1 max-w-chat px-4 pb-6 mx-auto z-1"
188
  messages={messages}
189
  isStreaming={isStreaming}
190
  />
@@ -193,10 +192,11 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
193
  </ClientOnly>
194
  <div
195
  className={classNames(
196
- 'bg-bolt-elements-background-depth-2 border-y border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt',
197
  {
198
- 'sticky bottom-0': chatStarted
199
- })}
 
200
  >
201
  <ModelSelector
202
  key={provider?.name + ':' + modelList.length}
@@ -206,7 +206,9 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
206
  provider={provider}
207
  setProvider={setProvider}
208
  providerList={PROVIDER_LIST}
 
209
  />
 
210
  {provider && (
211
  <APIKeyManager
212
  provider={provider}
@@ -214,6 +216,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
214
  setApiKey={(key) => updateApiKey(provider.name, key)}
215
  />
216
  )}
 
217
  <div
218
  className={classNames(
219
  'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
@@ -221,7 +224,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
221
  >
222
  <textarea
223
  ref={textareaRef}
224
- className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent transition-all`}
225
  onKeyDown={(event) => {
226
  if (event.key === 'Enter') {
227
  if (event.shiftKey) {
@@ -294,7 +297,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
294
  ) : null}
295
  </div>
296
  </div>
297
- <div className="bg-bolt-elements-background-depth-1 pb-6">{/* Ghost Element */}</div>
298
  </div>
299
  </div>
300
  {!chatStarted && (
 
27
 
28
  const providerList = PROVIDER_LIST;
29
 
30
+ const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList, apiKeys }) => {
31
  return (
32
+ <div className="mb-2 flex gap-2 flex-col sm:flex-row">
33
  <select
34
  value={provider?.name}
35
  onChange={(e) => {
 
49
  key={provider?.name}
50
  value={model}
51
  onChange={(e) => setModel(e.target.value)}
52
+ className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all lg:max-w-[70%] "
 
53
  >
54
  {[...modelList]
55
  .filter((e) => e.provider == provider?.name && e.name)
 
156
  ref={ref}
157
  className={classNames(
158
  styles.BaseChat,
159
+ 'relative flex flex-col lg:flex-row h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',
160
  )}
161
  data-chat-visible={showChat}
162
  >
163
  <ClientOnly>{() => <Menu />}</ClientOnly>
164
+ <div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
165
+ <div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
166
  {!chatStarted && (
167
+ <div id="intro" className="mt-[26vh] max-w-chat mx-auto text-center px-4 lg:px-0">
168
+ <h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
169
  Where ideas begin
170
  </h1>
171
+ <p className="text-md lg:text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
172
  Bring ideas to life in seconds or get help on existing projects.
173
  </p>
174
  </div>
175
  )}
176
  <div
177
+ className={classNames('pt-6 px-2 sm:px-6', {
178
  'h-full flex flex-col': chatStarted,
179
  })}
180
  >
 
183
  return chatStarted ? (
184
  <Messages
185
  ref={messageRef}
186
+ className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
187
  messages={messages}
188
  isStreaming={isStreaming}
189
  />
 
192
  </ClientOnly>
193
  <div
194
  className={classNames(
195
+ ' bg-bolt-elements-background-depth-2 p-3 rounded-lg border border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt mb-6',
196
  {
197
+ 'sticky bottom-2': chatStarted,
198
+ },
199
+ )}
200
  >
201
  <ModelSelector
202
  key={provider?.name + ':' + modelList.length}
 
206
  provider={provider}
207
  setProvider={setProvider}
208
  providerList={PROVIDER_LIST}
209
+ apiKeys={apiKeys}
210
  />
211
+
212
  {provider && (
213
  <APIKeyManager
214
  provider={provider}
 
216
  setApiKey={(key) => updateApiKey(provider.name, key)}
217
  />
218
  )}
219
+
220
  <div
221
  className={classNames(
222
  'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
 
224
  >
225
  <textarea
226
  ref={textareaRef}
227
+ className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-0 focus:border-none focus:shadow-none resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent transition-all`}
228
  onKeyDown={(event) => {
229
  if (event.key === 'Enter') {
230
  if (event.shiftKey) {
 
297
  ) : null}
298
  </div>
299
  </div>
 
300
  </div>
301
  </div>
302
  {!chatStarted && (
app/components/chat/Messages.client.tsx CHANGED
@@ -4,7 +4,7 @@ import { classNames } from '~/utils/classNames';
4
  import { AssistantMessage } from './AssistantMessage';
5
  import { UserMessage } from './UserMessage';
6
  import * as Tooltip from '@radix-ui/react-tooltip';
7
- import { useLocation, useNavigate } from '@remix-run/react';
8
  import { db, chatId } from '~/lib/persistence/useChatHistory';
9
  import { forkChat } from '~/lib/persistence/db';
10
  import { toast } from 'react-toastify';
@@ -19,7 +19,6 @@ interface MessagesProps {
19
  export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props: MessagesProps, ref) => {
20
  const { id, isStreaming = false, messages = [] } = props;
21
  const location = useLocation();
22
- const navigate = useNavigate();
23
 
24
  const handleRewind = (messageId: string) => {
25
  const searchParams = new URLSearchParams(location.search);
@@ -69,53 +68,57 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
69
  <div className="grid grid-col-1 w-full">
70
  {isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />}
71
  </div>
72
- {!isUserMessage && (<div className="flex gap-2">
73
- <Tooltip.Root>
74
- <Tooltip.Trigger asChild>
75
- {messageId && (<button
76
- onClick={() => handleRewind(messageId)}
77
- key='i-ph:arrow-u-up-left'
78
- className={classNames(
79
- 'i-ph:arrow-u-up-left',
80
- 'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors'
 
 
 
 
81
  )}
82
- />)}
83
- </Tooltip.Trigger>
84
- <Tooltip.Portal>
85
- <Tooltip.Content
86
- className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
87
- sideOffset={5}
88
- style={{zIndex: 1000}}
89
- >
90
- Revert to this message
91
- <Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
92
- </Tooltip.Content>
93
- </Tooltip.Portal>
94
- </Tooltip.Root>
95
 
96
- <Tooltip.Root>
97
- <Tooltip.Trigger asChild>
98
- <button
99
- onClick={() => handleFork(messageId)}
100
- key='i-ph:git-fork'
101
- className={classNames(
102
- 'i-ph:git-fork',
103
- 'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors'
104
- )}
105
- />
106
- </Tooltip.Trigger>
107
- <Tooltip.Portal>
108
- <Tooltip.Content
109
- className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
110
- sideOffset={5}
111
- style={{zIndex: 1000}}
112
- >
113
- Fork chat from this message
114
- <Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
115
- </Tooltip.Content>
116
- </Tooltip.Portal>
117
- </Tooltip.Root>
118
- </div>)}
 
119
  </div>
120
  );
121
  })
 
4
  import { AssistantMessage } from './AssistantMessage';
5
  import { UserMessage } from './UserMessage';
6
  import * as Tooltip from '@radix-ui/react-tooltip';
7
+ import { useLocation } from '@remix-run/react';
8
  import { db, chatId } from '~/lib/persistence/useChatHistory';
9
  import { forkChat } from '~/lib/persistence/db';
10
  import { toast } from 'react-toastify';
 
19
  export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props: MessagesProps, ref) => {
20
  const { id, isStreaming = false, messages = [] } = props;
21
  const location = useLocation();
 
22
 
23
  const handleRewind = (messageId: string) => {
24
  const searchParams = new URLSearchParams(location.search);
 
68
  <div className="grid grid-col-1 w-full">
69
  {isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />}
70
  </div>
71
+ {!isUserMessage && (
72
+ <div className="flex gap-2 flex-col lg:flex-row">
73
+ <Tooltip.Root>
74
+ <Tooltip.Trigger asChild>
75
+ {messageId && (
76
+ <button
77
+ onClick={() => handleRewind(messageId)}
78
+ key="i-ph:arrow-u-up-left"
79
+ className={classNames(
80
+ 'i-ph:arrow-u-up-left',
81
+ 'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
82
+ )}
83
+ />
84
  )}
85
+ </Tooltip.Trigger>
86
+ <Tooltip.Portal>
87
+ <Tooltip.Content
88
+ className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
89
+ sideOffset={5}
90
+ style={{ zIndex: 1000 }}
91
+ >
92
+ Revert to this message
93
+ <Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
94
+ </Tooltip.Content>
95
+ </Tooltip.Portal>
96
+ </Tooltip.Root>
 
97
 
98
+ <Tooltip.Root>
99
+ <Tooltip.Trigger asChild>
100
+ <button
101
+ onClick={() => handleFork(messageId)}
102
+ key="i-ph:git-fork"
103
+ className={classNames(
104
+ 'i-ph:git-fork',
105
+ 'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
106
+ )}
107
+ />
108
+ </Tooltip.Trigger>
109
+ <Tooltip.Portal>
110
+ <Tooltip.Content
111
+ className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
112
+ sideOffset={5}
113
+ style={{ zIndex: 1000 }}
114
+ >
115
+ Fork chat from this message
116
+ <Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
117
+ </Tooltip.Content>
118
+ </Tooltip.Portal>
119
+ </Tooltip.Root>
120
+ </div>
121
+ )}
122
  </div>
123
  );
124
  })
app/components/header/HeaderActionButtons.client.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { useStore } from '@nanostores/react';
 
2
  import { chatStore } from '~/lib/stores/chat';
3
  import { workbenchStore } from '~/lib/stores/workbench';
4
  import { classNames } from '~/utils/classNames';
@@ -9,6 +10,8 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
9
  const showWorkbench = useStore(workbenchStore.showWorkbench);
10
  const { showChat } = useStore(chatStore);
11
 
 
 
12
  const canHideChat = showWorkbench || !showChat;
13
 
14
  return (
@@ -16,7 +19,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
16
  <div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
17
  <Button
18
  active={showChat}
19
- disabled={!canHideChat}
20
  onClick={() => {
21
  if (canHideChat) {
22
  chatStore.setKey('showChat', !showChat);
 
1
  import { useStore } from '@nanostores/react';
2
+ import useViewport from '~/lib/hooks';
3
  import { chatStore } from '~/lib/stores/chat';
4
  import { workbenchStore } from '~/lib/stores/workbench';
5
  import { classNames } from '~/utils/classNames';
 
10
  const showWorkbench = useStore(workbenchStore.showWorkbench);
11
  const { showChat } = useStore(chatStore);
12
 
13
+ const isSmallViewport = useViewport(1024);
14
+
15
  const canHideChat = showWorkbench || !showChat;
16
 
17
  return (
 
19
  <div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
20
  <Button
21
  active={showChat}
22
+ disabled={!canHideChat || isSmallViewport} // expand button is disabled on mobile as it's needed
23
  onClick={() => {
24
  if (canHideChat) {
25
  chatStore.setKey('showChat', !showChat);
app/components/workbench/Workbench.client.tsx CHANGED
@@ -16,6 +16,7 @@ import { cubicEasingFn } from '~/utils/easings';
16
  import { renderLogger } from '~/utils/logger';
17
  import { EditorPanel } from './EditorPanel';
18
  import { Preview } from './Preview';
 
19
 
20
  interface WorkspaceProps {
21
  chatStarted?: boolean;
@@ -65,6 +66,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
65
  const files = useStore(workbenchStore.files);
66
  const selectedView = useStore(workbenchStore.currentView);
67
 
 
 
68
  const setSelectedView = (view: WorkbenchViewType) => {
69
  workbenchStore.currentView.set(view);
70
  };
@@ -128,18 +131,20 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
128
  className={classNames(
129
  'fixed top-[calc(var(--header-height)+1.5rem)] bottom-6 w-[var(--workbench-inner-width)] mr-4 z-0 transition-[left,width] duration-200 bolt-ease-cubic-bezier',
130
  {
 
 
131
  'left-[var(--workbench-left)]': showWorkbench,
132
  'left-[100%]': !showWorkbench,
133
  },
134
  )}
135
  >
136
- <div className="absolute inset-0 px-6">
137
  <div className="h-full flex flex-col bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor shadow-sm rounded-lg overflow-hidden">
138
  <div className="flex items-center px-3 py-2 border-b border-bolt-elements-borderColor">
139
  <Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
140
  <div className="ml-auto" />
141
  {selectedView === 'code' && (
142
- <>
143
  <PanelHeaderButton
144
  className="mr-1 text-sm"
145
  onClick={() => {
@@ -165,29 +170,32 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
165
  <PanelHeaderButton
166
  className="mr-1 text-sm"
167
  onClick={() => {
168
- const repoName = prompt("Please enter a name for your new GitHub repository:", "bolt-generated-project");
 
 
 
169
  if (!repoName) {
170
- alert("Repository name is required. Push to GitHub cancelled.");
171
  return;
172
  }
173
- const githubUsername = prompt("Please enter your GitHub username:");
174
  if (!githubUsername) {
175
- alert("GitHub username is required. Push to GitHub cancelled.");
176
  return;
177
  }
178
- const githubToken = prompt("Please enter your GitHub personal access token:");
179
  if (!githubToken) {
180
- alert("GitHub token is required. Push to GitHub cancelled.");
181
  return;
182
  }
183
-
184
- workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
185
  }}
186
  >
187
  <div className="i-ph:github-logo" />
188
  Push to GitHub
189
  </PanelHeaderButton>
190
- </>
191
  )}
192
  <IconButton
193
  icon="i-ph:x-circle"
 
16
  import { renderLogger } from '~/utils/logger';
17
  import { EditorPanel } from './EditorPanel';
18
  import { Preview } from './Preview';
19
+ import useViewport from '~/lib/hooks';
20
 
21
  interface WorkspaceProps {
22
  chatStarted?: boolean;
 
66
  const files = useStore(workbenchStore.files);
67
  const selectedView = useStore(workbenchStore.currentView);
68
 
69
+ const isSmallViewport = useViewport(1024);
70
+
71
  const setSelectedView = (view: WorkbenchViewType) => {
72
  workbenchStore.currentView.set(view);
73
  };
 
131
  className={classNames(
132
  'fixed top-[calc(var(--header-height)+1.5rem)] bottom-6 w-[var(--workbench-inner-width)] mr-4 z-0 transition-[left,width] duration-200 bolt-ease-cubic-bezier',
133
  {
134
+ 'w-full': isSmallViewport,
135
+ 'left-0': showWorkbench && isSmallViewport,
136
  'left-[var(--workbench-left)]': showWorkbench,
137
  'left-[100%]': !showWorkbench,
138
  },
139
  )}
140
  >
141
+ <div className="absolute inset-0 px-2 lg:px-6">
142
  <div className="h-full flex flex-col bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor shadow-sm rounded-lg overflow-hidden">
143
  <div className="flex items-center px-3 py-2 border-b border-bolt-elements-borderColor">
144
  <Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
145
  <div className="ml-auto" />
146
  {selectedView === 'code' && (
147
+ <div className="flex overflow-y-auto">
148
  <PanelHeaderButton
149
  className="mr-1 text-sm"
150
  onClick={() => {
 
170
  <PanelHeaderButton
171
  className="mr-1 text-sm"
172
  onClick={() => {
173
+ const repoName = prompt(
174
+ 'Please enter a name for your new GitHub repository:',
175
+ 'bolt-generated-project',
176
+ );
177
  if (!repoName) {
178
+ alert('Repository name is required. Push to GitHub cancelled.');
179
  return;
180
  }
181
+ const githubUsername = prompt('Please enter your GitHub username:');
182
  if (!githubUsername) {
183
+ alert('GitHub username is required. Push to GitHub cancelled.');
184
  return;
185
  }
186
+ const githubToken = prompt('Please enter your GitHub personal access token:');
187
  if (!githubToken) {
188
+ alert('GitHub token is required. Push to GitHub cancelled.');
189
  return;
190
  }
191
+
192
+ workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
193
  }}
194
  >
195
  <div className="i-ph:github-logo" />
196
  Push to GitHub
197
  </PanelHeaderButton>
198
+ </div>
199
  )}
200
  <IconButton
201
  icon="i-ph:x-circle"
app/lib/hooks/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './useMessageParser';
2
  export * from './usePromptEnhancer';
3
  export * from './useShortcuts';
4
  export * from './useSnapScroll';
 
 
2
  export * from './usePromptEnhancer';
3
  export * from './useShortcuts';
4
  export * from './useSnapScroll';
5
+ export { default } from './useViewport';
app/lib/hooks/useViewport.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+
3
+ const useViewport = (threshold = 1024) => {
4
+ const [isSmallViewport, setIsSmallViewport] = useState(window.innerWidth < threshold);
5
+
6
+ useEffect(() => {
7
+ const handleResize = () => setIsSmallViewport(window.innerWidth < threshold);
8
+ window.addEventListener('resize', handleResize);
9
+
10
+ return () => {
11
+ window.removeEventListener('resize', handleResize);
12
+ };
13
+ }, [threshold]);
14
+
15
+ return isSmallViewport;
16
+ };
17
+
18
+ export default useViewport;