veryfront 0.0.44 → 0.0.46
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/dist/ai/index.d.ts +11 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/index.js.map +2 -2
- package/dist/cli.js +240 -87
- package/dist/components.js +1 -1
- package/dist/components.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/data.js +1 -1
- package/dist/data.js.map +1 -1
- package/dist/index.js +34 -32
- package/dist/index.js.map +2 -2
- package/dist/integrations/_base/connector.json +11 -0
- package/dist/integrations/_base/files/SETUP.md +132 -0
- package/dist/integrations/_base/files/app/api/integrations/status/route.ts +38 -0
- package/dist/integrations/_base/files/app/setup/page.tsx +461 -0
- package/dist/integrations/_base/files/lib/oauth.ts +145 -0
- package/dist/integrations/_base/files/lib/token-store.ts +109 -0
- package/dist/integrations/calendar/connector.json +77 -0
- package/dist/integrations/calendar/files/ai/tools/create-event.ts +83 -0
- package/dist/integrations/calendar/files/ai/tools/find-free-time.ts +108 -0
- package/dist/integrations/calendar/files/ai/tools/list-events.ts +98 -0
- package/dist/integrations/calendar/files/app/api/auth/calendar/callback/route.ts +114 -0
- package/dist/integrations/calendar/files/app/api/auth/calendar/route.ts +29 -0
- package/dist/integrations/calendar/files/lib/calendar-client.ts +309 -0
- package/dist/integrations/calendar/files/lib/oauth.ts +145 -0
- package/dist/integrations/calendar/files/lib/token-store.ts +109 -0
- package/dist/integrations/github/connector.json +84 -0
- package/dist/integrations/github/files/ai/tools/create-issue.ts +75 -0
- package/dist/integrations/github/files/ai/tools/get-pr-diff.ts +82 -0
- package/dist/integrations/github/files/ai/tools/list-prs.ts +93 -0
- package/dist/integrations/github/files/ai/tools/list-repos.ts +81 -0
- package/dist/integrations/github/files/app/api/auth/github/callback/route.ts +132 -0
- package/dist/integrations/github/files/app/api/auth/github/route.ts +29 -0
- package/dist/integrations/github/files/lib/github-client.ts +282 -0
- package/dist/integrations/github/files/lib/oauth.ts +145 -0
- package/dist/integrations/github/files/lib/token-store.ts +109 -0
- package/dist/integrations/gmail/connector.json +78 -0
- package/dist/integrations/gmail/files/ai/tools/list-emails.ts +92 -0
- package/dist/integrations/gmail/files/ai/tools/search-emails.ts +92 -0
- package/dist/integrations/gmail/files/ai/tools/send-email.ts +77 -0
- package/dist/integrations/gmail/files/app/api/auth/gmail/callback/route.ts +114 -0
- package/dist/integrations/gmail/files/app/api/auth/gmail/route.ts +29 -0
- package/dist/integrations/gmail/files/lib/gmail-client.ts +259 -0
- package/dist/integrations/gmail/files/lib/oauth.ts +145 -0
- package/dist/integrations/gmail/files/lib/token-store.ts +109 -0
- package/dist/integrations/slack/connector.json +74 -0
- package/dist/integrations/slack/files/ai/tools/get-messages.ts +65 -0
- package/dist/integrations/slack/files/ai/tools/list-channels.ts +63 -0
- package/dist/integrations/slack/files/ai/tools/send-message.ts +49 -0
- package/dist/integrations/slack/files/app/api/auth/slack/callback/route.ts +132 -0
- package/dist/integrations/slack/files/app/api/auth/slack/route.ts +29 -0
- package/dist/integrations/slack/files/lib/oauth.ts +145 -0
- package/dist/integrations/slack/files/lib/slack-client.ts +215 -0
- package/dist/integrations/slack/files/lib/token-store.ts +109 -0
- package/package.json +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Service Integration Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide will help you set up OAuth credentials for your AI agent's service integrations.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Start the development server: `veryfront dev`
|
|
8
|
+
2. Visit `http://localhost:3000/setup` for an interactive setup guide
|
|
9
|
+
3. Follow the step-by-step instructions for each service
|
|
10
|
+
|
|
11
|
+
## Service-Specific Setup
|
|
12
|
+
|
|
13
|
+
### Gmail & Google Calendar
|
|
14
|
+
|
|
15
|
+
Both Gmail and Calendar use Google OAuth. You only need one set of credentials.
|
|
16
|
+
|
|
17
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
|
|
18
|
+
2. Create a new project or select an existing one
|
|
19
|
+
3. Enable APIs:
|
|
20
|
+
- Gmail API
|
|
21
|
+
- Google Calendar API
|
|
22
|
+
4. Create OAuth 2.0 credentials:
|
|
23
|
+
- Application type: **Web application**
|
|
24
|
+
- Authorized redirect URIs:
|
|
25
|
+
- `http://localhost:3000/api/auth/gmail/callback`
|
|
26
|
+
- `http://localhost:3000/api/auth/calendar/callback`
|
|
27
|
+
5. Copy credentials to `.env`:
|
|
28
|
+
```
|
|
29
|
+
GOOGLE_CLIENT_ID=your-client-id
|
|
30
|
+
GOOGLE_CLIENT_SECRET=your-client-secret
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Slack
|
|
34
|
+
|
|
35
|
+
1. Go to [Slack API Apps](https://api.slack.com/apps)
|
|
36
|
+
2. Click "Create New App" > "From scratch"
|
|
37
|
+
3. Add OAuth scopes under "OAuth & Permissions":
|
|
38
|
+
- `channels:read` - View basic channel info
|
|
39
|
+
- `chat:write` - Send messages
|
|
40
|
+
- `users:read` - View users
|
|
41
|
+
4. Add redirect URL:
|
|
42
|
+
- `http://localhost:3000/api/auth/slack/callback`
|
|
43
|
+
5. Install to your workspace
|
|
44
|
+
6. Copy credentials to `.env`:
|
|
45
|
+
```
|
|
46
|
+
SLACK_CLIENT_ID=your-client-id
|
|
47
|
+
SLACK_CLIENT_SECRET=your-client-secret
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### GitHub
|
|
51
|
+
|
|
52
|
+
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
|
53
|
+
2. Click "New OAuth App"
|
|
54
|
+
3. Fill in the form:
|
|
55
|
+
- Homepage URL: `http://localhost:3000`
|
|
56
|
+
- Authorization callback URL: `http://localhost:3000/api/auth/github/callback`
|
|
57
|
+
4. Copy credentials to `.env`:
|
|
58
|
+
```
|
|
59
|
+
GITHUB_CLIENT_ID=your-client-id
|
|
60
|
+
GITHUB_CLIENT_SECRET=your-client-secret
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Jira (Atlassian)
|
|
64
|
+
|
|
65
|
+
1. Go to [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/)
|
|
66
|
+
2. Create a new OAuth 2.0 integration
|
|
67
|
+
3. Add required scopes:
|
|
68
|
+
- `read:jira-work`
|
|
69
|
+
- `write:jira-work`
|
|
70
|
+
4. Set callback URL:
|
|
71
|
+
- `http://localhost:3000/api/auth/jira/callback`
|
|
72
|
+
5. Copy credentials to `.env`:
|
|
73
|
+
```
|
|
74
|
+
JIRA_CLIENT_ID=your-client-id
|
|
75
|
+
JIRA_CLIENT_SECRET=your-client-secret
|
|
76
|
+
JIRA_CLOUD_ID=your-atlassian-cloud-id
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Notion
|
|
80
|
+
|
|
81
|
+
1. Go to [Notion Integrations](https://www.notion.so/my-integrations)
|
|
82
|
+
2. Click "New integration"
|
|
83
|
+
3. Configure:
|
|
84
|
+
- Name: Your app name
|
|
85
|
+
- Associated workspace: Select your workspace
|
|
86
|
+
- Capabilities: Select required capabilities
|
|
87
|
+
4. Copy the Internal Integration Token to `.env`:
|
|
88
|
+
```
|
|
89
|
+
NOTION_API_KEY=your-integration-token
|
|
90
|
+
```
|
|
91
|
+
5. **Important**: Share the pages you want to access with your integration
|
|
92
|
+
|
|
93
|
+
## Testing Connections
|
|
94
|
+
|
|
95
|
+
After setting up credentials:
|
|
96
|
+
|
|
97
|
+
1. Start the dev server: `veryfront dev`
|
|
98
|
+
2. Visit each connection URL to authorize:
|
|
99
|
+
- Gmail: `http://localhost:3000/api/auth/gmail`
|
|
100
|
+
- Slack: `http://localhost:3000/api/auth/slack`
|
|
101
|
+
- Calendar: `http://localhost:3000/api/auth/calendar`
|
|
102
|
+
- GitHub: `http://localhost:3000/api/auth/github`
|
|
103
|
+
3. Check connection status at `http://localhost:3000/setup`
|
|
104
|
+
|
|
105
|
+
## Troubleshooting
|
|
106
|
+
|
|
107
|
+
### "Invalid redirect URI" error
|
|
108
|
+
- Make sure the callback URL in your OAuth app matches exactly
|
|
109
|
+
- Include the full path: `/api/auth/{service}/callback`
|
|
110
|
+
|
|
111
|
+
### "Access denied" error
|
|
112
|
+
- Check that all required scopes are added
|
|
113
|
+
- For Slack, ensure the app is installed to your workspace
|
|
114
|
+
|
|
115
|
+
### Tokens expire
|
|
116
|
+
- Tokens are stored in memory by default (reset on server restart)
|
|
117
|
+
- For production, implement persistent storage in `lib/token-store.ts`
|
|
118
|
+
|
|
119
|
+
## Production Deployment
|
|
120
|
+
|
|
121
|
+
Before deploying:
|
|
122
|
+
|
|
123
|
+
1. Update redirect URIs to your production domain
|
|
124
|
+
2. Implement persistent token storage (database/KV)
|
|
125
|
+
3. Add proper error handling and logging
|
|
126
|
+
4. Consider rate limiting for API calls
|
|
127
|
+
|
|
128
|
+
## Need Help?
|
|
129
|
+
|
|
130
|
+
- Check the interactive setup guide at `/setup`
|
|
131
|
+
- Review the API documentation for each service
|
|
132
|
+
- Ensure environment variables are properly set
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Status API
|
|
3
|
+
*
|
|
4
|
+
* Returns the connection status of all configured integrations.
|
|
5
|
+
* Used by the setup guide to show which services are connected.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { tokenStore } from "../../../../lib/token-store.ts";
|
|
9
|
+
|
|
10
|
+
// Define available integrations - will be populated based on project config
|
|
11
|
+
const INTEGRATIONS = [
|
|
12
|
+
{ id: "gmail", name: "Gmail", icon: "mail" },
|
|
13
|
+
{ id: "slack", name: "Slack", icon: "slack" },
|
|
14
|
+
{ id: "calendar", name: "Calendar", icon: "calendar" },
|
|
15
|
+
{ id: "github", name: "GitHub", icon: "github" },
|
|
16
|
+
{ id: "jira", name: "Jira", icon: "jira" },
|
|
17
|
+
{ id: "notion", name: "Notion", icon: "notion" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export async function GET(_req: Request) {
|
|
21
|
+
// Get actual user ID from session in production
|
|
22
|
+
const userId = "current-user";
|
|
23
|
+
|
|
24
|
+
const statuses = await Promise.all(
|
|
25
|
+
INTEGRATIONS.map(async (integration) => {
|
|
26
|
+
const token = await tokenStore.getToken(userId, integration.id);
|
|
27
|
+
return {
|
|
28
|
+
id: integration.id,
|
|
29
|
+
name: integration.name,
|
|
30
|
+
icon: integration.icon,
|
|
31
|
+
connected: !!token,
|
|
32
|
+
connectUrl: `/api/auth/${integration.id}`,
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return Response.json({ integrations: statuses });
|
|
38
|
+
}
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
interface Integration {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
connected: boolean;
|
|
10
|
+
connectUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SetupStep {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description: string;
|
|
17
|
+
completed: boolean;
|
|
18
|
+
action?: () => void;
|
|
19
|
+
link?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const OAUTH_SETUP_GUIDES: Record<string, { title: string; steps: string[]; link: string }> = {
|
|
23
|
+
gmail: {
|
|
24
|
+
title: "Google OAuth Setup (Gmail & Calendar)",
|
|
25
|
+
steps: [
|
|
26
|
+
"Go to Google Cloud Console",
|
|
27
|
+
"Create a new project or select existing one",
|
|
28
|
+
"Enable Gmail API and Calendar API",
|
|
29
|
+
"Create OAuth 2.0 credentials (Web application)",
|
|
30
|
+
"Add redirect URI: http://localhost:3000/api/auth/gmail/callback",
|
|
31
|
+
"Copy Client ID and Secret to your .env file",
|
|
32
|
+
],
|
|
33
|
+
link: "https://console.cloud.google.com/apis/credentials",
|
|
34
|
+
},
|
|
35
|
+
calendar: {
|
|
36
|
+
title: "Google Calendar Setup",
|
|
37
|
+
steps: [
|
|
38
|
+
"Uses same credentials as Gmail",
|
|
39
|
+
"Make sure Calendar API is enabled",
|
|
40
|
+
"Add redirect URI: http://localhost:3000/api/auth/calendar/callback",
|
|
41
|
+
],
|
|
42
|
+
link: "https://console.cloud.google.com/apis/credentials",
|
|
43
|
+
},
|
|
44
|
+
slack: {
|
|
45
|
+
title: "Slack App Setup",
|
|
46
|
+
steps: [
|
|
47
|
+
"Go to Slack API Apps page",
|
|
48
|
+
"Create New App > From scratch",
|
|
49
|
+
"Add OAuth Scopes: channels:read, chat:write, users:read",
|
|
50
|
+
"Install to Workspace",
|
|
51
|
+
"Copy Client ID and Secret to your .env file",
|
|
52
|
+
"Add redirect URL: http://localhost:3000/api/auth/slack/callback",
|
|
53
|
+
],
|
|
54
|
+
link: "https://api.slack.com/apps",
|
|
55
|
+
},
|
|
56
|
+
github: {
|
|
57
|
+
title: "GitHub OAuth App Setup",
|
|
58
|
+
steps: [
|
|
59
|
+
"Go to GitHub Developer Settings",
|
|
60
|
+
"Click 'New OAuth App'",
|
|
61
|
+
"Set Homepage URL to http://localhost:3000",
|
|
62
|
+
"Set callback URL to http://localhost:3000/api/auth/github/callback",
|
|
63
|
+
"Copy Client ID and Secret to your .env file",
|
|
64
|
+
],
|
|
65
|
+
link: "https://github.com/settings/developers",
|
|
66
|
+
},
|
|
67
|
+
jira: {
|
|
68
|
+
title: "Atlassian (Jira) Setup",
|
|
69
|
+
steps: [
|
|
70
|
+
"Go to Atlassian Developer Console",
|
|
71
|
+
"Create OAuth 2.0 integration",
|
|
72
|
+
"Add Jira API scopes",
|
|
73
|
+
"Set callback URL: http://localhost:3000/api/auth/jira/callback",
|
|
74
|
+
"Copy Client ID and Secret to your .env file",
|
|
75
|
+
],
|
|
76
|
+
link: "https://developer.atlassian.com/console/myapps/",
|
|
77
|
+
},
|
|
78
|
+
notion: {
|
|
79
|
+
title: "Notion Integration Setup",
|
|
80
|
+
steps: [
|
|
81
|
+
"Go to Notion Integrations page",
|
|
82
|
+
"Create new integration",
|
|
83
|
+
"Copy the Internal Integration Token",
|
|
84
|
+
"Add token to your .env file",
|
|
85
|
+
"Share desired pages with your integration",
|
|
86
|
+
],
|
|
87
|
+
link: "https://www.notion.so/my-integrations",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default function SetupPage() {
|
|
92
|
+
const [integrations, setIntegrations] = useState<Integration[]>([]);
|
|
93
|
+
const [loading, setLoading] = useState(true);
|
|
94
|
+
const [expandedGuide, setExpandedGuide] = useState<string | null>(null);
|
|
95
|
+
const [envChecked, setEnvChecked] = useState(false);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
fetchStatus();
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
async function fetchStatus() {
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch("/api/integrations/status");
|
|
104
|
+
const data = await res.json();
|
|
105
|
+
setIntegrations(data.integrations);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error("Failed to fetch integration status:", error);
|
|
108
|
+
} finally {
|
|
109
|
+
setLoading(false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const connectedCount = integrations.filter((i) => i.connected).length;
|
|
114
|
+
const totalCount = integrations.length;
|
|
115
|
+
const progress = totalCount > 0 ? (connectedCount / totalCount) * 100 : 0;
|
|
116
|
+
|
|
117
|
+
const setupSteps: SetupStep[] = [
|
|
118
|
+
{
|
|
119
|
+
id: "env",
|
|
120
|
+
title: "Configure Environment Variables",
|
|
121
|
+
description: "Add your OAuth credentials to the .env file",
|
|
122
|
+
completed: envChecked,
|
|
123
|
+
action: () => setEnvChecked(true),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "oauth",
|
|
127
|
+
title: "Create OAuth Apps",
|
|
128
|
+
description: "Set up OAuth applications for each service",
|
|
129
|
+
completed: false,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "connect",
|
|
133
|
+
title: "Connect Services",
|
|
134
|
+
description: "Authorize your app to access each service",
|
|
135
|
+
completed: connectedCount === totalCount && totalCount > 0,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-900">
|
|
141
|
+
<div className="max-w-4xl mx-auto px-4 py-12">
|
|
142
|
+
{/* Header */}
|
|
143
|
+
<div className="text-center mb-12">
|
|
144
|
+
<h1 className="text-4xl font-bold text-neutral-900 dark:text-white mb-4">
|
|
145
|
+
Setup Your AI Agent
|
|
146
|
+
</h1>
|
|
147
|
+
<p className="text-lg text-neutral-600 dark:text-neutral-400">
|
|
148
|
+
Connect your services to enable AI-powered automation
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Progress Bar */}
|
|
153
|
+
<div className="bg-white dark:bg-neutral-800 rounded-2xl p-6 shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8">
|
|
154
|
+
<div className="flex items-center justify-between mb-2">
|
|
155
|
+
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
|
156
|
+
Setup Progress
|
|
157
|
+
</span>
|
|
158
|
+
<span className="text-sm font-medium text-neutral-900 dark:text-white">
|
|
159
|
+
{connectedCount} / {totalCount} services connected
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div className="w-full bg-neutral-200 dark:bg-neutral-700 rounded-full h-3">
|
|
163
|
+
<div
|
|
164
|
+
className="bg-gradient-to-r from-green-500 to-emerald-500 h-3 rounded-full transition-all duration-500"
|
|
165
|
+
style={{ width: `${progress}%` }}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Setup Steps */}
|
|
171
|
+
<div className="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8 overflow-hidden">
|
|
172
|
+
<div className="p-6 border-b border-neutral-200 dark:border-neutral-700">
|
|
173
|
+
<h2 className="text-xl font-semibold text-neutral-900 dark:text-white">
|
|
174
|
+
Quick Start Guide
|
|
175
|
+
</h2>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="divide-y divide-neutral-200 dark:divide-neutral-700">
|
|
178
|
+
{setupSteps.map((step, index) => (
|
|
179
|
+
<div
|
|
180
|
+
key={step.id}
|
|
181
|
+
className="p-6 flex items-start gap-4"
|
|
182
|
+
>
|
|
183
|
+
<div
|
|
184
|
+
className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
|
|
185
|
+
step.completed
|
|
186
|
+
? "bg-green-500 text-white"
|
|
187
|
+
: "bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400"
|
|
188
|
+
}`}
|
|
189
|
+
>
|
|
190
|
+
{step.completed
|
|
191
|
+
? (
|
|
192
|
+
<svg
|
|
193
|
+
className="w-5 h-5"
|
|
194
|
+
fill="none"
|
|
195
|
+
stroke="currentColor"
|
|
196
|
+
viewBox="0 0 24 24"
|
|
197
|
+
>
|
|
198
|
+
<path
|
|
199
|
+
strokeLinecap="round"
|
|
200
|
+
strokeLinejoin="round"
|
|
201
|
+
strokeWidth={2}
|
|
202
|
+
d="M5 13l4 4L19 7"
|
|
203
|
+
/>
|
|
204
|
+
</svg>
|
|
205
|
+
)
|
|
206
|
+
: <span className="font-semibold">{index + 1}</span>}
|
|
207
|
+
</div>
|
|
208
|
+
<div className="flex-1">
|
|
209
|
+
<h3 className="font-semibold text-neutral-900 dark:text-white">
|
|
210
|
+
{step.title}
|
|
211
|
+
</h3>
|
|
212
|
+
<p className="text-sm text-neutral-600 dark:text-neutral-400 mt-1">
|
|
213
|
+
{step.description}
|
|
214
|
+
</p>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
))}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{/* Service Connections */}
|
|
222
|
+
<div className="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 overflow-hidden">
|
|
223
|
+
<div className="p-6 border-b border-neutral-200 dark:border-neutral-700">
|
|
224
|
+
<h2 className="text-xl font-semibold text-neutral-900 dark:text-white">
|
|
225
|
+
Service Connections
|
|
226
|
+
</h2>
|
|
227
|
+
<p className="text-sm text-neutral-600 dark:text-neutral-400 mt-1">
|
|
228
|
+
Click on a service to see setup instructions or connect
|
|
229
|
+
</p>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{loading
|
|
233
|
+
? <div className="p-12 text-center text-neutral-500">Loading...</div>
|
|
234
|
+
: (
|
|
235
|
+
<div className="divide-y divide-neutral-200 dark:divide-neutral-700">
|
|
236
|
+
{integrations.map((integration) => (
|
|
237
|
+
<div key={integration.id}>
|
|
238
|
+
<div className="p-6 flex items-center justify-between">
|
|
239
|
+
<div className="flex items-center gap-4">
|
|
240
|
+
<div className="w-12 h-12 bg-neutral-100 dark:bg-neutral-700 rounded-xl flex items-center justify-center">
|
|
241
|
+
<ServiceIcon name={integration.icon} />
|
|
242
|
+
</div>
|
|
243
|
+
<div>
|
|
244
|
+
<h3 className="font-semibold text-neutral-900 dark:text-white">
|
|
245
|
+
{integration.name}
|
|
246
|
+
</h3>
|
|
247
|
+
<p
|
|
248
|
+
className={`text-sm ${
|
|
249
|
+
integration.connected
|
|
250
|
+
? "text-green-600 dark:text-green-400"
|
|
251
|
+
: "text-neutral-500"
|
|
252
|
+
}`}
|
|
253
|
+
>
|
|
254
|
+
{integration.connected ? "Connected" : "Not connected"}
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="flex items-center gap-3">
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
onClick={() =>
|
|
262
|
+
setExpandedGuide(
|
|
263
|
+
expandedGuide === integration.id ? null : integration.id,
|
|
264
|
+
)}
|
|
265
|
+
className="px-4 py-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white"
|
|
266
|
+
>
|
|
267
|
+
{expandedGuide === integration.id ? "Hide Guide" : "Setup Guide"}
|
|
268
|
+
</button>
|
|
269
|
+
{integration.connected
|
|
270
|
+
? (
|
|
271
|
+
<span className="inline-flex items-center gap-1.5 px-4 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 rounded-xl text-sm font-medium">
|
|
272
|
+
<span className="w-2 h-2 bg-green-500 rounded-full" />
|
|
273
|
+
Connected
|
|
274
|
+
</span>
|
|
275
|
+
)
|
|
276
|
+
: (
|
|
277
|
+
<a
|
|
278
|
+
href={integration.connectUrl}
|
|
279
|
+
className="px-4 py-2 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-xl text-sm font-medium hover:opacity-90 transition-opacity"
|
|
280
|
+
>
|
|
281
|
+
Connect
|
|
282
|
+
</a>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
{/* Expanded Setup Guide */}
|
|
288
|
+
{expandedGuide === integration.id && (() => {
|
|
289
|
+
const guide = OAUTH_SETUP_GUIDES[integration.id];
|
|
290
|
+
if (!guide) return null;
|
|
291
|
+
return (
|
|
292
|
+
<div className="px-6 pb-6">
|
|
293
|
+
<div className="bg-neutral-50 dark:bg-neutral-900 rounded-xl p-6 border border-neutral-200 dark:border-neutral-700">
|
|
294
|
+
<h4 className="font-semibold text-neutral-900 dark:text-white mb-4">
|
|
295
|
+
{guide.title}
|
|
296
|
+
</h4>
|
|
297
|
+
<ol className="space-y-3">
|
|
298
|
+
{guide.steps.map((step, i) => (
|
|
299
|
+
<li key={i} className="flex items-start gap-3">
|
|
300
|
+
<span className="w-6 h-6 bg-neutral-200 dark:bg-neutral-700 rounded-full flex items-center justify-center text-sm font-medium text-neutral-600 dark:text-neutral-400 flex-shrink-0">
|
|
301
|
+
{i + 1}
|
|
302
|
+
</span>
|
|
303
|
+
<span className="text-neutral-700 dark:text-neutral-300">
|
|
304
|
+
{step}
|
|
305
|
+
</span>
|
|
306
|
+
</li>
|
|
307
|
+
))}
|
|
308
|
+
</ol>
|
|
309
|
+
<a
|
|
310
|
+
href={guide.link}
|
|
311
|
+
target="_blank"
|
|
312
|
+
rel="noopener noreferrer"
|
|
313
|
+
className="mt-4 inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 text-sm font-medium hover:underline"
|
|
314
|
+
>
|
|
315
|
+
Open Developer Console
|
|
316
|
+
<svg
|
|
317
|
+
className="w-4 h-4"
|
|
318
|
+
fill="none"
|
|
319
|
+
stroke="currentColor"
|
|
320
|
+
viewBox="0 0 24 24"
|
|
321
|
+
>
|
|
322
|
+
<path
|
|
323
|
+
strokeLinecap="round"
|
|
324
|
+
strokeLinejoin="round"
|
|
325
|
+
strokeWidth={2}
|
|
326
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
327
|
+
/>
|
|
328
|
+
</svg>
|
|
329
|
+
</a>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
})()}
|
|
334
|
+
</div>
|
|
335
|
+
))}
|
|
336
|
+
</div>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
{/* All Connected Message */}
|
|
341
|
+
{connectedCount === totalCount && totalCount > 0 && (
|
|
342
|
+
<div className="mt-8 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-2xl p-6 border border-green-200 dark:border-green-800 text-center">
|
|
343
|
+
<div className="text-4xl mb-4">🎉</div>
|
|
344
|
+
<h3 className="text-xl font-semibold text-green-800 dark:text-green-200 mb-2">
|
|
345
|
+
All Services Connected!
|
|
346
|
+
</h3>
|
|
347
|
+
<p className="text-green-700 dark:text-green-300 mb-4">
|
|
348
|
+
Your AI agent is ready to use. Start chatting to automate your workflows.
|
|
349
|
+
</p>
|
|
350
|
+
<a
|
|
351
|
+
href="/"
|
|
352
|
+
className="inline-flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-xl font-medium hover:bg-green-700 transition-colors"
|
|
353
|
+
>
|
|
354
|
+
Start Using Your Agent
|
|
355
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
356
|
+
<path
|
|
357
|
+
strokeLinecap="round"
|
|
358
|
+
strokeLinejoin="round"
|
|
359
|
+
strokeWidth={2}
|
|
360
|
+
d="M13 7l5 5m0 0l-5 5m5-5H6"
|
|
361
|
+
/>
|
|
362
|
+
</svg>
|
|
363
|
+
</a>
|
|
364
|
+
</div>
|
|
365
|
+
)}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function ServiceIcon({ name }: { name: string }) {
|
|
372
|
+
const iconMap: Record<string, JSX.Element> = {
|
|
373
|
+
mail: (
|
|
374
|
+
<svg className="w-6 h-6 text-red-500" fill="currentColor" viewBox="0 0 24 24">
|
|
375
|
+
<path
|
|
376
|
+
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
377
|
+
stroke="currentColor"
|
|
378
|
+
strokeWidth="2"
|
|
379
|
+
fill="none"
|
|
380
|
+
/>
|
|
381
|
+
</svg>
|
|
382
|
+
),
|
|
383
|
+
slack: (
|
|
384
|
+
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none">
|
|
385
|
+
<path
|
|
386
|
+
d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
|
|
387
|
+
fill="#E01E5A"
|
|
388
|
+
/>
|
|
389
|
+
<path
|
|
390
|
+
d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
|
|
391
|
+
fill="#36C5F0"
|
|
392
|
+
/>
|
|
393
|
+
<path
|
|
394
|
+
d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312z"
|
|
395
|
+
fill="#2EB67D"
|
|
396
|
+
/>
|
|
397
|
+
<path
|
|
398
|
+
d="M15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"
|
|
399
|
+
fill="#ECB22E"
|
|
400
|
+
/>
|
|
401
|
+
</svg>
|
|
402
|
+
),
|
|
403
|
+
calendar: (
|
|
404
|
+
<svg className="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
405
|
+
<path
|
|
406
|
+
strokeLinecap="round"
|
|
407
|
+
strokeLinejoin="round"
|
|
408
|
+
strokeWidth={2}
|
|
409
|
+
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
410
|
+
/>
|
|
411
|
+
</svg>
|
|
412
|
+
),
|
|
413
|
+
github: (
|
|
414
|
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
|
415
|
+
<path
|
|
416
|
+
fillRule="evenodd"
|
|
417
|
+
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
|
418
|
+
clipRule="evenodd"
|
|
419
|
+
/>
|
|
420
|
+
</svg>
|
|
421
|
+
),
|
|
422
|
+
jira: (
|
|
423
|
+
<svg className="w-6 h-6" viewBox="0 0 24 24">
|
|
424
|
+
<defs>
|
|
425
|
+
<linearGradient id="jira-gradient" x1="98.031%" x2="58.888%" y1=".161%" y2="40.766%">
|
|
426
|
+
<stop offset="0%" stopColor="#0052CC" />
|
|
427
|
+
<stop offset="100%" stopColor="#2684FF" />
|
|
428
|
+
</linearGradient>
|
|
429
|
+
</defs>
|
|
430
|
+
<path
|
|
431
|
+
fill="url(#jira-gradient)"
|
|
432
|
+
d="M11.571 11.513H0a5.218 5.218 0 005.232 5.215h2.13v2.057A5.215 5.215 0 0012.575 24V12.518a1.005 1.005 0 00-1.005-1.005z"
|
|
433
|
+
/>
|
|
434
|
+
<path
|
|
435
|
+
fill="#2684FF"
|
|
436
|
+
d="M17.151 5.97H5.58a5.215 5.215 0 005.215 5.214h2.129v2.058a5.218 5.218 0 005.232 5.215V6.975a1.005 1.005 0 00-1.005-1.005z"
|
|
437
|
+
/>
|
|
438
|
+
<path
|
|
439
|
+
fill="#2684FF"
|
|
440
|
+
d="M22.723.426H11.152a5.215 5.215 0 005.215 5.215h2.129v2.057a5.218 5.218 0 005.232 5.215V1.431a1.005 1.005 0 00-1.005-1.005z"
|
|
441
|
+
/>
|
|
442
|
+
</svg>
|
|
443
|
+
),
|
|
444
|
+
notion: (
|
|
445
|
+
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="currentColor">
|
|
446
|
+
<path d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.98-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466l1.823 1.447zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.84-.046.933-.56.933-1.167V6.354c0-.606-.233-.933-.746-.886l-15.177.887c-.56.046-.747.326-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.746 0-.933-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.14c-.093-.514.28-.886.747-.933l3.222-.186zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.933.653.933 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.448-1.632z" />
|
|
447
|
+
</svg>
|
|
448
|
+
),
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
return iconMap[name] || (
|
|
452
|
+
<svg className="w-6 h-6 text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
453
|
+
<path
|
|
454
|
+
strokeLinecap="round"
|
|
455
|
+
strokeLinejoin="round"
|
|
456
|
+
strokeWidth={2}
|
|
457
|
+
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
458
|
+
/>
|
|
459
|
+
</svg>
|
|
460
|
+
);
|
|
461
|
+
}
|