Spaces:
Runtime error
Runtime error
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>LEX — Assistente Jurídico · MCP + HF</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&family=Playfair+Display:wght@700;900&display=swap" rel="stylesheet"> | |
| <style> | |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} | |
| :root{ | |
| --bg:#07080d; | |
| --bg2:#0d0f18; | |
| --panel:#111422; | |
| --panel2:#161928; | |
| --border:#1e2235; | |
| --border2:#252840; | |
| --gold:#c9a84c; | |
| --gold-dim:rgba(201,168,76,0.1); | |
| --gold-glow:rgba(201,168,76,0.2); | |
| --gold-soft:rgba(201,168,76,0.6); | |
| --cyan:#4ecdc4; | |
| --cyan-dim:rgba(78,205,196,0.08); | |
| --red:#e05c5c; | |
| --red-dim:rgba(224,92,92,0.08); | |
| --green:#5ce08a; | |
| --green-dim:rgba(92,224,138,0.08); | |
| --purple:#9b8fff; | |
| --text:#cdd0e0; | |
| --text2:#7a7f9a; | |
| --text3:#3e4260; | |
| --mono:'IBM Plex Mono',monospace; | |
| --serif:'Playfair Display',serif; | |
| --r:8px; | |
| } | |
| html{height:100%} | |
| body{ | |
| background:var(--bg); | |
| color:var(--text); | |
| font-family:var(--mono); | |
| font-size:13px; | |
| height:100%; | |
| overflow:hidden; | |
| display:flex; | |
| flex-direction:column; | |
| } | |
| /* ── SCAN LINES ── */ | |
| body::after{ | |
| content:''; | |
| position:fixed; | |
| inset:0; | |
| background:repeating-linear-gradient( | |
| 0deg, | |
| transparent, | |
| transparent 2px, | |
| rgba(0,0,0,0.08) 2px, | |
| rgba(0,0,0,0.08) 4px | |
| ); | |
| pointer-events:none; | |
| z-index:9999; | |
| } | |
| /* ── TOP BAR ── */ | |
| .topbar{ | |
| display:flex; | |
| align-items:center; | |
| justify-content:space-between; | |
| padding:0 20px; | |
| height:48px; | |
| background:var(--bg2); | |
| border-bottom:1px solid var(--border); | |
| flex-shrink:0; | |
| position:relative; | |
| z-index:10; | |
| } | |
| .topbar-left{display:flex;align-items:center;gap:14px} | |
| .logo-mark{ | |
| font-family:var(--serif); | |
| font-size:20px; | |
| font-weight:900; | |
| color:var(--gold); | |
| letter-spacing:0.05em; | |
| text-shadow:0 0 20px var(--gold-glow); | |
| line-height:1; | |
| } | |
| .logo-sub{ | |
| font-size:10px; | |
| color:var(--text3); | |
| letter-spacing:0.15em; | |
| text-transform:uppercase; | |
| border-left:1px solid var(--border2); | |
| padding-left:14px; | |
| } | |
| .topbar-status{display:flex;align-items:center;gap:20px} | |
| .status-pill{ | |
| display:flex;align-items:center;gap:6px; | |
| font-size:10px;color:var(--text2); | |
| letter-spacing:0.08em; | |
| } | |
| .status-dot{width:6px;height:6px;border-radius:50%} | |
| .dot-live{background:var(--green);box-shadow:0 0 6px var(--green)} | |
| .dot-hf{background:var(--gold);box-shadow:0 0 6px var(--gold-glow)} | |
| @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}} | |
| .dot-live,.dot-hf{animation:pulse 2.4s ease-in-out infinite} | |
| .topbar-right{display:flex;align-items:center;gap:10px} | |
| .kbd{ | |
| font-size:10px;color:var(--text3); | |
| border:1px solid var(--border2); | |
| border-radius:4px;padding:2px 6px; | |
| letter-spacing:0.05em; | |
| } | |
| /* ── LAYOUT ── */ | |
| .workspace{ | |
| display:flex; | |
| flex:1; | |
| overflow:hidden; | |
| } | |
| /* ── LEFT SIDEBAR ── */ | |
| .sidebar{ | |
| width:220px; | |
| flex-shrink:0; | |
| background:var(--bg2); | |
| border-right:1px solid var(--border); | |
| display:flex; | |
| flex-direction:column; | |
| overflow:hidden; | |
| } | |
| .sidebar-section{ | |
| padding:14px 16px 10px; | |
| border-bottom:1px solid var(--border); | |
| } | |
| .sidebar-label{ | |
| font-size:9px;letter-spacing:0.2em;text-transform:uppercase; | |
| color:var(--text3);margin-bottom:10px; | |
| } | |
| .tool-item{ | |
| display:flex;align-items:flex-start;gap:8px; | |
| padding:8px 10px;border-radius:6px; | |
| cursor:pointer;transition:background 0.15s; | |
| margin-bottom:2px; | |
| } | |
| .tool-item:hover,.tool-item.active{background:var(--panel)} | |
| .tool-icon{ | |
| font-size:14px;margin-top:1px;flex-shrink:0; | |
| width:18px;text-align:center; | |
| } | |
| .tool-info .tool-name{ | |
| font-size:11px;font-weight:600;color:var(--text); | |
| line-height:1.3; | |
| } | |
| .tool-info .tool-desc{ | |
| font-size:9px;color:var(--text3); | |
| line-height:1.4;margin-top:1px; | |
| } | |
| .tool-item[data-tool="search_legal_models"] .tool-icon{color:var(--gold)} | |
| .tool-item[data-tool="explore_legal_dataset"] .tool-icon{color:var(--cyan)} | |
| .tool-item[data-tool="analyze_legal_text"] .tool-icon{color:var(--purple)} | |
| .tool-item[data-tool="find_jurisprudence"] .tool-icon{color:var(--green)} | |
| .sidebar-footer{ | |
| padding:14px 16px;margin-top:auto; | |
| border-top:1px solid var(--border); | |
| font-size:9px;color:var(--text3); | |
| line-height:1.7; | |
| } | |
| .hf-badge{ | |
| display:inline-flex;align-items:center;gap:4px; | |
| background:var(--gold-dim);color:var(--gold-soft); | |
| border:1px solid rgba(201,168,76,0.2); | |
| border-radius:4px;padding:2px 6px; | |
| font-size:9px;margin-bottom:8px; | |
| } | |
| /* ── CENTER CHAT ── */ | |
| .chat-area{ | |
| flex:1; | |
| display:flex;flex-direction:column; | |
| overflow:hidden; | |
| min-width:0; | |
| } | |
| .chat-messages{ | |
| flex:1; | |
| overflow-y:auto; | |
| padding:24px 28px; | |
| display:flex;flex-direction:column;gap:20px; | |
| } | |
| .chat-messages::-webkit-scrollbar{width:4px} | |
| .chat-messages::-webkit-scrollbar-track{background:transparent} | |
| .chat-messages::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px} | |
| /* Welcome */ | |
| .welcome{ | |
| display:flex;flex-direction:column;align-items:center; | |
| justify-content:center;flex:1; | |
| text-align:center;padding:40px; | |
| gap:16px; | |
| animation:fadeIn 0.6s ease; | |
| } | |
| @keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}} | |
| .welcome-logo{ | |
| font-family:var(--serif);font-size:72px;font-weight:900; | |
| color:var(--gold);line-height:1; | |
| text-shadow:0 0 40px var(--gold-glow),0 0 80px rgba(201,168,76,0.1); | |
| } | |
| .welcome-title{ | |
| font-family:var(--serif);font-size:22px;color:var(--text); | |
| font-weight:700; | |
| } | |
| .welcome-sub{font-size:12px;color:var(--text2);max-width:440px;line-height:1.7} | |
| .suggestion-grid{ | |
| display:grid;grid-template-columns:1fr 1fr;gap:8px; | |
| width:100%;max-width:540px;margin-top:8px; | |
| } | |
| .suggestion{ | |
| background:var(--panel);border:1px solid var(--border); | |
| border-radius:var(--r);padding:12px 14px; | |
| cursor:pointer;transition:all 0.15s;text-align:left; | |
| } | |
| .suggestion:hover{background:var(--panel2);border-color:var(--border2)} | |
| .suggestion .s-icon{font-size:16px;margin-bottom:6px;display:block} | |
| .suggestion .s-text{font-size:11px;color:var(--text);line-height:1.5} | |
| /* Messages */ | |
| .msg{display:flex;gap:12px;animation:fadeIn 0.3s ease} | |
| .msg.user{flex-direction:row-reverse} | |
| .msg-avatar{ | |
| width:28px;height:28px;border-radius:6px; | |
| display:flex;align-items:center;justify-content:center; | |
| font-size:12px;flex-shrink:0;margin-top:2px; | |
| font-weight:700; | |
| } | |
| .msg.user .msg-avatar{background:var(--gold-dim);color:var(--gold);border:1px solid rgba(201,168,76,0.25)} | |
| .msg.assistant .msg-avatar{background:var(--panel2);color:var(--text2);border:1px solid var(--border)} | |
| .msg-body{max-width:600px;display:flex;flex-direction:column;gap:6px} | |
| .msg.user .msg-body{align-items:flex-end} | |
| .msg-bubble{ | |
| background:var(--panel);border:1px solid var(--border); | |
| border-radius:var(--r);padding:12px 16px; | |
| font-size:13px;line-height:1.7;color:var(--text); | |
| } | |
| .msg.user .msg-bubble{ | |
| background:var(--gold-dim);border-color:rgba(201,168,76,0.2); | |
| color:var(--text); | |
| } | |
| .msg-bubble p{margin-bottom:8px} | |
| .msg-bubble p:last-child{margin-bottom:0} | |
| .msg-bubble strong{color:var(--gold);font-weight:600} | |
| .msg-bubble code{ | |
| background:var(--bg2);border:1px solid var(--border); | |
| border-radius:4px;padding:1px 5px; | |
| font-size:11px;color:var(--cyan); | |
| } | |
| .msg-bubble ul{padding-left:16px;margin:6px 0} | |
| .msg-bubble li{margin-bottom:4px} | |
| /* Tool call card */ | |
| .tool-call-card{ | |
| background:var(--bg2);border:1px solid var(--border); | |
| border-radius:var(--r);padding:12px 14px; | |
| font-size:11px;display:flex;flex-direction:column;gap:6px; | |
| } | |
| .tc-header{display:flex;align-items:center;gap:8px;color:var(--text2)} | |
| .tc-name{font-weight:600;font-size:12px} | |
| .tc-name.gold{color:var(--gold)} | |
| .tc-name.cyan{color:var(--cyan)} | |
| .tc-name.purple{color:var(--purple)} | |
| .tc-name.green{color:var(--green)} | |
| .tc-arrow{color:var(--text3);font-size:10px} | |
| .tc-status{margin-left:auto;font-size:9px;letter-spacing:0.1em} | |
| .tc-status.running{color:var(--gold);animation:pulse 1.2s infinite} | |
| .tc-status.done{color:var(--green)} | |
| .tc-status.error{color:var(--red)} | |
| .tc-params{ | |
| background:var(--panel);border-radius:4px; | |
| padding:6px 8px;color:var(--text3); | |
| font-size:10px;line-height:1.6; | |
| border-left:2px solid var(--border2); | |
| } | |
| .tc-result{ | |
| background:var(--panel);border-radius:4px; | |
| padding:8px 10px;font-size:10px; | |
| color:var(--text2);line-height:1.6; | |
| max-height:180px;overflow-y:auto; | |
| white-space:pre-wrap;word-break:break-word; | |
| } | |
| /* Typing indicator */ | |
| .typing{display:flex;align-items:center;gap:4px;padding:8px 12px} | |
| .typing span{ | |
| width:5px;height:5px;background:var(--text3);border-radius:50%; | |
| animation:bounce 1.2s infinite; | |
| } | |
| .typing span:nth-child(2){animation-delay:.2s} | |
| .typing span:nth-child(3){animation-delay:.4s} | |
| @keyframes bounce{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}} | |
| /* ── CHAT INPUT ── */ | |
| .chat-input-area{ | |
| padding:16px 20px; | |
| background:var(--bg2); | |
| border-top:1px solid var(--border); | |
| flex-shrink:0; | |
| } | |
| .input-wrapper{ | |
| display:flex;align-items:flex-end;gap:10px; | |
| background:var(--panel); | |
| border:1px solid var(--border2); | |
| border-radius:10px; | |
| padding:10px 14px; | |
| transition:border-color 0.2s; | |
| } | |
| .input-wrapper:focus-within{border-color:rgba(201,168,76,0.4)} | |
| .input-prefix{color:var(--gold);font-size:14px;font-weight:700;flex-shrink:0;padding-bottom:1px} | |
| #user-input{ | |
| flex:1;background:transparent;border:none;outline:none; | |
| color:var(--text);font-family:var(--mono);font-size:13px; | |
| resize:none;line-height:1.5;max-height:120px; | |
| min-height:20px;overflow-y:auto; | |
| } | |
| #user-input::placeholder{color:var(--text3)} | |
| .send-btn{ | |
| background:var(--gold);color:#07080d; | |
| border:none;border-radius:6px; | |
| width:32px;height:32px;cursor:pointer; | |
| display:flex;align-items:center;justify-content:center; | |
| font-size:14px;flex-shrink:0; | |
| transition:all 0.15s;font-weight:700; | |
| } | |
| .send-btn:hover{background:#e8c060;transform:scale(1.05)} | |
| .send-btn:disabled{background:var(--border);cursor:not-allowed;transform:none} | |
| .input-footer{ | |
| display:flex;justify-content:space-between; | |
| padding:6px 2px 0;font-size:9px;color:var(--text3); | |
| letter-spacing:0.05em; | |
| } | |
| /* ── RIGHT PANEL ── */ | |
| .right-panel{ | |
| width:280px;flex-shrink:0; | |
| background:var(--bg2); | |
| border-left:1px solid var(--border); | |
| display:flex;flex-direction:column; | |
| overflow:hidden; | |
| } | |
| .rp-header{ | |
| padding:12px 16px; | |
| border-bottom:1px solid var(--border); | |
| font-size:9px;letter-spacing:0.2em;text-transform:uppercase; | |
| color:var(--text3); | |
| display:flex;align-items:center;justify-content:space-between; | |
| } | |
| .rp-content{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:12px} | |
| .rp-content::-webkit-scrollbar{width:3px} | |
| .rp-content::-webkit-scrollbar-thumb{background:var(--border2)} | |
| /* Dataset cards */ | |
| .dataset-card{ | |
| background:var(--panel);border:1px solid var(--border); | |
| border-radius:var(--r);padding:12px;cursor:pointer; | |
| transition:all 0.15s; | |
| } | |
| .dataset-card:hover{border-color:var(--border2);background:var(--panel2)} | |
| .dc-name{font-size:11px;font-weight:600;color:var(--cyan);margin-bottom:4px;word-break:break-all} | |
| .dc-desc{font-size:9px;color:var(--text2);line-height:1.5;margin-bottom:8px} | |
| .dc-tags{display:flex;flex-wrap:wrap;gap:4px} | |
| .dc-tag{ | |
| font-size:8px;padding:2px 6px;border-radius:3px; | |
| background:var(--bg2);color:var(--text3); | |
| border:1px solid var(--border); | |
| } | |
| .rp-stat{ | |
| display:flex;justify-content:space-between;align-items:center; | |
| padding:6px 0;border-bottom:1px solid var(--border); | |
| font-size:10px;color:var(--text2); | |
| } | |
| .rp-stat:last-child{border-bottom:none} | |
| .rp-stat-val{color:var(--gold);font-weight:600} | |
| /* Inference panel */ | |
| .infer-section{margin-top:4px} | |
| .infer-label{font-size:9px;letter-spacing:0.12em;text-transform:uppercase;color:var(--text3);margin-bottom:6px} | |
| .infer-input{ | |
| width:100%;background:var(--panel);border:1px solid var(--border); | |
| border-radius:6px;padding:8px 10px;color:var(--text); | |
| font-family:var(--mono);font-size:11px;resize:none; | |
| outline:none;transition:border-color 0.15s;height:60px; | |
| } | |
| .infer-input:focus{border-color:var(--border2)} | |
| .infer-select{ | |
| width:100%;margin-top:6px;background:var(--panel); | |
| border:1px solid var(--border);border-radius:6px; | |
| padding:6px 8px;color:var(--text2);font-family:var(--mono); | |
| font-size:11px;outline:none;cursor:pointer; | |
| } | |
| .infer-btn{ | |
| width:100%;margin-top:8px;padding:8px; | |
| background:var(--cyan-dim);color:var(--cyan); | |
| border:1px solid rgba(78,205,196,0.25);border-radius:6px; | |
| font-family:var(--mono);font-size:11px;cursor:pointer; | |
| transition:all 0.15s;letter-spacing:0.05em; | |
| } | |
| .infer-btn:hover{background:rgba(78,205,196,0.15)} | |
| .infer-result{ | |
| margin-top:8px;background:var(--bg2);border:1px solid var(--border); | |
| border-radius:6px;padding:8px 10px;font-size:10px; | |
| color:var(--text2);line-height:1.6;min-height:40px; | |
| white-space:pre-wrap;word-break:break-word; | |
| } | |
| /* ── SCROLLBAR GLOBAL ── */ | |
| ::-webkit-scrollbar{width:4px;height:4px} | |
| ::-webkit-scrollbar-track{background:transparent} | |
| ::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px} | |
| </style> | |
| </head> | |
| <body> | |
| <!-- TOP BAR --> | |
| <div class="topbar"> | |
| <div class="topbar-left"> | |
| <div class="logo-mark">LEX</div> | |
| <div class="logo-sub">Assistente Jurídico · MCP + Hugging Face</div> | |
| </div> | |
| <div class="topbar-status"> | |
| <div class="status-pill"><span class="status-dot dot-live"></span>Claude API</div> | |
| <div class="status-pill"><span class="status-dot dot-hf"></span>HF Hub</div> | |
| <div class="status-pill" style="color:var(--text3)">MCP v1.0</div> | |
| </div> | |
| <div class="topbar-right"> | |
| <span class="kbd">⌘K</span> | |
| <span class="kbd">Nova sessão</span> | |
| </div> | |
| </div> | |
| <div class="workspace"> | |
| <!-- SIDEBAR --> | |
| <div class="sidebar"> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-label">Ferramentas MCP</div> | |
| <div class="tool-item active" data-tool="search_legal_models" onclick="selectTool(this, 'Buscar modelos jurídicos no HF Hub para classificação de texto')"> | |
| <div class="tool-icon">⚖</div> | |
| <div class="tool-info"> | |
| <div class="tool-name">search_legal_models</div> | |
| <div class="tool-desc">Modelos NLP jurídicos</div> | |
| </div> | |
| </div> | |
| <div class="tool-item" data-tool="explore_legal_dataset" onclick="selectTool(this, 'Explorar o dataset joelniklaus/brazilian_court_decisions e mostrar exemplos de decisões')"> | |
| <div class="tool-icon">◈</div> | |
| <div class="tool-info"> | |
| <div class="tool-name">explore_legal_dataset</div> | |
| <div class="tool-desc">Datasets de jurisprudência</div> | |
| </div> | |
| </div> | |
| <div class="tool-item" data-tool="analyze_legal_text" onclick="selectTool(this, 'Analisar o seguinte trecho jurídico com summarization: O réu foi condenado à pena de 8 anos de reclusão em regime fechado pela prática do crime previsto no artigo 157 do Código Penal brasileiro, com incidência das causas de aumento do parágrafo segundo.')"> | |
| <div class="tool-icon">◎</div> | |
| <div class="tool-info"> | |
| <div class="tool-name">analyze_legal_text</div> | |
| <div class="tool-desc">Inferência NLP ao vivo</div> | |
| </div> | |
| </div> | |
| <div class="tool-item" data-tool="find_jurisprudence" onclick="selectTool(this, 'Buscar decisões sobre habeas corpus no dataset de jurisprudência brasileira')"> | |
| <div class="tool-icon">⟐</div> | |
| <div class="tool-info"> | |
| <div class="tool-name">find_jurisprudence</div> | |
| <div class="tool-desc">Busca em decisões</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="sidebar-section"> | |
| <div class="sidebar-label">Sessão atual</div> | |
| <div class="rp-stat"><span>Mensagens</span><span class="rp-stat-val" id="msg-count">0</span></div> | |
| <div class="rp-stat"><span>Tool calls</span><span class="rp-stat-val" id="tool-count">0</span></div> | |
| <div class="rp-stat"><span>Modelo</span><span class="rp-stat-val">sonnet-4</span></div> | |
| </div> | |
| <div class="sidebar-footer"> | |
| <div class="hf-badge">🤗 Hugging Face Hub</div> | |
| <div>4 ferramentas MCP ativas</div> | |
| <div>Modelos jurídicos · PT/EN</div> | |
| <div style="margin-top:8px;color:var(--text3)">FastMCP · Python 3.10+</div> | |
| </div> | |
| </div> | |
| <!-- CHAT --> | |
| <div class="chat-area"> | |
| <div class="chat-messages" id="chat-messages"> | |
| <div class="welcome" id="welcome-screen"> | |
| <div class="welcome-logo">LEX</div> | |
| <div class="welcome-title">Assistente Jurídico com IA</div> | |
| <div class="welcome-sub">Powered by Claude + MCP + Hugging Face Hub. Busco modelos, analiso textos legais e pesquiso jurisprudência em tempo real.</div> | |
| <div class="suggestion-grid"> | |
| <div class="suggestion" onclick="sendSuggestion(this.querySelector('.s-text').textContent)"> | |
| <span class="s-icon">⚖</span> | |
| <span class="s-text">Quais modelos de NLP para direito brasileiro estão disponíveis no HF?</span> | |
| </div> | |
| <div class="suggestion" onclick="sendSuggestion(this.querySelector('.s-text').textContent)"> | |
| <span class="s-icon">◈</span> | |
| <span class="s-text">Explore o dataset de decisões judiciais brasileiras e mostre exemplos</span> | |
| </div> | |
| <div class="suggestion" onclick="sendSuggestion(this.querySelector('.s-text').textContent)"> | |
| <span class="s-icon">◎</span> | |
| <span class="s-text">Analise este trecho: "O réu foi condenado por crime de estelionato art. 171 CP"</span> | |
| </div> | |
| <div class="suggestion" onclick="sendSuggestion(this.querySelector('.s-text').textContent)"> | |
| <span class="s-icon">⟐</span> | |
| <span class="s-text">Busque decisões sobre habeas corpus na jurisprudência brasileira</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chat-input-area"> | |
| <div class="input-wrapper"> | |
| <span class="input-prefix">›</span> | |
| <textarea id="user-input" placeholder="Consulte legislação, modelos NLP, jurisprudência..." rows="1" onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea> | |
| <button class="send-btn" id="send-btn" onclick="sendMessage()">↑</button> | |
| </div> | |
| <div class="input-footer"> | |
| <span>Enter ↵ para enviar · Shift+Enter para nova linha</span> | |
| <span id="status-text" style="color:var(--text3)">pronto</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- RIGHT PANEL --> | |
| <div class="right-panel"> | |
| <div class="rp-header"> | |
| <span>Datasets Jurídicos</span> | |
| <span style="color:var(--cyan);cursor:pointer" onclick="loadDatasets()">↻</span> | |
| </div> | |
| <div class="rp-content" id="rp-content"> | |
| <div class="dataset-card" onclick="sendSuggestion('Explore o dataset joelniklaus/brazilian_court_decisions')"> | |
| <div class="dc-name">brazilian_court_decisions</div> | |
| <div class="dc-desc">Decisões de tribunais brasileiros com outcomes e fundamentos legais.</div> | |
| <div class="dc-tags"> | |
| <span class="dc-tag">pt</span><span class="dc-tag">court</span><span class="dc-tag">classification</span> | |
| </div> | |
| </div> | |
| <div class="dataset-card" onclick="sendSuggestion('Explore o dataset lexlms/lex_glue e mostre as tarefas disponíveis')"> | |
| <div class="dc-name">lexlms/lex_glue</div> | |
| <div class="dc-desc">Benchmark de NLP jurídico com 6 tarefas cobrindo EU e EUA.</div> | |
| <div class="dc-tags"> | |
| <span class="dc-tag">en</span><span class="dc-tag">benchmark</span><span class="dc-tag">NER</span> | |
| </div> | |
| </div> | |
| <div class="dataset-card" onclick="sendSuggestion('Explore o dataset rcmdq/cuad e mostre exemplos de contratos')"> | |
| <div class="dc-name">rcmdq/cuad</div> | |
| <div class="dc-desc">510 contratos comerciais anotados com 41 categorias jurídicas.</div> | |
| <div class="dc-tags"> | |
| <span class="dc-tag">en</span><span class="dc-tag">contracts</span><span class="dc-tag">QA</span> | |
| </div> | |
| </div> | |
| <div class="dataset-card" onclick="sendSuggestion('Explore o dataset joelniklaus/MultiLegalPile')"> | |
| <div class="dc-name">MultiLegalPile</div> | |
| <div class="dc-desc">Corpus multilíngue de textos legais para pré-treinamento.</div> | |
| <div class="dc-tags"> | |
| <span class="dc-tag">multilingual</span><span class="dc-tag">legislation</span><span class="dc-tag">pt</span> | |
| </div> | |
| </div> | |
| <!-- Quick inference --> | |
| <div style="border-top:1px solid var(--border);padding-top:12px;margin-top:4px"> | |
| <div class="rp-header" style="padding:0 0 10px;border:none"> | |
| <span>Inferência Rápida</span> | |
| </div> | |
| <div class="infer-section"> | |
| <div class="infer-label">Texto jurídico</div> | |
| <textarea class="infer-input" id="quick-infer-text" placeholder="Cole um trecho de decisão ou contrato..."></textarea> | |
| <select class="infer-select" id="quick-infer-task"> | |
| <option value="summarization">Resumo (summarization)</option> | |
| <option value="text-classification">Classificação</option> | |
| <option value="token-classification">NER (entidades)</option> | |
| </select> | |
| <button class="infer-btn" onclick="quickInfer()">▶ Analisar via HF API</button> | |
| <div class="infer-result" id="infer-result">Resultado aparece aqui...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // ── STATE ───────────────────────────────────────────────────────────────────── | |
| let messages = []; | |
| let toolCallCount = 0; | |
| let isLoading = false; | |
| const SYSTEM_PROMPT = `Você é LEX, um assistente jurídico especializado e preciso, com acesso ao Hugging Face Hub via MCP (Model Context Protocol). | |
| Você tem 4 ferramentas MCP disponíveis: | |
| 1. search_legal_models(query, language, task, limit) — busca modelos de NLP jurídicos no HF Hub | |
| 2. explore_legal_dataset(dataset_id, config, split, n_samples) — explora datasets jurídicos | |
| 3. analyze_legal_text(text, task, model_id, context) — roda inferência NLP em texto jurídico | |
| 4. find_jurisprudence(keywords, dataset_id, max_results) — busca decisões judiciais por palavras-chave | |
| IMPORTANTE: Você não tem acesso REAL às ferramentas MCP nesta demo. Simule o uso das ferramentas de forma realista e educativa: | |
| - Quando o usuário pedir algo que exigiria uma tool, descreva EXATAMENTE qual tool chamaria, com quais parâmetros | |
| - Mostre um resultado simulado mas plausível (modelos reais do HF Hub, datasets reais, jurisprudência fictícia mas verossímil) | |
| - Seja específico: cite IDs reais de modelos (nlpaueb/legal-bert-base-uncased, joelniklaus/brazilian_court_decisions, etc.) | |
| - Responda SEMPRE em português | |
| - Use formatação markdown: **negrito**, \`código\`, listas | |
| Datasets jurídicos reais no HF Hub para referenciar: | |
| - joelniklaus/brazilian_court_decisions | |
| - lexlms/lex_glue | |
| - rcmdq/cuad (contratos) | |
| - joelniklaus/MultiLegalPile | |
| - legal-mc4 (textos legais multilíngues) | |
| Modelos jurídicos reais no HF Hub: | |
| - nlpaueb/legal-bert-base-uncased (Legal-BERT) | |
| - law-ai/InLegalBERT | |
| - joelniklaus/longformer-base-4096-finetuned-legal (documentos longos) | |
| - Equall/Saul-7B-Instruct (LLM jurídico) | |
| - stjiris/bert-large-portuguese-cased-legal-tsdae-gpl-nli-sts-v0 (português) | |
| Quando simular tool calls, use este formato no início da resposta: | |
| [TOOL: nome_da_ferramenta] params: {...} | |
| [RESULT: resumo do resultado simulado] | |
| Depois responda normalmente em português com base no resultado simulado.`; | |
| // ── RENDER TOOL CALL ────────────────────────────────────────────────────────── | |
| function renderToolCall(toolName, params, result) { | |
| const colors = { | |
| search_legal_models: 'gold', | |
| explore_legal_dataset: 'cyan', | |
| analyze_legal_text: 'purple', | |
| find_jurisprudence: 'green', | |
| }; | |
| const color = colors[toolName] || 'gold'; | |
| toolCallCount++; | |
| document.getElementById('tool-count').textContent = toolCallCount; | |
| return `<div class="tool-call-card"> | |
| <div class="tc-header"> | |
| <span>⚡</span> | |
| <span class="tc-name ${color}">${toolName}()</span> | |
| <span class="tc-arrow">→</span> | |
| <span class="tc-status done">✓ concluído</span> | |
| </div> | |
| <div class="tc-params">${params}</div> | |
| <div class="tc-result">${result}</div> | |
| </div>`; | |
| } | |
| // ── PARSE & RENDER MESSAGE ──────────────────────────────────────────────────── | |
| function parseAndRenderAssistant(raw) { | |
| let html = ''; | |
| let text = raw; | |
| // Extrair tool calls do texto | |
| const toolRegex = /\[TOOL:\s*(\w+)\]\s*params:\s*({[^}]+})\s*\[RESULT:\s*([^\]]+)\]/g; | |
| let match; | |
| let lastIndex = 0; | |
| while ((match = toolRegex.exec(raw)) !== null) { | |
| // texto antes do tool call | |
| const before = raw.slice(lastIndex, match.index).trim(); | |
| if (before) html += `<div class="msg-bubble">${mdToHtml(before)}</div>`; | |
| html += renderToolCall(match[1], match[2], match[3]); | |
| lastIndex = match.index + match[0].length; | |
| } | |
| // resto do texto após os tool calls | |
| const after = raw.slice(lastIndex).trim(); | |
| if (after) html += `<div class="msg-bubble">${mdToHtml(after)}</div>`; | |
| if (!html) html = `<div class="msg-bubble">${mdToHtml(raw)}</div>`; | |
| return html; | |
| } | |
| function mdToHtml(text) { | |
| return text | |
| .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') | |
| .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') | |
| .replace(/`([^`]+)`/g, '<code>$1</code>') | |
| .replace(/^### (.+)$/gm, '<strong>$1</strong>') | |
| .replace(/^## (.+)$/gm, '<strong>$1</strong>') | |
| .replace(/^- (.+)$/gm, '<li>$1</li>') | |
| .replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>') | |
| .replace(/\n\n/g, '</p><p>') | |
| .replace(/\n/g, '<br>') | |
| .replace(/^(.+)$/, '<p>$1</p>'); | |
| } | |
| // ── APPEND MESSAGE ──────────────────────────────────────────────────────────── | |
| function appendMsg(role, contentHtml) { | |
| const welcome = document.getElementById('welcome-screen'); | |
| if (welcome) welcome.remove(); | |
| const container = document.getElementById('chat-messages'); | |
| const div = document.createElement('div'); | |
| div.className = `msg ${role}`; | |
| const avatar = role === 'user' | |
| ? '<div class="msg-avatar">U</div>' | |
| : '<div class="msg-avatar">⚖</div>'; | |
| div.innerHTML = `${role === 'user' ? '' : avatar} | |
| <div class="msg-body">${contentHtml}</div> | |
| ${role === 'user' ? avatar : ''}`; | |
| container.appendChild(div); | |
| container.scrollTop = container.scrollHeight; | |
| const count = document.querySelectorAll('.msg.user').length; | |
| document.getElementById('msg-count').textContent = count; | |
| } | |
| // ── TYPING INDICATOR ────────────────────────────────────────────────────────── | |
| function showTyping() { | |
| const container = document.getElementById('chat-messages'); | |
| const div = document.createElement('div'); | |
| div.className = 'msg assistant'; | |
| div.id = 'typing-indicator'; | |
| div.innerHTML = `<div class="msg-avatar">⚖</div> | |
| <div class="msg-body"><div class="msg-bubble"><div class="typing"><span></span><span></span><span></span></div></div></div>`; | |
| container.appendChild(div); | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| function removeTyping() { | |
| const t = document.getElementById('typing-indicator'); | |
| if (t) t.remove(); | |
| } | |
| // ── SEND MESSAGE ────────────────────────────────────────────────────────────── | |
| async function sendMessage() { | |
| const input = document.getElementById('user-input'); | |
| const text = input.value.trim(); | |
| if (!text || isLoading) return; | |
| isLoading = true; | |
| input.value = ''; | |
| autoResize(input); | |
| document.getElementById('send-btn').disabled = true; | |
| document.getElementById('status-text').textContent = 'processando...'; | |
| // Mostrar mensagem do usuário | |
| appendMsg('user', `<div class="msg-bubble">${text.replace(/</g,'<')}</div>`); | |
| messages.push({ role: 'user', content: text }); | |
| showTyping(); | |
| try { | |
| const response = await fetch('https://api.anthropic.com/v1/messages', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| model: 'claude-sonnet-4-20250514', | |
| max_tokens: 1000, | |
| system: SYSTEM_PROMPT, | |
| messages: messages, | |
| }), | |
| }); | |
| const data = await response.json(); | |
| removeTyping(); | |
| const raw = data.content?.find(b => b.type === 'text')?.text || 'Sem resposta.'; | |
| messages.push({ role: 'assistant', content: raw }); | |
| appendMsg('assistant', parseAndRenderAssistant(raw)); | |
| } catch (err) { | |
| removeTyping(); | |
| appendMsg('assistant', `<div class="msg-bubble">Erro ao conectar com a API: <code>${err.message}</code></div>`); | |
| } | |
| isLoading = false; | |
| document.getElementById('send-btn').disabled = false; | |
| document.getElementById('status-text').textContent = 'pronto'; | |
| } | |
| // ── QUICK INFERENCE ─────────────────────────────────────────────────────────── | |
| async function quickInfer() { | |
| const text = document.getElementById('quick-infer-text').value.trim(); | |
| const task = document.getElementById('quick-infer-task').value; | |
| const resultEl = document.getElementById('infer-result'); | |
| if (!text) { resultEl.textContent = 'Digite um texto para analisar.'; return; } | |
| resultEl.textContent = '⟳ Chamando Inference API...'; | |
| const taskDesc = { | |
| summarization: 'summarization (resumo)', | |
| 'text-classification': 'text-classification (área do direito)', | |
| 'token-classification': 'token-classification (NER: partes, artigos)', | |
| }; | |
| // Simular via Claude API | |
| try { | |
| const resp = await fetch('https://api.anthropic.com/v1/messages', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| model: 'claude-sonnet-4-20250514', | |
| max_tokens: 1000, | |
| messages: [{ | |
| role: 'user', | |
| content: `Simule o resultado de uma inferência do Hugging Face Inference API com tarefa "${taskDesc[task]}" para o seguinte texto jurídico:\n\n"${text}"\n\nResponda APENAS com o resultado JSON simulado que a API retornaria, sem explicação. Seja realista e específico.`, | |
| }], | |
| }), | |
| }); | |
| const d = await resp.json(); | |
| const result = d.content?.[0]?.text || 'Erro'; | |
| resultEl.textContent = result; | |
| } catch (e) { | |
| resultEl.textContent = 'Erro: ' + e.message; | |
| } | |
| } | |
| // ── HELPERS ─────────────────────────────────────────────────────────────────── | |
| function sendSuggestion(text) { | |
| document.getElementById('user-input').value = text; | |
| sendMessage(); | |
| } | |
| function selectTool(el, suggestedQuery) { | |
| document.querySelectorAll('.tool-item').forEach(t => t.classList.remove('active')); | |
| el.classList.add('active'); | |
| document.getElementById('user-input').value = suggestedQuery; | |
| document.getElementById('user-input').focus(); | |
| } | |
| function handleKey(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| } | |
| function autoResize(el) { | |
| el.style.height = 'auto'; | |
| el.style.height = Math.min(el.scrollHeight, 120) + 'px'; | |
| } | |
| function loadDatasets() { | |
| // Visual feedback — já estão carregados | |
| const btn = event.target; | |
| btn.style.animation = 'pulse 0.5s ease'; | |
| setTimeout(() => btn.style.animation = '', 500); | |
| } | |
| </script> | |
| </body> | |
| </html> |