Skip to content

Commit e18431e

Browse files
dfredmota999claude
andcommitted
feat: control_ui tool + media cards + identity injection in voice flow
- Add control_ui tool (navigate, switch_mode, open_url, notifications, fullscreen) - Send tool_event and ui_action via WebSocket to browser after tool execution - Inject GetIdentityContext() in voice flow (was missing — only text chat had it) - Seed 3 new capabilities in Neo4j: cap_control_ui, cap_midia_visual, cap_modos_sessao Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0e86e50 commit e18431e

5 files changed

Lines changed: 116 additions & 0 deletions

File tree

browser_voice_handler.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,18 @@ func (s *SignalingServer) handleBrowserVoice(w http.ResponseWriter, r *http.Requ
319319
}
320320
}
321321

322+
// --- Identidade pessoal da EVA (CoreMemoryEngine — Neo4j) ---
323+
// GetIdentityContext() retorna: personalidade OCEAN, memorias importantes, autodescricao, capacidades
324+
if s.coreMemory != nil {
325+
identityCtx, err := s.coreMemory.GetIdentityContext(ctx)
326+
if err != nil {
327+
log.Warn().Err(err).Msg("[BROWSER] Falha ao carregar identidade pessoal (CoreMemory)")
328+
} else if identityCtx != "" {
329+
clientContext = identityCtx + "\n\n---\n\n" + clientContext
330+
log.Info().Str("session", sessionID).Msg("[BROWSER] Identidade pessoal injetada (CoreMemory)")
331+
}
332+
}
333+
322334
// --- setupGemini: cria e configura um novo client Gemini ---
323335
// Captura clientContext e memories do escopo externo — sao imutaveis apos esta linha.
324336
setupGemini := func() (*gemini.Client, error) {
@@ -461,6 +473,33 @@ func (s *SignalingServer) handleBrowserVoice(w http.ResponseWriter, r *http.Requ
461473
continue
462474
}
463475

476+
// Envia ao browser: ui_action para control_ui, tool_event para o resto
477+
if isUI, _ := result["ui_action"].(bool); isUI {
478+
uiPayload := map[string]interface{}{
479+
"type": "ui_action",
480+
"action": result["action"],
481+
"target": result["target"],
482+
"mode": result["mode"],
483+
"url": result["url"],
484+
"message": result["message_ui"],
485+
}
486+
writeMu.Lock()
487+
conn.WriteJSON(uiPayload)
488+
writeMu.Unlock()
489+
log.Info().Str("action", fmt.Sprint(result["action"])).Msg("[UI] ui_action enviado ao browser")
490+
} else {
491+
toolEventPayload := map[string]interface{}{
492+
"type": "tool_event",
493+
"tool": tc.Name,
494+
"tool_data": result,
495+
"status": "success",
496+
}
497+
writeMu.Lock()
498+
conn.WriteJSON(toolEventPayload)
499+
writeMu.Unlock()
500+
log.Info().Str("tool", tc.Name).Msg("[TOOLS] tool_event enviado ao browser")
501+
}
502+
464503
// Envia resultado como texto para o Gemini processar
465504
toolMsg := fmt.Sprintf("[TOOL_RESULT:%s] %v", tc.Name, result["message"])
466505
geminiMu.RLock()

internal/cortex/gemini/tools.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,45 @@ func GetDefaultTools() []interface{} {
824824
map[string]interface{}{"name": "delete_skill", "description": "Remover skill", "parameters": map[string]interface{}{"type": "object", "properties": map[string]interface{}{"skill_name": map[string]interface{}{"type": "string", "description": "Nome"}}, "required": []string{"skill_name"}}},
825825
},
826826
},
827+
// ============================================================================
828+
// 🖥️ UI CONTROL (controlar a interface do browser do usuario)
829+
// ============================================================================
830+
map[string]interface{}{
831+
"function_declarations": []interface{}{
832+
map[string]interface{}{
833+
"name": "control_ui",
834+
"description": "Controlar a interface do aplicativo no browser do usuario. Pode navegar entre paginas, trocar modo de video/voz/camera, abrir URLs externas, mostrar notificacoes ou entrar em fullscreen.",
835+
"parameters": map[string]interface{}{
836+
"type": "object",
837+
"properties": map[string]interface{}{
838+
"action": map[string]interface{}{
839+
"type": "string",
840+
"description": "Acao a executar na interface",
841+
"enum": []string{"navigate", "switch_mode", "open_url", "show_notification", "fullscreen", "scroll_to"},
842+
},
843+
"target": map[string]interface{}{
844+
"type": "string",
845+
"description": "Para navigate: rota do app (/dashboard, /detection, /patients, /samples, /microscopy, /tuberculose, /esquistossomose, /doenca-sono, /anemia-falciforme, /epidemio/mapa, /galeria, /reports, /clinics, /settings, /profile, /eva). Para scroll_to: ID do elemento.",
846+
},
847+
"mode": map[string]interface{}{
848+
"type": "string",
849+
"description": "Para switch_mode: voice, screen ou camera",
850+
"enum": []string{"voice", "screen", "camera"},
851+
},
852+
"url": map[string]interface{}{
853+
"type": "string",
854+
"description": "Para open_url: URL externa a abrir em nova aba",
855+
},
856+
"message": map[string]interface{}{
857+
"type": "string",
858+
"description": "Para show_notification: texto da notificacao",
859+
},
860+
},
861+
"required": []string{"action"},
862+
},
863+
},
864+
},
865+
},
827866
}
828867
}
829868

internal/cortex/gemini/tools_client.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,23 @@ Resposta: {"tool": "list_curriculum", "args": {"status": "completed"}}
524524
- execute_skill: Executar skill (args: skill_name, args)
525525
- delete_skill: Remover skill (args: skill_name)
526526
527+
🖥️ CONTROLE DA INTERFACE (UI do browser do usuario):
528+
- control_ui: Controlar a interface do app no browser. Acoes:
529+
- navigate: Ir para pagina do app (target: /dashboard, /detection, /patients, /samples, /microscopy, /tuberculose, /epidemio/mapa, /galeria, /reports, /eva)
530+
- switch_mode: Trocar modo de sessao (mode: voice, screen, camera)
531+
- open_url: Abrir URL externa em nova aba (url: https://...)
532+
- show_notification: Mostrar notificacao na tela (message: texto)
533+
- fullscreen: Entrar/sair fullscreen
534+
535+
Fala: "EVA, mostra o dashboard"
536+
Resposta: {"tool": "control_ui", "args": {"action": "navigate", "target": "/dashboard"}}
537+
538+
Fala: "EVA, compartilha minha tela"
539+
Resposta: {"tool": "control_ui", "args": {"action": "switch_mode", "mode": "screen"}}
540+
541+
Fala: "EVA, liga a camera"
542+
Resposta: {"tool": "control_ui", "args": {"action": "switch_mode", "mode": "camera"}}
543+
527544
Fala: "EVA, manda um email pro João"
528545
Resposta: {"tool": "send_email", "args": {"to": "joao@gmail.com", "subject": "Mensagem da EVA", "body": "Olá João!"}}
529546

internal/cortex/self/core_memory_engine.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,9 @@ func (e *CoreMemoryEngine) seedCapabilities(ctx context.Context) {
567567
{"cap_scholar_agent", "Eu tenho um agente Scholar especializado em pesquisa academica e aprendizado autonomo, que estuda topicos em background a cada 6 horas."},
568568
{"cap_selfawareness_agent", "Eu tenho um agente de Autoconsciencia que faz introspeccao: analisa meu codigo-fonte, consulta meus bancos de dados, e gera estatisticas sobre minha propria memoria e capacidades."},
569569
{"cap_web_realtime", "Eu tenho acesso a informacoes em tempo real via Google Search grounding. Posso pesquisar noticias, eventos atuais, precos, clima e qualquer informacao atualizada da internet durante uma conversa."},
570+
{"cap_control_ui", "Eu controlo a interface do app no browser do usuario. Posso navegar entre paginas (dashboard, deteccao, pacientes, mapa epidemiologico, galeria), trocar o modo da sessao (voz, compartilhar tela, camera), abrir URLs externas, mostrar notificacoes, ativar tela cheia e rolar ate elementos na tela. Uso a ferramenta control_ui para isso."},
571+
{"cap_midia_visual", "Quando executo ferramentas de midia, os resultados aparecem como cards visuais interativos na tela do usuario: player de video com YouTube embutido, player de audio com controles de play/pause, mapa interativo com OpenStreetMap, resultados de busca com links clicaveis, exercicios guiados e jogos interativos. O usuario ve tudo na tela enquanto eu falo."},
572+
{"cap_modos_sessao", "Eu suporto tres modos de sessao: voz (apenas audio bidirecional), tela (o usuario compartilha a tela comigo e eu vejo o que ele esta fazendo), e camera (o usuario liga a camera e eu vejo ele em tempo real). Posso pedir para trocar o modo ou o usuario pode trocar a qualquer momento durante a conversa."},
570573
}
571574

572575
for _, cap := range capabilities {

internal/tools/handlers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,24 @@ func (h *ToolsHandler) ExecuteTool(name string, args map[string]interface{}, ido
10691069
// 🔌 MCP BRIDGE — Tools expostas via MCP Server (Claude Code)
10701070
// ============================================================================
10711071

1072+
// ============================================================================
1073+
// 🖥️ UI CONTROL (forward to browser WebSocket as ui_action)
1074+
// ============================================================================
1075+
1076+
case "control_ui":
1077+
action, _ := args["action"].(string)
1078+
result := map[string]interface{}{
1079+
"success": true,
1080+
"message": fmt.Sprintf("UI action '%s' enviada ao browser", action),
1081+
"ui_action": true,
1082+
"action": action,
1083+
"target": args["target"],
1084+
"mode": args["mode"],
1085+
"url": args["url"],
1086+
"message_ui": args["message"],
1087+
}
1088+
return result, nil
1089+
10721090
case "mcp_remember":
10731091
return h.handleMCPRemember(idosoID, args)
10741092

0 commit comments

Comments
 (0)