diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 1c6eca2f..a13cdcc2 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -422,8 +422,16 @@ export const ChatImpl = memo( chatStore.setKey('aborted', false); - if (modifiedFiles !== undefined) { - const userUpdateArtifact = filesToArtifacts(modifiedFiles, `${Date.now()}`); + if (modifiedFiles) { + // Convert the modifiedFiles object to an array of [filePath, file] entries + const modifiedFilesWithContent = Object.entries(modifiedFiles).reduce>((acc, [filePath, file]) => { + if (file?.type === 'file') { + acc[filePath] = { content: file.content }; + } + return acc; + }, {}); + + const userUpdateArtifact = filesToArtifacts(modifiedFilesWithContent, `${Date.now()}`); append({ role: 'user', content: [ diff --git a/app/components/workbench/Preview.tsx b/app/components/workbench/Preview.tsx index 0d415716..49ae73f5 100644 --- a/app/components/workbench/Preview.tsx +++ b/app/components/workbench/Preview.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState, useMemo } from 'react'; import { useStore } from '@nanostores/react'; import { IconButton } from '~/components/ui/IconButton'; import { workbenchStore } from '~/lib/stores/workbench'; @@ -6,6 +6,7 @@ import { PortDropdown } from './PortDropdown'; import { ScreenshotSelector } from './ScreenshotSelector'; import { expoUrlAtom } from '~/lib/stores/qrCodeStore'; import { ExpoQrModal } from '~/components/workbench/ExpoQrModal'; +import type { PreviewInfo } from '~/lib/stores/previews'; type ResizeSide = 'left' | 'right' | null; @@ -56,16 +57,33 @@ export const Preview = memo(() => { const [isPortDropdownOpen, setIsPortDropdownOpen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const hasSelectedPreview = useRef(false); - const previews = useStore(workbenchStore.previews); + + const rawPreviews = useStore(workbenchStore.previews); + + const previews = useMemo(() => { + if (rawPreviews && typeof rawPreviews === 'object' && !Array.isArray(rawPreviews)) { + return Object.values(rawPreviews) + .map((v) => v as unknown) + .filter( + (v): v is PreviewInfo => + typeof v === 'object' && + v !== null && + typeof (v as any).port === 'number' && + typeof (v as any).ready === 'boolean' && + typeof (v as any).baseUrl === 'string', + ); + } + + return []; + }, [rawPreviews]); + const activePreview = previews[activePreviewIndex]; const [displayPath, setDisplayPath] = useState('/'); const [iframeUrl, setIframeUrl] = useState(); const [isSelectionMode, setIsSelectionMode] = useState(false); - // Toggle between responsive mode and device mode const [isDeviceModeOn, setIsDeviceModeOn] = useState(false); - // Use percentage for width const [widthPercent, setWidthPercent] = useState(37.5); const [currentWidth, setCurrentWidth] = useState(0); @@ -78,7 +96,6 @@ export const Preview = memo(() => { pointerId: null as number | null, }); - // Reduce scaling factor to make resizing less sensitive const SCALING_FACTOR = 1; const [isWindowSizeDropdownOpen, setIsWindowSizeDropdownOpen] = useState(false); @@ -103,8 +120,11 @@ export const Preview = memo(() => { }, [activePreview]); const findMinPortIndex = useCallback( - (minIndex: number, preview: { port: number }, index: number, array: { port: number }[]) => { - return preview.port < array[minIndex].port ? index : minIndex; + (minIndex: number, preview: PreviewInfo, index: number, array: PreviewInfo[]) => { + const currentPort = typeof preview.port === 'string' ? parseInt(preview.port, 10) : preview.port; + const minPort = typeof array[minIndex].port === 'string' ? parseInt(array[minIndex].port, 10) : array[minIndex].port; + + return currentPort < minPort ? index : minIndex; }, [], ); @@ -454,11 +474,11 @@ export const Preview = memo(() => { overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } - + .device-container { position: relative; } - + .device-name { position: absolute; top: -30px; @@ -468,7 +488,7 @@ export const Preview = memo(() => { font-size: 14px; color: #333; } - + .device-frame { position: relative; border-radius: ${frameRadius}; @@ -477,7 +497,7 @@ export const Preview = memo(() => { box-shadow: 0 10px 30px rgba(0,0,0,0.2); overflow: hidden; } - + /* Notch */ .device-frame:before { content: ''; @@ -491,7 +511,7 @@ export const Preview = memo(() => { border-radius: 4px; z-index: 2; } - + /* Home button */ .device-frame:after { content: ''; @@ -505,7 +525,7 @@ export const Preview = memo(() => { border-radius: 50%; z-index: 2; } - + iframe { border: none; width: ${width}px;