mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-15 21:11:08 +02:00
use native gmail in copilot
This commit is contained in:
parent
6b2120d376
commit
b493baf689
7 changed files with 104 additions and 27 deletions
|
|
@ -622,6 +622,9 @@ function emitServiceEvent(event: z.infer<typeof ServiceEvent>): void {
|
|||
}
|
||||
|
||||
export function emitOAuthEvent(event: { provider: string; success: boolean; error?: string; userId?: string }): void {
|
||||
// Native connection status (e.g. Google) is baked into the Copilot system
|
||||
// prompt, so any OAuth state change must rebuild it.
|
||||
invalidateCopilotInstructionsCache();
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
for (const win of windows) {
|
||||
if (!win.isDestroyed() && win.webContents) {
|
||||
|
|
|
|||
|
|
@ -631,7 +631,7 @@ type ViewState =
|
|||
| { type: 'suggested-topics' }
|
||||
| { type: 'meetings' }
|
||||
| { type: 'live-notes' }
|
||||
| { type: 'email'; threadId?: string }
|
||||
| { type: 'email'; threadId?: string; searchQuery?: string }
|
||||
| { type: 'workspace'; path?: string }
|
||||
| { type: 'knowledge-view'; folderPath?: string; mode?: KnowledgeViewMode }
|
||||
| { type: 'chat-history' }
|
||||
|
|
@ -646,7 +646,7 @@ function viewStatesEqual(a: ViewState, b: ViewState): boolean {
|
|||
if (a.type === 'task' && b.type === 'task') return a.name === b.name
|
||||
if (a.type === 'workspace' && b.type === 'workspace') return (a.path ?? '') === (b.path ?? '')
|
||||
if (a.type === 'knowledge-view' && b.type === 'knowledge-view') return (a.folderPath ?? '') === (b.folderPath ?? '') && (a.mode ?? '') === (b.mode ?? '')
|
||||
if (a.type === 'email' && b.type === 'email') return (a.threadId ?? '') === (b.threadId ?? '')
|
||||
if (a.type === 'email' && b.type === 'email') return (a.threadId ?? '') === (b.threadId ?? '') && (a.searchQuery ?? '') === (b.searchQuery ?? '')
|
||||
return true // both graph
|
||||
}
|
||||
|
||||
|
|
@ -842,6 +842,10 @@ function App() {
|
|||
const [isHomeOpen, setIsHomeOpen] = useState(true)
|
||||
const [emailInitialThreadId, setEmailInitialThreadId] = useState<string | null>(null)
|
||||
const [emailThreadIdVersion, setEmailThreadIdVersion] = useState(0)
|
||||
// Search query pushed into the email view's search box (e.g. the assistant's
|
||||
// read-view email query), so threads outside the synced inbox get real rows.
|
||||
const [emailInitialSearchQuery, setEmailInitialSearchQuery] = useState<string | null>(null)
|
||||
const [emailSearchQueryVersion, setEmailSearchQueryVersion] = useState(0)
|
||||
const [expandedFrom, setExpandedFrom] = useState<{
|
||||
path: string | null
|
||||
graph: boolean
|
||||
|
|
@ -4258,6 +4262,10 @@ function App() {
|
|||
setEmailInitialThreadId(view.threadId)
|
||||
setEmailThreadIdVersion((v) => v + 1)
|
||||
}
|
||||
if (view.searchQuery) {
|
||||
setEmailInitialSearchQuery(view.searchQuery)
|
||||
setEmailSearchQueryVersion((v) => v + 1)
|
||||
}
|
||||
ensureEmailFileTab()
|
||||
return
|
||||
case 'workspace':
|
||||
|
|
@ -4606,7 +4614,15 @@ function App() {
|
|||
break
|
||||
case 'open-view':
|
||||
case 'read-view':
|
||||
navigateToNamedView(result.view as string)
|
||||
// A read-view email search runs against the whole mailbox, so drive
|
||||
// the email view's own search box with the same query — matched
|
||||
// threads get real rows even when they're outside the synced inbox
|
||||
// (and a follow-up open-item can then select them).
|
||||
if (result.action === 'read-view' && result.view === 'email' && typeof result.query === 'string' && result.query.trim()) {
|
||||
void navigateToView({ type: 'email', searchQuery: result.query.trim() })
|
||||
} else {
|
||||
navigateToNamedView(result.view as string)
|
||||
}
|
||||
break
|
||||
case 'open-item': {
|
||||
switch (result.kind) {
|
||||
|
|
@ -6185,7 +6201,7 @@ function App() {
|
|||
</div>
|
||||
) : isEmailOpen ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
<EmailView initialThreadId={emailInitialThreadId} threadIdVersion={emailThreadIdVersion} />
|
||||
<EmailView initialThreadId={emailInitialThreadId} threadIdVersion={emailThreadIdVersion} initialSearchQuery={emailInitialSearchQuery} searchQueryVersion={emailSearchQueryVersion} />
|
||||
</div>
|
||||
) : isWorkspaceOpen ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -2383,9 +2383,13 @@ export type EmailViewProps = {
|
|||
initialThreadId?: string | null
|
||||
/** Bump to re-focus on the same threadId after navigating away inside the view. */
|
||||
threadIdVersion?: number
|
||||
/** Query to load into the search box (e.g. the assistant's read-view email search). */
|
||||
initialSearchQuery?: string | null
|
||||
/** Bump to re-apply the same search query. */
|
||||
searchQueryVersion?: number
|
||||
}
|
||||
|
||||
export function EmailView({ initialThreadId, threadIdVersion }: EmailViewProps = {}) {
|
||||
export function EmailView({ initialThreadId, threadIdVersion, initialSearchQuery, searchQueryVersion }: EmailViewProps = {}) {
|
||||
const [important, setImportant] = useState<SectionState>(() => clearLoadingFlag(persistedImportant))
|
||||
const [other, setOther] = useState<SectionState>(() => clearLoadingFlag(persistedOther))
|
||||
const hadPersistedDataOnMount = useRef(persistedImportant !== null)
|
||||
|
|
@ -2404,6 +2408,13 @@ export function EmailView({ initialThreadId, threadIdVersion }: EmailViewProps =
|
|||
const [refreshing, setRefreshing] = useState(!hadPersistedDataOnMount.current)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [query, setQuery] = useState('')
|
||||
// Externally-driven search (assistant read-view email query): load it into
|
||||
// the search box so the debounced full-mailbox search below runs — matched
|
||||
// threads get real rows even when they're not in the synced inbox.
|
||||
useEffect(() => {
|
||||
const q = initialSearchQuery?.trim()
|
||||
if (q) setQuery(q)
|
||||
}, [initialSearchQuery, searchQueryVersion])
|
||||
const [composeOpen, setComposeOpen] = useState(false)
|
||||
// Stable so the open composer isn't re-rendered on every inbox sync tick.
|
||||
const closeCompose = useCallback(() => setComposeOpen(false), [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue