Skip to content

Commit 5e590aa

Browse files
authored
Merge pull request #1748 from xKevIsDev/enhancements
feat: add inspector, design palette and redesign
2 parents 41e604c + 33e0860 commit 5e590aa

29 files changed

+1506
-295
lines changed

app/components/chat/BaseChat.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
3131
import { useStore } from '@nanostores/react';
3232
import { StickToBottom, useStickToBottomContext } from '~/lib/hooks';
3333
import { ChatBox } from './ChatBox';
34+
import type { DesignScheme } from '~/types/design-scheme';
35+
import type { ElementInfo } from '~/components/workbench/Inspector';
3436

3537
const TEXTAREA_MIN_HEIGHT = 76;
3638

@@ -73,6 +75,10 @@ interface BaseChatProps {
7375
chatMode?: 'discuss' | 'build';
7476
setChatMode?: (mode: 'discuss' | 'build') => void;
7577
append?: (message: Message) => void;
78+
designScheme?: DesignScheme;
79+
setDesignScheme?: (scheme: DesignScheme) => void;
80+
selectedElement?: ElementInfo | null;
81+
setSelectedElement?: (element: ElementInfo | null) => void;
7682
}
7783

7884
export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
@@ -114,6 +120,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
114120
chatMode,
115121
setChatMode,
116122
append,
123+
designScheme,
124+
setDesignScheme,
125+
selectedElement,
126+
setSelectedElement,
117127
},
118128
ref,
119129
) => {
@@ -253,6 +263,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
253263
const handleSendMessage = (event: React.UIEvent, messageInput?: string) => {
254264
if (sendMessage) {
255265
sendMessage(event, messageInput);
266+
setSelectedElement?.(null);
256267

257268
if (recognition) {
258269
recognition.abort(); // Stop current recognition
@@ -332,7 +343,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
332343
<div className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
333344
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
334345
{!chatStarted && (
335-
<div id="intro" className="mt-[16vh] max-w-chat mx-auto text-center px-4 lg:px-0">
346+
<div id="intro" className="mt-[16vh] max-w-2xl mx-auto text-center px-4 lg:px-0">
336347
<h1 className="text-3xl lg:text-6xl font-bold text-bolt-elements-textPrimary mb-4 animate-fade-in">
337348
Where ideas begin
338349
</h1>
@@ -348,12 +359,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
348359
resize="smooth"
349360
initial="smooth"
350361
>
351-
<StickToBottom.Content className="flex flex-col gap-4">
362+
<StickToBottom.Content className="flex flex-col gap-4 relative ">
352363
<ClientOnly>
353364
{() => {
354365
return chatStarted ? (
355366
<Messages
356-
className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1"
367+
className="flex flex-col w-full flex-1 max-w-chat pb-4 mx-auto z-1"
357368
messages={messages}
358369
isStreaming={isStreaming}
359370
append={append}
@@ -365,6 +376,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
365376
) : null;
366377
}}
367378
</ClientOnly>
379+
<ScrollToBottom />
368380
</StickToBottom.Content>
369381
<div
370382
className={classNames('my-auto flex flex-col gap-2 w-full max-w-chat mx-auto z-prompt mb-6', {
@@ -403,7 +415,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
403415
/>
404416
)}
405417
</div>
406-
<ScrollToBottom />
407418
{progressAnnotations && <ProgressCompilation data={progressAnnotations} />}
408419
<ChatBox
409420
isModelSettingsCollapsed={isModelSettingsCollapsed}
@@ -442,6 +453,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
442453
handleFileUpload={handleFileUpload}
443454
chatMode={chatMode}
444455
setChatMode={setChatMode}
456+
designScheme={designScheme}
457+
setDesignScheme={setDesignScheme}
458+
selectedElement={selectedElement}
459+
setSelectedElement={setSelectedElement}
445460
/>
446461
</div>
447462
</StickToBottom>
@@ -472,6 +487,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
472487
actionRunner={actionRunner ?? ({} as ActionRunner)}
473488
chatStarted={chatStarted}
474489
isStreaming={isStreaming}
490+
setSelectedElement={setSelectedElement}
475491
/>
476492
)}
477493
</ClientOnly>
@@ -488,13 +504,16 @@ function ScrollToBottom() {
488504

489505
return (
490506
!isAtBottom && (
491-
<button
492-
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"
493-
onClick={() => scrollToBottom()}
494-
>
495-
Go to last message
496-
<span className="i-ph:arrow-down animate-bounce" />
497-
</button>
507+
<>
508+
<div className="sticky bottom-0 left-0 right-0 bg-gradient-to-t from-bolt-elements-background-depth-1 to-transparent h-20 z-10" />
509+
<button
510+
className="sticky z-50 bottom-0 left-0 right-0 text-4xl rounded-lg px-1.5 py-0.5 flex items-center justify-center mx-auto gap-2 bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor text-bolt-elements-textPrimary text-sm"
511+
onClick={() => scrollToBottom()}
512+
>
513+
Go to last message
514+
<span className="i-ph:arrow-down animate-bounce" />
515+
</button>
516+
</>
498517
)
499518
);
500519
}

app/components/chat/Chat.client.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ 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 { defaultDesignScheme, type DesignScheme } from '~/types/design-scheme';
31+
import type { ElementInfo } from '~/components/workbench/Inspector';
3032

3133
const toastAnimation = cssTransition({
3234
enter: 'animated fadeInRight',
@@ -124,6 +126,7 @@ export const ChatImpl = memo(
124126
const [searchParams, setSearchParams] = useSearchParams();
125127
const [fakeLoading, setFakeLoading] = useState(false);
126128
const files = useStore(workbenchStore.files);
129+
const [designScheme, setDesignScheme] = useState<DesignScheme>(defaultDesignScheme);
127130
const actionAlert = useStore(workbenchStore.alert);
128131
const deployAlert = useStore(workbenchStore.deployAlert);
129132
const supabaseConn = useStore(supabaseConnection); // Add this line to get Supabase connection
@@ -132,7 +135,6 @@ export const ChatImpl = memo(
132135
);
133136
const supabaseAlert = useStore(workbenchStore.supabaseAlert);
134137
const { activeProviders, promptId, autoSelectTemplate, contextOptimizationEnabled } = useSettings();
135-
136138
const [model, setModel] = useState(() => {
137139
const savedModel = Cookies.get('selectedModel');
138140
return savedModel || DEFAULT_MODEL;
@@ -141,14 +143,11 @@ export const ChatImpl = memo(
141143
const savedProvider = Cookies.get('selectedProvider');
142144
return (PROVIDER_LIST.find((p) => p.name === savedProvider) || DEFAULT_PROVIDER) as ProviderInfo;
143145
});
144-
145146
const { showChat } = useStore(chatStore);
146-
147147
const [animationScope, animate] = useAnimate();
148-
149148
const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
150-
151149
const [chatMode, setChatMode] = useState<'discuss' | 'build'>('build');
150+
const [selectedElement, setSelectedElement] = useState<ElementInfo | null>(null);
152151
const {
153152
messages,
154153
isLoading,
@@ -170,6 +169,7 @@ export const ChatImpl = memo(
170169
promptId,
171170
contextOptimization: contextOptimizationEnabled,
172171
chatMode,
172+
designScheme,
173173
supabase: {
174174
isConnected: supabaseConn.isConnected,
175175
hasSelectedProject: !!selectedProject,
@@ -312,8 +312,14 @@ export const ChatImpl = memo(
312312
return;
313313
}
314314

315-
// If no locked items, proceed normally with the original message
316-
const finalMessageContent = messageContent;
315+
let finalMessageContent = messageContent;
316+
317+
if (selectedElement) {
318+
console.log('Selected Element:', selectedElement);
319+
320+
const elementInfo = `<div class=\"__boltSelectedElement__\" data-element='${JSON.stringify(selectedElement)}'>${JSON.stringify(`${selectedElement.displayText}`)}</div>`;
321+
finalMessageContent = messageContent + elementInfo;
322+
}
317323

318324
runAnimation();
319325

@@ -569,6 +575,10 @@ export const ChatImpl = memo(
569575
chatMode={chatMode}
570576
setChatMode={setChatMode}
571577
append={append}
578+
designScheme={designScheme}
579+
setDesignScheme={setDesignScheme}
580+
selectedElement={selectedElement}
581+
setSelectedElement={setSelectedElement}
572582
/>
573583
);
574584
},

app/components/chat/ChatBox.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import { SendButton } from './SendButton.client';
1111
import { IconButton } from '~/components/ui/IconButton';
1212
import { toast } from 'react-toastify';
1313
import { SpeechRecognitionButton } from '~/components/chat/SpeechRecognition';
14-
import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportChatButton';
1514
import { SupabaseConnection } from './SupabaseConnection';
1615
import { ExpoQrModal } from '~/components/workbench/ExpoQrModal';
1716
import styles from './BaseChat.module.scss';
1817
import type { ProviderInfo } from '~/types/model';
18+
import { ColorSchemeDialog } from '~/components/ui/ColorSchemeDialog';
19+
import type { DesignScheme } from '~/types/design-scheme';
20+
import type { ElementInfo } from '~/components/workbench/Inspector';
1921

2022
interface ChatBoxProps {
2123
isModelSettingsCollapsed: boolean;
@@ -54,13 +56,17 @@ interface ChatBoxProps {
5456
enhancePrompt?: (() => void) | undefined;
5557
chatMode?: 'discuss' | 'build';
5658
setChatMode?: (mode: 'discuss' | 'build') => void;
59+
designScheme?: DesignScheme;
60+
setDesignScheme?: (scheme: DesignScheme) => void;
61+
selectedElement?: ElementInfo | null;
62+
setSelectedElement?: ((element: ElementInfo | null) => void) | undefined;
5763
}
5864

5965
export const ChatBox: React.FC<ChatBoxProps> = (props) => {
6066
return (
6167
<div
6268
className={classNames(
63-
'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',
69+
'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',
6470

6571
/*
6672
* {
@@ -143,6 +149,22 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
143149
/>
144150
)}
145151
</ClientOnly>
152+
{props.selectedElement && (
153+
<div className="flex mx-1.5 gap-2 items-center justify-between rounded-lg rounded-b-none border border-b-none border-bolt-elements-borderColor text-bolt-elements-textPrimary flex py-1 px-2.5 font-medium text-xs">
154+
<div className="flex gap-2 items-center lowercase">
155+
<code className="bg-accent-500 rounded-4px px-1.5 py-1 mr-0.5 text-white">
156+
{props?.selectedElement?.tagName}
157+
</code>
158+
selected for inspection
159+
</div>
160+
<button
161+
className="bg-transparent text-accent-500 pointer-auto"
162+
onClick={() => props.setSelectedElement?.(null)}
163+
>
164+
Clear
165+
</button>
166+
</div>
167+
)}
146168
<div
147169
className={classNames('relative shadow-xs border border-bolt-elements-borderColor backdrop-blur rounded-lg')}
148170
>
@@ -237,6 +259,7 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
237259
</ClientOnly>
238260
<div className="flex justify-between items-center text-sm p-4 pt-2">
239261
<div className="flex gap-1 items-center">
262+
<ColorSchemeDialog designScheme={props.designScheme} setDesignScheme={props.setDesignScheme} />
240263
<IconButton title="Upload file" className="transition-all" onClick={() => props.handleFileUpload()}>
241264
<div className="i-ph:paperclip text-xl"></div>
242265
</IconButton>
@@ -279,7 +302,6 @@ export const ChatBox: React.FC<ChatBoxProps> = (props) => {
279302
{props.chatMode === 'discuss' ? <span>Discuss</span> : <span />}
280303
</IconButton>
281304
)}
282-
{props.chatStarted && <ClientOnly>{() => <ExportChatButton exportChat={props.exportChat} />}</ClientOnly>}
283305
<IconButton
284306
title="Model Settings"
285307
className={classNames('transition-all flex items-center gap-1', {

app/components/chat/Markdown.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,42 @@ export const Markdown = memo(
4242
return <Artifact messageId={messageId} />;
4343
}
4444

45+
if (className?.includes('__boltSelectedElement__')) {
46+
const messageId = node?.properties.dataMessageId as string;
47+
const elementDataAttr = node?.properties.dataElement as string;
48+
49+
// Parse the element data if it exists
50+
let elementData: any = null;
51+
52+
if (elementDataAttr) {
53+
try {
54+
elementData = JSON.parse(elementDataAttr);
55+
} catch (e) {
56+
console.error('Failed to parse element data:', e);
57+
}
58+
}
59+
60+
if (!messageId) {
61+
logger.error(`Invalid message id ${messageId}`);
62+
}
63+
64+
return (
65+
<div className="bg-bolt-elements-background-depth-3 border border-bolt-elements-borderColor rounded-lg p-3 my-2">
66+
<div className="flex items-center gap-2 mb-2">
67+
<span className="text-xs font-mono bg-bolt-elements-background-depth-2 px-2 py-1 rounded text-bolt-elements-textTer">
68+
{elementData?.tagName}
69+
</span>
70+
{elementData?.className && (
71+
<span className="text-xs text-bolt-elements-textSecondary">.{elementData.className}</span>
72+
)}
73+
</div>
74+
<code className="block text-sm !text-bolt-elements-textSecondary !bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor p-2 rounded">
75+
{elementData?.displayText}
76+
</code>
77+
</div>
78+
);
79+
}
80+
4581
if (className?.includes('__boltThought__')) {
4682
return <ThoughtBox title="Thought process">{children}</ThoughtBox>;
4783
}

app/components/chat/Messages.client.tsx

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import { useLocation } from '@remix-run/react';
77
import { db, chatId } from '~/lib/persistence/useChatHistory';
88
import { forkChat } from '~/lib/persistence/db';
99
import { toast } from 'react-toastify';
10-
import { useStore } from '@nanostores/react';
11-
import { profileStore } from '~/lib/stores/profile';
1210
import { forwardRef } from 'react';
1311
import type { ForwardedRef } from 'react';
1412
import type { ProviderInfo } from '~/types/model';
@@ -29,7 +27,6 @@ export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
2927
(props: MessagesProps, ref: ForwardedRef<HTMLDivElement> | undefined) => {
3028
const { id, isStreaming = false, messages = [] } = props;
3129
const location = useLocation();
32-
const profile = useStore(profileStore);
3330

3431
const handleRewind = (messageId: string) => {
3532
const searchParams = new URLSearchParams(location.search);
@@ -58,7 +55,6 @@ export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
5855
const { role, content, id: messageId, annotations } = message;
5956
const isUserMessage = role === 'user';
6057
const isFirst = index === 0;
61-
const isLast = index === messages.length - 1;
6258
const isHidden = annotations?.includes('hidden');
6359

6460
if (isHidden) {
@@ -68,28 +64,10 @@ export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
6864
return (
6965
<div
7066
key={index}
71-
className={classNames('flex gap-4 p-6 py-5 w-full rounded-[calc(0.75rem-1px)]', {
72-
'bg-bolt-elements-messages-background': isUserMessage || !isStreaming || (isStreaming && !isLast),
73-
'bg-gradient-to-b from-bolt-elements-messages-background from-30% to-transparent':
74-
isStreaming && isLast,
67+
className={classNames('flex gap-4 py-3 w-full rounded-lg', {
7568
'mt-4': !isFirst,
7669
})}
7770
>
78-
{isUserMessage && (
79-
<div className="flex items-center justify-center w-[40px] h-[40px] overflow-hidden bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-500 rounded-full shrink-0 self-start">
80-
{profile?.avatar ? (
81-
<img
82-
src={profile.avatar}
83-
alt={profile?.username || 'User'}
84-
className="w-full h-full object-cover"
85-
loading="eager"
86-
decoding="sync"
87-
/>
88-
) : (
89-
<div className="i-ph:user-fill text-2xl" />
90-
)}
91-
</div>
92-
)}
9371
<div className="grid grid-col-1 w-full">
9472
{isUserMessage ? (
9573
<UserMessage content={content} />

0 commit comments

Comments
 (0)