feat(design): add design scheme support and UI improvements
- Implement design scheme system with palette, typography, and feature customization - Add color scheme dialog for user customization - Update chat UI components to use design scheme values - Improve header actions with consolidated deploy and export buttons - Adjust layout spacing and styling across multiple components (chat, workbench etc...) - Add model and provider info to chat messages - Refactor workbench and sidebar components for better responsiveness
This commit is contained in:
@@ -6,6 +6,7 @@ import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { WORK_DIR } from '~/utils/constants';
|
||||
import WithTooltip from '~/components/ui/Tooltip';
|
||||
import type { Message } from 'ai';
|
||||
import type { ProviderInfo } from '~/types/model';
|
||||
|
||||
interface AssistantMessageProps {
|
||||
content: string;
|
||||
@@ -16,6 +17,8 @@ interface AssistantMessageProps {
|
||||
append?: (message: Message) => void;
|
||||
chatMode?: 'discuss' | 'build';
|
||||
setChatMode?: (mode: 'discuss' | 'build') => void;
|
||||
model?: string;
|
||||
provider?: ProviderInfo;
|
||||
}
|
||||
|
||||
function openArtifactInWorkbench(filePath: string) {
|
||||
@@ -43,7 +46,18 @@ function normalizedFilePath(path: string) {
|
||||
}
|
||||
|
||||
export const AssistantMessage = memo(
|
||||
({ content, annotations, messageId, onRewind, onFork, append, chatMode, setChatMode }: AssistantMessageProps) => {
|
||||
({
|
||||
content,
|
||||
annotations,
|
||||
messageId,
|
||||
onRewind,
|
||||
onFork,
|
||||
append,
|
||||
chatMode,
|
||||
setChatMode,
|
||||
model,
|
||||
provider,
|
||||
}: AssistantMessageProps) => {
|
||||
const filteredAnnotations = (annotations?.filter(
|
||||
(annotation: JSONValue) =>
|
||||
annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
|
||||
@@ -141,7 +155,7 @@ export const AssistantMessage = memo(
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<Markdown append={append} chatMode={chatMode} setChatMode={setChatMode} html>
|
||||
<Markdown append={append} chatMode={chatMode} setChatMode={setChatMode} model={model} provider={provider} html>
|
||||
{content}
|
||||
</Markdown>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@ import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { StickToBottom, useStickToBottomContext } from '~/lib/hooks';
|
||||
import { ChatBox } from './ChatBox';
|
||||
import type { DesignScheme } from '~/types/design-scheme';
|
||||
|
||||
const TEXTAREA_MIN_HEIGHT = 76;
|
||||
|
||||
@@ -73,6 +74,8 @@ interface BaseChatProps {
|
||||
chatMode?: 'discuss' | 'build';
|
||||
setChatMode?: (mode: 'discuss' | 'build') => void;
|
||||
append?: (message: Message) => void;
|
||||
designScheme?: DesignScheme;
|
||||
setDesignScheme?: (scheme: DesignScheme) => void;
|
||||
}
|
||||
|
||||
export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
@@ -114,6 +117,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
chatMode,
|
||||
setChatMode,
|
||||
append,
|
||||
designScheme,
|
||||
setDesignScheme,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -332,7 +337,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
<div className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
|
||||
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
|
||||
{!chatStarted && (
|
||||
<div id="intro" className="mt-[16vh] max-w-chat mx-auto text-center px-4 lg:px-0">
|
||||
<div id="intro" className="mt-[16vh] max-w-2xl mx-auto text-center px-4 lg:px-0">
|
||||
<h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
|
||||
Where ideas begin
|
||||
</h1>
|
||||
@@ -353,12 +358,14 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
{() => {
|
||||
return chatStarted ? (
|
||||
<Messages
|
||||
className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
|
||||
className="flex flex-col w-full flex-1 max-w-chat pb-4 mx-auto z-1"
|
||||
messages={messages}
|
||||
isStreaming={isStreaming}
|
||||
append={append}
|
||||
chatMode={chatMode}
|
||||
setChatMode={setChatMode}
|
||||
model={model}
|
||||
provider={provider}
|
||||
/>
|
||||
) : null;
|
||||
}}
|
||||
@@ -440,6 +447,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
handleFileUpload={handleFileUpload}
|
||||
chatMode={chatMode}
|
||||
setChatMode={setChatMode}
|
||||
designScheme={designScheme}
|
||||
setDesignScheme={setDesignScheme}
|
||||
/>
|
||||
</div>
|
||||
</StickToBottom>
|
||||
|
||||
@@ -27,6 +27,7 @@ import { logStore } from '~/lib/stores/logs';
|
||||
import { streamingState } from '~/lib/stores/streaming';
|
||||
import { filesToArtifacts } from '~/utils/fileUtils';
|
||||
import { supabaseConnection } from '~/lib/stores/supabase';
|
||||
import { defaultDesignScheme, type DesignScheme } from '~/types/design-scheme';
|
||||
|
||||
const toastAnimation = cssTransition({
|
||||
enter: 'animated fadeInRight',
|
||||
@@ -124,6 +125,10 @@ export const ChatImpl = memo(
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [fakeLoading, setFakeLoading] = useState(false);
|
||||
const files = useStore(workbenchStore.files);
|
||||
const [designScheme, setDesignScheme] = useState<DesignScheme>(defaultDesignScheme);
|
||||
|
||||
console.log(designScheme);
|
||||
|
||||
const actionAlert = useStore(workbenchStore.alert);
|
||||
const deployAlert = useStore(workbenchStore.deployAlert);
|
||||
const supabaseConn = useStore(supabaseConnection); // Add this line to get Supabase connection
|
||||
@@ -170,6 +175,7 @@ export const ChatImpl = memo(
|
||||
promptId,
|
||||
contextOptimization: contextOptimizationEnabled,
|
||||
chatMode,
|
||||
designScheme,
|
||||
supabase: {
|
||||
isConnected: supabaseConn.isConnected,
|
||||
hasSelectedProject: !!selectedProject,
|
||||
@@ -569,6 +575,8 @@ export const ChatImpl = memo(
|
||||
chatMode={chatMode}
|
||||
setChatMode={setChatMode}
|
||||
append={append}
|
||||
designScheme={designScheme}
|
||||
setDesignScheme={setDesignScheme}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -11,11 +11,12 @@ import { SendButton } from './SendButton.client';
|
||||
import { IconButton } from '~/components/ui/IconButton';
|
||||
import { toast } from 'react-toastify';
|
||||
import { SpeechRecognitionButton } from '~/components/chat/SpeechRecognition';
|
||||
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
|
||||
import { SupabaseConnection } from './SupabaseConnection';
|
||||
import { ExpoQrModal } from '~/components/workbench/ExpoQrModal';
|
||||
import styles from './BaseChat.module.scss';
|
||||
import type { ProviderInfo } from '~/types/model';
|
||||
import { ColorSchemeDialog } from '~/components/ui/ColorSchemeDialog';
|
||||
import type { DesignScheme } from '~/types/design-scheme';
|
||||
|
||||
interface ChatBoxProps {
|
||||
isModelSettingsCollapsed: boolean;
|
||||
@@ -54,13 +55,15 @@ interface ChatBoxProps {
|
||||
enhancePrompt?: (() => void) | undefined;
|
||||
chatMode?: 'discuss' | 'build';
|
||||
setChatMode?: (mode: 'discuss' | 'build') => void;
|
||||
designScheme?: DesignScheme;
|
||||
setDesignScheme?: (scheme: DesignScheme) => void;
|
||||
}
|
||||
|
||||
export const ChatBox: React.FC<ChatBoxProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'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',
|
||||
'relative bg-bolt-elements-background-depth-2 backdrop-blur p-3 rounded-lg border border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt',
|
||||
|
||||
/*
|
||||
* {
|
||||
@@ -237,6 +240,7 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
|
||||
</ClientOnly>
|
||||
<div className="flex justify-between items-center text-sm p-4 pt-2">
|
||||
<div className="flex gap-1 items-center">
|
||||
<ColorSchemeDialog designScheme={props.designScheme} setDesignScheme={props.setDesignScheme} />
|
||||
<IconButton title="Upload file" className="transition-all" onClick={() => props.handleFileUpload()}>
|
||||
<div className="i-ph:paperclip text-xl"></div>
|
||||
</IconButton>
|
||||
@@ -279,7 +283,6 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
|
||||
{props.chatMode === 'discuss' ? <span>Discuss</span> : <span />}
|
||||
</IconButton>
|
||||
)}
|
||||
{props.chatStarted && <ClientOnly>{() => <ExportChatButton exportChat={props.exportChat} />}</ClientOnly>}
|
||||
<IconButton
|
||||
title="Model Settings"
|
||||
className={classNames('transition-all flex items-center gap-1', {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CodeBlock } from './CodeBlock';
|
||||
import type { Message } from 'ai';
|
||||
import styles from './Markdown.module.scss';
|
||||
import ThoughtBox from './ThoughtBox';
|
||||
import type { ProviderInfo } from '~/types/model';
|
||||
|
||||
const logger = createScopedLogger('MarkdownComponent');
|
||||
|
||||
@@ -18,10 +19,12 @@ interface MarkdownProps {
|
||||
append?: (message: Message) => void;
|
||||
chatMode?: 'discuss' | 'build';
|
||||
setChatMode?: (mode: 'discuss' | 'build') => void;
|
||||
model?: string;
|
||||
provider?: ProviderInfo;
|
||||
}
|
||||
|
||||
export const Markdown = memo(
|
||||
({ children, html = false, limitedMarkdown = false, append, setChatMode }: MarkdownProps) => {
|
||||
({ children, html = false, limitedMarkdown = false, append, setChatMode, model, provider }: MarkdownProps) => {
|
||||
logger.trace('Render');
|
||||
|
||||
const components = useMemo(() => {
|
||||
@@ -106,17 +109,17 @@ export const Markdown = memo(
|
||||
openArtifactInWorkbench(path);
|
||||
} else if (type === 'message' && append) {
|
||||
append({
|
||||
id: 'random-message', // Replace with your ID generation logic
|
||||
content: message as string, // The message content from the action
|
||||
role: 'user', // Or another role as appropriate
|
||||
id: `quick-action-message-${Date.now()}`,
|
||||
content: `[Model: ${model}]\n\n[Provider: ${provider?.name}]\n\n${message}`,
|
||||
role: 'user',
|
||||
});
|
||||
console.log('Message appended:', message); // Log the appended message
|
||||
console.log('Message appended:', message);
|
||||
} else if (type === 'implement' && append && setChatMode) {
|
||||
setChatMode('build');
|
||||
append({
|
||||
id: 'implement-message', // Replace with your ID generation logic
|
||||
content: message as string, // The message content from the action
|
||||
role: 'user', // Or another role as appropriate
|
||||
id: `quick-action-implement-${Date.now()}`,
|
||||
content: `[Model: ${model}]\n\n[Provider: ${provider?.name}]\n\n${message}`,
|
||||
role: 'user',
|
||||
});
|
||||
} else if (type === 'link' && typeof href === 'string') {
|
||||
try {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useStore } from '@nanostores/react';
|
||||
import { profileStore } from '~/lib/stores/profile';
|
||||
import { forwardRef } from 'react';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import type { ProviderInfo } from '~/types/model';
|
||||
|
||||
interface MessagesProps {
|
||||
id?: string;
|
||||
@@ -20,6 +21,8 @@ interface MessagesProps {
|
||||
append?: (message: Message) => void;
|
||||
chatMode?: 'discuss' | 'build';
|
||||
setChatMode?: (mode: 'discuss' | 'build') => void;
|
||||
model?: string;
|
||||
provider?: ProviderInfo;
|
||||
}
|
||||
|
||||
export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
|
||||
@@ -65,7 +68,7 @@ export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={classNames('flex gap-4 p-6 py-5 w-full rounded-[calc(0.75rem-1px)]', {
|
||||
className={classNames('flex gap-4 p-3 py-3 w-full rounded-lg', {
|
||||
'bg-bolt-elements-messages-background': isUserMessage || !isStreaming || (isStreaming && !isLast),
|
||||
'bg-gradient-to-b from-bolt-elements-messages-background from-30% to-transparent':
|
||||
isStreaming && isLast,
|
||||
@@ -100,6 +103,8 @@ export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
|
||||
append={props.append}
|
||||
chatMode={props.chatMode}
|
||||
setChatMode={props.setChatMode}
|
||||
model={props.model}
|
||||
provider={props.provider}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
import WithTooltip from '~/components/ui/Tooltip';
|
||||
import { IconButton } from '~/components/ui/IconButton';
|
||||
import React from 'react';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
|
||||
export const ExportChatButton = ({ exportChat }: { exportChat?: () => void }) => {
|
||||
return (
|
||||
<WithTooltip tooltip="Export Chat">
|
||||
<IconButton title="Export Chat" onClick={() => exportChat?.()}>
|
||||
<div className="i-ph:download-simple text-xl"></div>
|
||||
</IconButton>
|
||||
</WithTooltip>
|
||||
<div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden mr-2 text-sm">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger className="rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7">
|
||||
Export
|
||||
<span className={classNames('i-ph:caret-down transition-transform')} />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
className={classNames(
|
||||
'z-[250]',
|
||||
'bg-bolt-elements-background-depth-2',
|
||||
'rounded-lg shadow-lg',
|
||||
'border border-bolt-elements-borderColor',
|
||||
'animate-in fade-in-0 zoom-in-95',
|
||||
'py-1',
|
||||
)}
|
||||
sideOffset={5}
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className={classNames(
|
||||
'cursor-pointer flex items-center w-auto px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative',
|
||||
)}
|
||||
onClick={() => {
|
||||
workbenchStore.downloadZip();
|
||||
}}
|
||||
>
|
||||
<div className="i-ph:code size-4.5"></div>
|
||||
<span>Download Code</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className={classNames(
|
||||
'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative',
|
||||
)}
|
||||
onClick={() => exportChat?.()}
|
||||
>
|
||||
<div className="i-ph:chat size-4.5"></div>
|
||||
<span>Export Chat</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user