@datalayer/agent-runtimes 0.0.5 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -22
- package/lib/components/chat/components/AgentDetails.d.ts +15 -2
- package/lib/components/chat/components/AgentDetails.js +9 -93
- package/lib/components/chat/components/AgentIdentity.d.ts +92 -0
- package/lib/components/chat/components/AgentIdentity.js +318 -0
- package/lib/components/chat/components/Chat.d.ts +24 -1
- package/lib/components/chat/components/Chat.js +41 -19
- package/lib/components/chat/components/ChatFloating.d.ts +6 -1
- package/lib/components/chat/components/ChatFloating.js +12 -6
- package/lib/components/chat/components/ContextDistribution.d.ts +47 -0
- package/lib/components/chat/components/ContextDistribution.js +146 -0
- package/lib/components/chat/components/ContextUsage.d.ts +33 -0
- package/lib/components/chat/components/ContextUsage.js +127 -0
- package/lib/components/chat/components/base/ChatBase.d.ts +51 -1
- package/lib/components/chat/components/base/ChatBase.js +278 -74
- package/lib/components/chat/components/display/ToolCallDisplay.d.ts +16 -2
- package/lib/components/chat/components/display/ToolCallDisplay.js +148 -6
- package/lib/components/chat/components/display/index.d.ts +1 -1
- package/lib/components/chat/components/display/index.js +1 -1
- package/lib/components/chat/components/elements/ChatInputPrompt.d.ts +12 -1
- package/lib/components/chat/components/elements/ChatInputPrompt.js +8 -3
- package/lib/components/chat/components/index.d.ts +3 -0
- package/lib/components/chat/components/index.js +3 -0
- package/lib/components/chat/components/parts/ToolPart.d.ts +1 -1
- package/lib/components/chat/components/parts/ToolPart.js +142 -6
- package/lib/components/chat/index.d.ts +1 -1
- package/lib/components/chat/index.js +1 -1
- package/lib/components/chat/protocols/A2AAdapter.d.ts +9 -0
- package/lib/components/chat/protocols/A2AAdapter.js +13 -2
- package/lib/components/chat/protocols/ACPAdapter.d.ts +9 -0
- package/lib/components/chat/protocols/ACPAdapter.js +13 -2
- package/lib/components/chat/protocols/AGUIAdapter.d.ts +9 -0
- package/lib/components/chat/protocols/AGUIAdapter.js +19 -1
- package/lib/components/chat/protocols/VercelAIAdapter.d.ts +7 -0
- package/lib/components/chat/protocols/VercelAIAdapter.js +19 -0
- package/lib/components/chat/types/execution.d.ts +78 -0
- package/lib/components/chat/types/execution.js +64 -0
- package/lib/components/chat/types/index.d.ts +1 -0
- package/lib/components/chat/types/index.js +1 -0
- package/lib/components/chat/types/protocol.d.ts +9 -0
- package/lib/components/ui/pagination.d.ts +2 -2
- package/lib/components/ui/pagination.js +4 -4
- package/lib/components/ui/resizable.d.ts +4 -4
- package/lib/components/ui/resizable.js +4 -4
- package/lib/examples/A2UiRestaurantExample.js +2 -2
- package/lib/examples/AgUiAgenticExample.js +2 -2
- package/lib/examples/AgUiBackendToolRenderingExample.js +2 -2
- package/lib/examples/AgUiHaikuGenUIExample.js +2 -2
- package/lib/examples/AgUiHumanInTheLoopExample.js +2 -2
- package/lib/examples/AgUiSharedStateExample.js +2 -2
- package/lib/examples/AgUiToolsBasedGenUIExample.js +2 -2
- package/lib/examples/AgentRuntimeCustomExample.js +2 -2
- package/lib/examples/AgentRuntimeLexical2Example.js +2 -1
- package/lib/examples/AgentRuntimeLexicalExample.js +5 -2
- package/lib/examples/AgentRuntimeLexicalSidebarExample.js +4 -2
- package/lib/examples/AgentRuntimeNotebookExample.js +1 -1
- package/lib/examples/AgentRuntimeStandaloneExample.js +2 -2
- package/lib/examples/AgentSpaceFormExample.d.ts +70 -2
- package/lib/examples/AgentSpaceFormExample.js +204 -35
- package/lib/examples/CopilotKitLexicalExample.js +2 -1
- package/lib/examples/components/AgentConfiguration.d.ts +37 -0
- package/lib/examples/components/AgentConfiguration.js +239 -8
- package/lib/examples/components/Header.d.ts +0 -2
- package/lib/examples/components/Header.js +2 -16
- package/lib/examples/components/LexicalEditor.js +2 -1
- package/lib/examples/components/MockFileBrowser.js +6 -2
- package/lib/examples/components/index.d.ts +0 -1
- package/lib/examples/components/index.js +0 -1
- package/lib/examples/example-selector.js +0 -1
- package/lib/examples/index.d.ts +0 -1
- package/lib/examples/index.js +0 -1
- package/lib/examples/lexical/editorConfig.d.ts +3 -2
- package/lib/examples/lexical/editorConfig.js +7 -1
- package/lib/examples/lexical/initial-content.json +2210 -0
- package/lib/examples/main.js +15 -1
- package/lib/identity/IdentityConnect.d.ts +90 -0
- package/lib/identity/IdentityConnect.js +316 -0
- package/lib/identity/OAuthCallback.d.ts +58 -0
- package/lib/identity/OAuthCallback.js +223 -0
- package/lib/identity/dcr.d.ts +257 -0
- package/lib/identity/dcr.js +282 -0
- package/lib/identity/identityStore.d.ts +72 -0
- package/lib/identity/identityStore.js +529 -0
- package/lib/identity/index.d.ts +46 -0
- package/lib/identity/index.js +17 -0
- package/lib/identity/pkce.d.ts +30 -0
- package/lib/identity/pkce.js +65 -0
- package/lib/identity/types.d.ts +293 -0
- package/lib/identity/types.js +73 -0
- package/lib/identity/useIdentity.d.ts +108 -0
- package/lib/identity/useIdentity.js +323 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/lib/utils.js +1 -1
- package/lib/renderers/a2ui/lib/utils.js +1 -1
- package/lib/runtime/index.d.ts +35 -0
- package/lib/runtime/index.js +40 -0
- package/lib/runtime/runtimeStore.d.ts +77 -0
- package/lib/runtime/runtimeStore.js +184 -0
- package/lib/runtime/types.d.ts +84 -0
- package/lib/runtime/types.js +15 -0
- package/lib/runtime/useAgentConnection.d.ts +46 -0
- package/lib/runtime/useAgentConnection.js +112 -0
- package/lib/runtime/useAgentRuntime.d.ts +94 -0
- package/lib/runtime/useAgentRuntime.js +125 -0
- package/lib/test-setup.d.ts +1 -1
- package/lib/test-setup.js +1 -0
- package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.js +32 -1
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +6 -0
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +16 -17
- package/package.json +20 -7
- package/patches/@datalayer+jupyter-lexical+1.0.8.patch +11628 -0
- package/patches/@datalayer+jupyter-react+2.0.2.patch +5338 -0
- package/lib/examples/AgentSpaceHomeExample.d.ts +0 -8
- package/lib/examples/AgentSpaceHomeExample.js +0 -171
- package/lib/examples/components/AgentsDataTable.d.ts +0 -13
- package/lib/examples/components/AgentsDataTable.js +0 -74
- package/lib/examples/components/Rating.d.ts +0 -14
- package/lib/examples/components/Rating.js +0 -12
package/lib/examples/main.js
CHANGED
|
@@ -10,6 +10,7 @@ import { loadJupyterConfig, JupyterReactTheme, createServerSettings, setJupyterS
|
|
|
10
10
|
import { ServiceManager } from '@jupyterlab/services';
|
|
11
11
|
import { coreStore, iamStore, createDatalayerServiceManager, } from '@datalayer/core';
|
|
12
12
|
import { useChatStore } from '../components/chat/store';
|
|
13
|
+
import { OAuthCallback } from '../identity';
|
|
13
14
|
import { EXAMPLES } from './example-selector';
|
|
14
15
|
import nbformatExample from './stores/notebooks/NotebookExample1.ipynb.json';
|
|
15
16
|
// Load configurations from DOM
|
|
@@ -94,6 +95,14 @@ const isNotebookOnlyRoute = () => {
|
|
|
94
95
|
console.log('Is notebook-only route:', isNotebookRoute);
|
|
95
96
|
return isNotebookRoute;
|
|
96
97
|
};
|
|
98
|
+
// Check if we're handling an OAuth callback (code and state in URL params)
|
|
99
|
+
const isOAuthCallback = () => {
|
|
100
|
+
const params = new URLSearchParams(window.location.search);
|
|
101
|
+
const hasCode = params.has('code');
|
|
102
|
+
const hasState = params.has('state');
|
|
103
|
+
const hasError = params.has('error');
|
|
104
|
+
return (hasCode && hasState) || hasError;
|
|
105
|
+
};
|
|
97
106
|
// Get the default example name from localStorage
|
|
98
107
|
const getDefaultExampleName = () => {
|
|
99
108
|
const stored = localStorage.getItem('selectedExample');
|
|
@@ -320,7 +329,12 @@ export const ExampleApp = () => {
|
|
|
320
329
|
// Mount the app - check route to determine which app to render
|
|
321
330
|
const root = document.getElementById('root');
|
|
322
331
|
if (root) {
|
|
323
|
-
if (
|
|
332
|
+
if (isOAuthCallback()) {
|
|
333
|
+
// Handle OAuth callback - render OAuthCallback component
|
|
334
|
+
console.log('Rendering OAuthCallback (popup flow)');
|
|
335
|
+
createRoot(root).render(_jsx(JupyterReactTheme, { children: _jsx(OAuthCallback, { autoClose: true, autoCloseDelay: 1000 }) }));
|
|
336
|
+
}
|
|
337
|
+
else if (isNotebookOnlyRoute()) {
|
|
324
338
|
console.log('Rendering NotebookOnlyApp');
|
|
325
339
|
createRoot(root).render(_jsx(NotebookOnlyApp, {}));
|
|
326
340
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { OAuthProvider, OAuthProviderConfig, Identity } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Props for IdentityButton component
|
|
5
|
+
*/
|
|
6
|
+
export interface IdentityButtonProps {
|
|
7
|
+
/** Provider type */
|
|
8
|
+
provider: OAuthProvider;
|
|
9
|
+
/** OAuth client ID (required for connecting) */
|
|
10
|
+
clientId?: string;
|
|
11
|
+
/** Custom provider configuration (for custom providers) */
|
|
12
|
+
providerConfig?: Partial<OAuthProviderConfig>;
|
|
13
|
+
/** Scopes to request (defaults to provider defaults) */
|
|
14
|
+
scopes?: string[];
|
|
15
|
+
/** Whether the button is disabled */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Size variant */
|
|
18
|
+
size?: 'small' | 'medium' | 'large';
|
|
19
|
+
/** Show full provider info or just icon */
|
|
20
|
+
variant?: 'full' | 'compact' | 'icon';
|
|
21
|
+
/** Callback when connection completes */
|
|
22
|
+
onConnect?: (identity: Identity) => void;
|
|
23
|
+
/** Callback when disconnection completes */
|
|
24
|
+
onDisconnect?: (provider: OAuthProvider) => void;
|
|
25
|
+
/** Callback when error occurs */
|
|
26
|
+
onError?: (error: Error) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Single provider connect/disconnect button
|
|
30
|
+
*/
|
|
31
|
+
export declare const IdentityButton: React.FC<IdentityButtonProps>;
|
|
32
|
+
/**
|
|
33
|
+
* Props for IdentityConnect component
|
|
34
|
+
*/
|
|
35
|
+
export interface IdentityConnectProps {
|
|
36
|
+
/** Provider configurations with client IDs */
|
|
37
|
+
providers: {
|
|
38
|
+
[K in OAuthProvider]?: {
|
|
39
|
+
clientId: string;
|
|
40
|
+
scopes?: string[];
|
|
41
|
+
config?: Partial<OAuthProviderConfig>;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/** Layout variant */
|
|
45
|
+
layout?: 'list' | 'grid' | 'inline';
|
|
46
|
+
/** Show title/header */
|
|
47
|
+
showHeader?: boolean;
|
|
48
|
+
/** Custom title */
|
|
49
|
+
title?: string;
|
|
50
|
+
/** Size variant */
|
|
51
|
+
size?: 'small' | 'medium' | 'large';
|
|
52
|
+
/** Whether to show descriptions */
|
|
53
|
+
showDescriptions?: boolean;
|
|
54
|
+
/** Callback when any identity connects */
|
|
55
|
+
onConnect?: (identity: Identity) => void;
|
|
56
|
+
/** Callback when any identity disconnects */
|
|
57
|
+
onDisconnect?: (provider: OAuthProvider) => void;
|
|
58
|
+
/** Callback when error occurs */
|
|
59
|
+
onError?: (provider: OAuthProvider, error: Error) => void;
|
|
60
|
+
/** Whether all buttons are disabled */
|
|
61
|
+
disabled?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Multi-provider identity connection panel
|
|
65
|
+
*/
|
|
66
|
+
export declare const IdentityConnect: React.FC<IdentityConnectProps>;
|
|
67
|
+
/**
|
|
68
|
+
* Props for IdentityMenu component
|
|
69
|
+
*/
|
|
70
|
+
export interface IdentityMenuProps {
|
|
71
|
+
/** Provider configurations with client IDs */
|
|
72
|
+
providers: {
|
|
73
|
+
[K in OAuthProvider]?: {
|
|
74
|
+
clientId: string;
|
|
75
|
+
scopes?: string[];
|
|
76
|
+
config?: Partial<OAuthProviderConfig>;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
/** Callback when any identity connects */
|
|
80
|
+
onConnect?: (identity: Identity) => void;
|
|
81
|
+
/** Callback when any identity disconnects */
|
|
82
|
+
onDisconnect?: (provider: OAuthProvider) => void;
|
|
83
|
+
/** Whether the menu is disabled */
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Dropdown menu for identity management
|
|
88
|
+
*/
|
|
89
|
+
export declare const IdentityMenu: React.FC<IdentityMenuProps>;
|
|
90
|
+
export default IdentityConnect;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
import React, { useCallback } from 'react';
|
|
7
|
+
import { Box, Text, Button, Label, Flash, ActionList, ActionMenu, Avatar, } from '@primer/react';
|
|
8
|
+
import { MarkGithubIcon, LinkIcon, UnlinkIcon, CheckCircleFillIcon, AlertIcon, KeyIcon, } from '@primer/octicons-react';
|
|
9
|
+
import { useIdentity } from './useIdentity';
|
|
10
|
+
import { GITHUB_PROVIDER, GOOGLE_PROVIDER, KAGGLE_PROVIDER } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* Provider-specific display configurations.
|
|
13
|
+
* - GitHub: Uses official GitHub icon and dark theme color
|
|
14
|
+
* - Google: Uses key icon placeholder and Google blue
|
|
15
|
+
* - Kaggle: Uses key icon placeholder and Kaggle cyan
|
|
16
|
+
*/
|
|
17
|
+
const PROVIDER_DISPLAY = {
|
|
18
|
+
github: {
|
|
19
|
+
name: 'GitHub',
|
|
20
|
+
icon: MarkGithubIcon, // Official GitHub Octicon
|
|
21
|
+
color: '#24292f', // GitHub dark theme color
|
|
22
|
+
description: 'Access GitHub repositories and APIs',
|
|
23
|
+
},
|
|
24
|
+
google: {
|
|
25
|
+
name: 'Google',
|
|
26
|
+
icon: KeyIcon, // Would use Google icon if available
|
|
27
|
+
color: '#4285f4',
|
|
28
|
+
description: 'Access Google services and APIs',
|
|
29
|
+
},
|
|
30
|
+
kaggle: {
|
|
31
|
+
name: 'Kaggle',
|
|
32
|
+
icon: KeyIcon, // Would use Kaggle icon if available
|
|
33
|
+
color: '#20beff',
|
|
34
|
+
description: 'Access Kaggle datasets and notebooks',
|
|
35
|
+
},
|
|
36
|
+
linkedin: {
|
|
37
|
+
name: 'LinkedIn',
|
|
38
|
+
icon: KeyIcon,
|
|
39
|
+
color: '#0077b5',
|
|
40
|
+
description: 'Access LinkedIn profile and connections',
|
|
41
|
+
},
|
|
42
|
+
slack: {
|
|
43
|
+
name: 'Slack',
|
|
44
|
+
icon: KeyIcon,
|
|
45
|
+
color: '#4a154b',
|
|
46
|
+
description: 'Access Slack workspaces and channels',
|
|
47
|
+
},
|
|
48
|
+
notion: {
|
|
49
|
+
name: 'Notion',
|
|
50
|
+
icon: KeyIcon,
|
|
51
|
+
color: '#000000',
|
|
52
|
+
description: 'Access Notion pages and databases',
|
|
53
|
+
},
|
|
54
|
+
custom: {
|
|
55
|
+
name: 'Custom',
|
|
56
|
+
icon: LinkIcon,
|
|
57
|
+
color: '#6f42c1',
|
|
58
|
+
description: 'Custom OAuth provider',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Default OAuth scopes for common providers.
|
|
63
|
+
* - GitHub: read:user and user:email for basic profile access
|
|
64
|
+
* - Google: OpenID Connect scopes for profile and email
|
|
65
|
+
* - Kaggle: read access for datasets
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_SCOPES = {
|
|
68
|
+
github: ['read:user', 'user:email'], // GitHub scopes for user profile
|
|
69
|
+
google: ['openid', 'profile', 'email'], // OpenID Connect scopes
|
|
70
|
+
kaggle: ['read'],
|
|
71
|
+
linkedin: ['r_liteprofile', 'r_emailaddress'],
|
|
72
|
+
slack: ['users:read', 'channels:read'],
|
|
73
|
+
notion: ['read_content'],
|
|
74
|
+
custom: [],
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Single provider connect/disconnect button
|
|
78
|
+
*/
|
|
79
|
+
export const IdentityButton = ({ provider, clientId, providerConfig, scopes, disabled = false, size = 'medium', variant = 'full', onConnect, onDisconnect, onError, }) => {
|
|
80
|
+
const { identities, isAuthorizing, error, connectWithPopup, disconnect, configureProvider, } = useIdentity();
|
|
81
|
+
const display = PROVIDER_DISPLAY[provider] || PROVIDER_DISPLAY.custom;
|
|
82
|
+
const identity = identities.find(id => id.provider === provider);
|
|
83
|
+
const isConnected = !!identity;
|
|
84
|
+
const isPending = isAuthorizing;
|
|
85
|
+
// Configure provider on mount if clientId is provided
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
if (clientId) {
|
|
88
|
+
const baseConfig = provider === 'github'
|
|
89
|
+
? GITHUB_PROVIDER
|
|
90
|
+
: provider === 'google'
|
|
91
|
+
? GOOGLE_PROVIDER
|
|
92
|
+
: provider === 'kaggle'
|
|
93
|
+
? KAGGLE_PROVIDER
|
|
94
|
+
: undefined;
|
|
95
|
+
if (baseConfig) {
|
|
96
|
+
// Build redirect URI from current page URL (without query params) if not provided
|
|
97
|
+
// This allows OAuth callback to return to the same page that initiated the flow
|
|
98
|
+
const redirectUri = providerConfig?.redirectUri ||
|
|
99
|
+
(typeof window !== 'undefined'
|
|
100
|
+
? `${window.location.origin}${window.location.pathname}`
|
|
101
|
+
: '');
|
|
102
|
+
configureProvider({
|
|
103
|
+
...baseConfig,
|
|
104
|
+
...providerConfig,
|
|
105
|
+
provider,
|
|
106
|
+
clientId,
|
|
107
|
+
redirectUri,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}, [clientId, provider, providerConfig, configureProvider]);
|
|
112
|
+
const handleClick = useCallback(async () => {
|
|
113
|
+
try {
|
|
114
|
+
if (isConnected) {
|
|
115
|
+
await disconnect(provider);
|
|
116
|
+
onDisconnect?.(provider);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Use popup flow to avoid losing application state
|
|
120
|
+
const identity = await connectWithPopup(provider, scopes || DEFAULT_SCOPES[provider]);
|
|
121
|
+
onConnect?.(identity);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// Don't treat 'popup closed by user' as an error to display
|
|
126
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
127
|
+
if (errorMessage !== 'Popup closed by user') {
|
|
128
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, [
|
|
132
|
+
isConnected,
|
|
133
|
+
provider,
|
|
134
|
+
scopes,
|
|
135
|
+
connectWithPopup,
|
|
136
|
+
disconnect,
|
|
137
|
+
onConnect,
|
|
138
|
+
onDisconnect,
|
|
139
|
+
onError,
|
|
140
|
+
]);
|
|
141
|
+
// Handle error display
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
if (error) {
|
|
144
|
+
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
145
|
+
}
|
|
146
|
+
}, [error, onError]);
|
|
147
|
+
const buttonProps = {
|
|
148
|
+
onClick: handleClick,
|
|
149
|
+
disabled: disabled || isPending || (!clientId && !isConnected),
|
|
150
|
+
size: size === 'large' ? 'large' : size === 'small' ? 'small' : 'medium',
|
|
151
|
+
};
|
|
152
|
+
if (variant === 'icon') {
|
|
153
|
+
return (_jsx(Button, { ...buttonProps, variant: isConnected ? 'primary' : 'default', "aria-label": isConnected ? `Disconnect ${display.name}` : `Connect ${display.name}`, sx: {
|
|
154
|
+
width: size === 'small' ? 28 : size === 'large' ? 44 : 36,
|
|
155
|
+
height: size === 'small' ? 28 : size === 'large' ? 44 : 36,
|
|
156
|
+
p: 0,
|
|
157
|
+
}, children: isPending ? (_jsx(Box, { as: "span", className: "anim-rotate", sx: { display: 'inline-block' }, children: "\u23F3" })) : (_jsx(display.icon, { size: size === 'small' ? 14 : 18 })) }));
|
|
158
|
+
}
|
|
159
|
+
if (variant === 'compact') {
|
|
160
|
+
return (_jsx(Button, { ...buttonProps, variant: isConnected ? 'primary' : 'default', leadingVisual: display.icon, trailingVisual: isConnected ? CheckCircleFillIcon : undefined, children: isPending ? 'Connecting...' : display.name }));
|
|
161
|
+
}
|
|
162
|
+
// Full variant
|
|
163
|
+
return (_jsxs(Box, { sx: {
|
|
164
|
+
display: 'flex',
|
|
165
|
+
flexDirection: 'column',
|
|
166
|
+
gap: 2,
|
|
167
|
+
p: 2,
|
|
168
|
+
border: '1px solid',
|
|
169
|
+
borderColor: isConnected ? 'success.muted' : 'border.default',
|
|
170
|
+
borderRadius: 2,
|
|
171
|
+
backgroundColor: isConnected ? 'success.subtle' : 'canvas.subtle',
|
|
172
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Box, { sx: {
|
|
173
|
+
width: 40,
|
|
174
|
+
height: 40,
|
|
175
|
+
borderRadius: 2,
|
|
176
|
+
display: 'flex',
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
justifyContent: 'center',
|
|
179
|
+
backgroundColor: display.color,
|
|
180
|
+
color: 'white',
|
|
181
|
+
}, children: _jsx(display.icon, { size: 20 }) }), _jsxs(Box, { sx: { flex: 1 }, children: [_jsx(Text, { sx: { fontWeight: 'bold', display: 'block' }, children: display.name }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block' }, children: isConnected && identity?.userInfo
|
|
182
|
+
? `Connected as ${identity.userInfo.name || identity.userInfo.email}`
|
|
183
|
+
: display.description })] }), !isConnected && (_jsx(Button, { ...buttonProps, variant: "primary", leadingVisual: LinkIcon, children: isPending ? 'Working...' : 'Connect' }))] }), isConnected && identity && (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, pl: 1 }, children: [identity.userInfo?.avatarUrl && (_jsx("a", { href: identity.userInfo.profileUrl ||
|
|
184
|
+
`https://github.com/${identity.userInfo.username}`, target: "_blank", rel: "noopener noreferrer", style: { display: 'block', lineHeight: 0 }, children: _jsx("img", { src: identity.userInfo.avatarUrl, alt: identity.userInfo.name || 'User avatar', style: {
|
|
185
|
+
width: 32,
|
|
186
|
+
height: 32,
|
|
187
|
+
borderRadius: '50%',
|
|
188
|
+
cursor: 'pointer',
|
|
189
|
+
} }) })), _jsxs(Box, { sx: { flex: 1 }, children: [identity.userInfo?.username && (_jsx("a", { href: identity.userInfo.profileUrl ||
|
|
190
|
+
`https://github.com/${identity.userInfo.username}`, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: 'none' }, children: _jsxs(Text, { sx: {
|
|
191
|
+
fontSize: 1,
|
|
192
|
+
color: 'fg.muted',
|
|
193
|
+
display: 'block',
|
|
194
|
+
':hover': { textDecoration: 'underline' },
|
|
195
|
+
}, children: ["@", identity.userInfo.username] }) })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block' }, children: identity.token?.expiresAt
|
|
196
|
+
? `Expires ${new Date(identity.token.expiresAt).toLocaleDateString()}`
|
|
197
|
+
: 'Token does not expire' })] })] }), identity.scopes && identity.scopes.length > 0 && (_jsx(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 1, pl: 1 }, children: identity.scopes.map(scope => (_jsx(Text, { sx: {
|
|
198
|
+
fontSize: 0,
|
|
199
|
+
px: 2,
|
|
200
|
+
py: 1,
|
|
201
|
+
bg: 'neutral.muted',
|
|
202
|
+
borderRadius: 2,
|
|
203
|
+
color: 'fg.muted',
|
|
204
|
+
}, children: scope }, scope))) })), _jsxs(Button, { ...buttonProps, variant: "danger", leadingVisual: UnlinkIcon, children: ["Disconnect from ", display.name] })] }))] }));
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Multi-provider identity connection panel
|
|
208
|
+
*/
|
|
209
|
+
export const IdentityConnect = ({ providers, layout = 'list', showHeader = true, title = 'Connected Accounts', size = 'medium', showDescriptions = true, onConnect, onDisconnect, onError, disabled = false, }) => {
|
|
210
|
+
const { identities, error } = useIdentity();
|
|
211
|
+
const connectedCount = identities.length;
|
|
212
|
+
const providerKeys = Object.keys(providers);
|
|
213
|
+
const handleConnect = useCallback((identity) => {
|
|
214
|
+
onConnect?.(identity);
|
|
215
|
+
}, [onConnect]);
|
|
216
|
+
const handleDisconnect = useCallback((provider) => {
|
|
217
|
+
onDisconnect?.(provider);
|
|
218
|
+
}, [onDisconnect]);
|
|
219
|
+
const handleError = useCallback((provider) => (err) => {
|
|
220
|
+
onError?.(provider, err);
|
|
221
|
+
}, [onError]);
|
|
222
|
+
if (layout === 'inline') {
|
|
223
|
+
return (_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [showHeader && (_jsxs(Text, { sx: { fontSize: 1, color: 'fg.muted', mr: 2 }, children: [title, ":"] })), providerKeys.map(provider => {
|
|
224
|
+
const config = providers[provider];
|
|
225
|
+
return (_jsx(IdentityButton, { provider: provider, clientId: config.clientId, scopes: config.scopes, providerConfig: config.config, size: size, variant: "icon", disabled: disabled, onConnect: handleConnect, onDisconnect: handleDisconnect, onError: handleError(provider) }, provider));
|
|
226
|
+
}), connectedCount > 0 && (_jsxs(Label, { variant: "success", children: [_jsx(Box, { as: "span", sx: {
|
|
227
|
+
mr: 1,
|
|
228
|
+
display: 'inline-flex',
|
|
229
|
+
verticalAlign: 'text-bottom',
|
|
230
|
+
}, children: _jsx(CheckCircleFillIcon, { size: 12 }) }), connectedCount, " connected"] }))] }));
|
|
231
|
+
}
|
|
232
|
+
return (_jsxs(Box, { sx: {
|
|
233
|
+
border: '1px solid',
|
|
234
|
+
borderColor: 'border.default',
|
|
235
|
+
borderRadius: 2,
|
|
236
|
+
backgroundColor: 'canvas.default',
|
|
237
|
+
overflow: 'hidden',
|
|
238
|
+
}, children: [showHeader && (_jsxs(Box, { sx: {
|
|
239
|
+
px: 3,
|
|
240
|
+
py: 2,
|
|
241
|
+
borderBottom: '1px solid',
|
|
242
|
+
borderColor: 'border.default',
|
|
243
|
+
backgroundColor: 'canvas.subtle',
|
|
244
|
+
display: 'flex',
|
|
245
|
+
alignItems: 'center',
|
|
246
|
+
justifyContent: 'space-between',
|
|
247
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'bold' }, children: title }), connectedCount > 0 && (_jsxs(Label, { variant: "success", children: [_jsx(Box, { as: "span", sx: {
|
|
248
|
+
mr: 1,
|
|
249
|
+
display: 'inline-flex',
|
|
250
|
+
verticalAlign: 'text-bottom',
|
|
251
|
+
}, children: _jsx(CheckCircleFillIcon, { size: 12 }) }), connectedCount, " connected"] }))] })), error && (_jsxs(Flash, { variant: "danger", sx: { m: 2, borderRadius: 1 }, children: [_jsx(Box, { as: "span", sx: { mr: 2, display: 'inline-flex', verticalAlign: 'text-bottom' }, children: _jsx(AlertIcon, { size: 16 }) }), error instanceof Error ? error.message : String(error)] })), _jsx(Box, { sx: {
|
|
252
|
+
p: 2,
|
|
253
|
+
display: layout === 'grid' ? 'grid' : 'flex',
|
|
254
|
+
flexDirection: layout === 'list' ? 'column' : undefined,
|
|
255
|
+
gridTemplateColumns: layout === 'grid'
|
|
256
|
+
? 'repeat(auto-fill, minmax(250px, 1fr))'
|
|
257
|
+
: undefined,
|
|
258
|
+
gap: 2,
|
|
259
|
+
}, children: providerKeys.map(provider => {
|
|
260
|
+
const config = providers[provider];
|
|
261
|
+
return (_jsx(IdentityButton, { provider: provider, clientId: config.clientId, scopes: config.scopes, providerConfig: config.config, size: size, variant: showDescriptions ? 'full' : 'compact', disabled: disabled, onConnect: handleConnect, onDisconnect: handleDisconnect, onError: handleError(provider) }, provider));
|
|
262
|
+
}) })] }));
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Dropdown menu for identity management
|
|
266
|
+
*/
|
|
267
|
+
export const IdentityMenu = ({ providers, onConnect, onDisconnect, disabled = false, }) => {
|
|
268
|
+
const { identities, connect, disconnect, configureProvider, isAuthorizing } = useIdentity();
|
|
269
|
+
const providerKeys = Object.keys(providers);
|
|
270
|
+
const connectedCount = identities.length;
|
|
271
|
+
// Configure all providers on mount
|
|
272
|
+
React.useEffect(() => {
|
|
273
|
+
providerKeys.forEach(provider => {
|
|
274
|
+
const config = providers[provider];
|
|
275
|
+
const baseConfig = provider === 'github'
|
|
276
|
+
? GITHUB_PROVIDER
|
|
277
|
+
: provider === 'google'
|
|
278
|
+
? GOOGLE_PROVIDER
|
|
279
|
+
: provider === 'kaggle'
|
|
280
|
+
? KAGGLE_PROVIDER
|
|
281
|
+
: undefined;
|
|
282
|
+
if (baseConfig) {
|
|
283
|
+
// Build redirect URI from current page URL
|
|
284
|
+
const redirectUri = typeof window !== 'undefined'
|
|
285
|
+
? `${window.location.origin}${window.location.pathname}`
|
|
286
|
+
: '';
|
|
287
|
+
configureProvider({
|
|
288
|
+
...baseConfig,
|
|
289
|
+
...config.config,
|
|
290
|
+
provider,
|
|
291
|
+
clientId: config.clientId,
|
|
292
|
+
redirectUri,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}, [providerKeys, providers, configureProvider]);
|
|
297
|
+
const handleProviderClick = useCallback(async (provider) => {
|
|
298
|
+
const identity = identities.find(id => id.provider === provider);
|
|
299
|
+
const config = providers[provider];
|
|
300
|
+
if (identity) {
|
|
301
|
+
await disconnect(provider);
|
|
302
|
+
onDisconnect?.(provider);
|
|
303
|
+
}
|
|
304
|
+
else if (config) {
|
|
305
|
+
await connect(provider, config.scopes || DEFAULT_SCOPES[provider]);
|
|
306
|
+
// Connection initiated, identity will be updated in store after callback
|
|
307
|
+
}
|
|
308
|
+
}, [identities, providers, connect, disconnect, onConnect, onDisconnect]);
|
|
309
|
+
return (_jsxs(ActionMenu, { children: [_jsxs(ActionMenu.Button, { disabled: disabled, variant: connectedCount > 0 ? 'primary' : 'default', leadingVisual: KeyIcon, children: ["Identities", connectedCount > 0 && (_jsx(Label, { variant: "accent", sx: { ml: 2 }, children: connectedCount }))] }), _jsx(ActionMenu.Overlay, { children: _jsx(ActionList, { children: _jsx(ActionList.Group, { title: "OAuth Providers", children: providerKeys.map(provider => {
|
|
310
|
+
const display = PROVIDER_DISPLAY[provider] || PROVIDER_DISPLAY.custom;
|
|
311
|
+
const identity = identities.find(id => id.provider === provider);
|
|
312
|
+
const isConnected = !!identity;
|
|
313
|
+
return (_jsxs(ActionList.Item, { onSelect: () => handleProviderClick(provider), disabled: isAuthorizing, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(display.icon, { size: 16 }) }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Text, { children: display.name }), isConnected && (_jsxs(_Fragment, { children: [identity?.userInfo?.avatarUrl && (_jsx(Avatar, { src: identity.userInfo.avatarUrl, size: 16 })), _jsx(Label, { variant: "success", sx: { ml: 1 }, children: "Connected" })] }))] }), _jsx(ActionList.TrailingVisual, { children: isConnected ? (_jsx(UnlinkIcon, { size: 16 })) : (_jsx(LinkIcon, { size: 16 })) })] }, provider));
|
|
314
|
+
}) }) }) })] }));
|
|
315
|
+
};
|
|
316
|
+
export default IdentityConnect;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for OAuthCallback component
|
|
4
|
+
*/
|
|
5
|
+
export interface OAuthCallbackProps {
|
|
6
|
+
/**
|
|
7
|
+
* Custom success message
|
|
8
|
+
*/
|
|
9
|
+
successMessage?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Custom error message prefix
|
|
12
|
+
*/
|
|
13
|
+
errorMessagePrefix?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to automatically close the window/tab on success
|
|
16
|
+
* @default true for popup flow, false for redirect flow
|
|
17
|
+
*/
|
|
18
|
+
autoClose?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Delay before auto-closing (in ms)
|
|
21
|
+
* @default 1500
|
|
22
|
+
*/
|
|
23
|
+
autoCloseDelay?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Callback when processing completes successfully
|
|
26
|
+
*/
|
|
27
|
+
onSuccess?: (provider: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* Callback when processing fails
|
|
30
|
+
*/
|
|
31
|
+
onError?: (error: Error) => void;
|
|
32
|
+
/**
|
|
33
|
+
* URL to redirect to after success (for redirect flow)
|
|
34
|
+
* If not provided, will try to use window.opener for popup flow
|
|
35
|
+
*/
|
|
36
|
+
redirectUrl?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether to show the close button
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
showCloseButton?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* OAuth callback handler component.
|
|
45
|
+
*
|
|
46
|
+
* This component should be rendered on the OAuth callback URL (e.g., /oauth/callback).
|
|
47
|
+
* It handles the authorization code exchange and token storage.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // For redirect flow - mount at /oauth/callback
|
|
51
|
+
* <Route path="/oauth/callback" element={<OAuthCallback redirectUrl="/" />} />
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // For popup flow - mount at /oauth/callback
|
|
55
|
+
* <Route path="/oauth/callback" element={<OAuthCallback autoClose />} />
|
|
56
|
+
*/
|
|
57
|
+
export declare const OAuthCallback: React.FC<OAuthCallbackProps>;
|
|
58
|
+
export default OAuthCallback;
|