From 9239eda5b2e2fa5da86b01929f1134d3aaedfdf1 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 20 Jan 2025 15:59:37 -0800 Subject: [PATCH 01/26] Add prompt box --- .../routes/projects/PromptCreation/index.tsx | 55 +++++++++++++++++++ apps/studio/src/routes/projects/index.tsx | 12 ++-- 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 apps/studio/src/routes/projects/PromptCreation/index.tsx diff --git a/apps/studio/src/routes/projects/PromptCreation/index.tsx b/apps/studio/src/routes/projects/PromptCreation/index.tsx new file mode 100644 index 000000000..3c1c1c469 --- /dev/null +++ b/apps/studio/src/routes/projects/PromptCreation/index.tsx @@ -0,0 +1,55 @@ +import { Button } from '@onlook/ui/button'; +import { Card, CardContent, CardHeader } from '@onlook/ui/card'; +import { Icons } from '@onlook/ui/icons'; +import { Input } from '@onlook/ui/input'; + +export const PromptCreation = () => { + return ( +
+ {/* Background placeholder */} +
+ {/* Logo */} +
+ +
+ {/* Content */} + + +

What kind of website do you want to make?

+

+ Paste a link, imagery, or more as inspiration for your next site +

+
+ +
+ +
+ + + +
+
+
+
+
+ ); +}; + +export default PromptCreation; diff --git a/apps/studio/src/routes/projects/index.tsx b/apps/studio/src/routes/projects/index.tsx index b9cabf1c1..698fc523a 100644 --- a/apps/studio/src/routes/projects/index.tsx +++ b/apps/studio/src/routes/projects/index.tsx @@ -1,9 +1,10 @@ import { sendAnalytics } from '@/lib/utils'; import type { CreateMethod } from '@/routes/projects/helpers'; import { useState } from 'react'; -import ProjectsTab from './ProjectsTab'; -import CreateProject from './ProjectsTab/Create'; -import SettingsTab from './SettingsTab'; +// import ProjectsTab from './ProjectsTab'; +// import CreateProject from './ProjectsTab/Create'; +// import SettingsTab from './SettingsTab'; +import PromptCreation from './PromptCreation'; import TopBar from './TopBar'; export enum ProjectTabs { @@ -26,7 +27,8 @@ export default function Projects() {
- {createMethod ? ( + + {/* {createMethod ? ( ) : ( <> @@ -37,7 +39,7 @@ export default function Projects() { )} - )} + )} */}
); From b8707ccae74550d7a6ab3de3275cb673b49aa5f1 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 20 Jan 2025 16:03:18 -0800 Subject: [PATCH 02/26] Submit --- .../routes/projects/PromptCreation/index.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/studio/src/routes/projects/PromptCreation/index.tsx b/apps/studio/src/routes/projects/PromptCreation/index.tsx index 3c1c1c469..76aadf062 100644 --- a/apps/studio/src/routes/projects/PromptCreation/index.tsx +++ b/apps/studio/src/routes/projects/PromptCreation/index.tsx @@ -2,8 +2,15 @@ import { Button } from '@onlook/ui/button'; import { Card, CardContent, CardHeader } from '@onlook/ui/card'; import { Icons } from '@onlook/ui/icons'; import { Input } from '@onlook/ui/input'; +import { useState } from 'react'; export const PromptCreation = () => { + const [inputValue, setInputValue] = useState(''); + + const handleSubmit = (value: string) => { + console.log('Submitted value:', value); + }; + return (
{/* Background placeholder */} @@ -25,6 +32,13 @@ export const PromptCreation = () => { setInputValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleSubmit(inputValue); + } + }} />
-
From 3a82759946fb0bc82bfdfab155774c670b36f5fe Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 20 Jan 2025 18:46:48 -0800 Subject: [PATCH 03/26] Saving --- apps/studio/src/routes/projects/PromptCreation/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/src/routes/projects/PromptCreation/index.tsx b/apps/studio/src/routes/projects/PromptCreation/index.tsx index 76aadf062..0622e1fec 100644 --- a/apps/studio/src/routes/projects/PromptCreation/index.tsx +++ b/apps/studio/src/routes/projects/PromptCreation/index.tsx @@ -8,7 +8,7 @@ export const PromptCreation = () => { const [inputValue, setInputValue] = useState(''); const handleSubmit = (value: string) => { - console.log('Submitted value:', value); + console.log(value); }; return ( From 3fe142199e7ec1892f6dc4798cfd6a28d289b7d2 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 20 Jan 2025 21:11:30 -0800 Subject: [PATCH 04/26] Restyle start prompt --- .../routes/projects/PromptCreation/index.tsx | 128 ++++++++++++++---- apps/studio/src/routes/projects/index.tsx | 2 +- 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/apps/studio/src/routes/projects/PromptCreation/index.tsx b/apps/studio/src/routes/projects/PromptCreation/index.tsx index 0622e1fec..ffe4117d9 100644 --- a/apps/studio/src/routes/projects/PromptCreation/index.tsx +++ b/apps/studio/src/routes/projects/PromptCreation/index.tsx @@ -2,35 +2,101 @@ import { Button } from '@onlook/ui/button'; import { Card, CardContent, CardHeader } from '@onlook/ui/card'; import { Icons } from '@onlook/ui/icons'; import { Input } from '@onlook/ui/input'; +import { cn } from '@onlook/ui/utils'; import { useState } from 'react'; export const PromptCreation = () => { const [inputValue, setInputValue] = useState(''); + const [isDragging, setIsDragging] = useState(false); + const [selectedImages, setSelectedImages] = useState([]); const handleSubmit = (value: string) => { console.log(value); }; + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + + const files = Array.from(e.dataTransfer.files); + const imageFiles = files.filter((file) => file.type.startsWith('image/')); + + if (imageFiles.length > 0) { + // Handle the dropped image files + setSelectedImages([...selectedImages, ...imageFiles]); + } + }; + + const handleFileSelect = (e: React.ChangeEvent) => { + const files = Array.from(e.target.files || []); + const imageFiles = files.filter((file) => file.type.startsWith('image/')); + + if (imageFiles.length > 0) { + // Handle the selected image files + setSelectedImages([...selectedImages, ...imageFiles]); + } + }; + + const handleRemoveImage = (file: File) => { + setSelectedImages(selectedImages.filter((f) => f !== file)); + }; + return ( -
+
{/* Background placeholder */} -
- {/* Logo */} -
- -
+ {/*
*/} {/* Content */} - + -

What kind of website do you want to make?

+

+ What kind of website do you want to make? +

Paste a link, imagery, or more as inspiration for your next site

-
+
+ {selectedImages.length > 0 && ( +
+ {selectedImages.map((file, index) => ( +
+ {file.name} + +
+ ))} +
+ )} setInputValue(e.target.value)} @@ -41,22 +107,34 @@ export const PromptCreation = () => { }} />
+
+ + +
- -
diff --git a/packages/models/src/constants/ipc.ts b/packages/models/src/constants/ipc.ts index e97526a8a..b67cbf32b 100644 --- a/packages/models/src/constants/ipc.ts +++ b/packages/models/src/constants/ipc.ts @@ -84,6 +84,8 @@ export enum MainChannels { SETUP_PROJECT = 'setup-project', SETUP_PROJECT_CALLBACK = 'setup-project-callback', INSTALL_PROJECT_DEPENDENCIES = 'install-project-dependencies', + CREATE_NEW_PROJECT_PROMPT = 'create-new-project-prompt', + CREATE_NEW_PROJECT_PROMPT_CALLBACK = 'create-new-project-prompt-callback', // Chat SEND_CHAT_MESSAGES_STREAM = 'send-chat-messages-stream', From 741a4548859b7fe06f70c9ad35ccbd04cd84acca Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 20 Jan 2025 22:47:27 -0800 Subject: [PATCH 07/26] Parallel processes --- apps/studio/electron/main/create/index.ts | 46 ++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/apps/studio/electron/main/create/index.ts b/apps/studio/electron/main/create/index.ts index f4071db10..0230c8513 100644 --- a/apps/studio/electron/main/create/index.ts +++ b/apps/studio/electron/main/create/index.ts @@ -1,6 +1,50 @@ import type { ImageMessageContext } from '@onlook/models/chat'; -export function createProjectPrompt(prompt: string, images: ImageMessageContext[]) { +export async function createProjectPrompt(prompt: string, images: ImageMessageContext[]) { console.log('createProjectPrompt'); console.log(prompt, images); + + try { + const [generatedPage, projectSetup] = await Promise.all([ + generatePage(prompt, images), + setupProject(), + ]); + + // Apply the generated page to the project + await applyGeneratedPage(projectSetup.projectPath, generatedPage); + + return { + success: true, + projectPath: projectSetup.projectPath, + }; + } catch (error) { + console.error('Failed to create project:', error); + throw error; + } +} + +async function generatePage(prompt: string, images: ImageMessageContext[]) { + // Mock AI generation delay + await new Promise((resolve) => setTimeout(resolve, 2000)); + return { + name: '/* Generated styles here */', + code: '// Generated code here', + }; +} + +async function setupProject() { + // Mock project setup steps + await new Promise((resolve) => setTimeout(resolve, 1500)); + const projectPath = '/path/to/project'; + + return { + projectPath, + success: true, + }; +} + +async function applyGeneratedPage(projectPath: string, generatedPage: any) { + // Mock applying the generated code + await new Promise((resolve) => setTimeout(resolve, 500)); + return true; } From 128b8e9623f48185fbe85754123ec42cc290d413 Mon Sep 17 00:00:00 2001 From: Daniel R Farrell Date: Tue, 21 Jan 2025 22:37:25 -0800 Subject: [PATCH 08/26] Extremely fresh UI adjustments --- .../editor/EditPanel/ChatTab/ChatInput.tsx | 3 +- .../routes/projects/PromptCreation/index.tsx | 292 +++++++++++++----- 2 files changed, 220 insertions(+), 75 deletions(-) diff --git a/apps/studio/src/routes/editor/EditPanel/ChatTab/ChatInput.tsx b/apps/studio/src/routes/editor/EditPanel/ChatTab/ChatInput.tsx index c48e0596c..ef256a5a1 100644 --- a/apps/studio/src/routes/editor/EditPanel/ChatTab/ChatInput.tsx +++ b/apps/studio/src/routes/editor/EditPanel/ChatTab/ChatInput.tsx @@ -162,7 +162,8 @@ export const ChatInput = observer(() => {
{ handleDrop(e); diff --git a/apps/studio/src/routes/projects/PromptCreation/index.tsx b/apps/studio/src/routes/projects/PromptCreation/index.tsx index 6a618aec4..4d465877a 100644 --- a/apps/studio/src/routes/projects/PromptCreation/index.tsx +++ b/apps/studio/src/routes/projects/PromptCreation/index.tsx @@ -5,15 +5,28 @@ import { Button } from '@onlook/ui/button'; import { Card, CardContent, CardHeader } from '@onlook/ui/card'; import { Icons } from '@onlook/ui/icons'; import { Input } from '@onlook/ui/input'; +import { Textarea } from '@onlook/ui/textarea'; import { cn } from '@onlook/ui/utils'; -import { useState } from 'react'; +import { useState, useRef } from 'react'; +import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from '@onlook/ui/tooltip'; +import { AnimatePresence } from 'motion/react'; +import { DraftImagePill } from '../../editor/EditPanel/ChatTab/ContextPills/DraftingImagePill'; export const PromptCreation = () => { const [inputValue, setInputValue] = useState(''); const [isDragging, setIsDragging] = useState(false); const [selectedImages, setSelectedImages] = useState([]); + const [imageTooltipOpen, setImageTooltipOpen] = useState(false); + const [isHandlingFile, setIsHandlingFile] = useState(false); + const [textareaHeight, setTextareaHeight] = useState('auto'); + + // Add a ref for the textarea + const textareaRef = useRef(null); const handleSubmit = () => { + if (inputValue.trim().length < 10) { + return; + } invokeMainChannel(MainChannels.CREATE_NEW_PROJECT_PROMPT, { prompt: inputValue, images: selectedImages, @@ -33,11 +46,19 @@ export const PromptCreation = () => { const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); + setImageTooltipOpen(false); + // Find and reset the container's data attribute + const container = e.currentTarget.closest('.bg-background-secondary'); + if (container) { + container.setAttribute('data-dragging-image', 'false'); + } const files = Array.from(e.dataTransfer.files); handleNewImageFiles(files); }; const handleFileSelect = async (e: React.ChangeEvent) => { + setIsHandlingFile(true); + setImageTooltipOpen(false); const files = Array.from(e.target.files || []); handleNewImageFiles(files); }; @@ -57,6 +78,7 @@ export const PromptCreation = () => { } console.log('imageContexts', imageContexts); setSelectedImages([...selectedImages, ...imageContexts]); + setIsHandlingFile(false); }; const handleRemoveImage = (imageContext: ImageMessageContext) => { @@ -86,95 +108,217 @@ export const PromptCreation = () => { } }; + const handleDragStateChange = (isDragging: boolean, e: React.DragEvent) => { + const hasImage = + e.dataTransfer.types.length > 0 && + Array.from(e.dataTransfer.items).some( + (item) => + item.type.startsWith('image/') || + (item.type === 'Files' && e.dataTransfer.types.includes('public.file-url')), + ); + if (hasImage) { + setIsDragging(isDragging); + // Find the container div with the bg-background-secondary class + const container = e.currentTarget.closest('.bg-background-secondary'); + if (container) { + container.setAttribute('data-dragging-image', isDragging.toString()); + } + } + }; + + const handleContainerClick = (e: React.MouseEvent) => { + // Don't focus if clicking on a button, pill, or the textarea itself + if ( + e.target instanceof Element && + (e.target.closest('button') || + e.target.closest('.group') || // Pills have 'group' class + e.target === textareaRef.current) + ) { + return; + } + + textareaRef.current?.focus(); + }; + + const adjustTextareaHeight = () => { + if (textareaRef.current) { + // Reset height to auto to get the correct scrollHeight + textareaRef.current.style.height = 'auto'; + + // Calculate lines based on line height (assuming 1.5 line height for text-small) + const lineHeight = 20; // Approximate line height in pixels + const maxHeight = lineHeight * 10; // 10 lines maximum + + const newHeight = Math.min(textareaRef.current.scrollHeight, maxHeight); + textareaRef.current.style.height = `${newHeight}px`; + } + }; + return ( -
- {/* Background placeholder */} - {/*
*/} - {/* Content */} - +
+

What kind of website do you want to make?

- Paste a link, imagery, or more as inspiration for your next site + Tell us a bit about your project. Be as detailed as possible.

-
- {selectedImages.length > 0 && ( -
- {selectedImages.map((file, index) => ( -
- {file.displayName} +
0 ? 'p-4' : 'px-4 py-2'}`} + > +
0 ? 'min-h-6' : 'h-0', + )} + > + + {selectedImages.map((imageContext, index) => ( + handleRemoveImage(imageContext)} /> - -
- ))} + ))} +
- )} - setInputValue(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSubmit(); - } - }} - /> -
-
-