Skip to content

Commit 0d0fa4e

Browse files
fixed template importing
1 parent 23459a8 commit 0d0fa4e

37 files changed

Lines changed: 734 additions & 181 deletions

app/components/chat/APIKeyManager.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface APIKeyManagerProps {
1111
labelForGetApiKey?: string;
1212
}
1313

14-
// cache which stores whether the provider's API key is set via environment variable
14+
// cache which stores whether the provider's API key is Connected
1515
const providerEnvKeyStatusCache: Record<string, boolean> = {};
1616

1717
const apiKeyMemoizeCache: { [k: string]: Record<string, string> } = {};
@@ -100,12 +100,12 @@ export const APIKeyManager: React.FC<APIKeyManagerProps> = ({ provider, apiKey,
100100
) : isEnvKeySet ? (
101101
<>
102102
<div className="i-ph:check-circle-fill text-green-500 w-4 h-4" />
103-
<span className="text-xs text-green-500">Set via environment variable</span>
103+
<span className="text-xs text-green-500">Connected</span>
104104
</>
105105
) : (
106106
<>
107107
<div className="i-ph:x-circle-fill text-red-500 w-4 h-4" />
108-
<span className="text-xs text-red-500">Not Set (Please set via UI or ENV_VAR)</span>
108+
<span className="text-xs text-red-500">Disconnected (Please add API key)</span>
109109
</>
110110
)}
111111
</div>

app/components/chat/BaseChat.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,12 +338,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
338338
<div className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
339339
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
340340
{!chatStarted && (
341-
<div id="intro" className="mt-[16vh] max-w-chat mx-auto text-center px-4 lg:px-0">
341+
<div id="intro" className="mt-[16vh] max-w-2xl mx-auto text-center px-4 lg:px-0">
342342
<h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
343-
Where ideas begin
343+
Prompt Build & Deploy
344344
</h1>
345345
<p className="text-md lg:text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
346-
Bring ideas to life in seconds or get help on existing projects.
346+
Let your imagination build your next startup idea.
347347
</p>
348348
</div>
349349
)}

app/components/chat/Chat.client.tsx

Lines changed: 137 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { logStore } from '~/lib/stores/logs';
2727
import { streamingState } from '~/lib/stores/streaming';
2828
import { filesToArtifacts } from '~/utils/fileUtils';
2929
import { supabaseConnection } from '~/lib/stores/supabase';
30+
import { ImportErrorModal } from '~/components/ui/ImportErrorModal';
3031

3132
const toastAnimation = cssTransition({
3233
enter: 'animated fadeInRight',
@@ -123,6 +124,14 @@ export const ChatImpl = memo(
123124
const [imageDataList, setImageDataList] = useState<string[]>([]);
124125
const [searchParams, setSearchParams] = useSearchParams();
125126
const [fakeLoading, setFakeLoading] = useState(false);
127+
const [importError, setImportError] = useState<{
128+
type: 'warning' | 'error';
129+
title: string;
130+
message: string;
131+
failedFiles?: string[];
132+
onRetry?: () => void;
133+
onContinue?: () => void;
134+
} | null>(null);
126135
const files = useStore(workbenchStore.files);
127136
const actionAlert = useStore(workbenchStore.alert);
128137
const deployAlert = useStore(workbenchStore.deployAlert);
@@ -327,17 +336,67 @@ export const ChatImpl = memo(
327336

328337
if (template !== 'blank') {
329338
const temResp = await getTemplates(template, title).catch((e) => {
330-
if (e.message.includes('rate limit')) {
331-
toast.warning('Rate limit exceeded. Skipping starter template\n Continuing with blank template');
339+
// Check for specific error types from selectStarterTemplate.ts
340+
if (e.message === 'RATE_LIMIT') {
341+
setImportError({
342+
type: 'error',
343+
title: 'Rate Limit Exceeded',
344+
message: 'GitHub API rate limit exceeded. Please try again in a few minutes.',
345+
});
346+
} else if (e.message === 'REPO_NOT_FOUND') {
347+
setImportError({
348+
type: 'error',
349+
title: 'Repository Not Found',
350+
message: 'Template repository not found. Using blank template instead.',
351+
});
352+
} else if (e.message === 'NETWORK_ERROR') {
353+
setImportError({
354+
type: 'error',
355+
title: 'Network Error',
356+
message: 'Network error while fetching template. Please check your connection.',
357+
});
332358
} else {
333-
toast.warning('Failed to import starter template\n Continuing with blank template');
359+
setImportError({
360+
type: 'error',
361+
title: 'Template Fetch Failed',
362+
message: 'Failed to fetch template. Please try again.',
363+
});
334364
}
335365

336366
return null;
337367
});
338368

339369
if (temResp) {
340-
const { assistantMessage, userMessage } = temResp;
370+
const { assistantMessage, userMessage, failedFiles } = temResp;
371+
372+
// Check for failed files
373+
if (failedFiles && failedFiles.length > 0) {
374+
setImportError({
375+
type: 'warning',
376+
title: 'Template Partially Imported',
377+
message: `${failedFiles.length} file(s) failed to import due to network errors.`,
378+
failedFiles: failedFiles.map((f: { path: string }) => f.path),
379+
onRetry: async () => {
380+
const retryResp = await getTemplates(template, title);
381+
382+
if (retryResp && (!retryResp.failedFiles || retryResp.failedFiles.length === 0)) {
383+
setImportError(null);
384+
385+
// Continue with template
386+
} else if (retryResp && retryResp.failedFiles) {
387+
// Still have failures, update modal
388+
setImportError((prev) => ({
389+
...prev!,
390+
failedFiles: retryResp.failedFiles.map((f: { path: string }) => f.path),
391+
}));
392+
}
393+
},
394+
onContinue: () => {
395+
setImportError(null);
396+
},
397+
});
398+
}
399+
341400
setMessages([
342401
{
343402
id: `1-${new Date().getTime()}`,
@@ -365,10 +424,10 @@ export const ChatImpl = memo(
365424
annotations: ['hidden'],
366425
},
367426
]);
427+
368428
reload();
369429
setInput('');
370430
Cookies.remove(PROMPT_COOKIE_KEY);
371-
372431
setUploadedFiles([]);
373432
setImageDataList([]);
374433

@@ -505,66 +564,80 @@ export const ChatImpl = memo(
505564
};
506565

507566
return (
508-
<BaseChat
509-
ref={animationScope}
510-
textareaRef={textareaRef}
511-
input={input}
512-
showChat={showChat}
513-
chatStarted={chatStarted}
514-
isStreaming={isLoading || fakeLoading}
515-
onStreamingChange={(streaming) => {
516-
streamingState.set(streaming);
517-
}}
518-
enhancingPrompt={enhancingPrompt}
519-
promptEnhanced={promptEnhanced}
520-
sendMessage={sendMessage}
521-
model={model}
522-
setModel={handleModelChange}
523-
provider={provider}
524-
setProvider={handleProviderChange}
525-
providerList={activeProviders}
526-
handleInputChange={(e) => {
527-
onTextareaChange(e);
528-
debouncedCachePrompt(e);
529-
}}
530-
handleStop={abort}
531-
description={description}
532-
importChat={importChat}
533-
exportChat={exportChat}
534-
messages={messages.map((message, i) => {
535-
if (message.role === 'user') {
536-
return message;
537-
}
567+
<>
568+
<BaseChat
569+
ref={animationScope}
570+
textareaRef={textareaRef}
571+
input={input}
572+
showChat={showChat}
573+
chatStarted={chatStarted}
574+
isStreaming={isLoading || fakeLoading}
575+
onStreamingChange={(streaming) => {
576+
streamingState.set(streaming);
577+
}}
578+
enhancingPrompt={enhancingPrompt}
579+
promptEnhanced={promptEnhanced}
580+
sendMessage={sendMessage}
581+
model={model}
582+
setModel={handleModelChange}
583+
provider={provider}
584+
setProvider={handleProviderChange}
585+
providerList={activeProviders}
586+
handleInputChange={(e) => {
587+
onTextareaChange(e);
588+
debouncedCachePrompt(e);
589+
}}
590+
handleStop={abort}
591+
description={description}
592+
importChat={importChat}
593+
exportChat={exportChat}
594+
messages={messages.map((message, i) => {
595+
if (message.role === 'user') {
596+
return message;
597+
}
538598

539-
return {
540-
...message,
541-
content: parsedMessages[i] || '',
542-
};
543-
})}
544-
enhancePrompt={() => {
545-
enhancePrompt(
546-
input,
547-
(input) => {
548-
setInput(input);
549-
scrollTextArea();
550-
},
551-
model,
552-
provider,
553-
apiKeys,
554-
);
555-
}}
556-
uploadedFiles={uploadedFiles}
557-
setUploadedFiles={setUploadedFiles}
558-
imageDataList={imageDataList}
559-
setImageDataList={setImageDataList}
560-
actionAlert={actionAlert}
561-
clearAlert={() => workbenchStore.clearAlert()}
562-
supabaseAlert={supabaseAlert}
563-
clearSupabaseAlert={() => workbenchStore.clearSupabaseAlert()}
564-
deployAlert={deployAlert}
565-
clearDeployAlert={() => workbenchStore.clearDeployAlert()}
566-
data={chatData}
567-
/>
599+
return {
600+
...message,
601+
content: parsedMessages[i] || '',
602+
};
603+
})}
604+
enhancePrompt={() => {
605+
enhancePrompt(
606+
input,
607+
(input) => {
608+
setInput(input);
609+
scrollTextArea();
610+
},
611+
model,
612+
provider,
613+
apiKeys,
614+
);
615+
}}
616+
uploadedFiles={uploadedFiles}
617+
setUploadedFiles={setUploadedFiles}
618+
imageDataList={imageDataList}
619+
setImageDataList={setImageDataList}
620+
actionAlert={actionAlert}
621+
clearAlert={() => workbenchStore.clearAlert()}
622+
supabaseAlert={supabaseAlert}
623+
clearSupabaseAlert={() => workbenchStore.clearSupabaseAlert()}
624+
deployAlert={deployAlert}
625+
clearDeployAlert={() => workbenchStore.clearDeployAlert()}
626+
data={chatData}
627+
/>
628+
{importError && (
629+
<ImportErrorModal
630+
isOpen={Boolean(importError)}
631+
type={importError.type}
632+
title={importError.title}
633+
message={importError.message}
634+
failedFiles={importError.failedFiles}
635+
onRetry={importError.onRetry}
636+
onContinue={importError.onContinue}
637+
onClose={() => setImportError(null)}
638+
/>
639+
)}
640+
</>
568641
);
569642
},
570643
);

app/components/chat/ModelSelector.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ export const ModelSelector = ({
241241
tabIndex={0}
242242
>
243243
<div className="flex items-center justify-between">
244-
<div className="truncate">{provider?.name || 'Select provider'}</div>
244+
<div className="flex items-center gap-2 truncate">
245+
{provider?.icon && <img src={provider.icon} alt={provider.name} className="w-5 h-5" />}
246+
{provider?.name || 'Select provider'}
247+
</div>
245248
<div
246249
className={classNames(
247250
'i-ph:caret-down w-4 h-4 text-bolt-elements-textSecondary opacity-75',
@@ -335,7 +338,12 @@ export const ModelSelector = ({
335338
}}
336339
tabIndex={focusedProviderIndex === index ? 0 : -1}
337340
>
338-
{providerOption.name}
341+
<div className="flex items-center gap-2">
342+
{providerOption.icon && (
343+
<img src={providerOption.icon} alt={providerOption.name} className="w-5 h-5" />
344+
)}
345+
{providerOption.name}
346+
</div>
339347
</div>
340348
))
341349
)}
@@ -368,7 +376,16 @@ export const ModelSelector = ({
368376
tabIndex={0}
369377
>
370378
<div className="flex items-center justify-between">
371-
<div className="truncate">{modelList.find((m) => m.name === model)?.label || 'Select model'}</div>
379+
<div className="flex items-center gap-2 truncate">
380+
{modelList.find((m) => m.name === model)?.icon && (
381+
<img
382+
src={modelList.find((m) => m.name === model)?.icon}
383+
alt={modelList.find((m) => m.name === model)?.label}
384+
className="w-5 h-5"
385+
/>
386+
)}
387+
{modelList.find((m) => m.name === model)?.label || 'Select model'}
388+
</div>
372389
<div
373390
className={classNames(
374391
'i-ph:caret-down w-4 h-4 text-bolt-elements-textSecondary opacity-75',
@@ -454,7 +471,10 @@ export const ModelSelector = ({
454471
}}
455472
tabIndex={focusedModelIndex === index ? 0 : -1}
456473
>
457-
{modelOption.label}
474+
<div className="flex items-center gap-2">
475+
{modelOption.icon && <img src={modelOption.icon} alt={modelOption.label} className="w-5 h-5" />}
476+
{modelOption.label}
477+
</div>
458478
</div>
459479
))
460480
)}

0 commit comments

Comments
 (0)