Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -77,45 +77,45 @@ Render the product with hyperrealistic lighting and shadows that accentuate its
|
|
| 77 |
**CONTEXT:** This is a professional, high-end commercial photoshoot for a children's clothing catalog or brand campaign. The overall atmosphere must be bright, clean, and joyful."""
|
| 78 |
},
|
| 79 |
"flagship_styles": {
|
| 80 |
-
"studio": "Impeccable studio photoshoot.
|
| 81 |
-
"street": "Dynamic street style shot in a bustling metropolis (e.g., Tokyo, New York). Cinematic, candid feel with natural urban lighting
|
| 82 |
-
"lookbook": "
|
| 83 |
-
"minimalism": "Extreme architectural minimalism. The model is
|
| 84 |
-
"selfie": "
|
| 85 |
-
"creative": "Avant-garde, conceptual photoshoot.
|
| 86 |
-
"new_year": "
|
| 87 |
-
"retro": "Authentic 35mm film photograph emulation. Rich grain,
|
| 88 |
-
"boho": "
|
| 89 |
-
"gothic": "
|
| 90 |
-
"editorial": "High-fashion glossy magazine editorial.
|
| 91 |
-
"film_noir": "Cinematic black and white film noir.
|
| 92 |
-
"cottagecore": "Idyllic cottagecore aesthetic. A cozy, rustic
|
| 93 |
-
"royalcore": "Opulent royalcore aesthetic. Set
|
| 94 |
-
"solarpunk": "
|
| 95 |
-
"skater": "
|
| 96 |
-
"baroque": "
|
| 97 |
-
"japandi": "
|
| 98 |
-
"coastal": "
|
| 99 |
-
"cyberpunk": "
|
| 100 |
-
"fantasy": "
|
| 101 |
-
"90s_grunge": "Raw 90s grunge aesthetic.
|
| 102 |
-
"techwear": "Sleek, functional Techwear style. Set against
|
| 103 |
-
"avant_garde": "Experimental avant-garde fashion.
|
| 104 |
-
"home_casual": "
|
| 105 |
-
"social_media_candid": "
|
| 106 |
-
"backstage": "
|
| 107 |
-
"road_trip": "
|
| 108 |
-
"rainy_day": "
|
| 109 |
-
"night_flash": "Edgy, direct-flash night photography.
|
| 110 |
-
"golden_hour_picnic": "
|
| 111 |
-
"beach": "Vibrant beach scene. Shot during the golden hour with soft, warm sunlight. The model is near the water with gentle waves and fine
|
| 112 |
},
|
| 113 |
"object_styles": {
|
| 114 |
-
"studio": "
|
| 115 |
-
"minimalism": "
|
| 116 |
-
"nature": "The product is artfully placed in a complementary natural environment. E.g., on mossy rocks in a forest, beside a clear stream, or nestled among flowers. The lighting is natural and enhances the organic feel.",
|
| 117 |
-
"luxe": "
|
| 118 |
-
"dark": "
|
| 119 |
}
|
| 120 |
}
|
| 121 |
with open(PROMPTS_FILE, 'w', encoding='utf-8') as f:
|
|
@@ -358,19 +358,27 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 358 |
.empty-list-placeholder { text-align:center; padding: 20px; color: #888; }
|
| 359 |
.no-margin { margin-bottom: 0; }
|
| 360 |
|
| 361 |
-
.
|
| 362 |
-
.summary-
|
| 363 |
-
.
|
| 364 |
-
.
|
| 365 |
-
.
|
| 366 |
-
.
|
| 367 |
-
.
|
| 368 |
-
.
|
| 369 |
-
.
|
| 370 |
-
.
|
| 371 |
-
.
|
| 372 |
-
.
|
| 373 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
@media (max-width: 768px) {
|
| 376 |
.env-item { grid-template-columns: 1fr; gap: 12px; }
|
|
@@ -388,7 +396,6 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 388 |
.add-env-form .button { width: 100%; padding: 14px; }
|
| 389 |
|
| 390 |
.stats-table th, .stats-table td { font-size: 0.75rem; padding: 6px 4px; }
|
| 391 |
-
.summary-list .timestamp { text-align: left; }
|
| 392 |
}
|
| 393 |
</style>
|
| 394 |
</head>
|
|
@@ -403,51 +410,47 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 403 |
{% endif %}
|
| 404 |
{% endwith %}
|
| 405 |
|
| 406 |
-
<
|
| 407 |
-
<
|
| 408 |
-
|
| 409 |
-
<div class="
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
|
|
|
| 413 |
<li>
|
| 414 |
<div>
|
| 415 |
-
<span class="env-id-small">
|
| 416 |
-
<span class="keyword-small">{{ env.keyword }}</span>
|
| 417 |
</div>
|
| 418 |
-
<span>{{ env.hits }} <i class="fas fa-eye fa-xs"></i></span>
|
| 419 |
</li>
|
| 420 |
{% endfor %}
|
| 421 |
</ul>
|
| 422 |
{% else %}
|
| 423 |
-
<div class="empty-list-placeholder" style="padding:
|
| 424 |
{% endif %}
|
| 425 |
</div>
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
<summary>Последние 5 входов</summary>
|
| 432 |
-
<div class="summary-widget-content">
|
| 433 |
-
{% if last_5_entries %}
|
| 434 |
-
<ul class="summary-list">
|
| 435 |
-
{% for log in last_5_entries %}
|
| 436 |
<li>
|
| 437 |
<div>
|
| 438 |
-
<span class="env-id-small">
|
| 439 |
-
<span
|
| 440 |
</div>
|
| 441 |
-
<span class="
|
| 442 |
</li>
|
| 443 |
{% endfor %}
|
| 444 |
</ul>
|
| 445 |
-
|
| 446 |
-
<div class="empty-list-placeholder" style="padding:
|
| 447 |
{% endif %}
|
| 448 |
</div>
|
| 449 |
-
</
|
| 450 |
-
</
|
| 451 |
|
| 452 |
<div class="section">
|
| 453 |
<form method="POST" action="{{ url_for('create_environment') }}" class="add-env-form">
|
|
@@ -466,7 +469,6 @@ ADMHOSTO_TEMPLATE = '''
|
|
| 466 |
<input type="text" id="search-env" placeholder="🔍 Поиск...">
|
| 467 |
</div>
|
| 468 |
|
| 469 |
-
<h2>Активные Среды</h2>
|
| 470 |
<div class="section">
|
| 471 |
{% if active_environments %}
|
| 472 |
<ul class="env-list">
|
|
@@ -964,12 +966,12 @@ textarea {
|
|
| 964 |
<select id="nationality">
|
| 965 |
<option value="Eastern European">Восточная Европа</option>
|
| 966 |
<option value="Northern European">Скандинавская</option>
|
|
|
|
| 967 |
<option value="Asian">Азиатская</option>
|
| 968 |
<option value="Latin American">Латиноамериканская</option>
|
| 969 |
-
<option value="Mediterranean">Средиземноморская</option>
|
| 970 |
-
<option value="African">Африканская</option>
|
| 971 |
<option value="Middle Eastern">Ближневосточная</option>
|
| 972 |
<option value="South Asian">Южноазиатская</option>
|
|
|
|
| 973 |
<option value="Mixed Race">Смешанная</option>
|
| 974 |
</select>
|
| 975 |
</div>
|
|
@@ -1065,9 +1067,10 @@ textarea {
|
|
| 1065 |
<select id="child_nationality">
|
| 1066 |
<option value="Eastern European">Восточная Европа</option>
|
| 1067 |
<option value="Northern European">Скандинавская</option>
|
| 1068 |
-
<option value="Asian">Азиатская</option>
|
| 1069 |
<option value="Mediterranean">Средиземноморская</option>
|
| 1070 |
-
<option value="
|
|
|
|
|
|
|
| 1071 |
</select>
|
| 1072 |
</div>
|
| 1073 |
<div class="form-group">
|
|
@@ -1208,7 +1211,7 @@ const femaleHairstyles = {
|
|
| 1208 |
};
|
| 1209 |
|
| 1210 |
const maleHairstyles = {
|
| 1211 |
-
'
|
| 1212 |
};
|
| 1213 |
|
| 1214 |
function switchMode(mode) {
|
|
@@ -1222,12 +1225,15 @@ function switchMode(mode) {
|
|
| 1222 |
document.getElementById('modeObjectBtn').classList.toggle('active', mode === 'object');
|
| 1223 |
}
|
| 1224 |
|
| 1225 |
-
function populateSelect(selectElement, options) {
|
| 1226 |
selectElement.innerHTML = '';
|
| 1227 |
for (const value in options) {
|
| 1228 |
const option = document.createElement('option');
|
| 1229 |
option.value = value;
|
| 1230 |
option.textContent = options[value];
|
|
|
|
|
|
|
|
|
|
| 1231 |
selectElement.appendChild(option);
|
| 1232 |
}
|
| 1233 |
}
|
|
@@ -1238,8 +1244,7 @@ function updateModelOptions() {
|
|
| 1238 |
const hairstyleSelect = document.getElementById('hairstyle');
|
| 1239 |
|
| 1240 |
populateSelect(bodyTypeSelect, gender === 'female' ? femaleBodyTypes : maleBodyTypes);
|
| 1241 |
-
populateSelect(hairstyleSelect, gender === 'female' ? femaleHairstyles : maleHairstyles);
|
| 1242 |
-
hairstyleSelect.value = 'long straight hair';
|
| 1243 |
}
|
| 1244 |
|
| 1245 |
function toggleOwnModel(isOwnModel) {
|
|
@@ -1427,57 +1432,56 @@ def admhosto():
|
|
| 1427 |
data = load_data()
|
| 1428 |
active_environments = []
|
| 1429 |
archived_environments = []
|
| 1430 |
-
all_logs = []
|
| 1431 |
|
| 1432 |
for env_id, env_data in data.items():
|
| 1433 |
if not isinstance(env_data, dict): continue
|
| 1434 |
-
|
| 1435 |
-
|
| 1436 |
-
|
| 1437 |
-
|
| 1438 |
-
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
active_environments.append(env_item)
|
| 1445 |
-
|
| 1446 |
-
for log in env_data.get("logs", []):
|
| 1447 |
-
try:
|
| 1448 |
-
utc_dt = datetime.fromisoformat(log['time'])
|
| 1449 |
-
almaty_dt = utc_dt + timedelta(hours=5)
|
| 1450 |
-
log_item = {
|
| 1451 |
-
"env_id": env_id,
|
| 1452 |
-
"time": log["time"],
|
| 1453 |
-
"time_str": almaty_dt.strftime('%Y-%m-%d %H:%M:%S'),
|
| 1454 |
-
"ip": log.get("ip", "N/A")
|
| 1455 |
-
}
|
| 1456 |
-
all_logs.append(log_item)
|
| 1457 |
-
except:
|
| 1458 |
-
continue
|
| 1459 |
else:
|
| 1460 |
-
|
| 1461 |
-
"id": env_id,
|
| 1462 |
-
"keyword": env_data.get("keyword", "N/A"),
|
| 1463 |
-
"type": env_data.get("type", "closed"),
|
| 1464 |
-
"created_at": env_data.get("created_at", ""),
|
| 1465 |
-
}
|
| 1466 |
-
archived_environments.append(archived_item)
|
| 1467 |
|
| 1468 |
active_environments.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
| 1469 |
archived_environments.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
| 1470 |
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
| 1474 |
-
|
| 1475 |
-
|
| 1476 |
-
|
| 1477 |
-
|
| 1478 |
-
|
| 1479 |
-
|
| 1480 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1481 |
|
| 1482 |
@app.route('/admhosto/create', methods=['POST'])
|
| 1483 |
def create_environment():
|
|
|
|
| 77 |
**CONTEXT:** This is a professional, high-end commercial photoshoot for a children's clothing catalog or brand campaign. The overall atmosphere must be bright, clean, and joyful."""
|
| 78 |
},
|
| 79 |
"flagship_styles": {
|
| 80 |
+
"studio": "Impeccable high-fashion studio photoshoot. Bathed in flawless, soft, multi-point lighting that carves out every detail. Set against a seamless, infinite cyclorama wall (subtle off-white or light grey). The final image is ultra-high resolution, tack-sharp, with vibrant, true-to-life colors, emulating a major brand's advertising campaign.",
|
| 81 |
+
"street": "Dynamic, authentic street style shot in a bustling metropolis (e.g., Tokyo, New York). Cinematic, candid feel with beautiful natural urban lighting, authentic urban grit, and cinematic depth. The model should look effortlessly chic, integrated into the environment with a subtle motion blur suggesting movement and life.",
|
| 82 |
+
"lookbook": "Sophisticated and minimalist lookbook aesthetic. A clean, textured background (e.g., cast concrete, handmade paper, linen curtain). Soft, diffused, single-source light creating a gentle, sophisticated, and modern mood. The entire focus is on the garment's form, drape, and texture.",
|
| 83 |
+
"minimalism": "Extreme architectural minimalism. The model is a sculptural element against a monumental backdrop of brutalist concrete or stark, monolithic plaster. A single, dramatic, long shadow, cast by a harsh light source, creates a powerful and unforgettable graphic composition.",
|
| 84 |
+
"selfie": "**MANDATORY HYPERREALISM.** Imperfect, authentic 'moment-in-time' selfie. Shot on a smartphone, not a professional camera. Emulate real-world conditions: slight grain, natural skin textures with pores and minor imperfections (NO plastic skin), asymmetrical features. Include subtle environmental details like authentic reflections in a mirror, fingerprint smudges on the screen edge, or slight lens flare from a light source. The expression must be candid and genuine, not a posed 'influencer' look.",
|
| 85 |
+
"creative": "Avant-garde, conceptual, and visually arresting photoshoot. Utilizes unique, artistic props, dramatic and unconventional lighting, and a surreal background to create a visually striking, editorial-worthy image that tells a compelling and ambiguous story.",
|
| 86 |
+
"new_year": "Vibrant and festive New Year's atmosphere. The scene is illuminated by the soft, magical bokeh of fairy lights and dynamic, glowing sparkler trails. Set against a beautifully decorated, opulent tree or a magical snowy landscape under a starry sky. Evokes warmth, joy, and celebration.",
|
| 87 |
+
"retro": "Authentic 35mm film photograph emulation from the 1970s or 80s. Rich, warm analog grain, nostalgic color shifts, and subtle, dreamy light leaks. Poses, environment, and styling are perfectly period-correct, creating a powerful sense of nostalgia.",
|
| 88 |
+
"boho": "Ethereal golden hour boho dreamscape. Shot in a field of vibrant wildflowers during the final moments of sunset. The light is a warm, liquid gold, creating a soft, glowing aura around the model, highlighting natural textures and creating a serene, free-spirited, and magical vibe.",
|
| 89 |
+
"gothic": "Darkly romantic, moody, gothic atmosphere. Set in ancient, imposing architecture like a stone cathedral, castle ruins, or a grand library. Low-key, dramatic lighting creates deep, velvety shadows and a profound sense of mystery and timeless drama.",
|
| 90 |
+
"editorial": "High-fashion glossy magazine editorial. A bold, hyper-saturated, solid colored background commands attention. Clever use of multiple mirrors to create compelling, fragmented reflections and complex, artistic views of the model and outfit. A true statement image.",
|
| 91 |
+
"film_noir": "Cinematic black and white film noir scene. Extremely high contrast, dramatic 'chiaroscuro' lighting, casting long, stark shadows. An overwhelming sense of suspense and narrative. May incorporate atmospheric elements like dense fog, pouring rain, or Venetian blinds.",
|
| 92 |
+
"cottagecore": "Idyllic and romanticized cottagecore aesthetic. A cozy, rustic scene in a sun-dappled country house or a lush, overgrown garden. Beautiful, soft natural light streams through a window, highlighting organic textures and creating a feeling of wholesome, romantic, and peaceful rural life.",
|
| 93 |
+
"royalcore": "Opulent and regal royalcore aesthetic. Set within a lavish, historic palace interior featuring ornate gold details, rich velvet curtains, and gilded antique furniture. The lighting is grand and dramatic, as if from a chandelier, creating an unmistakable air of aristocracy and old-world luxury.",
|
| 94 |
+
"solarpunk": "Vibrant, optimistic solarpunk future. Sleek, futuristic architecture with organic curves is seamlessly integrated with lush, vertical greenery. Bright, clean, hopeful light fills the scene, suggesting a utopian, tech-advanced society living in harmony with nature.",
|
| 95 |
+
"skater": "Dynamic and energetic skater aesthetic. A wide-angle, low-perspective shot in a gritty, sun-bleached skate park or on urban streets. Captures explosive movement, raw youthful energy, and a real, counter-culture spirit.",
|
| 96 |
+
"baroque": "A dramatic, living Baroque painting. An ornate, richly detailed setting with overflowing textures like silk, velvet, and brocade. The lighting is high-contrast and theatrical, reminiscent of a Caravaggio masterpiece, creating deep, intense colors and an epic, painterly quality.",
|
| 97 |
+
"japandi": "A serene and tranquil Japandi style interior. A perfect fusion of Japanese minimalism and Scandinavian functionality. Clean lines, a muted neutral palette, natural wood, and a focus on handcrafted details create a space of ultimate calm and uncluttered beauty.",
|
| 98 |
+
"coastal": "Effortlessly chic 'coastal grandmother' style. A bright, airy, sun-filled setting by the sea. A clean palette of whites, beiges, and soft blues. Natural materials like linen and weathered wood create a feeling of relaxed, timeless, seaside elegance.",
|
| 99 |
+
"cyberpunk": "A gritty, neon-drenched cyberpunk cityscape at night. High-tech, futuristic elements are everywhere, with brilliant, colorful reflections from countless neon signs on wet, reflective streets. A cool, cinematic color palette and a sense of awe-inspiring urban dystopia.",
|
| 100 |
+
"fantasy": "An enchanting and otherworldly fantasy scene. A magical, bioluminescent forest, ancient vine-covered ruins, or an ethereal, dreamlike landscape. The lighting is mystical and magical, creating a narrative-driven image that feels like a still from an epic fantasy film.",
|
| 101 |
+
"90s_grunge": "Raw, authentic 90s grunge aesthetic. A backdrop of urban decay, an abandoned warehouse, or a graffiti-covered alley. A desaturated, moody color palette. Captures a feeling of angst, rebellion, and effortless, non-conformist style.",
|
| 102 |
+
"techwear": "Sleek, functional, and futuristic Techwear style. Set against imposing, modern, urban architecture. The lighting is clean, sharp, and slightly cool, highlighting the technical details, innovative fabrics, and functionality of the garments.",
|
| 103 |
+
"avant_garde": "Experimental, high-concept avant-garde fashion. A composition of abstract shapes, bold and jarring color clashes, and unconventional poses. A highly artistic and conceptual approach that challenges all traditional notions of beauty and style.",
|
| 104 |
+
"home_casual": "A cozy, authentic, lived-in home setting. Soft, beautiful, natural light streaming through a large window. A relaxed, intimate atmosphere filled with personal details like books, plants, and comfortable, textured furnishings.",
|
| 105 |
+
"social_media_candid": "An authentic, 'Instagrammable' candid moment. Shot in a trendy cafe with great lighting or during a walk through a picturesque street. Looks completely spontaneous and natural, as if capturing a real, unfiltered moment in time.",
|
| 106 |
+
"backstage": "The hectic, atmospheric, and energetic backstage of a high-fashion show. Racks of designer clothes, glowing makeup stations, and a palpable sense of focused energy. The lighting is functional but chaotic, creating a compelling 'behind-the-scenes' narrative.",
|
| 107 |
+
"road_trip": "A cinematic American West road trip aesthetic. The model leans against a classic vintage car, set against a vast, epic landscape at sunset. A powerful sense of freedom, adventure, wanderlust, and golden-hued nostalgia.",
|
| 108 |
+
"rainy_day": "A romantic, melancholic, and beautiful rainy day scene. Glistening reflections on wet city pavement, artistic droplets on windows, and the soft, diffused light of an overcast sky. A cozy, thoughtful, and introspective mood.",
|
| 109 |
+
"night_flash": "Edgy, high-energy, direct-flash night photography. Creates high contrast, deeply saturated colors, and sharp, graphic shadows. A raw, spontaneous, 'paparazzi' or intimate party-snapshot feeling.",
|
| 110 |
+
"golden_hour_picnic": "An idyllic and romantic golden hour picnic. Warm, glowing sunset light filters dreamily through the trees. A beautifully styled picnic scene creates a relaxed, joyful, and deeply romantic atmosphere.",
|
| 111 |
+
"beach": "Vibrant and serene beach scene. Shot during the magical golden hour with soft, warm, glowing sunlight. The model is near the sparkling water with gentle waves and fine, golden sand, creating a relaxed, sun-kissed, and utterly beautiful atmosphere."
|
| 112 |
},
|
| 113 |
"object_styles": {
|
| 114 |
+
"studio": "Immaculate product photography on a seamless, neutral background (white, grey, or beige). Perfect, diffused, multi-point lighting that eliminates all harsh shadows and reveals every microscopic detail of the product's texture, form, and material.",
|
| 115 |
+
"minimalism": "An artistic, minimalist composition on a highly textured surface like raw concrete, luxe marble, or fine sand. A single, crisp, hard light source creates a single, long, graphic shadow, emphasizing the product's pure silhouette and form.",
|
| 116 |
+
"nature": "The product is artfully and harmoniously placed in a complementary natural environment. E.g., resting on mossy rocks in a misty forest, beside a clear, flowing stream, or nestled among vibrant flowers. The lighting is soft, natural, and enhances the organic, fresh feel.",
|
| 117 |
+
"luxe": "A sophisticated luxury still life. The product is arranged on a rich, tactile surface like deep-colored silk, plush velvet, or dark, veined marble. The lighting is low-key, moody, and sophisticated, with soft, elegant highlights that suggest opulence, exclusivity, and desire.",
|
| 118 |
+
"dark": "A moody and dramatic 'dark academia' aesthetic. The product is set against a dark, richly textured background like aged wood or a leather-bound book. A single, directional light source carves the product out of the deep shadows, creating a mysterious, intense, and intellectual atmosphere."
|
| 119 |
}
|
| 120 |
}
|
| 121 |
with open(PROMPTS_FILE, 'w', encoding='utf-8') as f:
|
|
|
|
| 358 |
.empty-list-placeholder { text-align:center; padding: 20px; color: #888; }
|
| 359 |
.no-margin { margin-bottom: 0; }
|
| 360 |
|
| 361 |
+
.dashboard { border: 1px solid #e0e0e0; border-radius: 10px; margin-bottom: 25px; }
|
| 362 |
+
.dashboard summary { padding: 15px; font-weight: 600; color: var(--bg-medium); cursor: pointer; list-style: none; display: flex; justify-content: space-between; align-items: center; }
|
| 363 |
+
.dashboard summary::-webkit-details-marker { display: none; }
|
| 364 |
+
.dashboard summary:after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; transition: transform 0.2s; }
|
| 365 |
+
.dashboard[open] summary:after { transform: rotate(180deg); }
|
| 366 |
+
.dashboard-content { padding: 0 15px 15px; display: flex; flex-direction: column; gap: 20px; border-top: 1px solid #e0e0e0; }
|
| 367 |
+
.dashboard-section h3 { font-size: 1rem; margin-top: 15px; margin-bottom: 10px; color: #333; }
|
| 368 |
+
.dashboard-list { list-style: none; padding: 0; margin: 0; font-size: 0.9rem; }
|
| 369 |
+
.dashboard-list li { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
|
| 370 |
+
.dashboard-list li:last-child { border-bottom: none; }
|
| 371 |
+
.dashboard-list .env-id-small { font-weight: 600; color: var(--bg-medium); }
|
| 372 |
+
.dashboard-list .keyword-small { color: #777; font-style: italic; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
|
| 373 |
+
.dashboard-list .hits-small { font-weight: 500; }
|
| 374 |
+
.dashboard-list .time-small { color: #777; font-size: 0.8rem; }
|
| 375 |
+
|
| 376 |
+
@media (min-width: 601px) {
|
| 377 |
+
.dashboard-content { flex-direction: row; justify-content: space-between; align-items: flex-start; }
|
| 378 |
+
.dashboard-section { flex: 1; min-width: 0; }
|
| 379 |
+
.dashboard-section:first-child { padding-right: 15px; border-right: 1px solid #e0e0e0;}
|
| 380 |
+
.dashboard-section:last-child { padding-left: 15px;}
|
| 381 |
+
}
|
| 382 |
|
| 383 |
@media (max-width: 768px) {
|
| 384 |
.env-item { grid-template-columns: 1fr; gap: 12px; }
|
|
|
|
| 396 |
.add-env-form .button { width: 100%; padding: 14px; }
|
| 397 |
|
| 398 |
.stats-table th, .stats-table td { font-size: 0.75rem; padding: 6px 4px; }
|
|
|
|
| 399 |
}
|
| 400 |
</style>
|
| 401 |
</head>
|
|
|
|
| 410 |
{% endif %}
|
| 411 |
{% endwith %}
|
| 412 |
|
| 413 |
+
<details class="dashboard">
|
| 414 |
+
<summary>Сводка</summary>
|
| 415 |
+
<div class="dashboard-content">
|
| 416 |
+
<div class="dashboard-section">
|
| 417 |
+
<h3><i class="fas fa-fire"></i> Топ-5 активных сред</h3>
|
| 418 |
+
{% if top_5_envs %}
|
| 419 |
+
<ul class="dashboard-list">
|
| 420 |
+
{% for env in top_5_envs %}
|
| 421 |
<li>
|
| 422 |
<div>
|
| 423 |
+
<span class="env-id-small">{{ env.id }}</span>
|
| 424 |
+
<span class="keyword-small" title="{{ env.keyword }}">{{ env.keyword }}</span>
|
| 425 |
</div>
|
| 426 |
+
<span class="hits-small">{{ env.hits }} <i class="fas fa-eye fa-xs"></i></span>
|
| 427 |
</li>
|
| 428 |
{% endfor %}
|
| 429 |
</ul>
|
| 430 |
{% else %}
|
| 431 |
+
<div class="empty-list-placeholder" style="padding: 5px 0;">Нет данных</div>
|
| 432 |
{% endif %}
|
| 433 |
</div>
|
| 434 |
+
<div class="dashboard-section">
|
| 435 |
+
<h3><i class="fas fa-history"></i> Последние 5 входов</h3>
|
| 436 |
+
{% if last_5_logs %}
|
| 437 |
+
<ul class="dashboard-list">
|
| 438 |
+
{% for log in last_5_logs %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 439 |
<li>
|
| 440 |
<div>
|
| 441 |
+
<span class="env-id-small">{{ log.env_id }}</span>
|
| 442 |
+
<span class="time-small">{{ log.time.split(' ')[1] }}</span>
|
| 443 |
</div>
|
| 444 |
+
<span class="time-small">{{ log.time.split(' ')[0] }}</span>
|
| 445 |
</li>
|
| 446 |
{% endfor %}
|
| 447 |
</ul>
|
| 448 |
+
{% else %}
|
| 449 |
+
<div class="empty-list-placeholder" style="padding: 5px 0;">Нет данных</div>
|
| 450 |
{% endif %}
|
| 451 |
</div>
|
| 452 |
+
</div>
|
| 453 |
+
</details>
|
| 454 |
|
| 455 |
<div class="section">
|
| 456 |
<form method="POST" action="{{ url_for('create_environment') }}" class="add-env-form">
|
|
|
|
| 469 |
<input type="text" id="search-env" placeholder="🔍 Поиск...">
|
| 470 |
</div>
|
| 471 |
|
|
|
|
| 472 |
<div class="section">
|
| 473 |
{% if active_environments %}
|
| 474 |
<ul class="env-list">
|
|
|
|
| 966 |
<select id="nationality">
|
| 967 |
<option value="Eastern European">Восточная Европа</option>
|
| 968 |
<option value="Northern European">Скандинавская</option>
|
| 969 |
+
<option value="Mediterranean">Средиземноморская</option>
|
| 970 |
<option value="Asian">Азиатская</option>
|
| 971 |
<option value="Latin American">Латиноамериканская</option>
|
|
|
|
|
|
|
| 972 |
<option value="Middle Eastern">Ближневосточная</option>
|
| 973 |
<option value="South Asian">Южноазиатская</option>
|
| 974 |
+
<option value="African">Африканская</option>
|
| 975 |
<option value="Mixed Race">Смешанная</option>
|
| 976 |
</select>
|
| 977 |
</div>
|
|
|
|
| 1067 |
<select id="child_nationality">
|
| 1068 |
<option value="Eastern European">Восточная Европа</option>
|
| 1069 |
<option value="Northern European">Скандинавская</option>
|
|
|
|
| 1070 |
<option value="Mediterranean">Средиземноморская</option>
|
| 1071 |
+
<option value="Asian">Азиатская</option>
|
| 1072 |
+
<option value="South Asian">Южноазиатская</option>
|
| 1073 |
+
<option value="African">Африканская</option>
|
| 1074 |
</select>
|
| 1075 |
</div>
|
| 1076 |
<div class="form-group">
|
|
|
|
| 1211 |
};
|
| 1212 |
|
| 1213 |
const maleHairstyles = {
|
| 1214 |
+
'short classic cut': 'Короткая классическая', 'fade haircut': 'Фейд', 'slicked back hair': 'Зачесанные назад', 'textured crop': 'Текстурированный кроп', 'quiff': 'Квифф', 'man bun': 'Мужской пучок', 'buzz cut': 'Под ноль', 'medium-length wavy hair': 'Волнистые средней длины', 'long straight hair': 'Длинные прямые', 'side part': 'С боковым пробором', 'undercut': 'Андеркат'
|
| 1215 |
};
|
| 1216 |
|
| 1217 |
function switchMode(mode) {
|
|
|
|
| 1225 |
document.getElementById('modeObjectBtn').classList.toggle('active', mode === 'object');
|
| 1226 |
}
|
| 1227 |
|
| 1228 |
+
function populateSelect(selectElement, options, defaultValue = null) {
|
| 1229 |
selectElement.innerHTML = '';
|
| 1230 |
for (const value in options) {
|
| 1231 |
const option = document.createElement('option');
|
| 1232 |
option.value = value;
|
| 1233 |
option.textContent = options[value];
|
| 1234 |
+
if (value === defaultValue) {
|
| 1235 |
+
option.selected = true;
|
| 1236 |
+
}
|
| 1237 |
selectElement.appendChild(option);
|
| 1238 |
}
|
| 1239 |
}
|
|
|
|
| 1244 |
const hairstyleSelect = document.getElementById('hairstyle');
|
| 1245 |
|
| 1246 |
populateSelect(bodyTypeSelect, gender === 'female' ? femaleBodyTypes : maleBodyTypes);
|
| 1247 |
+
populateSelect(hairstyleSelect, gender === 'female' ? femaleHairstyles : maleHairstyles, gender === 'female' ? 'long straight hair' : null);
|
|
|
|
| 1248 |
}
|
| 1249 |
|
| 1250 |
function toggleOwnModel(isOwnModel) {
|
|
|
|
| 1432 |
data = load_data()
|
| 1433 |
active_environments = []
|
| 1434 |
archived_environments = []
|
|
|
|
| 1435 |
|
| 1436 |
for env_id, env_data in data.items():
|
| 1437 |
if not isinstance(env_data, dict): continue
|
| 1438 |
+
env_item = {
|
| 1439 |
+
"id": env_id,
|
| 1440 |
+
"keyword": env_data.get("keyword", "N/A"),
|
| 1441 |
+
"type": env_data.get("type", "closed"),
|
| 1442 |
+
"hits": env_data.get("hits", 0),
|
| 1443 |
+
"created_at": env_data.get("created_at", ""),
|
| 1444 |
+
"link": url_for('serve_env', env_id=env_id, _external=True)
|
| 1445 |
+
}
|
| 1446 |
+
if env_data.get("archived"):
|
| 1447 |
+
archived_environments.append(env_item)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1448 |
else:
|
| 1449 |
+
active_environments.append(env_item)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1450 |
|
| 1451 |
active_environments.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
| 1452 |
archived_environments.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
| 1453 |
|
| 1454 |
+
top_5_envs = sorted([e for e in active_environments if e.get('hits', 0) > 0], key=lambda x: x['hits'], reverse=True)[:5]
|
| 1455 |
+
|
| 1456 |
+
all_logs = []
|
| 1457 |
+
for env_id, env_data in data.items():
|
| 1458 |
+
if env_data and isinstance(env_data.get('logs'), list):
|
| 1459 |
+
for log in env_data['logs']:
|
| 1460 |
+
if isinstance(log, dict):
|
| 1461 |
+
log_copy = log.copy()
|
| 1462 |
+
log_copy['env_id'] = env_id
|
| 1463 |
+
all_logs.append(log_copy)
|
| 1464 |
+
|
| 1465 |
+
sorted_logs = sorted(all_logs, key=lambda x: x.get('time', ''), reverse=True)
|
| 1466 |
+
|
| 1467 |
+
last_5_logs = []
|
| 1468 |
+
for log in sorted_logs[:5]:
|
| 1469 |
+
try:
|
| 1470 |
+
utc_dt = datetime.fromisoformat(log['time'])
|
| 1471 |
+
almaty_dt = utc_dt + timedelta(hours=5)
|
| 1472 |
+
time_str = almaty_dt.strftime('%Y-%m-%d %H:%M:%S')
|
| 1473 |
+
last_5_logs.append({
|
| 1474 |
+
"env_id": log.get('env_id', 'N/A'),
|
| 1475 |
+
"time": time_str
|
| 1476 |
+
})
|
| 1477 |
+
except (ValueError, TypeError):
|
| 1478 |
+
continue
|
| 1479 |
+
|
| 1480 |
+
return render_template_string(ADMHOSTO_TEMPLATE,
|
| 1481 |
+
active_environments=active_environments,
|
| 1482 |
+
archived_environments=archived_environments,
|
| 1483 |
+
top_5_envs=top_5_envs,
|
| 1484 |
+
last_5_logs=last_5_logs)
|
| 1485 |
|
| 1486 |
@app.route('/admhosto/create', methods=['POST'])
|
| 1487 |
def create_environment():
|