Complete integration of prompt studio with full preset library: - Added 40+ application/lighting presets across all categories - Studio & Portrait (2) - Product & Macro (5) - Outdoor & Natural (3) - Action & Motion (1) - Creative & Artistic (6) - Auteur Styles (9) - Professional Production (6) - Editorial & Fashion (4) - Documentary & Journalism (3) - Architectural & Interior (2) All lighting physics data now matches original React app. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
1207 lines
43 KiB
PHP
1207 lines
43 KiB
PHP
<?php
|
|
session_start();
|
|
|
|
// Initialize session variables if not set
|
|
if (!isset($_SESSION['conversation_history'])) {
|
|
$_SESSION['conversation_history'] = [];
|
|
}
|
|
if (!isset($_SESSION['current_image'])) {
|
|
$_SESSION['current_image'] = null;
|
|
}
|
|
if (!isset($_SESSION['current_image_mime'])) {
|
|
$_SESSION['current_image_mime'] = 'image/png';
|
|
}
|
|
if (!isset($_SESSION['image_history'])) {
|
|
$_SESSION['image_history'] = [];
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Nano Banana Pro - Image Generator</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Montserrat', sans-serif;
|
|
background: #000;
|
|
color: #fff;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
header {
|
|
text-align: center;
|
|
margin-bottom: 40px;
|
|
padding: 30px 0;
|
|
border-bottom: 2px solid #FFC407;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
color: #FFC407;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 1rem;
|
|
color: #999;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.main-content {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 30px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
@media (max-width: 968px) {
|
|
.main-content {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.panel {
|
|
background: #1a1a1a;
|
|
border-radius: 12px;
|
|
padding: 30px;
|
|
border: 1px solid #333;
|
|
}
|
|
|
|
.panel h2 {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
color: #FFC407;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
color: #FFC407;
|
|
}
|
|
|
|
textarea, select, .file-input {
|
|
width: 100%;
|
|
padding: 15px;
|
|
background: #000;
|
|
border: 2px solid #333;
|
|
border-radius: 8px;
|
|
color: #fff;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-size: 0.95rem;
|
|
transition: border-color 0.3s;
|
|
}
|
|
|
|
textarea:focus, select:focus, .file-input:focus {
|
|
outline: none;
|
|
border-color: #FFC407;
|
|
}
|
|
|
|
.file-input {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.file-input::file-selector-button {
|
|
background: #FFC407;
|
|
color: #000;
|
|
border: none;
|
|
padding: 8px 15px;
|
|
border-radius: 4px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.file-input::file-selector-button:hover {
|
|
background: #ffcd1f;
|
|
}
|
|
|
|
textarea {
|
|
min-height: 120px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.btn {
|
|
background: #FFC407;
|
|
color: #000;
|
|
border: none;
|
|
padding: 15px 30px;
|
|
border-radius: 8px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
width: 100%;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: #ffcd1f;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.btn:disabled {
|
|
background: #666;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #333;
|
|
color: #FFC407;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #444;
|
|
}
|
|
|
|
.image-display {
|
|
background: #000;
|
|
border-radius: 8px;
|
|
min-height: 400px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 2px dashed #333;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.image-display.has-image {
|
|
border-style: solid;
|
|
border-color: #FFC407;
|
|
}
|
|
|
|
.image-display img {
|
|
max-width: 100%;
|
|
max-height: 600px;
|
|
display: block;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.placeholder {
|
|
text-align: center;
|
|
color: #666;
|
|
}
|
|
|
|
.placeholder-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.loading {
|
|
text-align: center;
|
|
color: #FFC407;
|
|
}
|
|
|
|
.spinner {
|
|
border: 3px solid #333;
|
|
border-top: 3px solid #FFC407;
|
|
border-radius: 50%;
|
|
width: 50px;
|
|
height: 50px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 15px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.history {
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.history-item {
|
|
background: #1a1a1a;
|
|
border: 1px solid #333;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-bottom: 10px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.history-prompt {
|
|
color: #FFC407;
|
|
font-weight: 600;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.history-time {
|
|
color: #666;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.error-message {
|
|
background: #ff4444;
|
|
color: #fff;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.error-message.show {
|
|
display: block;
|
|
}
|
|
|
|
.success-message {
|
|
background: #44ff44;
|
|
color: #000;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.success-message.show {
|
|
display: block;
|
|
}
|
|
|
|
.image-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.image-actions .btn {
|
|
width: auto;
|
|
flex: 1;
|
|
}
|
|
|
|
.quick-actions {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.quick-btn {
|
|
background: #222;
|
|
color: #FFC407;
|
|
border: 1px solid #FFC407;
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.quick-btn:hover {
|
|
background: #FFC407;
|
|
color: #000;
|
|
}
|
|
|
|
.settings-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 15px;
|
|
}
|
|
|
|
.debug-panel {
|
|
background: #0a0a0a;
|
|
border: 2px solid #333;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-top: 30px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.debug-panel h3 {
|
|
color: #0ff;
|
|
margin-bottom: 15px;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.debug-section {
|
|
background: #000;
|
|
border-left: 3px solid #0f0;
|
|
padding: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.debug-section h4 {
|
|
color: #ff0;
|
|
margin-bottom: 8px;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.debug-section pre {
|
|
color: #0f0;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
margin: 0;
|
|
}
|
|
|
|
.debug-error {
|
|
border-left-color: #f00;
|
|
}
|
|
|
|
.debug-error pre {
|
|
color: #f00;
|
|
}
|
|
|
|
.debug-success {
|
|
border-left-color: #0f0;
|
|
}
|
|
|
|
.debug-toggle {
|
|
background: #222;
|
|
color: #0ff;
|
|
border: 1px solid #0ff;
|
|
padding: 10px 20px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-family: 'Courier New', monospace;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.debug-toggle:hover {
|
|
background: #333;
|
|
}
|
|
|
|
.debug-content {
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Prompt Studio Styles */
|
|
.prompt-studio {
|
|
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
|
|
border-radius: 12px;
|
|
padding: 30px;
|
|
margin-bottom: 30px;
|
|
border: 2px solid #FFC407;
|
|
}
|
|
|
|
.prompt-studio h2 {
|
|
font-size: 1.8rem;
|
|
color: #FFC407;
|
|
margin-bottom: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.prompt-studio-subtitle {
|
|
color: #999;
|
|
font-size: 0.9rem;
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.studio-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
@media (max-width: 968px) {
|
|
.studio-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.studio-field {
|
|
background: #000;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #333;
|
|
}
|
|
|
|
.studio-field label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-size: 0.85rem;
|
|
color: #FFC407;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.studio-field select {
|
|
width: 100%;
|
|
padding: 12px;
|
|
background: #1a1a1a;
|
|
border: 2px solid #333;
|
|
border-radius: 6px;
|
|
color: #fff;
|
|
font-family: 'Montserrat', sans-serif;
|
|
}
|
|
|
|
.studio-field select:focus {
|
|
border-color: #FFC407;
|
|
outline: none;
|
|
}
|
|
|
|
.slider-container {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.slider-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.slider-header label {
|
|
font-size: 0.9rem;
|
|
color: #FFC407;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.slider-value {
|
|
background: #FFC407;
|
|
color: #000;
|
|
padding: 4px 12px;
|
|
border-radius: 4px;
|
|
font-weight: 700;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
input[type="range"] {
|
|
width: 100%;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
background: #333;
|
|
outline: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: #FFC407;
|
|
cursor: pointer;
|
|
}
|
|
|
|
input[type="range"]::-moz-range-thumb {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: #FFC407;
|
|
cursor: pointer;
|
|
border: none;
|
|
}
|
|
|
|
.scene-input {
|
|
width: 100%;
|
|
padding: 15px;
|
|
background: #000;
|
|
border: 2px solid #333;
|
|
border-radius: 8px;
|
|
color: #fff;
|
|
font-family: 'Montserrat', sans-serif;
|
|
min-height: 100px;
|
|
resize: vertical;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.scene-input:focus {
|
|
border-color: #FFC407;
|
|
outline: none;
|
|
}
|
|
|
|
.enhance-btn {
|
|
background: linear-gradient(135deg, #FFC407 0%, #ff9500 100%);
|
|
color: #000;
|
|
border: none;
|
|
padding: 15px 40px;
|
|
border-radius: 8px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-weight: 700;
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.enhance-btn:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 5px 20px rgba(255, 196, 7, 0.4);
|
|
}
|
|
|
|
.enhance-btn:disabled {
|
|
background: #666;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.enhanced-output {
|
|
background: #0a0a0a;
|
|
border: 2px solid #FFC407;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-top: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.enhanced-output.show {
|
|
display: block;
|
|
animation: fadeIn 0.3s;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.enhanced-output h3 {
|
|
color: #FFC407;
|
|
margin-bottom: 15px;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.enhanced-text {
|
|
color: #fff;
|
|
line-height: 1.6;
|
|
font-size: 0.95rem;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.use-prompt-btn {
|
|
background: #FFC407;
|
|
color: #000;
|
|
border: none;
|
|
padding: 10px 25px;
|
|
border-radius: 6px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.use-prompt-btn:hover {
|
|
background: #ffcd1f;
|
|
transform: translateY(-2px);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header>
|
|
<h1>Nano Banana Pro</h1>
|
|
<p class="subtitle">AI Image Generation & Iterative Editing</p>
|
|
</header>
|
|
|
|
<div id="errorMessage" class="error-message"></div>
|
|
<div id="successMessage" class="success-message"></div>
|
|
|
|
<!-- Prompt Studio Section -->
|
|
<div class="prompt-studio">
|
|
<h2>
|
|
🎬 Cinematography Prompt Studio
|
|
</h2>
|
|
<p class="prompt-studio-subtitle">Generate professional cinematography prompts with real camera and lens physics</p>
|
|
|
|
<div class="studio-grid">
|
|
<div class="studio-field">
|
|
<label for="studioApplication">Application / Lighting</label>
|
|
<select id="studioApplication">
|
|
<optgroup label="Studio & Portrait">
|
|
<option value="Portrait Studio">Portrait Studio</option>
|
|
<option value="Corporate Headshot">Corporate Headshot</option>
|
|
</optgroup>
|
|
<optgroup label="Product & Macro">
|
|
<option value="Product (Crisp)">Product (Crisp)</option>
|
|
<option value="Food Photography">Food Photography</option>
|
|
<option value="Macro: Luxury Jewelry">Macro: Luxury Jewelry</option>
|
|
<option value="Macro: Nature Details">Macro: Nature Details</option>
|
|
<option value="Tech Commercial (Macro)">Tech Commercial (Macro)</option>
|
|
</optgroup>
|
|
<optgroup label="Outdoor & Natural">
|
|
<option value="Golden Hour (Outdoor)" selected>Golden Hour (Outdoor)</option>
|
|
<option value="Blue Hour (City)">Blue Hour (City)</option>
|
|
<option value="Wildlife / Safari">Wildlife / Safari</option>
|
|
</optgroup>
|
|
<optgroup label="Action & Motion">
|
|
<option value="Sports Action">Sports Action</option>
|
|
</optgroup>
|
|
<optgroup label="Creative & Artistic">
|
|
<option value="Neon Cyberpunk">Neon Cyberpunk</option>
|
|
<option value="Nostalgic Memory">Nostalgic Memory</option>
|
|
<option value="Cinematic Horror">Cinematic Horror</option>
|
|
<option value="Cassette Futurism (Retro Sci-Fi)">Cassette Futurism (Retro Sci-Fi)</option>
|
|
<option value="Surreal Infrared">Surreal Infrared</option>
|
|
<option value="Spaghetti Western">Spaghetti Western</option>
|
|
</optgroup>
|
|
<optgroup label="Auteur Styles">
|
|
<option value="Symmetrical Whimsy">Symmetrical Whimsy</option>
|
|
<option value="IMAX Scale Epic">IMAX Scale Epic</option>
|
|
<option value="Clinical Thriller">Clinical Thriller</option>
|
|
<option value="Brutalist Atmosphere">Brutalist Atmosphere</option>
|
|
<option value="Technicolor Dream">Technicolor Dream</option>
|
|
<option value="Obsessive Symmetry">Obsessive Symmetry</option>
|
|
<option value="Hong Kong Nostalgia">Hong Kong Nostalgia</option>
|
|
<option value="Industrial Haze">Industrial Haze</option>
|
|
<option value="Gothic Fantasy">Gothic Fantasy</option>
|
|
</optgroup>
|
|
<optgroup label="Professional Production">
|
|
<option value="LED Volume (Virtual Production)">LED Volume (Virtual Production)</option>
|
|
<option value="Automotive: Showroom">Automotive: Showroom</option>
|
|
<option value="Automotive: Process Trailer">Automotive: Process Trailer</option>
|
|
<option value="Product (Liquid/Splash)">Product (Liquid/Splash)</option>
|
|
<option value="VFX / Green Screen">VFX / Green Screen</option>
|
|
<option value="Knolling / Flat Lay">Knolling / Flat Lay</option>
|
|
</optgroup>
|
|
<optgroup label="Editorial & Fashion">
|
|
<option value="NYC Street Editorial">NYC Street Editorial</option>
|
|
<option value="90s Grunge Editorial">90s Grunge Editorial</option>
|
|
<option value="Fashion Editorial">Fashion Editorial</option>
|
|
<option value="Underground Rave / Flash">Underground Rave / Flash</option>
|
|
</optgroup>
|
|
<optgroup label="Documentary & Journalism">
|
|
<option value="Conflict Photography">Conflict Photography</option>
|
|
<option value="Street Photography">Street Photography</option>
|
|
<option value="Docu / Realism">Docu / Realism</option>
|
|
</optgroup>
|
|
<optgroup label="Architectural & Interior">
|
|
<option value="Architectural Digest Interior">Architectural Digest Interior</option>
|
|
<option value="Architecture">Architecture</option>
|
|
</optgroup>
|
|
<optgroup label="Other">
|
|
<option value="Custom">Custom</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="studio-field">
|
|
<label for="studioCamera">Camera Body</label>
|
|
<select id="studioCamera">
|
|
<option value="Arri Alexa 35" selected>Arri Alexa 35</option>
|
|
<option value="Sony Venice 2">Sony Venice 2</option>
|
|
<option value="Red V-Raptor">Red V-Raptor</option>
|
|
<option value="Arriflex 416">Arriflex 416 (Film)</option>
|
|
<option value="Arricam LT">Arricam LT (Film)</option>
|
|
<option value="Fujifilm GFX 100">Fujifilm GFX 100</option>
|
|
<option value="Phantom Flex4K">Phantom Flex4K</option>
|
|
<option value="Blackmagic URSA Cine 12K">URSA Cine 12K</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="studio-field">
|
|
<label for="studioLens">Lens Kit</label>
|
|
<select id="studioLens">
|
|
<option value="Panavision C-Series">Panavision C-Series</option>
|
|
<option value="Cooke S7/i" selected>Cooke S7/i</option>
|
|
<option value="Canon K-35">Canon K-35</option>
|
|
<option value="Arri Signature">Arri Signature</option>
|
|
<option value="Zeiss Super Speed">Zeiss Super Speed</option>
|
|
<option value="Fujinon GF">Fujinon GF</option>
|
|
<option value="Laowa Probe">Laowa Probe</option>
|
|
<option value="Helios 44-2">Helios 44-2 (Vintage)</option>
|
|
<option value="Canon TS-E">Canon Tilt-Shift</option>
|
|
<option value="Angénieux Optimo">Angénieux Optimo</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="slider-container">
|
|
<div class="slider-header">
|
|
<label>Creative Freedom</label>
|
|
<span class="slider-value" id="creativeFreedomValue">30%</span>
|
|
</div>
|
|
<input type="range" id="creativeFreedom" min="0" max="100" value="30">
|
|
<div style="display: flex; justify-content: space-between; font-size: 0.75rem; color: #666; margin-top: 5px;">
|
|
<span>Strict (Literal)</span>
|
|
<span>Creative (Smart Fill)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="studioScene" style="display: block; margin-bottom: 8px; color: #FFC407; font-weight: 600;">Scene Description</label>
|
|
<textarea
|
|
id="studioScene"
|
|
class="scene-input"
|
|
placeholder="Describe your scene... e.g., 'A vintage car parked in front of a neon-lit diner at dusk'"
|
|
></textarea>
|
|
</div>
|
|
|
|
<button class="enhance-btn" id="enhanceBtn">
|
|
✨ Enhance Prompt with AI
|
|
</button>
|
|
|
|
<div class="enhanced-output" id="enhancedOutput">
|
|
<h3>✅ Enhanced Cinematography Prompt</h3>
|
|
<div class="enhanced-text" id="enhancedText"></div>
|
|
<button class="use-prompt-btn" id="usePromptBtn">Use This Prompt →</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main-content">
|
|
<!-- Left Panel - Controls -->
|
|
<div class="panel">
|
|
<h2>Create or Edit Image</h2>
|
|
|
|
<form id="imageForm">
|
|
<?php if (!$_SESSION['current_image']): ?>
|
|
<div class="form-group">
|
|
<label for="uploadImage">Upload Your Own Image (Optional)</label>
|
|
<input
|
|
type="file"
|
|
id="uploadImage"
|
|
name="uploadImage"
|
|
accept="image/jpeg,image/jpg,image/png,image/webp"
|
|
class="file-input"
|
|
>
|
|
<small style="color: #999; display: block; margin-top: 5px;">
|
|
Upload an image to start editing, or leave empty to generate from scratch
|
|
</small>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="form-group">
|
|
<label for="prompt">Prompt <?php echo !$_SESSION['current_image'] ? '(Optional if uploading)' : ''; ?></label>
|
|
<textarea
|
|
id="prompt"
|
|
name="prompt"
|
|
placeholder="<?php echo $_SESSION['current_image'] ? 'Enter edit instructions (e.g., "make it red", "add sunset", "remove background")' : 'Describe the image you want to create or edit... Leave empty to just upload. Examples: "A cyberpunk city at night with neon signs", "Make this photo look like a painting"'; ?>"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="form-group">
|
|
<label for="aspectRatio">Aspect Ratio</label>
|
|
<select id="aspectRatio" name="aspectRatio">
|
|
<option value="16:9">16:9 (Landscape)</option>
|
|
<option value="1:1">1:1 (Square)</option>
|
|
<option value="9:16">9:16 (Portrait)</option>
|
|
<option value="4:3">4:3</option>
|
|
<option value="3:4">3:4</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="imageSize">Resolution</label>
|
|
<select id="imageSize" name="imageSize">
|
|
<option value="1K">1K (Fast)</option>
|
|
<option value="2K" selected>2K (Balanced)</option>
|
|
<option value="4K">4K (High Quality)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn" id="generateBtn">
|
|
<?php echo $_SESSION['current_image'] ? 'Edit Image' : 'Generate Image'; ?>
|
|
</button>
|
|
|
|
<?php if ($_SESSION['current_image']): ?>
|
|
<button type="button" class="btn btn-secondary" id="resetBtn">
|
|
Start New Image
|
|
</button>
|
|
<?php endif; ?>
|
|
</form>
|
|
|
|
<?php if ($_SESSION['current_image']): ?>
|
|
<div class="quick-actions">
|
|
<button class="quick-btn" onclick="quickEdit('Add dramatic lighting')">Add Lighting</button>
|
|
<button class="quick-btn" onclick="quickEdit('Add sunset in background')">Add Sunset</button>
|
|
<button class="quick-btn" onclick="quickEdit('Make colors more vibrant')">More Vibrant</button>
|
|
<button class="quick-btn" onclick="quickEdit('Add motion blur')">Motion Blur</button>
|
|
<button class="quick-btn" onclick="quickEdit('Make it photorealistic')">Photorealistic</button>
|
|
<button class="quick-btn" onclick="quickEdit('Add depth of field effect')">Depth of Field</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($_SESSION['conversation_history'])): ?>
|
|
<div class="history">
|
|
<h2>Conversation History</h2>
|
|
<?php foreach (array_reverse($_SESSION['conversation_history']) as $item): ?>
|
|
<div class="history-item">
|
|
<div class="history-prompt"><?php echo htmlspecialchars($item['prompt']); ?></div>
|
|
<div class="history-time"><?php echo date('g:i A', $item['timestamp']); ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Right Panel - Image Display -->
|
|
<div class="panel">
|
|
<h2>Generated Image</h2>
|
|
|
|
<div class="image-display <?php echo $_SESSION['current_image'] ? 'has-image' : ''; ?>" id="imageDisplay">
|
|
<?php if ($_SESSION['current_image']): ?>
|
|
<img src="data:<?php echo $_SESSION['current_image_mime']; ?>;base64,<?php echo $_SESSION['current_image']; ?>" alt="Generated Image" id="currentImage">
|
|
<?php else: ?>
|
|
<div class="placeholder">
|
|
<div class="placeholder-icon">🎨</div>
|
|
<p>Your generated image will appear here</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if ($_SESSION['current_image']): ?>
|
|
<div class="image-actions">
|
|
<button class="btn" onclick="downloadImage()">Download Image</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Debug Panel -->
|
|
<div class="debug-panel">
|
|
<button class="debug-toggle" onclick="toggleDebug()">🔍 Toggle Debug Panel</button>
|
|
<div id="debugContent" class="debug-content" style="display: none;">
|
|
<h3>Debug Information</h3>
|
|
|
|
<div class="debug-section">
|
|
<h4>Session Status</h4>
|
|
<pre>Has Current Image: <?php echo $_SESSION['current_image'] ? 'YES' : 'NO'; ?>
|
|
Image MIME Type: <?php echo $_SESSION['current_image_mime'] ?? 'Not Set'; ?>
|
|
Image Data Length: <?php echo $_SESSION['current_image'] ? strlen($_SESSION['current_image']) . ' chars' : '0'; ?>
|
|
Conversation History: <?php echo count($_SESSION['conversation_history']); ?> items
|
|
Image History: <?php echo count($_SESSION['image_history']); ?> items</pre>
|
|
<button class="quick-btn" onclick="loadServerLogs()" style="margin-top: 10px;">📋 Load Server Logs</button>
|
|
</div>
|
|
|
|
<div class="debug-section" id="serverLogs" style="display: none;">
|
|
<h4>Recent Server Logs</h4>
|
|
<pre id="serverLogsData"></pre>
|
|
</div>
|
|
|
|
<?php if (!empty($_SESSION['conversation_history'])): ?>
|
|
<div class="debug-section">
|
|
<h4>Recent Prompts</h4>
|
|
<pre><?php
|
|
$recent = array_slice($_SESSION['conversation_history'], -3);
|
|
foreach ($recent as $item) {
|
|
echo date('H:i:s', $item['timestamp']) . ' [' . $item['type'] . ']: ' . htmlspecialchars($item['prompt']) . "\n";
|
|
}
|
|
?></pre>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="debug-section" id="lastRequest" style="display: none;">
|
|
<h4>Last API Request</h4>
|
|
<pre id="lastRequestData"></pre>
|
|
</div>
|
|
|
|
<div class="debug-section" id="lastResponse" style="display: none;">
|
|
<h4>Last API Response</h4>
|
|
<pre id="lastResponseData"></pre>
|
|
</div>
|
|
|
|
<div class="debug-section debug-error" id="lastError" style="display: none;">
|
|
<h4>Last Error</h4>
|
|
<pre id="lastErrorData"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const form = document.getElementById('imageForm');
|
|
const generateBtn = document.getElementById('generateBtn');
|
|
const resetBtn = document.getElementById('resetBtn');
|
|
const imageDisplay = document.getElementById('imageDisplay');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
const successMessage = document.getElementById('successMessage');
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const prompt = document.getElementById('prompt').value.trim();
|
|
const aspectRatio = document.getElementById('aspectRatio').value;
|
|
const imageSize = document.getElementById('imageSize').value;
|
|
const uploadInput = document.getElementById('uploadImage');
|
|
|
|
// Check if we have either a prompt or an upload
|
|
const hasUploadFile = uploadInput && uploadInput.files && uploadInput.files[0];
|
|
const hasExistingImage = <?php echo $_SESSION['current_image'] ? 'true' : 'false'; ?>;
|
|
|
|
if (!prompt && !hasUploadFile && !hasExistingImage) {
|
|
showError('Please enter a prompt or upload an image');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
generateBtn.disabled = true;
|
|
generateBtn.textContent = hasUploadFile ? 'Uploading...' : 'Processing...';
|
|
imageDisplay.innerHTML = '<div class="loading"><div class="spinner"></div><p>' + (hasUploadFile ? 'Uploading and processing...' : 'Creating your image...') + '</p></div>';
|
|
hideMessages();
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('action', 'generate');
|
|
formData.append('prompt', prompt);
|
|
formData.append('aspectRatio', aspectRatio);
|
|
formData.append('imageSize', imageSize);
|
|
|
|
// Handle file upload if present
|
|
let hasUpload = false;
|
|
if (uploadInput && uploadInput.files && uploadInput.files[0]) {
|
|
const file = uploadInput.files[0];
|
|
hasUpload = true;
|
|
|
|
// Validate file size (max 10MB)
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
throw new Error('File too large. Maximum size is 10MB.');
|
|
}
|
|
|
|
// Convert to base64
|
|
const base64 = await fileToBase64(file);
|
|
formData.append('uploadedImage', base64);
|
|
formData.append('uploadedImageType', file.type);
|
|
}
|
|
|
|
// Log request
|
|
logDebug('request', {
|
|
action: 'generate',
|
|
prompt: prompt,
|
|
aspectRatio: aspectRatio,
|
|
imageSize: imageSize,
|
|
hasExistingImage: <?php echo $_SESSION['current_image'] ? 'true' : 'false'; ?>,
|
|
hasUpload: hasUpload
|
|
});
|
|
|
|
const response = await fetch('api.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
// Log response
|
|
logDebug('response', result);
|
|
|
|
if (result.success) {
|
|
showSuccess('Image generated successfully!');
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 500);
|
|
} else {
|
|
showError(result.error || 'Failed to generate image');
|
|
logDebug('error', result.error || 'Failed to generate image');
|
|
generateBtn.disabled = false;
|
|
generateBtn.textContent = '<?php echo $_SESSION['current_image'] ? 'Edit Image' : 'Generate Image'; ?>';
|
|
}
|
|
} catch (error) {
|
|
showError('Network error: ' + error.message);
|
|
logDebug('error', 'Network error: ' + error.message);
|
|
generateBtn.disabled = false;
|
|
generateBtn.textContent = '<?php echo $_SESSION['current_image'] ? 'Edit Image' : 'Generate Image'; ?>';
|
|
}
|
|
});
|
|
|
|
<?php if ($_SESSION['current_image']): ?>
|
|
resetBtn.addEventListener('click', async () => {
|
|
if (confirm('Are you sure you want to start a new image? This will clear your current image and history.')) {
|
|
const formData = new FormData();
|
|
formData.append('action', 'reset');
|
|
|
|
await fetch('api.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
window.location.reload();
|
|
}
|
|
});
|
|
<?php endif; ?>
|
|
|
|
function quickEdit(editPrompt) {
|
|
document.getElementById('prompt').value = editPrompt;
|
|
form.dispatchEvent(new Event('submit'));
|
|
}
|
|
|
|
function downloadImage() {
|
|
const img = document.getElementById('currentImage');
|
|
const link = document.createElement('a');
|
|
link.href = img.src;
|
|
link.download = 'nano-banana-pro-' + Date.now() + '.png';
|
|
link.click();
|
|
}
|
|
|
|
function showError(message) {
|
|
errorMessage.textContent = message;
|
|
errorMessage.classList.add('show');
|
|
setTimeout(() => errorMessage.classList.remove('show'), 5000);
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
successMessage.textContent = message;
|
|
successMessage.classList.add('show');
|
|
setTimeout(() => successMessage.classList.remove('show'), 3000);
|
|
}
|
|
|
|
function hideMessages() {
|
|
errorMessage.classList.remove('show');
|
|
successMessage.classList.remove('show');
|
|
}
|
|
|
|
function toggleDebug() {
|
|
const content = document.getElementById('debugContent');
|
|
content.style.display = content.style.display === 'none' ? 'block' : 'none';
|
|
}
|
|
|
|
function logDebug(type, data) {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
|
|
if (type === 'request') {
|
|
document.getElementById('lastRequest').style.display = 'block';
|
|
document.getElementById('lastRequestData').textContent =
|
|
`[${timestamp}] Request:\n${JSON.stringify(data, null, 2)}`;
|
|
} else if (type === 'response') {
|
|
document.getElementById('lastResponse').style.display = 'block';
|
|
document.getElementById('lastResponseData').textContent =
|
|
`[${timestamp}] Response:\n${JSON.stringify(data, null, 2)}`;
|
|
} else if (type === 'error') {
|
|
document.getElementById('lastError').style.display = 'block';
|
|
document.getElementById('lastErrorData').textContent =
|
|
`[${timestamp}] Error:\n${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}`;
|
|
}
|
|
}
|
|
|
|
function fileToBase64(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
// Get base64 string without data URL prefix
|
|
const base64 = reader.result.split(',')[1];
|
|
resolve(base64);
|
|
};
|
|
reader.onerror = reject;
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
async function loadServerLogs() {
|
|
try {
|
|
const response = await fetch('get_logs.php');
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const logsSection = document.getElementById('serverLogs');
|
|
const logsData = document.getElementById('serverLogsData');
|
|
|
|
logsSection.style.display = 'block';
|
|
logsData.textContent = data.logs.join('\n');
|
|
} else {
|
|
showError('Failed to load server logs');
|
|
}
|
|
} catch (error) {
|
|
showError('Error loading logs: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Prompt Studio Functionality
|
|
const creativeFreedomSlider = document.getElementById('creativeFreedom');
|
|
const creativeFreedomValue = document.getElementById('creativeFreedomValue');
|
|
const enhanceBtn = document.getElementById('enhanceBtn');
|
|
const studioScene = document.getElementById('studioScene');
|
|
const enhancedOutput = document.getElementById('enhancedOutput');
|
|
const enhancedText = document.getElementById('enhancedText');
|
|
const usePromptBtn = document.getElementById('usePromptBtn');
|
|
const aspectRatioSelect = document.getElementById('aspectRatio');
|
|
|
|
// Update slider value display
|
|
creativeFreedomSlider.addEventListener('input', (e) => {
|
|
creativeFreedomValue.textContent = e.target.value + '%';
|
|
});
|
|
|
|
// Enhance prompt with AI
|
|
enhanceBtn.addEventListener('click', async () => {
|
|
const sceneDescription = studioScene.value.trim();
|
|
|
|
if (!sceneDescription) {
|
|
showError('Please enter a scene description');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
enhanceBtn.disabled = true;
|
|
enhanceBtn.innerHTML = '⏳ Enhancing...';
|
|
enhancedOutput.classList.remove('show');
|
|
|
|
try {
|
|
const requestData = {
|
|
sceneDescription: sceneDescription,
|
|
camera: document.getElementById('studioCamera').value,
|
|
lens: document.getElementById('studioLens').value,
|
|
application: document.getElementById('studioApplication').value,
|
|
aspectRatio: aspectRatioSelect.value,
|
|
creativeFreedom: creativeFreedomSlider.value / 100
|
|
};
|
|
|
|
const response = await fetch('enhance_prompt.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(requestData)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
enhancedText.textContent = result.enhancedPrompt;
|
|
enhancedOutput.classList.add('show');
|
|
showSuccess('Prompt enhanced successfully!');
|
|
} else {
|
|
showError('Enhancement failed: ' + (result.error || 'Unknown error'));
|
|
}
|
|
} catch (error) {
|
|
showError('Network error: ' + error.message);
|
|
} finally {
|
|
enhanceBtn.disabled = false;
|
|
enhanceBtn.innerHTML = '✨ Enhance Prompt with AI';
|
|
}
|
|
});
|
|
|
|
// Use the enhanced prompt in the main form
|
|
usePromptBtn.addEventListener('click', () => {
|
|
const enhanced = enhancedText.textContent;
|
|
document.getElementById('prompt').value = enhanced;
|
|
|
|
// Also update aspect ratio in main form to match studio selection
|
|
document.getElementById('aspectRatio').value = aspectRatioSelect.value;
|
|
|
|
// Scroll to the main form
|
|
document.querySelector('.main-content').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
|
|
showSuccess('Prompt transferred! Ready to generate image.');
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|