feat: Samsung lock screen face widget, voice assistant services, PiP mode and gitignore installers

Add Samsung proprietary Face Widget (lock screen/AOD) with terminal status display.
Add voice interaction services (AgentVoiceInteractionService, RecognitionService) for
digital assistant registration. Add PiP mode with voice/expand actions. Add session-state
proxy, voice transcript routes, window controls component. Ignore installers/ directory.
This commit is contained in:
2026-02-23 20:52:11 -06:00
parent e1aa8b1bdb
commit 65303df96a
35 changed files with 2640 additions and 484 deletions

View File

@@ -11,6 +11,7 @@ import TerminalFabStack from './components/transcript-debug/TerminalFabStack.vue
import PwaInstallBanner from './components/PwaInstallBanner.vue'
import HooksApprovalModal from './components/HooksApprovalModal.vue'
import ServerConfigDialog from './components/ServerConfigDialog.vue'
import WindowControls from './components/WindowControls.vue'
import { useGlobalApproval } from './composables/useGlobalApproval'
import { initWebMCP, getWebMCP } from './services/webmcp'
import { initTorch, destroyTorch } from './services/torch'
@@ -20,7 +21,7 @@ import { setResponseControls } from './services/tools/handlers/responseHandlers'
import { useCanvasStore } from './stores/canvas'
import { useProjectCanvasStore } from './stores/projectCanvas'
import { useSessionState } from './stores/session-state'
import { isTauri } from './lib/tauri'
import { isTauri, isMobileTauri, getTauriNotification } from './lib/tauri'
import { useServerConfig } from './stores/server-config'
const route = useRoute()
@@ -35,7 +36,7 @@ const showVoice = ref(false)
const showTranscriptDebug = ref(false)
const showDebugConsole = ref(false)
const toolbarVisible = ref(true)
const forceWco = ref(false)
const forceWco = ref(isTauri && !isMobileTauri())
const debugLogs = ref<Array<{ type: string; message: string; time: string }>>([])
// Intercept console.log for debug panel
@@ -222,6 +223,31 @@ function syncThemeColor() {
}
onMounted(async () => {
// Bridge for Android widget navigation (called from MainActivity via evaluateJavascript)
;(window as any).__WIDGET_NAVIGATE__ = (route: string) => {
router.push(route)
return true
}
// Bridge for Android voice assistant — opens FloatingTranscriptDebug on the target terminal
;(window as any).__VOICE_OPEN_TERMINAL__ = (ephemeralSessionId: string) => {
const entry = sessionState.terminalRegistry.find(
t => t.ephemeralSessionId === ephemeralSessionId
)
if (entry && transcriptDebugRef.value) {
transcriptDebugRef.value.switchToTerminal(entry.transcriptSessionId)
showTranscriptDebug.value = true
return true
}
// Fallback: open on first terminal
if (sessionState.terminalRegistry.length && transcriptDebugRef.value) {
transcriptDebugRef.value.switchToTerminal(sessionState.terminalRegistry[0].transcriptSessionId)
showTranscriptDebug.value = true
return true
}
return false
}
// Sync Windows titlebar color with CSS variable
syncThemeColor()
@@ -287,6 +313,34 @@ onMounted(async () => {
await torchReady
})
async function sendTestNotification() {
const title = 'Agent UI'
const body = 'Test notification from Agent UI — all platforms!'
if (isTauri) {
try {
const { isPermissionGranted, requestPermission, sendNotification } = await getTauriNotification()
let granted = await isPermissionGranted()
if (!granted) {
const perm = await requestPermission()
granted = perm === 'granted'
}
if (granted) {
sendNotification({ title, body })
}
} catch (e) {
console.warn('[Notification] Tauri plugin failed:', e)
}
} else if ('Notification' in window) {
if (Notification.permission === 'granted') {
new Notification(title, { body })
} else if (Notification.permission !== 'denied') {
const perm = await Notification.requestPermission()
if (perm === 'granted') new Notification(title, { body })
}
}
}
onUnmounted(() => {
document.removeEventListener('mousemove', trackMouse)
document.removeEventListener('keydown', handleGlobalKeydown)
@@ -367,6 +421,12 @@ if (serverConfig) {
</svg>
<span v-if="totalPending > 0" class="approval-count">{{ totalPending }}</span>
</button>
<button class="refresh-btn" @click="sendTestNotification" title="Test notification">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>
</button>
<button class="refresh-btn" @click="hardRefresh" title="Hard refresh (Ctrl+F5)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
@@ -375,6 +435,7 @@ if (serverConfig) {
</button>
<span class="wco-dot" :class="{ on: forceWco }" @click="forceWco = !forceWco"></span>
<TorchButton />
<WindowControls />
</div>
</header>
<main class="app-main">
@@ -503,9 +564,9 @@ if (serverConfig) {
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
padding-top: calc(0.5rem + env(safe-area-inset-top, 0px));
padding-left: calc(1rem + env(safe-area-inset-left, 0px));
padding-right: calc(1rem + env(safe-area-inset-right, 0px));
padding-top: calc(0.5rem + var(--sat, env(safe-area-inset-top, 0px)));
padding-left: calc(1rem + var(--sal, env(safe-area-inset-left, 0px)));
padding-right: calc(1rem + var(--sar, env(safe-area-inset-right, 0px)));
background: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
flex-shrink: 0;
@@ -537,6 +598,7 @@ if (serverConfig) {
min-height: 32px;
max-height: 32px;
padding: 0 0.5rem;
padding-right: 0;
border-bottom: none;
overflow: visible;
}