Spaces:
Running
Running
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Iris Pro - Assistente com Memória Vetorial e Dados Reais</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;500;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --neon-blue: #00f7ff; | |
| --neon-pink: #ff00ff; | |
| --neon-purple: #9d00ff; | |
| --dark-bg: #0a0a12; | |
| } | |
| body { | |
| font-family: 'Rajdhani', sans-serif; | |
| background-color: var(--dark-bg); | |
| color: white; | |
| overflow-x: hidden; | |
| } | |
| .cyber-font { | |
| font-family: 'Orbitron', sans-serif; | |
| } | |
| .neon-text-blue { | |
| text-shadow: 0 0 5px var(--neon-blue), 0 0 10px var(--neon-blue); | |
| color: var(--neon-blue); | |
| } | |
| .neon-text-pink { | |
| text-shadow: 0 0 5px var(--neon-pink), 0 0 10px var(--neon-pink); | |
| color: var(--neon-pink); | |
| } | |
| .neon-text-purple { | |
| text-shadow: 0 0 5px var(--neon-purple), 0 0 10px var(--neon-purple); | |
| color: var(--neon-purple); | |
| } | |
| .neon-border-blue { | |
| box-shadow: 0 0 10px var(--neon-blue), inset 0 0 10px var(--neon-blue); | |
| border: 1px solid var(--neon-blue); | |
| } | |
| .neon-border-pink { | |
| box-shadow: 0 0 10px var(--neon-pink), inset 0 0 10px var(--neon-pink); | |
| border: 1px solid var(--neon-pink); | |
| } | |
| .neon-border-purple { | |
| box-shadow: 0 0 10px var(--neon-purple), inset 0 0 10px var(--neon-purple); | |
| border: 1px solid var(--neon-purple); | |
| } | |
| .glow-effect { | |
| animation: glow 2s infinite alternate; | |
| } | |
| @keyframes glow { | |
| from { | |
| box-shadow: 0 0 5px var(--neon-blue), 0 0 10px var(--neon-blue); | |
| } | |
| to { | |
| box-shadow: 0 0 15px var(--neon-blue), 0 0 20px var(--neon-blue); | |
| } | |
| } | |
| .chat-container { | |
| background: rgba(10, 10, 18, 0.7); | |
| backdrop-filter: blur(10px); | |
| } | |
| .message-bubble { | |
| max-width: 80%; | |
| border-radius: 1rem; | |
| padding: 0.75rem 1rem; | |
| margin-bottom: 0.5rem; | |
| position: relative; | |
| } | |
| .user-message { | |
| background: rgba(0, 247, 255, 0.15); | |
| border-left: 3px solid var(--neon-blue); | |
| margin-left: auto; | |
| } | |
| .iris-message { | |
| background: rgba(255, 0, 255, 0.15); | |
| border-right: 3px solid var(--neon-pink); | |
| margin-right: auto; | |
| } | |
| .system-message { | |
| background: rgba(157, 0, 255, 0.15); | |
| border-right: 3px solid var(--neon-purple); | |
| margin: 0 auto; | |
| max-width: 90%; | |
| text-align: center; | |
| font-size: 0.85rem; | |
| } | |
| .typing-indicator span { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| background-color: var(--neon-pink); | |
| border-radius: 50%; | |
| margin-right: 3px; | |
| animation: bounce 1.5s infinite ease-in-out; | |
| } | |
| .typing-indicator span:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .typing-indicator span:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes bounce { | |
| 0%, 100% { | |
| transform: translateY(0); | |
| } | |
| 50% { | |
| transform: translateY(-5px); | |
| } | |
| } | |
| .scanline { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient( | |
| to bottom, | |
| rgba(0, 247, 255, 0.1) 0%, | |
| rgba(0, 247, 255, 0) 10% | |
| ); | |
| background-size: 100% 8px; | |
| pointer-events: none; | |
| animation: scanline 8s linear infinite; | |
| } | |
| @keyframes scanline { | |
| 0% { | |
| background-position: 0 0; | |
| } | |
| 100% { | |
| background-position: 0 100%; | |
| } | |
| } | |
| .memory-chip { | |
| position: relative; | |
| width: 30px; | |
| height: 30px; | |
| background: linear-gradient(135deg, rgba(0, 247, 255, 0.2), rgba(255, 0, 255, 0.2)); | |
| border-radius: 4px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .memory-chip::before { | |
| content: ""; | |
| position: absolute; | |
| top: -2px; | |
| left: -2px; | |
| right: -2px; | |
| bottom: -2px; | |
| border: 1px solid var(--neon-blue); | |
| border-radius: 6px; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 0.6; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.6; } | |
| } | |
| .command-chip { | |
| display: inline-block; | |
| padding: 2px 6px; | |
| background: rgba(0, 247, 255, 0.2); | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| margin-right: 4px; | |
| margin-bottom: 4px; | |
| } | |
| .progress-bar { | |
| height: 4px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 2px; | |
| overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--neon-blue), var(--neon-pink)); | |
| border-radius: 2px; | |
| transition: width 0.3s ease; | |
| } | |
| /* Animation for new message */ | |
| @keyframes messageAppear { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .new-message { | |
| animation: messageAppear 0.3s ease-out; | |
| } | |
| /* Settings panel */ | |
| .settings-option { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 8px 0; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .toggle-switch { | |
| position: relative; | |
| display: inline-block; | |
| width: 40px; | |
| height: 20px; | |
| } | |
| .toggle-switch input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .toggle-slider { | |
| position: absolute; | |
| cursor: pointer; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: rgba(255, 255, 255, 0.1); | |
| transition: .4s; | |
| border-radius: 20px; | |
| } | |
| .toggle-slider:before { | |
| position: absolute; | |
| content: ""; | |
| height: 16px; | |
| width: 16px; | |
| left: 2px; | |
| bottom: 2px; | |
| background-color: white; | |
| transition: .4s; | |
| border-radius: 50%; | |
| } | |
| input:checked + .toggle-slider { | |
| background-color: var(--neon-blue); | |
| } | |
| input:checked + .toggle-slider:before { | |
| transform: translateX(20px); | |
| } | |
| .api-status { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| margin-right: 5px; | |
| } | |
| .api-active { | |
| background-color: #00ff00; | |
| box-shadow: 0 0 5px #00ff00; | |
| } | |
| .api-inactive { | |
| background-color: #ff0000; | |
| box-shadow: 0 0 5px #ff0000; | |
| } | |
| .knowledge-graph { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| margin-top: 10px; | |
| } | |
| .knowledge-node { | |
| background: rgba(157, 0, 255, 0.2); | |
| border: 1px solid var(--neon-purple); | |
| border-radius: 12px; | |
| padding: 6px 10px; | |
| font-size: 0.8rem; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .knowledge-node i { | |
| margin-right: 5px; | |
| font-size: 0.7rem; | |
| } | |
| .vector-score { | |
| font-size: 0.7rem; | |
| color: var(--neon-blue); | |
| margin-left: 5px; | |
| } | |
| .context-chip { | |
| background: rgba(0, 247, 255, 0.1); | |
| border: 1px solid var(--neon-blue); | |
| border-radius: 4px; | |
| padding: 2px 6px; | |
| font-size: 0.7rem; | |
| margin-right: 4px; | |
| margin-bottom: 4px; | |
| display: inline-block; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex flex-col"> | |
| <div class="scanline"></div> | |
| <!-- Header --> | |
| <header class="py-4 px-6 flex justify-between items-center neon-border-b border-b-2 border-blue-500"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full neon-border-blue flex items-center justify-center mr-3"> | |
| <i class="fas fa-robot neon-text-blue text-xl"></i> | |
| </div> | |
| <h1 class="cyber-font neon-text-blue text-2xl">IRIS PRO</h1> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <button class="neon-text-blue hover:neon-text-pink transition-colors" id="settings-btn"> | |
| <i class="fas fa-cog text-xl"></i> | |
| </button> | |
| <button class="neon-text-blue hover:neon-text-pink transition-colors" id="memory-btn"> | |
| <div class="memory-chip"> | |
| <i class="fas fa-brain text-xs"></i> | |
| </div> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="flex-1 flex flex-col p-4 max-w-3xl mx-auto w-full"> | |
| <!-- Status Bar --> | |
| <div class="flex justify-between items-center mb-4 neon-text-blue text-sm"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-wifi mr-1"></i> | |
| <span id="connection-status">CONECTADO</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <i class="fas fa-microchip mr-1"></i> | |
| <span>IA: MISTRAL-7B</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <i class="fas fa-database mr-1"></i> | |
| <span>MEM: <span id="memory-usage">78</span>%</span> | |
| </div> | |
| </div> | |
| <!-- API Status --> | |
| <div class="flex justify-between mb-2 text-xs neon-text-purple"> | |
| <div class="flex items-center"> | |
| <span class="api-status api-active" id="weather-api-status"></span> | |
| <span>Clima</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="api-status api-active" id="news-api-status"></span> | |
| <span>Notícias</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="api-status api-active" id="translate-api-status"></span> | |
| <span>Tradução</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="api-status api-active" id="finance-api-status"></span> | |
| <span>Finanças</span> | |
| </div> | |
| </div> | |
| <!-- Chat Container --> | |
| <div class="chat-container flex-1 rounded-lg neon-border-blue p-4 mb-4 overflow-y-auto"> | |
| <div class="chat-messages space-y-2" id="chat-messages"> | |
| <!-- Initial greeting --> | |
| <div class="message-bubble iris-message new-message"> | |
| <div class="flex items-start"> | |
| <div class="w-6 h-6 rounded-full bg-pink-500 mr-2 flex items-center justify-center neon-border-pink"> | |
| <i class="fas fa-robot text-xs"></i> | |
| </div> | |
| <div> | |
| <p class="neon-text-pink">Olá, eu sou a Iris Pro. Seu assistente de IA com memória vetorial e acesso a dados reais. Como posso te ajudar hoje?</p> | |
| <div class="text-xs text-gray-400 mt-1">Sistema: Memória vetorial ativa | APIs conectadas</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- System message with commands --> | |
| <div class="message-bubble system-message new-message"> | |
| <p class="neon-text-purple">Você pode me pedir coisas como:</p> | |
| <div class="mt-2 flex flex-wrap justify-center"> | |
| <span class="command-chip">"Previsão do tempo em São Paulo"</span> | |
| <span class="command-chip">"Notícias sobre tecnologia"</span> | |
| <span class="command-chip">"Cotação do dólar hoje"</span> | |
| <span class="command-chip">"Traduzir 'hello' para português"</span> | |
| <span class="command-chip">"Lembretes para hoje"</span> | |
| <span class="command-chip">"Pesquisar sobre IA"</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Voice Activation Button --> | |
| <div class="flex justify-center mb-4 relative"> | |
| <div class="absolute -top-4 w-full max-w-xs"> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="voice-level" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <button id="voice-btn" class="w-16 h-16 rounded-full neon-border-blue glow-effect flex items-center justify-center neon-text-blue hover:neon-text-pink transition-all transform hover:scale-110"> | |
| <i class="fas fa-microphone text-2xl"></i> | |
| </button> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="flex items-center neon-border-blue rounded-lg p-2"> | |
| <input type="text" id="message-input" placeholder="Digite sua mensagem..." class="flex-1 bg-transparent outline-none px-3 py-2 neon-text-blue placeholder-blue-400"> | |
| <button id="send-btn" class="w-10 h-10 rounded-md neon-border-blue flex items-center justify-center neon-text-blue hover:neon-text-pink transition-colors"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| <!-- Quick Actions --> | |
| <div class="grid grid-cols-4 gap-2 mt-4"> | |
| <button class="quick-action neon-border-blue rounded-md py-2 px-1 neon-text-blue hover:neon-text-pink transition-colors text-xs" data-command="Previsão do tempo em São Paulo"> | |
| <i class="fas fa-cloud-sun mr-1"></i> Clima | |
| </button> | |
| <button class="quick-action neon-border-blue rounded-md py-2 px-1 neon-text-blue hover:neon-text-pink transition-colors text-xs" data-command="Notícias sobre tecnologia"> | |
| <i class="fas fa-newspaper mr-1"></i> Notícias | |
| </button> | |
| <button class="quick-action neon-border-blue rounded-md py-2 px-1 neon-text-blue hover:neon-text-pink transition-colors text-xs" data-command="Cotação do dólar hoje"> | |
| <i class="fas fa-dollar-sign mr-1"></i> Dólar | |
| </button> | |
| <button class="quick-action neon-border-blue rounded-md py-2 px-1 neon-text-blue hover:neon-text-pink transition-colors text-xs" data-command="Traduzir 'hello' para português"> | |
| <i class="fas fa-language mr-1"></i> Traduzir | |
| </button> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="py-3 px-6 text-center neon-border-t border-t-2 border-blue-500 text-xs neon-text-blue"> | |
| <p>IRIS PRO v3.0 | Memória vetorial ativa | Dados em tempo real | Tempo de resposta: <span id="response-time">1.4</span>s</p> | |
| </footer> | |
| <!-- Memory Panel (hidden by default) --> | |
| <div id="memory-panel" class="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center hidden"> | |
| <div class="neon-border-blue bg-gray-900 rounded-lg p-6 max-w-md w-full max-h-[80vh] overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="cyber-font neon-text-blue text-xl">Memória Vetorial</h3> | |
| <button id="close-memory" class="neon-text-pink hover:text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-4"> | |
| <div class="neon-border-pink p-4 rounded-lg"> | |
| <h4 class="neon-text-pink font-bold mb-2">Contexto Atual</h4> | |
| <div id="current-context"> | |
| <!-- Filled by JavaScript --> | |
| </div> | |
| </div> | |
| <div class="neon-border-blue p-4 rounded-lg"> | |
| <h4 class="neon-text-blue font-bold mb-2">Memória de Longo Prazo</h4> | |
| <div class="knowledge-graph" id="knowledge-graph"> | |
| <!-- Filled by JavaScript --> | |
| </div> | |
| </div> | |
| <div class="neon-border-purple p-4 rounded-lg"> | |
| <h4 class="neon-text-purple font-bold mb-2">Dados do Sistema</h4> | |
| <div class="text-sm space-y-2"> | |
| <div class="flex justify-between"> | |
| <span>Vetores armazenados:</span> | |
| <span id="vector-count">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Similaridade média:</span> | |
| <span id="avg-similarity">0%</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Requisições API hoje:</span> | |
| <span id="api-requests">12</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 grid grid-cols-2 gap-2"> | |
| <button class="py-2 neon-border-blue rounded-lg neon-text-blue hover:neon-text-pink" id="export-vectors"> | |
| Exportar vetores | |
| </button> | |
| <button class="py-2 neon-border-pink rounded-lg neon-text-pink hover:neon-text-blue" id="clear-context"> | |
| Limpar contexto | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Panel --> | |
| <div id="settings-panel" class="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center hidden"> | |
| <div class="neon-border-blue bg-gray-900 rounded-lg p-6 max-w-md w-full max-h-[80vh] overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="cyber-font neon-text-blue text-xl">Configurações</h3> | |
| <button id="close-settings" class="neon-text-pink hover:text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-4"> | |
| <div class="neon-border-blue p-4 rounded-lg"> | |
| <h4 class="neon-text-blue font-bold mb-3">Preferências de voz</h4> | |
| <div class="settings-option"> | |
| <span>Ativar voz</span> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="voice-toggle" checked> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| </div> | |
| <div class="settings-option"> | |
| <span>Volume padrão</span> | |
| <input type="range" id="volume-slider" min="0" max="100" value="75" class="w-24"> | |
| <span id="volume-value" class="w-8 text-right">75%</span> | |
| </div> | |
| <div class="settings-option"> | |
| <span>Velocidade da voz</span> | |
| <select id="voice-speed" class="bg-gray-800 text-white rounded px-2 py-1"> | |
| <option value="0.8">Lenta</option> | |
| <option value="1.0" selected>Normal</option> | |
| <option value="1.2">Rápida</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="neon-border-pink p-4 rounded-lg"> | |
| <h4 class="neon-text-pink font-bold mb-3">Configurações de Memória</h4> | |
| <div class="settings-option"> | |
| <span>Memória vetorial</span> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="vector-memory-toggle" checked> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| </div> | |
| <div class="settings-option"> | |
| <span>Limite de contexto</span> | |
| <select id="context-limit" class="bg-gray-800 text-white rounded px-2 py-1"> | |
| <option value="5">5 mensagens</option> | |
| <option value="10" selected>10 mensagens</option> | |
| <option value="20">20 mensagens</option> | |
| </select> | |
| </div> | |
| <div class="settings-option"> | |
| <span>Limiar de similaridade</span> | |
| <input type="range" id="similarity-threshold" min="50" max="95" value="75" class="w-24"> | |
| <span id="similarity-value" class="w-8 text-right">75%</span> | |
| </div> | |
| </div> | |
| <div class="neon-border-purple p-4 rounded-lg"> | |
| <h4 class="neon-text-purple font-bold mb-3">Configurações de privacidade</h4> | |
| <div class="settings-option"> | |
| <span>Armazenar histórico</span> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="store-history-toggle" checked> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| </div> | |
| <div class="settings-option"> | |
| <span>Compartilhar dados anônimos</span> | |
| <label class="toggle-switch"> | |
| <input type="checkbox" id="share-data-toggle"> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6"> | |
| <button class="w-full py-2 neon-border-blue rounded-lg neon-text-blue hover:neon-text-pink" id="save-settings"> | |
| Salvar configurações | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Enhanced user profile with vector memory | |
| const userProfile = { | |
| name: "Usuário", | |
| location: { | |
| city: "São Paulo", | |
| country: "Brasil", | |
| timezone: "America/Sao_Paulo" | |
| }, | |
| preferences: { | |
| voice: { | |
| active: true, | |
| volume: 75, | |
| speed: 1.0 | |
| }, | |
| memory: { | |
| vectorEnabled: true, | |
| contextLimit: 10, | |
| similarityThreshold: 0.75 | |
| }, | |
| privacy: { | |
| storeHistory: true, | |
| shareData: false | |
| }, | |
| apis: { | |
| weather: true, | |
| news: true, | |
| finance: true, | |
| translate: true | |
| } | |
| }, | |
| history: [], | |
| vectorMemory: [], | |
| context: [], | |
| apiRequests: 0 | |
| }; | |
| // Free API endpoints with real data | |
| const API_ENDPOINTS = { | |
| weather: "https://api.open-meteo.com/v1/forecast", | |
| news: "https://newsapi.org/v2/top-headlines", | |
| finance: "https://api.exchangerate-api.com/v4/latest/USD", | |
| translate: "https://libretranslate.de/translate", | |
| geocoding: "https://geocoding-api.open-meteo.com/v1/search", | |
| wikipedia: "https://pt.wikipedia.org/w/api.php" | |
| }; | |
| // API Keys (in a real app, these would be secured) | |
| const API_KEYS = { | |
| news: "a2a4b2e0e4msh7a9a8b3d3c3a4d6p1b3a2ajsn4a3b2c1d0e0f" // Demo key - would be replaced in production | |
| }; | |
| // Vector memory model (simplified for demo) | |
| class VectorMemory { | |
| constructor() { | |
| this.vectors = []; | |
| this.model = null; | |
| this.initialized = false; | |
| } | |
| async init() { | |
| // Load a simple text embedding model (in a real app, would use a proper model) | |
| this.model = await this.loadSimpleEmbeddingModel(); | |
| this.initialized = true; | |
| } | |
| async loadSimpleEmbeddingModel() { | |
| // This is a simplified embedding approach | |
| // In a real app, you would use TensorFlow.js with a proper model | |
| return { | |
| embed: async (text) => { | |
| // Simple word frequency vector (for demo purposes) | |
| const words = text.toLowerCase().split(/\s+/); | |
| const uniqueWords = [...new Set(words)]; | |
| const vector = new Array(100).fill(0); | |
| // Simple hash-based embedding | |
| uniqueWords.forEach(word => { | |
| const hash = this.stringHash(word) % 100; | |
| vector[hash] = 1; | |
| }); | |
| return vector; | |
| } | |
| }; | |
| } | |
| stringHash(str) { | |
| let hash = 0; | |
| for (let i = 0; i < str.length; i++) { | |
| hash = ((hash << 5) - hash) + str.charCodeAt(i); | |
| hash |= 0; // Convert to 32bit integer | |
| } | |
| return Math.abs(hash); | |
| } | |
| async addMemory(text, metadata = {}) { | |
| if (!this.initialized) await this.init(); | |
| const vector = await this.model.embed(text); | |
| this.vectors.push({ | |
| text, | |
| vector, | |
| metadata: { | |
| timestamp: new Date().toISOString(), | |
| ...metadata | |
| } | |
| }); | |
| return vector; | |
| } | |
| async findSimilar(text, limit = 5, threshold = 0.7) { | |
| if (!this.initialized) await this.init(); | |
| const queryVector = await this.model.embed(text); | |
| const results = []; | |
| this.vectors.forEach((memory, index) => { | |
| const similarity = this.cosineSimilarity(queryVector, memory.vector); | |
| if (similarity >= threshold) { | |
| results.push({ | |
| text: memory.text, | |
| similarity, | |
| metadata: memory.metadata | |
| }); | |
| } | |
| }); | |
| // Sort by similarity | |
| results.sort((a, b) => b.similarity - a.similarity); | |
| return results.slice(0, limit); | |
| } | |
| cosineSimilarity(vecA, vecB) { | |
| let dotProduct = 0; | |
| let normA = 0; | |
| let normB = 0; | |
| for (let i = 0; i < vecA.length; i++) { | |
| dotProduct += vecA[i] * vecB[i]; | |
| normA += vecA[i] * vecA[i]; | |
| normB += vecB[i] * vecB[i]; | |
| } | |
| normA = Math.sqrt(normA); | |
| normB = Math.sqrt(normB); | |
| return dotProduct / (normA * normB); | |
| } | |
| getMemoryStats() { | |
| return { | |
| count: this.vectors.length, | |
| avgSimilarity: this.calculateAvgSimilarity() | |
| }; | |
| } | |
| calculateAvgSimilarity() { | |
| if (this.vectors.length < 2) return 0; | |
| let totalSimilarity = 0; | |
| let comparisons = 0; | |
| // Compare each vector with a few others | |
| for (let i = 0; i < Math.min(10, this.vectors.length); i++) { | |
| for (let j = i + 1; j < Math.min(i + 5, this.vectors.length); j++) { | |
| totalSimilarity += this.cosineSimilarity( | |
| this.vectors[i].vector, | |
| this.vectors[j].vector | |
| ); | |
| comparisons++; | |
| } | |
| } | |
| return comparisons > 0 ? totalSimilarity / comparisons : 0; | |
| } | |
| } | |
| // Initialize vector memory | |
| const vectorMemory = new VectorMemory(); | |
| // Enhanced knowledge base with vector memory integration | |
| const knowledgeBase = { | |
| greetings: [ | |
| "Olá! Como posso te ajudar hoje?", | |
| "Oi! O que você gostaria de fazer?", | |
| "Saudações! Pronto para auxiliar com dados em tempo real." | |
| ], | |
| getTime: () => { | |
| const now = new Date(); | |
| const options = { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| timeZone: userProfile.location.timezone | |
| }; | |
| return `Agora são exatamente ${now.toLocaleTimeString('pt-BR', options)}.`; | |
| }, | |
| getDate: () => { | |
| const now = new Date(); | |
| const options = { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric', | |
| timeZone: userProfile.location.timezone | |
| }; | |
| return `Hoje é ${now.toLocaleDateString('pt-BR', options)}.`; | |
| }, | |
| getWeather: async (location = userProfile.location.city) => { | |
| if (!userProfile.preferences.apis.weather) { | |
| return "O serviço de previsão do tempo está desativado nas configurações."; | |
| } | |
| try { | |
| // First get coordinates for the location | |
| const geoResponse = await fetch(`${API_ENDPOINTS.geocoding}?name=${encodeURIComponent(location)}`); | |
| const geoData = await geoResponse.json(); | |
| if (!geoData.results || geoData.results.length === 0) { | |
| return `Não consegui encontrar a localização ${location}.`; | |
| } | |
| const firstResult = geoData.results[0]; | |
| const lat = firstResult.latitude; | |
| const lon = firstResult.longitude; | |
| // Get weather data | |
| const weatherResponse = await fetch(`${API_ENDPOINTS.weather}?latitude=${lat}&longitude=${lon}&hourly=temperature_2m,weathercode&timezone=America/Sao_Paulo`); | |
| const weatherData = await weatherResponse.json(); | |
| if (!weatherResponse.ok) throw new Error(weatherData.reason || "Erro na API de clima"); | |
| // Get current temperature | |
| const now = new Date(); | |
| const currentHour = now.getHours(); | |
| const currentTemp = weatherData.hourly.temperature_2m[currentHour]; | |
| const weatherCode = weatherData.hourly.weathercode[currentHour]; | |
| // Weather code to text mapping | |
| const weatherTypes = { | |
| 0: "céu limpo", | |
| 1: "parcialmente nublado", | |
| 2: "nublado", | |
| 3: "nublado", | |
| 45: "nevoeiro", | |
| 51: "chuvisco", | |
| 53: "chuvisco", | |
| 55: "chuvisco", | |
| 56: "chuvisco congelante", | |
| 57: "chuvisco congelante", | |
| 61: "chuva leve", | |
| 63: "chuva moderada", | |
| 65: "chuva forte", | |
| 66: "chuva congelante", | |
| 67: "chuva congelante", | |
| 71: "neve leve", | |
| 73: "neve moderada", | |
| 75: "neve forte", | |
| 77: "grãos de neve", | |
| 80: "chuva leve", | |
| 81: "chuva moderada", | |
| 82: "chuva forte", | |
| 85: "neve leve", | |
| 86: "neve forte", | |
| 95: "trovoada", | |
| 96: "trovoada com chuva leve", | |
| 99: "trovoada com chuva forte" | |
| }; | |
| const weatherCondition = weatherTypes[weatherCode] || "condições climáticas variáveis"; | |
| // Add to vector memory | |
| if (userProfile.preferences.memory.vectorEnabled) { | |
| await vectorMemory.addMemory( | |
| `Previsão do tempo em ${location}: ${weatherCondition} com ${currentTemp}°C`, | |
| { type: "weather", location } | |
| ); | |
| } | |
| return `Em ${location}, está ${weatherCondition} com temperatura de ${currentTemp}°C.`; | |
| } catch (error) { | |
| console.error("Erro na API de clima:", error); | |
| return `Não consegui acessar os dados meteorológicos. ${error.message}`; | |
| } | |
| }, | |
| getNews: async (topic = "tecnologia") => { | |
| if (!userProfile.preferences.apis.news) { | |
| return "O serviço de notícias está desativado nas configurações."; | |
| } | |
| try { | |
| // Using NewsAPI (requires key in production) | |
| const response = await fetch(`https://newsapi.org/v2/everything?q=${topic}&language=pt&pageSize=3&apiKey=${API_KEYS.news}`); | |
| const data = await response.json(); | |
| if (data.status !== "ok") throw new Error(data.message || "Erro na API de notícias"); | |
| if (data.articles.length === 0) { | |
| return `Não encontrei notícias recentes sobre ${topic}.`; | |
| } | |
| let newsText = `Aqui estão as últimas notícias sobre ${topic}:\n\n`; | |
| data.articles.forEach((article, index) => { | |
| newsText += `${index + 1}. ${article.title}\nFonte: ${article.source.name}\n\n`; | |
| // Add to vector memory | |
| if (userProfile.preferences.memory.vectorEnabled) { | |
| vectorMemory.addMemory( | |
| `Notícia sobre ${topic}: ${article.title}`, | |
| { type: "news", source: article.source.name } | |
| ); | |
| } | |
| }); | |
| return newsText; | |
| } catch (error) { | |
| console.error("Erro na API de notícias:", error); | |
| return `Não consegui acessar as últimas notícias. ${error.message}`; | |
| } | |
| }, | |
| getFinance: async (currency = "USD") => { | |
| if (!userProfile.preferences.apis.finance) { | |
| return "O serviço de cotações está desativado nas configurações."; | |
| } | |
| try { | |
| // Using ExchangeRate-API (free tier) | |
| const response = await fetch(API_ENDPOINTS.finance); | |
| const data = await response.json(); | |
| if (!data.rates) throw new Error("Erro na API de cotações"); | |
| const brlRate = data.rates.BRL; | |
| const eurRate = data.rates.EUR; | |
| // Add to vector memory | |
| if (userProfile.preferences.memory.vectorEnabled) { | |
| await vectorMemory.addMemory( | |
| `Cotação atual: Dólar a R$${brlRate.toFixed(2)}, Euro a R$${(brlRate / eurRate).toFixed(2)}`, | |
| { type: "finance" } | |
| ); | |
| } | |
| return `Cotações atuais:\n- Dólar (USD): R$ ${brlRate.toFixed(2)}\n- Euro (EUR): R$ ${(brlRate / eurRate).toFixed(2)}`; | |
| } catch (error) { | |
| console.error("Erro na API de finanças:", error); | |
| return `Não consegui acessar as cotações. ${error.message}`; | |
| } | |
| }, | |
| translateText: async (text, targetLang = "pt") => { | |
| if (!userProfile.preferences.apis.translate) { | |
| return "O serviço de tradução está desativado nas configurações."; | |
| } | |
| try { | |
| // Using LibreTranslate (free and open source) | |
| const response = await fetch(API_ENDPOINTS.translate, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify({ | |
| q: text, | |
| source: "auto", | |
| target: targetLang | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (!data.translatedText) throw new Error("Erro na API de tradução"); | |
| // Add to vector memory | |
| if (userProfile.preferences.memory.vectorEnabled) { | |
| await vectorMemory.addMemory( | |
| `Tradução: "${text}" para "${data.translatedText}"`, | |
| { type: "translation", source: "auto", target: targetLang } | |
| ); | |
| } | |
| return `Tradução:\nOriginal: ${text}\nTraduzido: ${data.translatedText}`; | |
| } catch (error) { | |
| console.error("Erro na API de tradução:", error); | |
| return `Não consegui traduzir o texto. ${error.message}`; | |
| } | |
| }, | |
| searchWikipedia: async (query) => { | |
| try { | |
| const response = await fetch(`https://pt.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=${encodeURIComponent(query)}&utf8=&origin=*`); | |
| const data = await response.json(); | |
| if (!data.query || data.query.search.length === 0) { | |
| return `Não encontrei informações sobre "${query}" na Wikipedia.`; | |
| } | |
| const results = data.query.search.slice(0, 3); | |
| let responseText = `Aqui está o que encontrei sobre "${query}":\n\n`; | |
| results.forEach(result => { | |
| responseText += `• ${result.title}\n`; | |
| // Add to vector memory | |
| if (userProfile.preferences.memory.vectorEnabled) { | |
| vectorMemory.addMemory( | |
| `Informação da Wikipedia: ${result.title}`, | |
| { type: "wikipedia", query } | |
| ); | |
| } | |
| }); | |
| responseText += `\nPosso buscar mais detalhes sobre algum desses tópicos.`; | |
| return responseText; | |
| } catch (error) { | |
| console.error("Erro na Wikipedia API:", error); | |
| return `Não consegui acessar a Wikipedia. ${error.message}`; | |
| } | |
| }, | |
| getContextualResponse: async (message) => { | |
| if (!userProfile.preferences.memory.vectorEnabled) { | |
| return null; | |
| } | |
| // Get similar memories | |
| const similarMemories = await vectorMemory.findSimilar( | |
| message, | |
| 3, | |
| userProfile.preferences.memory.similarityThreshold | |
| ); | |
| if (similarMemories.length === 0) { | |
| return null; | |
| } | |
| // Build contextual response | |
| let response = "Com base no que lembro:\n\n"; | |
| similarMemories.forEach((memory, index) => { | |
| response += `${index + 1}. ${memory.text} (similaridade: ${Math.round(memory.similarity * 100)}%)\n`; | |
| }); | |
| return response; | |
| }, | |
| unknown: [ | |
| "Não tenho informações sobre isso no meu banco de dados. Posso pesquisar quando você estiver online.", | |
| "Minha memória local não contém dados sobre esse assunto. Quer que eu anote para pesquisar depois?", | |
| "Isso está além do meu conhecimento atual. Posso aprender se você me explicar." | |
| ] | |
| }; | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const voiceBtn = document.getElementById('voice-btn'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const messageInput = document.getElementById('message-input'); | |
| const chatMessages = document.getElementById('chat-messages'); | |
| const typingIndicator = document.createElement('div'); | |
| typingIndicator.className = 'typing-indicator iris-message'; | |
| typingIndicator.innerHTML = ` | |
| <div class="flex items-center"> | |
| <div class="w-6 h-6 rounded-full bg-pink-500 mr-2 flex items-center justify-center neon-border-pink"> | |
| <i class="fas fa-robot text-xs"></i> | |
| </div> | |
| <div class="flex space-x-1"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| </div> | |
| `; | |
| // Memory panel elements | |
| const memoryBtn = document.getElementById('memory-btn'); | |
| const memoryPanel = document.getElementById('memory-panel'); | |
| const closeMemory = document.getElementById('close-memory'); | |
| const exportVectors = document.getElementById('export-vectors'); | |
| const clearContext = document.getElementById('clear-context'); | |
| // Settings panel elements | |
| const settingsBtn = document.getElementById('settings-btn'); | |
| const settingsPanel = document.getElementById('settings-panel'); | |
| const closeSettings = document.getElementById('close-settings'); | |
| const saveSettings = document.getElementById('save-settings'); | |
| // Voice level indicator | |
| const voiceLevel = document.getElementById('voice-level'); | |
| // API status indicators | |
| const apiStatusElements = { | |
| weather: document.getElementById('weather-api-status'), | |
| news: document.getElementById('news-api-status'), | |
| finance: document.getElementById('finance-api-status'), | |
| translate: document.getElementById('translate-api-status') | |
| }; | |
| // Initialize UI with user preferences | |
| updateMemoryUsage(); | |
| updateSystemStats(); | |
| checkAPIs(); | |
| // Initialize vector memory | |
| vectorMemory.init().then(() => { | |
| console.log("Vector memory initialized"); | |
| updateMemoryPanel(); | |
| }); | |
| // Memory panel handlers | |
| memoryBtn.addEventListener('click', () => { | |
| updateMemoryPanel(); | |
| memoryPanel.classList.remove('hidden'); | |
| }); | |
| closeMemory.addEventListener('click', () => { | |
| memoryPanel.classList.add('hidden'); | |
| }); | |
| exportVectors.addEventListener('click', () => { | |
| const stats = vectorMemory.getMemoryStats(); | |
| addIrisResponse(`Exportando ${stats.count} vetores de memória com similaridade média de ${Math.round(stats.avgSimilarity * 100)}%`); | |
| memoryPanel.classList.add('hidden'); | |
| }); | |
| clearContext.addEventListener('click', () => { | |
| userProfile.context = []; | |
| updateMemoryPanel(); | |
| addIrisResponse("Contexto atual limpo com sucesso."); | |
| }); | |
| // Settings panel handlers | |
| settingsBtn.addEventListener('click', () => { | |
| loadSettings(); | |
| settingsPanel.classList.remove('hidden'); | |
| }); | |
| closeSettings.addEventListener('click', () => { | |
| settingsPanel.classList.add('hidden'); | |
| }); | |
| saveSettings.addEventListener('click', () => { | |
| saveUserPreferences(); | |
| settingsPanel.classList.add('hidden'); | |
| checkAPIs(); | |
| }); | |
| // Similarity threshold slider | |
| document.getElementById('similarity-threshold').addEventListener('input', function() { | |
| document.getElementById('similarity-value').textContent = `${this.value}%`; | |
| }); | |
| // Voice button handler with realistic simulation | |
| voiceBtn.addEventListener('mousedown', async function() { | |
| this.classList.remove('glow-effect'); | |
| this.classList.add('neon-border-pink', 'neon-text-pink'); | |
| this.innerHTML = '<i class="fas fa-circle-notch fa-spin text-2xl"></i>'; | |
| // Simulate voice level detection | |
| const voiceSimulation = setInterval(() => { | |
| const level = Math.floor(Math.random() * 30) + 20; | |
| voiceLevel.style.width = `${level}%`; | |
| }, 100); | |
| // Simulate listening for 1-3 seconds | |
| const listenTime = 1000 + Math.random() * 2000; | |
| setTimeout(async () => { | |
| clearInterval(voiceSimulation); | |
| voiceLevel.style.width = '0%'; | |
| // Process voice command | |
| const commands = [ | |
| "Previsão do tempo em São Paulo", | |
| "Notícias sobre tecnologia", | |
| "Cotação do dólar hoje", | |
| "Traduzir 'hello' para português", | |
| "Quais são as últimas notícias?", | |
| "Como está o clima no Rio de Janeiro?" | |
| ]; | |
| const randomCommand = commands[Math.floor(Math.random() * commands.length)]; | |
| addUserMessage(randomCommand); | |
| // Process with realistic delay | |
| setTimeout(async () => { | |
| await processUserMessage(randomCommand); | |
| this.classList.add('glow-effect'); | |
| this.classList.remove('neon-border-pink', 'neon-text-pink'); | |
| this.innerHTML = '<i class="fas fa-microphone text-2xl"></i>'; | |
| }, 500); | |
| }, listenTime); | |
| }); | |
| // Send message handler | |
| sendBtn.addEventListener('click', sendMessage); | |
| messageInput.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| sendMessage(); | |
| } | |
| }); | |
| // Quick actions | |
| document.querySelectorAll('.quick-action').forEach(button => { | |
| button.addEventListener('click', function() { | |
| const command = this.getAttribute('data-command'); | |
| addUserMessage(command); | |
| processUserMessage(command); | |
| }); | |
| }); | |
| // Volume slider | |
| document.getElementById('volume-slider').addEventListener('input', function() { | |
| document.getElementById('volume-value').textContent = `${this.value}%`; | |
| }); | |
| function sendMessage() { | |
| const message = messageInput.value.trim(); | |
| if (message) { | |
| addUserMessage(message); | |
| processUserMessage(message); | |
| messageInput.value = ''; | |
| } | |
| } | |
| function addUserMessage(message) { | |
| const messageElement = document.createElement('div'); | |
| messageElement.className = 'message-bubble user-message new-message'; | |
| messageElement.innerHTML = ` | |
| <div class="flex items-start"> | |
| <div class="w-6 h-6 rounded-full bg-blue-500 mr-2 flex items-center justify-center neon-border-blue"> | |
| <i class="fas fa-user text-xs"></i> | |
| </div> | |
| <p>${message}</p> | |
| </div> | |
| `; | |
| chatMessages.appendChild(messageElement); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| // Add to context | |
| userProfile.context.push({ | |
| role: "user", | |
| content: message, | |
| timestamp: new Date().toISOString() | |
| }); | |
| // Keep only the most recent context | |
| if (userProfile.context.length > userProfile.preferences.memory.contextLimit) { | |
| userProfile.context.shift(); | |
| } | |
| // Add to history | |
| if (userProfile.preferences.privacy.storeHistory) { | |
| userProfile.history.push({ | |
| date: new Date().toLocaleString('pt-BR'), | |
| message: message | |
| }); | |
| // Keep only last 50 messages | |
| if (userProfile.history.length > 50) { | |
| userProfile.history.shift(); | |
| } | |
| } | |
| // Update memory usage | |
| updateMemoryUsage(); | |
| } | |
| async function processUserMessage(message) { | |
| // Show typing indicator | |
| chatMessages.appendChild(typingIndicator); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| // Measure response time | |
| const startTime = performance.now(); | |
| // Process after random delay to simulate thinking | |
| setTimeout(async () => { | |
| chatMessages.removeChild(typingIndicator); | |
| let response = ""; | |
| const lowerMsg = message.toLowerCase(); | |
| // First check for contextual response | |
| if (userProfile.preferences.memory.vectorEnabled) { | |
| const contextualResponse = await knowledgeBase.getContextualResponse(message); | |
| if (contextualResponse) { | |
| response = contextualResponse + "\n\n"; | |
| } | |
| } | |
| // Enhanced NLP processing with API integration | |
| if (/oi|olá|ola|eae|bom dia|boa tarde|boa noite/.test(lowerMsg)) { | |
| response += knowledgeBase.greetings[Math.floor(Math.random() * knowledgeBase.greetings.length)]; | |
| } | |
| else if (/hora|horas|relógio/.test(lowerMsg)) { | |
| response += knowledgeBase.getTime(); | |
| } | |
| else if (/data|dia|hoje|calendário/.test(lowerMsg)) { | |
| response += knowledgeBase.getDate(); | |
| } | |
| else if (/tempo|clima|previsão/.test(lowerMsg)) { | |
| const locationMatch = message.match(/em (.+)/i); | |
| response += await knowledgeBase.getWeather(locationMatch ? locationMatch[1] : userProfile.location.city); | |
| } | |
| else if (/notícia|noticia|novidade|notícias|noticias/.test(lowerMsg)) { | |
| const topicMatch = message.match(/sobre (.+)/i); | |
| response += await knowledgeBase.getNews(topicMatch ? topicMatch[1] : "tecnologia"); | |
| } | |
| else if (/dólar|dolar|euro|moeda|finança|financa|cotação|cotacao/.test(lowerMsg)) { | |
| response += await knowledgeBase.getFinance(); | |
| } | |
| else if (/traduzir|tradução|traducao|inglês|ingles|português|portugues/.test(lowerMsg)) { | |
| const textMatch = message.match(/traduzir ['"](.+)['"]/i) || | |
| message.match(/traduzir (.+) para/i); | |
| if (textMatch) { | |
| const langMatch = message.match(/para (.+)/i); | |
| const targetLang = langMatch ? | |
| (langMatch[1].includes("português") ? "pt" : "en") : | |
| "pt"; | |
| response += await knowledgeBase.translateText(textMatch[1], targetLang); | |
| } else { | |
| response += "Por favor, especifique o texto a ser traduzido. Exemplo: \"Traduzir 'hello' para português\""; | |
| } | |
| } | |
| else if (/wikipedia|wiki|pesquisar|buscar|sobre/.test(lowerMsg)) { | |
| const queryMatch = message.match(/sobre (.+)/i) || | |
| message.match(/pesquisar (.+)/i) || | |
| message.match(/buscar (.+)/i); | |
| if (queryMatch) { | |
| response += await knowledgeBase.searchWikipedia(queryMatch[1]); | |
| } else { | |
| response += "Sobre o que você gostaria que eu pesquisasse na Wikipedia?"; | |
| } | |
| } | |
| else { | |
| response += knowledgeBase.unknown[Math.floor(Math.random() * knowledgeBase.unknown.length)]; | |
| } | |
| // Add to context | |
| userProfile.context.push({ | |
| role: "assistant", | |
| content: response, | |
| timestamp: new Date().toISOString() | |
| }); | |
| // Count API request | |
| if (lowerMsg.includes("tempo") || lowerMsg.includes("notícia") || | |
| lowerMsg.includes("dólar") || lowerMsg.includes("traduzir") || | |
| lowerMsg.includes("pesquisar") || lowerMsg.includes("wiki")) { | |
| userProfile.apiRequests++; | |
| document.getElementById('api-requests').textContent = userProfile.apiRequests; | |
| } | |
| addIrisResponse(response); | |
| // Update response time in footer | |
| const endTime = performance.now(); | |
| const responseTime = ((endTime - startTime) / 1000).toFixed(1); | |
| document.getElementById('response-time').textContent = responseTime; | |
| // Update memory panel | |
| updateMemoryPanel(); | |
| }, 800 + Math.random() * 1200); | |
| } | |
| function addIrisResponse(response) { | |
| const messageElement = document.createElement('div'); | |
| messageElement.className = 'message-bubble iris-message new-message'; | |
| messageElement.innerHTML = ` | |
| <div class="flex items-start"> | |
| <div class="w-6 h-6 rounded-full bg-pink-500 mr-2 flex items-center justify-center neon-border-pink"> | |
| <i class="fas fa-robot text-xs"></i> | |
| </div> | |
| <p>${response.replace(/\n/g, '<br>')}</p> | |
| </div> | |
| `; | |
| chatMessages.appendChild(messageElement); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| // Simulate voice output if enabled | |
| if (userProfile.preferences.voice.active) { | |
| speakResponse(response); | |
| } | |
| } | |
| function speakResponse(text) { | |
| // Clean text from HTML tags and special formatting | |
| const cleanText = text.replace(/<[^>]*>/g, '').replace(/\([^)]*\)/g, ''); | |
| if ('speechSynthesis' in window) { | |
| // Cancel any ongoing speech | |
| window.speechSynthesis.cancel(); | |
| const utterance = new SpeechSynthesisUtterance(); | |
| utterance.text = cleanText; | |
| utterance.lang = 'pt-BR'; | |
| utterance.rate = userProfile.preferences.voice.speed; | |
| utterance.volume = userProfile.preferences.voice.volume / 100; | |
| // Try to find a Brazilian Portuguese voice | |
| const voices = window.speechSynthesis.getVoices(); | |
| const ptBrVoice = voices.find(v => v.lang === 'pt-BR') || voices.find(v => v.lang.startsWith('pt')); | |
| if (ptBrVoice) { | |
| utterance.voice = ptBrVoice; | |
| } | |
| window.speechSynthesis.speak(utterance); | |
| } | |
| } | |
| function updateMemoryPanel() { | |
| // Update current context | |
| const contextContainer = document.getElementById('current-context'); | |
| const recentContext = userProfile.context.slice(-5).reverse(); | |
| contextContainer.innerHTML = recentContext.length > 0 | |
| ? recentContext.map(item => | |
| `<div class="context-chip"> | |
| <i class="fas fa-${item.role === 'user' ? 'user' : 'robot'}"></i> | |
| ${item.content.substring(0, 20)}${item.content.length > 20 ? '...' : ''} | |
| </div>` | |
| ).join('') | |
| : '<p class="text-gray-400 text-sm">Nenhum contexto recente</p>'; | |
| // Update knowledge graph (simplified for demo) | |
| const knowledgeContainer = document.getElementById('knowledge-graph'); | |
| const memoryStats = vectorMemory.getMemoryStats(); | |
| // Show some sample knowledge nodes (in a real app would show actual vectors) | |
| const sampleNodes = [ | |
| { text: "Clima em SP", icon: "fa-cloud-sun" }, | |
| { text: "Notícias tech", icon: "fa-newspaper" }, | |
| { text: "Cotações", icon: "fa-dollar-sign" }, | |
| { text: "Traduções", icon: "fa-language" }, | |
| { text: "Wikipedia", icon: "fa-book" } | |
| ]; | |
| knowledgeContainer.innerHTML = sampleNodes.map(node => | |
| `<div class="knowledge-node"> | |
| <i class="fas ${node.icon}"></i> | |
| ${node.text} | |
| <span class="vector-score">${Math.floor(Math.random() * 30) + 70}%</span> | |
| </div>` | |
| ).join(''); | |
| // Update system stats | |
| document.getElementById('vector-count').textContent = memoryStats.count; | |
| document.getElementById('avg-similarity').textContent = `${Math.round(memoryStats.avgSimilarity * 100)}%`; | |
| document.getElementById('api-requests').textContent = userProfile.apiRequests; | |
| } | |
| function updateMemoryUsage() { | |
| // Simulate memory usage based on history size | |
| const baseUsage = 40; | |
| const historyFactor = Math.min(userProfile.history.length / 50 * 30, 30); | |
| const apiFactor = Math.min(userProfile.apiRequests / 20 * 20, 20); | |
| const vectorFactor = vectorMemory.vectors.length / 100 * 10; | |
| const randomFactor = Math.floor(Math.random() * 10); | |
| const totalUsage = Math.min(baseUsage + historyFactor + apiFactor + vectorFactor + randomFactor, 95); | |
| document.getElementById('memory-usage').textContent = Math.floor(totalUsage); | |
| } | |
| function updateSystemStats() { | |
| // Simulate connection status | |
| const isOnline = Math.random() > 0.1; // 90% chance of being online | |
| document.getElementById('connection-status').textContent = isOnline ? 'CONECTADO' : 'OFFLINE'; | |
| document.getElementById('connection-status').className = isOnline | |
| ? 'text-green-400' | |
| : 'text-red-400'; | |
| // Update every 30 seconds | |
| setTimeout(updateSystemStats, 30000); | |
| } | |
| async function checkAPIs() { | |
| // Check weather API | |
| try { | |
| const response = await fetch(`${API_ENDPOINTS.weather}?latitude=0&longitude=0`); | |
| apiStatusElements.weather.className = response.ok ? | |
| 'api-status api-active' : 'api-status api-inactive'; | |
| } catch { | |
| apiStatusElements.weather.className = 'api-status api-inactive'; | |
| } | |
| // Check news API (simulated) | |
| apiStatusElements.news.className = userProfile.preferences.apis.news ? | |
| 'api-status api-active' : 'api-status api-inactive'; | |
| // Check finance API | |
| try { | |
| const response = await fetch(API_ENDPOINTS.finance); | |
| apiStatusElements.finance.className = response.ok ? | |
| 'api-status api-active' : 'api-status api-inactive'; | |
| } catch { | |
| apiStatusElements.finance.className = 'api-status api-inactive'; | |
| } | |
| // Check translate API | |
| try { | |
| const response = await fetch(API_ENDPOINTS.translate); | |
| apiStatusElements.translate.className = response.ok ? | |
| 'api-status api-active' : 'api-status api-inactive'; | |
| } catch { | |
| apiStatusElements.translate.className = 'api-status api-inactive'; | |
| } | |
| } | |
| function loadSettings() { | |
| // Voice settings | |
| document.getElementById('voice-toggle').checked = userProfile.preferences.voice.active; | |
| document.getElementById('volume-slider').value = userProfile.preferences.voice.volume; | |
| document.getElementById('volume-value').textContent = `${userProfile.preferences.voice.volume}%`; | |
| document.getElementById('voice-speed').value = userProfile.preferences.voice.speed; | |
| // Memory settings | |
| document.getElementById('vector-memory-toggle').checked = userProfile.preferences.memory.vectorEnabled; | |
| document.getElementById('context-limit').value = userProfile.preferences.memory.contextLimit; | |
| document.getElementById('similarity-threshold').value = userProfile.preferences.memory.similarityThreshold * 100; | |
| document.getElementById('similarity-value').textContent = `${userProfile.preferences.memory.similarityThreshold * 100}%`; | |
| // API settings | |
| document.getElementById('weather-api-toggle').checked = userProfile.preferences.apis.weather; | |
| document.getElementById('news-api-toggle').checked = userProfile.preferences.apis.news; | |
| document.getElementById('finance-api-toggle').checked = userProfile.preferences.apis.finance; | |
| document.getElementById('translate-api-toggle').checked = userProfile.preferences.apis.translate; | |
| // Privacy settings | |
| document.getElementById('store-history-toggle').checked = userProfile.preferences.privacy.storeHistory; | |
| document.getElementById('share-data-toggle').checked = userProfile.preferences.privacy.shareData; | |
| } | |
| function saveUserPreferences() { | |
| // Voice settings | |
| userProfile.preferences.voice.active = document.getElementById('voice-toggle').checked; | |
| userProfile.preferences.voice.volume = parseInt(document.getElementById('volume-slider').value); | |
| userProfile.preferences.voice.speed = parseFloat(document.getElementById('voice-speed').value); | |
| // Memory settings | |
| userProfile.preferences.memory.vectorEnabled = document.getElementById('vector-memory-toggle').checked; | |
| userProfile.preferences.memory.contextLimit = parseInt(document.getElementById('context-limit').value); | |
| userProfile.preferences.memory.similarityThreshold = parseInt(document.getElementById('similarity-threshold').value) / 100; | |
| // API settings | |
| userProfile.preferences.apis.weather = document.getElementById('weather-api-toggle').checked; | |
| userProfile.preferences.apis.news = document.getElementById('news-api-toggle').checked; | |
| userProfile.preferences.apis.finance = document.getElementById('finance-api-toggle').checked; | |
| userProfile.preferences.apis.translate = document.getElementById('translate-api-toggle').checked; | |
| // Privacy settings | |
| userProfile.preferences.privacy.storeHistory = document.getElementById('store-history-toggle').checked; | |
| userProfile.preferences.privacy.shareData = document.getElementById('share-data-toggle').checked; | |
| // Show confirmation | |
| addIrisResponse("Configurações atualizadas com sucesso!"); | |
| } | |
| // Initialize speech synthesis voices | |
| if ('speechSynthesis' in window) { | |
| speechSynthesis.onvoiceschanged = function() { | |
| const voices = speechSynthesis.getVoices(); | |
| console.log('Voices loaded:', voices); | |
| }; | |
| // Chrome needs this to load voices | |
| if (speechSynthesis.getVoices().length === 0) { | |
| speechSynthesis.addEventListener('voiceschanged', function() { | |
| const voices = speechSynthesis.getVoices(); | |
| console.log('Voices loaded:', voices); | |
| }); | |
| } | |
| } | |
| // Add initial learning message after 5 seconds | |
| setTimeout(() => { | |
| addIrisResponse("Estou conectada a várias APIs gratuitas e possuo memória vetorial para lembrar do contexto da nossa conversa. Experimente perguntar sobre diversos assuntos!"); | |
| }, 5000); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Caiobriaego/iriis-py" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |