email syncyng
This commit is contained in:
@@ -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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user