diff --git a/apps/mail/components/create/ai-chat.tsx b/apps/mail/components/create/ai-chat.tsx
index c1e637e57e..81c01d0f56 100644
--- a/apps/mail/components/create/ai-chat.tsx
+++ b/apps/mail/components/create/ai-chat.tsx
@@ -1,8 +1,8 @@
import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar';
import { useAIFullScreen, useAISidebar } from '../ui/ai-sidebar';
+import { useRef, useCallback, useEffect, useState } from 'react';
import { VoiceProvider } from '@/providers/voice-provider';
import useComposeEditor from '@/hooks/use-compose-editor';
-import { useRef, useCallback, useEffect } from 'react';
import type { useAgentChat } from 'agents/ai-react';
import { Markdown } from '@react-email/components';
import { useBilling } from '@/hooks/use-billing';
@@ -146,9 +146,7 @@ export interface AIChatProps {
setMessages: (messages: AiMessage[]) => void;
}
-// Subcomponents for ToolResponse
const GetThreadToolResponse = ({ result, args }: { result: any; args: any }) => {
- // Extract threadId from result or args
let threadId: string | null = null;
if (typeof result === 'string') {
const match = result.match(//);
@@ -181,7 +179,6 @@ const ComposeEmailToolResponse = ({ result }: { result: any }) => {
);
};
-// Main ToolResponse switcher
const ToolResponse = ({ toolName, result, args }: { toolName: string; result: any; args: any }) => {
switch (toolName) {
case Tools.GetThread:
@@ -209,6 +206,7 @@ export function AIChat({
const [, setPricingDialog] = useQueryState('pricingDialog');
const [aiSidebarOpen] = useQueryState('aiSidebar');
const { toggleOpen } = useAISidebar();
+ const voiceResponseCallbackRef = useRef<((response: string) => void) | null>(null);
const scrollToBottom = useCallback(() => {
if (messagesEndRef.current) {
@@ -222,6 +220,25 @@ export function AIChat({
}
}, [status, scrollToBottom]);
+ const [isVoiceQuery, setIsVoiceQuery] = useState(false);
+
+ useEffect(() => {
+ if (isVoiceQuery && messages.length > 0) {
+ const lastMessage = messages[messages.length - 1];
+ if (lastMessage.role === 'assistant') {
+ const textContent = lastMessage.parts
+ .filter((part) => part.type === 'text' && 'text' in part)
+ .map((part) => (part as any).text)
+ .join(' ');
+
+ if (textContent && voiceResponseCallbackRef.current) {
+ voiceResponseCallbackRef.current(textContent);
+ setIsVoiceQuery(false);
+ }
+ }
+ }
+ }, [messages, isVoiceQuery]);
+
const editor = useComposeEditor({
placeholder: 'Ask Zero to do anything...',
onLengthChange: () => setInput(editor.getText()),
@@ -284,7 +301,6 @@ export function AIChat({
Ask to do or show anything using natural language
- {/* Example Thread */}
) : (
@@ -293,14 +309,19 @@ export function AIChat({
const toolParts = message.parts.filter((part) => part.type === 'tool-invocation');
return (
-
+
{toolParts.map(
(part, index) =>
- part.toolInvocation?.result && (
+ part.toolInvocation &&
+ 'result' in part.toolInvocation && (
),
@@ -367,7 +388,6 @@ export function AIChat({
- {/* Fixed input at bottom */}
@@ -387,7 +407,17 @@ export function AIChat({
-
+ {
+ setIsVoiceQuery(true);
+ editor.commands.setContent(transcript);
+ setInput(transcript);
+ onSubmit({ preventDefault: () => {} } as React.FormEvent);
+ }}
+ onResponseReady={(callback) => {
+ voiceResponseCallbackRef.current = callback;
+ }}
+ >