From d5a29c24277975db68cb0c6d0ff8e8b2f39ae9a5 Mon Sep 17 00:00:00 2001 From: Dominic Elm Date: Wed, 14 Aug 2024 11:08:52 +0200 Subject: [PATCH] feat(layout): allow to minimize chat (#35) --- packages/bolt/README.md | 3 +- .../app/components/chat/BaseChat.module.scss | 19 +++ .../bolt/app/components/chat/BaseChat.tsx | 25 ++-- .../bolt/app/components/chat/Chat.client.tsx | 6 +- .../bolt/app/components/header/Header.tsx | 18 ++- .../header/HeaderActionButtons.client.tsx | 72 +++++++++++ .../header/OpenStackBlitz.client.tsx | 42 +++++-- .../app/components/sidebar/Menu.client.tsx | 2 +- .../app/components/ui/PanelHeaderButton.tsx | 2 +- packages/bolt/app/components/ui/Slider.tsx | 5 +- .../app/components/workbench/EditorPanel.tsx | 11 +- .../components/workbench/FileTreePanel.tsx | 29 ----- .../bolt/app/components/workbench/Preview.tsx | 6 +- .../components/workbench/Workbench.client.tsx | 113 ++++++++++-------- packages/bolt/app/lib/stores/chat.ts | 1 + packages/bolt/app/styles/variables.scss | 9 +- packages/bolt/app/styles/z-index.scss | 8 ++ packages/bolt/uno.config.ts | 5 +- 18 files changed, 262 insertions(+), 114 deletions(-) create mode 100644 packages/bolt/app/components/chat/BaseChat.module.scss create mode 100644 packages/bolt/app/components/header/HeaderActionButtons.client.tsx delete mode 100644 packages/bolt/app/components/workbench/FileTreePanel.tsx diff --git a/packages/bolt/README.md b/packages/bolt/README.md index c4cedb0..c96d37d 100644 --- a/packages/bolt/README.md +++ b/packages/bolt/README.md @@ -30,10 +30,11 @@ pnpm install ANTHROPIC_API_KEY=XXX ``` -Optionally, you an set the debug level: +Optionally, you an set the debug level or disable authentication: ``` VITE_LOG_LEVEL=debug +VITE_DISABLE_AUTH=1 ``` If you want to run authentication against a local StackBlitz instance, add: diff --git a/packages/bolt/app/components/chat/BaseChat.module.scss b/packages/bolt/app/components/chat/BaseChat.module.scss new file mode 100644 index 0000000..3d6ed4c --- /dev/null +++ b/packages/bolt/app/components/chat/BaseChat.module.scss @@ -0,0 +1,19 @@ +.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; +} diff --git a/packages/bolt/app/components/chat/BaseChat.tsx b/packages/bolt/app/components/chat/BaseChat.tsx index c732065..b3820e1 100644 --- a/packages/bolt/app/components/chat/BaseChat.tsx +++ b/packages/bolt/app/components/chat/BaseChat.tsx @@ -8,10 +8,13 @@ import { classNames } from '~/utils/classNames'; import { Messages } from './Messages.client'; import { SendButton } from './SendButton.client'; +import styles from './BaseChat.module.scss'; + interface BaseChatProps { textareaRef?: React.RefObject | undefined; messageRef?: RefCallback | undefined; scrollRef?: RefCallback | undefined; + showChat?: boolean; chatStarted?: boolean; isStreaming?: boolean; messages?: Message[]; @@ -40,6 +43,7 @@ export const BaseChat = React.forwardRef( textareaRef, messageRef, scrollRef, + showChat = true, chatStarted = false, isStreaming = false, enhancingPrompt = false, @@ -56,12 +60,19 @@ export const BaseChat = React.forwardRef( const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200; return ( -
+
{() => }
-
+
{!chatStarted && ( -
+

Where ideas begin

@@ -71,7 +82,7 @@ export const BaseChat = React.forwardRef(
)}
@@ -80,7 +91,7 @@ export const BaseChat = React.forwardRef( return chatStarted ? ( @@ -88,7 +99,7 @@ export const BaseChat = React.forwardRef( }}
@@ -174,7 +185,7 @@ export const BaseChat = React.forwardRef(
{!chatStarted && ( -
+
{EXAMPLE_PROMPTS.map((examplePrompt, index) => { return ( diff --git a/packages/bolt/app/components/chat/Chat.client.tsx b/packages/bolt/app/components/chat/Chat.client.tsx index bc2ea31..e421a08 100644 --- a/packages/bolt/app/components/chat/Chat.client.tsx +++ b/packages/bolt/app/components/chat/Chat.client.tsx @@ -1,8 +1,10 @@ +import { useStore } from '@nanostores/react'; import type { Message } from 'ai'; import { useChat } from 'ai/react'; import { useAnimate } from 'framer-motion'; import { memo, useEffect, useRef, useState } from 'react'; import { cssTransition, toast, ToastContainer } from 'react-toastify'; +import { AnalyticsAction, AnalyticsTrackEvent, sendAnalyticsEvent } from '~/lib/analytics'; import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks'; import { useChatHistory } from '~/lib/persistence'; import { chatStore } from '~/lib/stores/chat'; @@ -11,7 +13,6 @@ import { fileModificationsToHTML } from '~/utils/diff'; import { cubicEasingFn } from '~/utils/easings'; import { createScopedLogger, renderLogger } from '~/utils/logger'; import { BaseChat } from './BaseChat'; -import { sendAnalyticsEvent, AnalyticsTrackEvent, AnalyticsAction } from '~/lib/analytics'; const toastAnimation = cssTransition({ enter: 'animated fadeInRight', @@ -71,6 +72,8 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp const [chatStarted, setChatStarted] = useState(initialMessages.length > 0); + const { showChat } = useStore(chatStore); + const [animationScope, animate] = useAnimate(); const { messages, isLoading, input, handleInputChange, setInput, stop, append } = useChat({ @@ -213,6 +216,7 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp ref={animationScope} textareaRef={textareaRef} input={input} + showChat={showChat} chatStarted={chatStarted} isStreaming={isLoading} enhancingPrompt={enhancingPrompt} diff --git a/packages/bolt/app/components/header/Header.tsx b/packages/bolt/app/components/header/Header.tsx index 32af432..2aa4d48 100644 --- a/packages/bolt/app/components/header/Header.tsx +++ b/packages/bolt/app/components/header/Header.tsx @@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react'; import { ClientOnly } from 'remix-utils/client-only'; import { chatStore } from '~/lib/stores/chat'; import { classNames } from '~/utils/classNames'; -import { OpenStackBlitz } from './OpenStackBlitz.client'; +import { HeaderActionButtons } from './HeaderActionButtons.client'; export function Header() { const chat = useStore(chatStore); @@ -17,14 +17,22 @@ export function Header() { }, )} > -
+
+ -
- {() => } -
+
+ {chat.started && ( + + {() => ( +
+ +
+ )} +
+ )} ); } diff --git a/packages/bolt/app/components/header/HeaderActionButtons.client.tsx b/packages/bolt/app/components/header/HeaderActionButtons.client.tsx new file mode 100644 index 0000000..80ed06e --- /dev/null +++ b/packages/bolt/app/components/header/HeaderActionButtons.client.tsx @@ -0,0 +1,72 @@ +import { useStore } from '@nanostores/react'; +import { chatStore } from '~/lib/stores/chat'; +import { workbenchStore } from '~/lib/stores/workbench'; +import { classNames } from '~/utils/classNames'; +import { OpenStackBlitz } from './OpenStackBlitz.client'; + +interface HeaderActionButtonsProps {} + +export function HeaderActionButtons({}: HeaderActionButtonsProps) { + const showWorkbench = useStore(workbenchStore.showWorkbench); + const { showChat } = useStore(chatStore); + + const canHideChat = showWorkbench || !showChat; + + return ( +
+
+ +
+ +
+
+ +
+
+ ); +} + +interface ButtonProps { + active?: boolean; + disabled?: boolean; + children?: any; + onClick?: VoidFunction; +} + +function Button({ active = false, disabled = false, children, onClick }: ButtonProps) { + return ( + + ); +} diff --git a/packages/bolt/app/components/header/OpenStackBlitz.client.tsx b/packages/bolt/app/components/header/OpenStackBlitz.client.tsx index a1c4c0b..92c5ba3 100644 --- a/packages/bolt/app/components/header/OpenStackBlitz.client.tsx +++ b/packages/bolt/app/components/header/OpenStackBlitz.client.tsx @@ -1,10 +1,11 @@ -import path from 'path'; import { useStore } from '@nanostores/react'; import sdk from '@stackblitz/sdk'; +import path from 'path'; +import { memo, useCallback, useEffect, useState } from 'react'; import type { FileMap } from '~/lib/stores/files'; import { workbenchStore, type ArtifactState } from '~/lib/stores/workbench'; +import { classNames } from '~/utils/classNames'; import { WORK_DIR } from '~/utils/constants'; -import { memo, useCallback, useEffect, useState } from 'react'; // extract relative path and content from file, wrapped in array for flatMap use const extractContent = ([file, value]: [string, FileMap[string]]) => { @@ -47,6 +48,8 @@ const useFirstArtifact = (): [boolean, ArtifactState | undefined] => { export const OpenStackBlitz = memo(() => { const [artifactLoaded, artifact] = useFirstArtifact(); + const disabled = !artifactLoaded; + const handleClick = useCallback(() => { if (!artifact) { return; @@ -66,13 +69,34 @@ export const OpenStackBlitz = memo(() => { }); }, [artifact]); - if (!artifactLoaded) { - return null; - } - return ( - - Open in StackBlitz - + ); }); diff --git a/packages/bolt/app/components/sidebar/Menu.client.tsx b/packages/bolt/app/components/sidebar/Menu.client.tsx index 1403a02..7b00200 100644 --- a/packages/bolt/app/components/sidebar/Menu.client.tsx +++ b/packages/bolt/app/components/sidebar/Menu.client.tsx @@ -50,7 +50,7 @@ export function Menu() { }, [open]); useEffect(() => { - const enterThreshold = 80; + const enterThreshold = 40; const exitThreshold = 40; function onMouseMove(event: MouseEvent) { diff --git a/packages/bolt/app/components/ui/PanelHeaderButton.tsx b/packages/bolt/app/components/ui/PanelHeaderButton.tsx index 35b889a..9faea1c 100644 --- a/packages/bolt/app/components/ui/PanelHeaderButton.tsx +++ b/packages/bolt/app/components/ui/PanelHeaderButton.tsx @@ -14,7 +14,7 @@ export const PanelHeaderButton = memo( return (