Eduards commited on
Commit
1f09459
·
1 Parent(s): fbea474

Small change to make review easier

Browse files
Files changed (1) hide show
  1. app/components/chat/BaseChat.tsx +215 -213
app/components/chat/BaseChat.tsx CHANGED
@@ -168,241 +168,243 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
168
  }
169
  };
170
 
171
- return (
172
- <Tooltip.Provider delayDuration={200}>
173
- <div
174
- ref={ref}
175
- className={classNames(
176
- styles.BaseChat,
177
- 'relative flex flex-col lg:flex-row h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',
178
- )}
179
- data-chat-visible={showChat}
180
- >
181
- <ClientOnly>{() => <Menu />}</ClientOnly>
182
- <div ref={scrollRef} className="flex flex-col lg:flex-rowoverflow-y-auto w-full h-full">
183
- <div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
184
- {!chatStarted && (
185
- <div id="intro" className="mt-[26vh] max-w-chat mx-auto text-centerpx-4 lg:px-0">
186
- <h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
187
- Where ideas begin
188
- </h1>
189
- <p className="text-md lg:text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
190
- Bring ideas to life in seconds or get help on existing projects.
191
- </p>
192
- </div>
193
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  <div
195
- className={classNames('pt-6 px-2 sm:px-6', {
196
- 'h-full flex flex-col': chatStarted,
197
- })}
 
 
 
198
  >
199
- <ClientOnly>
200
- {() => {
201
- return chatStarted ? (
202
- <Messages
203
- ref={messageRef}
204
- className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
205
- messages={messages}
206
- isStreaming={isStreaming}
207
- />
208
- ) : null;
209
- }}
210
- </ClientOnly>
 
 
 
 
 
 
 
211
  <div
212
  className={classNames(
213
- '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',
214
- {
215
- 'sticky bottom-2': chatStarted,
216
- },
217
  )}
218
  >
219
- <ModelSelector
220
- key={provider?.name + ':' + modelList.length}
221
- model={model}
222
- setModel={setModel}
223
- modelList={modelList}
224
- provider={provider}
225
- setProvider={setProvider}
226
- providerList={PROVIDER_LIST}
227
- apiKeys={apiKeys}
228
- />
229
- {provider && (
230
- <APIKeyManager
231
- provider={provider}
232
- apiKey={apiKeys[provider.name] || ''}
233
- setApiKey={(key) => updateApiKey(provider.name, key)}
234
- />
235
- )}
236
 
237
- <div
238
- className={classNames(
239
- 'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
240
- )}
241
- >
242
- <textarea
243
- ref={textareaRef}
244
- 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`}
245
- onKeyDown={(event) => {
246
- if (event.key === 'Enter') {
247
- if (event.shiftKey) {
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  return;
249
  }
250
 
251
- event.preventDefault();
252
-
253
  sendMessage?.(event);
254
- }
255
- }}
256
- value={input}
257
- onChange={(event) => {
258
- handleInputChange?.(event);
259
- }}
260
- style={{
261
- minHeight: TEXTAREA_MIN_HEIGHT,
262
- maxHeight: TEXTAREA_MAX_HEIGHT,
263
- }}
264
- placeholder="How can Bolt help you today?"
265
- translate="no"
266
- />
267
- <ClientOnly>
268
- {() => (
269
- <SendButton
270
- show={input.length > 0 || isStreaming}
271
- isStreaming={isStreaming}
272
- onClick={(event) => {
273
- if (isStreaming) {
274
- handleStop?.();
275
- return;
276
- }
277
-
278
- sendMessage?.(event);
279
- }}
280
- />
281
- )}
282
- </ClientOnly>
283
- <div className="flex justify-between items-center text-sm p-4 pt-2">
284
- <div className="flex gap-1 items-center">
285
- <IconButton
286
- title="Enhance prompt"
287
- disabled={input.length === 0 || enhancingPrompt}
288
- className={classNames('transition-all', {
289
- 'opacity-100!': enhancingPrompt,
290
- 'text-bolt-elements-item-contentAccent! pr-1.5 enabled:hover:bg-bolt-elements-item-backgroundAccent!':
291
- promptEnhanced,
292
- })}
293
- onClick={() => enhancePrompt?.()}
294
- >
295
- {enhancingPrompt ? (
296
- <>
297
- <div className="i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress text-xl animate-spin"></div>
298
- <div className="ml-1.5">Enhancing prompt...</div>
299
- </>
300
- ) : (
301
- <>
302
- <div className="i-bolt:stars text-xl"></div>
303
- {promptEnhanced && <div className="ml-1.5">Prompt enhanced</div>}
304
- </>
305
- )}
306
- </IconButton>
307
- <ClientOnly>{() => <ExportChatButton exportChat={exportChat} />}</ClientOnly>
308
- </div>
309
- {input.length > 3 ? (
310
- <div className="text-xs text-bolt-elements-textTertiary">
311
- Use <kbd className="kdb px-1.5 py-0.5 rounded bg-bolt-elements-background-depth-2">Shift</kbd>{' '}
312
- + <kbd className="kdb px-1.5 py-0.5 rounded bg-bolt-elements-background-depth-2">Return</kbd>{' '}
313
- for a new line
314
- </div>
315
- ) : null}
316
  </div>
 
 
 
 
 
 
 
317
  </div>
318
- <div className="bg-bolt-elements-background-depth-1 pb-6">{/* Ghost Element */}</div>
319
  </div>
320
  </div>
321
- {!chatStarted && (
322
- <div className="flex flex-col items-center justify-center flex-1 p-4">
323
- <input
324
- type="file"
325
- id="chat-import"
326
- className="hidden"
327
- accept=".json"
328
- onChange={async (e) => {
329
- const file = e.target.files?.[0];
330
-
331
- if (file && importChat) {
332
- try {
333
- const reader = new FileReader();
334
-
335
- reader.onload = async (e) => {
336
- try {
337
- const content = e.target?.result as string;
338
- const data = JSON.parse(content);
339
-
340
- if (!Array.isArray(data.messages)) {
341
- toast.error('Invalid chat file format');
342
- }
343
-
344
- await importChat(data.description, data.messages);
345
- toast.success('Chat imported successfully');
346
- } catch (error: unknown) {
347
- if(error instanceof Error) {
348
- toast.error('Failed to parse chat file: ' + error.message);
349
- } else {
350
- toast.error('Failed to parse chat file');
351
- }
352
- }
353
- };
354
- reader.onerror = () => toast.error('Failed to read chat file');
355
- reader.readAsText(file);
356
- } catch (error) {
357
- toast.error(error instanceof Error ? error.message : 'Failed to import chat');
358
- }
359
- e.target.value = ''; // Reset file input
360
- } else {
361
- toast.error('Something went wrong');
362
- }
363
- }}
364
- />
365
- <div className="flex flex-col items-center gap-4 max-w-2xl text-center">
366
- <div className="flex gap-2">
367
  <button
368
- onClick={() => {
369
- const input = document.getElementById('chat-import');
370
- input?.click();
371
  }}
372
- className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-all flex items-center gap-2"
373
  >
374
- <div className="i-ph:upload-simple" />
375
- Import Chat
376
  </button>
377
- </div>
378
- </div>
379
- </div>
380
- )}
381
- {!chatStarted && (
382
- <div id="examples" className="relative w-full max-w-xl mx-auto mt-8 flex justify-center">
383
- <div className="flex flex-col space-y-2 [mask-image:linear-gradient(to_bottom,black_0%,transparent_180%)] hover:[mask-image:none]">
384
- {EXAMPLE_PROMPTS.map((examplePrompt, index) => {
385
- return (
386
- <button
387
- key={index}
388
- onClick={(event) => {
389
- sendMessage?.(event, examplePrompt.text);
390
- }}
391
- className="group flex items-center w-full gap-2 justify-center bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary transition-theme"
392
- >
393
- {examplePrompt.text}
394
- <div className="i-ph:arrow-bend-down-left" />
395
- </button>
396
- );
397
- })}
398
- </div>
399
  </div>
400
- )}
401
- </div>
402
- <ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
403
  </div>
 
404
  </div>
405
- </Tooltip.Provider>
406
  );
 
 
407
  },
408
  );
 
168
  }
169
  };
170
 
171
+ const chatImportButton = !chatStarted && (
172
+ <div className="flex flex-col items-center justify-center flex-1 p-4">
173
+ <input
174
+ type="file"
175
+ id="chat-import"
176
+ className="hidden"
177
+ accept=".json"
178
+ onChange={async (e) => {
179
+ const file = e.target.files?.[0];
180
+
181
+ if (file && importChat) {
182
+ try {
183
+ const reader = new FileReader();
184
+
185
+ reader.onload = async (e) => {
186
+ try {
187
+ const content = e.target?.result as string;
188
+ const data = JSON.parse(content);
189
+
190
+ if (!Array.isArray(data.messages)) {
191
+ toast.error('Invalid chat file format');
192
+ }
193
+
194
+ await importChat(data.description, data.messages);
195
+ toast.success('Chat imported successfully');
196
+ } catch (error: unknown) {
197
+ if (error instanceof Error) {
198
+ toast.error('Failed to parse chat file: ' + error.message);
199
+ } else {
200
+ toast.error('Failed to parse chat file');
201
+ }
202
+ }
203
+ };
204
+ reader.onerror = () => toast.error('Failed to read chat file');
205
+ reader.readAsText(file);
206
+ } catch (error) {
207
+ toast.error(error instanceof Error ? error.message : 'Failed to import chat');
208
+ }
209
+ e.target.value = ''; // Reset file input
210
+ } else {
211
+ toast.error('Something went wrong');
212
+ }
213
+ }}
214
+ />
215
+ <div className="flex flex-col items-center gap-4 max-w-2xl text-center">
216
+ <div className="flex gap-2">
217
+ <button
218
+ onClick={() => {
219
+ const input = document.getElementById('chat-import');
220
+ input?.click();
221
+ }}
222
+ className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-all flex items-center gap-2"
223
+ >
224
+ <div className="i-ph:upload-simple" />
225
+ Import Chat
226
+ </button>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ );
231
+
232
+ const baseChat = (
233
+ <div
234
+ ref={ref}
235
+ className={classNames(
236
+ styles.BaseChat,
237
+ 'relative flex flex-col lg:flex-row h-full w-full overflow-hidden bg-bolt-elements-background-depth-1',
238
+ )}
239
+ data-chat-visible={showChat}
240
+ >
241
+ <ClientOnly>{() => <Menu />}</ClientOnly>
242
+ <div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
243
+ <div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
244
+ {!chatStarted && (
245
+ <div id="intro" className="mt-[26vh] max-w-chat mx-auto text-center px-4 lg:px-0">
246
+ <h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
247
+ Where ideas begin
248
+ </h1>
249
+ <p className="text-md lg:text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
250
+ Bring ideas to life in seconds or get help on existing projects.
251
+ </p>
252
+ </div>
253
+ )}
254
+ <div
255
+ className={classNames('pt-6 px-2 sm:px-6', {
256
+ 'h-full flex flex-col': chatStarted,
257
+ })}
258
+ >
259
+ <ClientOnly>
260
+ {() => {
261
+ return chatStarted ? (
262
+ <Messages
263
+ ref={messageRef}
264
+ className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
265
+ messages={messages}
266
+ isStreaming={isStreaming}
267
+ />
268
+ ) : null;
269
+ }}
270
+ </ClientOnly>
271
  <div
272
+ className={classNames(
273
+ ' 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',
274
+ {
275
+ 'sticky bottom-2': chatStarted,
276
+ },
277
+ )}
278
  >
279
+ <ModelSelector
280
+ key={provider?.name + ':' + modelList.length}
281
+ model={model}
282
+ setModel={setModel}
283
+ modelList={modelList}
284
+ provider={provider}
285
+ setProvider={setProvider}
286
+ providerList={PROVIDER_LIST}
287
+ apiKeys={apiKeys}
288
+ />
289
+
290
+ {provider && (
291
+ <APIKeyManager
292
+ provider={provider}
293
+ apiKey={apiKeys[provider.name] || ''}
294
+ setApiKey={(key) => updateApiKey(provider.name, key)}
295
+ />
296
+ )}
297
+
298
  <div
299
  className={classNames(
300
+ 'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
 
 
 
301
  )}
302
  >
303
+ <textarea
304
+ ref={textareaRef}
305
+ 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`}
306
+ onKeyDown={(event) => {
307
+ if (event.key === 'Enter') {
308
+ if (event.shiftKey) {
309
+ return;
310
+ }
 
 
 
 
 
 
 
 
 
311
 
312
+ event.preventDefault();
313
+
314
+ sendMessage?.(event);
315
+ }
316
+ }}
317
+ value={input}
318
+ onChange={(event) => {
319
+ handleInputChange?.(event);
320
+ }}
321
+ style={{
322
+ minHeight: TEXTAREA_MIN_HEIGHT,
323
+ maxHeight: TEXTAREA_MAX_HEIGHT,
324
+ }}
325
+ placeholder="How can Bolt help you today?"
326
+ translate="no"
327
+ />
328
+ <ClientOnly>
329
+ {() => (
330
+ <SendButton
331
+ show={input.length > 0 || isStreaming}
332
+ isStreaming={isStreaming}
333
+ onClick={(event) => {
334
+ if (isStreaming) {
335
+ handleStop?.();
336
  return;
337
  }
338
 
 
 
339
  sendMessage?.(event);
340
+ }}
341
+ />
342
+ )}
343
+ </ClientOnly>
344
+ <div className="flex justify-between items-center text-sm p-4 pt-2">
345
+ <div className="flex gap-1 items-center">
346
+ <IconButton
347
+ title="Enhance prompt"
348
+ disabled={input.length === 0 || enhancingPrompt}
349
+ className={classNames('transition-all', {
350
+ 'opacity-100!': enhancingPrompt,
351
+ 'text-bolt-elements-item-contentAccent! pr-1.5 enabled:hover:bg-bolt-elements-item-backgroundAccent!':
352
+ promptEnhanced,
353
+ })}
354
+ onClick={() => enhancePrompt?.()}
355
+ >
356
+ {enhancingPrompt ? (
357
+ <>
358
+ <div className="i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress text-xl animate-spin"></div>
359
+ <div className="ml-1.5">Enhancing prompt...</div>
360
+ </>
361
+ ) : (
362
+ <>
363
+ <div className="i-bolt:stars text-xl"></div>
364
+ {promptEnhanced && <div className="ml-1.5">Prompt enhanced</div>}
365
+ </>
366
+ )}
367
+ </IconButton>
368
+ <ClientOnly>{() => <ExportChatButton exportChat={exportChat} />}</ClientOnly>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  </div>
370
+ {input.length > 3 ? (
371
+ <div className="text-xs text-bolt-elements-textTertiary">
372
+ Use <kbd className="kdb px-1.5 py-0.5 rounded bg-bolt-elements-background-depth-2">Shift</kbd> +{' '}
373
+ <kbd className="kdb px-1.5 py-0.5 rounded bg-bolt-elements-background-depth-2">Return</kbd> for
374
+ a new line
375
+ </div>
376
+ ) : null}
377
  </div>
 
378
  </div>
379
  </div>
380
+ </div>
381
+ {chatImportButton}
382
+ {!chatStarted && (
383
+ <div id="examples" className="relative w-full max-w-xl mx-auto mt-8 flex justify-center">
384
+ <div className="flex flex-col space-y-2 [mask-image:linear-gradient(to_bottom,black_0%,transparent_180%)] hover:[mask-image:none]">
385
+ {EXAMPLE_PROMPTS.map((examplePrompt, index) => {
386
+ return (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  <button
388
+ key={index}
389
+ onClick={(event) => {
390
+ sendMessage?.(event, examplePrompt.text);
391
  }}
392
+ className="group flex items-center w-full gap-2 justify-center bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary transition-theme"
393
  >
394
+ {examplePrompt.text}
395
+ <div className="i-ph:arrow-bend-down-left" />
396
  </button>
397
+ );
398
+ })}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  </div>
400
+ </div>
401
+ )}
 
402
  </div>
403
+ <ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
404
  </div>
405
+ </div>
406
  );
407
+
408
+ return <Tooltip.Provider delayDuration={200}>{baseChat}</Tooltip.Provider>;
409
  },
410
  );