Nota: Clique em Salvar no editor Flatlogic para pe

This commit is contained in:
Flatlogic Bot 2026-02-16 01:19:44 +00:00
parent cfc1f5d70a
commit a195d0853c
15 changed files with 380 additions and 81 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from .models import Project, PipelineStep, CgiAsset
from .models import Project, PipelineStep, CgiAsset, Scene, StudioConfig
class PipelineStepInline(admin.TabularInline):
model = PipelineStep
@ -9,13 +9,17 @@ class CgiAssetInline(admin.TabularInline):
model = CgiAsset
extra = 1
class SceneInline(admin.TabularInline):
model = Scene
extra = 1
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
list_display = ('title', 'project_type', 'status', 'created_at')
list_filter = ('project_type', 'status')
list_display = ('title', 'project_type', 'category', 'status', 'is_ai_generated', 'created_at')
list_filter = ('project_type', 'status', 'is_ai_generated')
search_fields = ('title', 'description')
prepopulated_fields = {'slug': ('title',)}
inlines = [PipelineStepInline, CgiAssetInline]
inlines = [PipelineStepInline, CgiAssetInline, SceneInline]
@admin.register(PipelineStep)
class PipelineStepAdmin(admin.ModelAdmin):
@ -26,3 +30,12 @@ class PipelineStepAdmin(admin.ModelAdmin):
class CgiAssetAdmin(admin.ModelAdmin):
list_display = ('name', 'project', 'asset_type', 'is_realistic')
list_filter = ('asset_type', 'is_realistic')
@admin.register(Scene)
class SceneAdmin(admin.ModelAdmin):
list_display = ('project', 'number', 'title')
list_filter = ('project',)
@admin.register(StudioConfig)
class StudioConfigAdmin(admin.ModelAdmin):
list_display = ('id', 'admin_access_key', 'is_setup')

View File

@ -0,0 +1,48 @@
# Generated by Django 5.2.7 on 2026-02-16 01:12
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_studioconfig_cgiasset_assigned_artist_and_more'),
]
operations = [
migrations.AddField(
model_name='cgiasset',
name='physical_description',
field=models.TextField(blank=True, help_text='Detailed physical appearance based on real humans'),
),
migrations.AddField(
model_name='project',
name='category',
field=models.CharField(default='Sci-Fi', max_length=100),
),
migrations.AddField(
model_name='project',
name='full_script',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='project',
name='is_ai_generated',
field=models.BooleanField(default=False),
),
migrations.CreateModel(
name='Scene',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.PositiveIntegerField(default=1)),
('title', models.CharField(max_length=255)),
('description', models.TextField()),
('visual_prompt', models.TextField(blank=True)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scenes', to='core.project')),
],
options={
'ordering': ['number'],
},
),
]

View File

@ -31,11 +31,14 @@ class Project(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True, blank=True)
project_type = models.CharField(max_length=10, choices=TYPES, default='MOVIE')
category = models.CharField(max_length=100, default='Sci-Fi')
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='PRE')
description = models.TextField(blank=True)
full_script = models.TextField(blank=True)
thumbnail_url = models.URLField(blank=True, help_text="URL to a representative image")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_ai_generated = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if not self.slug:
@ -45,6 +48,19 @@ class Project(models.Model):
def __str__(self):
return self.title
class Scene(models.Model):
project = models.ForeignKey(Project, related_name='scenes', on_delete=models.CASCADE)
number = models.PositiveIntegerField(default=1)
title = models.CharField(max_length=255)
description = models.TextField()
visual_prompt = models.TextField(blank=True)
class Meta:
ordering = ['number']
def __str__(self):
return f"Scene {self.number}: {self.title}"
class PipelineStep(models.Model):
STAGES = (
# Pre-Production
@ -85,10 +101,11 @@ class CgiAsset(models.Model):
name = models.CharField(max_length=255)
asset_type = models.CharField(max_length=10, choices=ASSET_TYPES)
is_realistic = models.BooleanField(default=True)
physical_description = models.TextField(blank=True, help_text="Detailed physical appearance based on real humans")
current_stage = models.CharField(max_length=100, default='Modeling')
version = models.PositiveIntegerField(default=1)
file_location = models.CharField(max_length=500, blank=True, help_text="Path or URL to the digital file")
assigned_artist = models.CharField(max_length=100, blank=True)
def __str__(self):
return f"{self.name} ({self.get_asset_type_display()})"
return f"{self.name} ({self.get_asset_type_display()})"

View File

@ -28,6 +28,9 @@
<a href="{% url 'asset_library' %}" class="btn btn-outline-purple shadow-neon">
<i class="bi bi-box-seam me-2"></i> Biblioteca de Assets
</a>
<a href="{% url 'studio_ai' %}" class="btn btn-neon-purple">
<i class="bi bi-magic me-2"></i> STUDIO AI (Criador Automático)
</a>
</div>
<div>
{% if is_admin %}
@ -55,6 +58,9 @@
{% else %}
<div class="text-center">
<i class="bi bi-camera-reels text-purple display-1"></i>
{% if project.is_ai_generated %}
<div class="mt-2"><span class="badge bg-purple small">AI GENERATED</span></div>
{% endif %}
</div>
{% endif %}
</div>
@ -62,20 +68,23 @@
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-start mb-2">
<h3 class="card-title text-white font-syncopate h4 mb-0">{{ project.title }}</h3>
<span class="badge {% if project.status == 'PROD' %}bg-cyan text-dark{% else %}bg-purple{% endif %}">
{{ project.get_status_display }}
</span>
<div class="text-end">
<span class="badge {% if project.status == 'PROD' %}bg-cyan text-dark{% else %}bg-purple{% endif %} d-block mb-1">
{{ project.get_status_display }}
</span>
<span class="badge bg-dark border border-secondary small">{{ project.category }}</span>
</div>
</div>
<p class="text-secondary small mb-4">{{ project.description|truncatewords:20 }}</p>
<!-- Pipeline Progress Mini -->
<div class="mb-4">
<div class="d-flex justify-content-between small text-secondary mb-1">
<span>Progresso Geral</span>
<span>85%</span> <!-- Dynamic calculation could go here -->
<span>Status Pipeline</span>
<span>{% with last=project.steps.last %}{{ last.progress|default:0 }}{% endwith %}%</span>
</div>
<div class="progress bg-black" style="height: 6px;">
<div class="progress-bar bg-cyan shadow-neon" role="progressbar" style="width: 85%;"></div>
<div class="progress-bar bg-cyan shadow-neon" role="progressbar" style="width: {% with last=project.steps.last %}{{ last.progress|default:0 }}{% endwith %}%;"></div>
</div>
</div>
@ -98,4 +107,4 @@
}
.bg-cyan { background-color: var(--electric-cyan) !important; }
</style>
{% endblock %}
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ project.title }} | Pipeline Detail{% endblock %}
{% block title %}{{ project.title }} | Studio AI Production{% endblock %}
{% block content %}
<div class="hero-section py-5">
@ -15,13 +15,19 @@
<div class="row align-items-center">
<div class="col-lg-8">
<span class="pipeline-badge badge-{{ project.status|lower }} mb-3 d-inline-block">{{ project.get_status_display }}</span>
<h1 class="display-4 outfit mb-3">{{ project.title }}</h1>
<div class="d-flex align-items-center mb-3">
<span class="pipeline-badge badge-{{ project.status|lower }} me-2">{{ project.get_status_display }}</span>
{% if project.is_ai_generated %}
<span class="badge bg-purple text-white rounded-pill px-3 py-2"><i class="bi bi-robot me-1"></i> AI GENERATED</span>
{% endif %}
<span class="badge bg-dark border border-secondary rounded-pill px-3 py-2 ms-2">{{ project.category }}</span>
</div>
<h1 class="display-4 outfit mb-3 text-gradient-neon">{{ project.title }}</h1>
<p class="lead text-muted">{{ project.description }}</p>
</div>
<div class="col-lg-4 text-lg-end">
<div class="stats-card bg-opacity-10 border-opacity-10">
<span class="text-muted small text-uppercase d-block mb-1">Global Delivery</span>
<span class="text-muted small text-uppercase d-block mb-1">Status da Produção</span>
<span class="display-6 outfit fw-bold text-cyan">
{% with last_step=project.steps.last %}{{ last_step.progress|default:0 }}{% endwith %}%
</span>
@ -33,71 +39,105 @@
<section class="py-5">
<div class="container">
<div class="row g-5">
<div class="col-lg-8">
<h3 class="h4 outfit mb-4 section-title">CGI Production Pipeline</h3>
<div class="pipeline-flow">
<ul class="nav nav-pills mb-5 justify-content-center" id="prodTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active fw-bold px-4" id="pipeline-tab" data-bs-toggle="pill" data-bs-target="#pipeline" type="button" role="tab">PIPELINE CGI</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-4" id="script-tab" data-bs-toggle="pill" data-bs-target="#script" type="button" role="tab">ROTEIRO & CENAS</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-4" id="cast-tab" data-bs-toggle="pill" data-bs-target="#cast" type="button" role="tab">PERSONAGENS (REAL)</button>
</li>
</ul>
<div class="tab-content" id="prodTabContent">
<!-- Pipeline Tab -->
<div class="tab-pane fade show active" id="pipeline" role="tabpanel">
<div class="row g-4">
{% for step in steps %}
<div class="project-card p-4 mb-3 border-opacity-10">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center">
<div class="bg-cyan bg-opacity-10 text-cyan rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 32px; height: 32px; font-size: 0.8rem; font-weight: bold;">
{{ forloop.counter }}
</div>
<h4 class="h6 mb-0 outfit">{{ step.get_name_display }}</h4>
<div class="col-md-4 col-lg-3">
<div class="project-card p-4 h-100 border-opacity-10">
<div class="text-muted small mb-2">ETAPA {{ forloop.counter }}</div>
<h4 class="h6 mb-3 outfit">{{ step.get_name_display }}</h4>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="small text-muted">Progresso</span>
<span class="small text-cyan">{{ step.progress }}%</span>
</div>
<div class="progress" style="height: 4px;">
<div class="progress-bar {% if step.is_completed %}bg-cyan{% endif %}" style="width: {{ step.progress }}%"></div>
</div>
{% if step.is_completed %}
<span class="badge bg-cyan text-black rounded-pill">COMPLETO</span>
{% else %}
<span class="text-muted small">{{ step.progress }}%</span>
{% endif %}
</div>
<div class="progress" style="height: 4px;">
<div class="progress-bar {% if step.is_completed %}bg-cyan{% endif %}" style="width: {{ step.progress }}%"></div>
</div>
</div>
{% empty %}
<div class="text-center py-5 border border-dashed border-secondary border-opacity-25 rounded-4">
<p class="text-muted">Pipeline não inicializado. Configure as etapas no Admin.</p>
<a href="/admin/core/pipelinestep/add/?project={{ project.id }}" class="btn btn-sm btn-outline-cyan">Adicionar Etapa +</a>
</div>
{% endfor %}
</div>
</div>
<div class="col-lg-4">
<div class="sticky-top" style="top: 100px;">
<h3 class="h4 outfit mb-4 section-title">Assets Digitais</h3>
<div class="assets-container">
{% for asset in assets %}
<div class="asset-list-item d-flex justify-content-between align-items-center">
<div>
<span class="d-block outfit fw-bold small">{{ asset.name }}</span>
<span class="text-muted" style="font-size: 0.7rem;">{{ asset.get_asset_type_display }} • {{ asset.current_stage }}</span>
<!-- Script & Scenes Tab -->
<div class="tab-pane fade" id="script" role="tabpanel">
<div class="row justify-content-center">
<div class="col-lg-9">
{% for scene in scenes %}
<div class="scene-item mb-5 p-4 glass-card border-opacity-10 rounded-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-cyan text-black fw-bold rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px;">
{{ scene.number }}
</div>
<h3 class="h4 outfit mb-0 text-cyan">{{ scene.title }}</h3>
</div>
<div class="scene-body text-light lead-sm border-start border-cyan border-opacity-25 ps-4 ms-3">
{{ scene.description|linebreaks }}
</div>
{% if asset.is_realistic %}
<span class="badge bg-purple bg-opacity-10 text-purple border border-purple border-opacity-25" style="font-size: 0.6rem;">REALISTA</span>
{% endif %}
</div>
{% empty %}
<p class="text-muted small">Nenhum asset (personagens/cenários) vinculado.</p>
<p class="text-center text-muted py-5">Nenhuma cena gerada para este roteiro.</p>
{% endfor %}
</div>
<div class="mt-5 p-4 bg-purple bg-opacity-05 rounded-4 border border-purple border-opacity-10">
<h5 class="h6 outfit text-purple mb-3">Diretrizes de Qualidade</h5>
<ul class="list-unstyled small text-muted">
<li class="mb-2">✓ Topologia limpa para rigging</li>
<li class="mb-2">✓ Texturas 4K/8K PBR</li>
<li class="mb-2">✓ Iluminação física (PBR)</li>
<li>✓ Renderização em EXR Multi-camada</li>
</ul>
</div>
</div>
<!-- Cast & Characters Tab -->
<div class="tab-pane fade" id="cast" role="tabpanel">
<div class="row g-4">
{% for asset in assets %}
{% if asset.asset_type == 'CHAR' %}
<div class="col-md-6">
<div class="project-card p-4 h-100 border-opacity-10 d-flex gap-4">
<div class="char-placeholder rounded-4 bg-dark border border-secondary border-opacity-25 d-flex align-items-center justify-content-center" style="width: 120px; height: 160px; min-width: 120px;">
<i class="bi bi-person-bounding-box text-muted display-4"></i>
</div>
<div>
<h4 class="outfit text-cyan mb-2">{{ asset.name }}</h4>
<span class="badge bg-purple bg-opacity-10 text-purple border border-purple border-opacity-25 mb-3">APARÊNCIA REALISTA</span>
<p class="small text-muted mb-0"><strong>Físico:</strong> {{ asset.physical_description }}</p>
</div>
</div>
</div>
{% endif %}
{% empty %}
<p class="text-center text-muted py-5">Nenhum personagem definido no elenco.</p>
{% endfor %}
</div>
</div>
</div>
</div>
</section>
{% endblock %}
<style>
.nav-pills .nav-link {
color: rgba(255,255,255,0.5);
border: 1px solid rgba(255,255,255,0.1);
margin: 0 5px;
border-radius: 50px;
}
.nav-pills .nav-link.active {
background-color: #00e5ff;
color: #000;
box-shadow: 0 0 15px rgba(0,229,255,0.4);
}
.lead-sm {
font-size: 1.1rem;
line-height: 1.8;
}
</style>
{% endblock %}

View File

@ -0,0 +1,79 @@
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card glass-card border-0 shadow-lg text-white">
<div class="card-body p-5">
<div class="text-center mb-5">
<i class="bi bi-cpu-fill text-cyan display-1 mb-3"></i>
<h1 class="display-4 fw-bold text-gradient-neon">STUDIO AI AUTOMÁTICO</h1>
<p class="lead text-muted">Crie Super-Produções (Filmes e Séries) totalmente automatizadas com IA.</p>
</div>
<form action="{% url 'generate_production' %}" method="POST" id="aiForm">
{% csrf_token %}
<div class="mb-4">
<label class="form-label text-cyan fw-bold">TIPO DE PRODUÇÃO</label>
<select name="project_type" class="form-select bg-dark text-white border-secondary">
<option value="MOVIE">Longa-Metragem (Filme)</option>
<option value="SERIES">Série de TV</option>
<option value="SHORT">Curta-Metragem</option>
</select>
</div>
<div class="mb-4">
<label class="form-label text-cyan fw-bold">CATEGORIA / GÊNERO</label>
<select name="category" class="form-select bg-dark text-white border-secondary">
<option value="Sci-Fi">Ficção Científica</option>
<option value="Ação">Ação & Aventura</option>
<option value="Drama">Drama Cinematográfico</option>
<option value="Terror">Terror Psicológico</option>
<option value="Fantasia">Fantasia Épica</option>
<option value="Cyberpunk">Cyberpunk / Neon-Noir</option>
</select>
</div>
<div class="mb-4">
<label class="form-label text-cyan fw-bold">TEMA OU IDEIA CENTRAL</label>
<textarea name="theme" class="form-control bg-dark text-white border-secondary" rows="3" placeholder="Ex: Uma guerra entre IAs e humanos em um Rio de Janeiro futurista..."></textarea>
<div class="form-text text-muted">A IA criará personagens reais, cenas e roteiro completo com base nisso.</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-lg btn-neon-purple fw-bold py-3" id="generateBtn">
<span class="spinner-border spinner-border-sm d-none" id="loader"></span>
<i class="bi bi-magic me-2"></i> LANÇAR SUPER-PRODUÇÃO AGORA
</button>
</div>
</form>
<div id="loadingMsg" class="text-center mt-4 d-none">
<p class="text-cyan animate-pulse">A IA está gerando o roteiro, personagens e cenas... Aguarde o processamento cinematográfico.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.getElementById('aiForm').onsubmit = function() {
document.getElementById('generateBtn').disabled = true;
document.getElementById('loader').classList.remove('d-none');
document.getElementById('loadingMsg').classList.remove('d-none');
};
</script>
<style>
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: .5; }
}
</style>
{% endblock %}

View File

@ -1,10 +1,12 @@
from django.urls import path
from .views import home, project_detail, asset_library, admin_login, admin_logout
from . import views
urlpatterns = [
path("", home, name="home"),
path("project/<slug:slug>/", project_detail, name="project_detail"),
path("assets/", asset_library, name="asset_library"),
path("studio-admin/login/", admin_login, name="admin_login"),
path("studio-admin/logout/", admin_logout, name="admin_logout"),
]
path('', views.home, name='home'),
path('admin-login/', views.admin_login, name='admin_login'),
path('admin-logout/', views.admin_logout, name='admin_logout'),
path('assets/', views.asset_library, name='asset_library'),
path('studio-ai/', views.studio_ai, name='studio_ai'),
path('generate-production/', views.generate_production, name='generate_production'),
path('project/<slug:slug>/', views.project_detail, name='project_detail'),
]

View File

@ -1,11 +1,12 @@
import os
import platform
import json
from functools import wraps
from django import get_version as django_version
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.contrib import messages
from .models import Project, PipelineStep, CgiAsset, StudioConfig
from django.http import JsonResponse
from .models import Project, PipelineStep, CgiAsset, StudioConfig, Scene
from ai.local_ai_api import LocalAIApi
def studio_admin_required(view_func):
"""Decorator to restrict access to studio admin only."""
@ -19,12 +20,11 @@ def studio_admin_required(view_func):
def home(request):
"""Render the CGI Studio Command Center."""
# Ensure StudioConfig exists and generate key if needed
config, created = StudioConfig.objects.get_or_create(id=1)
if created or not config.admin_access_key:
config.save() # Triggers uuid generation
config.save()
projects = Project.objects.prefetch_related('steps').all()
projects = Project.objects.prefetch_related('steps').all().order_by('-created_at')
total_projects = projects.count()
active_productions = projects.filter(status='PROD').count()
@ -40,6 +40,96 @@ def home(request):
}
return render(request, "core/index.html", context)
@studio_admin_required
def studio_ai(request):
"""Page to configure and launch AI-automated productions."""
return render(request, "core/studio_ai.html")
@studio_admin_required
def generate_production(request):
"""AI logic to create a full production automatically."""
if request.method == "POST":
category = request.POST.get("category", "Sci-Fi")
proj_type = request.POST.get("project_type", "MOVIE")
theme = request.POST.get("theme", "Future of humanity")
prompt = f"""
Create a detailed production plan for a {proj_type} in the {category} category.
Theme: {theme}
Requirements:
1. Unique Title and a compelling description.
2. 3 Main Characters with names and detailed physical descriptions (based on REAL humans/actors style).
3. 5 Key Scenes with titles and narrative descriptions.
Return the result ONLY in JSON format with the following structure:
{{
"title": "...",
"description": "...",
"characters": [
{{"name": "...", "description": "...", "type": "CHAR"}}
],
"scenes": [
{{"title": "...", "description": "..."}}
]
}}
"""
response = LocalAIApi.create_response({
"input": [
{"role": "system", "content": "You are an expert Hollywood Producer and AI Cinema Director."},
{"role": "user", "content": prompt},
],
"response_format": {"type": "json_object"},
})
if response.get("success"):
try:
data = LocalAIApi.decode_json_from_response(response)
# Create the Project
project = Project.objects.create(
title=data['title'],
project_type=proj_type,
category=category,
description=data['description'],
is_ai_generated=True,
status='PRE'
)
# Create default Pipeline Steps
stages = [s[0] for s in PipelineStep.STAGES]
for stage in stages:
PipelineStep.objects.create(project=project, name=stage, progress=0)
# Create Characters (Assets)
for char in data['characters']:
CgiAsset.objects.create(
project=project,
name=char['name'],
asset_type='CHAR',
physical_description=char['description']
)
# Create Scenes
for i, scene in enumerate(data['scenes']):
Scene.objects.create(
project=project,
number=i+1,
title=scene['title'],
description=scene['description']
)
messages.success(request, f"Super Produção '{project.title}' criada com sucesso pelo Studio AI!")
return redirect('project_detail', slug=project.slug)
except Exception as e:
messages.error(request, f"Erro ao processar resposta da AI: {str(e)}")
else:
messages.error(request, f"Erro na API de IA: {response.get('error')}")
return redirect('studio_ai')
def admin_login(request):
"""View to enter the unique admin access key."""
if request.method == "POST":
@ -80,11 +170,12 @@ def asset_library(request):
def project_detail(request, slug):
"""Render the detailed pipeline for a specific production."""
project = get_object_or_404(Project.objects.prefetch_related('steps', 'assets'), slug=slug)
project = get_object_or_404(Project.objects.prefetch_related('steps', 'assets', 'scenes'), slug=slug)
context = {
"project": project,
"steps": project.steps.all(),
"assets": project.assets.all(),
"scenes": project.scenes.all(),
}
return render(request, "core/project_detail.html", context)
return render(request, "core/project_detail.html", context)