mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Revert "Pro 1364 ux and codebase improvements"
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
.BaseChat {
|
||||
&[data-chat-visible='false'] {
|
||||
--workbench-inner-width: 100%;
|
||||
--workbench-left: 0;
|
||||
|
||||
.Chat {
|
||||
--at-apply: bolt-ease-cubic-bezier;
|
||||
transition-property: transform, opacity;
|
||||
transition-duration: 0.3s;
|
||||
will-change: transform, opacity;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Chat {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.PromptEffectContainer {
|
||||
--prompt-container-offset: 50px;
|
||||
--prompt-line-stroke-width: 1px;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
inset: calc(var(--prompt-container-offset) / -2);
|
||||
width: calc(100% + var(--prompt-container-offset));
|
||||
height: calc(100% + var(--prompt-container-offset));
|
||||
}
|
||||
|
||||
.PromptEffectLine {
|
||||
width: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
|
||||
height: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
|
||||
x: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
|
||||
y: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
|
||||
rx: calc(8px - var(--prompt-line-stroke-width));
|
||||
fill: transparent;
|
||||
stroke-width: var(--prompt-line-stroke-width);
|
||||
stroke: url(#line-gradient);
|
||||
stroke-dasharray: 35px 65px;
|
||||
stroke-dashoffset: 10;
|
||||
}
|
||||
|
||||
.PromptShine {
|
||||
fill: url(#shine-gradient);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filterInput {
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* @ts-nocheck
|
||||
* Preventing TS checks with files presented in the video for a better presentation.
|
||||
*/
|
||||
import React, { type RefCallback, useCallback } from 'react';
|
||||
import { ClientOnly } from 'remix-utils/client-only';
|
||||
import { Menu } from '~/components/sidebar/Menu.client';
|
||||
import { Workbench } from '~/components/workbench/Workbench.client';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { Messages } from '~/components/chat/Messages.client';
|
||||
import { type Message } from '~/lib/persistence/message';
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { IntroSection } from '~/components/chat/BaseChat/components/IntroSection/IntroSection';
|
||||
import { SearchInput } from '~/components/chat/SearchInput/SearchInput';
|
||||
import { ChatPromptContainer } from '~/components/chat/BaseChat/components/ChatPromptContainer/ChatPromptContainer';
|
||||
import { useSpeechRecognition } from '~/hooks/useSpeechRecognition';
|
||||
import styles from './BaseChat.module.scss';
|
||||
import { ExamplePrompts } from '~/components/chat/ExamplePrompts';
|
||||
import { ExampleLibraryApps } from '~/components/app-library/ExampleLibraryApps';
|
||||
import type { RejectChangeData } from '~/components/chat/ApproveChange';
|
||||
import { type MessageInputProps } from '~/components/chat/MessageInput/MessageInput';
|
||||
|
||||
export const TEXTAREA_MIN_HEIGHT = 76;
|
||||
|
||||
interface BaseChatProps {
|
||||
textareaRef?: React.RefObject<HTMLTextAreaElement>;
|
||||
messageRef?: RefCallback<HTMLDivElement>;
|
||||
scrollRef?: RefCallback<HTMLDivElement>;
|
||||
showChat?: boolean;
|
||||
chatStarted?: boolean;
|
||||
hasPendingMessage?: boolean;
|
||||
pendingMessageStatus?: string;
|
||||
messages?: Message[];
|
||||
input?: string;
|
||||
handleStop?: () => void;
|
||||
sendMessage?: (messageInput?: string) => void;
|
||||
handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
uploadedFiles?: File[];
|
||||
setUploadedFiles?: (files: File[]) => void;
|
||||
imageDataList?: string[];
|
||||
setImageDataList?: (dataList: string[]) => void;
|
||||
onApproveChange?: (messageId: string) => void;
|
||||
onRejectChange?: (messageId: string, data: RejectChangeData) => void;
|
||||
}
|
||||
|
||||
type ExtendedMessage = Message & {
|
||||
repositoryId?: string;
|
||||
peanuts?: boolean;
|
||||
approved?: boolean;
|
||||
};
|
||||
|
||||
export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
(
|
||||
{
|
||||
textareaRef,
|
||||
messageRef,
|
||||
scrollRef,
|
||||
showChat = true,
|
||||
chatStarted = false,
|
||||
hasPendingMessage = false,
|
||||
pendingMessageStatus = '',
|
||||
input = '',
|
||||
handleInputChange,
|
||||
sendMessage,
|
||||
handleStop,
|
||||
uploadedFiles = [],
|
||||
setUploadedFiles,
|
||||
imageDataList = [],
|
||||
setImageDataList,
|
||||
messages,
|
||||
onApproveChange,
|
||||
onRejectChange,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
|
||||
const [rejectFormOpen, setRejectFormOpen] = React.useState(false);
|
||||
const [filterText, setFilterText] = React.useState('');
|
||||
|
||||
const onTranscriptChange = useCallback(
|
||||
(transcript: string) => {
|
||||
if (handleInputChange) {
|
||||
const syntheticEvent = {
|
||||
target: { value: transcript },
|
||||
} as React.ChangeEvent<HTMLTextAreaElement>;
|
||||
handleInputChange(syntheticEvent);
|
||||
}
|
||||
},
|
||||
[handleInputChange],
|
||||
);
|
||||
|
||||
const { isListening, startListening, stopListening, abortListening } = useSpeechRecognition({
|
||||
onTranscriptChange,
|
||||
});
|
||||
|
||||
const handleSendMessage = (event: React.UIEvent, messageInput?: string) => {
|
||||
if (sendMessage) {
|
||||
sendMessage(messageInput);
|
||||
abortListening();
|
||||
|
||||
if (handleInputChange) {
|
||||
const syntheticEvent = {
|
||||
target: { value: '' },
|
||||
} as React.ChangeEvent<HTMLTextAreaElement>;
|
||||
handleInputChange(syntheticEvent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const approveChangeMessageId = (() => {
|
||||
if (hasPendingMessage || !messages) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i] as ExtendedMessage;
|
||||
if (message.repositoryId && message.peanuts) {
|
||||
return message.approved ? undefined : message.id;
|
||||
}
|
||||
if (message.role === 'user') {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
const messageInputProps = {
|
||||
textareaRef,
|
||||
input,
|
||||
handleInputChange,
|
||||
handleSendMessage,
|
||||
handleStop,
|
||||
hasPendingMessage,
|
||||
chatStarted,
|
||||
uploadedFiles,
|
||||
setUploadedFiles,
|
||||
imageDataList,
|
||||
setImageDataList,
|
||||
isListening,
|
||||
onStartListening: startListening,
|
||||
onStopListening: stopListening,
|
||||
minHeight: TEXTAREA_MIN_HEIGHT,
|
||||
maxHeight: TEXTAREA_MAX_HEIGHT,
|
||||
};
|
||||
|
||||
const baseChat = (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(styles.BaseChat, 'relative flex h-full w-full overflow-hidden p-6')}
|
||||
data-chat-visible={showChat}
|
||||
>
|
||||
<ClientOnly>{() => <Menu />}</ClientOnly>
|
||||
<div ref={scrollRef} className="flex flex-col lg:flex-row w-full h-full">
|
||||
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
|
||||
{!chatStarted && <IntroSection />}
|
||||
<div
|
||||
className={classNames('px-2 sm:px-6', {
|
||||
'h-full flex flex-col': chatStarted,
|
||||
})}
|
||||
>
|
||||
<ClientOnly>
|
||||
{() => {
|
||||
return chatStarted ? (
|
||||
<Messages
|
||||
ref={messageRef}
|
||||
className="flex flex-col w-full flex-1 max-w-chat pb-6 mx-auto z-1 overflow-y-auto"
|
||||
messages={messages}
|
||||
hasPendingMessage={hasPendingMessage}
|
||||
pendingMessageStatus={pendingMessageStatus}
|
||||
/>
|
||||
) : null;
|
||||
}}
|
||||
</ClientOnly>
|
||||
<ChatPromptContainer
|
||||
chatStarted={chatStarted}
|
||||
uploadedFiles={uploadedFiles}
|
||||
setUploadedFiles={setUploadedFiles!}
|
||||
imageDataList={imageDataList}
|
||||
setImageDataList={setImageDataList!}
|
||||
approveChangeMessageId={approveChangeMessageId}
|
||||
rejectFormOpen={rejectFormOpen}
|
||||
setRejectFormOpen={setRejectFormOpen}
|
||||
onApproveChange={onApproveChange}
|
||||
onRejectChange={onRejectChange}
|
||||
messageInputProps={messageInputProps as MessageInputProps}
|
||||
/>
|
||||
</div>
|
||||
{!chatStarted && (
|
||||
<>
|
||||
{ExamplePrompts((event: React.UIEvent, messageInput?: string) => {
|
||||
if (hasPendingMessage) {
|
||||
handleStop?.();
|
||||
return;
|
||||
}
|
||||
handleSendMessage(event, messageInput);
|
||||
})}
|
||||
<div className="text-2xl lg:text-4xl font-bold text-bolt-elements-textPrimary mt-8 mb-4 animate-fade-in text-center max-w-chat mx-auto">
|
||||
Arboretum
|
||||
</div>
|
||||
<div className="text-bolt-elements-textSecondary text-center max-w-chat mx-auto">
|
||||
Browse these auto-generated apps for a place to start
|
||||
</div>
|
||||
<SearchInput onSearch={setFilterText} />
|
||||
<ExampleLibraryApps filterText={filterText} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ClientOnly>{() => <Workbench chatStarted={chatStarted} />}</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <Tooltip.Provider delayDuration={200}>{baseChat}</Tooltip.Provider>;
|
||||
},
|
||||
);
|
||||
@@ -1,101 +0,0 @@
|
||||
import React from 'react';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import FilePreview from '~/components/chat/FilePreview';
|
||||
import { ScreenshotStateManager } from '~/components/chat/ScreenshotStateManager';
|
||||
import { ClientOnly } from 'remix-utils/client-only';
|
||||
import ApproveChange from '~/components/chat/ApproveChange';
|
||||
import { MessageInput } from '~/components/chat/MessageInput/MessageInput';
|
||||
import styles from '~/components/chat/BaseChat/BaseChat.module.scss';
|
||||
|
||||
interface ChatPromptContainerProps {
|
||||
chatStarted: boolean;
|
||||
uploadedFiles: File[];
|
||||
setUploadedFiles: (files: File[]) => void;
|
||||
imageDataList: string[];
|
||||
setImageDataList: (dataList: string[]) => void;
|
||||
approveChangeMessageId?: string;
|
||||
rejectFormOpen: boolean;
|
||||
setRejectFormOpen: (open: boolean) => void;
|
||||
onApproveChange?: (messageId: string) => void;
|
||||
onRejectChange?: (messageId: string, data: any) => void;
|
||||
messageInputProps: Partial<React.ComponentProps<typeof MessageInput>>;
|
||||
}
|
||||
|
||||
export const ChatPromptContainer: React.FC<ChatPromptContainerProps> = ({
|
||||
chatStarted,
|
||||
uploadedFiles,
|
||||
setUploadedFiles,
|
||||
imageDataList,
|
||||
setImageDataList,
|
||||
approveChangeMessageId,
|
||||
rejectFormOpen,
|
||||
setRejectFormOpen,
|
||||
onApproveChange,
|
||||
onRejectChange,
|
||||
messageInputProps,
|
||||
}) => {
|
||||
return (
|
||||
<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',
|
||||
{
|
||||
'sticky bottom-2': chatStarted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<svg className={classNames(styles.PromptEffectContainer)}>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="line-gradient"
|
||||
x1="20%"
|
||||
y1="0%"
|
||||
x2="-14%"
|
||||
y2="10%"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(-45)"
|
||||
>
|
||||
<stop offset="0%" stopColor="#b44aff" stopOpacity="0%"></stop>
|
||||
<stop offset="40%" stopColor="#b44aff" stopOpacity="80%"></stop>
|
||||
<stop offset="50%" stopColor="#b44aff" stopOpacity="80%"></stop>
|
||||
<stop offset="100%" stopColor="#b44aff" stopOpacity="0%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="shine-gradient">
|
||||
<stop offset="0%" stopColor="white" stopOpacity="0%"></stop>
|
||||
<stop offset="40%" stopColor="#ffffff" stopOpacity="80%"></stop>
|
||||
<stop offset="50%" stopColor="#ffffff" stopOpacity="80%"></stop>
|
||||
<stop offset="100%" stopColor="white" stopOpacity="0%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect className={classNames(styles.PromptEffectLine)} pathLength="100" strokeLinecap="round"></rect>
|
||||
<rect className={classNames(styles.PromptShine)} x="48" y="24" width="70" height="1"></rect>
|
||||
</svg>
|
||||
<FilePreview
|
||||
files={uploadedFiles}
|
||||
imageDataList={imageDataList}
|
||||
onRemove={(index) => {
|
||||
setUploadedFiles(uploadedFiles.filter((_, i) => i !== index));
|
||||
setImageDataList(imageDataList.filter((_, i) => i !== index));
|
||||
}}
|
||||
/>
|
||||
<ClientOnly>
|
||||
{() => (
|
||||
<ScreenshotStateManager
|
||||
setUploadedFiles={setUploadedFiles}
|
||||
setImageDataList={setImageDataList}
|
||||
uploadedFiles={uploadedFiles}
|
||||
imageDataList={imageDataList}
|
||||
/>
|
||||
)}
|
||||
</ClientOnly>
|
||||
{approveChangeMessageId && (
|
||||
<ApproveChange
|
||||
rejectFormOpen={rejectFormOpen}
|
||||
setRejectFormOpen={setRejectFormOpen}
|
||||
onApprove={() => onApproveChange?.(approveChangeMessageId)}
|
||||
onReject={(data) => onRejectChange?.(approveChangeMessageId, data)}
|
||||
/>
|
||||
)}
|
||||
{!rejectFormOpen && <MessageInput {...messageInputProps} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export const IntroSection: React.FC = () => {
|
||||
return (
|
||||
<div id="intro" className="mt-[16vh] max-w-chat 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">
|
||||
Get what you want
|
||||
</h1>
|
||||
<p className="text-md lg:text-xl mb-8 text-bolt-elements-textSecondary animate-fade-in animation-delay-200">
|
||||
Write, test, and fix your app all from one prompt
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user