email syncyng

This commit is contained in:
2026-02-26 01:39:38 +00:00
parent 5d4f0114cd
commit 42abb97d7b
22 changed files with 1422 additions and 348 deletions
@@ -0,0 +1,17 @@
import { EmbeddableChat, usePiChat } from 'officerdev';
const PROMPT_PREFIX = `You are an email assistant. The user has a local SQLite email database available via the "email-db" tool — use it for all email queries (search, count, stats, aggregations, deletions) unless the user explicitly asks you to use Gmail. Do not use the Gmail integration for questions about existing emails.`;
export const EmailChat = () => {
const chat = usePiChat(undefined, undefined, { replaceUrl: false });
return (
<EmbeddableChat
className="h-full"
chat={chat}
sandboxed
replaceUrl={false}
promptPrefix={PROMPT_PREFIX}
/>
);
};
@@ -1,8 +1,10 @@
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { ChevronLeft, ChevronRight, Mail, Paperclip } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { ChevronLeft, ChevronRight, Loader2, Mail, Paperclip, RefreshCw } from 'lucide-react';
import { toast } from 'sonner';
import { useClient } from 'hooks/useClient';
import { useGlobal } from 'hooks/useGlobal';
import { useJobs } from 'hooks/useJobs';
import type { EmailSummary } from 'types';
const LIMIT = 50;
@@ -19,8 +21,11 @@ const formatDate = (iso: string) => {
export const EmailList = () => {
const client = useClient();
const queryClient = useQueryClient();
const [selectedId, setSelectedId] = useGlobal<string | null>('EMAIL_SELECTED', null);
const [page, setPage] = useState(1);
const { jobs, createJob } = useJobs({ type: 'gmail-sync' });
const isSyncing = jobs.some((j) => j.status === 'queued' || j.status === 'running');
const { data, isLoading } = useQuery({
queryKey: ['email-messages', page],
@@ -28,6 +33,24 @@ export const EmailList = () => {
client.get<{ messages: EmailSummary[]; total: number }>(`/email/messages?page=${page}&limit=${LIMIT}`),
});
const handleSync = async () => {
try {
await createJob({ lane: 'google-api', type: 'gmail-sync' });
toast.success('Gmail sync started');
} catch {
toast.error('Failed to start sync');
}
};
// Refresh email list when a sync job completes
const prevSyncing = useRef(false);
useEffect(() => {
if (prevSyncing.current && !isSyncing) {
queryClient.invalidateQueries({ queryKey: ['email-messages'] });
}
prevSyncing.current = isSyncing;
}, [isSyncing, queryClient]);
const messages = data?.messages ?? [];
const total = data?.total ?? 0;
const totalPages = Math.max(1, Math.ceil(total / LIMIT));
@@ -55,6 +78,18 @@ export const EmailList = () => {
<Mail className="h-4 w-4 opacity-60" />
<span className="text-sm font-medium">Inbox</span>
<span className="text-xs opacity-50">{total}</span>
<button
onClick={handleSync}
disabled={isSyncing}
className="p-0.5 rounded hover:bg-black/10 transition-colors cursor-pointer disabled:cursor-default disabled:opacity-50 shrink-0"
title={isSyncing ? 'Syncing...' : 'Sync emails'}
>
{isSyncing ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<RefreshCw className="h-3.5 w-3.5" />
)}
</button>
{totalPages > 1 && (
<div className="ml-auto flex items-center gap-2">
<button
@@ -6,6 +6,7 @@ import { useGlobal } from 'hooks/useGlobal';
import { defaultLayout } from './defaultLayout';
import { EmailList } from './EmailList';
import { EmailReader } from './EmailReader';
import { EmailChat } from './EmailChat';
export const EmailScreen = () => {
const isMobile = useIsMobile();
@@ -15,6 +16,7 @@ export const EmailScreen = () => {
() => ({
'email-list': EmailList,
'email-reader': EmailReader,
'email-chat': EmailChat,
}),
[],
);
@@ -5,7 +5,18 @@ export const defaultLayout: LayoutNode = {
id: 'email-root',
direction: 'horizontal',
children: [
{ node: { type: 'panel', id: 'email-list', appType: null }, size: 30 },
{ node: { type: 'panel', id: 'email-reader', appType: null }, size: 70 },
{ node: { type: 'panel', id: 'email-list', appType: null }, size: 25 },
{
node: {
type: 'group',
id: 'email-right',
direction: 'vertical',
children: [
{ node: { type: 'panel', id: 'email-reader', appType: null }, size: 60 },
{ node: { type: 'panel', id: 'email-chat', appType: null }, size: 40 },
],
},
size: 75,
},
],
};