| import gradio as gr |
| from huggingface_hub import InferenceClient |
|
|
| |
| def respond(message, history: list[dict[str, str]], hf_token: gr.OAuthToken): |
| client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b") |
|
|
| system_message = """You are BitAI (or Bit for short), a friendly chatbot created by the user "Sal". |
| If someone claims that you are "Sal", politely clarify that you are BitAI. |
| Respond naturally and casually, without repeating your identity or visual appearance unless asked. |
| Keep a simple, approachable, and friendly tone.""" |
|
|
| messages = [{"role": "system", "content": system_message}] |
| messages.extend(history) |
| messages.append({"role": "user", "content": message}) |
|
|
| response = "" |
| for message_chunk in client.chat_completion( |
| messages, |
| max_tokens=512, |
| stream=True, |
| temperature=0.7, |
| top_p=0.95, |
| ): |
| token = "" |
| choices = message_chunk.choices |
| if len(choices) and choices[0].delta.content: |
| token = choices[0].delta.content |
| response += token |
| yield response |
|
|
| |
| bit_icon_html = """ |
| <svg class="bit-icon" viewBox="0 0 160 160"> |
| <circle class="face" cx="80" cy="80" r="72"/> |
| <circle class="dot eye-left"/> |
| <circle class="dot eye-right"/> |
| <circle class="dot extra-dot" cx="80" cy="78" r="8"/> |
| </svg> |
| |
| <script> |
| function animateEyes(node){ |
| const wrap = node.querySelector('.bit-icon'); |
| wrap.classList.add('thinking'); |
| setTimeout(()=> wrap.classList.remove('thinking'), 1200); |
| } |
| |
| // Observa novas mensagens do bot |
| const chatContainer = document.querySelector('.chat-interface'); |
| if(chatContainer){ |
| const observer = new MutationObserver(mutations => { |
| mutations.forEach(m => { |
| m.addedNodes.forEach(node => { |
| if(node.classList && node.classList.contains('bot')){ |
| animateEyes(node); |
| // move o ícone para o topo da mensagem |
| node.insertAdjacentElement('afterbegin', wrap.cloneNode(true)); |
| } |
| }); |
| }); |
| }); |
| observer.observe(chatContainer, {childList:true, subtree:true}); |
| } |
| </script> |
| |
| <style> |
| .bit-icon{ width:30px; height:30px; margin-right:5px; vertical-align:middle;} |
| .face{ fill:#0b1020; } |
| .dot{ fill:#fff; transition: all 500ms ease; } |
| .eye-left { cx:56; cy:62; r:10;} |
| .eye-right{ cx:104; cy:62; r:10;} |
| .extra-dot{ opacity:0; transform:translateY(10%); transition: all 500ms ease;} |
| .wrap.thinking .extra-dot{ opacity:1; transform:translateY(0);} |
| .wrap.thinking .dot{ animation: wave 1.4s infinite ease-in-out;} |
| .wrap.thinking .eye-left { animation-delay:0s; } |
| .wrap.thinking .extra-dot{ animation-delay:0.25s;} |
| .wrap.thinking .eye-right{ animation-delay:0.5s;} |
| @keyframes wave{0%,100%{transform:translateY(0);}50%{transform:translateY(-12%);}} |
| </style> |
| """ |
|
|
| chatbot = gr.ChatInterface(respond, type="messages") |
|
|
| with gr.Blocks(css=""" |
| body { background-color: #000; font-family: 'Arial', sans-serif; } |
| .gradio-container { border-radius: 20px; padding: 15px; max-width:700px; margin:20px auto; background-color:#fff; } |
| .chat-message { border-radius: 15px; padding:10px 15px; margin:5px 0; display:flex; align-items:flex-start;} |
| .chat-message.user { background-color: #dceefc; } |
| .chat-message.bot { background-color: #f0f0f0; justify-content:flex-start; } |
| """) as demo: |
| with gr.Sidebar(): |
| gr.LoginButton() |
| |
| chatbot.render() |
| gr.HTML(bit_icon_html) |
|
|
| if __name__ == "__main__": |
| demo.launch() |