20 Commits
2.0 ... 2.2

Author SHA1 Message Date
Fábio Cavalcanti
69cb3b1965 personalização de comportamento e mensagens de transcrição 2025-01-07 11:36:39 -03:00
Fábio Cavalcanti
ec65839beb correção na lógica de resumos 2025-01-07 10:15:56 -03:00
Fábio Cavalcanti
abc4c4298a romeno no readme 2024-12-19 18:08:40 -03:00
Fábio Cavalcanti
161251a403 adicionado Romeno as linguagens novas 2024-12-19 18:05:59 -03:00
Fábio Cavalcanti
97cc842eb8 Adicionado rotação de chaves groq e seleção de idioma de tradução 2024-12-19 17:34:26 -03:00
Fábio Cavalcanti
ffd916c855 Merge branch 'dev' 2024-12-18 16:14:58 -03:00
Fábio Cavalcanti
fede0057e5 ajuste prompt 2024-12-18 16:11:04 -03:00
Fábio Cavalcanti
345cc26186 ajuste readme 2024-12-12 23:27:53 -03:00
Fábio Cavalcanti
a646e724f6 ajuste readme 2024-12-12 23:26:32 -03:00
Fábio Cavalcanti
c88c014e86 ajuste readme 2024-12-12 23:25:27 -03:00
Fábio Cavalcanti
c72c143609 Ajuste de orientação de uso 2024-12-12 21:04:44 -03:00
Fábio Cavalcanti
64f7f64b17 ajuste de orientação de uso 2024-12-12 21:04:03 -03:00
Fábio Cavalcanti
8e775b7379 Corrigido chamada para buscar grupos 2024-12-12 19:57:04 -03:00
Fábio Cavalcanti
374169e56f ajuste na busca de grupos para permitir transcrição 2024-12-12 19:54:13 -03:00
Fábio Cavalcanti
d6bbb5bc6e correções de variaveis de sistema e redis 2024-12-12 19:28:30 -03:00
Fábio Cavalcanti
d0c3ffca09 readme igual ao do main 2024-12-12 19:01:38 -03:00
Fábio Cavalcanti
1f40b128fa Melhorias de Inicialização do Serviço Docker 2024-12-12 18:58:48 -03:00
Fábio Cavalcanti
b3753e768c ajustes de inicialização, adicionado orientações de uso da API no Manager 2024-12-12 18:52:35 -03:00
Fábio Cavalcanti
b6e3ea8ec3 adicionado orientação de uso da api na interface 2024-12-12 18:30:26 -03:00
Fábio Cavalcanti
bb63c590e4 branch 2.0 no readme 2024-12-12 17:36:20 -03:00
11 changed files with 659 additions and 94 deletions

View File

@@ -11,4 +11,5 @@ __pycache__
*.md
*.postman_collection.json
deploy_*.sh
manager_atualizar.py
manager_atualizar.py
roadmap.md

3
.gitignore vendored
View File

@@ -6,4 +6,5 @@ GPT.postman_collection.json
.gitignore
deploy_producao.sh
Dockerfile
manager_atualizar.py
manager_atualizar.py
roadmap.md

View File

@@ -1,8 +1,9 @@
# Usar uma imagem oficial do Python como base
FROM python:3.10-slim
# Instalar dependências do sistema
# Instalar dependências do sistema, incluindo redis-tools
RUN apt-get update && apt-get install -y --no-install-recommends \
redis-tools \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Definir o diretório de trabalho
@@ -22,10 +23,14 @@ RUN mkdir -p /app/static
COPY static/ /app/static/
# Garantir permissões de execução ao script inicial
COPY start.sh .
RUN chmod +x start.sh
# Converter possíveis caracteres de retorno de carro do Windows
RUN apt-get update && apt-get install -y dos2unix && dos2unix start.sh && apt-get remove -y dos2unix && apt-get autoremove -y && apt-get clean
# Expor as portas usadas pela aplicação
EXPOSE 8005 8501
# Definir o comando inicial
CMD ["./start.sh"]
CMD ["/bin/bash", "/app/start.sh"]

View File

@@ -47,8 +47,10 @@ class Settings:
self.BUSINESS_MESSAGE = self.get_redis_value("BUSINESS_MESSAGE", "*Impacte AI* Premium Services")
self.PROCESS_GROUP_MESSAGES = self.get_redis_value("PROCESS_GROUP_MESSAGES", "false").lower() == "true"
self.PROCESS_SELF_MESSAGES = self.get_redis_value("PROCESS_SELF_MESSAGES", "true").lower() == "true"
self.LOG_LEVEL = self.get_redis_value("LOG_LEVEL", "INFO").upper()
self.DEBUG_MODE = os.getenv("DEBUG_MODE", "false").lower() == "true"
self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
self.TRANSCRIPTION_LANGUAGE = self.get_redis_value("TRANSCRIPTION_LANGUAGE", "pt")
# Mascarar chave ao logar
if self.GROQ_API_KEY:
masked_key = f"{self.GROQ_API_KEY[:10]}...{self.GROQ_API_KEY[-4:]}"

View File

@@ -4,7 +4,7 @@ services:
tcaudio:
image: impacteai/transcrevezap:latest
networks:
- transcrevezap_network
- sua_rede_externa # Substitua pelo nome da sua rede externa
ports:
- 8005:8005 # Porta para FastAPI
- 8501:8501 # Porta para Streamlit
@@ -13,12 +13,13 @@ services:
- UVICORN_HOST=0.0.0.0
- UVICORN_RELOAD=true
- UVICORN_WORKERS=1
- API_DOMAIN=seu.dominio.com #coloque seu subdominio da API apontado aqui
- DEBUG_MODE=false
- LOG_LEVEL=INFO
- MANAGER_USER=seu_usuario_admin
- MANAGER_PASSWORD=sua_senha_segura
- MANAGER_USER=seu_usuario_admin # Defina Usuário do Manager
- MANAGER_PASSWORD=sua_senha_segura # Defina Senha do Manager
- REDIS_HOST=redis-transcrevezap
- REDIS_PORT=6380
- REDIS_PORT=6380 # Porta personalizada para o Redis do TranscreveZAP
depends_on:
- redis-transcrevezap
deploy:
@@ -29,7 +30,7 @@ services:
- node.role == manager
labels:
- traefik.enable=true
- traefik.http.routers.tcaudio.rule=Host(`seu.dominio.com`)
- traefik.http.routers.tcaudio.rule=Host(`seu.dominio.com`) #coloque seu subdominio da API apontado aqui
- traefik.http.routers.tcaudio.entrypoints=websecure
- traefik.http.routers.tcaudio.tls.certresolver=letsencryptresolver
- traefik.http.services.tcaudio.loadbalancer.server.port=8005
@@ -38,7 +39,7 @@ services:
- traefik.http.middlewares.traefik-compress.compress=true
- traefik.http.routers.tcaudio.middlewares=traefik-compress
# Configuração do Streamlit
- traefik.http.routers.tcaudio-manager.rule=Host(`manager.seu.dominio.com`)
- traefik.http.routers.tcaudio-manager.rule=Host(`manager.seu.dominio.com`) #coloque seu subdominio do Manager apontado aqui
- traefik.http.routers.tcaudio-manager.entrypoints=websecure
- traefik.http.routers.tcaudio-manager.tls.certresolver=letsencryptresolver
- traefik.http.services.tcaudio-manager.loadbalancer.server.port=8501
@@ -51,10 +52,10 @@ services:
volumes:
- redis_transcrevezap_data:/data
networks:
- transcrevezap_network
- sua_rede_externa # Substitua pelo nome da sua rede externa
networks:
transcrevezap_network:
sua_rede_externa: # Substitua pelo nome da sua rede externa
external: true
name: sua_rede_externa # Substitua pelo nome da sua rede externa

46
main.py
View File

@@ -14,7 +14,10 @@ import os
app = FastAPI()
storage = StorageHandler()
@app.on_event("startup")
async def startup_event():
api_domain = os.getenv("API_DOMAIN", "seu.dominio.com")
redis_client.set("API_DOMAIN", api_domain)
# Função para buscar configurações do Redis com fallback para valores padrão
def get_config(key, default=None):
try:
@@ -105,21 +108,42 @@ async def transcreve_audios(request: Request):
"source": audio_source
})
# Carregar configurações de formatação
output_mode = get_config("output_mode", "both")
summary_header = get_config("summary_header", "🤖 *Resumo do áudio:*")
transcription_header = get_config("transcription_header", "🔊 *Transcrição do áudio:*")
character_limit = int(get_config("character_limit", "500"))
# Transcrever áudio
storage.add_log("INFO", "Iniciando transcrição")
transcription_text, _ = await transcribe_audio(audio_source)
# Resumir se necessário
summary_text = await summarize_text_if_needed(transcription_text)
# Determinar se precisa de resumo baseado no modo de saída
summary_text = None
if output_mode in ["both", "summary_only"] or (
output_mode == "smart" and len(transcription_text) > character_limit
):
summary_text = await summarize_text_if_needed(transcription_text)
# Construir mensagem baseada no modo de saída
message_parts = []
# Formatar mensagem
summary_message = (
f"🤖 *Resumo do áudio:*\n\n"
f"{summary_text}\n\n"
f"🔊 *Transcrição do áudio:*\n\n"
f"{transcription_text}\n\n"
f"{dynamic_settings['BUSINESS_MESSAGE']}"
)
if output_mode == "smart":
if len(transcription_text) > character_limit:
message_parts.append(f"{summary_header}\n\n{summary_text}")
else:
message_parts.append(f"{transcription_header}\n\n{transcription_text}")
else:
if output_mode in ["both", "summary_only"] and summary_text:
message_parts.append(f"{summary_header}\n\n{summary_text}")
if output_mode in ["both", "transcription_only"]:
message_parts.append(f"{transcription_header}\n\n{transcription_text}")
# Adicionar mensagem de negócio
message_parts.append(dynamic_settings['BUSINESS_MESSAGE'])
# Juntar todas as partes da mensagem
summary_message = "\n\n".join(message_parts)
# Enviar resposta
await send_message_to_whatsapp(

View File

@@ -34,9 +34,15 @@ def fetch_whatsapp_groups(server_url, instance, api_key):
params = {"getParticipants": "false"} # Adicionando o parâmetro de query
try:
st.write(f"Requisição para URL: {url}") # Debug para URL
st.write(f"Cabeçalhos: {headers}") # Debug para headers
st.write(f"Parâmetros: {params}") # Debug para parâmetros
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
st.write(f"Status Code: {response.status_code}") # Debug para status HTTP
response.raise_for_status() # Levanta exceções HTTP
return response.json() # Retorna o JSON da resposta
except requests.RequestException as e:
st.error(f"Erro ao buscar grupos: {str(e)}")
if response.text:
@@ -108,6 +114,7 @@ def load_settings():
"BUSINESS_MESSAGE": get_from_redis("BUSINESS_MESSAGE", "*Impacte AI* Premium Services"),
"PROCESS_GROUP_MESSAGES": get_from_redis("PROCESS_GROUP_MESSAGES", "false"),
"PROCESS_SELF_MESSAGES": get_from_redis("PROCESS_SELF_MESSAGES", "true"),
"TRANSCRIPTION_LANGUAGE": get_from_redis("TRANSCRIPTION_LANGUAGE", "pt"),
}
except Exception as e:
st.error(f"Erro ao carregar configurações do Redis: {e}")
@@ -209,6 +216,24 @@ def show_statistics():
st.plotly_chart(fig, use_container_width=True)
else:
st.info("Ainda não há dados de processamento disponíveis.")
# Adicionar informações sobre o endpoint da API
st.subheader("Endpoint da API")
api_domain = get_from_redis("API_DOMAIN", "seu.dominio.com")
api_endpoint = f"https://{api_domain}/transcreve-audios"
st.code(api_endpoint, language="text")
if st.button(" Instruções de Uso"):
st.info(
"Para utilizar o serviço de transcrição, siga estas etapas:\n\n"
"1. Copie a URL completa acima.\n"
"2. Na configuração de webhook da Evolution API:\n"
" - Cole a URL no campo apropriado.\n"
" - Ative o webhook.\n"
" - Marque as opções 'Webhook Base64' e o Evento 'MESSAGES_UPSERT'.\n\n"
"Isso permitirá que a Evolution API envie as mensagens de áudio para o nosso serviço de transcrição."
)
except Exception as e:
st.error(f"Erro ao carregar estatísticas: {e}")
@@ -317,15 +342,268 @@ def manage_blocks():
else:
st.info("Nenhum usuário bloqueado.")
# manager.py - Adicionar na seção de configurações
def message_settings_section():
st.subheader("📝 Configurações de Mensagem")
# Carregar configurações atuais
message_settings = storage.get_message_settings()
# Headers personalizados
col1, col2 = st.columns(2)
with col1:
summary_header = st.text_input(
"Cabeçalho do Resumo",
value=message_settings["summary_header"],
help="Formato do cabeçalho para o resumo do áudio"
)
with col2:
transcription_header = st.text_input(
"Cabeçalho da Transcrição",
value=message_settings["transcription_header"],
help="Formato do cabeçalho para a transcrição do áudio"
)
# Modo de saída
output_mode = st.selectbox(
"Modo de Saída",
options=["both", "summary_only", "transcription_only", "smart"],
format_func=lambda x: {
"both": "Resumo e Transcrição",
"summary_only": "Apenas Resumo",
"transcription_only": "Apenas Transcrição",
"smart": "Modo Inteligente (baseado no tamanho)"
}[x],
value=message_settings["output_mode"]
)
# Configuração do limite de caracteres (visível apenas no modo inteligente)
if output_mode == "smart":
character_limit = st.number_input(
"Limite de Caracteres para Modo Inteligente",
min_value=100,
max_value=5000,
value=int(message_settings["character_limit"]),
help="Se a transcrição exceder este limite, será enviado apenas o resumo"
)
else:
character_limit = message_settings["character_limit"]
# Botão de salvar
if st.button("💾 Salvar Configurações de Mensagem"):
try:
new_settings = {
"summary_header": summary_header,
"transcription_header": transcription_header,
"output_mode": output_mode,
"character_limit": character_limit
}
storage.save_message_settings(new_settings)
st.success("Configurações de mensagem salvas com sucesso!")
except Exception as e:
st.error(f"Erro ao salvar configurações: {str(e)}")
def manage_settings():
st.title("⚙️ Configurações")
st.subheader("Configurações do Sistema")
st.text_input("GROQ_API_KEY", value=st.session_state.settings["GROQ_API_KEY"], key="groq_api_key")
st.text_input("Mensagem de Serviço no Rodapé", value=st.session_state.settings["BUSINESS_MESSAGE"], key="business_message")
st.selectbox("Processar Mensagens em Grupos", options=["true", "false"], index=["true", "false"].index(st.session_state.settings["PROCESS_GROUP_MESSAGES"]), key="process_group_messages")
st.selectbox("Processar Mensagens Próprias", options=["true", "false"], index=["true", "false"].index(st.session_state.settings["PROCESS_SELF_MESSAGES"]), key="process_self_messages")
if st.button("Salvar Configurações"):
save_settings()
# Criar tabs para melhor organização
tab1, tab2, tab3 = st.tabs(["🔑 Chaves API", "🌐 Configurações Gerais", "📝 Formatação de Mensagens"])
with tab1:
st.subheader("Gerenciamento de Chaves GROQ")
# Campo para gerenciamento de chaves GROQ
main_key = st.text_input(
"GROQ API Key Principal",
value=st.session_state.settings["GROQ_API_KEY"],
key="groq_api_key",
type="password",
help="Chave GROQ principal do sistema"
)
# Seção de chaves adicionais
st.markdown("---")
st.subheader("Chaves GROQ Adicionais (Sistema de Rodízio)")
# Exibir chaves existentes
groq_keys = storage.get_groq_keys()
if groq_keys:
st.write("Chaves configuradas para rodízio:")
for key in groq_keys:
col1, col2 = st.columns([4, 1])
with col1:
masked_key = f"{key[:10]}...{key[-4:]}"
st.code(masked_key, language=None)
with col2:
if st.button("🗑️", key=f"remove_{key}", help="Remover esta chave"):
storage.remove_groq_key(key)
st.success(f"Chave removida do rodízio!")
st.experimental_rerun()
# Adicionar nova chave
new_key = st.text_input(
"Adicionar Nova Chave GROQ",
key="new_groq_key",
type="password",
help="Insira uma nova chave GROQ para adicionar ao sistema de rodízio"
)
col1, col2 = st.columns([4, 1])
with col1:
if st.button(" Adicionar ao Rodízio", help="Adicionar esta chave ao sistema de rodízio"):
if new_key:
if new_key.startswith("gsk_"):
storage.add_groq_key(new_key)
st.success("Nova chave adicionada ao sistema de rodízio!")
st.experimental_rerun()
else:
st.error("Chave inválida! A chave deve começar com 'gsk_'")
else:
st.warning("Por favor, insira uma chave válida")
pass
with tab2:
st.subheader("Configurações do Sistema")
# Business Message
st.text_input(
"Mensagem de Serviço no Rodapé",
value=st.session_state.settings["BUSINESS_MESSAGE"],
key="business_message"
)
# Process Group Messages
st.selectbox(
"Processar Mensagens em Grupos",
options=["true", "false"],
index=["true", "false"].index(st.session_state.settings["PROCESS_GROUP_MESSAGES"]),
key="process_group_messages"
)
# Process Self Messages
st.selectbox(
"Processar Mensagens Próprias",
options=["true", "false"],
index=["true", "false"].index(st.session_state.settings["PROCESS_SELF_MESSAGES"]),
key="process_self_messages"
)
# Configuração de idioma
st.markdown("---")
st.subheader("🌐 Idioma")
# Dicionário de idiomas em português
IDIOMAS = {
"pt": "Português",
"en": "Inglês",
"es": "Espanhol",
"fr": "Francês",
"de": "Alemão",
"it": "Italiano",
"ja": "Japonês",
"ko": "Coreano",
"zh": "Chinês",
"ro": "Romeno",
"ru": "Russo",
"ar": "Árabe",
"hi": "Hindi",
"nl": "Holandês",
"pl": "Polonês",
"tr": "Turco"
}
# Carregar configuração atual de idioma
current_language = get_from_redis("TRANSCRIPTION_LANGUAGE", "pt")
# Seleção de idioma
selected_language = st.selectbox(
"Idioma para Transcrição e Resumo",
options=list(IDIOMAS.keys()),
format_func=lambda x: IDIOMAS[x],
index=list(IDIOMAS.keys()).index(current_language) if current_language in IDIOMAS else 0,
help="Selecione o idioma para transcrição dos áudios e geração dos resumos",
key="transcription_language"
)
pass
with tab3:
st.subheader("Formatação de Mensagens")
# Headers personalizados
col1, col2 = st.columns(2)
with col1:
summary_header = st.text_input(
"Cabeçalho do Resumo",
value=get_from_redis("summary_header", "🤖 *Resumo do áudio:*"),
key="summary_header",
help="Formato do cabeçalho para o resumo do áudio"
)
with col2:
transcription_header = st.text_input(
"Cabeçalho da Transcrição",
value=get_from_redis("transcription_header", "🔊 *Transcrição do áudio:*"),
key="transcription_header",
help="Formato do cabeçalho para a transcrição do áudio"
)
# Modo de saída - Corrigido para usar index
output_modes = ["both", "summary_only", "transcription_only", "smart"]
output_mode_labels = {
"both": "Resumo e Transcrição",
"summary_only": "Apenas Resumo",
"transcription_only": "Apenas Transcrição",
"smart": "Modo Inteligente (baseado no tamanho)"
}
current_mode = get_from_redis("output_mode", "both")
mode_index = output_modes.index(current_mode) if current_mode in output_modes else 0
output_mode = st.selectbox(
"Modo de Saída",
options=output_modes,
format_func=lambda x: output_mode_labels[x],
index=mode_index,
key="output_mode",
help="Selecione como deseja que as mensagens sejam enviadas"
)
if output_mode == "smart":
character_limit = st.number_input(
"Limite de Caracteres para Modo Inteligente",
min_value=100,
max_value=5000,
value=int(get_from_redis("character_limit", "500")),
help="Se a transcrição exceder este limite, será enviado apenas o resumo"
)
# Botão de salvar unificado
if st.button("💾 Salvar Todas as Configurações"):
try:
# Salvar configurações existentes
save_settings()
# Salvar novas configurações de mensagem
save_to_redis("summary_header", summary_header)
save_to_redis("transcription_header", transcription_header)
save_to_redis("output_mode", output_mode)
if output_mode == "smart":
save_to_redis("character_limit", str(character_limit))
# Se há uma chave principal, adicionar ao sistema de rodízio
if main_key and main_key.startswith("gsk_"):
storage.add_groq_key(main_key)
# Salvar configuração de idioma
save_to_redis("TRANSCRIPTION_LANGUAGE", selected_language)
st.success("✅ Todas as configurações foram salvas com sucesso!")
# Mostrar resumo
total_keys = len(storage.get_groq_keys())
st.info(f"""Sistema configurado com {total_keys} chave(s) GROQ no rodízio
Idioma definido: {IDIOMAS[selected_language]}
Modo de saída: {output_mode_labels[output_mode]}""")
except Exception as e:
st.error(f"Erro ao salvar configurações: {str(e)}")
if "authenticated" not in st.session_state:
st.session_state.authenticated = False

119
readme.md
View File

@@ -17,20 +17,20 @@ Antes de começar, certifique-se de ter os seguintes requisitos:
- Docker e Docker Compose instalados ([Instruções](https://docs.docker.com/get-docker/))
- Uma conta Evolution API com chave válida
- Uma conta GROQ API com chave válida (começa com 'gsk_') ([Crie sua CONTA](https://console.groq.com/login))
* Em caso de uso com Proxy Reverso Aponte um Subdomínio para a API e outro para o MANAGER da aplicação
---
## 🚀 **Instalação e Configuração**
### 🐳 Docker Compose
1. Clone o repositório:
```bash
```bash
git clone https://github.com/seu-usuario/transcrevezap.git
cd transcrevezap
```
```
2. Configure o arquivo docker-compose.yaml:
```yaml
```yaml
version: "3.7"
services:
tcaudio:
@@ -41,6 +41,9 @@ Antes de começar, certifique-se de ter os seguintes requisitos:
environment:
- REDIS_HOST=redis
- REDIS_PORT=6380
- API_DOMAIN=seu-ip
- DEBUG_MODE=false
- LOG_LEVEL=INFO
- MANAGER_USER=admin
- MANAGER_PASSWORD=sua_senha_aqui
depends_on:
@@ -54,7 +57,7 @@ Antes de começar, certifique-se de ter os seguintes requisitos:
volumes:
redis_data:
```
```
3. Inicie os serviços:
```bash
@@ -67,11 +70,10 @@ Acesse a interface de gerenciamento em http://seu-ip:8501.
Faça login com as credenciais definidas em MANAGER_USER e MANAGER_PASSWORD.
Na seção "Configurações", defina:
GROQ_API_KEY: Sua chave da API GROQ
BUSINESS_MESSAGE: Mensagem de rodapé após transcrição
PROCESS_GROUP_MESSAGES: Habilitar processamento de mensagens em grupos
PROCESS_SELF_MESSAGES: Habilitar processamento de mensagens próprias
1. GROQ_API_KEY: Sua chave da API GROQ
2. BUSINESS_MESSAGE: Mensagem de rodapé após transcrição
3. PROCESS_GROUP_MESSAGES: Habilitar processamento de mensagens em grupos
4. PROCESS_SELF_MESSAGES: Habilitar processamento de mensagens próprias
## 🔧 Uso
Endpoint para Webhook da Evolution API
@@ -125,7 +127,8 @@ uvicorn main:app --host 0.0.0.0 --port 8005
```bash
http://127.0.0.1:8005/transcreve-audios
```
1. Aponte um subomínio com o IP do seu servidor para a API da TranscreveZAP
2. Aponte um subomínio com o IP do seu servidor para o MANAGER da TranscreveZAP
### 🌟 Docker Swarm com Traefik
```yaml
@@ -135,7 +138,7 @@ services:
tcaudio:
image: impacteai/transcrevezap:latest
networks:
- transcrevezap_network
- sua_rede_externa # Substitua pelo nome da sua rede externa
ports:
- 8005:8005 # Porta para FastAPI
- 8501:8501 # Porta para Streamlit
@@ -144,12 +147,13 @@ services:
- UVICORN_HOST=0.0.0.0
- UVICORN_RELOAD=true
- UVICORN_WORKERS=1
- API_DOMAIN=seu.dominio.com #coloque seu subdominio da API apontado aqui
- DEBUG_MODE=false
- LOG_LEVEL=INFO
- MANAGER_USER=seu_usuario_admin
- MANAGER_PASSWORD=sua_senha_segura
- MANAGER_USER=seu_usuario_admin # Defina Usuário do Manager
- MANAGER_PASSWORD=sua_senha_segura # Defina Senha do Manager
- REDIS_HOST=redis-transcrevezap
- REDIS_PORT=6380
- REDIS_PORT=6380 # Porta personalizada para o Redis do TranscreveZAP
depends_on:
- redis-transcrevezap
deploy:
@@ -160,7 +164,7 @@ services:
- node.role == manager
labels:
- traefik.enable=true
- traefik.http.routers.tcaudio.rule=Host(`seu.dominio.com`)
- traefik.http.routers.tcaudio.rule=Host(`seu.dominio.com`) #coloque seu subdominio da API apontado aqui
- traefik.http.routers.tcaudio.entrypoints=websecure
- traefik.http.routers.tcaudio.tls.certresolver=letsencryptresolver
- traefik.http.services.tcaudio.loadbalancer.server.port=8005
@@ -169,7 +173,7 @@ services:
- traefik.http.middlewares.traefik-compress.compress=true
- traefik.http.routers.tcaudio.middlewares=traefik-compress
# Configuração do Streamlit
- traefik.http.routers.tcaudio-manager.rule=Host(`manager.seu.dominio.com`)
- traefik.http.routers.tcaudio-manager.rule=Host(`manager.seu.dominio.com`) #coloque seu subdominio do Manager apontado aqui
- traefik.http.routers.tcaudio-manager.entrypoints=websecure
- traefik.http.routers.tcaudio-manager.tls.certresolver=letsencryptresolver
- traefik.http.services.tcaudio-manager.loadbalancer.server.port=8501
@@ -182,10 +186,10 @@ services:
volumes:
- redis_transcrevezap_data:/data
networks:
- transcrevezap_network
- sua_rede_externa # Substitua pelo nome da sua rede externa
networks:
transcrevezap_network:
sua_rede_externa: # Substitua pelo nome da sua rede externa
external: true
name: sua_rede_externa # Substitua pelo nome da sua rede externa
@@ -203,9 +207,10 @@ https://transcricaoaudio.seudominio.com.br/transcreve-audios
Para usar com Traefik, certifique-se de:
1. Ter o Traefik configurado em seu ambiente Docker Swarm
2. Configurar o DNS do seu domínio para apontar para o servidor
2. Configurar 2 DNS do seu domínio para apontar para a API e para o MANAGER
3. Ajustar as labels do Traefik conforme seu ambiente
4. Verificar se a rede externa existe no Docker Swarm
5. Utilize a stack de exemplo contida no projeto para guiar a instalação
## 📝 **Notas Importantes**
- A GROQ_API_KEY deve começar com 'gsk_'
@@ -214,6 +219,68 @@ Para usar com Traefik, certifique-se de:
- Em produção, recomenda-se DEBUG_MODE=false
- Configure LOG_LEVEL=DEBUG apenas para troubleshooting
## ✨ Novos Recursos na v2.1
### 🌍 Suporte Multilíngue
- Transcrição e resumo em 10+ idiomas
- Mudança instantânea de idioma
- Interface intuitiva para seleção de idioma
- Mantém consistência entre transcrição e resumo
### 🔄 Sistema Inteligente de Rodízio de Chaves
- Suporte a múltiplas chaves GROQ
- Balanceamento automático de carga
- Maior redundância e disponibilidade
- Gestão simplificada de chaves via interface
## 🌍 Sistema de Idiomas
O TranscreveZAP agora suporta transcrição e resumo em múltiplos idiomas. Na seção "Configurações", você pode:
1. Selecionar o idioma principal para transcrição e resumo
2. O sistema manterá Português como padrão se nenhum outro for selecionado
3. A mudança de idioma é aplicada instantaneamente após salvar
Idiomas suportados:
- 🇩🇪 Alemão
- 🇸🇦 Árabe
- 🇨🇳 Chinês
- 🇰🇷 Coreano
- 🇪🇸 Espanhol
- 🇫🇷 Francês
- 🇮🇳 Hindi
- 🇳🇱 Holandês
- 🇬🇧 Inglês
- 🇮🇹 Italiano
- 🇯🇵 Japonês
- 🇵🇱 Polonês
- 🇧🇷 Português (padrão)
- 🇷🇴 Romeno
- 🇷🇺 Russo
- 🇹🇷 Turco
## 🔄 Sistema de Rodízio de Chaves GROQ
O TranscreveZAP agora suporta múltiplas chaves GROQ com sistema de rodízio automático para melhor distribuição de carga e redundância.
### Funcionalidades:
1. Adicione múltiplas chaves GROQ para distribuição de carga
2. O sistema alterna automaticamente entre as chaves disponíveis
3. Se uma chave falhar, o sistema usa a próxima disponível
4. Visualize todas as chaves configuradas no painel
5. Adicione ou remova chaves sem interromper o serviço
### Como Configurar:
1. Acesse a seção "Configurações"
2. Na área "🔑 Gerenciamento de Chaves GROQ":
- Adicione a chave principal
- Use "Adicionar Nova Chave GROQ" para incluir chaves adicionais
- O sistema começará a usar todas as chaves em rodízio automaticamente
### Boas Práticas:
- Mantenha pelo menos duas chaves ativas para redundância
- Monitore o uso das chaves pelo painel administrativo
- Remova chaves expiradas ou inválidas
- Todas as chaves devem começar com 'gsk_'
## 🔍 **Troubleshooting**
Se encontrar problemas:
1. Verifique se todas as variáveis obrigatórias estão configuradas
@@ -221,6 +288,18 @@ Se encontrar problemas:
3. Verifique os logs do container
4. Certifique-se que as APIs estão acessíveis
### Problemas com Múltiplas Chaves GROQ:
1. Verifique se todas as chaves começam com 'gsk_'
2. Confirme se as chaves estão ativas na console GROQ
3. Monitore os logs para identificar falhas específicas de chaves
4. Mantenha pelo menos uma chave válida no sistema
### Problemas com Idiomas:
1. Verifique se o idioma está corretamente selecionado nas configurações
2. Confirme se a configuração foi salva com sucesso
3. Reinicie o serviço se as alterações não forem aplicadas
4. Verifique os logs para confirmar o idioma em uso
## 📄 **Licença**
Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.

View File

@@ -31,32 +31,114 @@ async def convert_base64_to_file(base64_data):
})
raise
async def get_groq_key():
"""Obtém a próxima chave GROQ do sistema de rodízio."""
key = storage.get_next_groq_key()
if not key:
raise HTTPException(
status_code=500,
detail="Nenhuma chave GROQ configurada. Configure pelo menos uma chave no painel administrativo."
)
return key
async def summarize_text_if_needed(text):
"""Resumir texto usando a API GROQ"""
"""Resumir texto usando a API GROQ com sistema de rodízio de chaves"""
storage.add_log("DEBUG", "Iniciando processo de resumo", {
"text_length": len(text)
})
# Obter idioma configurado
language = redis_client.get("TRANSCRIPTION_LANGUAGE") or "pt"
storage.add_log("DEBUG", "Idioma configurado para resumo", {
"language": language,
"redis_value": redis_client.get("TRANSCRIPTION_LANGUAGE")
})
url_completions = "https://api.groq.com/openai/v1/chat/completions"
groq_key = await get_groq_key()
headers = {
"Authorization": f"Bearer {settings.GROQ_API_KEY}",
"Authorization": f"Bearer {groq_key}",
"Content-Type": "application/json",
}
# Adaptar o prompt para considerar o idioma
prompt_by_language = {
"pt": """
Entenda o contexto desse áudio e faça um resumo super enxuto sobre o que se trata.
Esse áudio foi enviado pelo whatsapp, de alguém, para Fabio.
Escreva APENAS o resumo do áudio como se fosse você que estivesse enviando
essa mensagem! Não cumprimente, não de oi, não escreva nada antes nem depois
do resumo, responda apenas um resumo enxuto do que foi falado no áudio.
""",
"en": """
Understand the context of this audio and make a very concise summary of what it's about.
This audio was sent via WhatsApp, from someone, to Fabio.
Write ONLY the summary of the audio as if you were sending this message yourself!
Don't greet, don't say hi, don't write anything before or after the summary,
respond with just a concise summary of what was said in the audio.
""",
"es": """
Entiende el contexto de este audio y haz un resumen muy conciso sobre de qué se trata.
Este audio fue enviado por WhatsApp, de alguien, para Fabio.
Escribe SOLO el resumen del audio como si tú estuvieras enviando este mensaje.
No saludes, no escribas nada antes ni después del resumen, responde únicamente un resumen conciso de lo dicho en el audio.
""",
"fr": """
Comprenez le contexte de cet audio et faites un résumé très concis de ce dont il s'agit.
Cet audio a été envoyé via WhatsApp, par quelqu'un, à Fabio.
Écrivez UNIQUEMENT le résumé de l'audio comme si c'était vous qui envoyiez ce message.
Ne saluez pas, n'écrivez rien avant ou après le résumé, répondez seulement par un résumé concis de ce qui a été dit dans l'audio.
""",
"de": """
Verstehen Sie den Kontext dieses Audios und erstellen Sie eine sehr kurze Zusammenfassung, worum es geht.
Dieses Audio wurde über WhatsApp von jemandem an Fabio gesendet.
Schreiben Sie NUR die Zusammenfassung des Audios, als ob Sie diese Nachricht senden würden.
Grüßen Sie nicht, schreiben Sie nichts vor oder nach der Zusammenfassung, antworten Sie nur mit einer kurzen Zusammenfassung dessen, was im Audio gesagt wurde.
""",
"it": """
Comprendi il contesto di questo audio e fai un riassunto molto conciso di cosa si tratta.
Questo audio è stato inviato tramite WhatsApp, da qualcuno, a Fabio.
Scrivi SOLO il riassunto dell'audio come se fossi tu a inviare questo messaggio.
Non salutare, non scrivere nulla prima o dopo il riassunto, rispondi solo con un riassunto conciso di ciò che è stato detto nell'audio.
""",
"ja": """
この音声の内容を理解し、それが何について話されているのかを非常に簡潔に要約してください。
この音声は、誰かがWhatsAppでファビオに送ったものです。
あなたがそのメッセージを送っているように、音声の要約だけを記述してください。
挨拶や前置き、後書きは書かず、音声で話された内容の簡潔な要約のみを返信してください。
""",
"ko": """
이 오디오의 맥락을 이해하고, 무엇에 관한 것인지 매우 간략하게 요약하세요.
이 오디오는 누군가가 WhatsApp을 통해 Fabio에게 보낸 것입니다.
마치 당신이 메시지를 보내는 것처럼 오디오의 요약만 작성하세요.
인사하거나, 요약 전후로 아무것도 쓰지 말고, 오디오에서 말한 내용을 간략하게 요약한 답변만 하세요.
""",
"zh": """
理解这个音频的上下文,并简洁地总结它的内容。
这个音频是某人通过WhatsApp发送给Fabio的。
请仅以摘要的形式回答,就好像是你在发送这条消息。
不要问候,也不要在摘要前后写任何内容,只需用一句简短的话总结音频中所说的内容。
""",
"ro": """
Înțelege contextul acestui audio și creează un rezumat foarte concis despre ce este vorba.
Acest audio a fost trimis prin WhatsApp, de cineva, către Fabio.
Scrie DOAR rezumatul audio-ului ca și cum tu ai trimite acest mesaj.
Nu saluta, nu scrie nimic înainte sau după rezumat, răspunde doar cu un rezumat concis despre ce s-a spus în audio.
""",
"ru": """
Поймите контекст этого аудио и сделайте очень краткое резюме, о чем идет речь.
Это аудио было отправлено через WhatsApp кем-то Фабио.
Напишите ТОЛЬКО резюме аудио, как будто вы отправляете это сообщение.
Не приветствуйте, не пишите ничего до или после резюме, ответьте только кратким резюме того, что говорилось в аудио.
"""
}
# Usar o prompt do idioma configurado ou fallback para português
base_prompt = prompt_by_language.get(language, prompt_by_language["pt"])
json_data = {
"messages": [{
"role": "user",
"content": f"""
Entenda o contexto desse áudio e faça um resumo super enxuto sobre o que se trata, coloque os pontos relevantes e mais importantes no resumo de forma muito curta.
Esse áudio foi enviado pelo whatsapp, de alguém, para Gabriel.
Escreva APENAS o resumo do áudio como se fosse você que estivesse enviando
essa mensagem! Não comprimente, não de oi, não escreva nada antes nem depois
do resumo, responda apenas um resumo enxuto do que foi falado no áudio.
IMPORTANTE: Não faça esse resumo como se fosse um áudio que uma terceira
pessoa enviou, não diga coisas como 'a pessoa está falando...' etc.
Escreva o resumo com base nessa mensagem do áudio,
como se você estivesse escrevendo esse resumo e enviando em
texto pelo whatsapp: {text}""",
"content": f"{base_prompt}\n\nTexto para resumir: {text}",
}],
"model": "llama-3.3-70b-versatile",
}
@@ -70,7 +152,8 @@ async def summarize_text_if_needed(text):
summary_text = summary_result["choices"][0]["message"]["content"]
storage.add_log("INFO", "Resumo gerado com sucesso", {
"original_length": len(text),
"summary_length": len(summary_text)
"summary_length": len(summary_text),
"language": language
})
return summary_text
else:
@@ -88,11 +171,28 @@ async def summarize_text_if_needed(text):
raise
async def transcribe_audio(audio_source, apikey=None):
"""Transcreve áudio usando a API GROQ"""
"""
Transcreve áudio usando a API GROQ com sistema de rodízio de chaves.
Args:
audio_source: Caminho do arquivo de áudio ou URL
apikey: Chave da API opcional para download de áudio
Returns:
tuple: (texto_transcrito, False)
"""
storage.add_log("INFO", "Iniciando processo de transcrição")
url = "https://api.groq.com/openai/v1/audio/transcriptions"
groq_headers = {"Authorization": f"Bearer {settings.GROQ_API_KEY}"}
groq_key = await get_groq_key()
groq_headers = {"Authorization": f"Bearer {groq_key}"}
# Obter idioma configurado
language = redis_client.get("TRANSCRIPTION_LANGUAGE") or "pt"
storage.add_log("DEBUG", "Idioma configurado para transcrição", {
"language": language,
"redis_value": redis_client.get("TRANSCRIPTION_LANGUAGE")
})
try:
async with aiohttp.ClientSession() as session:
# Se o audio_source for uma URL
@@ -123,26 +223,18 @@ async def transcribe_audio(audio_source, apikey=None):
data = aiohttp.FormData()
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
data.add_field('model', 'whisper-large-v3')
data.add_field('language', 'pt')
data.add_field('language', language)
storage.add_log("DEBUG", "Enviando áudio para transcrição")
async with session.post(url, headers=groq_headers, data=data) as response:
if response.status == 200:
result = await response.json()
message = result.get("text", "")
transcription = result.get("text", "")
storage.add_log("INFO", "Transcrição concluída com sucesso", {
"text_length": len(message)
"text_length": len(transcription)
})
is_summary = False
if len(message) > 1000:
storage.add_log("DEBUG", "Texto longo detectado, iniciando resumo", {
"text_length": len(message)
})
is_summary = True
message = await summarize_text_if_needed(message)
return message, is_summary
return transcription, False
else:
error_text = await response.text()
storage.add_log("ERROR", "Erro na transcrição", {
@@ -161,7 +253,6 @@ async def transcribe_audio(audio_source, apikey=None):
# Limpar arquivos temporários
if isinstance(audio_source, str) and os.path.exists(audio_source):
os.unlink(audio_source)
async def send_message_to_whatsapp(server_url, instance, apikey, message, remote_jid, message_id):
"""Envia mensagem via WhatsApp"""
storage.add_log("DEBUG", "Preparando envio de mensagem", {
@@ -264,4 +355,35 @@ async def get_audio_base64(server_url, instance, apikey, message_id):
"type": type(e).__name__,
"message_id": message_id
})
raise
raise
async def format_message(transcription_text, summary_text=None):
"""Formata a mensagem baseado nas configurações."""
settings = storage.get_message_settings()
message_parts = []
# Determinar modo de saída
output_mode = settings["output_mode"]
char_limit = int(settings["character_limit"])
if output_mode == "smart":
# Modo inteligente baseado no tamanho
if len(transcription_text) > char_limit:
if summary_text:
message_parts.append(f"{settings['summary_header']}\n\n{summary_text}")
else:
message_parts.append(f"{settings['transcription_header']}\n\n{transcription_text}")
elif output_mode == "summary_only":
if summary_text:
message_parts.append(f"{settings['summary_header']}\n\n{summary_text}")
elif output_mode == "transcription_only":
message_parts.append(f"{settings['transcription_header']}\n\n{transcription_text}")
else: # both
if summary_text:
message_parts.append(f"{settings['summary_header']}\n\n{summary_text}")
message_parts.append(f"{settings['transcription_header']}\n\n{transcription_text}")
# Adicionar mensagem de negócio
message_parts.append(dynamic_settings['BUSINESS_MESSAGE'])
return "\n\n".join(message_parts)

View File

@@ -1,15 +1,22 @@
#!/bin/bash
# Função para inicializar configurações no Redis
# Função para inicializar configurações no Redis se não existirem
initialize_redis_config() {
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET GROQ_API_KEY "sua_api_key_aqui"
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET BUSINESS_MESSAGE "*Impacte AI* Premium Services"
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET PROCESS_GROUP_MESSAGES "false"
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET PROCESS_SELF_MESSAGES "true"
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET DEBUG_MODE "false"
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET GROQ_API_KEY "sua_api_key_aqui" NX
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET BUSINESS_MESSAGE "*Impacte AI* Premium Services" NX
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET PROCESS_GROUP_MESSAGES "false" NX
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET PROCESS_SELF_MESSAGES "true" NX
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET API_DOMAIN "$API_DOMAIN" NX
}
# Inicializar configurações no Redis
# Aguardar o Redis estar pronto
echo "Aguardando o Redis ficar disponível..."
until redis-cli -h $REDIS_HOST -p $REDIS_PORT PING; do
echo "Redis não está pronto - aguardando..."
sleep 5
done
# Inicializar configurações no Redis (apenas se não existirem)
initialize_redis_config
# Iniciar o FastAPI em background

View File

@@ -168,4 +168,49 @@ class StorageHandler:
if self.redis.ttl(key) <= 0:
self.redis.delete(key)
except Exception as e:
self.logger.error(f"Erro ao limpar backups antigos: {e}")
self.logger.error(f"Erro ao limpar backups antigos: {e}")
# Método de rotação de chaves groq
def get_groq_keys(self) -> List[str]:
"""Obtém todas as chaves GROQ armazenadas."""
return list(self.redis.smembers(self._get_redis_key("groq_keys")))
def add_groq_key(self, key: str):
"""Adiciona uma nova chave GROQ ao conjunto."""
if key and key.startswith("gsk_"):
self.redis.sadd(self._get_redis_key("groq_keys"), key)
return True
return False
def remove_groq_key(self, key: str):
"""Remove uma chave GROQ do conjunto."""
self.redis.srem(self._get_redis_key("groq_keys"), key)
def get_next_groq_key(self) -> str:
"""
Obtém a próxima chave GROQ no sistema de rodízio.
Utiliza um contador no Redis para controlar a rotação.
"""
keys = self.get_groq_keys()
if not keys:
return None
# Obtém e incrementa o contador de rodízio
counter = int(self.redis.get(self._get_redis_key("groq_key_counter")) or "0")
next_counter = (counter + 1) % len(keys)
self.redis.set(self._get_redis_key("groq_key_counter"), str(next_counter))
return keys[counter % len(keys)]
def get_message_settings(self):
"""Obtém as configurações de mensagens."""
return {
"summary_header": self.redis.get(self._get_redis_key("summary_header")) or "🤖 *Resumo do áudio:*",
"transcription_header": self.redis.get(self._get_redis_key("transcription_header")) or "🔊 *Transcrição do áudio:*",
"output_mode": self.redis.get(self._get_redis_key("output_mode")) or "both",
"character_limit": int(self.redis.get(self._get_redis_key("character_limit")) or "500"),
}
def save_message_settings(self, settings: dict):
"""Salva as configurações de mensagens."""
for key, value in settings.items():
self.redis.set(self._get_redis_key(key), str(value))