feat: Morning Digest with Voice Mode + unified OAuth + Apple connectors (#174)

This commit is contained in:
Jon Saad-Falcon
2026-04-03 11:05:41 -07:00
committed by GitHub
parent 867a96fd6e
commit a009646071
122 changed files with 7496 additions and 15388 deletions
@@ -8,7 +8,8 @@ import {
Loader2,
} from 'lucide-react';
import { SOURCE_CATALOG } from '../../types/connectors';
import { connectSource } from '../../lib/connectors-api';
import { connectSource, getConnector } from '../../lib/connectors-api';
import { getBase } from '../../lib/api';
import type { ConnectRequest, ConnectorMeta } from '../../types/connectors';
// ---------------------------------------------------------------------------
@@ -138,89 +139,71 @@ function FilesystemPanel({
function OAuthPanel({
displayName,
authUrl,
connectorId,
onConnect,
onSkip,
isConnecting,
}: {
displayName: string;
authUrl?: string;
connectorId: string;
onConnect: (req: ConnectRequest) => void;
onSkip: () => void;
isConnecting: boolean;
}) {
const [token, setToken] = useState('');
const [phase, setPhase] = useState<'start' | 'paste'>('start');
const [waiting, setWaiting] = useState(false);
const openBrowser = () => {
if (authUrl) {
window.open(authUrl, '_blank');
}
setPhase('paste');
const startOAuth = () => {
// Open the server's OAuth start endpoint which redirects to the provider
const oauthUrl = `${getBase()}/v1/connectors/${encodeURIComponent(connectorId)}/oauth/start`;
window.open(oauthUrl, '_blank', 'width=600,height=700');
setWaiting(true);
// Poll for connection status
const interval = setInterval(async () => {
try {
const info = await getConnector(connectorId);
if (info.connected) {
clearInterval(interval);
setWaiting(false);
onConnect({});
}
} catch {
// ignore polling errors
}
}, 2000);
// Stop polling after 3 minutes
setTimeout(() => {
clearInterval(interval);
setWaiting(false);
}, 180000);
};
return (
<div className="flex flex-col gap-4">
{phase === 'start' ? (
<>
<p className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>
Authorize OpenJarvis to access your {displayName} account.
</p>
<button
onClick={openBrowser}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium self-start transition-all"
style={{
background: 'var(--color-accent)',
color: 'white',
}}
>
<ExternalLink size={14} />
Open in browser
</button>
</>
<p className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>
{waiting
? `Waiting for ${displayName} authorization... Complete it in the browser window.`
: `Connect your ${displayName} account with one click.`}
</p>
{waiting ? (
<div className="flex items-center gap-2 text-sm" style={{ color: 'var(--color-accent)' }}>
<Loader2 size={16} className="animate-spin" />
Waiting for authorization...
</div>
) : (
<>
<p className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>
After authorizing, paste the token or code below.
</p>
<textarea
value={token}
onChange={(e) => setToken(e.target.value)}
rows={3}
placeholder="Paste auth token or code here..."
className="w-full px-3 py-2 rounded-lg text-sm outline-none resize-none font-mono"
style={{
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
color: 'var(--color-text)',
}}
/>
<div className="flex gap-2">
<button
onClick={() => onConnect({ token, code: token })}
disabled={!token.trim() || isConnecting}
className="px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-all"
style={{
background: token.trim() ? 'var(--color-accent)' : 'var(--color-bg-tertiary)',
color: token.trim() ? 'white' : 'var(--color-text-tertiary)',
cursor: token.trim() && !isConnecting ? 'pointer' : 'not-allowed',
}}
>
{isConnecting && <Loader2 size={14} className="animate-spin" />}
Confirm
</button>
<button
onClick={() => setPhase('start')}
className="px-4 py-2 rounded-lg text-sm font-medium transition-all"
style={{
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
color: 'var(--color-text-secondary)',
}}
>
Back
</button>
</div>
</>
<button
onClick={startOAuth}
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium self-start transition-all"
style={{
background: 'var(--color-accent)',
color: 'white',
}}
>
<ExternalLink size={14} />
Connect {displayName}
</button>
)}
<button
onClick={onSkip}
@@ -589,6 +572,7 @@ export function SourceConnectFlow({
<OAuthPanel
displayName={activeCard.display_name}
authUrl={undefined}
connectorId={activeEntry.id}
onConnect={(req) => handleConnect(activeEntry.id, req)}
onSkip={() => handleSkip(activeEntry.id)}
isConnecting={activeEntry.state === 'connecting'}