From a8d3d1a423ce6e3be3cf9f0a2103e7c217354d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 26 Feb 2026 23:29:13 +0000 Subject: [PATCH] email chat uses officerdev/chat app with session history and email context scoping Co-Authored-By: Claude Opus 4.6 --- .../Screens/Dashboard/Email/EmailScreen.tsx | 6 ++++-- .../Screens/Dashboard/Email/defaultLayout.ts | 2 +- .../officerdev/src/apps/Chat/ChatHeader.tsx | 14 ++++++++++---- .../officerdev/src/apps/Chat/ChatPanelWrapper.tsx | 15 +++++++++------ .../src/components/Workspace/WorkspaceContext.ts | 1 + .../src/components/Workspace/WorkspaceLayout.tsx | 5 +++-- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/apps/officer-web/Screens/Dashboard/Email/EmailScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Email/EmailScreen.tsx index e19be403..d67eb369 100644 --- a/src/apps/officer-web/Screens/Dashboard/Email/EmailScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Email/EmailScreen.tsx @@ -6,7 +6,8 @@ import { useGlobal } from 'hooks/useGlobal'; import { defaultLayout } from './defaultLayout'; import { EmailList } from './EmailList'; import { EmailReader } from './EmailReader'; -import { EmailChat } from './EmailChat'; + +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 EmailScreen = () => { const isMobile = useIsMobile(); @@ -16,7 +17,6 @@ export const EmailScreen = () => { () => ({ 'email-list': EmailList, 'email-reader': EmailReader, - 'email-chat': EmailChat, }), [], ); @@ -30,6 +30,8 @@ export const EmailScreen = () => { layout={defaultLayout} onLayoutChange={() => {}} components={components} + workspaceId="email" + promptPrefix={PROMPT_PREFIX} isMobile={isMobile} mobilePanelId={mobilePanelId} onMobileBack={mobilePanelId ? onMobileBack : null} diff --git a/src/apps/officer-web/Screens/Dashboard/Email/defaultLayout.ts b/src/apps/officer-web/Screens/Dashboard/Email/defaultLayout.ts index 77ae4863..5fcdc180 100644 --- a/src/apps/officer-web/Screens/Dashboard/Email/defaultLayout.ts +++ b/src/apps/officer-web/Screens/Dashboard/Email/defaultLayout.ts @@ -13,7 +13,7 @@ export const defaultLayout: LayoutNode = { direction: 'vertical', children: [ { node: { type: 'panel', id: 'email-reader', appType: null }, size: 60 }, - { node: { type: 'panel', id: 'email-chat', appType: null }, size: 40 }, + { node: { type: 'panel', id: 'email-chat', appType: 'officerdev/chat' }, size: 40 }, ], }, size: 75, diff --git a/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx b/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx index 093c4ea3..03778552 100644 --- a/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/ChatHeader.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { MessageSquare, History, Plus } from 'lucide-react'; import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; import { useWorkspace } from '../../components/Workspace/WorkspaceContext'; -import { useChatSessions } from './useChatSessions'; +import { useChatSessions } from 'state/useChatSessions'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { getProviderDisplayName } from 'state/useModels'; @@ -21,9 +21,15 @@ function formatModel(model: string): string { } export const ChatHeader = () => { - const { cwd, root } = useWorkspace(); - const scoped = cwd !== '~'; - const { sessions } = useChatSessions(scoped ? { cwd, cwdRoot: root } : {}); + const { workspaceId } = useWorkspace(); + const contextFilter = workspaceId === 'email' + ? { context: 'email' as const } + : workspaceId?.startsWith('proj-layout-') + ? { context: 'project' as const, contextId: workspaceId.replace('proj-layout-', '') } + : workspaceId && !workspaceId.startsWith('screens/') + ? { context: 'workspace' as const, contextId: workspaceId } + : undefined; + const { sessions } = useChatSessions(contextFilter); const [selection, setSelection] = usePanelChannel('chat:panel-session', null); const [activeSessionId] = usePanelChannel('chat:active-session', null); const [open, setOpen] = useState(false); diff --git a/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx b/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx index af5bbf89..757e24e1 100644 --- a/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx @@ -10,16 +10,18 @@ type ChatSessionSelection = { }; export const ChatPanelWrapper = () => { - const { workspaceId, cwd, root } = useWorkspace(); + const { workspaceId, cwd, root, promptPrefix } = useWorkspace(); const scoped = cwd !== '~'; const hostRoot = root === '~' || root === 'officer.dev'; const sandboxed = !hostRoot; - const chatContext = workspaceId?.startsWith('proj-layout-') - ? { context: 'project' as const, contextId: workspaceId.replace('proj-layout-', '') } - : workspaceId && !workspaceId.startsWith('screens/') - ? { context: 'workspace' as const, contextId: workspaceId } - : {}; + const chatContext = workspaceId === 'email' + ? { context: 'email' as const } + : workspaceId?.startsWith('proj-layout-') + ? { context: 'project' as const, contextId: workspaceId.replace('proj-layout-', '') } + : workspaceId && !workspaceId.startsWith('screens/') + ? { context: 'workspace' as const, contextId: workspaceId } + : {}; const [selection] = usePanelChannel('chat:panel-session', null); const [, setActiveSession] = usePanelChannel('chat:active-session', null); @@ -45,6 +47,7 @@ export const ChatPanelWrapper = () => { cwd={cwdParam} sandboxed={sandboxed} replaceUrl={false} + promptPrefix={promptPrefix} {...chatContext} /> ); diff --git a/src/workspaces/officerdev/src/components/Workspace/WorkspaceContext.ts b/src/workspaces/officerdev/src/components/Workspace/WorkspaceContext.ts index 241a4428..c4ed9c43 100644 --- a/src/workspaces/officerdev/src/components/Workspace/WorkspaceContext.ts +++ b/src/workspaces/officerdev/src/components/Workspace/WorkspaceContext.ts @@ -13,6 +13,7 @@ type WorkspaceContextValue = { root?: string; initialFilePath?: string; defaultFileSort?: DefaultFileSort; + promptPrefix?: string; swapSourceId: string | null; setSwapSourceId: (id: string | null) => void; onSwap: (sourceId: string, targetId: string) => void; diff --git a/src/workspaces/officerdev/src/components/Workspace/WorkspaceLayout.tsx b/src/workspaces/officerdev/src/components/Workspace/WorkspaceLayout.tsx index ed20943b..cf3af3a9 100644 --- a/src/workspaces/officerdev/src/components/Workspace/WorkspaceLayout.tsx +++ b/src/workspaces/officerdev/src/components/Workspace/WorkspaceLayout.tsx @@ -12,6 +12,7 @@ type WorkspaceLayoutProps = { components?: PanelComponents; workspaceId?: string; cwd?: string; + promptPrefix?: string; noHeader?: boolean; isMobile?: boolean; mobilePanelId?: string; @@ -20,7 +21,7 @@ type WorkspaceLayoutProps = { const noop = () => {}; -export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp, components, workspaceId, cwd, noHeader, isMobile, mobilePanelId, onMobileBack }: WorkspaceLayoutProps) => { +export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp, components, workspaceId, cwd, promptPrefix, noHeader, isMobile, mobilePanelId, onMobileBack }: WorkspaceLayoutProps) => { const { registry: globalRegistry } = useAppRegistry(); const registry = registryProp ?? globalRegistry; @@ -32,7 +33,7 @@ export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp ); return ( - +