mirror of
https://github.com/Mail-0/Zero.git
synced 2026-07-01 08:16:28 +00:00
Merge branch 'staging' into fix-back-button
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
'use server';
|
||||
import { getAuthenticatedUserId } from '@/app/api/utils';
|
||||
import { connection, summary } from '@zero/db/schema';
|
||||
import { headers } from 'next/headers';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { db } from '@zero/db';
|
||||
import { getActiveConnection } from './utils';
|
||||
import axios from 'axios';
|
||||
|
||||
export const GetSummary = async (threadId: string) => {
|
||||
@@ -14,11 +10,7 @@ export const GetSummary = async (threadId: string) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await axios.get(process.env.BRAIN_URL + `/brain/thread/summary/${threadId}`, {
|
||||
headers: {
|
||||
// 'Authorization': `Bearer ${}`
|
||||
},
|
||||
});
|
||||
const response = await axios.get(process.env.BRAIN_URL + `/brain/thread/summary/${threadId}`);
|
||||
|
||||
return response.data ?? null;
|
||||
} catch (error) {
|
||||
@@ -26,3 +18,21 @@ export const GetSummary = async (threadId: string) => {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const GetState = async () => {
|
||||
try {
|
||||
if (!process.env.BRAIN_URL) {
|
||||
return null;
|
||||
}
|
||||
const connection = await getActiveConnection();
|
||||
if (!connection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await axios.get(process.env.BRAIN_URL + `/limit/${connection.id}`);
|
||||
return response.data ?? null;
|
||||
} catch (error) {
|
||||
console.error('Error getting summary:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,9 +4,6 @@ import { IGetThreadResponse } from '@/app/api/driver/types';
|
||||
import { ParsedMessage } from '@/types';
|
||||
|
||||
export const getMail = async ({ id }: { id: string }): Promise<IGetThreadResponse | null> => {
|
||||
if (!id) {
|
||||
throw new Error('Missing required fields');
|
||||
}
|
||||
try {
|
||||
const driver = await getActiveDriver();
|
||||
const mailData = await driver.get(id);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use server';
|
||||
|
||||
import { updateWritingStyleMatrix } from '@/services/writing-style-service';
|
||||
import { createDriver } from '@/app/api/driver';
|
||||
import { getActiveConnection } from './utils';
|
||||
import { ISendEmail } from '@/types';
|
||||
import { updateWritingStyleMatrix } from '@/services/writing-style-service';
|
||||
import { after } from 'next/server';
|
||||
|
||||
export async function sendEmail({
|
||||
@@ -19,7 +19,9 @@ export async function sendEmail({
|
||||
draftId,
|
||||
}: ISendEmail & { draftId?: string }) {
|
||||
if (!to || !subject || !message) {
|
||||
throw new Error('Missing required fields');
|
||||
throw new Error(
|
||||
`Missing required fields, ${to ? '' : 'to'} ${subject ? '' : 'subject'} ${message ? '' : 'message'}`,
|
||||
);
|
||||
}
|
||||
|
||||
const connection = await getActiveConnection();
|
||||
@@ -56,14 +58,13 @@ export async function sendEmail({
|
||||
|
||||
after(async () => {
|
||||
try {
|
||||
console.warn('Saving writing style matrix...')
|
||||
await updateWritingStyleMatrix(connection.id, message)
|
||||
console.warn('Saved writing style matrix.')
|
||||
console.warn('Saving writing style matrix...');
|
||||
await updateWritingStyleMatrix(connection.id, message);
|
||||
console.warn('Saved writing style matrix.');
|
||||
} catch (error) {
|
||||
console.error('Failed to save writing style matrix', error)
|
||||
console.error('Failed to save writing style matrix', error);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
<div className="relative flex max-h-screen w-full overflow-hidden">
|
||||
<SWRConfig
|
||||
value={{
|
||||
// provider: typeof window !== 'undefined' ? dexieStorageProvider : undefined,
|
||||
provider: typeof window !== 'undefined' ? dexieStorageProvider : undefined,
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false,
|
||||
shouldRetryOnError: false,
|
||||
|
||||
@@ -103,7 +103,7 @@ export async function POST(req: Request) {
|
||||
console.error('Error creating label:', error);
|
||||
throw new Error('Failed to create label');
|
||||
}
|
||||
return { created: 'label.id' };
|
||||
return { success: true };
|
||||
},
|
||||
},
|
||||
addLabelsToThreads: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useSearchValue } from '@/hooks/use-search-value';
|
||||
import { useConnections } from '@/hooks/use-connections';
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import { Markdown } from '@react-email/components';
|
||||
import { TextShimmer } from '../ui/text-shimmer';
|
||||
import { useThread } from '@/hooks/use-threads';
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
import { cn, getEmailLogo } from '@/lib/utils';
|
||||
@@ -42,12 +43,6 @@ interface Message {
|
||||
};
|
||||
}
|
||||
|
||||
interface AIChatProps {
|
||||
editor: any;
|
||||
onMessagesChange?: (messages: Message[]) => void;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
const renderThread = (thread: { id: string; title: string; snippet: string }) => {
|
||||
const [, setThreadId] = useQueryState('threadId');
|
||||
const { data: getThread } = useThread(thread.id);
|
||||
@@ -102,26 +97,16 @@ const RenderThreads = ({
|
||||
return <div className="flex flex-col gap-2">{threads.map(renderThread)}</div>;
|
||||
};
|
||||
|
||||
export function AIChat({ editor, onMessagesChange, onReset }: AIChatProps) {
|
||||
const [value, setValue] = useState('');
|
||||
export function AIChat() {
|
||||
const [showVoiceChat, setShowVoiceChat] = useState(false);
|
||||
const [expandedResults, setExpandedResults] = useState<Set<string>>(new Set());
|
||||
const [searchValue, setSearchValue] = useSearchValue();
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { data: session } = useSession();
|
||||
const { data: connections } = useConnections();
|
||||
|
||||
const { messages, input, setInput, error, handleSubmit, status } = useChat({
|
||||
api: '/api/chat',
|
||||
maxSteps: 5,
|
||||
});
|
||||
|
||||
// Scroll to bottom function
|
||||
const scrollToBottom = useCallback(() => {
|
||||
if (messagesEndRef.current) {
|
||||
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
@@ -134,134 +119,10 @@ export function AIChat({ editor, onMessagesChange, onReset }: AIChatProps) {
|
||||
// if (onMessagesChange) {
|
||||
// onMessagesChange(messages);
|
||||
// }
|
||||
}, [messages, onMessagesChange, scrollToBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onReset) {
|
||||
onReset();
|
||||
}
|
||||
}, [onReset]);
|
||||
|
||||
// const handleSendMessage = async () => {
|
||||
// if (!value.trim() || isLoading) return;
|
||||
|
||||
// const userMessage: Message = {
|
||||
// id: generateId(),
|
||||
// role: 'user',
|
||||
// content: value.trim(),
|
||||
// timestamp: new Date(),
|
||||
// };
|
||||
|
||||
// setMessages((prev) => [...prev, userMessage]);
|
||||
// setValue('');
|
||||
// setIsLoading(true);
|
||||
|
||||
// try {
|
||||
// if (!response.ok) {
|
||||
// throw new Error('Failed to get response');
|
||||
// }
|
||||
|
||||
// const data = await response.json();
|
||||
|
||||
// // Update the search value
|
||||
// setSearchValue({
|
||||
// value: data.searchQuery,
|
||||
// highlight: value.trim(),
|
||||
// isLoading: false,
|
||||
// isAISearching: false,
|
||||
// folder: searchValue.folder,
|
||||
// });
|
||||
|
||||
// // Add assistant message with search results
|
||||
// const assistantMessage: Message = {
|
||||
// id: generateId(),
|
||||
// role: 'assistant',
|
||||
// content: data.content,
|
||||
// timestamp: new Date(),
|
||||
// type: 'search',
|
||||
// searchContent: {
|
||||
// searchDisplay: data.searchDisplay,
|
||||
// results: data.results,
|
||||
// },
|
||||
// };
|
||||
|
||||
// setMessages((prev) => [...prev, assistantMessage]);
|
||||
// } catch (error) {
|
||||
// console.error('Error:', error);
|
||||
// toast.error('Failed to generate response. Please try again.');
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleAcceptSuggestion = (emailContent: { subject?: string; content: string }) => {
|
||||
if (!editor) {
|
||||
toast.error('Editor not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Format the content to preserve line breaks
|
||||
const formattedContent = emailContent.content
|
||||
.split('\n')
|
||||
.map((line) => `<p>${line}</p>`)
|
||||
.join('');
|
||||
|
||||
// Set the content in the editor
|
||||
editor.commands.setContent(formattedContent);
|
||||
|
||||
// Find the create-email component and update its content
|
||||
const createEmailElement = document.querySelector('[data-create-email]');
|
||||
if (createEmailElement) {
|
||||
const handler = (createEmailElement as any).onContentGenerated;
|
||||
if (handler && typeof handler === 'function') {
|
||||
handler({ content: emailContent.content, subject: emailContent.subject });
|
||||
}
|
||||
}
|
||||
|
||||
toast.success('Email content applied successfully');
|
||||
} catch (error) {
|
||||
console.error('Error applying suggestion:', error);
|
||||
toast.error('Failed to apply email content');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRejectSuggestion = (messageId: string) => {
|
||||
toast.info('Email suggestion rejected');
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const generateId = () => nanoid();
|
||||
|
||||
const toggleExpandResults = (messageId: string) => {
|
||||
setExpandedResults((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(messageId)) {
|
||||
newSet.delete(messageId);
|
||||
} else {
|
||||
newSet.add(messageId);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const sanitizeSnippet = (snippet: string) => {
|
||||
return snippet
|
||||
.replace(/<\/?[^>]+(>|$)/g, '') // Remove HTML tags
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
};
|
||||
}, [messages, messagesEndRef]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Messages container */}
|
||||
<div className="flex-1 overflow-y-auto" ref={messagesContainerRef}>
|
||||
<div className="min-h-full space-y-4 px-4 py-4">
|
||||
{!messages.length ? (
|
||||
@@ -325,10 +186,6 @@ export function AIChat({ editor, onMessagesChange, onReset }: AIChatProps) {
|
||||
: 'overflow-wrap-anywhere mr-auto break-words bg-[#f0f0f0] p-2 dark:bg-[#313131]', // Assistant messages aligned to left
|
||||
)}
|
||||
>
|
||||
{/* <div className="prose dark:prose-invert overflow-wrap-anywhere break-words text-sm font-medium">
|
||||
{message.content}
|
||||
</div> */}
|
||||
|
||||
{message.parts.map((part) => {
|
||||
if (part.type === 'text') {
|
||||
return <Markdown>{part.text}</Markdown>;
|
||||
@@ -341,108 +198,24 @@ export function AIChat({ editor, onMessagesChange, onReset }: AIChatProps) {
|
||||
) : null)
|
||||
);
|
||||
}
|
||||
// if (part.type === 'source') {
|
||||
// return <p>Source: {part.source.title}</p>;
|
||||
// }
|
||||
// if (part.type === 'step-start') {
|
||||
// return <ArrowDownCircle className="mx-auto h-4 w-4" />;
|
||||
// }
|
||||
// return <p>{part.type}</p>;
|
||||
})}
|
||||
|
||||
{/* {message.type === 'search' &&
|
||||
message.searchContent &&
|
||||
message.searchContent.results.length > 0 && (
|
||||
<div className="bg-muted space-y-4 rounded-lg px-4 pt-3">
|
||||
{(expandedResults.has(message.id)
|
||||
? message.searchContent.results
|
||||
: message.searchContent.results.slice(0, 5)
|
||||
).map((result: any, i: number) => (
|
||||
<div key={i} className="border-t pt-4 first:border-t-0 first:pt-0">
|
||||
<div className="font-medium">
|
||||
<p className="max-w-sm truncate text-sm">
|
||||
{result.subject.toLowerCase().includes('meeting') ? (
|
||||
<span className="text-blue-500">📅 {result.subject}</span>
|
||||
) : (
|
||||
result.subject || 'No subject'
|
||||
)}
|
||||
</p>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
from {result.from || 'Unknown sender'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-muted-foreground mt-1 line-clamp-2 text-xs">
|
||||
{sanitizeSnippet(result.snippet)}
|
||||
</div>
|
||||
<div className="text-muted-foreground mt-1 text-sm">
|
||||
<button
|
||||
onClick={() => handleThreadClick(result.id)}
|
||||
className="cursor-pointer border-none bg-transparent p-0 text-blue-500 hover:underline"
|
||||
>
|
||||
Open email
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{message.searchContent.results.length > 5 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-muted-foreground hover:text-foreground w-full"
|
||||
onClick={() => toggleExpandResults(message.id)}
|
||||
>
|
||||
{expandedResults.has(message.id)
|
||||
? `Show less (${message.searchContent.results.length - 5} fewer results)`
|
||||
: `Show more (${message.searchContent.results.length - 5} more results)`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message.type === 'email' && message.emailContent && (
|
||||
<div className="bg-background mt-4 rounded border p-4 font-mono text-sm">
|
||||
{message.emailContent.subject && (
|
||||
<div className="mb-2 text-blue-500">
|
||||
Subject: {message.emailContent.subject}
|
||||
</div>
|
||||
)}
|
||||
<div className="whitespace-pre-wrap">{message.emailContent.content}</div>
|
||||
<div className="mt-4 flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 border-green-500/20 hover:bg-green-500/10 hover:text-green-500"
|
||||
onClick={() => handleAcceptSuggestion(message.emailContent!)}
|
||||
>
|
||||
<CheckIcon className="mr-1 h-4 w-4" />
|
||||
Accept
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-destructive/20 hover:bg-destructive/10 hover:text-destructive h-8"
|
||||
onClick={() => handleRejectSuggestion(message.id)}
|
||||
>
|
||||
<XIcon className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
{/* Invisible element to scroll to */}
|
||||
<div ref={messagesEndRef} />
|
||||
|
||||
{JSON.stringify(error)}
|
||||
{/* Loading indicator */}
|
||||
{status === 'submitted' && (
|
||||
<div className="flex flex-col gap-2 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-sm">zero is thinking...</span>
|
||||
<TextShimmer className="text-muted-foreground text-sm">
|
||||
zero is thinking...
|
||||
</TextShimmer>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(status === 'error' || !!error) && (
|
||||
<div className="text-red-500">Error, please try again later</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -469,7 +242,7 @@ export function AIChat({ editor, onMessagesChange, onReset }: AIChatProps) {
|
||||
form="ai-chat-form"
|
||||
type="submit"
|
||||
className="border-border/50 inline-flex h-7 cursor-pointer items-center justify-center gap-1.5 overflow-hidden rounded-md border bg-white pl-1.5 pr-1 dark:bg-[#262626]"
|
||||
disabled={!input.trim()}
|
||||
disabled={!input.trim() || status !== 'ready'}
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2.5 pl-0.5">
|
||||
<div className="justify-start text-center text-sm leading-none text-black dark:text-white">
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Command, Paperclip, Plus } from 'lucide-react';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Avatar, AvatarFallback } from '../ui/avatar';
|
||||
import { aiCompose } from '@/actions/ai-composer';
|
||||
import { cn, formatFileSize } from '@/lib/utils';
|
||||
import { useThread } from '@/hooks/use-threads';
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
import { createDraft } from '@/actions/drafts';
|
||||
@@ -27,7 +28,6 @@ import { useRef, useState } from 'react';
|
||||
import { useQueryState } from 'nuqs';
|
||||
import pluralize from 'pluralize';
|
||||
import { useEffect } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -693,32 +693,38 @@ export function EmailComposer({
|
||||
{attachments && attachments.length > 0 && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="flex items-center gap-1 rounded-md bg-white/5 px-2 py-1 text-sm hover:bg-white/10 transition-colors border">
|
||||
<button className="flex items-center gap-1 rounded-md border bg-white/5 px-2 py-1 text-sm transition-colors hover:bg-white/10">
|
||||
<Paperclip className="h-3 w-3 text-[#9A9A9A]" />
|
||||
<span>{pluralize('file', attachments.length, true)}</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent className="w-80 dark:bg-[#202020] p-3 z-[100]" align="start" sideOffset={5}>
|
||||
|
||||
<PopoverContent
|
||||
className="z-[100] w-80 p-3 dark:bg-[#202020]"
|
||||
align="start"
|
||||
sideOffset={5}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium text-black dark:text-white/90">Attachments</h4>
|
||||
<h4 className="text-sm font-medium text-black dark:text-white/90">
|
||||
Attachments
|
||||
</h4>
|
||||
<div className="max-h-[200px] space-y-2 overflow-y-auto">
|
||||
{attachments.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between gap-2 rounded-md bg-black/5 dark:bg-white/5 p-2"
|
||||
className="flex items-center justify-between gap-2 rounded-md bg-black/5 p-2 dark:bg-white/5"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div>
|
||||
<p className="text-sm text-black dark:text-white/90">{file.name}</p>
|
||||
<p className="text-xs text-[#9A9A9A] dark:text-white/90">
|
||||
{(file.size / (1024 * 1024)).toFixed(2)} MB
|
||||
{formatFileSize(file.size)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => removeAttachment(index)}
|
||||
className="rounded-sm p-1 hover:bg-white/10 transition-colors relative z-[101]"
|
||||
className="relative z-[101] rounded-sm p-1 transition-colors hover:bg-white/10"
|
||||
>
|
||||
<X className="h-4 w-4 fill-[#9A9A9A]" />
|
||||
</button>
|
||||
|
||||
@@ -631,7 +631,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo }: Props) => {
|
||||
{formatDate(emailData?.receivedOn)}
|
||||
</time>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-1 pb-2">
|
||||
<p className="text-sm font-medium text-[#6D6D6D] dark:text-[#8C8C8C]">
|
||||
To:{' '}
|
||||
{(() => {
|
||||
@@ -761,7 +761,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo }: Props) => {
|
||||
</div>
|
||||
)}
|
||||
{emailData?.attachments && emailData?.attachments.length > 0 ? (
|
||||
<div className="mb-4 flex items-center gap-2 pt-4">
|
||||
<div className="mb-4 flex items-center gap-2 pt-4 px-4">
|
||||
{emailData?.attachments.map((attachment, index) => (
|
||||
<div key={index}>
|
||||
<button
|
||||
@@ -803,7 +803,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo }: Props) => {
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mb-2 mt-2 flex gap-2">
|
||||
<div className="mb-2 mt-2 flex gap-2 px-4">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -286,7 +286,7 @@ function BulkSelectActions() {
|
||||
setIsLoading(true);
|
||||
toast.promise(
|
||||
Promise.all(
|
||||
mail.bulkSelected.map(async (bulkSelected) => {
|
||||
mail.bulkSelected.filter(Boolean).map(async (bulkSelected) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 499));
|
||||
const emailData = await getMail({ id: bulkSelected });
|
||||
if (emailData) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { signOut, useSession } from '@/lib/auth-client';
|
||||
import { AddConnectionDialog } from '../connection/add';
|
||||
import { putConnection } from '@/actions/connections';
|
||||
import { useSidebar } from '@/components/ui/sidebar';
|
||||
import { useBrainState } from '@/hooks/use-summary';
|
||||
import { dexieStorageProvider } from '@/lib/idb';
|
||||
import { EnableBrain } from '@/actions/brain';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -101,6 +102,8 @@ export function NavUser() {
|
||||
);
|
||||
};
|
||||
|
||||
const { data: brainState } = useBrainState();
|
||||
|
||||
const handleThemeToggle = () => {
|
||||
setTheme(theme === 'dark' ? 'light' : 'dark');
|
||||
};
|
||||
@@ -266,14 +269,6 @@ export function NavUser() {
|
||||
Terms
|
||||
</a>
|
||||
</div>
|
||||
<DropdownMenuSeparator className="mt-1" />
|
||||
<p className="text-muted-foreground px-2 py-1 text-[11px] font-medium">Debug</p>
|
||||
<DropdownMenuItem onClick={handleClearCache}>
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle size={16} className="opacity-60" />
|
||||
<p className="text-[13px] opacity-60">Clear Local Cache</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -394,12 +389,14 @@ export function NavUser() {
|
||||
<p className="text-[13px] opacity-60">Clear Local Cache</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
{/* <DropdownMenuItem onClick={handleEnableBrain}>
|
||||
<div className="flex items-center gap-2">
|
||||
<BrainIcon size={16} className="opacity-60" />
|
||||
<p className="text-[13px] opacity-60">Enable Brain Activity</p>
|
||||
</div>
|
||||
</DropdownMenuItem> */}
|
||||
{!brainState?.enabled ? (
|
||||
<DropdownMenuItem onClick={handleEnableBrain}>
|
||||
<div className="flex items-center gap-2">
|
||||
<BrainIcon size={16} className="opacity-60" />
|
||||
<p className="text-[13px] opacity-60">Enable Brain Activity</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
import { GetSummary } from '@/actions/getSummary';
|
||||
import { string } from 'zod';
|
||||
import { GetState, GetSummary } from '@/actions/getSummary';
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
import useSWR from 'swr';
|
||||
|
||||
export const useSummary = (threadId: string | null) => {
|
||||
const { data: session } = useSession();
|
||||
const { data, isLoading } = useSWR<{ short: string; long: string } | null>(
|
||||
threadId ? `ai:summary:${threadId}` : null,
|
||||
session && threadId ? `ai:summary:${threadId}` : null,
|
||||
async () => {
|
||||
if (!threadId) return null;
|
||||
return await GetSummary(threadId);
|
||||
@@ -14,3 +15,15 @@ export const useSummary = (threadId: string | null) => {
|
||||
|
||||
return { data, isLoading };
|
||||
};
|
||||
|
||||
export const useBrainState = () => {
|
||||
const { data: session } = useSession();
|
||||
const { data, isLoading } = useSWR<{ enabled: boolean } | null>(
|
||||
session ? `brain:state:${session?.connectionId}` : null,
|
||||
async () => {
|
||||
return await GetState();
|
||||
},
|
||||
);
|
||||
|
||||
return { data, isLoading };
|
||||
};
|
||||
|
||||
@@ -13,10 +13,6 @@ export const handleUnsubscribe = async ({ emailData }: { emailData: ParsedMessag
|
||||
listUnsubscribePost: emailData.listUnsubscribePost,
|
||||
});
|
||||
if (listUnsubscribeAction) {
|
||||
track('Unsubscribe', {
|
||||
domain: emailData.sender.email.split('@')?.[1] ?? 'unknown',
|
||||
});
|
||||
|
||||
switch (listUnsubscribeAction.type) {
|
||||
case 'get':
|
||||
window.open(listUnsubscribeAction.url, '_blank');
|
||||
@@ -48,12 +44,14 @@ export const handleUnsubscribe = async ({ emailData }: { emailData: ParsedMessag
|
||||
name: listUnsubscribeAction.emailAddress,
|
||||
},
|
||||
],
|
||||
subject: listUnsubscribeAction.subject,
|
||||
subject: listUnsubscribeAction.subject ?? 'Unsubscribe Request',
|
||||
message: 'Zero sent this email to unsubscribe from this mailing list.',
|
||||
attachments: [],
|
||||
});
|
||||
return true;
|
||||
}
|
||||
track('Unsubscribe', {
|
||||
domain: emailData.sender.email.split('@')?.[1] ?? 'unknown',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user