- {videoChatMode === 'off'
- ? 'Video chat off'
- : videoChatMode === 'chat'
- ? 'Video chat on — camera frames are shared with the assistant'
- : 'Video call on — speak freely, the assistant answers out loud'}
+ {inCall
+ ? 'End call'
+ : callAvailable
+ ? 'Start a call — it sees your screen while you talk it through'
+ : 'Calls need voice input and output configured'}
- {videoChatMode !== 'off' && (
+ {!inCall && (
-
- onVideoModeChange(v as VideoChatMode)}
- >
- Video + chat
-
- Video call (hands-free)
-
-
- Video call (full screen)
-
-
+
+ {CALL_PRESET_MENU.map(({ preset, label, description, Icon }) => (
+ onStartCall(preset)}
+ className="items-start gap-3 py-2"
+ >
+
+
+ {label}
+ {description}
+
+
+ ))}
)}
@@ -1593,14 +1557,11 @@ export interface ChatInputWithMentionsProps {
voiceAvailable?: boolean
ttsAvailable?: boolean
ttsEnabled?: boolean
- ttsMode?: 'summary' | 'full'
onToggleTts?: () => void
- onTtsModeChange?: (mode: 'summary' | 'full') => void
- ttsAvatarEnabled?: boolean
- onToggleTtsAvatar?: () => void
- videoChatMode?: VideoChatMode
- onVideoModeChange?: (mode: VideoChatMode) => void
- videoCallAvailable?: boolean
+ inCall?: boolean
+ onStartCall?: (preset: CallPreset) => void
+ onEndCall?: () => void
+ callAvailable?: boolean
onSelectedModelChange?: (model: SelectedModel | null) => void
workDir?: string | null
onWorkDirChange?: (value: string | null) => void
@@ -1632,14 +1593,11 @@ export function ChatInputWithMentions({
voiceAvailable,
ttsAvailable,
ttsEnabled,
- ttsMode,
onToggleTts,
- onTtsModeChange,
- ttsAvatarEnabled,
- onToggleTtsAvatar,
- videoChatMode,
- onVideoModeChange,
- videoCallAvailable,
+ inCall,
+ onStartCall,
+ onEndCall,
+ callAvailable,
onSelectedModelChange,
workDir,
onWorkDirChange,
@@ -1668,14 +1626,11 @@ export function ChatInputWithMentions({
voiceAvailable={voiceAvailable}
ttsAvailable={ttsAvailable}
ttsEnabled={ttsEnabled}
- ttsMode={ttsMode}
onToggleTts={onToggleTts}
- onTtsModeChange={onTtsModeChange}
- ttsAvatarEnabled={ttsAvatarEnabled}
- onToggleTtsAvatar={onToggleTtsAvatar}
- videoChatMode={videoChatMode}
- onVideoModeChange={onVideoModeChange}
- videoCallAvailable={videoCallAvailable}
+ inCall={inCall}
+ onStartCall={onStartCall}
+ onEndCall={onEndCall}
+ callAvailable={callAvailable}
onSelectedModelChange={onSelectedModelChange}
workDir={workDir}
onWorkDirChange={onWorkDirChange}
diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx
index c32bac18..26ef078a 100644
--- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx
+++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx
@@ -37,7 +37,7 @@ import { MarkdownPreOverride } from '@/components/ai-elements/markdown-code-over
import { defaultRemarkPlugins } from 'streamdown'
import remarkBreaks from 'remark-breaks'
import { type ChatTab } from '@/components/tab-bar'
-import { ChatInputWithMentions, type PermissionMode, type StagedAttachment, type SelectedModel, type VideoChatMode } from '@/components/chat-input-with-mentions'
+import { ChatInputWithMentions, type CallPreset, type PermissionMode, type StagedAttachment, type SelectedModel } from '@/components/chat-input-with-mentions'
import { ChatMessageAttachments } from '@/components/chat-message-attachments'
import { useSidebar } from '@/components/ui/sidebar'
import { wikiLabel } from '@/lib/wiki-links'
@@ -189,14 +189,11 @@ interface ChatSidebarProps {
voiceAvailable?: boolean
ttsAvailable?: boolean
ttsEnabled?: boolean
- ttsMode?: 'summary' | 'full'
onToggleTts?: () => void
- onTtsModeChange?: (mode: 'summary' | 'full') => void
- ttsAvatarEnabled?: boolean
- onToggleTtsAvatar?: () => void
- videoChatMode?: VideoChatMode
- onVideoModeChange?: (mode: VideoChatMode) => void
- videoCallAvailable?: boolean
+ inCall?: boolean
+ onStartCall?: (preset: CallPreset) => void
+ onEndCall?: () => void
+ callAvailable?: boolean
onComposioConnected?: (toolkitSlug: string) => void
}
@@ -258,14 +255,11 @@ export function ChatSidebar({
voiceAvailable,
ttsAvailable,
ttsEnabled,
- ttsMode,
onToggleTts,
- onTtsModeChange,
- ttsAvatarEnabled,
- onToggleTtsAvatar,
- videoChatMode,
- onVideoModeChange,
- videoCallAvailable,
+ inCall,
+ onStartCall,
+ onEndCall,
+ callAvailable,
onComposioConnected,
}: ChatSidebarProps) {
const { state: sidebarState } = useSidebar()
@@ -837,14 +831,11 @@ export function ChatSidebar({
voiceAvailable={isActive && voiceAvailable}
ttsAvailable={isActive && ttsAvailable}
ttsEnabled={ttsEnabled}
- ttsMode={ttsMode}
onToggleTts={isActive ? onToggleTts : undefined}
- onTtsModeChange={isActive ? onTtsModeChange : undefined}
- ttsAvatarEnabled={ttsAvatarEnabled}
- onToggleTtsAvatar={isActive ? onToggleTtsAvatar : undefined}
- videoChatMode={videoChatMode}
- onVideoModeChange={isActive ? onVideoModeChange : undefined}
- videoCallAvailable={videoCallAvailable}
+ inCall={inCall}
+ onStartCall={isActive ? onStartCall : undefined}
+ onEndCall={isActive ? onEndCall : undefined}
+ callAvailable={callAvailable}
/>
)
diff --git a/apps/x/apps/renderer/src/components/video-call-view.tsx b/apps/x/apps/renderer/src/components/video-call-view.tsx
index 91a12c4a..8f95ce8c 100644
--- a/apps/x/apps/renderer/src/components/video-call-view.tsx
+++ b/apps/x/apps/renderer/src/components/video-call-view.tsx
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
-import { MonitorUp, PhoneOff, User, Video, VideoOff } from 'lucide-react'
+import { Minimize2, MonitorUp, PhoneOff, Presentation, User, Video, VideoOff } from 'lucide-react'
import { MascotFaceIcon, TalkingHead } from '@/components/talking-head'
import type { TTSState } from '@/hooks/useVoiceTTS'
@@ -10,12 +10,15 @@ export type VideoCallStatus = 'listening' | 'thinking' | 'speaking'
interface VideoCallViewProps {
/** Live camera stream from useVideoMode — attached to the user's tile. */
streamRef: React.MutableRefObject