Spaces:
Running
Running
add image upload
Browse files
frontend/src/app/page.tsx
CHANGED
|
@@ -59,6 +59,7 @@ export default function Home() {
|
|
| 59 |
const loadModels = async () => {
|
| 60 |
try {
|
| 61 |
const modelsList = await apiClient.getModels();
|
|
|
|
| 62 |
setModels(modelsList);
|
| 63 |
} catch (error) {
|
| 64 |
console.error('Failed to load models:', error);
|
|
@@ -67,6 +68,13 @@ export default function Home() {
|
|
| 67 |
|
| 68 |
// Check if current model supports images
|
| 69 |
const currentModelSupportsImages = models.find(m => m.id === selectedModel)?.supports_images || false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
// Load messages from localStorage on mount (client-side only to avoid hydration issues)
|
| 72 |
useEffect(() => {
|
|
|
|
| 59 |
const loadModels = async () => {
|
| 60 |
try {
|
| 61 |
const modelsList = await apiClient.getModels();
|
| 62 |
+
console.log('[Models] Loaded models:', modelsList);
|
| 63 |
setModels(modelsList);
|
| 64 |
} catch (error) {
|
| 65 |
console.error('Failed to load models:', error);
|
|
|
|
| 68 |
|
| 69 |
// Check if current model supports images
|
| 70 |
const currentModelSupportsImages = models.find(m => m.id === selectedModel)?.supports_images || false;
|
| 71 |
+
|
| 72 |
+
// Debug log for image support
|
| 73 |
+
useEffect(() => {
|
| 74 |
+
console.log('[Image Support] Selected model:', selectedModel);
|
| 75 |
+
console.log('[Image Support] Models loaded:', models.length);
|
| 76 |
+
console.log('[Image Support] Supports images:', currentModelSupportsImages);
|
| 77 |
+
}, [selectedModel, models, currentModelSupportsImages]);
|
| 78 |
|
| 79 |
// Load messages from localStorage on mount (client-side only to avoid hydration issues)
|
| 80 |
useEffect(() => {
|
frontend/src/components/LandingPage.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { useState, useEffect, useRef } from 'react';
|
|
|
|
| 4 |
import { apiClient } from '@/lib/api';
|
| 5 |
import {
|
| 6 |
initializeOAuth,
|
|
@@ -30,7 +31,7 @@ export default function LandingPage({
|
|
| 30 |
onImport,
|
| 31 |
isAuthenticated,
|
| 32 |
initialLanguage = 'html',
|
| 33 |
-
initialModel = '
|
| 34 |
onAuthChange,
|
| 35 |
setPendingPR,
|
| 36 |
pendingPRRef
|
|
@@ -74,6 +75,10 @@ export default function LandingPage({
|
|
| 74 |
const [isRedesigning, setIsRedesigning] = useState(false);
|
| 75 |
const [redesignError, setRedesignError] = useState('');
|
| 76 |
const [createPR, setCreatePR] = useState(false); // Default to normal redesign (not PR)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
// Debug effect for dropdown state
|
| 79 |
useEffect(() => {
|
|
@@ -211,6 +216,9 @@ export default function LandingPage({
|
|
| 211 |
console.error('Failed to load languages:', error);
|
| 212 |
}
|
| 213 |
};
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
const loadTrendingApps = async () => {
|
| 216 |
try {
|
|
@@ -225,10 +233,31 @@ export default function LandingPage({
|
|
| 225 |
e.preventDefault();
|
| 226 |
if (prompt.trim() && isAuthenticated) {
|
| 227 |
onStart(prompt.trim(), selectedLanguage, selectedModel);
|
|
|
|
|
|
|
| 228 |
} else if (!isAuthenticated) {
|
| 229 |
alert('Please sign in with HuggingFace first!');
|
| 230 |
}
|
| 231 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
const formatLanguageName = (lang: Language) => {
|
| 234 |
if (lang === 'html') return 'HTML';
|
|
@@ -645,6 +674,29 @@ Note: After generating the redesign, I will create a Pull Request on the origina
|
|
| 645 |
{/* Simple prompt form */}
|
| 646 |
<form onSubmit={handleSubmit} className="relative w-full mb-8">
|
| 647 |
<div className="relative bg-[#2d2d30] rounded-2xl border border-[#424245] shadow-2xl">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
{/* Textarea */}
|
| 649 |
<textarea
|
| 650 |
value={prompt}
|
|
@@ -1012,6 +1064,31 @@ Note: After generating the redesign, I will create a Pull Request on the origina
|
|
| 1012 |
</div>
|
| 1013 |
</div>
|
| 1014 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1015 |
{/* Send button on the right - Apple style */}
|
| 1016 |
<button
|
| 1017 |
type="submit"
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { useState, useEffect, useRef } from 'react';
|
| 4 |
+
import Image from 'next/image';
|
| 5 |
import { apiClient } from '@/lib/api';
|
| 6 |
import {
|
| 7 |
initializeOAuth,
|
|
|
|
| 31 |
onImport,
|
| 32 |
isAuthenticated,
|
| 33 |
initialLanguage = 'html',
|
| 34 |
+
initialModel = 'zai-org/GLM-4.6V:zai-org',
|
| 35 |
onAuthChange,
|
| 36 |
setPendingPR,
|
| 37 |
pendingPRRef
|
|
|
|
| 75 |
const [isRedesigning, setIsRedesigning] = useState(false);
|
| 76 |
const [redesignError, setRedesignError] = useState('');
|
| 77 |
const [createPR, setCreatePR] = useState(false); // Default to normal redesign (not PR)
|
| 78 |
+
|
| 79 |
+
// Image upload state
|
| 80 |
+
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
|
| 81 |
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 82 |
|
| 83 |
// Debug effect for dropdown state
|
| 84 |
useEffect(() => {
|
|
|
|
| 216 |
console.error('Failed to load languages:', error);
|
| 217 |
}
|
| 218 |
};
|
| 219 |
+
|
| 220 |
+
// Check if current model supports images
|
| 221 |
+
const currentModelSupportsImages = models.find(m => m.id === selectedModel)?.supports_images || false;
|
| 222 |
|
| 223 |
const loadTrendingApps = async () => {
|
| 224 |
try {
|
|
|
|
| 233 |
e.preventDefault();
|
| 234 |
if (prompt.trim() && isAuthenticated) {
|
| 235 |
onStart(prompt.trim(), selectedLanguage, selectedModel);
|
| 236 |
+
// Clear image after sending
|
| 237 |
+
setUploadedImageUrl(null);
|
| 238 |
} else if (!isAuthenticated) {
|
| 239 |
alert('Please sign in with HuggingFace first!');
|
| 240 |
}
|
| 241 |
};
|
| 242 |
+
|
| 243 |
+
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
| 244 |
+
const file = e.target.files?.[0];
|
| 245 |
+
if (file) {
|
| 246 |
+
const reader = new FileReader();
|
| 247 |
+
reader.onload = (event) => {
|
| 248 |
+
const imageUrl = event.target?.result as string;
|
| 249 |
+
setUploadedImageUrl(imageUrl);
|
| 250 |
+
};
|
| 251 |
+
reader.readAsDataURL(file);
|
| 252 |
+
}
|
| 253 |
+
};
|
| 254 |
+
|
| 255 |
+
const removeImage = () => {
|
| 256 |
+
setUploadedImageUrl(null);
|
| 257 |
+
if (fileInputRef.current) {
|
| 258 |
+
fileInputRef.current.value = '';
|
| 259 |
+
}
|
| 260 |
+
};
|
| 261 |
|
| 262 |
const formatLanguageName = (lang: Language) => {
|
| 263 |
if (lang === 'html') return 'HTML';
|
|
|
|
| 674 |
{/* Simple prompt form */}
|
| 675 |
<form onSubmit={handleSubmit} className="relative w-full mb-8">
|
| 676 |
<div className="relative bg-[#2d2d30] rounded-2xl border border-[#424245] shadow-2xl">
|
| 677 |
+
{/* Image Preview */}
|
| 678 |
+
{uploadedImageUrl && (
|
| 679 |
+
<div className="px-4 pt-3">
|
| 680 |
+
<div className="relative inline-block">
|
| 681 |
+
<Image
|
| 682 |
+
src={uploadedImageUrl}
|
| 683 |
+
alt="Upload preview"
|
| 684 |
+
width={120}
|
| 685 |
+
height={120}
|
| 686 |
+
className="rounded-lg object-cover"
|
| 687 |
+
unoptimized
|
| 688 |
+
/>
|
| 689 |
+
<button
|
| 690 |
+
type="button"
|
| 691 |
+
onClick={removeImage}
|
| 692 |
+
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full hover:bg-red-600 transition-all flex items-center justify-center text-xs font-bold"
|
| 693 |
+
>
|
| 694 |
+
×
|
| 695 |
+
</button>
|
| 696 |
+
</div>
|
| 697 |
+
</div>
|
| 698 |
+
)}
|
| 699 |
+
|
| 700 |
{/* Textarea */}
|
| 701 |
<textarea
|
| 702 |
value={prompt}
|
|
|
|
| 1064 |
</div>
|
| 1065 |
</div>
|
| 1066 |
|
| 1067 |
+
{/* Image Upload Button (only if model supports images) */}
|
| 1068 |
+
{currentModelSupportsImages && (
|
| 1069 |
+
<>
|
| 1070 |
+
<input
|
| 1071 |
+
ref={fileInputRef}
|
| 1072 |
+
type="file"
|
| 1073 |
+
accept="image/*"
|
| 1074 |
+
onChange={handleImageUpload}
|
| 1075 |
+
className="hidden"
|
| 1076 |
+
disabled={!isAuthenticated}
|
| 1077 |
+
/>
|
| 1078 |
+
<button
|
| 1079 |
+
type="button"
|
| 1080 |
+
onClick={() => fileInputRef.current?.click()}
|
| 1081 |
+
disabled={!isAuthenticated}
|
| 1082 |
+
className="p-2 bg-[#1d1d1f] text-[#f5f5f7] rounded-full hover:bg-[#424245] disabled:opacity-30 disabled:cursor-not-allowed transition-all active:scale-95"
|
| 1083 |
+
title="Upload image"
|
| 1084 |
+
>
|
| 1085 |
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
|
| 1086 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
|
| 1087 |
+
</svg>
|
| 1088 |
+
</button>
|
| 1089 |
+
</>
|
| 1090 |
+
)}
|
| 1091 |
+
|
| 1092 |
{/* Send button on the right - Apple style */}
|
| 1093 |
<button
|
| 1094 |
type="submit"
|