Spaces:
Sleeping
Sleeping
Guilherme Favaron
Fix bullet point formatting for training opportunities and information capture assessment fields
4c67ffd | import gradio as gr | |
| import json | |
| import os | |
| from openai import OpenAI | |
| # Dicionário para tradução dos campos de avaliação para português | |
| traducoes = { | |
| "call_strengths": "Pontos Fortes da Chamada", | |
| "general_assessment": "Avaliação Geral", | |
| "areas_of_improvement": "Áreas de Melhoria", | |
| "training_opportunities": "Oportunidades de Treinamento", | |
| "process_adherence_score": "Pontuação de Aderência ao Processo", | |
| "development_recommendations": "Recomendações de Desenvolvimento", | |
| "process_adherence_scorecard": "Scorecard de Aderência ao Processo", | |
| "information_capture_assessment": "Avaliação de Captura de Informações", | |
| "next_steps_and_meeting_confirmation": "Próximos Passos e Confirmação de Reunião", | |
| # Campos do scorecard | |
| "name": "Nome", | |
| "score": "Pontuação", | |
| "evaluation": "Avaliação" | |
| } | |
| # Configurar cliente OpenAI | |
| client = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) | |
| def traduzir_json_para_portugues(json_content): | |
| """Traduz o conteúdo JSON para português brasileiro usando OpenAI""" | |
| try: | |
| prompt = f"""Traduza o seguinte JSON de avaliação de chamada para português brasileiro. | |
| IMPORTANTE: | |
| 1. Mantenha a estrutura JSON EXATA e válida | |
| 2. Traduza APENAS os valores de texto (strings), não as chaves | |
| 3. Preserve quebras de linha (\n) nos valores | |
| 4. Retorne um JSON válido e bem formatado | |
| 5. Não adicione explicações, apenas o JSON | |
| JSON para traduzir: | |
| {json_content}""" | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[ | |
| { | |
| "role": "user", | |
| "content": prompt | |
| } | |
| ], | |
| temperature=0.1, | |
| max_tokens=4000 | |
| ) | |
| # Limpar a resposta e garantir que é um JSON válido | |
| translated_content = response.choices[0].message.content.strip() | |
| # Remover possíveis marcadores de código se existirem | |
| if translated_content.startswith('```json'): | |
| translated_content = translated_content[7:] | |
| if translated_content.endswith('```'): | |
| translated_content = translated_content[:-3] | |
| translated_content = translated_content.strip() | |
| # Validar se é um JSON válido | |
| import json | |
| json.loads(translated_content) # Isso vai gerar erro se não for JSON válido | |
| return translated_content | |
| except Exception as e: | |
| return f"Erro na tradução: {str(e)}" | |
| def processar_transcricao(json_data): | |
| """Processa o JSON de transcrição para exibição estilizada - apenas segmentos formatados""" | |
| try: | |
| data = json.loads(json_data) | |
| # Informações gerais | |
| html = "<div class='container'>" | |
| html += f"<h2>Transcrição da Chamada</h2>" | |
| html += f"<p><strong>Idioma:</strong> {data.get('language', 'Não especificado')}</p>" | |
| # Segmentos da conversa | |
| html += "<div class='segments'>" | |
| html += "<h3>Segmentos da Conversa</h3>" | |
| html += "<table>" | |
| html += "<tr><th>Início</th><th>Fim</th><th>Falante</th><th>Texto</th></tr>" | |
| for segment in data.get('segments', []): | |
| speaker_class = 'speaker-0' if segment.get('speaker') == 'speaker_0' else 'speaker-1' | |
| html += f"<tr class='{speaker_class}'>" | |
| html += f"<td>{segment.get('start')}</td>" | |
| html += f"<td>{segment.get('end')}</td>" | |
| html += f"<td>{segment.get('speaker')}</td>" | |
| html += f"<td>{segment.get('text')}</td>" | |
| html += "</tr>" | |
| html += "</table>" | |
| html += "</div>" | |
| html += "</div>" | |
| return html | |
| except Exception as e: | |
| return f"<div class='error'>Erro ao processar a transcrição: {str(e)}</div>" | |
| def processar_avaliacao(json_data): | |
| """Processa o JSON de avaliação para exibição estilizada em português""" | |
| try: | |
| data = json.loads(json_data) | |
| html = "<div class='container'>" | |
| html += "<h2>Avaliação da Chamada</h2>" | |
| # Pontuação geral | |
| score = data.get('process_adherence_score', 'N/A') | |
| html += f"<div class='score-box'><h3>{traducoes['process_adherence_score']}</h3><div class='score'>{score}/5</div></div>" | |
| # Pontos fortes | |
| call_strengths = data.get('call_strengths', '').replace('\n', '<br>') | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['call_strengths']}</h3>" | |
| html += f"<div class='content'>{call_strengths}</div>" | |
| html += "</div>" | |
| # Avaliação geral | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['general_assessment']}</h3>" | |
| html += f"<div class='content'>{data.get('general_assessment', '')}</div>" | |
| html += "</div>" | |
| # Áreas de melhoria | |
| areas_improvement = data.get('areas_of_improvement', '').replace('\n', '<br>') | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['areas_of_improvement']}</h3>" | |
| html += f"<div class='content'>{areas_improvement}</div>" | |
| html += "</div>" | |
| # Oportunidades de treinamento | |
| training_opportunities = data.get('training_opportunities', '').replace('\n', '<br>') | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['training_opportunities']}</h3>" | |
| html += f"<div class='content'>{training_opportunities}</div>" | |
| html += "</div>" | |
| # Recomendações de desenvolvimento | |
| dev_recommendations = data.get('development_recommendations', '').replace('\n', '<br>') | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['development_recommendations']}</h3>" | |
| html += f"<div class='content'>{dev_recommendations}</div>" | |
| html += "</div>" | |
| # Scorecard | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['process_adherence_scorecard']}</h3>" | |
| html += "<table class='scorecard'>" | |
| html += f"<tr><th>{traducoes['name']}</th><th>{traducoes['score']}</th><th>{traducoes['evaluation']}</th></tr>" | |
| for item in data.get('process_adherence_scorecard', []): | |
| score_class = f"score-{item.get('score')}" | |
| html += f"<tr class='{score_class}'>" | |
| html += f"<td>{item.get('name')}</td>" | |
| html += f"<td class='score-cell'>{item.get('score')}</td>" | |
| html += f"<td>{item.get('evaluation')}</td>" | |
| html += "</tr>" | |
| html += "</table>" | |
| html += "</div>" | |
| # Avaliação de captura de informações | |
| information_capture = data.get('information_capture_assessment', '').replace('\n', '<br>') | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['information_capture_assessment']}</h3>" | |
| html += f"<div class='content'>{information_capture}</div>" | |
| html += "</div>" | |
| # Próximos passos | |
| html += f"<div class='section'>" | |
| html += f"<h3>{traducoes['next_steps_and_meeting_confirmation']}</h3>" | |
| html += f"<div class='content'>{data.get('next_steps_and_meeting_confirmation', '')}</div>" | |
| html += "</div>" | |
| html += "</div>" | |
| return html | |
| except Exception as e: | |
| return f"<div class='error'>Erro ao processar a avaliação: {str(e)}</div>" | |
| def processar_arquivos(arquivo_transcricao=None, arquivo_avaliacao=None, texto_transcricao="", texto_avaliacao="", traduzir=False): | |
| """Processa os arquivos de transcrição e avaliação ou texto colado diretamente""" | |
| resultado_html = "" | |
| css = """ | |
| <style> | |
| .container { | |
| font-family: Arial, sans-serif; | |
| margin: 20px; | |
| padding: 20px; | |
| border-radius: 10px; | |
| background-color: #f9f9f9; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
| } | |
| h2 { | |
| color: #2c3e50; | |
| border-bottom: 2px solid #3498db; | |
| padding-bottom: 10px; | |
| } | |
| h3 { | |
| color: #2980b9; | |
| margin-top: 20px; | |
| } | |
| .section { | |
| margin: 20px 0; | |
| padding: 15px; | |
| background-color: white; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| } | |
| .content { | |
| line-height: 1.6; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 15px 0; | |
| } | |
| th { | |
| background-color: #3498db; | |
| color: white; | |
| padding: 10px; | |
| text-align: left; | |
| } | |
| td { | |
| padding: 8px 10px; | |
| border-bottom: 1px solid #ddd; | |
| } | |
| .speaker-0 { | |
| background-color: #e8f4f8; | |
| } | |
| .speaker-1 { | |
| background-color: #f0f0f0; | |
| } | |
| .score-box { | |
| display: flex; | |
| align-items: center; | |
| margin: 20px 0; | |
| } | |
| .score { | |
| margin-left: 20px; | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: white; | |
| background-color: #3498db; | |
| padding: 10px 15px; | |
| border-radius: 50%; | |
| } | |
| .score-cell { | |
| font-weight: bold; | |
| text-align: center; | |
| } | |
| .score-1 { background-color: #ffcccc; } | |
| .score-2 { background-color: #ffe0b3; } | |
| .score-3 { background-color: #ffffcc; } | |
| .score-4 { background-color: #ccffcc; } | |
| .score-5 { background-color: #b3ffb3; } | |
| .raw-text { | |
| background-color: #f5f5f5; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin: 15px 0; | |
| white-space: pre-wrap; | |
| } | |
| .error { | |
| color: red; | |
| padding: 10px; | |
| background-color: #ffeeee; | |
| border-radius: 5px; | |
| } | |
| </style> | |
| """ | |
| # Processar transcrição (arquivo ou texto) | |
| conteudo_transcricao = None | |
| if arquivo_transcricao is not None: | |
| try: | |
| conteudo_transcricao = arquivo_transcricao.decode('utf-8') | |
| except Exception as e: | |
| resultado_html += f"<div class='error'>Erro ao ler o arquivo de transcrição: {str(e)}</div>" | |
| elif texto_transcricao.strip(): | |
| conteudo_transcricao = texto_transcricao.strip() | |
| if conteudo_transcricao: | |
| resultado_html += processar_transcricao(conteudo_transcricao) | |
| # Processar avaliação (arquivo ou texto) | |
| conteudo_avaliacao = None | |
| if arquivo_avaliacao is not None: | |
| try: | |
| conteudo_avaliacao = arquivo_avaliacao.decode('utf-8') | |
| except Exception as e: | |
| resultado_html += f"<div class='error'>Erro ao ler o arquivo de avaliação: {str(e)}</div>" | |
| elif texto_avaliacao.strip(): | |
| conteudo_avaliacao = texto_avaliacao.strip() | |
| if conteudo_avaliacao: | |
| # Se tradução foi solicitada, traduzir primeiro | |
| if traduzir: | |
| try: | |
| conteudo_traduzido = traduzir_json_para_portugues(conteudo_avaliacao) | |
| # Processar apenas o JSON traduzido formatado (sem mostrar o JSON bruto) | |
| resultado_html += processar_avaliacao(conteudo_traduzido) | |
| except Exception as e: | |
| resultado_html += f"<div class='error'>Erro na tradução: {str(e)}</div>" | |
| resultado_html += processar_avaliacao(conteudo_avaliacao) | |
| else: | |
| resultado_html += processar_avaliacao(conteudo_avaliacao) | |
| # Se nenhum conteúdo foi fornecido | |
| if not resultado_html: | |
| resultado_html = "<div class='container'><h2>Por favor, faça upload dos arquivos JSON ou cole o conteúdo nos campos de texto</h2></div>" | |
| # Retornar resultado HTML e campos limpos | |
| return css + resultado_html, "", "" | |
| # Interface Gradio | |
| with gr.Blocks(title="Beautiful Jayson - Estilizador de JSONs de Chamadas") as demo: | |
| gr.Markdown("# Beautiful Jayson 🎨") | |
| gr.Markdown("### Estilizador de JSONs de Chamadas e Avaliações") | |
| gr.Markdown("**Você pode fazer upload de arquivos JSON ou colar o conteúdo diretamente nos campos de texto abaixo.**") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("#### 📁 Upload de Arquivos") | |
| arquivo_transcricao = gr.File(label="Upload do JSON de Transcrição") | |
| arquivo_avaliacao = gr.File(label="Upload do JSON de Avaliação") | |
| gr.Markdown("#### 📝 Ou Cole o JSON Diretamente") | |
| texto_transcricao = gr.Textbox( | |
| label="JSON de Transcrição", | |
| placeholder="Cole aqui o conteúdo JSON da transcrição...", | |
| lines=8, | |
| max_lines=15 | |
| ) | |
| texto_avaliacao = gr.Textbox( | |
| label="JSON de Avaliação", | |
| placeholder="Cole aqui o conteúdo JSON da avaliação...", | |
| lines=8, | |
| max_lines=15 | |
| ) | |
| gr.Markdown("#### 🌐 Opções de Tradução") | |
| traduzir_checkbox = gr.Checkbox( | |
| label="Traduzir JSON de Avaliação para Português Brasileiro", | |
| value=False, | |
| info="Usa OpenAI GPT-4o-mini para traduzir o conteúdo da avaliação" | |
| ) | |
| btn = gr.Button("🎨 Processar e Estilizar", variant="primary") | |
| with gr.Column(): | |
| saida = gr.HTML(label="Visualização Estilizada") | |
| btn.click(fn=processar_arquivos, | |
| inputs=[arquivo_transcricao, arquivo_avaliacao, texto_transcricao, texto_avaliacao, traduzir_checkbox], | |
| outputs=[saida, texto_transcricao, texto_avaliacao]) | |
| # Exemplos removidos para proteger dados confidenciais | |
| # Iniciar a aplicação | |
| if __name__ == "__main__": | |
| demo.launch(share=True, server_name="0.0.0.0", server_port=None) |