@@ -170,14 +170,34 @@ def call_openai(
170170 "Responda de forma breve e direta. "
171171 "Não mencione o nome do usuário a menos que a pergunta seja sobre o nome/identidade. "
172172 f"Contexto principal: { domain } . "
173- "Se a pergunta for ambígua (ex.: 'célula ', 'rede ', 'banco '), "
173+ "Se a pergunta for ambígua (ex.: 'deploy ', 'pipeline ', 'modelo '), "
174174 f"RESPONDA APENAS no sentido de { domain } e NÃO mencione outros significados."
175175 )
176+
176177 examples = [
177- {"role" : "user" , "content" : "O que é uma célula? (no contexto de saúde)" },
178- {"role" : "assistant" , "content" : "Uma célula é a menor unidade estrutural e funcional dos seres vivos." },
179- {"role" : "user" , "content" : "O que é uma célula? (no contexto de engenharia de software)" },
180- {"role" : "assistant" , "content" : "Em computação, célula costuma se referir a uma unidade em uma tabela/planilha ou a um componente isolado de execução." },
178+ # Engenharia de Software
179+ {"role" : "user" , "content" : "O que é um deploy? (no contexto de engenharia de software)" },
180+ {"role" : "assistant" ,
181+ "content" : "Deploy é o processo de disponibilizar uma nova versão de software em produção." },
182+ {"role" : "user" , "content" : "O que é um pipeline? (no contexto de engenharia de software)" },
183+ {"role" : "assistant" ,
184+ "content" : "Um pipeline é uma sequência automatizada de etapas para construir, testar e implantar código." },
185+
186+ # Financeiro
187+ {"role" : "user" , "content" : "O que é um deploy? (no contexto de finanças corporativas)" },
188+ {"role" : "assistant" ,
189+ "content" : "No contexto financeiro, deploy pode se referir à liberação de um novo processo, sistema ou investimento para uso interno." },
190+ {"role" : "user" , "content" : "O que é um pipeline? (no contexto de vendas e finanças)" },
191+ {"role" : "assistant" ,
192+ "content" : "Pipeline é a lista de oportunidades ou previsões de receita que ainda estão em andamento." },
193+
194+ # Bilíngue — cache semântico cross-language
195+ {"role" : "user" , "content" : "Explique o que é aprendizado de máquina." },
196+ {"role" : "assistant" ,
197+ "content" : "Aprendizado de máquina é uma área da IA que permite que sistemas aprendam padrões a partir de dados sem programação explícita." },
198+ {"role" : "user" , "content" : "What is machine learning?" },
199+ {"role" : "assistant" ,
200+ "content" : "Machine learning is a branch of AI that enables systems to learn patterns from data and make predictions or decisions without being explicitly programmed." },
181201 ]
182202 msgs = [{"role" : "system" , "content" : system_ctx }, * examples , {"role" : "user" , "content" : prompt }]
183203 resp = openai_client .chat .completions .create (
@@ -434,6 +454,7 @@ def calc_savings(tokens_est: int, price_in: float, price_out: float, frac_in: fl
434454:root {
435455 --redis-red:#D82C20; --ink:#0b1220; --soft:#475569; --muted:#64748b;
436456 --line:#e5e7eb; --bg:#f6f7f9; --white:#ffffff; --radius:14px;
457+ --success:#10b981; --warning:#f59e0b;
437458}
438459
439460* { font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; }
@@ -475,42 +496,65 @@ def calc_savings(tokens_est: int, price_in: float, price_out: float, frac_in: fl
475496}
476497
477498/* KPIs */
478- .kpi-row { display:flex; gap:12px; margin: 0 16px 10px ; }
499+ .kpi-row { display:flex; gap:12px; margin: 0 16px 16px; flex-wrap: wrap ; }
479500.kpi {
480- flex:1; background: var(--white); border:1px solid var(--line); border-radius:12px;
481- padding:12px 14px;
501+ flex:1; min-width: 140px; background: var(--white); border:1px solid var(--line); border-radius:12px;
502+ padding:14px 16px; transition: transform .2s ease, box-shadow .2s ease ;
482503}
504+ .kpi:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,.08); }
483505.kpi .kpi-num {
484506 font-family: 'Space Grotesk', Inter, sans-serif;
485- font-size:22px; font-weight:700; color:var(--ink); line-height:1.1;
507+ font-size:24px; font-weight:700; color:var(--ink); line-height:1.1;
508+ }
509+ .kpi .kpi-label {
510+ font-size:11px; color:var(--muted); margin-top:6px;
511+ text-transform:uppercase; letter-spacing:.8px; font-weight:600;
486512}
487- .kpi .kpi-label { font-size:12px; color:var(--muted ); margin-top:4px; text-transform:uppercase; letter-spacing:.6px ; }
488- .kpi-accent { border- color: var(--redis-red); }
513+ .kpi-accent { border- color: var(--redis-red ); border-width: 2px ; }
514+ .kpi-accent .kpi-num { color: var(--redis-red); }
489515
490516/* Cenários lado a lado */
491- .scenarios { display:grid; grid-template-columns: 1fr 1fr; gap: 14px ; margin: 10px 16px; }
517+ .scenarios { display:grid; grid-template-columns: 1fr 1fr; gap: 16px ; margin: 10px 16px; }
492518@media (max-width: 1024px) { .scenarios { grid-template-columns: 1fr; } }
493519
494520.card {
495- background: var(--white); border:1px solid var(--line); border-radius: var(--radius);
496- padding:12px ;
521+ background: var(--white); border:2px solid var(--line); border-radius: var(--radius);
522+ padding:16px; transition: border-color .2s ease ;
497523}
524+ .card:hover { border-color: var(--redis-red); }
498525.card .card-title {
499526 font-family: 'Space Grotesk', Inter, sans-serif;
500- font-size:16px; font-weight:700; color:var(--ink); margin-bottom:8px;
527+ font-size:18px; font-weight:700; color:var(--ink); margin-bottom:12px;
528+ display: flex; align-items: center; gap: 8px;
501529}
502530
503- /* Flush */
504- .flush-wrap { margin-top:8px; border-top:1px dashed var(--line); padding-top:10px; }
531+ /* Source badges */
532+ .source-badge {
533+ display: inline-block; padding: 4px 10px; border-radius: 6px;
534+ font-size: 11px; font-weight: 700; text-transform: uppercase;
535+ letter-spacing: .5px;
536+ }
537+ .source-cache { background: #d1fae5; color: #065f46; }
538+ .source-llm { background: #fef3c7; color: #92400e; }
505539
506540/* History */
507541.dataframe { background: var(--white); border:1px solid var(--line); border-radius: var(--radius); }
508- .dataframe thead tr th { font-size:12px; }
542+ .dataframe thead tr th { font-size:12px; font-weight:600; }
509543.dataframe tbody tr td { font-size:12px; }
510544
511545/* Buttons */
512546button.primary, .gr-button-primary {
513547 background: var(--redis-red) !important; border-color: var(--redis-red) !important; color:#fff !important;
548+ font-weight: 600 !important; transition: all .2s ease !important;
549+ }
550+ button.primary:hover, .gr-button-primary:hover {
551+ background: #c02518 !important; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(216,44,32,.3) !important;
552+ }
553+
554+ /* Secondary buttons */
555+ .secondary-btn {
556+ background: var(--white) !important; border: 1px solid var(--line) !important;
557+ color: var(--soft) !important; font-weight: 600 !important;
514558}
515559
516560/* --- HERO (título + subtítulo) --- */
@@ -605,51 +649,63 @@ def calc_savings(tokens_est: int, price_in: float, price_out: float, frac_in: fl
605649 with gr .Row (elem_classes = ["scenarios" ]):
606650 # --- Cenário A ---
607651 with gr .Column (elem_classes = ["card" ]):
608- gr .Markdown ("<div class='card-title'>Cenário A </div>" )
652+ gr .Markdown ("<div class='card-title'>Chat 🅰️ </div>" )
609653 with gr .Row ():
610654 a_company = gr .Textbox (label = "Company" , value = "RedisLabs" )
611- a_bu = gr .Textbox (label = "Business Unit" , value = "Saude-Medicos " )
655+ a_bu = gr .Textbox (label = "Business Unit" , value = "Engenharia-de-Software " )
612656 a_person = gr .Textbox (label = "Person" , value = "Gabriel" )
613657 a_prompt = gr .Textbox (label = "Pergunta" , placeholder = "Pergunte algo…" , lines = 3 )
614658 a_btn = gr .Button ("Perguntar (A)" , variant = "primary" )
615- a_answer = gr .Textbox (label = "Resposta" , lines = 6 )
659+ a_answer = gr .Textbox (label = "Resposta" , lines = 6 , interactive = False )
616660 with gr .Row ():
617- a_source = gr .Label (label = "Origem" )
661+ a_source = gr .HTML (label = "Origem" )
618662 a_latency = gr .Label (label = "Latência" )
619- a_debug = gr .Code (label = "Debug" )
620- gr .HTML ("<div class='flush-wrap'></div>" )
621- a_flush_btn = gr .Button ("🧹 Limpar Cache (Escopo A)" )
622- a_flush_status = gr .HTML ()
623- a_flush_debug = gr .Code ()
663+ with gr .Accordion ("🔍 Debug Info" , open = False ):
664+ a_debug = gr .Code (label = "Debug JSON" , language = "json" )
665+ with gr .Accordion ("🧹 Gerenciar Cache" , open = False ):
666+ a_flush_btn = gr .Button ("Limpar Cache (Escopo A)" , variant = "secondary" )
667+ a_flush_status = gr .HTML ()
668+ with gr .Accordion ("Debug do Flush" , open = False ):
669+ a_flush_debug = gr .Code (language = "json" )
624670
625671 # --- Cenário B ---
626672 with gr .Column (elem_classes = ["card" ]):
627- gr .Markdown ("<div class='card-title'>Cenário B </div>" )
673+ gr .Markdown ("<div class='card-title'>Chat 🅱️ </div>" )
628674 with gr .Row ():
629675 b_company = gr .Textbox (label = "Company" , value = "RedisLabs" )
630- b_bu = gr .Textbox (label = "Business Unit" , value = "Engenharia-de-Software " )
631- b_person = gr .Textbox (label = "Person" , value = "Janine " )
676+ b_bu = gr .Textbox (label = "Business Unit" , value = "Financeiro " )
677+ b_person = gr .Textbox (label = "Person" , value = "Diego " )
632678 b_prompt = gr .Textbox (label = "Pergunta" , placeholder = "Pergunte algo…" , lines = 3 )
633679 b_btn = gr .Button ("Perguntar (B)" , variant = "primary" )
634- b_answer = gr .Textbox (label = "Resposta" , lines = 6 )
680+ b_answer = gr .Textbox (label = "Resposta" , lines = 6 , interactive = False )
635681 with gr .Row ():
636- b_source = gr .Label (label = "Origem" )
682+ b_source = gr .HTML (label = "Origem" )
637683 b_latency = gr .Label (label = "Latência" )
638- b_debug = gr .Code (label = "Debug" )
639- gr .HTML ("<div class='flush-wrap'></div>" )
640- b_flush_btn = gr .Button ("🧹 Limpar Cache (Escopo B)" )
641- b_flush_status = gr .HTML ()
642- b_flush_debug = gr .Code ()
684+ with gr .Accordion ("🔍 Debug Info" , open = False ):
685+ b_debug = gr .Code (label = "Debug JSON" , language = "json" )
686+ with gr .Accordion ("🧹 Gerenciar Cache" , open = False ):
687+ b_flush_btn = gr .Button ("Limpar Cache (Escopo B)" , variant = "secondary" )
688+ b_flush_status = gr .HTML ()
689+ with gr .Accordion ("Debug do Flush" , open = False ):
690+ b_flush_debug = gr .Code (language = "json" )
691+
692+ # Copy A → B button
693+ with gr .Row ():
694+ gr .HTML ("<div style='flex:1'></div>" )
695+ copy_btn = gr .Button ("📋 Copiar A → B" , variant = "secondary" , size = "sm" )
696+ gr .HTML ("<div style='flex:1'></div>" )
643697
644698 # Flush ambos
645699 with gr .Group ():
646700 gr .Markdown ("### 🧹 Limpeza Combinada (A + B)" )
647- flush_both_btn = gr .Button ("🧹 Limpar Ambos (A+B)" )
648- flush_both_status = gr .HTML ()
649- flush_both_debug = gr .Code ()
701+ with gr .Accordion ("Opções Avançadas" , open = False ):
702+ flush_both_btn = gr .Button ("🧹 Limpar Ambos (A+B)" , variant = "secondary" )
703+ flush_both_status = gr .HTML ()
704+ with gr .Accordion ("Debug do Flush" , open = False ):
705+ flush_both_debug = gr .Code (language = "json" )
650706
651707 # Histórico
652- gr .Markdown ("### Histórico (últimos 50)" )
708+ gr .Markdown ("### 📊 Histórico de Consultas (últimos 50)" )
653709 history_table = gr .Dataframe (
654710 headers = ["Hora" , "Cenário" , "Company" , "BU" , "Person" , "Fonte" , "Latência" , "Tokens (est.)" , "Economia" , "Prompt" ],
655711 datatype = ["str" , "str" , "str" , "str" , "str" , "str" , "str" , "number" , "str" , "str" ],
@@ -732,9 +788,16 @@ def handle_submit(
732788 kpi_tok_html = f"<div class='kpi'><div class='kpi-num'>{ k ['tokens' ]} </div><div class='kpi-label'>Tokens</div></div>"
733789 kpi_usd_html = f"<div class='kpi kpi-accent'><div class='kpi-num'>{ k ['usd' ]} </div><div class='kpi-label'>Economia</div></div>"
734790
791+ # Create visual badge for source
792+ source_badge = (
793+ f"<span class='source-badge source-{ source } '>"
794+ f"{ '✓ CACHE HIT' if source == 'cache' else '⚡ LLM CALL' } "
795+ f"</span>"
796+ )
797+
735798 return (
736799 answer ,
737- json . dumps ({ "fonte" : source }, ensure_ascii = False ) ,
800+ source_badge ,
738801 debug_json ,
739802 f"{ latency } · { saved_str } " ,
740803 gr .update (value = kpi_hits_html ),
@@ -746,6 +809,7 @@ def handle_submit(
746809 state ,
747810 )
748811
812+ # Scenario A submit handlers
749813 a_btn .click (
750814 fn = handle_submit ,
751815 inputs = [
@@ -763,6 +827,25 @@ def handle_submit(
763827 ],
764828 )
765829
830+ # Enable Enter key submission for A
831+ a_prompt .submit (
832+ fn = handle_submit ,
833+ inputs = [
834+ gr .State ("A" ),
835+ a_company , a_bu , a_person , a_prompt ,
836+ isolation_global , threshold_global , exact_sem_global , ttl_global ,
837+ price_in , price_out , frac_in , currency ,
838+ st ,
839+ ],
840+ outputs = [
841+ a_answer , a_source , a_debug , a_latency ,
842+ kpi_hits , kpi_misses , kpi_rate , kpi_tokens , kpi_savings ,
843+ history_table ,
844+ st ,
845+ ],
846+ )
847+
848+ # Scenario B submit handlers
766849 b_btn .click (
767850 fn = handle_submit ,
768851 inputs = [
@@ -780,6 +863,34 @@ def handle_submit(
780863 ],
781864 )
782865
866+ # Enable Enter key submission for B
867+ b_prompt .submit (
868+ fn = handle_submit ,
869+ inputs = [
870+ gr .State ("B" ),
871+ b_company , b_bu , b_person , b_prompt ,
872+ isolation_global , threshold_global , exact_sem_global , ttl_global ,
873+ price_in , price_out , frac_in , currency ,
874+ st ,
875+ ],
876+ outputs = [
877+ b_answer , b_source , b_debug , b_latency ,
878+ kpi_hits , kpi_misses , kpi_rate , kpi_tokens , kpi_savings ,
879+ history_table ,
880+ st ,
881+ ],
882+ )
883+
884+ # Copy A → B functionality
885+ def copy_a_to_b (company , bu , person , prompt ):
886+ return company , bu , person , prompt
887+
888+ copy_btn .click (
889+ fn = copy_a_to_b ,
890+ inputs = [a_company , a_bu , a_person , a_prompt ],
891+ outputs = [b_company , b_bu , b_person , b_prompt ]
892+ )
893+
783894 a_flush_btn .click (
784895 fn = handle_flush_scope ,
785896 inputs = [a_company , a_bu , a_person , isolation_global ],
0 commit comments