mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-06 08:16:03 +00:00
183 lines
5.1 KiB
TypeScript
183 lines
5.1 KiB
TypeScript
export const NOVA_KNOWLEDGE_BASE_PROVIDERS = [
|
|
"google-drive",
|
|
"notion",
|
|
"onedrive",
|
|
"granola",
|
|
] as const
|
|
|
|
export type NovaKnowledgeBaseProvider =
|
|
(typeof NOVA_KNOWLEDGE_BASE_PROVIDERS)[number]
|
|
|
|
export type NovaKnowledgeBaseStatus =
|
|
| "active"
|
|
| "syncing"
|
|
| "error"
|
|
| "not_connected"
|
|
| "upgrade_required"
|
|
|
|
export type KnowledgeConnection = {
|
|
provider: string
|
|
metadata?: { documentCount?: number } | null
|
|
lastSyncRun?: {
|
|
status?: "running" | "completed" | "failed"
|
|
startedAt?: string
|
|
completedAt?: string
|
|
} | null
|
|
}
|
|
|
|
export type KnowledgeConnectorState = {
|
|
status: NovaKnowledgeBaseStatus
|
|
connectionCount: number
|
|
documentCount: number
|
|
lastSyncStatus?: "running" | "completed" | "failed"
|
|
lastSyncAt?: string
|
|
}
|
|
|
|
type PendingKnowledgeConnectionWindow = {
|
|
popup: Window
|
|
cleanupTimer: number
|
|
}
|
|
|
|
const pendingConnectionWindows = new Map<
|
|
NovaKnowledgeBaseProvider,
|
|
PendingKnowledgeConnectionWindow
|
|
>()
|
|
|
|
const KNOWLEDGE_BASE_NAMES: Record<NovaKnowledgeBaseProvider, string> = {
|
|
"google-drive": "Google Drive",
|
|
notion: "Notion",
|
|
onedrive: "OneDrive",
|
|
granola: "Granola",
|
|
}
|
|
|
|
function closePendingConnectionWindow(
|
|
provider: NovaKnowledgeBaseProvider,
|
|
closePopup: boolean,
|
|
) {
|
|
const pending = pendingConnectionWindows.get(provider)
|
|
if (!pending) return
|
|
window.clearTimeout(pending.cleanupTimer)
|
|
pendingConnectionWindows.delete(provider)
|
|
if (closePopup && !pending.popup.closed) pending.popup.close()
|
|
}
|
|
|
|
export function reserveNovaKnowledgeConnectionWindow(
|
|
provider: NovaKnowledgeBaseProvider,
|
|
): boolean {
|
|
if (provider === "granola" || typeof window === "undefined") return false
|
|
const existing = pendingConnectionWindows.get(provider)
|
|
if (existing && !existing.popup.closed) {
|
|
existing.popup.focus()
|
|
return true
|
|
}
|
|
if (existing) closePendingConnectionWindow(provider, false)
|
|
|
|
const popup = window.open(
|
|
"about:blank",
|
|
`supermemory-nova-${provider}-connection`,
|
|
)
|
|
if (!popup) return false
|
|
popup.opener = null
|
|
popup.document.title = `Connecting ${KNOWLEDGE_BASE_NAMES[provider]}…`
|
|
popup.document.body.style.cssText =
|
|
"margin:0;min-height:100vh;display:grid;place-items:center;background:#080b0f;color:#fafafa;font-family:ui-sans-serif,system-ui,sans-serif"
|
|
const status = popup.document.createElement("p")
|
|
status.textContent = `Preparing ${KNOWLEDGE_BASE_NAMES[provider]} connection…`
|
|
status.style.cssText = "font-size:16px;opacity:.75"
|
|
popup.document.body.append(status)
|
|
|
|
const cleanupTimer = window.setTimeout(
|
|
() => closePendingConnectionWindow(provider, true),
|
|
5 * 60 * 1000,
|
|
)
|
|
pendingConnectionWindows.set(provider, { popup, cleanupTimer })
|
|
return true
|
|
}
|
|
|
|
export function navigateReservedNovaKnowledgeConnectionWindow(
|
|
provider: NovaKnowledgeBaseProvider,
|
|
authLink: string,
|
|
): boolean {
|
|
const pending = pendingConnectionWindows.get(provider)
|
|
if (!pending || pending.popup.closed) {
|
|
if (pending) closePendingConnectionWindow(provider, false)
|
|
return false
|
|
}
|
|
pending.popup.location.replace(authLink)
|
|
pending.popup.focus()
|
|
return true
|
|
}
|
|
|
|
export function isNovaKnowledgeConnectionWindowOpen(
|
|
provider: NovaKnowledgeBaseProvider,
|
|
): boolean {
|
|
const pending = pendingConnectionWindows.get(provider)
|
|
return Boolean(pending && !pending.popup.closed)
|
|
}
|
|
|
|
export function releaseNovaKnowledgeConnectionWindow(
|
|
provider: NovaKnowledgeBaseProvider,
|
|
) {
|
|
if (typeof window === "undefined") return
|
|
closePendingConnectionWindow(provider, true)
|
|
}
|
|
|
|
export function isNovaKnowledgeBaseProvider(
|
|
value: string | undefined,
|
|
): value is NovaKnowledgeBaseProvider {
|
|
return NOVA_KNOWLEDGE_BASE_PROVIDERS.some((provider) => provider === value)
|
|
}
|
|
|
|
function latestConnection(connections: KnowledgeConnection[]) {
|
|
return connections.reduce<KnowledgeConnection | undefined>((latest, item) => {
|
|
if (!latest) return item
|
|
const latestTime = Date.parse(
|
|
latest.lastSyncRun?.startedAt ?? latest.lastSyncRun?.completedAt ?? "",
|
|
)
|
|
const itemTime = Date.parse(
|
|
item.lastSyncRun?.startedAt ?? item.lastSyncRun?.completedAt ?? "",
|
|
)
|
|
return (Number.isFinite(itemTime) ? itemTime : 0) >
|
|
(Number.isFinite(latestTime) ? latestTime : 0)
|
|
? item
|
|
: latest
|
|
}, undefined)
|
|
}
|
|
|
|
export function deriveKnowledgeConnectorState({
|
|
provider,
|
|
connections,
|
|
fallbackStatus,
|
|
}: {
|
|
provider: NovaKnowledgeBaseProvider
|
|
connections: KnowledgeConnection[]
|
|
fallbackStatus: NovaKnowledgeBaseStatus
|
|
}): KnowledgeConnectorState {
|
|
const providerConnections = connections.filter(
|
|
(connection) => connection.provider === provider,
|
|
)
|
|
const latest = latestConnection(providerConnections)
|
|
const lastSyncStatus = latest?.lastSyncRun?.status
|
|
const status: NovaKnowledgeBaseStatus =
|
|
fallbackStatus === "upgrade_required"
|
|
? "upgrade_required"
|
|
: lastSyncStatus === "running"
|
|
? "syncing"
|
|
: lastSyncStatus === "failed"
|
|
? "error"
|
|
: providerConnections.length > 0
|
|
? "active"
|
|
: "not_connected"
|
|
|
|
return {
|
|
status,
|
|
connectionCount: providerConnections.length,
|
|
documentCount: providerConnections.reduce(
|
|
(total, connection) => total + (connection.metadata?.documentCount ?? 0),
|
|
0,
|
|
),
|
|
lastSyncStatus,
|
|
lastSyncAt:
|
|
latest?.lastSyncRun?.completedAt ?? latest?.lastSyncRun?.startedAt,
|
|
}
|
|
}
|