Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69cb3b1965 | ||
|
|
ec65839beb | ||
|
|
abc4c4298a | ||
|
|
161251a403 | ||
|
|
97cc842eb8 | ||
|
|
ffd916c855 | ||
|
|
fede0057e5 | ||
|
|
345cc26186 | ||
|
|
a646e724f6 | ||
|
|
c88c014e86 | ||
|
|
c72c143609 | ||
|
|
64f7f64b17 | ||
|
|
8e775b7379 | ||
|
|
374169e56f | ||
|
|
d6bbb5bc6e | ||
|
|
d0c3ffca09 | ||
|
|
1f40b128fa | ||
|
|
b3753e768c | ||
|
|
b6e3ea8ec3 | ||
|
|
bb63c590e4 |
@@ -12,3 +12,4 @@ __pycache__
|
|||||||
*.postman_collection.json
|
*.postman_collection.json
|
||||||
deploy_*.sh
|
deploy_*.sh
|
||||||
manager_atualizar.py
|
manager_atualizar.py
|
||||||
|
roadmap.md
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ GPT.postman_collection.json
|
|||||||
deploy_producao.sh
|
deploy_producao.sh
|
||||||
Dockerfile
|
Dockerfile
|
||||||
manager_atualizar.py
|
manager_atualizar.py
|
||||||
|
roadmap.md
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# Usar uma imagem oficial do Python como base
|
# Usar uma imagem oficial do Python como base
|
||||||
FROM python:3.10-slim
|
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 \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
redis-tools \
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Definir o diretório de trabalho
|
# Definir o diretório de trabalho
|
||||||
@@ -22,10 +23,14 @@ RUN mkdir -p /app/static
|
|||||||
COPY static/ /app/static/
|
COPY static/ /app/static/
|
||||||
|
|
||||||
# Garantir permissões de execução ao script inicial
|
# Garantir permissões de execução ao script inicial
|
||||||
|
COPY start.sh .
|
||||||
RUN chmod +x 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
|
# Expor as portas usadas pela aplicação
|
||||||
EXPOSE 8005 8501
|
EXPOSE 8005 8501
|
||||||
|
|
||||||
# Definir o comando inicial
|
# Definir o comando inicial
|
||||||
CMD ["./start.sh"]
|
CMD ["/bin/bash", "/app/start.sh"]
|
||||||
@@ -47,7 +47,9 @@ class Settings:
|
|||||||
self.BUSINESS_MESSAGE = self.get_redis_value("BUSINESS_MESSAGE", "*Impacte AI* Premium Services")
|
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_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.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
|
# Mascarar chave ao logar
|
||||||
if self.GROQ_API_KEY:
|
if self.GROQ_API_KEY:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ services:
|
|||||||
tcaudio:
|
tcaudio:
|
||||||
image: impacteai/transcrevezap:latest
|
image: impacteai/transcrevezap:latest
|
||||||
networks:
|
networks:
|
||||||
- transcrevezap_network
|
- sua_rede_externa # Substitua pelo nome da sua rede externa
|
||||||
ports:
|
ports:
|
||||||
- 8005:8005 # Porta para FastAPI
|
- 8005:8005 # Porta para FastAPI
|
||||||
- 8501:8501 # Porta para Streamlit
|
- 8501:8501 # Porta para Streamlit
|
||||||
@@ -13,12 +13,13 @@ services:
|
|||||||
- UVICORN_HOST=0.0.0.0
|
- UVICORN_HOST=0.0.0.0
|
||||||
- UVICORN_RELOAD=true
|
- UVICORN_RELOAD=true
|
||||||
- UVICORN_WORKERS=1
|
- UVICORN_WORKERS=1
|
||||||
|
- API_DOMAIN=seu.dominio.com #coloque seu subdominio da API apontado aqui
|
||||||
- DEBUG_MODE=false
|
- DEBUG_MODE=false
|
||||||
- LOG_LEVEL=INFO
|
- LOG_LEVEL=INFO
|
||||||
- MANAGER_USER=seu_usuario_admin
|
- MANAGER_USER=seu_usuario_admin # Defina Usuário do Manager
|
||||||
- MANAGER_PASSWORD=sua_senha_segura
|
- MANAGER_PASSWORD=sua_senha_segura # Defina Senha do Manager
|
||||||
- REDIS_HOST=redis-transcrevezap
|
- REDIS_HOST=redis-transcrevezap
|
||||||
- REDIS_PORT=6380
|
- REDIS_PORT=6380 # Porta personalizada para o Redis do TranscreveZAP
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-transcrevezap
|
- redis-transcrevezap
|
||||||
deploy:
|
deploy:
|
||||||
@@ -29,7 +30,7 @@ services:
|
|||||||
- node.role == manager
|
- node.role == manager
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- 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.entrypoints=websecure
|
||||||
- traefik.http.routers.tcaudio.tls.certresolver=letsencryptresolver
|
- traefik.http.routers.tcaudio.tls.certresolver=letsencryptresolver
|
||||||
- traefik.http.services.tcaudio.loadbalancer.server.port=8005
|
- traefik.http.services.tcaudio.loadbalancer.server.port=8005
|
||||||
@@ -38,7 +39,7 @@ services:
|
|||||||
- traefik.http.middlewares.traefik-compress.compress=true
|
- traefik.http.middlewares.traefik-compress.compress=true
|
||||||
- traefik.http.routers.tcaudio.middlewares=traefik-compress
|
- traefik.http.routers.tcaudio.middlewares=traefik-compress
|
||||||
# Configuração do Streamlit
|
# 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.entrypoints=websecure
|
||||||
- traefik.http.routers.tcaudio-manager.tls.certresolver=letsencryptresolver
|
- traefik.http.routers.tcaudio-manager.tls.certresolver=letsencryptresolver
|
||||||
- traefik.http.services.tcaudio-manager.loadbalancer.server.port=8501
|
- traefik.http.services.tcaudio-manager.loadbalancer.server.port=8501
|
||||||
@@ -51,10 +52,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis_transcrevezap_data:/data
|
- redis_transcrevezap_data:/data
|
||||||
networks:
|
networks:
|
||||||
- transcrevezap_network
|
- sua_rede_externa # Substitua pelo nome da sua rede externa
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
transcrevezap_network:
|
sua_rede_externa: # Substitua pelo nome da sua rede externa
|
||||||
external: true
|
external: true
|
||||||
name: sua_rede_externa # Substitua pelo nome da sua rede externa
|
name: sua_rede_externa # Substitua pelo nome da sua rede externa
|
||||||
|
|
||||||
|
|||||||
44
main.py
44
main.py
@@ -14,7 +14,10 @@ import os
|
|||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
storage = StorageHandler()
|
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
|
# Função para buscar configurações do Redis com fallback para valores padrão
|
||||||
def get_config(key, default=None):
|
def get_config(key, default=None):
|
||||||
try:
|
try:
|
||||||
@@ -105,21 +108,42 @@ async def transcreve_audios(request: Request):
|
|||||||
"source": audio_source
|
"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
|
# Transcrever áudio
|
||||||
storage.add_log("INFO", "Iniciando transcrição")
|
storage.add_log("INFO", "Iniciando transcrição")
|
||||||
transcription_text, _ = await transcribe_audio(audio_source)
|
transcription_text, _ = await transcribe_audio(audio_source)
|
||||||
|
|
||||||
# Resumir se necessário
|
# 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)
|
summary_text = await summarize_text_if_needed(transcription_text)
|
||||||
|
|
||||||
# Formatar mensagem
|
# Construir mensagem baseada no modo de saída
|
||||||
summary_message = (
|
message_parts = []
|
||||||
f"🤖 *Resumo do áudio:*\n\n"
|
|
||||||
f"{summary_text}\n\n"
|
if output_mode == "smart":
|
||||||
f"🔊 *Transcrição do áudio:*\n\n"
|
if len(transcription_text) > character_limit:
|
||||||
f"{transcription_text}\n\n"
|
message_parts.append(f"{summary_header}\n\n{summary_text}")
|
||||||
f"{dynamic_settings['BUSINESS_MESSAGE']}"
|
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
|
# Enviar resposta
|
||||||
await send_message_to_whatsapp(
|
await send_message_to_whatsapp(
|
||||||
|
|||||||
292
manager.py
292
manager.py
@@ -34,9 +34,15 @@ def fetch_whatsapp_groups(server_url, instance, api_key):
|
|||||||
params = {"getParticipants": "false"} # Adicionando o parâmetro de query
|
params = {"getParticipants": "false"} # Adicionando o parâmetro de query
|
||||||
|
|
||||||
try:
|
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 = requests.get(url, headers=headers, params=params)
|
||||||
response.raise_for_status()
|
st.write(f"Status Code: {response.status_code}") # Debug para status HTTP
|
||||||
return response.json()
|
|
||||||
|
response.raise_for_status() # Levanta exceções HTTP
|
||||||
|
return response.json() # Retorna o JSON da resposta
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
st.error(f"Erro ao buscar grupos: {str(e)}")
|
st.error(f"Erro ao buscar grupos: {str(e)}")
|
||||||
if response.text:
|
if response.text:
|
||||||
@@ -108,6 +114,7 @@ def load_settings():
|
|||||||
"BUSINESS_MESSAGE": get_from_redis("BUSINESS_MESSAGE", "*Impacte AI* Premium Services"),
|
"BUSINESS_MESSAGE": get_from_redis("BUSINESS_MESSAGE", "*Impacte AI* Premium Services"),
|
||||||
"PROCESS_GROUP_MESSAGES": get_from_redis("PROCESS_GROUP_MESSAGES", "false"),
|
"PROCESS_GROUP_MESSAGES": get_from_redis("PROCESS_GROUP_MESSAGES", "false"),
|
||||||
"PROCESS_SELF_MESSAGES": get_from_redis("PROCESS_SELF_MESSAGES", "true"),
|
"PROCESS_SELF_MESSAGES": get_from_redis("PROCESS_SELF_MESSAGES", "true"),
|
||||||
|
"TRANSCRIPTION_LANGUAGE": get_from_redis("TRANSCRIPTION_LANGUAGE", "pt"),
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"Erro ao carregar configurações do Redis: {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)
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
else:
|
else:
|
||||||
st.info("Ainda não há dados de processamento disponíveis.")
|
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:
|
except Exception as e:
|
||||||
st.error(f"Erro ao carregar estatísticas: {e}")
|
st.error(f"Erro ao carregar estatísticas: {e}")
|
||||||
|
|
||||||
@@ -317,16 +342,269 @@ def manage_blocks():
|
|||||||
else:
|
else:
|
||||||
st.info("Nenhum usuário bloqueado.")
|
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():
|
def manage_settings():
|
||||||
st.title("⚙️ Configurações")
|
st.title("⚙️ Configurações")
|
||||||
|
|
||||||
|
# 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")
|
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")
|
# 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.text_input(
|
||||||
st.selectbox("Processar Mensagens Próprias", options=["true", "false"], index=["true", "false"].index(st.session_state.settings["PROCESS_SELF_MESSAGES"]), key="process_self_messages")
|
"Mensagem de Serviço no Rodapé",
|
||||||
if st.button("Salvar Configurações"):
|
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()
|
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:
|
if "authenticated" not in st.session_state:
|
||||||
st.session_state.authenticated = False
|
st.session_state.authenticated = False
|
||||||
|
|
||||||
|
|||||||
119
readme.md
119
readme.md
@@ -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/))
|
- Docker e Docker Compose instalados ([Instruções](https://docs.docker.com/get-docker/))
|
||||||
- Uma conta Evolution API com chave válida
|
- 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))
|
- 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**
|
## 🚀 **Instalação e Configuração**
|
||||||
|
|
||||||
### 🐳 Docker Compose
|
### 🐳 Docker Compose
|
||||||
1. Clone o repositório:
|
1. Clone o repositório:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/seu-usuario/transcrevezap.git
|
git clone https://github.com/seu-usuario/transcrevezap.git
|
||||||
cd transcrevezap
|
cd transcrevezap
|
||||||
```
|
```
|
||||||
2. Configure o arquivo docker-compose.yaml:
|
2. Configure o arquivo docker-compose.yaml:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
tcaudio:
|
tcaudio:
|
||||||
@@ -41,6 +41,9 @@ Antes de começar, certifique-se de ter os seguintes requisitos:
|
|||||||
environment:
|
environment:
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6380
|
- REDIS_PORT=6380
|
||||||
|
- API_DOMAIN=seu-ip
|
||||||
|
- DEBUG_MODE=false
|
||||||
|
- LOG_LEVEL=INFO
|
||||||
- MANAGER_USER=admin
|
- MANAGER_USER=admin
|
||||||
- MANAGER_PASSWORD=sua_senha_aqui
|
- MANAGER_PASSWORD=sua_senha_aqui
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -54,7 +57,7 @@ Antes de começar, certifique-se de ter os seguintes requisitos:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
redis_data:
|
redis_data:
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Inicie os serviços:
|
3. Inicie os serviços:
|
||||||
```bash
|
```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.
|
Faça login com as credenciais definidas em MANAGER_USER e MANAGER_PASSWORD.
|
||||||
Na seção "Configurações", defina:
|
Na seção "Configurações", defina:
|
||||||
|
|
||||||
GROQ_API_KEY: Sua chave da API GROQ
|
1. GROQ_API_KEY: Sua chave da API GROQ
|
||||||
BUSINESS_MESSAGE: Mensagem de rodapé após transcrição
|
2. BUSINESS_MESSAGE: Mensagem de rodapé após transcrição
|
||||||
PROCESS_GROUP_MESSAGES: Habilitar processamento de mensagens em grupos
|
3. PROCESS_GROUP_MESSAGES: Habilitar processamento de mensagens em grupos
|
||||||
PROCESS_SELF_MESSAGES: Habilitar processamento de mensagens próprias
|
4. PROCESS_SELF_MESSAGES: Habilitar processamento de mensagens próprias
|
||||||
|
|
||||||
|
|
||||||
## 🔧 Uso
|
## 🔧 Uso
|
||||||
Endpoint para Webhook da Evolution API
|
Endpoint para Webhook da Evolution API
|
||||||
@@ -125,7 +127,8 @@ uvicorn main:app --host 0.0.0.0 --port 8005
|
|||||||
```bash
|
```bash
|
||||||
http://127.0.0.1:8005/transcreve-audios
|
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
|
### 🌟 Docker Swarm com Traefik
|
||||||
```yaml
|
```yaml
|
||||||
@@ -135,7 +138,7 @@ services:
|
|||||||
tcaudio:
|
tcaudio:
|
||||||
image: impacteai/transcrevezap:latest
|
image: impacteai/transcrevezap:latest
|
||||||
networks:
|
networks:
|
||||||
- transcrevezap_network
|
- sua_rede_externa # Substitua pelo nome da sua rede externa
|
||||||
ports:
|
ports:
|
||||||
- 8005:8005 # Porta para FastAPI
|
- 8005:8005 # Porta para FastAPI
|
||||||
- 8501:8501 # Porta para Streamlit
|
- 8501:8501 # Porta para Streamlit
|
||||||
@@ -144,12 +147,13 @@ services:
|
|||||||
- UVICORN_HOST=0.0.0.0
|
- UVICORN_HOST=0.0.0.0
|
||||||
- UVICORN_RELOAD=true
|
- UVICORN_RELOAD=true
|
||||||
- UVICORN_WORKERS=1
|
- UVICORN_WORKERS=1
|
||||||
|
- API_DOMAIN=seu.dominio.com #coloque seu subdominio da API apontado aqui
|
||||||
- DEBUG_MODE=false
|
- DEBUG_MODE=false
|
||||||
- LOG_LEVEL=INFO
|
- LOG_LEVEL=INFO
|
||||||
- MANAGER_USER=seu_usuario_admin
|
- MANAGER_USER=seu_usuario_admin # Defina Usuário do Manager
|
||||||
- MANAGER_PASSWORD=sua_senha_segura
|
- MANAGER_PASSWORD=sua_senha_segura # Defina Senha do Manager
|
||||||
- REDIS_HOST=redis-transcrevezap
|
- REDIS_HOST=redis-transcrevezap
|
||||||
- REDIS_PORT=6380
|
- REDIS_PORT=6380 # Porta personalizada para o Redis do TranscreveZAP
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-transcrevezap
|
- redis-transcrevezap
|
||||||
deploy:
|
deploy:
|
||||||
@@ -160,7 +164,7 @@ services:
|
|||||||
- node.role == manager
|
- node.role == manager
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- 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.entrypoints=websecure
|
||||||
- traefik.http.routers.tcaudio.tls.certresolver=letsencryptresolver
|
- traefik.http.routers.tcaudio.tls.certresolver=letsencryptresolver
|
||||||
- traefik.http.services.tcaudio.loadbalancer.server.port=8005
|
- traefik.http.services.tcaudio.loadbalancer.server.port=8005
|
||||||
@@ -169,7 +173,7 @@ services:
|
|||||||
- traefik.http.middlewares.traefik-compress.compress=true
|
- traefik.http.middlewares.traefik-compress.compress=true
|
||||||
- traefik.http.routers.tcaudio.middlewares=traefik-compress
|
- traefik.http.routers.tcaudio.middlewares=traefik-compress
|
||||||
# Configuração do Streamlit
|
# 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.entrypoints=websecure
|
||||||
- traefik.http.routers.tcaudio-manager.tls.certresolver=letsencryptresolver
|
- traefik.http.routers.tcaudio-manager.tls.certresolver=letsencryptresolver
|
||||||
- traefik.http.services.tcaudio-manager.loadbalancer.server.port=8501
|
- traefik.http.services.tcaudio-manager.loadbalancer.server.port=8501
|
||||||
@@ -182,10 +186,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis_transcrevezap_data:/data
|
- redis_transcrevezap_data:/data
|
||||||
networks:
|
networks:
|
||||||
- transcrevezap_network
|
- sua_rede_externa # Substitua pelo nome da sua rede externa
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
transcrevezap_network:
|
sua_rede_externa: # Substitua pelo nome da sua rede externa
|
||||||
external: true
|
external: true
|
||||||
name: sua_rede_externa # Substitua pelo nome da sua rede externa
|
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:
|
Para usar com Traefik, certifique-se de:
|
||||||
1. Ter o Traefik configurado em seu ambiente Docker Swarm
|
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
|
3. Ajustar as labels do Traefik conforme seu ambiente
|
||||||
4. Verificar se a rede externa existe no Docker Swarm
|
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**
|
## 📝 **Notas Importantes**
|
||||||
- A GROQ_API_KEY deve começar com 'gsk_'
|
- 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
|
- Em produção, recomenda-se DEBUG_MODE=false
|
||||||
- Configure LOG_LEVEL=DEBUG apenas para troubleshooting
|
- 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**
|
## 🔍 **Troubleshooting**
|
||||||
Se encontrar problemas:
|
Se encontrar problemas:
|
||||||
1. Verifique se todas as variáveis obrigatórias estão configuradas
|
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
|
3. Verifique os logs do container
|
||||||
4. Certifique-se que as APIs estão acessíveis
|
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**
|
## 📄 **Licença**
|
||||||
Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
|
Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
|
||||||
|
|
||||||
|
|||||||
180
services.py
180
services.py
@@ -31,32 +31,114 @@ async def convert_base64_to_file(base64_data):
|
|||||||
})
|
})
|
||||||
raise
|
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):
|
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", {
|
storage.add_log("DEBUG", "Iniciando processo de resumo", {
|
||||||
"text_length": len(text)
|
"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"
|
url_completions = "https://api.groq.com/openai/v1/chat/completions"
|
||||||
|
groq_key = await get_groq_key()
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {settings.GROQ_API_KEY}",
|
"Authorization": f"Bearer {groq_key}",
|
||||||
"Content-Type": "application/json",
|
"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 = {
|
json_data = {
|
||||||
"messages": [{
|
"messages": [{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": f"""
|
"content": f"{base_prompt}\n\nTexto para resumir: {text}",
|
||||||
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}""",
|
|
||||||
}],
|
}],
|
||||||
"model": "llama-3.3-70b-versatile",
|
"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"]
|
summary_text = summary_result["choices"][0]["message"]["content"]
|
||||||
storage.add_log("INFO", "Resumo gerado com sucesso", {
|
storage.add_log("INFO", "Resumo gerado com sucesso", {
|
||||||
"original_length": len(text),
|
"original_length": len(text),
|
||||||
"summary_length": len(summary_text)
|
"summary_length": len(summary_text),
|
||||||
|
"language": language
|
||||||
})
|
})
|
||||||
return summary_text
|
return summary_text
|
||||||
else:
|
else:
|
||||||
@@ -88,10 +171,27 @@ async def summarize_text_if_needed(text):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
async def transcribe_audio(audio_source, apikey=None):
|
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")
|
storage.add_log("INFO", "Iniciando processo de transcrição")
|
||||||
url = "https://api.groq.com/openai/v1/audio/transcriptions"
|
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:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
@@ -123,26 +223,18 @@ async def transcribe_audio(audio_source, apikey=None):
|
|||||||
data = aiohttp.FormData()
|
data = aiohttp.FormData()
|
||||||
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
|
data.add_field('file', open(audio_source, 'rb'), filename='audio.mp3')
|
||||||
data.add_field('model', 'whisper-large-v3')
|
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")
|
storage.add_log("DEBUG", "Enviando áudio para transcrição")
|
||||||
async with session.post(url, headers=groq_headers, data=data) as response:
|
async with session.post(url, headers=groq_headers, data=data) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
result = await response.json()
|
result = await response.json()
|
||||||
message = result.get("text", "")
|
transcription = result.get("text", "")
|
||||||
storage.add_log("INFO", "Transcrição concluída com sucesso", {
|
storage.add_log("INFO", "Transcrição concluída com sucesso", {
|
||||||
"text_length": len(message)
|
"text_length": len(transcription)
|
||||||
})
|
})
|
||||||
|
|
||||||
is_summary = False
|
return transcription, 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
|
|
||||||
else:
|
else:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
storage.add_log("ERROR", "Erro na transcrição", {
|
storage.add_log("ERROR", "Erro na transcrição", {
|
||||||
@@ -161,7 +253,6 @@ async def transcribe_audio(audio_source, apikey=None):
|
|||||||
# Limpar arquivos temporários
|
# Limpar arquivos temporários
|
||||||
if isinstance(audio_source, str) and os.path.exists(audio_source):
|
if isinstance(audio_source, str) and os.path.exists(audio_source):
|
||||||
os.unlink(audio_source)
|
os.unlink(audio_source)
|
||||||
|
|
||||||
async def send_message_to_whatsapp(server_url, instance, apikey, message, remote_jid, message_id):
|
async def send_message_to_whatsapp(server_url, instance, apikey, message, remote_jid, message_id):
|
||||||
"""Envia mensagem via WhatsApp"""
|
"""Envia mensagem via WhatsApp"""
|
||||||
storage.add_log("DEBUG", "Preparando envio de mensagem", {
|
storage.add_log("DEBUG", "Preparando envio de mensagem", {
|
||||||
@@ -265,3 +356,34 @@ async def get_audio_base64(server_url, instance, apikey, message_id):
|
|||||||
"message_id": message_id
|
"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)
|
||||||
21
start.sh
21
start.sh
@@ -1,15 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/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() {
|
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 GROQ_API_KEY "sua_api_key_aqui" NX
|
||||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET BUSINESS_MESSAGE "*Impacte AI* Premium Services"
|
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"
|
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"
|
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET PROCESS_SELF_MESSAGES "true" NX
|
||||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT SET DEBUG_MODE "false"
|
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
|
initialize_redis_config
|
||||||
|
|
||||||
# Iniciar o FastAPI em background
|
# Iniciar o FastAPI em background
|
||||||
|
|||||||
45
storage.py
45
storage.py
@@ -169,3 +169,48 @@ class StorageHandler:
|
|||||||
self.redis.delete(key)
|
self.redis.delete(key)
|
||||||
except Exception as e:
|
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))
|
||||||
Reference in New Issue
Block a user