diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 13546f66..94dc76aa 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -39,6 +39,9 @@ import type { ActionRunner } from '~/lib/runtime/action-runner'; import { LOCAL_PROVIDERS } from '~/lib/stores/settings'; import { SupabaseChatAlert } from '~/components/chat/SupabaseAlert'; import { SupabaseConnection } from './SupabaseConnection'; +import { ExpoQrModal } from '~/components/workbench/ExpoQrModal'; +import { expoUrlAtom } from '~/stores/qrCodeStore'; +import { useStore } from '@nanostores/react'; const TEXTAREA_MIN_HEIGHT = 76; @@ -130,6 +133,15 @@ export const BaseChat = React.forwardRef( const [transcript, setTranscript] = useState(''); const [isModelLoading, setIsModelLoading] = useState('all'); const [progressAnnotations, setProgressAnnotations] = useState([]); + const expoUrl = useStore(expoUrlAtom); + const [qrModalOpen, setQrModalOpen] = useState(false); + + useEffect(() => { + if (expoUrl) { + setQrModalOpen(true); + } + }, [expoUrl]); + useEffect(() => { if (data) { const progressList = data.filter( @@ -622,6 +634,7 @@ export const BaseChat = React.forwardRef( ) : null} + setQrModalOpen(false)} /> diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 4f976837..2ddd358b 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -249,6 +249,8 @@ export const ChatImpl = memo( }); }, [messages, isLoading, parseMessages]); + console.log('messages', messages); + const scrollTextArea = () => { const textarea = textareaRef.current; diff --git a/app/components/ui/Dialog.tsx b/app/components/ui/Dialog.tsx index 46af8785..ed072ddb 100644 --- a/app/components/ui/Dialog.tsx +++ b/app/components/ui/Dialog.tsx @@ -116,7 +116,7 @@ export const Dialog = memo(({ children, className, showCloseButton = true, onClo void; +} + +export const ExpoQrModal: React.FC = ({ open, onClose }) => { + const expoUrl = useStore(expoUrlAtom); + + return ( + !v && onClose()}> + +
+
+ + Preview on your own mobile device + + + Scan this QR code with the Expo Go app on your mobile device to open your project. + +
+ {expoUrl ? ( +
+ +
+ ) : ( +
No Expo URL detected.
+ )} +
+
+
+
+ ); +}; diff --git a/app/components/workbench/Preview.tsx b/app/components/workbench/Preview.tsx index aaf2b9d4..de7588f8 100644 --- a/app/components/workbench/Preview.tsx +++ b/app/components/workbench/Preview.tsx @@ -4,6 +4,8 @@ import { IconButton } from '~/components/ui/IconButton'; import { workbenchStore } from '~/lib/stores/workbench'; import { PortDropdown } from './PortDropdown'; import { ScreenshotSelector } from './ScreenshotSelector'; +import { expoUrlAtom } from '~/stores/qrCodeStore'; +import { ExpoQrModal } from '~/components/workbench/ExpoQrModal'; type ResizeSide = 'left' | 'right' | null; @@ -53,7 +55,6 @@ export const Preview = memo(() => { const [activePreviewIndex, setActivePreviewIndex] = useState(0); const [isPortDropdownOpen, setIsPortDropdownOpen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); - const [isPreviewOnly, setIsPreviewOnly] = useState(false); const hasSelectedPreview = useRef(false); const previews = useStore(workbenchStore.previews); const activePreview = previews[activePreviewIndex]; @@ -86,6 +87,8 @@ export const Preview = memo(() => { const [isLandscape, setIsLandscape] = useState(false); const [showDeviceFrame, setShowDeviceFrame] = useState(true); const [showDeviceFrameInPreview, setShowDeviceFrameInPreview] = useState(false); + const expoUrl = useStore(expoUrlAtom); + const [isExpoQrModalOpen, setIsExpoQrModalOpen] = useState(false); useEffect(() => { if (!activePreview) { @@ -636,10 +639,7 @@ export const Preview = memo(() => { }, [showDeviceFrameInPreview]); return ( -
+
{isPortDropdownOpen && (
setIsPortDropdownOpen(false)} /> )} @@ -693,6 +693,10 @@ export const Preview = memo(() => { title={isDeviceModeOn ? 'Switch to Responsive Mode' : 'Switch to Device Mode'} /> + {expoUrl && setIsExpoQrModalOpen(true)} title="Show QR" />} + + setIsExpoQrModalOpen(false)} /> + {isDeviceModeOn && ( <> { )} - setIsPreviewOnly(!isPreviewOnly)} - title={isPreviewOnly ? 'Show Full Interface' : 'Show Preview Only'} - /> - - {/* Simple preview button */} - { - if (!activePreview?.baseUrl) { - console.warn('[Preview] No active preview available'); - return; - } - - const match = activePreview.baseUrl.match( - /^https?:\/\/([^.]+)\.local-credentialless\.webcontainer-api\.io/, - ); - - if (!match) { - console.warn('[Preview] Invalid WebContainer URL:', activePreview.baseUrl); - return; - } - - const previewId = match[1]; - const previewUrl = `/webcontainer/preview/${previewId}`; - - // Open in a new window with simple parameters - window.open( - previewUrl, - `preview-${previewId}`, - 'width=1280,height=720,menubar=no,toolbar=no,location=no,status=no,resizable=yes', - ); - }} - title="Open Preview in New Window" - /> -
openInNewWindow(selectedWindowSize)} - title={`Open Preview in ${selectedWindowSize.name} Window`} - /> - setIsWindowSizeDropdownOpen(!isWindowSizeDropdownOpen)} - className="ml-1" - title="Select Window Size" + title="New Window Options" /> {isWindowSizeDropdownOpen && ( @@ -770,7 +731,7 @@ export const Preview = memo(() => {
- Device Options + Window Options
+
Show Device Frame