TEDDyx86 commited on
Commit
e3bdc52
·
0 Parent(s):

Cleanup: Repositório otimizado (código + dashboard apenas)

Browse files
.dockerignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ignorar pastas densas que já estão no repo local
2
+ venv/
3
+ .venv/
4
+ __pycache__/
5
+ .tmp/
6
+ .git/
7
+ .env
8
+
9
+ # Outros
10
+ *.wav
11
+ *.mp3
12
+ *.log
.gitattributes ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ *.wav filter=lfs diff=lfs merge=lfs -text
2
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
3
+ local_finetuned_model/model.safetensors filter=lfs diff=lfs merge=lfs -text
4
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
5
+ *.zip filter=lfs diff=lfs merge=lfs -text
6
+ *.bin filter=lfs diff=lfs merge=lfs -text
7
+ logo_base64.txt filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Venv
2
+ venv/
3
+ .venv/
4
+ env/
5
+
6
+ # Python caching
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+
11
+ # Temporary files
12
+ .tmp/
13
+ *.wav
14
+ *.mp3
15
+ *.png
16
+ *.jpg
17
+ *.jpeg
18
+
19
+ # Environments
20
+ .env
21
+ .flaskenv
22
+
23
+ # Models
24
+ local_finetuned_model/
25
+ *.safetensors
26
+ *.bin
27
+ *.h5
28
+ *.pt
29
+ *.onnx
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
.vercelignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ignora arquivos de backend e ML para o Vercel não tentar buildar Python
2
+ execution/
3
+ directives/
4
+ requirements.txt
5
+ Dockerfile
6
+ Procfile
7
+ main.py
8
+ venv/
9
+ .tmp/
10
+ .env
11
+ # Ignora caches
12
+ __pycache__/
ConfereAI_FastTrain_Colab.ipynb ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 0,
4
+ "metadata": {
5
+ "colab": {
6
+ "provenance": [],
7
+ "gpuType": "T4"
8
+ },
9
+ "kernelspec": {
10
+ "name": "python3",
11
+ "display_name": "Python 3"
12
+ },
13
+ "language_info": {
14
+ "name": "python"
15
+ }
16
+ },
17
+ "cells": [
18
+ {
19
+ "cell_type": "markdown",
20
+ "metadata": {
21
+ "id": "header"
22
+ },
23
+ "source": [
24
+ "# 🚀 ConfereAI - Fast Training (GPU Edition)\n",
25
+ "Este notebook permite treinar o motor neural do ConfereAI utilizando a GPU gratuita do Google Colab. \n",
26
+ "\n",
27
+ "**Instruções:**\n",
28
+ "1. Vá em `Ambiente de Execução` > `Alterar tipo de ambiente` e selecione **T4 GPU**.\n",
29
+ "2. Preencha as configurações abaixo.\n",
30
+ "3. Execute as células em ordem."
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": null,
36
+ "metadata": {
37
+ "id": "setup"
38
+ },
39
+ "outputs": [],
40
+ "source": [
41
+ "# @title 1. Instalar Dependências\n",
42
+ "!pip install -q transformers[torch] librosa soundfile huggingface_hub accelerate"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": null,
48
+ "metadata": {
49
+ "id": "config"
50
+ },
51
+ "outputs": [],
52
+ "source": [
53
+ "# @title 2. Configurações do Hugging Face\n",
54
+ "HF_TOKEN = \"\" # @param {type:\"string\"}\n",
55
+ "REPO_ID = \"TEDDyx86/confereai-dev\" # @param {type:\"string\"}\n",
56
+ "BRANCH = \"main\" # @param {type:\"string\"}\n",
57
+ "\n",
58
+ "from huggingface_hub import HfApi, login\n",
59
+ "if HF_TOKEN:\n",
60
+ " login(token=HF_TOKEN)\n",
61
+ "else:\n",
62
+ " print(\"❌ Por favor, insira o seu HF_TOKEN!\")"
63
+ ]
64
+ },
65
+ {
66
+ "cell_type": "code",
67
+ "execution_count": null,
68
+ "metadata": {
69
+ "id": "upload"
70
+ },
71
+ "outputs": [],
72
+ "source": [
73
+ "# @title 3. Upload do Dataset (.zip)\n",
74
+ "from google.colab import files\n",
75
+ "import zipfile\n",
76
+ "import os\n",
77
+ "import shutil\n",
78
+ "\n",
79
+ "uploaded = files.upload()\n",
80
+ "dataset_zip = list(uploaded.keys())[0]\n",
81
+ "\n",
82
+ "DATASET_DIR = \"dataset_training\"\n",
83
+ "if os.path.exists(DATASET_DIR): shutil.rmtree(DATASET_DIR)\n",
84
+ "os.makedirs(DATASET_DIR)\n",
85
+ "\n",
86
+ "with zipfile.ZipFile(dataset_zip, 'r') as zip_ref:\n",
87
+ " zip_ref.extractall(DATASET_DIR)\n",
88
+ "\n",
89
+ "print(f\"✅ Dataset extraído em: {DATASET_DIR}\")"
90
+ ]
91
+ },
92
+ {
93
+ "cell_type": "code",
94
+ "execution_count": null,
95
+ "metadata": {
96
+ "id": "training"
97
+ },
98
+ "outputs": [],
99
+ "source": [
100
+ "# @title 4. Executar Treinamento (Fine-Tuning)\n",
101
+ "import torch\n",
102
+ "from torch.utils.data import Dataset\n",
103
+ "from transformers import Wav2Vec2FeatureExtractor, Wav2Vec2ForSequenceClassification, Trainer, TrainingArguments\n",
104
+ "import librosa\n",
105
+ "\n",
106
+ "BASE_MODEL = \"HyperMoon/wav2vec2-base-960h-finetuned-deepfake\"\n",
107
+ "OUTPUT_DIR = \"local_finetuned_model\"\n",
108
+ "\n",
109
+ "class DeepfakeDataset(Dataset):\n",
110
+ " def __init__(self, root_dir, processor):\n",
111
+ " self.files = []\n",
112
+ " self.processor = processor\n",
113
+ " for label, folder in enumerate(['real', 'fake']):\n",
114
+ " path = os.path.join(root_dir, folder)\n",
115
+ " if os.path.exists(path):\n",
116
+ " for f in os.listdir(path):\n",
117
+ " if f.endswith(('.wav', '.mp3', '.flac')):\n",
118
+ " self.files.append({\"path\": os.path.join(path, f), \"label\": label})\n",
119
+ "\n",
120
+ " def __len__(self): return len(self.files)\n",
121
+ " def __getitem__(self, idx):\n",
122
+ " item = self.files[idx]\n",
123
+ " speech, _ = librosa.load(item[\"path\"], sr=16000)\n",
124
+ " input_values = self.processor(speech, sampling_rate=16000, return_tensors=\"pt\", padding=\"max_length\", max_length=160000, truncation=True).input_values[0]\n",
125
+ " return {\"input_values\": input_values, \"labels\": torch.tensor(item[\"label\"], dtype=torch.long)}\n",
126
+ "\n",
127
+ "processor = Wav2Vec2FeatureExtractor.from_pretrained(BASE_MODEL)\n",
128
+ "model = Wav2Vec2ForSequenceClassification.from_pretrained(BASE_MODEL, num_labels=2, ignore_mismatched_sizes=True)\n",
129
+ "\n",
130
+ "# Congelar base para focar no aprendizado das novas fraudes (Lógica Robusta)\n",
131
+ "if hasattr(model, 'freeze_feature_extractor'):\n",
132
+ " model.freeze_feature_extractor()\n",
133
+ "elif hasattr(model, 'freeze_feature_encoder'):\n",
134
+ " model.freeze_feature_encoder()\n",
135
+ "\n",
136
+ "if hasattr(model, 'wav2vec2'):\n",
137
+ " for param in model.wav2vec2.parameters(): param.requires_grad = False\n",
138
+ "\n",
139
+ "training_args = TrainingArguments(\n",
140
+ " output_dir=\"./results\",\n",
141
+ " num_train_epochs=5,\n",
142
+ " per_device_train_batch_size=4,\n",
143
+ " gradient_accumulation_steps=2,\n",
144
+ " learning_rate=2e-5,\n",
145
+ " logging_steps=1,\n",
146
+ " push_to_hub=False,\n",
147
+ " report_to=\"none\"\n",
148
+ ")\n",
149
+ "\n",
150
+ "trainer = Trainer(\n",
151
+ " model=model,\n",
152
+ " args=training_args,\n",
153
+ " train_dataset=DeepfakeDataset(DATASET_DIR, processor)\n",
154
+ ")\n",
155
+ "\n",
156
+ "print(\"🚀 Iniciando treinamento na GPU...\")\n",
157
+ "trainer.train()\n",
158
+ "\n",
159
+ "model.save_pretrained(OUTPUT_DIR)\n",
160
+ "processor.save_pretrained(OUTPUT_DIR)\n",
161
+ "print(f\"✅ Treinamento concluído. Modelo salvo em {OUTPUT_DIR}\")"
162
+ ]
163
+ },
164
+ {
165
+ "cell_type": "code",
166
+ "execution_count": null,
167
+ "metadata": {
168
+ "id": "push"
169
+ },
170
+ "outputs": [],
171
+ "source": [
172
+ "# @title 5. Sincronizar com Hugging Face Space\n",
173
+ "api = HfApi()\n",
174
+ "print(f\"📦 Subindo modelo para {REPO_ID}...\")\n",
175
+ "\n",
176
+ "api.upload_folder(\n",
177
+ " folder_path=OUTPUT_DIR,\n",
178
+ " path_in_repo=OUTPUT_DIR,\n",
179
+ " repo_id=REPO_ID,\n",
180
+ " repo_type=\"space\",\n",
181
+ " token=HF_TOKEN,\n",
182
+ " commit_message=\"🤖 Auto-Update: Novo modelo treinado via Google Colab\"\n",
183
+ ")\n",
184
+ "\n",
185
+ "print(\"✨ Sucesso! O seu Space irá reiniciar em breve com o novo modelo.\")"
186
+ ]
187
+ }
188
+ ]
189
+ }
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Instala dependências do sistema
4
+ RUN apt-get update && apt-get install -y \
5
+ libsndfile1 \
6
+ ffmpeg \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ WORKDIR /app
10
+
11
+ # Copia arquivos de requisitos
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copia o resto do código
16
+ COPY . .
17
+
18
+ # Garante que a pasta .tmp existe e tem permissão
19
+ RUN mkdir -p .tmp && chmod 777 .tmp
20
+
21
+ # Porta padrão do Hugging Face Spaces
22
+ ENV PORT=7860
23
+ EXPOSE 7860
24
+
25
+ # Comando para iniciar o servidor
26
+ CMD ["python", "main.py"]
README.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ConfereAI - Audio Fraud Detection (V2.2)
3
+ emoji: 🛡️
4
+ colorFrom: purple
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: true
9
+ ---
10
+
11
+ # 🛡️ CONFEREAI
12
+ ### *Verdade na voz, integridade no som.*
13
+
14
+ O **ConfereAI** é uma plataforma de segurança cibernética de última geração projetada para identificar e neutralizar fraudes de áudio, deepfakes e vozes clonadas via Inteligência Artificial. Utilizando uma arquitetura de redes neurais profundas, o sistema analisa micro-imperfeições acústicas imperceptíveis ao ouvido humano.
15
+
16
+ ![ConfereAI Dashboard](dashboard/assets/logo.png)
17
+
18
+ ## 🚀 Diferenciais Tecnológicos
19
+
20
+ - **🧠 Motor Neural Local**: Diferente de soluções que dependem de APIs instáveis, o ConfereAI utiliza um motor dedicado baseado em **Wav2Vec 2.0** (HyperMoon) rodando localmente no servidor.
21
+ - **📊 Evidência Espectral**: Gera espectrogramas de Mel em tempo real, permitindo uma análise forense visual das frequências de áudio.
22
+ - **⚡ Resposta Instantânea**: Análise completa em segundos, ideal para validação de identidade e prevenção de fraudes em tempo real.
23
+ - **💎 Interface Onyx**: Dashboard premium com Estética Onyx e Glassmorphism, focado em clareza e experiência do usuário (UX).
24
+
25
+ ## 🛠️ Arquitetura de Software
26
+
27
+ O sistema é dividido em duas camadas principais:
28
+
29
+ 1. **Backend (Python/FastAPI)**:
30
+ - Gerenciamento de arquivos e processamento paralelo.
31
+ - Extração de características com `Librosa`.
32
+ - Inferência neural via `PyTorch` e `Transformers`.
33
+ 2. **Frontend (Vanilla JS/CSS)**:
34
+ - Interface ultra-responsiva sem dependências pesadas.
35
+ - Visualização dinâmica de resultados e medidores de confiança neon.
36
+
37
+ ## 🔬 O Coração da IA: HyperMoon Engine
38
+
39
+ Utilizamos o modelo **HyperMoon/wav2vec2-base-960h-finetuned-deepfake**, treinado com o dataset acadêmico **ASVspoof**.
40
+ - **Foco**: Detecção de descontinuidades rítmicas e artefatos de compressão típicos de IAs generativas.
41
+ - **Veredito**: Entrega um score de probabilidade (0% a 100%) e um veredito direto: **AUTÊNTICO** ou **FRAUDE DETECTADA**.
42
+
43
+ ## 📦 Como Rodar o Projeto
44
+
45
+ ### Localmente (Docker)
46
+ ```bash
47
+ docker build -t confereai .
48
+ docker run -p 7860:7860 confereai
49
+ ```
50
+
51
+ ### Deploy no Hugging Face Spaces
52
+ 1. Crie um novo **Space** no Hugging Face.
53
+ 2. Selecione o SDK: **Docker**.
54
+ 3. Faça o push deste repositório.
55
+ 4. O sistema irá buildar e servir automaticamente na porta 7860.
56
+
57
+ ---
58
+ **CONFEREAI** - *Protegendo a integridade da comunicação humana na era da IA.*
agent.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Diretrizes do Agente IA (Antigravity)
2
+
3
+ Ao trabalhar neste projeto (ConfereAI), você (a IA) DEVE obrigatoriamente referenciar e seguir as diretrizes/metodologias listadas na pasta `superpowers/skills` conforme o contexto da tarefa em mãos.
4
+
5
+ Sempre que o Humano solicitar uma alteração ou criação, verifique qual skill se aplica e aja rigorosamente de acordo com ela:
6
+
7
+ ## 1. Regra Geral de Inicialização
8
+ - NUNCA comece a escrever código ou desenhar componentes para uma nova feature sem antes consultar e executar o processo iterativo definido em `superpowers/skills/brainstorming/SKILL.md`. O Humano deve aprovar a ideia antes do código nascer.
9
+
10
+ ## 2. Para Tarefas de Machine Learning (Motor)
11
+ - A precisão matemática é inegociável. Para qualquer script de processamento de dados, manipulação de tensores ou modelo em si, leia e aplique OBRIGATORIAMENTE o fluxo de `superpowers/skills/test-driven-development/SKILL.md`.
12
+ - Se o Humano relatar anomalias no treinamento (loss não cai, acurácia baixa) ou na inferência, NÃO altere nada sem antes invocar o `superpowers/skills/systematic-debugging/SKILL.md` para isolar a causa-raiz cientificamente.
13
+ - Quando uma etapa crucial do modelo for finalizada, exija as provas estabelecidas em `superpowers/skills/verification-before-completion/SKILL.md`.
14
+
15
+ ## 3. Para Tarefas de Frontend (Dashboard UI/UX)
16
+ - Antes de alterar layouts, reescrever CSS ou criar novos componentes globais, execute os passos em `superpowers/skills/writing-plans/SKILL.md`. Mostre o plano (passo a passo de 2 a 5 minutos) ao Humano e espere aprovação.
17
+ - Se a refatoração for de alto impacto, siga o `superpowers/skills/using-git-worktrees/SKILL.md` para criar um ambiente isolado de testes que proteja o dashboard atual.
18
+ - Para manter a harmonia do Design System, crie o hábito de acionar o `superpowers/skills/requesting-code-review/SKILL.md` ao final de cada etapa visual, passando a bola para o Humano aprovar a usabilidade.
19
+
20
+ ---
21
+ **Nota para o Agente:** Este arquivo é a sua espinha dorsal neste repositório. Confie nas metodologias do diretório *superpowers/skills* em detrimento de abordagens mais fáceis e desestruturadas.
dashboard/admin.html ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ConfereAI Admin | Fine-Tuning</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@700;900&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="style.css">
9
+ <style>
10
+ .hidden { display: none !important; }
11
+
12
+ /* Admin specific styles */
13
+ .admin-container {
14
+ max-width: 600px;
15
+ margin: 0 auto;
16
+ }
17
+
18
+ .form-group {
19
+ margin-bottom: 1.5rem;
20
+ text-align: left;
21
+ }
22
+
23
+ .form-group label {
24
+ display: block;
25
+ margin-bottom: 0.5rem;
26
+ color: var(--text-secondary);
27
+ }
28
+
29
+ .form-control {
30
+ width: 100%;
31
+ padding: 1rem;
32
+ background: rgba(0,0,0,0.3);
33
+ border: 1px solid var(--glass-border);
34
+ border-radius: 8px;
35
+ color: white;
36
+ font-family: 'Inter', sans-serif;
37
+ transition: border-color 0.3s;
38
+ }
39
+
40
+ .form-control:focus {
41
+ outline: none;
42
+ border-color: var(--accent);
43
+ }
44
+
45
+ .progress-bar-container {
46
+ width: 100%;
47
+ height: 20px;
48
+ background: rgba(0,0,0,0.3);
49
+ border-radius: 10px;
50
+ overflow: hidden;
51
+ margin-top: 1rem;
52
+ border: 1px solid var(--glass-border);
53
+ }
54
+
55
+ .progress-bar {
56
+ height: 100%;
57
+ background: linear-gradient(90deg, var(--primary), var(--cyan));
58
+ width: 0%;
59
+ transition: width 0.5s ease-in-out;
60
+ }
61
+
62
+ .status-text {
63
+ margin-top: 1rem;
64
+ font-size: 0.9rem;
65
+ color: var(--text-secondary);
66
+ }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <div class="aurora-mesh"></div>
71
+
72
+ <nav>
73
+ <div class="logo">
74
+ <a href="index.html" style="text-decoration: none; display: flex; align-items: center; gap: 12px;">
75
+ <span>Confere<span class="vibrance">AI</span> Admin</span>
76
+ </a>
77
+ </div>
78
+ <div class="nav-links">
79
+ <a href="index.html">Voltar ao App</a>
80
+ </div>
81
+ </nav>
82
+
83
+ <main>
84
+ <!-- Login Section -->
85
+ <section id="login-section" class="admin-container">
86
+ <div class="glass-card" style="text-align: center;">
87
+ <h2 style="font-family: 'Outfit'; font-size: 2rem; margin-bottom: 1rem;">Acesso Restrito</h2>
88
+ <p style="color: var(--text-secondary); margin-bottom: 2rem;">Insira a senha de administrador para gerenciar o aprendizado do motor neural.</p>
89
+
90
+ <form id="login-form">
91
+ <div class="form-group">
92
+ <input type="password" id="admin-password" class="form-control" placeholder="Senha do Admin" required>
93
+ </div>
94
+ <button type="submit" class="btn-primary" style="width: 100%;">Entrar</button>
95
+ </form>
96
+ <div id="login-error" class="status-text hidden" style="color: var(--danger);">Senha incorreta.</div>
97
+ </div>
98
+ </section>
99
+
100
+ <!-- Dashboard Section -->
101
+ <section id="dashboard-section" class="admin-container hidden">
102
+ <div class="glass-card" style="text-align: center;">
103
+ <h2 style="font-family: 'Outfit'; font-size: 2rem; margin-bottom: 1rem;">Treinar Modelo</h2>
104
+ <p style="color: var(--text-secondary); margin-bottom: 2rem;">Faça upload de um arquivo .zip ou .rar contendo pastas 'real' e 'fake' com áudios (.mp3, .wav, .flac).</p>
105
+
106
+ <div id="drop-zone" style="padding: 3rem; border-radius: 12px; margin-bottom: 2rem;">
107
+ <div class="upload-icon">
108
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
109
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
110
+ <polyline points="17 8 12 3 7 8"></polyline>
111
+ <line x1="12" y1="3" x2="12" y2="15"></line>
112
+ </svg>
113
+ </div>
114
+ <h3 style="margin-bottom: 10px;">Arraste o arquivo ou clique</h3>
115
+ <p style="color: var(--text-secondary); font-size: 0.9rem;">Limite: 50MB (max 5 arquivos recomendados por lote)</p>
116
+ <input type="file" id="file-input" class="hidden" accept=".zip,.rar">
117
+ </div>
118
+
119
+ <div id="selected-file-info" class="hidden" style="margin-bottom: 1.5rem; color: var(--success); font-weight: 500;">
120
+ Arquivo selecionado: <span id="filename-display"></span>
121
+ </div>
122
+
123
+ <button id="btn-upload-train" class="btn-primary" style="width: 100%;" disabled>Iniciar Upload e Treinamento</button>
124
+
125
+ <!-- Training Progress -->
126
+ <div id="training-progress-container" class="hidden" style="margin-top: 2rem;">
127
+ <h4 style="color: var(--cyan);">Status do Treinamento</h4>
128
+ <div class="progress-bar-container">
129
+ <div id="training-progress-bar" class="progress-bar"></div>
130
+ </div>
131
+ <div id="training-status-text" class="status-text">Preparando ambiente...</div>
132
+ </div>
133
+ </div>
134
+ </section>
135
+ </main>
136
+
137
+ <script src="js/admin.js"></script>
138
+ </body>
139
+ </html>
dashboard/app.js ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const dropZone = document.getElementById('drop-zone');
2
+ const audioInput = document.getElementById('audio-input');
3
+ const selectBtn = document.getElementById('select-file-btn');
4
+ const resultsSection = document.getElementById('results-section');
5
+ const verdictText = document.getElementById('verdict-text');
6
+ const confidenceValue = document.getElementById('confidence-value');
7
+ const confidencePath = document.getElementById('confidence-path');
8
+ const specContainer = document.getElementById('spec-container');
9
+ const verdictExplanation = document.getElementById('verdict-explanation');
10
+
11
+ // Event Listeners
12
+ selectBtn.addEventListener('click', () => audioInput.click());
13
+
14
+ audioInput.addEventListener('change', (e) => {
15
+ if (e.target.files.length) handleUpload(e.target.files[0]);
16
+ });
17
+
18
+ dropZone.addEventListener('dragover', (e) => {
19
+ e.preventDefault();
20
+ dropZone.classList.add('dragover');
21
+ });
22
+
23
+ dropZone.addEventListener('dragleave', () => {
24
+ dropZone.classList.remove('dragover');
25
+ });
26
+
27
+ dropZone.addEventListener('drop', (e) => {
28
+ e.preventDefault();
29
+ dropZone.classList.remove('dragover');
30
+ if (e.dataTransfer.files.length) handleUpload(e.dataTransfer.files[0]);
31
+ });
32
+
33
+ async function handleUpload(file) {
34
+ // Detecta se estamos rodando localmente ou no Hugging Face
35
+ const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
36
+
37
+ // Se você estiver no Vercel, mude '' para a URL do seu Space no Hugging Face
38
+ const API_URL = ''; // Usa o host atual (mesma porta)
39
+ // Reset e mostra seção de resultados
40
+ resultsSection.style.display = 'grid';
41
+ verdictText.textContent = 'PROCESSANDO...';
42
+ if (verdictExplanation) verdictExplanation.textContent = '';
43
+ confidenceValue.textContent = '0%';
44
+ confidencePath.setAttribute('stroke-dasharray', '0, 100');
45
+ specContainer.innerHTML = '<p>Analisando frequências...</p>';
46
+
47
+ const formData = new FormData();
48
+ formData.append('file', file);
49
+
50
+ try {
51
+ const response = await fetch(`${API_URL}/analyze`, {
52
+ method: 'POST',
53
+ body: formData
54
+ });
55
+
56
+ const data = await response.json();
57
+ displayResults(data);
58
+ } catch (error) {
59
+ console.error('Erro na análise:', error);
60
+ verdictText.innerText = 'ERRO NA CONEXÃO';
61
+ }
62
+ }
63
+
64
+ function displayResults(data) {
65
+ console.log('Resultados recebidos:', data);
66
+
67
+ // Atualiza veredito
68
+ const isSpoof = data.verdict === 'SPOOF';
69
+ verdictText.textContent = isSpoof ? ' FRAUDE DETECTADA' : ' ÁUDIO AUTÊNTICO';
70
+ verdictText.style.color = isSpoof ? '#EF4444' : '#10B981';
71
+
72
+ // Atualiza explicação do veredito (Consenso dos Motores)
73
+ /*
74
+ if (verdictExplanation) {
75
+ verdictExplanation.textContent = data.engines_consensus || '';
76
+ verdictExplanation.style.color = isSpoof ? '#FCA5A5' : '#6EE7B7';
77
+ }
78
+ */
79
+ verdictText.style.color = isSpoof ? '#EF4444' : '#10B981';
80
+
81
+ // Atualiza ponto de pulso
82
+ const pulseDot = document.querySelector('.pulse');
83
+ if (pulseDot) {
84
+ pulseDot.style.background = isSpoof ? '#EF4444' : '#10B981';
85
+ pulseDot.style.boxShadow = `0 0 10px ${isSpoof ? '#EF4444' : '#10B981'}`;
86
+ }
87
+
88
+ // Agora mostramos a PROBABILIDADE DE FRAUDE no círculo, pois é o que importa para o usuário
89
+ const fraudProb = Math.round((data.fraud_score || 0) * 100);
90
+ console.log('Calculated Fraud Prob:', fraudProb);
91
+
92
+ if (confidenceValue) {
93
+ confidenceValue.textContent = `${fraudProb}%`;
94
+ }
95
+
96
+ if (confidencePath) {
97
+ // Cor do círculo baseada no risco
98
+ if (fraudProb > 80) {
99
+ confidencePath.style.stroke = '#EF4444'; // Vermelho (Perigo)
100
+ if (pulseDot) pulseDot.style.background = '#EF4444';
101
+ } else if (fraudProb > 40) {
102
+ confidencePath.style.stroke = '#F59E0B'; // Amarelo (Atenção)
103
+ if (pulseDot) pulseDot.style.background = '#F59E0B';
104
+ } else {
105
+ confidencePath.style.stroke = '#10B981'; // Verde (Seguro)
106
+ if (pulseDot) pulseDot.style.background = '#10B981';
107
+ }
108
+
109
+ // Animação do círculo
110
+ confidencePath.setAttribute('stroke-dasharray', `${fraudProb}, 100`);
111
+ }
112
+ // Atualiza Espectrograma
113
+ // Atualiza Espectrograma e Heatmap (XAI)
114
+ if (data.spectrogram_url) {
115
+ const specName = data.spectrogram_url.split(/[\\/]/).pop();
116
+ const timestamp = new Date().getTime();
117
+
118
+ let heatmapHtml = '<div class="heatmap-overlay">';
119
+ if (data.temporal_scores && data.temporal_scores.length > 0) {
120
+ data.temporal_scores.forEach(score => {
121
+ // Interpola cor entre verde (seguro) e vermelho (fraude)
122
+ // Usando HSL: 120 (verde) a 0 (vermelho)
123
+ const hue = 120 - (score * 120);
124
+ const opacity = score > 0.4 ? (score * 0.7) : (score * 0.2);
125
+ heatmapHtml += `<div class="heatmap-segment" style="background: hsla(${hue}, 100%, 50%, ${opacity})"></div>`;
126
+ });
127
+ }
128
+ heatmapHtml += '</div>';
129
+
130
+ specContainer.innerHTML = `
131
+ <div class="spec-wrapper">
132
+ <img src="/tmp/${specName}?t=${timestamp}" alt="Espectrograma de Mel">
133
+ ${heatmapHtml}
134
+ </div>
135
+ `;
136
+ }
137
+
138
+ // Scroll automático suave para os resultados
139
+ resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
140
+
141
+ // Atualiza Diagnóstico
142
+ updateDiagnostics(data);
143
+ }
144
+
145
+ function updateDiagnostics(data) {
146
+ const diagSection = document.getElementById('diagnostic-section');
147
+ const toggleBtn = document.getElementById('toggle-diagnostic');
148
+ const details = document.getElementById('diagnostic-details');
149
+
150
+ if (!diagSection) return;
151
+
152
+ diagSection.style.display = 'block';
153
+
154
+ const w2vScore = Math.round((data.wav2vec_score || 0) * 100);
155
+ const astScore = Math.round((data.ast_score || 0) * 100);
156
+
157
+ // Atualiza valores e barras com delay para animação
158
+ setTimeout(() => {
159
+ document.getElementById('w2v-val').textContent = `${w2vScore}%`;
160
+ document.getElementById('ast-val').textContent = `${astScore}%`;
161
+ document.getElementById('w2v-bar').style.width = `${w2vScore}%`;
162
+ document.getElementById('ast-bar').style.width = `${astScore}%`;
163
+ document.getElementById('rigor-logic').textContent = data.engines_consensus || 'Padrão';
164
+ }, 100);
165
+
166
+ // Toggle behavior
167
+ if (toggleBtn && !toggleBtn.dataset.hasListener) {
168
+ toggleBtn.addEventListener('click', () => {
169
+ const isHidden = details.style.display === 'none';
170
+ details.style.display = isHidden ? 'block' : 'none';
171
+ toggleBtn.textContent = isHidden ? 'Esconder' : 'Ver Detalhes';
172
+
173
+ if (isHidden) {
174
+ details.style.animation = 'fadeInUp 0.5s forwards';
175
+ }
176
+ });
177
+ toggleBtn.dataset.hasListener = "true";
178
+ }
179
+ }
180
+
181
+ // Lógica do Modal "Como Funciona" (Overlay)
182
+ const modal = document.getElementById('how-it-works-modal');
183
+ const openBtn = document.getElementById('open-how-it-works');
184
+ const closeBtn = document.getElementById('close-modal');
185
+
186
+ if (openBtn && modal) {
187
+ openBtn.addEventListener('click', (e) => {
188
+ e.preventDefault();
189
+ modal.classList.add('active');
190
+ document.body.style.overflow = 'hidden'; // Trava o scroll
191
+ });
192
+ }
193
+
194
+ if (closeBtn && modal) {
195
+ closeBtn.addEventListener('click', () => {
196
+ modal.classList.remove('active');
197
+ document.body.style.overflow = 'auto'; // Destrava o scroll
198
+ });
199
+
200
+ // Fechar ao clicar fora do conteúdo
201
+ modal.addEventListener('click', (e) => {
202
+ if (e.target === modal) {
203
+ closeBtn.click();
204
+ }
205
+ });
206
+ }
207
+
208
+ // Fechar com a tecla ESC
209
+ document.addEventListener('keydown', (e) => {
210
+ if (e.key === 'Escape' && modal && modal.classList.contains('active')) {
211
+ closeBtn.click();
212
+ }
213
+ });
dashboard/assets/logo_base64.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c7b6ac9f86b805ee053582e8514ce0161e688ff38bde35e3e0f996dd9e012766
3
+ size 996806
dashboard/how-it-works.css ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Modal Overlay */
2
+ .modal-overlay {
3
+ position: fixed;
4
+ top: 0;
5
+ left: 0;
6
+ width: 100%;
7
+ height: 100%;
8
+ background: rgba(0, 0, 0, 0.85);
9
+ backdrop-filter: blur(20px);
10
+ z-index: 1000;
11
+ display: none; /* Escondido por padrão */
12
+ align-items: center;
13
+ justify-content: center;
14
+ padding: 20px;
15
+ opacity: 0;
16
+ transition: opacity 0.4s ease;
17
+ }
18
+
19
+ .modal-overlay.active {
20
+ display: flex;
21
+ opacity: 1;
22
+ }
23
+
24
+ .modal-content {
25
+ max-width: 1000px;
26
+ width: 95%;
27
+ max-height: 90vh;
28
+ overflow-y: auto;
29
+ position: relative;
30
+ padding: 60px 40px;
31
+ border: 1px solid rgba(157, 80, 187, 0.3);
32
+ animation: modalSlide 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
33
+ }
34
+
35
+ @keyframes modalSlide {
36
+ from { transform: scale(0.8) translateY(50px); opacity: 0; }
37
+ to { transform: scale(1) translateY(0); opacity: 1; }
38
+ }
39
+
40
+ .close-modal {
41
+ position: absolute;
42
+ top: 20px;
43
+ right: 25px;
44
+ background: none;
45
+ border: none;
46
+ color: #fff;
47
+ font-size: 2.5rem;
48
+ cursor: pointer;
49
+ line-height: 1;
50
+ transition: transform 0.3s, color 0.3s;
51
+ }
52
+
53
+ .close-modal:hover {
54
+ color: var(--accent);
55
+ transform: rotate(90deg);
56
+ }
57
+
58
+ .modal-footer {
59
+ margin-top: 50px;
60
+ text-align: center;
61
+ border-top: 1px solid rgba(255,255,255,0.05);
62
+ padding-top: 20px;
63
+ }
64
+
65
+ .modal-footer p {
66
+ color: var(--text-secondary);
67
+ font-size: 0.8rem;
68
+ font-style: italic;
69
+ }
70
+
71
+ /* Re-aproveitando os cartões dentro do modal */
72
+ .section-title {
73
+ text-align: center;
74
+ font-family: 'Outfit', sans-serif;
75
+ font-size: 2.5rem;
76
+ margin-bottom: 50px;
77
+ background: linear-gradient(135deg, #fff 0%, #9d50bb 100%);
78
+ -webkit-background-clip: text;
79
+ -webkit-text-fill-color: transparent;
80
+ }
81
+
82
+ .steps-grid {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
85
+ gap: 25px;
86
+ }
87
+
88
+ .step-card {
89
+ padding: 30px;
90
+ background: rgba(255, 255, 255, 0.03);
91
+ border: 1px solid rgba(255, 255, 255, 0.05);
92
+ border-radius: 16px;
93
+ position: relative;
94
+ transition: transform 0.3s, border-color 0.3s;
95
+ }
96
+
97
+ .step-card:hover {
98
+ transform: translateY(-5px);
99
+ border-color: rgba(157, 80, 187, 0.3);
100
+ }
101
+
102
+ .step-number {
103
+ position: absolute;
104
+ top: 15px;
105
+ right: 20px;
106
+ font-size: 2.5rem;
107
+ font-weight: 900;
108
+ opacity: 0.1;
109
+ color: var(--accent);
110
+ }
111
+
112
+ .step-icon {
113
+ width: 50px;
114
+ height: 50px;
115
+ background: rgba(157, 80, 187, 0.1);
116
+ border-radius: 10px;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ margin-bottom: 20px;
121
+ color: var(--accent);
122
+ }
123
+
124
+ .step-card h3 {
125
+ font-size: 1.3rem;
126
+ margin-bottom: 10px;
127
+ }
128
+
129
+ .step-card p {
130
+ color: var(--text-secondary);
131
+ line-height: 1.5;
132
+ font-size: 0.9rem;
133
+ }
134
+
135
+ /* Footer Original */
136
+ .glass-footer {
137
+ padding: 40px 5%;
138
+ border-top: 1px solid var(--glass-border);
139
+ }
140
+
141
+ .footer-content {
142
+ max-width: 1200px;
143
+ margin: 0 auto;
144
+ display: flex;
145
+ justify-content: space-between;
146
+ align-items: center;
147
+ }
148
+
149
+ .footer-links a {
150
+ color: var(--text-secondary);
151
+ text-decoration: none;
152
+ margin-left: 20px;
153
+ font-size: 0.85rem;
154
+ }
dashboard/index.html ADDED
The diff for this file is too large to render. See raw diff
 
dashboard/js/admin.js ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ // Elements
3
+ const loginSection = document.getElementById('login-section');
4
+ const dashboardSection = document.getElementById('dashboard-section');
5
+ const loginForm = document.getElementById('login-form');
6
+ const passwordInput = document.getElementById('admin-password');
7
+ const loginError = document.getElementById('login-error');
8
+
9
+ const dropZone = document.getElementById('drop-zone');
10
+ const fileInput = document.getElementById('file-input');
11
+ const selectedFileInfo = document.getElementById('selected-file-info');
12
+ const filenameDisplay = document.getElementById('filename-display');
13
+ const btnUploadTrain = document.getElementById('btn-upload-train');
14
+
15
+ const progressContainer = document.getElementById('training-progress-container');
16
+ const progressBar = document.getElementById('training-progress-bar');
17
+ const statusText = document.getElementById('training-status-text');
18
+
19
+ let currentFile = null;
20
+ let token = null; // JWT ou Token simples para as rotas autenticadas
21
+ let statusInterval = null;
22
+
23
+ // Login Handling
24
+ loginForm.addEventListener('submit', async (e) => {
25
+ e.preventDefault();
26
+ loginError.classList.add('hidden');
27
+ const password = passwordInput.value;
28
+
29
+ try {
30
+ // Simulando chamada de login para a API
31
+ const response = await fetch('/admin/login', {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ password: password })
35
+ });
36
+
37
+ if (response.ok) {
38
+ const data = await response.json();
39
+ token = data.token; // Armazena token temporário
40
+ loginSection.classList.add('hidden');
41
+ dashboardSection.classList.remove('hidden');
42
+ } else {
43
+ loginError.classList.remove('hidden');
44
+ }
45
+ } catch (error) {
46
+ console.error('Login error:', error);
47
+ loginError.textContent = 'Erro ao conectar no servidor.';
48
+ loginError.classList.remove('hidden');
49
+ }
50
+ });
51
+
52
+ // Drag and Drop Handling
53
+ dropZone.addEventListener('click', () => fileInput.click());
54
+
55
+ dropZone.addEventListener('dragover', (e) => {
56
+ e.preventDefault();
57
+ dropZone.classList.add('dragover');
58
+ });
59
+
60
+ dropZone.addEventListener('dragleave', () => {
61
+ dropZone.classList.remove('dragover');
62
+ });
63
+
64
+ dropZone.addEventListener('drop', (e) => {
65
+ e.preventDefault();
66
+ dropZone.classList.remove('dragover');
67
+
68
+ if (e.dataTransfer.files.length) {
69
+ handleFileSelect(e.dataTransfer.files[0]);
70
+ }
71
+ });
72
+
73
+ fileInput.addEventListener('change', (e) => {
74
+ if (e.target.files.length) {
75
+ handleFileSelect(e.target.files[0]);
76
+ }
77
+ });
78
+
79
+ function handleFileSelect(file) {
80
+ // Validações básicas (zip/rar)
81
+ if (!file.name.endsWith('.zip') && !file.name.endsWith('.rar')) {
82
+ alert('Apenas arquivos .zip ou .rar são permitidos.');
83
+ return;
84
+ }
85
+
86
+ currentFile = file;
87
+ filenameDisplay.textContent = file.name;
88
+ selectedFileInfo.classList.remove('hidden');
89
+ btnUploadTrain.removeAttribute('disabled');
90
+ }
91
+
92
+ // Upload & Train Handling
93
+ btnUploadTrain.addEventListener('click', async () => {
94
+ if (!currentFile || !token) return;
95
+
96
+ btnUploadTrain.setAttribute('disabled', 'true');
97
+ progressContainer.classList.remove('hidden');
98
+ statusText.textContent = 'Fazendo upload e extraindo dataset...';
99
+ progressBar.style.width = '10%';
100
+
101
+ const formData = new FormData();
102
+ formData.append('file', currentFile);
103
+
104
+ try {
105
+ // 1. Upload
106
+ const uploadResponse = await fetch('/admin/upload_dataset', {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Authorization': `Bearer ${token}`
110
+ },
111
+ body: formData
112
+ });
113
+
114
+ if (!uploadResponse.ok) {
115
+ const errData = await uploadResponse.json();
116
+ throw new Error(errData.detail || 'Erro no upload');
117
+ }
118
+
119
+ statusText.textContent = 'Upload concluído. Iniciando fine-tuning...';
120
+ progressBar.style.width = '30%';
121
+
122
+ // 2. Start Training
123
+ const trainResponse = await fetch('/admin/train', {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Authorization': `Bearer ${token}`
127
+ }
128
+ });
129
+
130
+ if (!trainResponse.ok) {
131
+ const errData = await trainResponse.json();
132
+ throw new Error(errData.detail || 'Erro ao iniciar treino');
133
+ }
134
+
135
+ // 3. Start Polling
136
+ startStatusPolling();
137
+
138
+ } catch (error) {
139
+ statusText.textContent = `Erro: ${error.message}`;
140
+ statusText.style.color = 'var(--danger)';
141
+ btnUploadTrain.removeAttribute('disabled');
142
+ }
143
+ });
144
+
145
+ function startStatusPolling() {
146
+ if (statusInterval) clearInterval(statusInterval);
147
+
148
+ statusInterval = setInterval(async () => {
149
+ try {
150
+ const response = await fetch('/admin/status', {
151
+ headers: { 'Authorization': `Bearer ${token}` }
152
+ });
153
+
154
+ if (response.ok) {
155
+ const data = await response.json();
156
+
157
+ progressBar.style.width = `${data.progress}%`;
158
+ statusText.textContent = data.message || `Treinamento: ${data.progress}%`;
159
+
160
+ if (data.status === 'completed') {
161
+ clearInterval(statusInterval);
162
+ statusText.textContent = 'Treinamento concluído com sucesso! Modelo atualizado.';
163
+ statusText.style.color = 'var(--success)';
164
+ btnUploadTrain.removeAttribute('disabled');
165
+ progressBar.style.width = '100%';
166
+ } else if (data.status === 'failed') {
167
+ clearInterval(statusInterval);
168
+ statusText.textContent = `Falha no treinamento: ${data.error}`;
169
+ statusText.style.color = 'var(--danger)';
170
+ btnUploadTrain.removeAttribute('disabled');
171
+ }
172
+ }
173
+ } catch (err) {
174
+ console.error("Erro ao verificar status:", err);
175
+ }
176
+ }, 2000); // Polling a cada 2 segundos
177
+ }
178
+ });
dashboard/style.css ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary: #9d50bb;
3
+ --accent: #6e48aa;
4
+ --accent-glow: rgba(139, 92, 246, 0.4);
5
+ --bg-dark: #0a0a0c;
6
+ --cyan: #06B6D4;
7
+ --text-primary: #F8F9FA;
8
+ --text-secondary: #94A3B8;
9
+ --glass: rgba(255, 255, 255, 0.03);
10
+ --glass-border: rgba(255, 255, 255, 0.1);
11
+ --success: #10B981;
12
+ --danger: #EF4444;
13
+ }
14
+
15
+ * {
16
+ margin: 0;
17
+ padding: 0;
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ body {
22
+ background-color: var(--bg-dark);
23
+ color: var(--text-primary);
24
+ font-family: 'Inter', sans-serif;
25
+ line-height: 1.6;
26
+ overflow-x: hidden;
27
+ min-height: 100vh;
28
+ }
29
+
30
+ .aurora-mesh {
31
+ position: fixed;
32
+ top: 0;
33
+ left: 0;
34
+ width: 100%;
35
+ height: 100%;
36
+ background: radial-gradient(circle at 20% 30%, rgba(139, 92, 246, 0.15) 0%, transparent 40%),
37
+ radial-gradient(circle at 80% 70%, rgba(6, 182, 212, 0.1) 0%, transparent 40%);
38
+ z-index: -1;
39
+ filter: blur(80px);
40
+ }
41
+
42
+ nav {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ padding: 2rem 5%;
47
+ background: rgba(10, 10, 11, 0.8);
48
+ backdrop-filter: blur(10px);
49
+ position: sticky;
50
+ top: 0;
51
+ z-index: 100;
52
+ border-bottom: 1px solid var(--glass-border);
53
+ }
54
+
55
+ .logo {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 12px;
59
+ }
60
+
61
+ .logo img {
62
+ height: 42px; /* Aumentado para melhor visibilidade com a nova logo */
63
+ width: auto;
64
+ image-rendering: -webkit-optimize-contrast;
65
+ object-fit: contain;
66
+ filter: drop-shadow(0 0 12px rgba(157, 80, 187, 0.4));
67
+ }
68
+
69
+ /* XAI Heatmap Styles */
70
+ .spec-wrapper {
71
+ position: relative;
72
+ width: 100%;
73
+ border-radius: 12px;
74
+ overflow: hidden;
75
+ border: 1px solid var(--glass-border);
76
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
77
+ }
78
+
79
+ .spec-wrapper img {
80
+ display: block;
81
+ width: 100%;
82
+ height: auto;
83
+ }
84
+
85
+ .heatmap-overlay {
86
+ position: absolute;
87
+ top: 0;
88
+ left: 0;
89
+ width: 100%;
90
+ height: 100%;
91
+ display: flex;
92
+ pointer-events: none;
93
+ }
94
+
95
+ .heatmap-segment {
96
+ flex: 1;
97
+ height: 100%;
98
+ transition: background 0.5s ease;
99
+ mix-blend-mode: color-burn; /* Mistura melhor com o espectrograma */
100
+ }
101
+
102
+ /* Diagnostic Section Refinement */
103
+ .diagnostic-container {
104
+ margin-top: 2rem;
105
+ padding: 1.5rem;
106
+ background: rgba(255, 255, 255, 0.03);
107
+ border: 1px solid var(--glass-border);
108
+ border-radius: 12px;
109
+ backdrop-filter: blur(5px);
110
+ transition: all 0.4s ease;
111
+ }
112
+
113
+ .diagnostic-header {
114
+ display: flex;
115
+ justify-content: space-between;
116
+ align-items: center;
117
+ margin-bottom: 1.2rem;
118
+ }
119
+
120
+ .diagnostic-header span {
121
+ font-family: 'Outfit', sans-serif;
122
+ font-weight: 700;
123
+ font-size: 0.85rem;
124
+ letter-spacing: 1.5px;
125
+ color: var(--cyan);
126
+ text-transform: uppercase;
127
+ }
128
+
129
+ .btn-mini {
130
+ background: rgba(6, 182, 212, 0.1);
131
+ border: 1px solid rgba(6, 182, 212, 0.3);
132
+ color: var(--cyan);
133
+ padding: 6px 14px;
134
+ border-radius: 20px;
135
+ font-size: 0.75rem;
136
+ font-weight: 600;
137
+ cursor: pointer;
138
+ transition: all 0.3s ease;
139
+ }
140
+
141
+ .btn-mini:hover {
142
+ background: var(--cyan);
143
+ color: var(--bg-dark);
144
+ box-shadow: 0 0 10px var(--cyan);
145
+ }
146
+
147
+ .diagnostic-content {
148
+ margin-top: 1rem;
149
+ padding-top: 1rem;
150
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
151
+ }
152
+
153
+ .engine-stat {
154
+ margin-bottom: 1.2rem;
155
+ }
156
+
157
+ .engine-stat label {
158
+ font-size: 0.8rem;
159
+ color: var(--text-secondary);
160
+ display: flex;
161
+ justify-content: space-between;
162
+ margin-bottom: 0.5rem;
163
+ }
164
+
165
+ .progress-mini {
166
+ height: 8px;
167
+ background: rgba(0,0,0,0.4);
168
+ border-radius: 4px;
169
+ overflow: hidden;
170
+ border: 1px solid rgba(255, 255, 255, 0.05);
171
+ }
172
+
173
+ .progress-mini .bar {
174
+ height: 100%;
175
+ background: linear-gradient(90deg, var(--primary), var(--cyan));
176
+ width: 0%;
177
+ transition: width 1.5s cubic-bezier(0.34, 1.56, 0.64, 1);
178
+ }
179
+
180
+ .rigor-status {
181
+ margin-top: 1rem;
182
+ padding: 1rem;
183
+ background: rgba(0, 0, 0, 0.3);
184
+ border-radius: 8px;
185
+ border: 1px solid rgba(255, 255, 255, 0.05);
186
+ text-align: center;
187
+ }
188
+
189
+ .rigor-status small {
190
+ display: block;
191
+ font-size: 0.75rem;
192
+ color: var(--text-secondary);
193
+ margin-bottom: 4px;
194
+ }
195
+
196
+ #rigor-logic {
197
+ color: var(--cyan);
198
+ font-weight: 600;
199
+ font-family: 'Outfit', sans-serif;
200
+ letter-spacing: 0.5px;
201
+ }
202
+
203
+ /* XAI Heatmap Overlay */
204
+ .spec-wrapper {
205
+ position: relative;
206
+ width: 100%;
207
+ border-radius: 16px;
208
+ overflow: hidden;
209
+ border: 1px solid var(--glass-border);
210
+ box-shadow: 0 20px 40px rgba(0,0,0,0.6);
211
+ }
212
+
213
+ .spec-wrapper img {
214
+ display: block;
215
+ width: 100%;
216
+ height: auto;
217
+ filter: contrast(1.1) brightness(0.9);
218
+ }
219
+
220
+ .heatmap-overlay {
221
+ position: absolute;
222
+ top: 0;
223
+ left: 0;
224
+ width: 100%;
225
+ height: 100%;
226
+ display: flex;
227
+ pointer-events: none;
228
+ opacity: 0.85;
229
+ }
230
+
231
+ .heatmap-segment {
232
+ flex: 1;
233
+ height: 100%;
234
+ transition: all 0.6s ease;
235
+ mix-blend-mode: screen;
236
+ border-right: 1px solid rgba(255,255,255,0.02);
237
+ }
238
+
239
+ @keyframes glowPulse {
240
+ 0% { box-shadow: 0 0 5px var(--accent); }
241
+ 50% { box-shadow: 0 0 20px var(--accent); }
242
+ 100% { box-shadow: 0 0 5px var(--accent); }
243
+ }
244
+
245
+ .pulse {
246
+ animation: glowPulse 2s infinite ease-in-out;
247
+ }
248
+
embed_logo.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+
4
+ def embed_logo():
5
+ logo_path = 'assets/logo.png'
6
+ html_path = 'dashboard/index.html'
7
+
8
+ if not os.path.exists(logo_path):
9
+ print("Logo not found")
10
+ return
11
+
12
+ with open(logo_path, 'rb') as f:
13
+ b64_string = base64.b64encode(f.read()).decode()
14
+
15
+ with open(html_path, 'r', encoding='utf-8') as f:
16
+ html_content = f.read()
17
+
18
+ # Substituir no favicon e na logo do nav
19
+ new_html = html_content.replace('href="assets/logo.png"', f'href="data:image/png;base64,{b64_string}"')
20
+ new_html = new_html.replace('src="assets/logo.png"', f'src="data:image/png;base64,{b64_string}"')
21
+
22
+ with open(html_path, 'w', encoding='utf-8') as f:
23
+ f.write(new_html)
24
+ print("Logo embedded successfully in HTML")
25
+
26
+ if __name__ == "__main__":
27
+ embed_logo()
execution/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Init file to make execution a package
execution/colab_training_script.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @title 4. Executar Treinamento (Fine-Tuning)
2
+ import os
3
+ import torch
4
+ import librosa
5
+ from torch.utils.data import Dataset
6
+ from transformers import Wav2Vec2FeatureExtractor, Wav2Vec2ForSequenceClassification, Trainer, TrainingArguments
7
+
8
+ # Configurações do Modelo
9
+ BASE_MODEL = "HyperMoon/wav2vec2-base-960h-finetuned-deepfake"
10
+ OUTPUT_DIR = "local_finetuned_model"
11
+
12
+ # Mapeamento Rígido de Labels para evitar conflitos (0=Real, 1=Fraude)
13
+ id2label = {0: "AUTHENTIC", 1: "FAKE"}
14
+ label2id = {"AUTHENTIC": 0, "FAKE": 1}
15
+
16
+ class DeepfakeDataset(Dataset):
17
+ def __init__(self, root_dir, processor):
18
+ self.files = []
19
+ self.processor = processor
20
+
21
+ # Carregamento explícito baseado em pastas
22
+ for label_name, label_id in label2id.items():
23
+ folder = "real" if label_name == "AUTHENTIC" else "fake"
24
+ path = os.path.join(root_dir, folder)
25
+ if os.path.exists(path):
26
+ print(f"Carregando audios de: {folder}...")
27
+ for f in os.listdir(path):
28
+ if f.lower().endswith(('.wav', '.mp3', '.flac')):
29
+ self.files.append({"path": os.path.join(path, f), "label": label_id})
30
+ else:
31
+ print(f"AVISO: Pasta {folder} não encontrada em {root_dir}")
32
+
33
+ def __len__(self): return len(self.files)
34
+
35
+ def __getitem__(self, idx):
36
+ item = self.files[idx]
37
+ try:
38
+ speech, _ = librosa.load(item["path"], sr=16000)
39
+ inputs = self.processor(speech, sampling_rate=16000, return_tensors="pt", padding="max_length", max_length=160000, truncation=True)
40
+ return {"input_values": inputs.input_values[0], "labels": torch.tensor(item["label"])}
41
+ except Exception as e:
42
+ print(f"Erro ao processar {item['path']}: {e}")
43
+ # Retorna o primeiro item como fallback para não quebrar o loop do Trainer
44
+ return self.__getitem__(0)
45
+
46
+ print("Inicializando Processador e Modelo...")
47
+ try:
48
+ processor = Wav2Vec2FeatureExtractor.from_pretrained(BASE_MODEL)
49
+ # Adicionado id2label e label2id aqui para garantir consistência
50
+ model = Wav2Vec2ForSequenceClassification.from_pretrained(
51
+ BASE_MODEL,
52
+ num_labels=2,
53
+ id2label=id2label,
54
+ label2id=label2id,
55
+ ignore_mismatched_sizes=True
56
+ )
57
+
58
+ # Congelar base para focar no aprendizado das novas fraudes (Lógica Robusta)
59
+ if hasattr(model, 'wav2vec2'):
60
+ for param in model.wav2vec2.parameters():
61
+ param.requires_grad = False
62
+ print("Modelo carregado e camadas base congeladas com sucesso!")
63
+
64
+ # Dataset (Aponte para a pasta onde você subiu os áudios no Colab)
65
+ # Ex: /content/dataset_treino
66
+ dataset_path = "/content/dataset"
67
+ train_data = DeepfakeDataset(dataset_path, processor)
68
+
69
+ if len(train_data) == 0:
70
+ print("ERRO: Nenhum dado encontrado. Verifique se as pastas 'real' e 'fake' existem dentro do caminho especificado.")
71
+ else:
72
+ training_args = TrainingArguments(
73
+ output_dir=OUTPUT_DIR,
74
+ num_train_epochs=3,
75
+ per_device_train_batch_size=2,
76
+ gradient_accumulation_steps=4,
77
+ save_steps=50,
78
+ logging_steps=10,
79
+ learning_rate=2e-5,
80
+ remove_unused_columns=False
81
+ )
82
+
83
+ trainer = Trainer(model=model, args=training_args, train_dataset=train_data)
84
+ print("Iniciando Treinamento...")
85
+ trainer.train()
86
+
87
+ # Salva o resultado final
88
+ model.save_pretrained(OUTPUT_DIR)
89
+ processor.save_pretrained(OUTPUT_DIR)
90
+ print(f"Sucesso! Modelo salvo em: {OUTPUT_DIR}")
91
+
92
+ except Exception as e:
93
+ print(f"ERRO CRÍTICO: {e}")
94
+ print("DICA: Se o erro for de conexão, tente rodar a célula novamente. O Hugging Face pode falhar ocasionalmente no download.")
execution/ensemble_manager.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from execution.inference_wav2vec import run_inference as run_wav2vec
2
+ from execution.inference_ast import run_ast_inference as run_ast
3
+
4
+ def get_combined_verdict(file_path):
5
+ """
6
+ Orquestra a execução dos dois motores e aplica o Protocolo de Rigor (Abordagem Conservadora).
7
+ """
8
+ # 1. Executa Motor 1 (Wav2Vec2 - Ritmo e Nuance)
9
+ res_w2v = run_wav2vec(file_path)
10
+ score_w2v = res_w2v.get("deepfake_probability", 0.0)
11
+
12
+ # 2. Executa Motor 2 (AST - Espectrograma e Frequência)
13
+ res_ast = run_ast(file_path)
14
+ score_ast = res_ast.get("risk_score", 0.0)
15
+
16
+ # 3. Lógica do Protocolo de Rigor (Abordagem Conservadora)
17
+ # Se qualquer motor detectar fraude com convicção alta, o veredito é FRAUDE.
18
+
19
+ HIGH_CONFIDENCE_THRESHOLD = 0.80
20
+
21
+ is_fraud = False
22
+ verdict = "AUTHENTIC"
23
+ final_score = max(score_w2v, score_ast) # Pega o maior risco detectado
24
+
25
+ if score_w2v >= HIGH_CONFIDENCE_THRESHOLD and score_ast >= HIGH_CONFIDENCE_THRESHOLD:
26
+ is_fraud = True
27
+ verdict = "SPOOF"
28
+ message = "CONSENSO CRÍTICO: Ambos os motores detectaram padrões de clonagem com alta convicção."
29
+ elif score_w2v >= HIGH_CONFIDENCE_THRESHOLD:
30
+ is_fraud = True
31
+ verdict = "SPOOF"
32
+ message = "ALERTA DE VOZ: O motor Wav2Vec2 detectou irregularidades na textura fonética humana."
33
+ elif score_ast >= HIGH_CONFIDENCE_THRESHOLD:
34
+ is_fraud = True
35
+ verdict = "SPOOF"
36
+ message = "ANOMALIA ESPECTRAL: O motor AST identificou assinaturas de frequências artificiais."
37
+ elif final_score > 0.5:
38
+ is_fraud = True
39
+ verdict = "SPOOF"
40
+ message = "RISCO DETECTADO: Evidências moderadas de manipulação neural identificadas."
41
+ else:
42
+ message = "INTEGRIDADE CONFIRMADA: Nenhuma evidência significativa de manipulação detectada."
43
+
44
+ return {
45
+ "verdict": verdict,
46
+ "fraud_probability": final_score,
47
+ "wav2vec_score": score_w2v,
48
+ "ast_score": score_ast,
49
+ "temporal_scores": res_w2v.get("temporal_scores", []), # Adicionado para XAI
50
+ "engines_consensus": message,
51
+ "details": {
52
+ "protocol": "Protocolo de Rigor (Conservador)"
53
+ },
54
+ "engines": ["Wav2Vec2-Deepfake", "AST-Spectrogram"]
55
+ }
56
+
57
+
58
+ if __name__ == "__main__":
59
+ import sys
60
+ if len(sys.argv) > 1:
61
+ import json
62
+ print(json.dumps(get_combined_verdict(sys.argv[1]), indent=2))
execution/fastapi_server.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from dotenv import load_dotenv
4
+ from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Depends, Header, status
5
+ from fastapi.staticfiles import StaticFiles
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+ import zipfile
9
+ import uuid
10
+ import uvicorn
11
+
12
+ # Carrega variáveis do arquivo .env
13
+ load_dotenv()
14
+
15
+ # Importamos nossos módulos de execução
16
+ from execution.feature_extractor import extract_features
17
+ from execution.ensemble_manager import get_combined_verdict
18
+
19
+ app = FastAPI(title="ConfereAI Audio Fraud Detection API")
20
+
21
+ # Configuração de CORS
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"],
25
+ allow_methods=["*"],
26
+ allow_headers=["*"],
27
+ )
28
+
29
+ # Estado global do treinamento (simplificado para MVP)
30
+ training_status = {
31
+ "status": "idle", # idle, processing, training, completed, failed
32
+ "progress": 0,
33
+ "message": "Aguardando",
34
+ "error": None
35
+ }
36
+
37
+ # Verificador de token super simples
38
+ def verify_admin_token(authorization: str = Header(None)):
39
+ if not authorization or not authorization.startswith("Bearer "):
40
+ raise HTTPException(status_code=401, detail="Token ausente ou inválido")
41
+
42
+ token = authorization.split(" ")[1]
43
+ # No mundo real, usaríamos JWT decodificado
44
+ if token != "confereai_admin_token_2026":
45
+ raise HTTPException(status_code=401, detail="Token inválido")
46
+ return token
47
+
48
+ class AnalysisResult(BaseModel):
49
+ filename: str
50
+ fraud_score: float
51
+ verdict: str
52
+ spectrogram_url: str
53
+ engine: str
54
+ wav2vec_score: float = 0.0
55
+ ast_score: float = 0.0
56
+ engines_consensus: str = ""
57
+ temporal_scores: list = []
58
+
59
+ @app.post("/analyze", response_model=AnalysisResult)
60
+ async def analyze_audio_endpoint(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
61
+ # Garante diretório temporário
62
+ temp_dir = ".tmp"
63
+ if not os.path.exists(temp_dir):
64
+ os.makedirs(temp_dir)
65
+
66
+ # Salva arquivo temporariamente com ID único para evitar colisões
67
+ unique_id = str(uuid.uuid4())[:8]
68
+ filename = f"{unique_id}_{file.filename}"
69
+ file_path = os.path.join(temp_dir, filename)
70
+ with open(file_path, "wb") as buffer:
71
+ shutil.copyfileobj(file.file, buffer)
72
+
73
+ try:
74
+ # 1. Extração de Imagens (Local)
75
+ features = extract_features(file_path, output_dir=temp_dir)
76
+
77
+ # 2. Inferência via Ensemble (Wav2Vec2 + AST)
78
+ analysis = get_combined_verdict(file_path)
79
+
80
+ # 3. Agenda limpeza em background (após 5 minutos para dar tempo do front ler a imagem)
81
+ def cleanup_temp_files(paths):
82
+ import time
83
+ time.sleep(300) # 5 minutos
84
+ for p in paths:
85
+ if os.path.exists(p):
86
+ try:
87
+ os.remove(p)
88
+ print(f"Cleanup: {p} removido.")
89
+ except Exception as e:
90
+ print(f"Cleanup error: {e}")
91
+
92
+ background_tasks.add_task(cleanup_temp_files, [file_path, features.get("spectrogram_path")])
93
+
94
+ # 4. Resposta Consolidada
95
+ return AnalysisResult(
96
+ filename=file.filename,
97
+ fraud_score=analysis.get("fraud_probability", 0.0),
98
+ verdict=analysis.get("verdict", "UNKNOWN"),
99
+ spectrogram_url=features.get("spectrogram_path", "").replace(".tmp/", "/tmp/"),
100
+ engine="Dual Engine (Wav2Vec2 + AST) - Protocolo de Rigor",
101
+ wav2vec_score=analysis.get("wav2vec_score", 0.0),
102
+ ast_score=analysis.get("ast_score", 0.0),
103
+ engines_consensus=analysis.get("engines_consensus", ""),
104
+ temporal_scores=analysis.get("temporal_scores", [])
105
+ )
106
+
107
+ except Exception as e:
108
+ print(f"Erro na análise: {e}")
109
+ raise e
110
+
111
+ # --- ADMIN ENDPOINTS ---
112
+
113
+ class LoginRequest(BaseModel):
114
+ password: str
115
+
116
+ @app.post("/admin/login")
117
+ async def admin_login(req: LoginRequest):
118
+ admin_pw = os.environ.get("ADMIN_PASSWORD", "Casa102030@")
119
+ if req.password == admin_pw:
120
+ return {"token": "confereai_admin_token_2026"}
121
+ raise HTTPException(status_code=401, detail="Senha incorreta")
122
+
123
+ @app.post("/admin/upload_dataset")
124
+ async def admin_upload(file: UploadFile = File(...), token: str = Depends(verify_admin_token)):
125
+ global training_status
126
+ if not file.filename.endswith(('.zip', '.rar')):
127
+ raise HTTPException(status_code=400, detail="Apenas .zip ou .rar")
128
+
129
+ dataset_dir = ".tmp/dataset"
130
+ if os.path.exists(dataset_dir):
131
+ shutil.rmtree(dataset_dir)
132
+ os.makedirs(dataset_dir)
133
+
134
+ file_path = os.path.join(".tmp", file.filename)
135
+ with open(file_path, "wb") as buffer:
136
+ shutil.copyfileobj(file.file, buffer)
137
+
138
+ training_status["status"] = "processing"
139
+ training_status["progress"] = 10
140
+ training_status["message"] = "Arquivo recebido. Extraindo..."
141
+
142
+ try:
143
+ # Extra�o
144
+ if file.filename.endswith('.zip'):
145
+ with zipfile.ZipFile(file_path, 'r') as zip_ref:
146
+ zip_ref.extractall(dataset_dir)
147
+ # RAR necessita do pacote rarfile, assumiremos ZIP para simplificar ou instruir o usuário.
148
+
149
+ training_status["progress"] = 25
150
+ training_status["message"] = "Dataset extraído. Aguardando início do treinamento."
151
+ return {"status": "success", "message": "Upload concluído."}
152
+ except Exception as e:
153
+ training_status["status"] = "failed"
154
+ training_status["message"] = "Erro na extração do dataset."
155
+ training_status["error"] = str(e)
156
+ raise HTTPException(status_code=500, detail=str(e))
157
+
158
+ from execution.train_wav2vec import start_finetuning
159
+
160
+ def real_training_task():
161
+ """Tarefa em background que executa o fine-tuning real no dataset."""
162
+ global training_status
163
+ training_status["status"] = "training"
164
+ training_status["progress"] = 35
165
+ training_status["message"] = "Carregando modelo e dataset para treinamento..."
166
+
167
+ try:
168
+ dataset_dir = ".tmp/dataset"
169
+ # Executa o fine-tuning
170
+ start_finetuning(dataset_dir)
171
+
172
+ training_status["progress"] = 100
173
+ training_status["status"] = "completed"
174
+ training_status["message"] = "Fine-Tuning concluído com sucesso! Modelo salvo localmente."
175
+ except Exception as e:
176
+ training_status["status"] = "failed"
177
+ training_status["message"] = f"Erro no treinamento: {str(e)}"
178
+ training_status["error"] = str(e)
179
+ print(f"Treinamento falhou: {e}")
180
+
181
+ @app.post("/admin/train")
182
+ async def admin_train(background_tasks: BackgroundTasks, token: str = Depends(verify_admin_token)):
183
+ global training_status
184
+ if training_status["status"] == "training":
185
+ raise HTTPException(status_code=400, detail="Treinamento já está em andamento.")
186
+
187
+ training_status["progress"] = 30
188
+ training_status["message"] = "Iniciando pipeline de treinamento..."
189
+ background_tasks.add_task(real_training_task)
190
+ return {"status": "success", "message": "Treinamento iniciado em background"}
191
+
192
+ @app.get("/admin/status")
193
+ async def admin_status(token: str = Depends(verify_admin_token)):
194
+ return training_status
195
+
196
+ # Garante diretório temporário para o mount não falhar
197
+ if not os.path.exists(".tmp"):
198
+ os.makedirs(".tmp")
199
+
200
+ # Servir arquivos do dashboard e imagens temporárias (se existirem)
201
+ app.mount("/tmp", StaticFiles(directory=".tmp"), name="tmp")
202
+
203
+ if os.path.exists("dashboard"):
204
+ app.mount("/", StaticFiles(directory="dashboard", html=True), name="dashboard")
205
+ else:
206
+ @app.get("/")
207
+ async def root_fallback():
208
+ return {"status": "ConfereAI API Running", "message": "Dashboard directory not found. Please use the Vercel frontend."}
209
+
210
+ if __name__ == "__main__":
211
+ import uvicorn
212
+ import os
213
+ port = int(os.environ.get("PORT", 8000))
214
+ host = os.environ.get("HOST", "0.0.0.0")
215
+ uvicorn.run(app, host=host, port=port)
execution/feature_extractor.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import json
3
+ import os
4
+
5
+ try:
6
+ import librosa
7
+ import librosa.display
8
+ import numpy as np
9
+ import matplotlib.pyplot as plt
10
+ HAS_LIBS = True
11
+ except ImportError:
12
+ HAS_LIBS = False
13
+
14
+ def extract_features(audio_path, output_dir=".tmp/"):
15
+ """
16
+ Extrai MFCC e Espectrograma de Mel do áudio.
17
+ """
18
+ if not HAS_LIBS:
19
+ return {"error": "Bibliotecas librosa/numpy não instaladas."}
20
+
21
+ # Carrega áudio
22
+ y, sr = librosa.load(audio_path)
23
+
24
+ # Mel Spectrogram
25
+ S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)
26
+ S_dB = librosa.power_to_db(S, ref=np.max)
27
+
28
+ # MFCC
29
+ mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40)
30
+
31
+ # Salva imagem do espectrograma para o dashboard
32
+ base_name = os.path.splitext(os.path.basename(audio_path))[0]
33
+ spec_filename = base_name + "_spec.png"
34
+ spec_path = os.path.join(output_dir, spec_filename)
35
+
36
+ plt.figure(figsize=(10, 4))
37
+ librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel')
38
+ plt.colorbar(format='%+2.0f dB')
39
+ plt.title('Mel-frequency spectrogram')
40
+ plt.tight_layout()
41
+ plt.savefig(spec_path)
42
+ plt.close()
43
+
44
+ return {
45
+ "audio_info": {
46
+ "duration": librosa.get_duration(y=y, sr=sr),
47
+ "sample_rate": sr
48
+ },
49
+ "spectrogram_path": spec_path,
50
+ "mfcc_shape": mfccs.shape
51
+ }
52
+
53
+ if __name__ == "__main__":
54
+ if len(sys.argv) < 2:
55
+ print("Uso: python feature_extractor.py <audio_path>")
56
+ else:
57
+ print(json.dumps(extract_features(sys.argv[1]), indent=2))
execution/inference_ast.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import librosa
3
+ import numpy as np
4
+ from transformers import AutoFeatureExtractor, ASTForAudioClassification
5
+
6
+ # Modelo AST (Audio Spectrogram Transformer)
7
+ # Usamos o modelo base do MIT como referência para análise espectral
8
+ MODEL_NAME = "MIT/ast-finetuned-audioset-10-10-0.4593"
9
+
10
+ # Singleton para carregar o modelo apenas uma vez
11
+ _extractor = None
12
+ _model = None
13
+
14
+ def get_ast_resources():
15
+ global _extractor, _model
16
+ if _extractor is None or _model is None:
17
+ print(f"Carregando motor AST: {MODEL_NAME}...")
18
+ _extractor = AutoFeatureExtractor.from_pretrained(MODEL_NAME)
19
+ _model = ASTForAudioClassification.from_pretrained(MODEL_NAME)
20
+ _model.eval()
21
+ return _extractor, _model
22
+
23
+ def run_ast_inference(file_path):
24
+ """
25
+ Executa a análise via Audio Spectrogram Transformer.
26
+ Identifica anomalias espectrais e inconsistências na textura sonora.
27
+ """
28
+ try:
29
+ extractor, model = get_ast_resources()
30
+
31
+ # Carrega áudio (resample para 16kHz conforme exigido pelo AST)
32
+ audio, _ = librosa.load(file_path, sr=16000)
33
+
34
+ # O AST espera entradas de 10 segundos (160.000 amostras)
35
+ # Vamos padronizar
36
+ if len(audio) > 160000:
37
+ audio = audio[:160000]
38
+ else:
39
+ audio = np.pad(audio, (0, 160000 - len(audio)), mode='constant')
40
+
41
+ # Extração de Features (Espectrograma de Mel)
42
+ inputs = extractor(audio, sampling_rate=16000, return_tensors="pt")
43
+
44
+ with torch.no_grad():
45
+ outputs = model(**inputs)
46
+ logits = outputs.logits
47
+
48
+ # No AudioSet, as classes são variadas. Para detecção de fraude sem fine-tuning específico,
49
+ # analisamos a "entropia" ou a probabilidade de classes sintéticas/anômalas.
50
+ # Como fallback funcional, calculamos um score de desvio estatístico.
51
+ probs = torch.nn.functional.softmax(logits, dim=-1)
52
+
53
+ # Simulação de detecção de anomalia baseada na textura espectral
54
+ # Em um cenário real com fine-tuning, usaríamos a classe 'deepfake'
55
+ # Aqui, usamos a variância das probabilidades como proxy de 'instabilidade' da IA
56
+ anomaly_score = float(torch.var(probs) * 100) # Exemplo de métrica de dispersão
57
+
58
+ # Normalizamos para um score de 0 a 1
59
+ risk_score = min(max(anomaly_score * 5, 0.0), 1.0)
60
+
61
+ return {
62
+ "risk_score": risk_score,
63
+ "engine": "AST-Transformer",
64
+ "status": "success"
65
+ }
66
+
67
+ except Exception as e:
68
+ print(f"Erro no motor AST: {e}")
69
+ return {"error": str(e), "risk_score": 0.0}
70
+
71
+ if __name__ == "__main__":
72
+ # Teste simples
73
+ import sys
74
+ if len(sys.argv) > 1:
75
+ print(run_ast_inference(sys.argv[1]))
execution/inference_wav2vec.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import json
3
+ import torch
4
+ import librosa
5
+ from transformers import AutoFeatureExtractor, AutoModelForAudioClassification
6
+
7
+ import os
8
+
9
+ # Configurações de Modelo
10
+ WAV2VEC_MODEL_ENV = os.environ.get("WAV2VEC_MODEL")
11
+ LOCAL_MODEL_DIR = "./local_finetuned_model"
12
+ DEFAULT_HUB_MODEL = "HyperMoon/wav2vec2-base-960h-finetuned-deepfake"
13
+
14
+ def run_inference(audio_path, fallback_model_name=DEFAULT_HUB_MODEL):
15
+ """
16
+ Realiza inferência real priorizando:
17
+ 1. Variável de ambiente WAV2VEC_MODEL (Se definida)
18
+ 2. Modelo fine-tuned localmente (Se existir)
19
+ 3. Modelo padrão do Hugging Face Hub
20
+ """
21
+ if WAV2VEC_MODEL_ENV:
22
+ model_path = WAV2VEC_MODEL_ENV
23
+ model_name = f"Env Model ({WAV2VEC_MODEL_ENV})"
24
+ elif os.path.exists(LOCAL_MODEL_DIR):
25
+ model_path = LOCAL_MODEL_DIR
26
+ model_name = "Local Fine-Tuned Model"
27
+ else:
28
+ model_path = fallback_model_name
29
+ model_name = f"Hub Model ({fallback_model_name})"
30
+
31
+ print(f"Rodando inferência REAL [{model_name}] em: {audio_path}", file=sys.stderr)
32
+
33
+ try:
34
+ # 1. Carrega extrator de características e modelo
35
+ print("Lendo modelo...", file=sys.stderr)
36
+ feature_extractor = AutoFeatureExtractor.from_pretrained(model_path)
37
+ model = AutoModelForAudioClassification.from_pretrained(model_path)
38
+
39
+ # 2. Carrega e pré-processa o áudio
40
+ print(f"Lendo áudio: {audio_path}", file=sys.stderr)
41
+ audio, sr = librosa.load(audio_path, sr=16000)
42
+ print(f"Áudio carregado. Shape: {audio.shape}", file=sys.stderr)
43
+
44
+ # 3. Prepara inputs
45
+ inputs = feature_extractor(audio, sampling_rate=16000, return_tensors="pt", padding=True)
46
+
47
+ # 3. Inferência
48
+ with torch.no_grad():
49
+ logits = model(**inputs).logits
50
+
51
+ # 4. Processa resultados
52
+ scores = torch.softmax(logits, dim=-1)
53
+ # O modelo HyperMoon geralmente tem 2 classes: 0 (Fake/Spoof) e 1 (Real/Bonafide)
54
+ # ou vice-versa. Vamos checar o config id2label
55
+ id2label = model.config.id2label
56
+
57
+ prediction_idx = torch.argmax(scores, dim=-1).item()
58
+ label = id2label[prediction_idx]
59
+ confidence = scores[0][prediction_idx].item()
60
+
61
+ # Normaliza para o nosso formato (precisamos saber quem é fraude)
62
+ # Se o label contiver 'fake', 'spoof' ou 'fraud', é fraude.
63
+ is_fraud = any(x in label.lower() for x in ['fake', 'spoof', 'fraud'])
64
+
65
+ # Queremos o 'deepfake_probability'
66
+ # Se o label 0 for fake, a probabilidade de deepfake é score[0][0]
67
+ # Tentamos encontrar o índice do 'fake'
68
+ fraud_idx = 0
69
+ for idx, lbl in id2label.items():
70
+ if any(x in lbl.lower() for x in ['fake', 'spoof', 'fraud']):
71
+ fraud_idx = int(idx) # Importante: converter para int
72
+ break
73
+
74
+ fraud_prob = scores[0][fraud_idx].item()
75
+
76
+ # --- NOVO: Análise Temporal (XAI) ---
77
+ temporal_scores = []
78
+ segment_duration = 1.0 # 1 segundo
79
+ samples_per_segment = int(segment_duration * 16000)
80
+
81
+ for i in range(0, len(audio), samples_per_segment):
82
+ segment = audio[i : i + samples_per_segment]
83
+ if len(segment) < samples_per_segment // 2: continue # Ignora restos muito pequenos
84
+
85
+ seg_inputs = feature_extractor(segment, sampling_rate=16000, return_tensors="pt", padding=True)
86
+ with torch.no_grad():
87
+ seg_logits = model(**seg_inputs).logits
88
+ seg_probs = torch.softmax(seg_logits, dim=-1)
89
+ seg_fraud_prob = seg_probs[0][fraud_idx].item()
90
+ temporal_scores.append(round(seg_fraud_prob, 3))
91
+ # ------------------------------------
92
+
93
+ results = {
94
+ "model": model_name,
95
+ "prediction": label.upper(),
96
+ "confidence": confidence,
97
+ "deepfake_probability": fraud_prob,
98
+ "temporal_scores": temporal_scores, # Novo campo para XAI
99
+ "verdict": "SPOOF" if is_fraud else "BONAFIDE",
100
+ "metadata": {
101
+ "id2label": id2label,
102
+ "all_scores": scores.tolist()
103
+ }
104
+ }
105
+
106
+ except Exception as e:
107
+ print(f"Erro na inferência: {e}")
108
+ results = {
109
+ "error": str(e),
110
+ "verdict": "ERROR"
111
+ }
112
+
113
+ return results
114
+
115
+ if __name__ == "__main__":
116
+ if len(sys.argv) < 2:
117
+ print("Uso: python inference_wav2vec.py <audio_path>")
118
+ else:
119
+ # Silenciamos warnings de transformers
120
+ import warnings
121
+ warnings.filterwarnings("ignore")
122
+ print(json.dumps(run_inference(sys.argv[1])))
execution/metadata_extractor.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import json
3
+
4
+ def extract_metadata(file_path):
5
+ """
6
+ Extrai metadados básicos de um arquivo de áudio.
7
+ """
8
+ # Mock de extração
9
+ metadata = {
10
+ "format": "WAV",
11
+ "sample_rate": 44100,
12
+ "channels": 2,
13
+ "duration_seconds": 12.5,
14
+ "encoder": "Lavf60.3.100",
15
+ "creation_time": "2026-04-23 19:40:00"
16
+ }
17
+ return metadata
18
+
19
+ if __name__ == "__main__":
20
+ if len(sys.argv) < 2:
21
+ print("Uso: python metadata_extractor.py <path_to_audio>")
22
+ sys.exit(1)
23
+
24
+ path = sys.argv[1]
25
+ meta = extract_metadata(path)
26
+ print(json.dumps(meta, indent=2))
execution/train_wav2vec.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import librosa
4
+ from torch.utils.data import Dataset
5
+ from transformers import Wav2Vec2FeatureExtractor, Wav2Vec2ForSequenceClassification, Trainer, TrainingArguments
6
+ from typing import Dict, List
7
+
8
+ # Define o modelo base usado pelo projeto
9
+ BASE_MODEL_NAME = "HyperMoon/wav2vec2-base-960h-finetuned-deepfake"
10
+ LOCAL_MODEL_DIR = "./local_finetuned_model"
11
+
12
+ def get_processor():
13
+ """Retorna o extrator de características do modelo base (processador de áudio puro, sem tokenizador de texto)"""
14
+ return Wav2Vec2FeatureExtractor.from_pretrained(BASE_MODEL_NAME)
15
+
16
+ class DeepfakeAudioDataset(Dataset):
17
+ """
18
+ Dataset Customizado do Pytorch para carregar áudios de Pastas.
19
+ Espera-se que o diretório base tenha duas subpastas: 'real' e 'fake'.
20
+ """
21
+ def __init__(self, root_dir: str, processor: Wav2Vec2FeatureExtractor, max_length: int = 160000):
22
+ self.root_dir = root_dir
23
+ self.processor = processor
24
+ self.max_length = max_length
25
+ self.files: List[Dict] = []
26
+
27
+ self._load_metadata()
28
+
29
+ def _load_metadata(self):
30
+ real_dir = os.path.join(self.root_dir, 'real')
31
+ fake_dir = os.path.join(self.root_dir, 'fake')
32
+
33
+ if os.path.exists(real_dir):
34
+ for f in os.listdir(real_dir):
35
+ if f.lower().endswith(('.wav', '.mp3', '.flac')):
36
+ self.files.append({"path": os.path.join(real_dir, f), "label": 0})
37
+
38
+ if os.path.exists(fake_dir):
39
+ for f in os.listdir(fake_dir):
40
+ if f.lower().endswith(('.wav', '.mp3', '.flac')):
41
+ self.files.append({"path": os.path.join(fake_dir, f), "label": 1})
42
+
43
+ def __len__(self):
44
+ return len(self.files)
45
+
46
+ def __getitem__(self, idx):
47
+ item = self.files[idx]
48
+ audio_path = item["path"]
49
+ label = item["label"]
50
+
51
+ # Load and resample audio to 16kHz
52
+ speech, _ = librosa.load(audio_path, sr=16000)
53
+
54
+ # Process audio to get input values
55
+ input_values = self.processor(
56
+ speech,
57
+ sampling_rate=16000,
58
+ return_tensors="pt",
59
+ padding="max_length",
60
+ max_length=self.max_length,
61
+ truncation=True
62
+ ).input_values[0]
63
+
64
+ return {
65
+ "input_values": input_values,
66
+ "labels": torch.tensor(label, dtype=torch.long)
67
+ }
68
+
69
+ def start_finetuning(dataset_dir: str):
70
+ """
71
+ Inicia o treinamento congelando as camadas base para evitar OOM e focar apenas na cabeça de classificação.
72
+ """
73
+ processor = get_processor()
74
+
75
+ # Prepara os datasets (simplificação: usando o mesmo para train e eval na V1)
76
+ train_dataset = DeepfakeAudioDataset(dataset_dir, processor)
77
+
78
+ if len(train_dataset) == 0:
79
+ raise ValueError("Nenhum áudio encontrado no dataset.")
80
+
81
+ # Mapeamento explícito para evitar confusão de labels (0=Real, 1=Fraude)
82
+ id2label = {0: "AUTHENTIC", 1: "FAKE"}
83
+ label2id = {"AUTHENTIC": 0, "FAKE": 1}
84
+
85
+ # Carrega modelo e congela base
86
+ model = Wav2Vec2ForSequenceClassification.from_pretrained(
87
+ BASE_MODEL_NAME,
88
+ num_labels=2,
89
+ id2label=id2label,
90
+ label2id=label2id,
91
+ ignore_mismatched_sizes=True
92
+ )
93
+
94
+ # Freeze feature extractor e a base do transformer para poupar memória e tempo (Adaptação para hardwares fracos)
95
+ if hasattr(model, 'freeze_feature_encoder'):
96
+ model.freeze_feature_encoder()
97
+ elif hasattr(model, 'freeze_feature_extractor'):
98
+ model.freeze_feature_extractor()
99
+
100
+ if hasattr(model, 'wav2vec2'):
101
+ for param in model.wav2vec2.parameters():
102
+ param.requires_grad = False
103
+
104
+ # Training args voltados para hardware modesto
105
+ training_args = TrainingArguments(
106
+ output_dir="./results",
107
+ num_train_epochs=5,
108
+ per_device_train_batch_size=2, # Batch muito pequeno para não estourar memória
109
+ gradient_accumulation_steps=4, # Acumula para dar efeito de batch=8
110
+ learning_rate=2e-5,
111
+ save_strategy="epoch",
112
+ logging_dir="./logs",
113
+ logging_steps=1,
114
+ remove_unused_columns=False,
115
+ report_to="none", # Evita erros de conexão com serviços externos de log
116
+ )
117
+
118
+ trainer = Trainer(
119
+ model=model,
120
+ args=training_args,
121
+ train_dataset=train_dataset,
122
+ eval_dataset=train_dataset, # Idealmente, devíamos fazer um split de 80/20
123
+ )
124
+
125
+ trainer.train()
126
+
127
+ # Salva o modelo afinado
128
+ model.save_pretrained(LOCAL_MODEL_DIR)
129
+ processor.save_pretrained(LOCAL_MODEL_DIR)
130
+
131
+ return True
132
+
133
+ if __name__ == "__main__":
134
+ import sys
135
+ if len(sys.argv) > 1:
136
+ start_finetuning(sys.argv[1])
main.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from execution.fastapi_server import app
2
+ import uvicorn
3
+ import os
4
+
5
+ if __name__ == "__main__":
6
+ # Hugging Face Spaces usa a porta 7860 por padrão
7
+ port = int(os.environ.get("PORT", 7860))
8
+ uvicorn.run(app, host="0.0.0.0", port=port)
9
+
10
+ @app.get("/version-check")
11
+ async def version_check():
12
+ return {"version": "2.2", "status": "updated"}
package.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "confereai-frontend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "echo 'Static build complete'"
7
+ }
8
+ }
requirements.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Backend
2
+ fastapi>=0.100.0
3
+ uvicorn>=0.23.0
4
+ python-multipart>=0.0.6
5
+ accelerate>=1.1.0
6
+
7
+ # Machine Learning & Audio
8
+ torch --index-url https://download.pytorch.org/whl/cpu
9
+ transformers
10
+ librosa
11
+ soundfile
12
+ matplotlib
13
+ scipy
14
+
15
+ # Utilities
16
+ python-dotenv
17
+ requests
superpowers ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit e7a2d16476bf042e9add4699c9d018a90f86e4a6
vercel.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": 2,
3
+ "name": "confereai",
4
+ "builds": [
5
+ {
6
+ "src": "dashboard/**/*",
7
+ "use": "@vercel/static"
8
+ }
9
+ ],
10
+ "routes": [
11
+ {
12
+ "src": "/(.*)",
13
+ "dest": "/dashboard/$1"
14
+ }
15
+ ]
16
+ }