Skip to content

Commit 09ff224

Browse files
authored
Merge pull request #335 from keboola/upstream/integrations-dialog
Add integrations dialog to home page
2 parents a031615 + bfbe018 commit 09ff224

File tree

6 files changed

+312
-1
lines changed

6 files changed

+312
-1
lines changed

web/app/(main)/page.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AppLayout } from "@/components/app-layout";
66
import { Input } from "@/components/ui/input";
77
import { Button } from "@/components/ui/button";
88
import { Search, Plus, Flame, Puzzle } from "lucide-react";
9+
import { IntegrationsDialog } from "@/components/integrations-dialog";
910
import { useTranslations } from "@/hooks/use-translations";
1011
import { RepositorySubmitForm } from "@/components/repo/repository-submit-form";
1112
import {
@@ -23,6 +24,7 @@ export default function Home() {
2324
const { user } = useAuth();
2425
const [activeItem, setActiveItem] = useState(t("sidebar.explore"));
2526
const [isFormOpen, setIsFormOpen] = useState(false);
27+
const [isIntegrationsOpen, setIsIntegrationsOpen] = useState(false);
2628
const [keyword, setKeyword] = useState("");
2729
const { isScrolled } = useScrollPosition(100);
2830

@@ -100,11 +102,19 @@ export default function Home() {
100102
</Button>
101103
</div>
102104
<div className="flex justify-center">
103-
<Button variant="ghost" className="gap-2 text-muted-foreground hover:text-foreground">
105+
<Button
106+
variant="ghost"
107+
className="gap-2 text-muted-foreground hover:text-foreground"
108+
onClick={() => setIsIntegrationsOpen(true)}
109+
>
104110
<Puzzle className="h-4 w-4" />
105111
{t("home.mcpIntegration")}
106112
</Button>
107113
</div>
114+
<IntegrationsDialog
115+
open={isIntegrationsOpen}
116+
onOpenChange={setIsIntegrationsOpen}
117+
/>
108118
</div>
109119
</div>
110120

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
"use client";
2+
3+
import { useState, useEffect, useCallback } from "react";
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogHeader,
8+
DialogTitle,
9+
DialogDescription,
10+
} from "@/components/ui/dialog";
11+
import { Button } from "@/components/ui/button";
12+
import { useTranslations } from "@/hooks/use-translations";
13+
import { useAuth } from "@/contexts/auth-context";
14+
import { getChatProviderConfigs } from "@/lib/admin-api";
15+
import {
16+
Copy,
17+
Check,
18+
ExternalLink,
19+
MessageSquare,
20+
Server,
21+
CheckCircle,
22+
XCircle,
23+
Loader2,
24+
} from "lucide-react";
25+
import Link from "next/link";
26+
27+
interface IntegrationsDialogProps {
28+
open: boolean;
29+
onOpenChange: (open: boolean) => void;
30+
}
31+
32+
export function IntegrationsDialog({ open, onOpenChange }: IntegrationsDialogProps) {
33+
const t = useTranslations();
34+
const { user } = useAuth();
35+
const isAdmin = user?.roles?.includes("Admin") ?? false;
36+
37+
const [slackLoading, setSlackLoading] = useState(false);
38+
const [slackConnected, setSlackConnected] = useState(false);
39+
const [slackError, setSlackError] = useState(false);
40+
const [mcpUrlCopied, setMcpUrlCopied] = useState(false);
41+
const [configCopied, setConfigCopied] = useState(false);
42+
43+
const mcpUrl = typeof window !== "undefined" ? `${window.location.origin}/api/mcp` : "/api/mcp";
44+
45+
const claudeDesktopConfig = JSON.stringify(
46+
{
47+
mcpServers: {
48+
deepwiki: {
49+
url: mcpUrl,
50+
},
51+
},
52+
},
53+
null,
54+
2
55+
);
56+
57+
useEffect(() => {
58+
if (!open) return;
59+
60+
setSlackLoading(true);
61+
setSlackError(false);
62+
setSlackConnected(false);
63+
64+
getChatProviderConfigs()
65+
.then((providers) => {
66+
const slack = providers.find((p) => p.platform === "slack");
67+
setSlackConnected(slack ? slack.isEnabled && slack.isRegistered : false);
68+
})
69+
.catch(() => {
70+
setSlackError(true);
71+
})
72+
.finally(() => {
73+
setSlackLoading(false);
74+
});
75+
}, [open]);
76+
77+
const copyToClipboard = useCallback(async (text: string, type: "url" | "config") => {
78+
try {
79+
await navigator.clipboard.writeText(text);
80+
if (type === "url") {
81+
setMcpUrlCopied(true);
82+
setTimeout(() => setMcpUrlCopied(false), 2000);
83+
} else {
84+
setConfigCopied(true);
85+
setTimeout(() => setConfigCopied(false), 2000);
86+
}
87+
} catch {
88+
// Clipboard API not available
89+
}
90+
}, []);
91+
92+
return (
93+
<Dialog open={open} onOpenChange={onOpenChange}>
94+
<DialogContent className="sm:max-w-lg max-h-[90vh] overflow-y-auto">
95+
<DialogHeader>
96+
<DialogTitle>{t("home.integrations.title")}</DialogTitle>
97+
<DialogDescription>{t("home.integrations.description")}</DialogDescription>
98+
</DialogHeader>
99+
100+
<div className="space-y-4 pt-2">
101+
{/* Slack Integration Section */}
102+
<div className="rounded-lg border p-4 space-y-3">
103+
<div className="flex items-center justify-between">
104+
<div className="flex items-center gap-2">
105+
<MessageSquare className="h-5 w-5 text-muted-foreground" />
106+
<h3 className="font-medium">{t("home.integrations.slack.title")}</h3>
107+
</div>
108+
<div className="flex items-center gap-1.5">
109+
{slackLoading ? (
110+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
111+
) : slackConnected ? (
112+
<>
113+
<CheckCircle className="h-4 w-4 text-green-500" />
114+
<span className="text-sm text-green-500">{t("home.integrations.slack.connected")}</span>
115+
</>
116+
) : (
117+
<>
118+
<XCircle className="h-4 w-4 text-muted-foreground" />
119+
<span className="text-sm text-muted-foreground">{t("home.integrations.slack.notConnected")}</span>
120+
</>
121+
)}
122+
</div>
123+
</div>
124+
<div className="text-sm text-muted-foreground">
125+
{isAdmin ? (
126+
<Link
127+
href="/admin/chat-providers"
128+
className="inline-flex items-center gap-1 text-primary hover:underline"
129+
onClick={() => onOpenChange(false)}
130+
>
131+
{t("home.integrations.slack.configure")}
132+
<ExternalLink className="h-3 w-3" />
133+
</Link>
134+
) : (
135+
<p>{t("home.integrations.slack.contactAdmin")}</p>
136+
)}
137+
</div>
138+
</div>
139+
140+
{/* MCP Server Section */}
141+
<div className="rounded-lg border p-4 space-y-3">
142+
<div className="flex items-center gap-2">
143+
<Server className="h-5 w-5 text-muted-foreground" />
144+
<h3 className="font-medium">{t("home.integrations.mcp.title")}</h3>
145+
</div>
146+
<p className="text-sm text-muted-foreground">{t("home.integrations.mcp.description")}</p>
147+
148+
{/* MCP URL */}
149+
<div className="space-y-1.5">
150+
<label className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
151+
{t("home.integrations.mcp.serverUrl")}
152+
</label>
153+
<div className="flex items-center gap-2">
154+
<code className="flex-1 rounded-md bg-muted px-3 py-2 text-sm font-mono break-all">
155+
{mcpUrl}
156+
</code>
157+
<Button
158+
variant="outline"
159+
size="sm"
160+
className="shrink-0 gap-1.5"
161+
onClick={() => copyToClipboard(mcpUrl, "url")}
162+
>
163+
{mcpUrlCopied ? (
164+
<>
165+
<Check className="h-3.5 w-3.5" />
166+
{t("home.integrations.mcp.copied")}
167+
</>
168+
) : (
169+
<>
170+
<Copy className="h-3.5 w-3.5" />
171+
{t("home.integrations.mcp.copyUrl")}
172+
</>
173+
)}
174+
</Button>
175+
</div>
176+
</div>
177+
178+
{/* Claude Desktop Config */}
179+
<div className="space-y-1.5">
180+
<p className="text-sm text-muted-foreground">
181+
{t("home.integrations.mcp.claudeDesktopHint")}
182+
</p>
183+
<div className="relative">
184+
<pre className="rounded-md bg-muted px-3 py-2 text-xs font-mono overflow-x-auto">
185+
{claudeDesktopConfig}
186+
</pre>
187+
<Button
188+
variant="outline"
189+
size="sm"
190+
className="absolute top-1.5 right-1.5 h-7 gap-1 text-xs"
191+
onClick={() => copyToClipboard(claudeDesktopConfig, "config")}
192+
>
193+
{configCopied ? (
194+
<>
195+
<Check className="h-3 w-3" />
196+
{t("home.integrations.mcp.copied")}
197+
</>
198+
) : (
199+
<>
200+
<Copy className="h-3 w-3" />
201+
{t("home.integrations.mcp.copyConfig")}
202+
</>
203+
)}
204+
</Button>
205+
</div>
206+
</div>
207+
208+
{/* Claude.ai Hint */}
209+
<p className="text-sm text-muted-foreground">
210+
{t("home.integrations.mcp.claudeAiHint")}
211+
</p>
212+
</div>
213+
</div>
214+
</DialogContent>
215+
</Dialog>
216+
);
217+
}

web/i18n/messages/en/home.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@
3737
},
3838
"exploreTrending": "Explore This Week's Trending Repos",
3939
"mcpIntegration": "Integrate KeboolaDeepWiki MCP into your dev tools 🔥",
40+
"integrations": {
41+
"title": "Integrations",
42+
"description": "Connect DeepWiki with your development tools",
43+
"slack": {
44+
"title": "Slack Integration",
45+
"connected": "Connected",
46+
"notConnected": "Not connected",
47+
"configure": "Configure Slack",
48+
"contactAdmin": "Contact your administrator to configure Slack integration."
49+
},
50+
"mcp": {
51+
"title": "MCP Server",
52+
"description": "Use DeepWiki as an MCP server in your AI tools.",
53+
"serverUrl": "Server URL",
54+
"copied": "Copied!",
55+
"copyUrl": "Copy URL",
56+
"copyConfig": "Copy config",
57+
"claudeDesktopHint": "Add this to your Claude Desktop configuration file (claude_desktop_config.json):",
58+
"claudeAiHint": "In Claude.ai settings, add the MCP server URL above as a remote MCP server."
59+
}
60+
},
4061
"private": {
4162
"visibility": {
4263
"public": "Public",

web/i18n/messages/ja/home.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@
3737
},
3838
"exploreTrending": "今週のトレンドリポジトリを探索",
3939
"mcpIntegration": "KeboolaDeepWiki MCPを開発ツールに統合 🔥",
40+
"integrations": {
41+
"title": "統合",
42+
"description": "DeepWikiを開発ツールと接続",
43+
"slack": {
44+
"title": "Slack連携",
45+
"connected": "接続済み",
46+
"notConnected": "未接続",
47+
"configure": "Slackを設定",
48+
"contactAdmin": "Slack連携の設定については管理者にお問い合わせください。"
49+
},
50+
"mcp": {
51+
"title": "MCPサーバー",
52+
"description": "AIツールでDeepWikiをMCPサーバーとして使用できます。",
53+
"serverUrl": "サーバーURL",
54+
"copied": "コピーしました!",
55+
"copyUrl": "URLをコピー",
56+
"copyConfig": "設定をコピー",
57+
"claudeDesktopHint": "Claude Desktopの設定ファイル (claude_desktop_config.json) に以下を追加してください:",
58+
"claudeAiHint": "Claude.aiの設定で、上記のMCPサーバーURLをリモートMCPサーバーとして追加してください。"
59+
}
60+
},
4061
"private": {
4162
"visibility": {
4263
"public": "公開",

web/i18n/messages/ko/home.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@
3737
},
3838
"exploreTrending": "이번 주 인기 저장소 탐색",
3939
"mcpIntegration": "개발 도구에 KeboolaDeepWiki MCP 통합 🔥",
40+
"integrations": {
41+
"title": "통합",
42+
"description": "DeepWiki를 개발 도구와 연결",
43+
"slack": {
44+
"title": "Slack 통합",
45+
"connected": "연결됨",
46+
"notConnected": "연결되지 않음",
47+
"configure": "Slack 구성",
48+
"contactAdmin": "Slack 통합을 구성하려면 관리자에게 문의하세요."
49+
},
50+
"mcp": {
51+
"title": "MCP 서버",
52+
"description": "AI 도구에서 DeepWiki를 MCP 서버로 사용하세요.",
53+
"serverUrl": "서버 URL",
54+
"copied": "복사됨!",
55+
"copyUrl": "URL 복사",
56+
"copyConfig": "설정 복사",
57+
"claudeDesktopHint": "Claude Desktop 설정 파일 (claude_desktop_config.json)에 다음을 추가하세요:",
58+
"claudeAiHint": "Claude.ai 설정에서 위의 MCP 서버 URL을 원격 MCP 서버로 추가하세요."
59+
}
60+
},
4061
"private": {
4162
"visibility": {
4263
"public": "공개",

web/i18n/messages/zh/home.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@
3737
},
3838
"exploreTrending": "探索本周的 热门仓库",
3939
"mcpIntegration": "把 KeboolaDeepWiki MCP 接入你的开发工具 🔥",
40+
"integrations": {
41+
"title": "集成",
42+
"description": "将 DeepWiki 与您的开发工具连接",
43+
"slack": {
44+
"title": "Slack 集成",
45+
"connected": "已连接",
46+
"notConnected": "未连接",
47+
"configure": "配置 Slack",
48+
"contactAdmin": "请联系管理员配置 Slack 集成。"
49+
},
50+
"mcp": {
51+
"title": "MCP 服务器",
52+
"description": "在您的 AI 工具中将 DeepWiki 用作 MCP 服务器。",
53+
"serverUrl": "服务器地址",
54+
"copied": "已复制!",
55+
"copyUrl": "复制地址",
56+
"copyConfig": "复制配置",
57+
"claudeDesktopHint": "将以下内容添加到您的 Claude Desktop 配置文件 (claude_desktop_config.json):",
58+
"claudeAiHint": "在 Claude.ai 设置中,将上方的 MCP 服务器地址添加为远程 MCP 服务器。"
59+
}
60+
},
4061
"private": {
4162
"visibility": {
4263
"public": "公开",

0 commit comments

Comments
 (0)