Spaces:
Sleeping
Sleeping
pref: Finalize Cloud deployment v0.1.5 with UI refinements
Browse files- Full engine isolation: ComfyUI & Ollama now correctly tag as '(Local Only)' when deployed on Hugging Face Spaces.
- Smart engine prioritization: Hugging Face engines are now the hard-coded default when running in a Space environment.
- Synthesis feedback: Integrated 'Loader2' spinners and processing states for 'Refine' and 'Synthesize' actions.
- Lightbox UX: Refactored fullscreen viewer to maximize space for 9:16 portraits by moving the download button to a subtle top-right icon.
- Version tracking: Bumped application version across the board to v0.1.5.
- commit_msg.txt +7 -0
- frontend/package.json +1 -1
- frontend/src/components/ClientApp.tsx +45 -31
commit_msg.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pref: Finalize Cloud deployment v0.1.5 with UI refinements
|
| 2 |
+
|
| 3 |
+
- Full engine isolation: ComfyUI & Ollama now correctly tag as '(Local Only)' when deployed on Hugging Face Spaces.
|
| 4 |
+
- Smart engine prioritization: Hugging Face engines are now the hard-coded default when running in a Space environment.
|
| 5 |
+
- Synthesis feedback: Integrated 'Loader2' spinners and processing states for 'Refine' and 'Synthesize' actions.
|
| 6 |
+
- Lightbox UX: Refactored fullscreen viewer to maximize space for 9:16 portraits by moving the download button to a subtle top-right icon.
|
| 7 |
+
- Version tracking: Bumped application version across the board to v0.1.5.
|
frontend/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "frontend",
|
| 3 |
-
"version": "0.1.
|
| 4 |
"private": true,
|
| 5 |
"scripts": {
|
| 6 |
"dev": "next dev",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "frontend",
|
| 3 |
+
"version": "0.1.5",
|
| 4 |
"private": true,
|
| 5 |
"scripts": {
|
| 6 |
"dev": "next dev",
|
frontend/src/components/ClientApp.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import React, { useState, useEffect, useRef } from 'react';
|
| 4 |
-
import { User, Sparkles, Sword, Globe, Palette, Settings, Image as ImageIcon, Wand2, Dices, Save, Download, Upload, Copy, X, LogIn, LogOut } from 'lucide-react';
|
| 5 |
import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
|
| 6 |
|
| 7 |
const API_URL = process.env.NODE_ENV === "development" ? "http://127.0.0.1:8000" : "";
|
|
@@ -40,6 +40,9 @@ export default function ClientApp() {
|
|
| 40 |
const [manualTextEntry, setManualTextEntry] = useState(false);
|
| 41 |
const [manualImageEntry, setManualImageEntry] = useState(false);
|
| 42 |
|
|
|
|
|
|
|
|
|
|
| 43 |
// Restore Settings from LocalStorage & Handle OAuth Redirect
|
| 44 |
useEffect(() => {
|
| 45 |
// 1. Handle OAuth Redirect
|
|
@@ -115,14 +118,18 @@ export default function ClientApp() {
|
|
| 115 |
setRandomization(Array(data.feature_sequence.length).fill(false));
|
| 116 |
|
| 117 |
// Smart Active Engine Defaults
|
| 118 |
-
if (
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
}
|
| 127 |
})
|
| 128 |
.catch(err => console.error("Failed to load config", err));
|
|
@@ -295,6 +302,7 @@ export default function ClientApp() {
|
|
| 295 |
|
| 296 |
const handleRefine = async () => {
|
| 297 |
setStatusMsg("Refining prompt...");
|
|
|
|
| 298 |
try {
|
| 299 |
const res = await fetch(`${API_URL}/api/refine_prompt`, {
|
| 300 |
method: 'POST',
|
|
@@ -318,10 +326,12 @@ export default function ClientApp() {
|
|
| 318 |
setStatusMsg(`Error: ${err.detail}`);
|
| 319 |
}
|
| 320 |
} catch (e) { setStatusMsg("Refinement failed."); }
|
|
|
|
| 321 |
};
|
| 322 |
|
| 323 |
const handleGenerateImage = async () => {
|
| 324 |
setStatusMsg("Synthesizing portrait...");
|
|
|
|
| 325 |
setImageSrc(null);
|
| 326 |
try {
|
| 327 |
const aspectIndex = config.feature_sequence.findIndex((seq: any) => seq[2] === "aspect_ratio");
|
|
@@ -351,6 +361,7 @@ export default function ClientApp() {
|
|
| 351 |
setStatusMsg(`Synthesis Error: ${err.detail}`);
|
| 352 |
}
|
| 353 |
} catch (e) { setStatusMsg("Synthesis failed to initialize."); }
|
|
|
|
| 354 |
};
|
| 355 |
|
| 356 |
const handleDownloadImage = () => {
|
|
@@ -418,7 +429,7 @@ export default function ClientApp() {
|
|
| 418 |
<div className="p-8 border-b border-[#2D241A] text-center">
|
| 419 |
<h1 className="font-cinzel font-bold text-3xl gold-gradient-text drop-shadow-md mb-2">Chronicle</h1>
|
| 420 |
<p className="text-[#A89880] text-sm uppercase tracking-[0.2em]">Portrait Studio</p>
|
| 421 |
-
<p className="text-[#665D4F] text-[10px] font-mono mt-2 opacity-80">v0.1.
|
| 422 |
</div>
|
| 423 |
|
| 424 |
<nav className="flex-1 px-4 py-8 space-y-2 overflow-y-auto custom-scrollbar">
|
|
@@ -810,11 +821,13 @@ export default function ClientApp() {
|
|
| 810 |
></textarea>
|
| 811 |
<button
|
| 812 |
onClick={handleRefine}
|
| 813 |
-
|
|
|
|
| 814 |
>
|
| 815 |
-
<div className="absolute inset-0 bg-white/10 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000 ease-out"></div>
|
| 816 |
<span className="relative z-10 flex items-center justify-center gap-3">
|
| 817 |
-
<
|
|
|
|
| 818 |
</span>
|
| 819 |
</button>
|
| 820 |
</div>
|
|
@@ -840,11 +853,13 @@ export default function ClientApp() {
|
|
| 840 |
|
| 841 |
<button
|
| 842 |
onClick={handleGenerateImage}
|
| 843 |
-
|
|
|
|
| 844 |
>
|
| 845 |
-
<div className="absolute inset-0 bg-white/20 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000 ease-out"></div>
|
| 846 |
<span className="relative z-10 flex items-center justify-center gap-3">
|
| 847 |
-
<
|
|
|
|
| 848 |
</span>
|
| 849 |
</button>
|
| 850 |
|
|
@@ -852,26 +867,25 @@ export default function ClientApp() {
|
|
| 852 |
</div>
|
| 853 |
{/* FULL SCREEN LIGHTBOX MODAL */}
|
| 854 |
{showLightbox && imageSrc && (
|
| 855 |
-
<div className="fixed inset-0 z-[100] bg-[#080604]/95 backdrop-blur-2xl flex items-center justify-center p-4 md:p-
|
| 856 |
-
<div className="absolute top-8 right-8 z-[110]">
|
| 857 |
-
<button onClick={() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
<X size={24} />
|
| 859 |
-
<span className="sr-only">Close</span>
|
| 860 |
</button>
|
| 861 |
</div>
|
| 862 |
|
| 863 |
<div className="relative max-w-full max-h-full flex items-center justify-center animate-in zoom-in-95 duration-500" onClick={e => e.stopPropagation()}>
|
| 864 |
-
<div className="
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
<div className="absolute bottom-[-60px] flex gap-4">
|
| 872 |
-
<button onClick={handleDownloadImage} className="flex items-center gap-3 px-6 py-2.5 bg-[#DAB062] text-[#0a0805] rounded-full font-cinzel font-bold text-sm tracking-widest hover:bg-[#F2DAA4] transition-colors shadow-lg shadow-yellow-900/20">
|
| 873 |
-
<Download size={18} /> Preserve Masterpiece
|
| 874 |
-
</button>
|
| 875 |
</div>
|
| 876 |
</div>
|
| 877 |
</div>
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import React, { useState, useEffect, useRef } from 'react';
|
| 4 |
+
import { User, Sparkles, Sword, Globe, Palette, Settings, Image as ImageIcon, Wand2, Dices, Save, Download, Upload, Copy, X, LogIn, LogOut, Loader2 } from 'lucide-react';
|
| 5 |
import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
|
| 6 |
|
| 7 |
const API_URL = process.env.NODE_ENV === "development" ? "http://127.0.0.1:8000" : "";
|
|
|
|
| 40 |
const [manualTextEntry, setManualTextEntry] = useState(false);
|
| 41 |
const [manualImageEntry, setManualImageEntry] = useState(false);
|
| 42 |
|
| 43 |
+
const [isRefining, setIsRefining] = useState(false);
|
| 44 |
+
const [isGenerating, setIsGenerating] = useState(false);
|
| 45 |
+
|
| 46 |
// Restore Settings from LocalStorage & Handle OAuth Redirect
|
| 47 |
useEffect(() => {
|
| 48 |
// 1. Handle OAuth Redirect
|
|
|
|
| 118 |
setRandomization(Array(data.feature_sequence.length).fill(false));
|
| 119 |
|
| 120 |
// Smart Active Engine Defaults
|
| 121 |
+
if (data.is_hf_space) {
|
| 122 |
+
setImageBackend("Hugging Face (Cloud)");
|
| 123 |
+
setRefinementBackend("Hugging Face (Cloud)");
|
| 124 |
+
} else {
|
| 125 |
+
if (!data.comfy_active) {
|
| 126 |
+
if (data.hf_active) setImageBackend("Hugging Face (Cloud)");
|
| 127 |
+
else if (data.gemini_active) setImageBackend("Gemini (Cloud)");
|
| 128 |
+
}
|
| 129 |
+
if (!data.ollama_models?.length) {
|
| 130 |
+
if (data.gemini_active) setRefinementBackend("Gemini (Cloud)");
|
| 131 |
+
else if (data.hf_active) setRefinementBackend("Hugging Face (Cloud)");
|
| 132 |
+
}
|
| 133 |
}
|
| 134 |
})
|
| 135 |
.catch(err => console.error("Failed to load config", err));
|
|
|
|
| 302 |
|
| 303 |
const handleRefine = async () => {
|
| 304 |
setStatusMsg("Refining prompt...");
|
| 305 |
+
setIsRefining(true);
|
| 306 |
try {
|
| 307 |
const res = await fetch(`${API_URL}/api/refine_prompt`, {
|
| 308 |
method: 'POST',
|
|
|
|
| 326 |
setStatusMsg(`Error: ${err.detail}`);
|
| 327 |
}
|
| 328 |
} catch (e) { setStatusMsg("Refinement failed."); }
|
| 329 |
+
finally { setIsRefining(false); }
|
| 330 |
};
|
| 331 |
|
| 332 |
const handleGenerateImage = async () => {
|
| 333 |
setStatusMsg("Synthesizing portrait...");
|
| 334 |
+
setIsGenerating(true);
|
| 335 |
setImageSrc(null);
|
| 336 |
try {
|
| 337 |
const aspectIndex = config.feature_sequence.findIndex((seq: any) => seq[2] === "aspect_ratio");
|
|
|
|
| 361 |
setStatusMsg(`Synthesis Error: ${err.detail}`);
|
| 362 |
}
|
| 363 |
} catch (e) { setStatusMsg("Synthesis failed to initialize."); }
|
| 364 |
+
finally { setIsGenerating(false); }
|
| 365 |
};
|
| 366 |
|
| 367 |
const handleDownloadImage = () => {
|
|
|
|
| 429 |
<div className="p-8 border-b border-[#2D241A] text-center">
|
| 430 |
<h1 className="font-cinzel font-bold text-3xl gold-gradient-text drop-shadow-md mb-2">Chronicle</h1>
|
| 431 |
<p className="text-[#A89880] text-sm uppercase tracking-[0.2em]">Portrait Studio</p>
|
| 432 |
+
<p className="text-[#665D4F] text-[10px] font-mono mt-2 opacity-80">v0.1.5</p>
|
| 433 |
</div>
|
| 434 |
|
| 435 |
<nav className="flex-1 px-4 py-8 space-y-2 overflow-y-auto custom-scrollbar">
|
|
|
|
| 821 |
></textarea>
|
| 822 |
<button
|
| 823 |
onClick={handleRefine}
|
| 824 |
+
disabled={isRefining}
|
| 825 |
+
className={`mt-4 w-full shrink-0 relative overflow-hidden group bg-gradient-to-r from-[#1A3F4D] via-[#2F657D] to-[#1A3F4D] text-[#E0F2FE] font-cinzel font-bold text-sm tracking-widest py-3.5 rounded-xl transition-all ${isRefining ? 'opacity-70 cursor-not-allowed border border-[#61C2DF]/30 shadow-[0_0_15px_rgba(97,194,223,0.3)]' : 'shadow-[0_0_15px_rgba(97,194,223,0.15)] hover:shadow-[0_0_25px_rgba(97,194,223,0.3)]'}`}
|
| 826 |
>
|
| 827 |
+
{!isRefining && <div className="absolute inset-0 bg-white/10 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000 ease-out"></div>}
|
| 828 |
<span className="relative z-10 flex items-center justify-center gap-3">
|
| 829 |
+
{isRefining ? <Loader2 size={18} className="animate-spin" /> : <Sparkles size={18} />}
|
| 830 |
+
{isRefining ? 'Refining...' : 'Enhance Narrative'}
|
| 831 |
</span>
|
| 832 |
</button>
|
| 833 |
</div>
|
|
|
|
| 853 |
|
| 854 |
<button
|
| 855 |
onClick={handleGenerateImage}
|
| 856 |
+
disabled={isGenerating}
|
| 857 |
+
className={`w-full shrink-0 relative overflow-hidden group bg-gradient-to-r from-[#8C6D3B] via-[#DAB062] to-[#8C6D3B] text-[#0a0805] font-cinzel font-bold text-base tracking-widest py-4 rounded-xl transition-all ${isGenerating ? 'opacity-70 cursor-not-allowed border border-[#DAB062]/50 shadow-[0_0_20px_rgba(218,176,98,0.4)]' : 'shadow-[0_0_20px_rgba(218,176,98,0.2)] hover:shadow-[0_0_30px_rgba(218,176,98,0.4)]'}`}
|
| 858 |
>
|
| 859 |
+
{!isGenerating && <div className="absolute inset-0 bg-white/20 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000 ease-out"></div>}
|
| 860 |
<span className="relative z-10 flex items-center justify-center gap-3">
|
| 861 |
+
{isGenerating ? <Loader2 size={22} className="animate-spin" /> : <Wand2 size={22} />}
|
| 862 |
+
{isGenerating ? 'Synthesizing...' : 'Synthesize'}
|
| 863 |
</span>
|
| 864 |
</button>
|
| 865 |
|
|
|
|
| 867 |
</div>
|
| 868 |
{/* FULL SCREEN LIGHTBOX MODAL */}
|
| 869 |
{showLightbox && imageSrc && (
|
| 870 |
+
<div className="fixed inset-0 z-[100] bg-[#080604]/95 backdrop-blur-2xl flex items-center justify-center p-4 md:p-8 animate-in fade-in duration-300" onClick={() => setShowLightbox(false)}>
|
| 871 |
+
<div className="absolute top-8 right-8 z-[110] flex gap-4">
|
| 872 |
+
<button onClick={(e) => { e.stopPropagation(); handleDownloadImage(); }} className="text-[#A89880] hover:text-[#DAB062] transition-colors bg-black/40 p-3 rounded-full border border-[#2D241A] shadow-xl group/btn" title="Download Masterpiece">
|
| 873 |
+
<Download size={24} />
|
| 874 |
+
<span className="absolute right-full mr-4 top-1/2 -translate-y-1/2 px-3 py-1 bg-black/80 text-[#DAB062] text-[10px] uppercase tracking-widest rounded-md border border-[#2D241A] opacity-0 group-hover/btn:opacity-100 transition-opacity whitespace-nowrap pointer-events-none">Preserve Masterpiece</span>
|
| 875 |
+
</button>
|
| 876 |
+
<button onClick={() => setShowLightbox(false)} className="text-[#A89880] hover:text-[#61C2DF] transition-colors bg-black/40 p-3 rounded-full border border-[#2D241A] shadow-xl" title="Close">
|
| 877 |
<X size={24} />
|
|
|
|
| 878 |
</button>
|
| 879 |
</div>
|
| 880 |
|
| 881 |
<div className="relative max-w-full max-h-full flex items-center justify-center animate-in zoom-in-95 duration-500" onClick={e => e.stopPropagation()}>
|
| 882 |
+
<div className="relative">
|
| 883 |
+
<div className="absolute inset-0 border border-[#DAB062]/20 rounded-xl pointer-events-none blur-sm"></div>
|
| 884 |
+
<img
|
| 885 |
+
src={imageSrc || ""}
|
| 886 |
+
alt="Portrait High Resolution"
|
| 887 |
+
className="max-w-full max-h-[92vh] rounded-xl shadow-[0_0_100px_rgba(218,176,98,0.15)] object-contain border border-[#2D241A]"
|
| 888 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 889 |
</div>
|
| 890 |
</div>
|
| 891 |
</div>
|