mirror of
https://github.com/stackblitz-labs/bolt.diy.git
synced 2025-06-18 01:49:07 +01:00
refactor(chat): improve UI layout, artifact handling, and template naming
- Restructured alert components in BaseChat for better layout organization - Updated artifact component to display dynamic titles based on state - Simplified template names in constants for better readability - Enhanced snapshot restoration process by consolidating command actions into a single artifact
This commit is contained in:
parent
bf03b6f0fe
commit
42eaa2f5e1
@ -75,7 +75,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
|
||||
>
|
||||
<div className="px-5 p-3.5 w-full text-left">
|
||||
<div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">
|
||||
{artifact.type === 'bundled' ? 'Setup Project' : artifact?.title}
|
||||
{artifact?.title}
|
||||
</div>
|
||||
<div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">
|
||||
Click to open Workbench
|
||||
@ -109,7 +109,13 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
|
||||
<div className="i-svg-spinners:90-ring-with-bg"></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-bolt-elements-textPrimary font-medium leading-5 text-sm">Create initial files</div>
|
||||
<div className="text-bolt-elements-textPrimary font-medium leading-5 text-sm">
|
||||
{allActionFinished
|
||||
? artifact.id === 'imported-files'
|
||||
? 'Restore files from snapshot'
|
||||
: 'Initial files created'
|
||||
: 'Creating initial files'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<AnimatePresence>
|
||||
|
@ -367,33 +367,32 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
}}
|
||||
</ClientOnly>
|
||||
</StickToBottom.Content>
|
||||
{deployAlert && (
|
||||
<DeployChatAlert
|
||||
alert={deployAlert}
|
||||
clearAlert={() => clearDeployAlert?.()}
|
||||
postMessage={(message: string | undefined) => {
|
||||
sendMessage?.({} as any, message);
|
||||
clearSupabaseAlert?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{supabaseAlert && (
|
||||
<SupabaseChatAlert
|
||||
alert={supabaseAlert}
|
||||
clearAlert={() => clearSupabaseAlert?.()}
|
||||
postMessage={(message) => {
|
||||
sendMessage?.({} as any, message);
|
||||
clearSupabaseAlert?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ScrollToBottom />
|
||||
<div
|
||||
className={classNames('my-auto flex flex-col gap-2 w-full max-w-chat mx-auto z-prompt mb-6', {
|
||||
'sticky bottom-2': chatStarted,
|
||||
})}
|
||||
>
|
||||
<div className="bg-bolt-elements-background-depth-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
{deployAlert && (
|
||||
<DeployChatAlert
|
||||
alert={deployAlert}
|
||||
clearAlert={() => clearDeployAlert?.()}
|
||||
postMessage={(message: string | undefined) => {
|
||||
sendMessage?.({} as any, message);
|
||||
clearSupabaseAlert?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{supabaseAlert && (
|
||||
<SupabaseChatAlert
|
||||
alert={supabaseAlert}
|
||||
clearAlert={() => clearSupabaseAlert?.()}
|
||||
postMessage={(message) => {
|
||||
sendMessage?.({} as any, message);
|
||||
clearSupabaseAlert?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{actionAlert && (
|
||||
<ChatAlert
|
||||
alert={actionAlert}
|
||||
@ -405,10 +404,11 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ScrollToBottom />
|
||||
{progressAnnotations && <ProgressCompilation data={progressAnnotations} />}
|
||||
<div
|
||||
className={classNames(
|
||||
'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',
|
||||
'relative 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',
|
||||
|
||||
/*
|
||||
* {
|
||||
@ -686,7 +686,7 @@ function ScrollToBottom() {
|
||||
return (
|
||||
!isAtBottom && (
|
||||
<button
|
||||
className="absolute z-50 top-[50%] translate-y-[-60%] text-4xl rounded-lg left-[50%] translate-x-[-50%] px-1.5 py-0.5 flex items-center gap-2 bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor text-bolt-elements-textPrimary text-sm"
|
||||
className="absolute z-50 top-[0%] translate-y-[-100%] text-4xl rounded-lg left-[50%] translate-x-[-50%] px-1.5 py-0.5 flex items-center gap-2 bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor text-bolt-elements-textPrimary text-sm"
|
||||
onClick={() => scrollToBottom()}
|
||||
>
|
||||
Go to last message
|
||||
|
@ -316,14 +316,14 @@ export const ChatImpl = memo(
|
||||
setFakeLoading(true);
|
||||
|
||||
if (autoSelectTemplate) {
|
||||
const { template, title } = await selectStarterTemplate({
|
||||
const { template } = await selectStarterTemplate({
|
||||
message: messageContent,
|
||||
model,
|
||||
provider,
|
||||
});
|
||||
|
||||
if (template !== 'blank') {
|
||||
const temResp = await getTemplates(template, title).catch((e) => {
|
||||
const temResp = await getTemplates(template).catch((e) => {
|
||||
if (e.message.includes('rate limit')) {
|
||||
toast.warning('Rate limit exceeded. Skipping starter template\n Continuing with blank template');
|
||||
} else {
|
||||
|
@ -99,7 +99,7 @@ export function SupabaseChatAlert({ alert, clearAlert, postMessage }: Props) {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="max-w-chat rounded-lg border-l-2 border-l-[#098F5F] border-bolt-elements-borderColor bg-bolt-elements-background-depth-2"
|
||||
className="max-w-chat rounded-lg border-l-2 border-l-[#098F5F] border border-bolt-elements-borderColor bg-bolt-elements-background-depth-2"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-4 pb-2">
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
import type { FileMap } from '~/lib/stores/files';
|
||||
import type { Snapshot } from './types';
|
||||
import { webcontainer } from '~/lib/webcontainer';
|
||||
import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands';
|
||||
import { detectProjectCommands, createCommandActionsString } from '~/utils/projectCommands';
|
||||
import type { ContextAnnotation } from '~/types/context';
|
||||
|
||||
export interface ChatHistoryItem {
|
||||
@ -112,23 +112,26 @@ export function useChatHistory() {
|
||||
path: key,
|
||||
};
|
||||
})
|
||||
.filter((x) => !!x);
|
||||
.filter((x): x is { content: string; path: string } => !!x); // Type assertion
|
||||
const projectCommands = await detectProjectCommands(files);
|
||||
const commands = createCommandsMessage(projectCommands);
|
||||
|
||||
// Call the modified function to get only the command actions string
|
||||
const commandActionsString = createCommandActionsString(projectCommands);
|
||||
|
||||
filteredMessages = [
|
||||
{
|
||||
id: generateId(),
|
||||
role: 'user',
|
||||
content: `Restore project from snapshot
|
||||
`,
|
||||
content: `Restore project from snapshot`, // Removed newline
|
||||
annotations: ['no-store', 'hidden'],
|
||||
},
|
||||
{
|
||||
id: storedMessages.messages[snapshotIndex].id,
|
||||
role: 'assistant',
|
||||
content: ` 📦 Chat Restored from snapshot, You can revert this message to load the full chat history
|
||||
<boltArtifact id="imported-files" title="Project Files Snapshot" type="bundled">
|
||||
|
||||
// Combine followup message and the artifact with files and command actions
|
||||
content: `Bolt Restored your chat from a snapshot. You can revert this message to load the full chat history.
|
||||
<boltArtifact id="restored-project-setup" title="Restored Project & Setup">
|
||||
${Object.entries(snapshot?.files || {})
|
||||
.map(([key, value]) => {
|
||||
if (value?.type === 'file') {
|
||||
@ -142,8 +145,9 @@ ${value.content}
|
||||
}
|
||||
})
|
||||
.join('\n')}
|
||||
${commandActionsString}
|
||||
</boltArtifact>
|
||||
`,
|
||||
`, // Added commandActionsString, followupMessage, updated id and title
|
||||
annotations: [
|
||||
'no-store',
|
||||
...(summary
|
||||
@ -157,33 +161,13 @@ ${value.content}
|
||||
: []),
|
||||
],
|
||||
},
|
||||
...(commands !== null
|
||||
? [
|
||||
{
|
||||
id: `${storedMessages.messages[snapshotIndex].id}-2`,
|
||||
role: 'user' as const,
|
||||
content: `setup project`,
|
||||
annotations: ['no-store', 'hidden'],
|
||||
},
|
||||
{
|
||||
...commands,
|
||||
id: `${storedMessages.messages[snapshotIndex].id}-3`,
|
||||
annotations: [
|
||||
'no-store',
|
||||
...(commands.annotations || []),
|
||||
...(summary
|
||||
? [
|
||||
{
|
||||
chatId: `${storedMessages.messages[snapshotIndex].id}-3`,
|
||||
type: 'chatSummary',
|
||||
summary,
|
||||
} satisfies ContextAnnotation,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
// Remove the separate user and assistant messages for commands
|
||||
/*
|
||||
*...(commands !== null // This block is no longer needed
|
||||
* ? [ ... ]
|
||||
* : []),
|
||||
*/
|
||||
...filteredMessages,
|
||||
];
|
||||
restoreSnapshot(mixedId);
|
||||
|
@ -26,7 +26,7 @@ PROVIDER_LIST.forEach((provider) => {
|
||||
|
||||
export const STARTER_TEMPLATES: Template[] = [
|
||||
{
|
||||
name: 'bolt-expo-app',
|
||||
name: 'Expo App',
|
||||
label: 'Expo App',
|
||||
description: 'Expo starter template for building cross-platform mobile apps',
|
||||
githubRepo: 'xKevIsDev/bolt-expo-template',
|
||||
@ -34,7 +34,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:expo',
|
||||
},
|
||||
{
|
||||
name: 'bolt-astro-basic',
|
||||
name: 'Basic Astro',
|
||||
label: 'Astro Basic',
|
||||
description: 'Lightweight Astro starter template for building fast static websites',
|
||||
githubRepo: 'xKevIsDev/bolt-astro-basic-template',
|
||||
@ -42,7 +42,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:astro',
|
||||
},
|
||||
{
|
||||
name: 'bolt-nextjs-shadcn',
|
||||
name: 'NextJS Shadcn',
|
||||
label: 'Next.js with shadcn/ui',
|
||||
description: 'Next.js starter fullstack template integrated with shadcn/ui components and styling system',
|
||||
githubRepo: 'xKevIsDev/bolt-nextjs-shadcn-template',
|
||||
@ -50,7 +50,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:nextjs',
|
||||
},
|
||||
{
|
||||
name: 'bolt-qwik-ts',
|
||||
name: 'Qwik Typescript',
|
||||
label: 'Qwik TypeScript',
|
||||
description: 'Qwik framework starter with TypeScript for building resumable applications',
|
||||
githubRepo: 'xKevIsDev/bolt-qwik-ts-template',
|
||||
@ -58,7 +58,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:qwik',
|
||||
},
|
||||
{
|
||||
name: 'bolt-remix-ts',
|
||||
name: 'Remix Typescript',
|
||||
label: 'Remix TypeScript',
|
||||
description: 'Remix framework starter with TypeScript for full-stack web applications',
|
||||
githubRepo: 'xKevIsDev/bolt-remix-ts-template',
|
||||
@ -66,7 +66,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:remix',
|
||||
},
|
||||
{
|
||||
name: 'bolt-slidev',
|
||||
name: 'Slidev',
|
||||
label: 'Slidev Presentation',
|
||||
description: 'Slidev starter template for creating developer-friendly presentations using Markdown',
|
||||
githubRepo: 'xKevIsDev/bolt-slidev-template',
|
||||
@ -74,7 +74,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:slidev',
|
||||
},
|
||||
{
|
||||
name: 'bolt-sveltekit',
|
||||
name: 'Sveltekit',
|
||||
label: 'SvelteKit',
|
||||
description: 'SvelteKit starter template for building fast, efficient web applications',
|
||||
githubRepo: 'bolt-sveltekit-template',
|
||||
@ -82,7 +82,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:svelte',
|
||||
},
|
||||
{
|
||||
name: 'vanilla-vite',
|
||||
name: 'Vanilla Vite',
|
||||
label: 'Vanilla + Vite',
|
||||
description: 'Minimal Vite starter template for vanilla JavaScript projects',
|
||||
githubRepo: 'xKevIsDev/vanilla-vite-template',
|
||||
@ -90,7 +90,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:vite',
|
||||
},
|
||||
{
|
||||
name: 'bolt-vite-react',
|
||||
name: 'Vite React',
|
||||
label: 'React + Vite + typescript',
|
||||
description: 'React starter template powered by Vite for fast development experience',
|
||||
githubRepo: 'xKevIsDev/bolt-vite-react-ts-template',
|
||||
@ -98,7 +98,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:react',
|
||||
},
|
||||
{
|
||||
name: 'bolt-vite-ts',
|
||||
name: 'Vite Typescript',
|
||||
label: 'Vite + TypeScript',
|
||||
description: 'Vite starter template with TypeScript configuration for type-safe development',
|
||||
githubRepo: 'xKevIsDev/bolt-vite-ts-template',
|
||||
@ -106,7 +106,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:typescript',
|
||||
},
|
||||
{
|
||||
name: 'bolt-vue',
|
||||
name: 'Vue',
|
||||
label: 'Vue.js',
|
||||
description: 'Vue.js starter template with modern tooling and best practices',
|
||||
githubRepo: 'xKevIsDev/bolt-vue-template',
|
||||
@ -114,7 +114,7 @@ export const STARTER_TEMPLATES: Template[] = [
|
||||
icon: 'i-bolt:vue',
|
||||
},
|
||||
{
|
||||
name: 'bolt-angular',
|
||||
name: 'Angular',
|
||||
label: 'Angular Starter',
|
||||
description: 'A modern Angular starter template with TypeScript support and best practices configuration',
|
||||
githubRepo: 'xKevIsDev/bolt-angular-template',
|
||||
|
@ -84,9 +84,10 @@ export function createCommandsMessage(commands: ProjectCommands): Message | null
|
||||
return {
|
||||
role: 'assistant',
|
||||
content: `
|
||||
${commands.followupMessage ? `\n\n${commands.followupMessage}` : ''}
|
||||
<boltArtifact id="project-setup" title="Project Setup">
|
||||
${commandString}
|
||||
</boltArtifact>${commands.followupMessage ? `\n\n${commands.followupMessage}` : ''}`,
|
||||
</boltArtifact>`,
|
||||
id: generateId(),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
@ -127,3 +128,26 @@ export function escapeBoltAActionTags(input: string) {
|
||||
export function escapeBoltTags(input: string) {
|
||||
return escapeBoltArtifactTags(escapeBoltAActionTags(input));
|
||||
}
|
||||
|
||||
// We have this seperate function to simplify the restore snapshot process in to one single artifact.
|
||||
export function createCommandActionsString(commands: ProjectCommands): string {
|
||||
if (!commands.setupCommand && !commands.startCommand) {
|
||||
// Return empty string if no commands
|
||||
return '';
|
||||
}
|
||||
|
||||
let commandString = '';
|
||||
|
||||
if (commands.setupCommand) {
|
||||
commandString += `
|
||||
<boltAction type="shell">${commands.setupCommand}</boltAction>`;
|
||||
}
|
||||
|
||||
if (commands.startCommand) {
|
||||
commandString += `
|
||||
<boltAction type="start">${commands.startCommand}</boltAction>
|
||||
`;
|
||||
}
|
||||
|
||||
return commandString;
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ const getGitHubRepoContent = async (repoName: string): Promise<{ name: string; p
|
||||
}
|
||||
};
|
||||
|
||||
export async function getTemplates(templateName: string, title?: string) {
|
||||
export async function getTemplates(templateName: string) {
|
||||
const template = STARTER_TEMPLATES.find((t) => t.name == templateName);
|
||||
|
||||
if (!template) {
|
||||
@ -182,7 +182,8 @@ export async function getTemplates(templateName: string, title?: string) {
|
||||
}
|
||||
|
||||
const assistantMessage = `
|
||||
<boltArtifact id="imported-files" title="${title || 'Importing Starter Files'}" type="bundled">
|
||||
Bolt is initializing your project with the required files using the ${template.name} template.
|
||||
<boltArtifact id="imported-files" title="Create initial files" type="bundled">
|
||||
${filesToImport.files
|
||||
.map(
|
||||
(file) =>
|
||||
|
Loading…
x
Reference in New Issue
Block a user