topguy commited on
Commit
ccc9c4e
·
1 Parent(s): 7973b76

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 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.0",
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 (!data.comfy_active) {
119
- if (data.hf_active) setImageBackend("Hugging Face (Cloud)");
120
- else if (data.gemini_active) setImageBackend("Gemini (Cloud)");
121
- }
122
-
123
- if (!data.ollama_models?.length) {
124
- if (data.gemini_active) setRefinementBackend("Gemini (Cloud)");
125
- else if (data.hf_active) setRefinementBackend("Hugging Face (Cloud)");
 
 
 
 
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.0</p>
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
- 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 shadow-[0_0_15px_rgba(97,194,223,0.15)] transition-all hover:shadow-[0_0_25px_rgba(97,194,223,0.3)]"
 
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
- <Sparkles size={18} /> Enhance Narrative
 
818
  </span>
819
  </button>
820
  </div>
@@ -840,11 +853,13 @@ export default function ClientApp() {
840
 
841
  <button
842
  onClick={handleGenerateImage}
843
- 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 shadow-[0_0_20px_rgba(218,176,98,0.2)] transition-all hover:shadow-[0_0_30px_rgba(218,176,98,0.4)]"
 
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
- <Wand2 size={22} /> Synthesize
 
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-12 animate-in fade-in duration-300" onClick={() => setShowLightbox(false)}>
856
- <div className="absolute top-8 right-8 z-[110]">
857
- <button onClick={() => setShowLightbox(false)} className="text-[#A89880] hover:text-[#DAB062] transition-colors bg-black/40 p-3 rounded-full border border-[#2D241A] shadow-xl">
 
 
 
 
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="absolute inset-0 border border-[#DAB062]/20 rounded-xl pointer-events-none blur-sm"></div>
865
- <img
866
- src={imageSrc || ""}
867
- alt="Portrait High Resolution"
868
- className="max-w-full max-h-full rounded-xl shadow-[0_0_100px_rgba(218,176,98,0.15)] object-contain border border-[#2D241A]"
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>