Skip to content

Commit 242cf5a

Browse files
committed
feat: frontend React + gitignores + env config
1 parent 6410265 commit 242cf5a

2,308 files changed

Lines changed: 502139 additions & 56 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
node_modules/

backend/.dockerignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
venv/
2+
__pycache__/
3+
*.pyc
4+
.env
5+
tests/
6+
scripts/
7+
*.md

backend/Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# imagen base slim — menos peso que la full
2+
FROM python:3.13-slim
3+
4+
# instala ffmpeg como dependencia del sistema
5+
RUN apt-get update && apt-get install -y \
6+
ffmpeg \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
# directorio de trabajo
10+
WORKDIR /app
11+
12+
# copia requirements primero — aprovecha cache de Docker
13+
COPY requirements.txt .
14+
RUN pip install --no-cache-dir -r requirements.txt
15+
16+
# copia el resto del código
17+
COPY app/ ./app/
18+
19+
# crea el directorio temp para audios
20+
RUN mkdir -p /tmp/azurely
21+
22+
EXPOSE 8000
23+
24+
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

backend/app/api/routes/health.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,20 @@ async def health_check():
2222
except Exception:
2323
pass
2424

25-
# verifica OpenAI — lista los deployments disponibles
25+
# verifica OpenAI — request mínimo para validar conectividad
2626
try:
2727
client = OpenAI(
28-
base_url="https://azurely-openai.openai.azure.com/openai/v1",
28+
base_url=settings.AZURE_OPENAI_ENDPOINT,
2929
api_key=settings.AZURE_OPENAI_KEY
3030
)
31-
client.models.list()
31+
client.chat.completions.create(
32+
model=settings.AZURE_OPENAI_DEPLOYMENT,
33+
messages=[{"role": "user", "content": "hi"}],
34+
max_tokens=1
35+
)
3236
azure_openai_ok = True
3337
except Exception as e:
34-
print(f"OpenAI error: {e}") # <-- aquí
38+
print(f"OpenAI error: {e}")
3539
pass
3640

3741
return {

backend/app/services/openai_service.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,26 @@
55
import json
66

77
SYSTEM_PROMPT = """
8-
Eres un asistente especializado en analizar transcripciones de reuniones.
9-
Responde SIEMPRE en JSON válido con esta estructura exacta, sin texto extra:
8+
Eres un asistente experto en análisis de reuniones de trabajo.
9+
Recibirás una transcripción y debes extraer información clave con precisión.
10+
11+
REGLAS ESTRICTAS:
12+
1. Responde SOLO en JSON válido, sin texto extra, sin markdown
13+
2. El summary debe ser ejecutivo: qué se decidió, no qué se habló
14+
3. key_points: mínimo 3, máximo 6 puntos. Cada uno debe ser una decisión o dato concreto
15+
4. action_items: extrae TODA tarea, compromiso o seguimiento mencionado, aunque sea implícito
16+
5. Para assignee: usa el nombre si se menciona, si no pon null
17+
6. Para deadline: usa la fecha o día mencionado, si no pon null
18+
7. duration_estimate: estima basándote en la cantidad de texto de la transcripción
19+
20+
ESTRUCTURA EXACTA:
1021
{
11-
"summary": "resumen ejecutivo en 3-5 oraciones",
12-
"key_points": ["punto clave 1", "punto clave 2"],
22+
"summary": "resumen ejecutivo enfocado en decisiones tomadas, 3-5 oraciones",
23+
"key_points": ["decisión o dato concreto 1", "decisión o dato concreto 2"],
1324
"action_items": [
14-
{"task": "descripción de la tarea", "assignee": "nombre o null", "deadline": "fecha o null"}
25+
{"task": "descripción clara de la tarea", "assignee": "nombre o null", "deadline": "fecha o null"}
1526
],
16-
"duration_estimate": "estimación en minutos o null"
27+
"duration_estimate": "estimación en minutos basada en el contenido"
1728
}
1829
"""
1930

backend/app/utils/audio.py

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77

88
SUPPORTED_EXTENSIONS = {".mp3", ".wav", ".m4a", ".ogg", ".mp4"}
99

10-
# path absoluto porque Windows no encuentra ffmpeg desde Python
11-
FFMPEG_PATH = r"C:\Users\andre\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.0.1-full_build\bin\ffmpeg.exe"
10+
# detecta si estamos en Linux (Docker) o Windows (local)
11+
FFMPEG_PATH = "/usr/bin/ffmpeg" if os.name != "nt" else r"C:\Users\andre\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.0.1-full_build\bin\ffmpeg.exe"
12+
FFPROBE_PATH = "/usr/bin/ffprobe" if os.name != "nt" else FFMPEG_PATH.replace("ffmpeg.exe", "ffprobe.exe")
1213

1314
def validate_audio(filename: str, size_bytes: int) -> None:
1415
ext = Path(filename).suffix.lower()
@@ -20,62 +21,25 @@ def validate_audio(filename: str, size_bytes: int) -> None:
2021
raise AudioTooLargeError(size_mb)
2122

2223
def convert_to_wav(input_path: str) -> str:
23-
"""
24-
Convierte cualquier audio a WAV 16kHz mono.
25-
Azure Speech SDK requiere este formato exacto.
26-
"""
27-
output_path = os.path.join(
28-
settings.TEMP_DIR,
29-
f"{uuid.uuid4()}.wav"
30-
)
24+
"""Convierte cualquier audio a WAV 16kHz mono."""
25+
output_path = os.path.join(settings.TEMP_DIR, f"{uuid.uuid4()}.wav")
3126

3227
(
3328
ffmpeg
3429
.input(input_path)
35-
.output(
36-
output_path,
37-
ar=16000, # 16kHz — requerido por Azure Speech
38-
ac=1, # mono — reduce tamaño y mejora STT
39-
format="wav"
40-
)
30+
.output(output_path, ar=16000, ac=1, format="wav")
4131
.overwrite_output()
42-
.run(cmd=FFMPEG_PATH, quiet=True) # cmd apunta al binario explícito
32+
.run(cmd=FFMPEG_PATH, quiet=True)
4333
)
4434

4535
return output_path
4636

47-
async def save_upload(file) -> str:
48-
"""
49-
Guarda el archivo subido en TEMP_DIR con nombre único.
50-
Retorna el path del archivo guardado.
51-
"""
52-
import aiofiles
53-
54-
os.makedirs(settings.TEMP_DIR, exist_ok=True)
55-
56-
ext = Path(file.filename).suffix.lower()
57-
temp_path = os.path.join(settings.TEMP_DIR, f"{uuid.uuid4()}{ext}")
58-
59-
async with aiofiles.open(temp_path, "wb") as f:
60-
content = await file.read()
61-
await f.write(content)
62-
63-
return temp_path
64-
6537
def split_audio(wav_path: str, chunk_minutes: int = 2) -> list[str]:
66-
"""
67-
Divide un WAV en chunks de chunk_minutes minutos.
68-
Retorna lista de paths de los chunks.
69-
"""
70-
import subprocess
71-
import json
72-
73-
# obtiene duración total con ffprobe
74-
probe = ffmpeg.probe(wav_path, cmd=FFMPEG_PATH.replace("ffmpeg.exe", "ffprobe.exe"))
38+
"""Divide un WAV en chunks de chunk_minutes minutos."""
39+
probe = ffmpeg.probe(wav_path, cmd=FFPROBE_PATH)
7540
duration = float(probe["format"]["duration"])
7641
chunk_seconds = chunk_minutes * 60
7742

78-
# si el audio es corto no necesita split
7943
if duration <= chunk_seconds:
8044
return [wav_path]
8145

@@ -99,4 +63,19 @@ def split_audio(wav_path: str, chunk_minutes: int = 2) -> list[str]:
9963
start += chunk_seconds
10064
index += 1
10165

102-
return chunks
66+
return chunks
67+
68+
async def save_upload(file) -> str:
69+
"""Guarda el archivo subido en TEMP_DIR con nombre único."""
70+
import aiofiles
71+
72+
os.makedirs(settings.TEMP_DIR, exist_ok=True)
73+
74+
ext = Path(file.filename).suffix.lower()
75+
temp_path = os.path.join(settings.TEMP_DIR, f"{uuid.uuid4()}{ext}")
76+
77+
async with aiofiles.open(temp_path, "wb") as f:
78+
content = await file.read()
79+
await f.write(content)
80+
81+
return temp_path

backend/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ python-multipart==0.0.9
44
pydantic-settings==2.4.0
55
azure-cognitiveservices-speech==1.40.0
66
openai==1.51.0
7+
httpx==0.27.2
78
ffmpeg-python==0.2.0
89
aiofiles==24.1.0
910
python-dotenv==1.0.1

docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: "3.9"
2+
3+
services:
4+
backend:
5+
build: ./backend
6+
ports:
7+
- "8000:8000"
8+
env_file:
9+
- ./backend/.env
10+
volumes:
11+
- /tmp/azurely:/tmp/azurely

frontend/.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_API_URL=http://localhost:8000

frontend/.env.production

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_API_URL=https://azurely-backend.lemonforest-be1990ff.eastus.azurecontainerapps.io

0 commit comments

Comments
 (0)