Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -62,7 +62,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN
|
|
| 62 |
try:
|
| 63 |
if file_name == DATA_FILE:
|
| 64 |
with open(file_name, 'w', encoding='utf-8') as f:
|
| 65 |
-
json.dump({}, f)
|
| 66 |
except Exception:
|
| 67 |
pass
|
| 68 |
success = True
|
|
@@ -110,18 +110,22 @@ def load_data():
|
|
| 110 |
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
| 111 |
data = json.load(f)
|
| 112 |
if not isinstance(data, dict):
|
| 113 |
-
data = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 115 |
if download_db_from_hf(specific_file=DATA_FILE):
|
| 116 |
try:
|
| 117 |
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
| 118 |
data = json.load(f)
|
| 119 |
-
if not
|
| 120 |
-
|
| 121 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 122 |
-
data = {}
|
| 123 |
else:
|
| 124 |
-
data = {}
|
| 125 |
return data
|
| 126 |
|
| 127 |
def save_data(data):
|
|
@@ -178,113 +182,39 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 178 |
--text-on-accent: #003C43;
|
| 179 |
--danger: #E57373;
|
| 180 |
--warning: #ffcc80;
|
|
|
|
|
|
|
| 181 |
}
|
| 182 |
* { box-sizing: border-box; }
|
| 183 |
body { font-family: 'Montserrat', sans-serif; background-color: var(--bg-light); color: var(--text-dark); margin: 0; padding: 15px; }
|
| 184 |
.container { max-width: 900px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 12px; box-shadow: 0 3px 15px rgba(0,0,0,0.08); }
|
| 185 |
-
h1 { font-weight: 600; color: var(--bg-medium); margin-bottom: 25px; text-align: center; font-size: 1.5rem; }
|
| 186 |
-
|
| 187 |
.section { margin-bottom: 25px; }
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
flex-direction: column;
|
| 192 |
-
gap: 15px;
|
| 193 |
-
background: #f8f9fa;
|
| 194 |
-
padding: 15px;
|
| 195 |
-
border-radius: 10px;
|
| 196 |
-
border: 1px solid #e9ecef;
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
input[type="text"] {
|
| 200 |
-
width: 100%;
|
| 201 |
-
padding: 12px;
|
| 202 |
-
border: 1px solid #ddd;
|
| 203 |
-
border-radius: 8px;
|
| 204 |
-
font-size: 1rem;
|
| 205 |
-
font-family: inherit;
|
| 206 |
-
background: #fff;
|
| 207 |
-
-webkit-appearance: none;
|
| 208 |
-
}
|
| 209 |
-
|
| 210 |
-
.controls-row {
|
| 211 |
-
display: flex;
|
| 212 |
-
align-items: center;
|
| 213 |
-
justify-content: space-between;
|
| 214 |
-
gap: 15px;
|
| 215 |
-
flex-wrap: wrap;
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
.radio-group { display: flex; gap: 15px; }
|
| 219 |
.radio-group label { cursor: pointer; display: flex; align-items: center; gap: 6px; font-weight: 500; font-size: 0.95rem; }
|
| 220 |
-
|
| 221 |
-
.button {
|
| 222 |
-
padding: 12px 20px;
|
| 223 |
-
border: none;
|
| 224 |
-
border-radius: 8px;
|
| 225 |
-
background-color: var(--accent);
|
| 226 |
-
color: var(--text-on-accent);
|
| 227 |
-
font-weight: 600;
|
| 228 |
-
cursor: pointer;
|
| 229 |
-
text-decoration: none;
|
| 230 |
-
display: inline-flex;
|
| 231 |
-
align-items: center;
|
| 232 |
-
justify-content: center;
|
| 233 |
-
gap: 8px;
|
| 234 |
-
font-size: 1rem;
|
| 235 |
-
transition: opacity 0.2s;
|
| 236 |
-
}
|
| 237 |
.button:hover { opacity: 0.9; }
|
| 238 |
.button:active { transform: scale(0.98); }
|
| 239 |
-
|
| 240 |
.env-list { list-style: none; padding: 0; margin: 0; }
|
| 241 |
-
.env-item {
|
| 242 |
-
background: #fff;
|
| 243 |
-
border: 1px solid #e0e0e0;
|
| 244 |
-
border-radius: 10px;
|
| 245 |
-
padding: 15px;
|
| 246 |
-
margin-bottom: 12px;
|
| 247 |
-
display: grid;
|
| 248 |
-
grid-template-columns: 1fr auto;
|
| 249 |
-
align-items: center;
|
| 250 |
-
gap: 15px;
|
| 251 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.02);
|
| 252 |
-
}
|
| 253 |
-
|
| 254 |
.env-details { display: flex; flex-direction: column; gap: 4px; overflow: hidden; }
|
| 255 |
.env-header { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
| 256 |
.env-id { font-weight: 700; color: var(--bg-medium); font-size: 1.1rem; }
|
| 257 |
.env-keyword { font-style: italic; color: #666; font-size: 0.9rem;}
|
| 258 |
-
|
| 259 |
-
.env-
|
| 260 |
-
font-size: 0.9rem;
|
| 261 |
-
color: #007bff;
|
| 262 |
-
word-break: break-all;
|
| 263 |
-
text-decoration: none;
|
| 264 |
-
padding: 5px 0;
|
| 265 |
-
display: block;
|
| 266 |
-
}
|
| 267 |
-
|
| 268 |
-
.env-type-badge {
|
| 269 |
-
font-size: 0.75rem;
|
| 270 |
-
padding: 3px 8px;
|
| 271 |
-
border-radius: 20px;
|
| 272 |
-
font-weight: bold;
|
| 273 |
-
text-transform: uppercase;
|
| 274 |
-
white-space: nowrap;
|
| 275 |
-
}
|
| 276 |
.type-open { background-color: #d4edda; color: #155724; }
|
| 277 |
.type-closed { background-color: #f8d7da; color: #721c24; }
|
| 278 |
-
|
| 279 |
-
.
|
| 280 |
-
.stats-button { background-color: #5d4037; color: white; padding: 10px 15px; font-size: 0.9rem;}
|
| 281 |
.delete-button { background-color: var(--danger); color: white; padding: 10px 15px; font-size: 0.9rem;}
|
| 282 |
-
|
| 283 |
.message { padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: center; font-size: 0.95rem; }
|
| 284 |
.message.success { background-color: #d4edda; color: #155724; }
|
| 285 |
.message.error { background-color: #f8d7da; color: #721c24; }
|
| 286 |
-
|
| 287 |
-
/* Modal */
|
| 288 |
.modal { display: none; position: fixed; z-index: 2000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(2px); }
|
| 289 |
.modal-content { background-color: #fff; margin: 15% auto; padding: 25px; width: 90%; max-width: 600px; border-radius: 12px; position: relative; box-shadow: 0 5px 20px rgba(0,0,0,0.2); }
|
| 290 |
.close-modal { color: #888; position: absolute; right: 15px; top: 10px; font-size: 30px; font-weight: bold; cursor: pointer; padding: 5px; }
|
|
@@ -292,46 +222,18 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 292 |
.stats-table th, .stats-table td { border: 1px solid #eee; padding: 10px 8px; text-align: left; }
|
| 293 |
.stats-table th { background-color: var(--bg-medium); color: white; }
|
| 294 |
.stats-table tr:nth-child(even) { background-color: #f9f9f9; }
|
| 295 |
-
|
| 296 |
-
|
| 297 |
@media (max-width: 600px) {
|
| 298 |
body { padding: 10px; }
|
| 299 |
.container { padding: 15px; }
|
| 300 |
h1 { font-size: 1.3rem; margin-bottom: 20px; }
|
| 301 |
-
|
| 302 |
-
.
|
| 303 |
-
flex-direction: column;
|
| 304 |
-
align-items: stretch;
|
| 305 |
-
}
|
| 306 |
-
.radio-group {
|
| 307 |
-
justify-content: space-between;
|
| 308 |
-
background: #fff;
|
| 309 |
-
padding: 10px;
|
| 310 |
-
border-radius: 8px;
|
| 311 |
-
border: 1px solid #ddd;
|
| 312 |
-
}
|
| 313 |
.button { width: 100%; padding: 14px; }
|
| 314 |
-
|
| 315 |
-
.env-
|
| 316 |
-
|
| 317 |
-
gap: 12px;
|
| 318 |
-
position: relative;
|
| 319 |
-
padding-bottom: 60px; /* Space for buttons */
|
| 320 |
-
}
|
| 321 |
-
|
| 322 |
-
.env-actions {
|
| 323 |
-
position: absolute;
|
| 324 |
-
bottom: 15px;
|
| 325 |
-
left: 15px;
|
| 326 |
-
right: 15px;
|
| 327 |
-
display: grid;
|
| 328 |
-
grid-template-columns: 3fr 1fr;
|
| 329 |
-
gap: 10px;
|
| 330 |
-
}
|
| 331 |
-
|
| 332 |
-
.env-actions button { width: 100%; }
|
| 333 |
-
.env-actions form { width: 100%; }
|
| 334 |
-
|
| 335 |
.modal-content { margin: 10% auto; width: 95%; padding: 20px 15px; }
|
| 336 |
.stats-table th, .stats-table td { font-size: 0.75rem; padding: 6px 4px; }
|
| 337 |
}
|
|
@@ -377,26 +279,68 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 377 |
{{ 'ЗАКРЫТАЯ' if env.type == 'closed' else 'ОТКРЫТАЯ' }}
|
| 378 |
</span>
|
| 379 |
<small style="color:#888">{{ env.hits }} <i class="fas fa-eye"></i></small>
|
|
|
|
|
|
|
|
|
|
| 380 |
</div>
|
| 381 |
<span class="env-keyword">{{ env.keyword }}</span>
|
| 382 |
<a href="{{ env.link }}" class="env-link" target="_blank">{{ env.link }}</a>
|
| 383 |
</div>
|
| 384 |
<div class="env-actions">
|
| 385 |
-
<button class="button
|
| 386 |
-
|
| 387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
</form>
|
| 389 |
</div>
|
| 390 |
</li>
|
| 391 |
{% endfor %}
|
| 392 |
</ul>
|
| 393 |
{% else %}
|
| 394 |
-
<div style="text-align:center; padding: 20px; color: #888;">Список пуст</div>
|
| 395 |
{% endif %}
|
| 396 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
</div>
|
| 398 |
|
| 399 |
-
<!-- Modal -->
|
| 400 |
<div id="statsModal" class="modal">
|
| 401 |
<div class="modal-content">
|
| 402 |
<span class="close-modal" onclick="closeStats()">×</span>
|
|
@@ -721,13 +665,13 @@ select option {
|
|
| 721 |
display: contents;
|
| 722 |
}
|
| 723 |
|
| 724 |
-
.
|
| 725 |
display: grid;
|
| 726 |
grid-template-columns: 1fr 1fr;
|
| 727 |
gap: 10px;
|
| 728 |
}
|
| 729 |
|
| 730 |
-
.
|
| 731 |
padding: 12px 10px;
|
| 732 |
background-color: var(--input-bg);
|
| 733 |
border: 1px solid var(--border);
|
|
@@ -741,12 +685,12 @@ select option {
|
|
| 741 |
width: 100%;
|
| 742 |
}
|
| 743 |
|
| 744 |
-
.
|
| 745 |
border-color: var(--primary);
|
| 746 |
color: var(--text);
|
| 747 |
}
|
| 748 |
|
| 749 |
-
.
|
| 750 |
background-color: var(--primary);
|
| 751 |
color: #000;
|
| 752 |
border-color: var(--primary);
|
|
@@ -890,13 +834,13 @@ select option {
|
|
| 890 |
</div>
|
| 891 |
<div class="form-group full-width">
|
| 892 |
<label>Эстетика</label>
|
| 893 |
-
<div id="styleSelector" class="
|
| 894 |
-
<button type="button" class="
|
| 895 |
-
<button type="button" class="
|
| 896 |
-
<button type="button" class="
|
| 897 |
-
<button type="button" class="
|
| 898 |
-
<button type="button" class="
|
| 899 |
-
<button type="button" class="
|
| 900 |
</div>
|
| 901 |
</div>
|
| 902 |
<div class="form-group">
|
|
@@ -910,19 +854,19 @@ select option {
|
|
| 910 |
</select>
|
| 911 |
</div>
|
| 912 |
<div class="form-group full-width">
|
| 913 |
-
<label
|
| 914 |
-
<
|
| 915 |
-
<
|
| 916 |
-
<
|
| 917 |
-
<
|
| 918 |
-
<
|
| 919 |
-
<
|
| 920 |
-
<
|
| 921 |
-
<
|
| 922 |
-
<
|
| 923 |
-
<
|
| 924 |
-
<
|
| 925 |
-
</
|
| 926 |
</div>
|
| 927 |
<div class="form-group full-width">
|
| 928 |
<label for="model_details">Одежда и Детали (Опишите ткань и фасон!)</label>
|
|
@@ -1231,14 +1175,14 @@ function toggleCreativeMode() {
|
|
| 1231 |
document.getElementById('object_background').disabled = isCreative;
|
| 1232 |
}
|
| 1233 |
|
| 1234 |
-
function
|
| 1235 |
-
const
|
| 1236 |
-
if (!
|
| 1237 |
-
const
|
| 1238 |
|
| 1239 |
-
|
| 1240 |
btn.addEventListener('click', () => {
|
| 1241 |
-
|
| 1242 |
btn.classList.add('active');
|
| 1243 |
});
|
| 1244 |
});
|
|
@@ -1251,11 +1195,11 @@ async function processAndOpen() {
|
|
| 1251 |
|
| 1252 |
if (currentMode === 'model') {
|
| 1253 |
const isMyModel = document.getElementById('my_model_checkbox').checked;
|
| 1254 |
-
const style = document.querySelector('#styleSelector .
|
| 1255 |
const shotType = document.getElementById('shotType').value;
|
| 1256 |
const bodyType = document.getElementById('bodyType').value;
|
| 1257 |
const pose = document.getElementById('pose').value;
|
| 1258 |
-
const location = document.
|
| 1259 |
const light = document.getElementById('light').value;
|
| 1260 |
const camera = document.getElementById('camera').value;
|
| 1261 |
|
|
@@ -1265,12 +1209,12 @@ async function processAndOpen() {
|
|
| 1265 |
INSTRUCTIONS FOR AI: This is a virtual try-on task. You will be given two images.
|
| 1266 |
- Image 1 (Model): Use this as the reference for the model's face, identity, pose, and body shape.
|
| 1267 |
- Image 2 (Garment): Use this image for the clothing item.
|
| 1268 |
-
Task: Accurately dress the model from Image 1 in the clothing from Image 2. The final image must preserve the model's exact likeness
|
| 1269 |
Style: ${style}, hyper-detailed, luxury brand campaign, Vogue aesthetic, impeccable.
|
| 1270 |
Composition: ${shotType}.
|
| 1271 |
Final Scene: Place the model in the following environment: ${location}.
|
| 1272 |
Lighting: The scene should have ${light}.
|
| 1273 |
-
Technical: Masterpiece professional photograph, shot on ${camera}, 8k UHD, tack sharp focus, breathtaking detail, perfect color grading.`;
|
| 1274 |
} else {
|
| 1275 |
const age = document.getElementById('age').value;
|
| 1276 |
const nationality = document.getElementById('nationality').value;
|
|
@@ -1280,15 +1224,15 @@ Technical: Masterpiece professional photograph, shot on ${camera}, 8k UHD, tack
|
|
| 1280 |
const emotion = document.getElementById('emotion').value;
|
| 1281 |
const details = document.getElementById('model_details').value || "high-end fashion garments";
|
| 1282 |
|
| 1283 |
-
fullPrompt = `${envKeyword}, style:: ${style}, high fashion editorial, luxury brand campaign
|
| 1284 |
composition:: ${shotType}.
|
| 1285 |
subject:: An ultra-high-resolution, flawless photograph of a striking ${age} ${nationality} high fashion model.
|
| 1286 |
-
model_characteristics:: physique ${bodyType}, ${hairColor} ${hairstyle}
|
| 1287 |
clothing_focus:: The model is wearing ${details}, emphasizing haute couture craftsmanship.
|
| 1288 |
-
texture_&_material_fidelity:: Extreme macro precision on textiles. Render the fabric weave, thread count, visible stitching, material weight, realistic creases
|
| 1289 |
-
human_realism_details:: Capture flawless yet realistic skin texture. Perfect complexion, highlighting bone structure
|
| 1290 |
-
scene_environment:: ${location}, creating a sophisticated
|
| 1291 |
-
technical:: Masterpiece professional photograph, ${light} meticulously crafted to sculpt the subject, shot on ${camera}, 8k UHD, tack sharp focus, breathtaking detail, uncompressed, color graded to perfection, award-winning photography.`;
|
| 1292 |
}
|
| 1293 |
|
| 1294 |
} else if (currentMode === 'children') {
|
|
@@ -1321,12 +1265,12 @@ technical:: Masterpiece professional photograph, ${light} meticulously crafted t
|
|
| 1321 |
|
| 1322 |
fullPrompt = `${envKeyword}, style:: ${style}, luxury children's fashion campaign, cinematic storytelling, enchanting, ultra-photorealistic.
|
| 1323 |
composition:: ${shotType}.
|
| 1324 |
-
subject:: ${subject} The photograph must look like a
|
| 1325 |
clothing_focus:: The child is wearing ${clothing_details}, presented as a luxury garment.
|
| 1326 |
-
texture_&_material_fidelity:: Macro-level detail on clothing textures. Focus on the weave of cotton, the softness of wool, the texture of denim. Show realistic wrinkles, creases from movement, and even subtle fabric pilling.
|
| 1327 |
-
human_realism_details:: Capture the pure
|
| 1328 |
scene_activity:: ${pose_info} The location is ${location}, creating a whimsical and high-end narrative.
|
| 1329 |
-
technical:: Masterpiece photograph, ${light} creating a magical and soft atmosphere, shot on Fujifilm XT4, 56mm F1.2 lens, 8k, tack sharp focus, impeccable detail, perfect color grading, looks like a real captured moment
|
| 1330 |
|
| 1331 |
} else {
|
| 1332 |
const objectName = document.getElementById('object_name').value || "a product";
|
|
@@ -1345,7 +1289,7 @@ technical:: Masterpiece photograph, ${light} creating a magical and soft atmosph
|
|
| 1345 |
|
| 1346 |
fullPrompt = `${envKeyword}, style:: Luxury product advertising, ${objectStyle}, sophisticated, sleek, ultra-photorealistic.
|
| 1347 |
subject:: A breathtaking, hyper-realistic photograph of the luxury product: ${objectName}. The image must evoke desire and exclusivity.
|
| 1348 |
-
material_focus:: Achieve
|
| 1349 |
scene_context:: Placed ${background}. Additional details: ${objectDetails}, arranged with artistic precision.
|
| 1350 |
composition:: ${objectComposition}, creating a powerful and elegant visual statement.
|
| 1351 |
technical:: Advertisement-grade photograph, ${objectLighting} designed to accentuate the product's luxury form, 8k UHD resolution, flawless focus, extreme macro detail, advanced ray-traced reflections, impeccably clean, exudes quality and high-end appeal, masterpiece.`;
|
|
@@ -1377,7 +1321,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 1377 |
switchMode('model');
|
| 1378 |
switchChildrenSubMode('newborn');
|
| 1379 |
autoAdjustDefaults();
|
| 1380 |
-
|
|
|
|
| 1381 |
});
|
| 1382 |
</script>
|
| 1383 |
|
|
@@ -1393,19 +1338,28 @@ def index():
|
|
| 1393 |
def admhosto():
|
| 1394 |
data = load_data()
|
| 1395 |
environments_data = []
|
| 1396 |
-
for env_id, env_data in data.items():
|
| 1397 |
environments_data.append({
|
| 1398 |
"id": env_id,
|
| 1399 |
"keyword": env_data.get("keyword", "N/A"),
|
| 1400 |
"type": env_data.get("type", "closed"),
|
| 1401 |
"hits": env_data.get("hits", 0),
|
| 1402 |
"created_at": env_data.get("created_at", ""),
|
| 1403 |
-
"link": url_for('serve_env', env_id=env_id, _external=True)
|
|
|
|
| 1404 |
})
|
| 1405 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1406 |
environments_data.sort(key=lambda x: x['created_at'], reverse=True)
|
| 1407 |
|
| 1408 |
-
return render_template_string(ADMHOSTO_TEMPLATE, environments=environments_data)
|
| 1409 |
|
| 1410 |
@app.route('/admhosto/create', methods=['POST'])
|
| 1411 |
def create_environment():
|
|
@@ -1419,10 +1373,10 @@ def create_environment():
|
|
| 1419 |
|
| 1420 |
while True:
|
| 1421 |
new_id = ''.join(random.choices(string.digits, k=6))
|
| 1422 |
-
if new_id not in all_data:
|
| 1423 |
break
|
| 1424 |
|
| 1425 |
-
all_data[new_id] = {
|
| 1426 |
"keyword": keyword,
|
| 1427 |
"type": env_type,
|
| 1428 |
"device_token": None,
|
|
@@ -1437,18 +1391,60 @@ def create_environment():
|
|
| 1437 |
@app.route('/admhosto/delete/<env_id>', methods=['POST'])
|
| 1438 |
def delete_environment(env_id):
|
| 1439 |
all_data = load_data()
|
| 1440 |
-
if env_id in all_data:
|
| 1441 |
-
|
|
|
|
| 1442 |
save_data(all_data)
|
| 1443 |
-
flash(f'Среда {env_id} была
|
| 1444 |
else:
|
| 1445 |
flash(f'Среда {env_id} не найдена.', 'error')
|
| 1446 |
return redirect(url_for('admhosto'))
|
| 1447 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1448 |
@app.route('/admhosto/stats/<env_id>')
|
| 1449 |
def get_env_stats(env_id):
|
| 1450 |
data = load_data()
|
| 1451 |
-
env_data = data.get(env_id)
|
| 1452 |
if not env_data:
|
| 1453 |
return jsonify({"error": "Среда не найдена"}), 404
|
| 1454 |
|
|
@@ -1480,7 +1476,7 @@ def get_env_stats(env_id):
|
|
| 1480 |
@app.route('/env/<env_id>')
|
| 1481 |
def serve_env(env_id):
|
| 1482 |
data = load_data()
|
| 1483 |
-
env_data = data.get(env_id)
|
| 1484 |
if not env_data:
|
| 1485 |
return "Среда не найдена.", 404
|
| 1486 |
|
|
@@ -1490,7 +1486,7 @@ def serve_env(env_id):
|
|
| 1490 |
current_log = {
|
| 1491 |
"time": datetime.utcnow().isoformat(),
|
| 1492 |
"ip": request.remote_addr,
|
| 1493 |
-
"ua": request.headers.get('User-Agent')[:
|
| 1494 |
}
|
| 1495 |
|
| 1496 |
env_data['hits'] = env_data.get('hits', 0) + 1
|
|
@@ -1501,7 +1497,7 @@ def serve_env(env_id):
|
|
| 1501 |
if len(env_data['logs']) > 30:
|
| 1502 |
env_data['logs'] = env_data['logs'][-30:]
|
| 1503 |
|
| 1504 |
-
data[env_id] = env_data
|
| 1505 |
save_data(data)
|
| 1506 |
|
| 1507 |
if env_type == 'open':
|
|
@@ -1538,11 +1534,11 @@ def serve_env(env_id):
|
|
| 1538 |
else:
|
| 1539 |
new_token = ''.join(random.choices(string.ascii_letters + string.digits, k=40))
|
| 1540 |
env_data['device_token'] = new_token
|
| 1541 |
-
data[env_id] = env_data
|
| 1542 |
save_data(data)
|
| 1543 |
|
| 1544 |
resp = make_response(render_template_string(SYNKRIS_LOOK_TEMPLATE, keyword=keyword))
|
| 1545 |
-
resp.set_cookie(f'access_token_{env_id}', new_token, max_age=31536000, httponly=True)
|
| 1546 |
return resp
|
| 1547 |
|
| 1548 |
if __name__ == '__main__':
|
|
|
|
| 62 |
try:
|
| 63 |
if file_name == DATA_FILE:
|
| 64 |
with open(file_name, 'w', encoding='utf-8') as f:
|
| 65 |
+
json.dump({"environments": {}, "archive": {}}, f)
|
| 66 |
except Exception:
|
| 67 |
pass
|
| 68 |
success = True
|
|
|
|
| 110 |
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
| 111 |
data = json.load(f)
|
| 112 |
if not isinstance(data, dict):
|
| 113 |
+
data = {"environments": {}, "archive": {}}
|
| 114 |
+
if "environments" not in data:
|
| 115 |
+
data["environments"] = {}
|
| 116 |
+
if "archive" not in data:
|
| 117 |
+
data["archive"] = {}
|
| 118 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 119 |
if download_db_from_hf(specific_file=DATA_FILE):
|
| 120 |
try:
|
| 121 |
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
| 122 |
data = json.load(f)
|
| 123 |
+
if "environments" not in data or "archive" not in data:
|
| 124 |
+
data = {"environments": data, "archive": {}}
|
| 125 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 126 |
+
data = {"environments": {}, "archive": {}}
|
| 127 |
else:
|
| 128 |
+
data = {"environments": {}, "archive": {}}
|
| 129 |
return data
|
| 130 |
|
| 131 |
def save_data(data):
|
|
|
|
| 182 |
--text-on-accent: #003C43;
|
| 183 |
--danger: #E57373;
|
| 184 |
--warning: #ffcc80;
|
| 185 |
+
--info: #64b5f6;
|
| 186 |
+
--grey: #9e9e9e;
|
| 187 |
}
|
| 188 |
* { box-sizing: border-box; }
|
| 189 |
body { font-family: 'Montserrat', sans-serif; background-color: var(--bg-light); color: var(--text-dark); margin: 0; padding: 15px; }
|
| 190 |
.container { max-width: 900px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 12px; box-shadow: 0 3px 15px rgba(0,0,0,0.08); }
|
| 191 |
+
h1, h2 { font-weight: 600; color: var(--bg-medium); margin-bottom: 25px; text-align: center; font-size: 1.5rem; }
|
| 192 |
+
h2 { font-size: 1.3rem; margin-top: 40px; border-top: 1px solid #eee; padding-top: 25px; }
|
| 193 |
.section { margin-bottom: 25px; }
|
| 194 |
+
.add-env-form { display: flex; flex-direction: column; gap: 15px; background: #f8f9fa; padding: 15px; border-radius: 10px; border: 1px solid #e9ecef; }
|
| 195 |
+
input[type="text"] { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 1rem; font-family: inherit; background: #fff; -webkit-appearance: none; }
|
| 196 |
+
.controls-row { display: flex; align-items: center; justify-content: space-between; gap: 15px; flex-wrap: wrap; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
.radio-group { display: flex; gap: 15px; }
|
| 198 |
.radio-group label { cursor: pointer; display: flex; align-items: center; gap: 6px; font-weight: 500; font-size: 0.95rem; }
|
| 199 |
+
.button { padding: 12px 20px; border: none; border-radius: 8px; background-color: var(--accent); color: var(--text-on-accent); font-weight: 600; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-size: 1rem; transition: opacity 0.2s; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
.button:hover { opacity: 0.9; }
|
| 201 |
.button:active { transform: scale(0.98); }
|
|
|
|
| 202 |
.env-list { list-style: none; padding: 0; margin: 0; }
|
| 203 |
+
.env-item { background: #fff; border: 1px solid #e0e0e0; border-radius: 10px; padding: 15px; margin-bottom: 12px; display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.02); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
.env-details { display: flex; flex-direction: column; gap: 4px; overflow: hidden; }
|
| 205 |
.env-header { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
| 206 |
.env-id { font-weight: 700; color: var(--bg-medium); font-size: 1.1rem; }
|
| 207 |
.env-keyword { font-style: italic; color: #666; font-size: 0.9rem;}
|
| 208 |
+
.env-link { font-size: 0.9rem; color: #007bff; word-break: break-all; text-decoration: none; padding: 5px 0; display: block; }
|
| 209 |
+
.env-type-badge { font-size: 0.75rem; padding: 3px 8px; border-radius: 20px; font-weight: bold; text-transform: uppercase; white-space: nowrap; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
.type-open { background-color: #d4edda; color: #155724; }
|
| 211 |
.type-closed { background-color: #f8d7da; color: #721c24; }
|
| 212 |
+
.env-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
| 213 |
+
.action-button { background-color: var(--info); color: white; padding: 10px 15px; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
|
|
| 214 |
.delete-button { background-color: var(--danger); color: white; padding: 10px 15px; font-size: 0.9rem;}
|
|
|
|
| 215 |
.message { padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: center; font-size: 0.95rem; }
|
| 216 |
.message.success { background-color: #d4edda; color: #155724; }
|
| 217 |
.message.error { background-color: #f8d7da; color: #721c24; }
|
|
|
|
|
|
|
| 218 |
.modal { display: none; position: fixed; z-index: 2000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(2px); }
|
| 219 |
.modal-content { background-color: #fff; margin: 15% auto; padding: 25px; width: 90%; max-width: 600px; border-radius: 12px; position: relative; box-shadow: 0 5px 20px rgba(0,0,0,0.2); }
|
| 220 |
.close-modal { color: #888; position: absolute; right: 15px; top: 10px; font-size: 30px; font-weight: bold; cursor: pointer; padding: 5px; }
|
|
|
|
| 222 |
.stats-table th, .stats-table td { border: 1px solid #eee; padding: 10px 8px; text-align: left; }
|
| 223 |
.stats-table th { background-color: var(--bg-medium); color: white; }
|
| 224 |
.stats-table tr:nth-child(even) { background-color: #f9f9f9; }
|
| 225 |
+
.archived-item { opacity: 0.6; border-left: 4px solid var(--grey); }
|
| 226 |
+
.archived-item .env-id { text-decoration: line-through; }
|
| 227 |
@media (max-width: 600px) {
|
| 228 |
body { padding: 10px; }
|
| 229 |
.container { padding: 15px; }
|
| 230 |
h1 { font-size: 1.3rem; margin-bottom: 20px; }
|
| 231 |
+
.controls-row { flex-direction: column; align-items: stretch; }
|
| 232 |
+
.radio-group { justify-content: space-between; background: #fff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
.button { width: 100%; padding: 14px; }
|
| 234 |
+
.env-item { grid-template-columns: 1fr; gap: 12px; }
|
| 235 |
+
.env-actions { flex-direction: column; }
|
| 236 |
+
.env-actions .button { width: 100%; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
.modal-content { margin: 10% auto; width: 95%; padding: 20px 15px; }
|
| 238 |
.stats-table th, .stats-table td { font-size: 0.75rem; padding: 6px 4px; }
|
| 239 |
}
|
|
|
|
| 279 |
{{ 'ЗАКРЫТАЯ' if env.type == 'closed' else 'ОТКРЫТАЯ' }}
|
| 280 |
</span>
|
| 281 |
<small style="color:#888">{{ env.hits }} <i class="fas fa-eye"></i></small>
|
| 282 |
+
{% if env.type == 'closed' and env.has_token %}
|
| 283 |
+
<i class="fas fa-user-check" title="Привязано к устройству" style="color: green;"></i>
|
| 284 |
+
{% endif %}
|
| 285 |
</div>
|
| 286 |
<span class="env-keyword">{{ env.keyword }}</span>
|
| 287 |
<a href="{{ env.link }}" class="env-link" target="_blank">{{ env.link }}</a>
|
| 288 |
</div>
|
| 289 |
<div class="env-actions">
|
| 290 |
+
<button class="button action-button" style="background-color: #5d4037;" onclick="openStats('{{ env.id }}')"><i class="fas fa-chart-bar"></i> Инфо</button>
|
| 291 |
+
|
| 292 |
+
<form method="POST" action="{{ url_for('toggle_type', env_id=env.id) }}" style="display:contents;">
|
| 293 |
+
<button type="submit" class="button action-button" style="background-color: var(--warning); color: #333;"><i class="fas fa-sync-alt"></i>
|
| 294 |
+
{{ 'Сделать открытой' if env.type == 'closed' else 'Сделать закрытой' }}
|
| 295 |
+
</button>
|
| 296 |
+
</form>
|
| 297 |
+
|
| 298 |
+
{% if env.type == 'closed' %}
|
| 299 |
+
<form method="POST" action="{{ url_for('reset_device', env_id=env.id) }}" style="display:contents;" onsubmit="return confirm('Отвязать устройство от среды {{ env.id }}?');">
|
| 300 |
+
<button type="submit" class="button action-button" style="background-color: var(--info);"><i class="fas fa-user-slash"></i> Сброс</button>
|
| 301 |
+
</form>
|
| 302 |
+
{% endif %}
|
| 303 |
+
|
| 304 |
+
<form method="POST" action="{{ url_for('delete_environment', env_id=env.id) }}" style="display:contents;" onsubmit="return confirm('Архивировать среду {{ env.id }}?');">
|
| 305 |
+
<button type="submit" class="button delete-button"><i class="fas fa-archive"></i></button>
|
| 306 |
</form>
|
| 307 |
</div>
|
| 308 |
</li>
|
| 309 |
{% endfor %}
|
| 310 |
</ul>
|
| 311 |
{% else %}
|
| 312 |
+
<div style="text-align:center; padding: 20px; color: #888;">Список активных сред пуст</div>
|
| 313 |
{% endif %}
|
| 314 |
</div>
|
| 315 |
+
|
| 316 |
+
{% if archived_environments %}
|
| 317 |
+
<h2><i class="fas fa-archive"></i> Архив</h2>
|
| 318 |
+
<div class="section">
|
| 319 |
+
<ul class="env-list">
|
| 320 |
+
{% for env in archived_environments %}
|
| 321 |
+
<li class="env-item archived-item">
|
| 322 |
+
<div class="env-details">
|
| 323 |
+
<div class="env-header">
|
| 324 |
+
<span class="env-id">{{ env.id }}</span>
|
| 325 |
+
<span class="env-type-badge type-{{ env.type }}">
|
| 326 |
+
{{ 'ЗАКРЫТАЯ' if env.type == 'closed' else 'ОТКРЫТАЯ' }}
|
| 327 |
+
</span>
|
| 328 |
+
</div>
|
| 329 |
+
<span class="env-keyword">{{ env.keyword }}</span>
|
| 330 |
+
</div>
|
| 331 |
+
<div class="env-actions">
|
| 332 |
+
<form method="POST" action="{{ url_for('restore_environment', env_id=env.id) }}" style="display:contents;">
|
| 333 |
+
<button type="submit" class="button action-button" style="background-color: #4caf50;"><i class="fas fa-undo"></i> Восстановить</button>
|
| 334 |
+
</form>
|
| 335 |
+
</div>
|
| 336 |
+
</li>
|
| 337 |
+
{% endfor %}
|
| 338 |
+
</ul>
|
| 339 |
+
</div>
|
| 340 |
+
{% endif %}
|
| 341 |
+
|
| 342 |
</div>
|
| 343 |
|
|
|
|
| 344 |
<div id="statsModal" class="modal">
|
| 345 |
<div class="modal-content">
|
| 346 |
<span class="close-modal" onclick="closeStats()">×</span>
|
|
|
|
| 665 |
display: contents;
|
| 666 |
}
|
| 667 |
|
| 668 |
+
.grid-selector {
|
| 669 |
display: grid;
|
| 670 |
grid-template-columns: 1fr 1fr;
|
| 671 |
gap: 10px;
|
| 672 |
}
|
| 673 |
|
| 674 |
+
.grid-btn {
|
| 675 |
padding: 12px 10px;
|
| 676 |
background-color: var(--input-bg);
|
| 677 |
border: 1px solid var(--border);
|
|
|
|
| 685 |
width: 100%;
|
| 686 |
}
|
| 687 |
|
| 688 |
+
.grid-btn:hover {
|
| 689 |
border-color: var(--primary);
|
| 690 |
color: var(--text);
|
| 691 |
}
|
| 692 |
|
| 693 |
+
.grid-btn.active {
|
| 694 |
background-color: var(--primary);
|
| 695 |
color: #000;
|
| 696 |
border-color: var(--primary);
|
|
|
|
| 834 |
</div>
|
| 835 |
<div class="form-group full-width">
|
| 836 |
<label>Эстетика</label>
|
| 837 |
+
<div id="styleSelector" class="grid-selector">
|
| 838 |
+
<button type="button" class="grid-btn active" data-value="Raw Candid Photography">Живое фото (Raw)</button>
|
| 839 |
+
<button type="button" class="grid-btn" data-value="Cinematic Movie Still">Кадр из фильма</button>
|
| 840 |
+
<button type="button" class="grid-btn" data-value="Fashion Editorial">Модный журнал</button>
|
| 841 |
+
<button type="button" class="grid-btn" data-value="Street Style Photo">Уличный стиль</button>
|
| 842 |
+
<button type="button" class="grid-btn" data-value="Dark Moody Atmosphere">Мрачная атмосфера</button>
|
| 843 |
+
<button type="button" class="grid-btn" data-value="Vintage Analog Film">Пленка (Vintage)</button>
|
| 844 |
</div>
|
| 845 |
</div>
|
| 846 |
<div class="form-group">
|
|
|
|
| 854 |
</select>
|
| 855 |
</div>
|
| 856 |
<div class="form-group full-width">
|
| 857 |
+
<label>Локация</label>
|
| 858 |
+
<div id="locationSelector" class="grid-selector">
|
| 859 |
+
<button type="button" class="grid-btn active" data-value="in a clean white seamless studio background">Студия (белый фон)</button>
|
| 860 |
+
<button type="button" class="grid-btn" data-value="in a raw industrial loft studio with exposed brick">Лофт (кирпич, бетон)</button>
|
| 861 |
+
<button type="button" class="grid-btn" data-value="on a rainy night street in Tokyo with neon reflections">Ночной Токио (неон)</button>
|
| 862 |
+
<button type="button" class="grid-btn" data-value="in a minimalist modern interior with designer furniture">Минимализм (интерьер)</button>
|
| 863 |
+
<button type="button" class="grid-btn" data-value="on a rooftop overlooking the city skyline at sunset">Крыша (закат)</button>
|
| 864 |
+
<button type="button" class="grid-btn" data-value="in a luxurious vintage room with classic furniture">Роскошный винтаж</button>
|
| 865 |
+
<button type="button" class="grid-btn" data-value="in a dense, magical forest with sunbeams filtering through">Сказочный лес</button>
|
| 866 |
+
<button type="button" class="grid-btn" data-value="on a beautiful sandy beach during the golden hour">Пляж (золотой час)</button>
|
| 867 |
+
<button type="button" class="grid-btn" data-value="in a charming cobblestone alley in a European city">Европейская улочка</button>
|
| 868 |
+
<button type="button" class="grid-btn" data-value="in a grand library with floor-to-ceiling bookshelves">Библиотека</button>
|
| 869 |
+
</div>
|
| 870 |
</div>
|
| 871 |
<div class="form-group full-width">
|
| 872 |
<label for="model_details">Одежда и Детали (Опишите ткань и фасон!)</label>
|
|
|
|
| 1175 |
document.getElementById('object_background').disabled = isCreative;
|
| 1176 |
}
|
| 1177 |
|
| 1178 |
+
function setupGridSelector(containerId) {
|
| 1179 |
+
const container = document.getElementById(containerId);
|
| 1180 |
+
if (!container) return;
|
| 1181 |
+
const buttons = container.querySelectorAll('.grid-btn');
|
| 1182 |
|
| 1183 |
+
buttons.forEach(btn => {
|
| 1184 |
btn.addEventListener('click', () => {
|
| 1185 |
+
buttons.forEach(innerBtn => innerBtn.classList.remove('active'));
|
| 1186 |
btn.classList.add('active');
|
| 1187 |
});
|
| 1188 |
});
|
|
|
|
| 1195 |
|
| 1196 |
if (currentMode === 'model') {
|
| 1197 |
const isMyModel = document.getElementById('my_model_checkbox').checked;
|
| 1198 |
+
const style = document.querySelector('#styleSelector .grid-btn.active').dataset.value;
|
| 1199 |
const shotType = document.getElementById('shotType').value;
|
| 1200 |
const bodyType = document.getElementById('bodyType').value;
|
| 1201 |
const pose = document.getElementById('pose').value;
|
| 1202 |
+
const location = document.querySelector('#locationSelector .grid-btn.active').dataset.value;
|
| 1203 |
const light = document.getElementById('light').value;
|
| 1204 |
const camera = document.getElementById('camera').value;
|
| 1205 |
|
|
|
|
| 1209 |
INSTRUCTIONS FOR AI: This is a virtual try-on task. You will be given two images.
|
| 1210 |
- Image 1 (Model): Use this as the reference for the model's face, identity, pose, and body shape.
|
| 1211 |
- Image 2 (Garment): Use this image for the clothing item.
|
| 1212 |
+
Task: Accurately dress the model from Image 1 in the clothing from Image 2. The final image must preserve the model's exact likeness, facial identity, and hair. The model should have a body type of "${bodyType}".
|
| 1213 |
Style: ${style}, hyper-detailed, luxury brand campaign, Vogue aesthetic, impeccable.
|
| 1214 |
Composition: ${shotType}.
|
| 1215 |
Final Scene: Place the model in the following environment: ${location}.
|
| 1216 |
Lighting: The scene should have ${light}.
|
| 1217 |
+
Technical: Masterpiece professional photograph, shot on ${camera}, 8k UHD, tack sharp focus, breathtaking detail, perfect color grading, 100% photorealistic, no hint of AI.`;
|
| 1218 |
} else {
|
| 1219 |
const age = document.getElementById('age').value;
|
| 1220 |
const nationality = document.getElementById('nationality').value;
|
|
|
|
| 1224 |
const emotion = document.getElementById('emotion').value;
|
| 1225 |
const details = document.getElementById('model_details').value || "high-end fashion garments";
|
| 1226 |
|
| 1227 |
+
fullPrompt = `${envKeyword}, style:: ${style}, high fashion editorial, luxury brand campaign, Vogue aesthetic, hyper-detailed, impeccable, 1000% photorealistic.
|
| 1228 |
composition:: ${shotType}.
|
| 1229 |
subject:: An ultra-high-resolution, flawless photograph of a striking ${age} ${nationality} high fashion model.
|
| 1230 |
+
model_characteristics:: physique ${bodyType}, ${hairColor} ${hairstyle} with visible individual hair strands and flyaways, expression ${emotion}.
|
| 1231 |
clothing_focus:: The model is wearing ${details}, emphasizing haute couture craftsmanship.
|
| 1232 |
+
texture_&_material_fidelity:: Extreme macro precision on textiles. Render the fabric weave, thread count, visible stitching, material weight, realistic creases, tactile surface imperfections. The texture must be palpable.
|
| 1233 |
+
human_realism_details:: Capture flawless yet utterly realistic skin texture with visible pores and subtle imperfections. Perfect complexion, highlighting bone structure. Avoid any hint of digital, plastic, or airbrushed skin. Eyes must be hyper-realistic with natural reflections.
|
| 1234 |
+
scene_environment:: ${location}, creating a sophisticated atmosphere.
|
| 1235 |
+
technical:: Masterpiece professional photograph, ${light} meticulously crafted to sculpt the subject, shot on ${camera}, 8k UHD, tack sharp focus, breathtaking detail, uncompressed, subtle filmic grain, chromatic aberration, color graded to perfection, award-winning photography.`;
|
| 1236 |
}
|
| 1237 |
|
| 1238 |
} else if (currentMode === 'children') {
|
|
|
|
| 1265 |
|
| 1266 |
fullPrompt = `${envKeyword}, style:: ${style}, luxury children's fashion campaign, cinematic storytelling, enchanting, ultra-photorealistic.
|
| 1267 |
composition:: ${shotType}.
|
| 1268 |
+
subject:: ${subject} The photograph must look like a real, captured moment for a high-end children's fashion magazine.
|
| 1269 |
clothing_focus:: The child is wearing ${clothing_details}, presented as a luxury garment.
|
| 1270 |
+
texture_&_material_fidelity:: Macro-level detail on clothing textures. Focus on the weave of cotton, the softness of wool, the texture of denim. Show realistic wrinkles, creases from movement, and even subtle fabric pilling. 1000% texture fidelity is crucial.
|
| 1271 |
+
human_realism_details:: Capture the pure beauty of the child. Flawless, dewy skin with a natural glow and realistic texture, not airbrushed. The light should have a painterly, magical quality. Eyes must be expressive and full of life with natural reflections. Individual hair strands should be visible.
|
| 1272 |
scene_activity:: ${pose_info} The location is ${location}, creating a whimsical and high-end narrative.
|
| 1273 |
+
technical:: Masterpiece photograph, ${light} creating a magical and soft atmosphere, shot on Fujifilm XT4, 56mm F1.2 lens, 8k, tack sharp focus, impeccable detail, perfect color grading, looks like a real captured moment from a luxury campaign.`;
|
| 1274 |
|
| 1275 |
} else {
|
| 1276 |
const objectName = document.getElementById('object_name').value || "a product";
|
|
|
|
| 1289 |
|
| 1290 |
fullPrompt = `${envKeyword}, style:: Luxury product advertising, ${objectStyle}, sophisticated, sleek, ultra-photorealistic.
|
| 1291 |
subject:: A breathtaking, hyper-realistic photograph of the luxury product: ${objectName}. The image must evoke desire and exclusivity.
|
| 1292 |
+
material_focus:: Achieve 1000% physical accuracy. Render pristine, flawless surfaces. Showcase intricate details of the material grain, polished metal sheen, subtle surface imperfections, dust particles, and crystal-clear refractions. Even microscopic details should look clean and perfect.
|
| 1293 |
scene_context:: Placed ${background}. Additional details: ${objectDetails}, arranged with artistic precision.
|
| 1294 |
composition:: ${objectComposition}, creating a powerful and elegant visual statement.
|
| 1295 |
technical:: Advertisement-grade photograph, ${objectLighting} designed to accentuate the product's luxury form, 8k UHD resolution, flawless focus, extreme macro detail, advanced ray-traced reflections, impeccably clean, exudes quality and high-end appeal, masterpiece.`;
|
|
|
|
| 1321 |
switchMode('model');
|
| 1322 |
switchChildrenSubMode('newborn');
|
| 1323 |
autoAdjustDefaults();
|
| 1324 |
+
setupGridSelector('styleSelector');
|
| 1325 |
+
setupGridSelector('locationSelector');
|
| 1326 |
});
|
| 1327 |
</script>
|
| 1328 |
|
|
|
|
| 1338 |
def admhosto():
|
| 1339 |
data = load_data()
|
| 1340 |
environments_data = []
|
| 1341 |
+
for env_id, env_data in data.get("environments", {}).items():
|
| 1342 |
environments_data.append({
|
| 1343 |
"id": env_id,
|
| 1344 |
"keyword": env_data.get("keyword", "N/A"),
|
| 1345 |
"type": env_data.get("type", "closed"),
|
| 1346 |
"hits": env_data.get("hits", 0),
|
| 1347 |
"created_at": env_data.get("created_at", ""),
|
| 1348 |
+
"link": url_for('serve_env', env_id=env_id, _external=True),
|
| 1349 |
+
"has_token": bool(env_data.get("device_token"))
|
| 1350 |
})
|
| 1351 |
|
| 1352 |
+
archived_environments_data = []
|
| 1353 |
+
for env_id, env_data in data.get("archive", {}).items():
|
| 1354 |
+
archived_environments_data.append({
|
| 1355 |
+
"id": env_id,
|
| 1356 |
+
"keyword": env_data.get("keyword", "N/A"),
|
| 1357 |
+
"type": env_data.get("type", "closed")
|
| 1358 |
+
})
|
| 1359 |
+
|
| 1360 |
environments_data.sort(key=lambda x: x['created_at'], reverse=True)
|
| 1361 |
|
| 1362 |
+
return render_template_string(ADMHOSTO_TEMPLATE, environments=environments_data, archived_environments=archived_environments_data)
|
| 1363 |
|
| 1364 |
@app.route('/admhosto/create', methods=['POST'])
|
| 1365 |
def create_environment():
|
|
|
|
| 1373 |
|
| 1374 |
while True:
|
| 1375 |
new_id = ''.join(random.choices(string.digits, k=6))
|
| 1376 |
+
if new_id not in all_data["environments"] and new_id not in all_data["archive"]:
|
| 1377 |
break
|
| 1378 |
|
| 1379 |
+
all_data["environments"][new_id] = {
|
| 1380 |
"keyword": keyword,
|
| 1381 |
"type": env_type,
|
| 1382 |
"device_token": None,
|
|
|
|
| 1391 |
@app.route('/admhosto/delete/<env_id>', methods=['POST'])
|
| 1392 |
def delete_environment(env_id):
|
| 1393 |
all_data = load_data()
|
| 1394 |
+
if env_id in all_data["environments"]:
|
| 1395 |
+
archived_env = all_data["environments"].pop(env_id)
|
| 1396 |
+
all_data["archive"][env_id] = archived_env
|
| 1397 |
save_data(all_data)
|
| 1398 |
+
flash(f'Среда {env_id} была архивирована.', 'success')
|
| 1399 |
else:
|
| 1400 |
flash(f'Среда {env_id} не найдена.', 'error')
|
| 1401 |
return redirect(url_for('admhosto'))
|
| 1402 |
|
| 1403 |
+
@app.route('/admhosto/restore/<env_id>', methods=['POST'])
|
| 1404 |
+
def restore_environment(env_id):
|
| 1405 |
+
all_data = load_data()
|
| 1406 |
+
if env_id in all_data["archive"]:
|
| 1407 |
+
restored_env = all_data["archive"].pop(env_id)
|
| 1408 |
+
all_data["environments"][env_id] = restored_env
|
| 1409 |
+
save_data(all_data)
|
| 1410 |
+
flash(f'Среда {env_id} была восстановлена.', 'success')
|
| 1411 |
+
else:
|
| 1412 |
+
flash(f'Среда {env_id} не найдена в архиве.', 'error')
|
| 1413 |
+
return redirect(url_for('admhosto'))
|
| 1414 |
+
|
| 1415 |
+
@app.route('/admhosto/reset_device/<env_id>', methods=['POST'])
|
| 1416 |
+
def reset_device(env_id):
|
| 1417 |
+
all_data = load_data()
|
| 1418 |
+
if env_id in all_data["environments"]:
|
| 1419 |
+
all_data["environments"][env_id]['device_token'] = None
|
| 1420 |
+
save_data(all_data)
|
| 1421 |
+
flash(f'Устройство для среды {env_id} было отвязано.', 'success')
|
| 1422 |
+
else:
|
| 1423 |
+
flash(f'Среда {env_id} не найдена.', 'error')
|
| 1424 |
+
return redirect(url_for('admhosto'))
|
| 1425 |
+
|
| 1426 |
+
@app.route('/admhosto/toggle_type/<env_id>', methods=['POST'])
|
| 1427 |
+
def toggle_type(env_id):
|
| 1428 |
+
all_data = load_data()
|
| 1429 |
+
if env_id in all_data["environments"]:
|
| 1430 |
+
current_type = all_data["environments"][env_id].get('type', 'closed')
|
| 1431 |
+
if current_type == 'closed':
|
| 1432 |
+
all_data["environments"][env_id]['type'] = 'open'
|
| 1433 |
+
flash(f'Среда {env_id} теперь открытая.', 'success')
|
| 1434 |
+
else:
|
| 1435 |
+
all_data["environments"][env_id]['type'] = 'closed'
|
| 1436 |
+
all_data["environments"][env_id]['device_token'] = None
|
| 1437 |
+
flash(f'Среда {env_id} теперь закрытая. Привязка к устройству сброшена.', 'success')
|
| 1438 |
+
save_data(all_data)
|
| 1439 |
+
else:
|
| 1440 |
+
flash(f'Среда {env_id} не найдена.', 'error')
|
| 1441 |
+
return redirect(url_for('admhosto'))
|
| 1442 |
+
|
| 1443 |
+
|
| 1444 |
@app.route('/admhosto/stats/<env_id>')
|
| 1445 |
def get_env_stats(env_id):
|
| 1446 |
data = load_data()
|
| 1447 |
+
env_data = data.get("environments", {}).get(env_id)
|
| 1448 |
if not env_data:
|
| 1449 |
return jsonify({"error": "Среда не найдена"}), 404
|
| 1450 |
|
|
|
|
| 1476 |
@app.route('/env/<env_id>')
|
| 1477 |
def serve_env(env_id):
|
| 1478 |
data = load_data()
|
| 1479 |
+
env_data = data.get("environments", {}).get(env_id)
|
| 1480 |
if not env_data:
|
| 1481 |
return "Среда не найдена.", 404
|
| 1482 |
|
|
|
|
| 1486 |
current_log = {
|
| 1487 |
"time": datetime.utcnow().isoformat(),
|
| 1488 |
"ip": request.remote_addr,
|
| 1489 |
+
"ua": request.headers.get('User-Agent')[:150]
|
| 1490 |
}
|
| 1491 |
|
| 1492 |
env_data['hits'] = env_data.get('hits', 0) + 1
|
|
|
|
| 1497 |
if len(env_data['logs']) > 30:
|
| 1498 |
env_data['logs'] = env_data['logs'][-30:]
|
| 1499 |
|
| 1500 |
+
data["environments"][env_id] = env_data
|
| 1501 |
save_data(data)
|
| 1502 |
|
| 1503 |
if env_type == 'open':
|
|
|
|
| 1534 |
else:
|
| 1535 |
new_token = ''.join(random.choices(string.ascii_letters + string.digits, k=40))
|
| 1536 |
env_data['device_token'] = new_token
|
| 1537 |
+
data["environments"][env_id] = env_data
|
| 1538 |
save_data(data)
|
| 1539 |
|
| 1540 |
resp = make_response(render_template_string(SYNKRIS_LOOK_TEMPLATE, keyword=keyword))
|
| 1541 |
+
resp.set_cookie(f'access_token_{env_id}', new_token, max_age=31536000, httponly=True, samesite='Lax')
|
| 1542 |
return resp
|
| 1543 |
|
| 1544 |
if __name__ == '__main__':
|