Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/web/client/src/app/_components/hero/create.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useAuthContext } from '@/app/auth/auth-context';
import { DraftImagePill } from '@/app/project/[id]/_components/right-panel/chat-tab/context-pills/draft-image-pill';
import { ImagePill } from '@/app/project/[id]/_components/right-panel/chat-tab/context-pills/image-pill';
import { validateImageLimit } from '@/app/project/[id]/_components/right-panel/chat-tab/context-pills/helpers';
import { useCreateManager } from '@/components/store/create';
import { Routes } from '@/utils/constants';
Expand Down Expand Up @@ -198,6 +198,7 @@ export const Create = observer(({

return {
type: MessageContextType.IMAGE,
source: 'external',
content: base64,
displayName: file.name,
mimeType: file.type,
Expand Down Expand Up @@ -287,7 +288,7 @@ export const Create = observer(({
>
<AnimatePresence mode="popLayout">
{selectedImages.map((imageContext) => (
<DraftImagePill
<ImagePill
key={imageContext.content}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more unique key for the ImagePill component rather than relying solely on imageContext.content. Using a unique identifier (e.g. imageContext.id or combining with an index) can help avoid potential key collisions.

Suggested change
key={imageContext.content}
key={imageContext.id}

context={imageContext}
onRemove={() => handleRemoveImage(imageContext)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { EditorView, ViewUpdate } from '@codemirror/view';
import { convertToBase64, getMimeType } from '@onlook/utility/src/file';
import { EditorView, keymap, ViewUpdate } from '@codemirror/view';
import type { CodeNavigationTarget } from '@onlook/models';
import { convertToBase64DataUrl, getMimeType } from '@onlook/utility/src/file';
import CodeMirror from '@uiw/react-codemirror';
import { type RefObject, useEffect, useMemo, useRef, useState } from 'react';
import type { CodeNavigationTarget } from '@onlook/models';
import type { BinaryEditorFile, EditorFile } from '../shared/types';
import { getBasicSetup, getExtensions, highlightElementRange, scrollToLineColumn } from './code-mirror-config';
import { FloatingAddToChatButton } from './floating-add-to-chat-button';
import { keymap } from '@codemirror/view';

interface CodeEditorProps {
file: EditorFile;
Expand Down Expand Up @@ -38,8 +37,7 @@ export const CodeEditor = ({

const getFileUrl = (file: BinaryEditorFile) => {
const mime = getMimeType(file.path.toLowerCase());
const base64 = convertToBase64(new Uint8Array(file.content));
return `data:${mime};base64,${base64}`;
return convertToBase64DataUrl(file.content, mime);
};

const selectionExtension = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const ImageItem = ({ image, projectId, branchId, onImageDragStart, onImag

const handleAddToChat = () => {
onAddToChat(image.path);
setDropdownOpen(false);
};

const handleKeyDown = (e: React.KeyboardEvent) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
'use client';

import { useEditorEngine } from '@/components/store/editor';
import { MessageContextType, type FileMessageContext } from '@onlook/models/chat';
import { observer } from 'mobx-react-lite';

import type { ImageMessageContext } from '@onlook/models/chat';
import { MessageContextType } from '@onlook/models/chat';
import { Icons } from '@onlook/ui/icons';
import { toast } from '@onlook/ui/sonner';
import { observer } from 'mobx-react-lite';
import { convertToBase64DataUrl, getMimeType } from '@onlook/utility';

import { useEditorEngine } from '@/components/store/editor';
import { BreadcrumbNavigation } from './breadcrumb-navigation';
import { FolderList } from './folder-list';
import { useImageOperations } from './hooks/use-image-operations';
Expand All @@ -29,7 +33,9 @@ export const ImagesTab = observer(() => {
} = useNavigation();

// Get the CodeEditorApi for the active branch
const branchData = editorEngine.branches.getBranchDataById(editorEngine.branches.activeBranch.id);
const branchData = editorEngine.branches.getBranchDataById(
editorEngine.branches.activeBranch.id,
);

// Image operations and data
const {
Expand All @@ -53,7 +59,9 @@ export const ImagesTab = observer(() => {
toast.success('Image renamed successfully');
} catch (error) {
console.error('Failed to rename image:', error);
toast.error(`Failed to rename image: ${error instanceof Error ? error.message : 'Unknown error'}`);
toast.error(
`Failed to rename image: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
throw error;
}
};
Expand All @@ -64,24 +72,37 @@ export const ImagesTab = observer(() => {
toast.success('Image deleted successfully');
} catch (error) {
console.error('Failed to delete image:', error);
toast.error(`Failed to delete image: ${error instanceof Error ? error.message : 'Unknown error'}`);
toast.error(
`Failed to delete image: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
throw error;
}
};

const handleAddToChat = async (imagePath: string) => {
try {
// Convert the image path to file context for chat
const fileName = imagePath.split('/').pop() || imagePath;
const fileContext: FileMessageContext = {
type: MessageContextType.FILE,
content: '', // File content will be loaded by the chat system
displayName: fileName,
const mimeType = getMimeType(fileName);

// Load the actual image file content
const fileContent = await branchData?.codeEditor.readFile(imagePath);
if (!fileContent) {
throw new Error('Failed to load image file');
}

const base64Content = convertToBase64DataUrl(fileContent, mimeType);

const imageContext: ImageMessageContext = {
type: MessageContextType.IMAGE,
source: 'local',
path: imagePath,
branchId: branchId,
content: base64Content,
displayName: fileName,
mimeType: mimeType,
};

editorEngine.chat.context.addContexts([fileContext]);
editorEngine.chat.context.addContexts([imageContext]);
toast.success('Image added to chat');
} catch (error) {
console.error('Failed to add image to chat:', error);
Expand All @@ -91,23 +112,23 @@ export const ImagesTab = observer(() => {

if (loading) {
return (
<div className="w-full h-full flex items-center justify-center gap-2">
<Icons.LoadingSpinner className="w-4 h-4 animate-spin" />
<div className="flex h-full w-full items-center justify-center gap-2">
<Icons.LoadingSpinner className="h-4 w-4 animate-spin" />
Loading images...
</div>
);
}

if (error) {
return (
<div className="w-full h-full flex items-center justify-center text-sm text-red-500">
<div className="flex h-full w-full items-center justify-center text-sm text-red-500">
Error: {error.message}
</div>
);
}

return (
<div className="w-full h-full flex flex-col gap-3 p-3">
<div className="flex h-full w-full flex-col gap-3 p-3">
<SearchUploadBar
search={search}
setSearch={setSearch}
Expand All @@ -120,10 +141,7 @@ export const ImagesTab = observer(() => {
onNavigate={navigateToFolder}
/>

<FolderList
folders={folders}
onFolderClick={handleFolderClick}
/>
<FolderList folders={folders} onFolderClick={handleFolderClick} />

<ImageGrid
images={images}
Expand All @@ -137,4 +155,4 @@ export const ImagesTab = observer(() => {
/>
</div>
);
});
});
Loading