mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-02-12 15:52:47 +00:00
chore(messages): update MCP server configurations in multiple languages
This commit is contained in:
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "فشل في إرسال البلاغ",
|
"reportFailed": "فشل في إرسال البلاغ",
|
||||||
"reasonRequired": "يرجى اختيار سبب",
|
"reasonRequired": "يرجى اختيار سبب",
|
||||||
"alreadyReported": "لقد أبلغت عن هذا الأمر بالفعل"
|
"alreadyReported": "لقد أبلغت عن هذا الأمر بالفعل"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "خادم MCP",
|
||||||
|
"title": "إعدادات خادم MCP",
|
||||||
|
"copy": "نسخ",
|
||||||
|
"copied": "تم النسخ!",
|
||||||
|
"customizeFilters": "تخصيص الفلاتر لتضييق نطاق الأوامر:",
|
||||||
|
"users": "المستخدمون",
|
||||||
|
"userPlaceholder": "إضافة اسم مستخدم...",
|
||||||
|
"categories": "الفئات",
|
||||||
|
"categoryPlaceholder": "إضافة رمز الفئة...",
|
||||||
|
"tags": "العلامات",
|
||||||
|
"tagPlaceholder": "إضافة رمز العلامة..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Meldung konnte nicht gesendet werden",
|
"reportFailed": "Meldung konnte nicht gesendet werden",
|
||||||
"reasonRequired": "Bitte wählen Sie einen Grund",
|
"reasonRequired": "Bitte wählen Sie einen Grund",
|
||||||
"alreadyReported": "Sie haben diesen Prompt bereits gemeldet"
|
"alreadyReported": "Sie haben diesen Prompt bereits gemeldet"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "MCP-Server",
|
||||||
|
"title": "MCP-Server-Konfiguration",
|
||||||
|
"copy": "Kopieren",
|
||||||
|
"copied": "Kopiert!",
|
||||||
|
"customizeFilters": "Filter anpassen, um Prompts einzugrenzen:",
|
||||||
|
"users": "Benutzer",
|
||||||
|
"userPlaceholder": "Benutzername hinzufügen...",
|
||||||
|
"categories": "Kategorien",
|
||||||
|
"categoryPlaceholder": "Kategorie-Slug hinzufügen...",
|
||||||
|
"tags": "Tags",
|
||||||
|
"tagPlaceholder": "Tag-Slug hinzufügen..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Failed to submit report",
|
"reportFailed": "Failed to submit report",
|
||||||
"reasonRequired": "Please select a reason",
|
"reasonRequired": "Please select a reason",
|
||||||
"alreadyReported": "You have already reported this prompt"
|
"alreadyReported": "You have already reported this prompt"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "MCP Server",
|
||||||
|
"title": "MCP Server Configuration",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied!",
|
||||||
|
"customizeFilters": "Customize filters to narrow down prompts:",
|
||||||
|
"users": "Users",
|
||||||
|
"userPlaceholder": "Add username...",
|
||||||
|
"categories": "Categories",
|
||||||
|
"categoryPlaceholder": "Add category slug...",
|
||||||
|
"tags": "Tags",
|
||||||
|
"tagPlaceholder": "Add tag slug..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Error al enviar el reporte",
|
"reportFailed": "Error al enviar el reporte",
|
||||||
"reasonRequired": "Por favor selecciona un motivo",
|
"reasonRequired": "Por favor selecciona un motivo",
|
||||||
"alreadyReported": "Ya has reportado este prompt"
|
"alreadyReported": "Ya has reportado este prompt"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "Servidor MCP",
|
||||||
|
"title": "Configuración del Servidor MCP",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"copied": "¡Copiado!",
|
||||||
|
"customizeFilters": "Personaliza los filtros para reducir los prompts:",
|
||||||
|
"users": "Usuarios",
|
||||||
|
"userPlaceholder": "Añadir nombre de usuario...",
|
||||||
|
"categories": "Categorías",
|
||||||
|
"categoryPlaceholder": "Añadir slug de categoría...",
|
||||||
|
"tags": "Etiquetas",
|
||||||
|
"tagPlaceholder": "Añadir slug de etiqueta..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Échec de l'envoi du signalement",
|
"reportFailed": "Échec de l'envoi du signalement",
|
||||||
"reasonRequired": "Veuillez sélectionner une raison",
|
"reasonRequired": "Veuillez sélectionner une raison",
|
||||||
"alreadyReported": "Vous avez déjà signalé ce prompt"
|
"alreadyReported": "Vous avez déjà signalé ce prompt"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "Serveur MCP",
|
||||||
|
"title": "Configuration du Serveur MCP",
|
||||||
|
"copy": "Copier",
|
||||||
|
"copied": "Copié !",
|
||||||
|
"customizeFilters": "Personnalisez les filtres pour affiner les prompts :",
|
||||||
|
"users": "Utilisateurs",
|
||||||
|
"userPlaceholder": "Ajouter un nom d'utilisateur...",
|
||||||
|
"categories": "Catégories",
|
||||||
|
"categoryPlaceholder": "Ajouter un slug de catégorie...",
|
||||||
|
"tags": "Tags",
|
||||||
|
"tagPlaceholder": "Ajouter un slug de tag..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Impossibile inviare la segnalazione",
|
"reportFailed": "Impossibile inviare la segnalazione",
|
||||||
"reasonRequired": "Seleziona un motivo",
|
"reasonRequired": "Seleziona un motivo",
|
||||||
"alreadyReported": "Hai già segnalato questo prompt"
|
"alreadyReported": "Hai già segnalato questo prompt"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "Server MCP",
|
||||||
|
"title": "Configurazione Server MCP",
|
||||||
|
"copy": "Copia",
|
||||||
|
"copied": "Copiato!",
|
||||||
|
"customizeFilters": "Personalizza i filtri per restringere i prompt:",
|
||||||
|
"users": "Utenti",
|
||||||
|
"userPlaceholder": "Aggiungi nome utente...",
|
||||||
|
"categories": "Categorie",
|
||||||
|
"categoryPlaceholder": "Aggiungi slug categoria...",
|
||||||
|
"tags": "Tag",
|
||||||
|
"tagPlaceholder": "Aggiungi slug tag..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "報告の送信に失敗しました",
|
"reportFailed": "報告の送信に失敗しました",
|
||||||
"reasonRequired": "理由を選択してください",
|
"reasonRequired": "理由を選択してください",
|
||||||
"alreadyReported": "このプロンプトは既に報告済みです"
|
"alreadyReported": "このプロンプトは既に報告済みです"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "MCPサーバー",
|
||||||
|
"title": "MCPサーバー設定",
|
||||||
|
"copy": "コピー",
|
||||||
|
"copied": "コピーしました!",
|
||||||
|
"customizeFilters": "フィルターをカスタマイズしてプロンプトを絞り込む:",
|
||||||
|
"users": "ユーザー",
|
||||||
|
"userPlaceholder": "ユーザー名を追加...",
|
||||||
|
"categories": "カテゴリー",
|
||||||
|
"categoryPlaceholder": "カテゴリースラッグを追加...",
|
||||||
|
"tags": "タグ",
|
||||||
|
"tagPlaceholder": "タグスラッグを追加..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "신고 제출에 실패했습니다",
|
"reportFailed": "신고 제출에 실패했습니다",
|
||||||
"reasonRequired": "사유를 선택하세요",
|
"reasonRequired": "사유를 선택하세요",
|
||||||
"alreadyReported": "이미 이 프롬프트를 신고하셨습니다"
|
"alreadyReported": "이미 이 프롬프트를 신고하셨습니다"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "MCP 서버",
|
||||||
|
"title": "MCP 서버 설정",
|
||||||
|
"copy": "복사",
|
||||||
|
"copied": "복사됨!",
|
||||||
|
"customizeFilters": "필터를 사용자 정의하여 프롬프트 범위를 좁히세요:",
|
||||||
|
"users": "사용자",
|
||||||
|
"userPlaceholder": "사용자 이름 추가...",
|
||||||
|
"categories": "카테고리",
|
||||||
|
"categoryPlaceholder": "카테고리 슬러그 추가...",
|
||||||
|
"tags": "태그",
|
||||||
|
"tagPlaceholder": "태그 슬러그 추가..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Falha ao enviar denúncia",
|
"reportFailed": "Falha ao enviar denúncia",
|
||||||
"reasonRequired": "Por favor, selecione um motivo",
|
"reasonRequired": "Por favor, selecione um motivo",
|
||||||
"alreadyReported": "Você já denunciou este prompt"
|
"alreadyReported": "Você já denunciou este prompt"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "Servidor MCP",
|
||||||
|
"title": "Configuração do Servidor MCP",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"copied": "Copiado!",
|
||||||
|
"customizeFilters": "Personalize os filtros para restringir os prompts:",
|
||||||
|
"users": "Usuários",
|
||||||
|
"userPlaceholder": "Adicionar nome de usuário...",
|
||||||
|
"categories": "Categorias",
|
||||||
|
"categoryPlaceholder": "Adicionar slug da categoria...",
|
||||||
|
"tags": "Tags",
|
||||||
|
"tagPlaceholder": "Adicionar slug da tag..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "Şikayet gönderilemedi",
|
"reportFailed": "Şikayet gönderilemedi",
|
||||||
"reasonRequired": "Lütfen bir sebep seçin",
|
"reasonRequired": "Lütfen bir sebep seçin",
|
||||||
"alreadyReported": "Bu promptu zaten şikayet ettiniz"
|
"alreadyReported": "Bu promptu zaten şikayet ettiniz"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "MCP Sunucusu",
|
||||||
|
"title": "MCP Sunucu Yapılandırması",
|
||||||
|
"copy": "Kopyala",
|
||||||
|
"copied": "Kopyalandı!",
|
||||||
|
"customizeFilters": "Promptları daraltmak için filtreleri özelleştirin:",
|
||||||
|
"users": "Kullanıcılar",
|
||||||
|
"userPlaceholder": "Kullanıcı adı ekle...",
|
||||||
|
"categories": "Kategoriler",
|
||||||
|
"categoryPlaceholder": "Kategori slug ekle...",
|
||||||
|
"tags": "Etiketler",
|
||||||
|
"tagPlaceholder": "Etiket slug ekle..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -717,5 +717,18 @@
|
|||||||
"reportFailed": "举报提交失败",
|
"reportFailed": "举报提交失败",
|
||||||
"reasonRequired": "请选择举报原因",
|
"reasonRequired": "请选择举报原因",
|
||||||
"alreadyReported": "您已经举报过此提示词"
|
"alreadyReported": "您已经举报过此提示词"
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"button": "MCP 服务器",
|
||||||
|
"title": "MCP 服务器配置",
|
||||||
|
"copy": "复制",
|
||||||
|
"copied": "已复制!",
|
||||||
|
"customizeFilters": "自定义过滤器以缩小提示词范围:",
|
||||||
|
"users": "用户",
|
||||||
|
"userPlaceholder": "添加用户名...",
|
||||||
|
"categories": "分类",
|
||||||
|
"categoryPlaceholder": "添加分类标识...",
|
||||||
|
"tags": "标签",
|
||||||
|
"tagPlaceholder": "添加标签标识..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
public/mcp.svg
Normal file
6
public/mcp.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200" fill="none">
|
||||||
|
<path d="M25 97.8528L92.8823 29.9706C102.255 20.598 117.451 20.598 126.823 29.9706V29.9706C136.196 39.3431 136.196 54.5391 126.823 63.9117L75.5581 115.177" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
||||||
|
<path d="M76.2653 114.47L126.823 63.9117C136.196 54.5391 151.392 54.5391 160.765 63.9117L161.118 64.2652C170.491 73.6378 170.491 88.8338 161.118 98.2063L99.7248 159.6C96.6006 162.724 96.6006 167.789 99.7248 170.913L112.331 183.52" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
||||||
|
<path d="M109.853 46.9411L59.6482 97.1457C50.2757 106.518 50.2757 121.714 59.6482 131.087V131.087C69.0208 140.459 84.2168 140.459 93.5894 131.087L143.794 80.8822" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 869 B |
@@ -13,6 +13,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { PromptList } from "@/components/prompts/prompt-list";
|
import { PromptList } from "@/components/prompts/prompt-list";
|
||||||
import { PromptCard, type PromptCardProps } from "@/components/prompts/prompt-card";
|
import { PromptCard, type PromptCardProps } from "@/components/prompts/prompt-card";
|
||||||
|
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
|
||||||
|
|
||||||
interface UserProfilePageProps {
|
interface UserProfilePageProps {
|
||||||
params: Promise<{ username: string }>;
|
params: Promise<{ username: string }>;
|
||||||
@@ -320,15 +321,18 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Edit button - desktop only */}
|
{/* Actions - desktop only */}
|
||||||
{isOwner && (
|
<div className="hidden md:flex items-center gap-2 shrink-0">
|
||||||
<Button variant="outline" size="sm" asChild className="hidden md:inline-flex shrink-0">
|
<McpServerPopup initialUsers={[user.username]} />
|
||||||
<Link href="/settings">
|
{isOwner && (
|
||||||
<Settings className="h-4 w-4 mr-1.5" />
|
<Button variant="outline" size="sm" asChild>
|
||||||
{t("editProfile")}
|
<Link href="/settings">
|
||||||
</Link>
|
<Settings className="h-4 w-4 mr-1.5" />
|
||||||
</Button>
|
{t("editProfile")}
|
||||||
)}
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats - stacked on mobile, inline on desktop */}
|
{/* Stats - stacked on mobile, inline on desktop */}
|
||||||
@@ -354,17 +358,18 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Edit button - below stats on mobile */}
|
{/* Actions - mobile only */}
|
||||||
{isOwner && (
|
<div className="md:hidden flex gap-2">
|
||||||
<div className="md:hidden">
|
<McpServerPopup initialUsers={[user.username]} />
|
||||||
<Button variant="outline" size="sm" asChild className="w-full">
|
{isOwner && (
|
||||||
|
<Button variant="outline" size="sm" asChild className="flex-1">
|
||||||
<Link href="/settings">
|
<Link href="/settings">
|
||||||
<Settings className="h-4 w-4 mr-1.5" />
|
<Settings className="h-4 w-4 mr-1.5" />
|
||||||
{t("editProfile")}
|
{t("editProfile")}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs for Prompts and Change Requests */}
|
{/* Tabs for Prompts and Change Requests */}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { db } from "@/lib/db";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { PromptList } from "@/components/prompts/prompt-list";
|
import { PromptList } from "@/components/prompts/prompt-list";
|
||||||
import { SubscribeButton } from "@/components/categories/subscribe-button";
|
import { SubscribeButton } from "@/components/categories/subscribe-button";
|
||||||
|
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
|
||||||
|
|
||||||
interface CategoryPageProps {
|
interface CategoryPageProps {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ slug: string }>;
|
||||||
@@ -114,28 +115,31 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div>
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div>
|
||||||
<h1 className="text-xl font-semibold">{category.name}</h1>
|
<div className="flex items-center gap-2">
|
||||||
{session?.user && (
|
<h1 className="text-xl font-semibold">{category.name}</h1>
|
||||||
<SubscribeButton
|
{session?.user && (
|
||||||
categoryId={category.id}
|
<SubscribeButton
|
||||||
categoryName={category.name}
|
categoryId={category.id}
|
||||||
initialSubscribed={!!isSubscribed}
|
categoryName={category.name}
|
||||||
pill
|
initialSubscribed={!!isSubscribed}
|
||||||
/>
|
pill
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{category.description && (
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
{category.description}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
<div className="flex items-center gap-3 mt-2 text-sm text-muted-foreground">
|
||||||
|
<span>{category._count.prompts} prompts</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{category._count.subscribers} subscribers</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{category.description && (
|
<McpServerPopup initialCategories={[slug]} />
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
{category.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-3 mt-2 text-sm text-muted-foreground">
|
|
||||||
<span>{category._count.prompts} prompts</span>
|
|
||||||
<span>•</span>
|
|
||||||
<span>{category._count.subscribers} subscribers</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { db } from "@/lib/db";
|
|||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { PromptCard } from "@/components/prompts/prompt-card";
|
import { PromptCard } from "@/components/prompts/prompt-card";
|
||||||
|
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
|
||||||
|
|
||||||
interface TagPageProps {
|
interface TagPageProps {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ slug: string }>;
|
||||||
@@ -112,15 +113,18 @@ export default async function TagPage({ params, searchParams }: TagPageProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div
|
<div className="flex items-center gap-3">
|
||||||
className="w-4 h-4 rounded-full"
|
<div
|
||||||
style={{ backgroundColor: tag.color }}
|
className="w-4 h-4 rounded-full"
|
||||||
/>
|
style={{ backgroundColor: tag.color }}
|
||||||
<h1 className="text-xl font-semibold">{tag.name}</h1>
|
/>
|
||||||
<span className="text-sm text-muted-foreground">
|
<h1 className="text-xl font-semibold">{tag.name}</h1>
|
||||||
{total} {t("prompts")}
|
<span className="text-sm text-muted-foreground">
|
||||||
</span>
|
{total} {t("prompts")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<McpServerPopup initialTags={[slug]} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
258
src/components/mcp/mcp-server-popup.tsx
Normal file
258
src/components/mcp/mcp-server-popup.tsx
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Check, Copy, Plus, X } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
|
// MCP Logo component
|
||||||
|
function McpIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src="/mcp.svg"
|
||||||
|
alt="MCP"
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { McpIcon };
|
||||||
|
|
||||||
|
interface McpServerPopupProps {
|
||||||
|
/** Pre-filled users (usernames) */
|
||||||
|
initialUsers?: string[];
|
||||||
|
/** Pre-filled categories (slugs) */
|
||||||
|
initialCategories?: string[];
|
||||||
|
/** Pre-filled tags (slugs) */
|
||||||
|
initialTags?: string[];
|
||||||
|
/** Base URL override */
|
||||||
|
baseUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function McpServerPopup({
|
||||||
|
initialUsers = [],
|
||||||
|
initialCategories = [],
|
||||||
|
initialTags = [],
|
||||||
|
baseUrl,
|
||||||
|
}: McpServerPopupProps) {
|
||||||
|
const t = useTranslations("mcp");
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [users, setUsers] = useState<string[]>(initialUsers);
|
||||||
|
const [categories, setCategories] = useState<string[]>(initialCategories);
|
||||||
|
const [tags, setTags] = useState<string[]>(initialTags);
|
||||||
|
const [userInput, setUserInput] = useState("");
|
||||||
|
const [categoryInput, setCategoryInput] = useState("");
|
||||||
|
const [tagInput, setTagInput] = useState("");
|
||||||
|
|
||||||
|
// Build the MCP URL
|
||||||
|
const mcpUrl = useMemo(() => {
|
||||||
|
const base = baseUrl || (typeof window !== "undefined" ? window.location.origin : "https://prompts.chat");
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (users.length > 0) {
|
||||||
|
params.set("users", users.join(","));
|
||||||
|
}
|
||||||
|
if (categories.length > 0) {
|
||||||
|
params.set("categories", categories.join(","));
|
||||||
|
}
|
||||||
|
if (tags.length > 0) {
|
||||||
|
params.set("tags", tags.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = params.toString();
|
||||||
|
return `${base}/api/mcp${queryString ? `?${queryString}` : ""}`;
|
||||||
|
}, [baseUrl, users, categories, tags]);
|
||||||
|
|
||||||
|
// Generate the JSON config
|
||||||
|
const configJson = useMemo(() => {
|
||||||
|
return JSON.stringify(
|
||||||
|
{
|
||||||
|
mcpServers: {
|
||||||
|
"prompts-chat": {
|
||||||
|
url: mcpUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}, [mcpUrl]);
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
await navigator.clipboard.writeText(configJson);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUser = () => {
|
||||||
|
const value = userInput.trim().replace(/^@/, "");
|
||||||
|
if (value && !users.includes(value)) {
|
||||||
|
setUsers([...users, value]);
|
||||||
|
setUserInput("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCategory = () => {
|
||||||
|
const value = categoryInput.trim().toLowerCase();
|
||||||
|
if (value && !categories.includes(value)) {
|
||||||
|
setCategories([...categories, value]);
|
||||||
|
setCategoryInput("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTag = () => {
|
||||||
|
const value = tagInput.trim().toLowerCase();
|
||||||
|
if (value && !tags.includes(value)) {
|
||||||
|
setTags([...tags, value]);
|
||||||
|
setTagInput("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeUser = (user: string) => setUsers(users.filter((u) => u !== user));
|
||||||
|
const removeCategory = (cat: string) => setCategories(categories.filter((c) => c !== cat));
|
||||||
|
const removeTag = (tag: string) => setTags(tags.filter((t) => t !== tag));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover modal>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm" className="h-8 gap-1.5">
|
||||||
|
<McpIcon className="h-4 w-4" />
|
||||||
|
<span className="hidden sm:inline">{t("button")}</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="end" className="w-[calc(100vw-2rem)] sm:w-[420px] p-4" sideOffset={8} collisionPadding={16}>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-semibold text-sm flex items-center gap-2">
|
||||||
|
<McpIcon className="h-4 w-4" />
|
||||||
|
{t("title")}
|
||||||
|
</h3>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 text-xs gap-1"
|
||||||
|
onClick={handleCopy}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<>
|
||||||
|
<Check className="h-3 w-3" />
|
||||||
|
{t("copied")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
{t("copy")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Config JSON */}
|
||||||
|
<div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
|
||||||
|
<pre className="whitespace-nowrap">{configJson}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filters */}
|
||||||
|
<div className="space-y-3 border-t pt-3">
|
||||||
|
<p className="text-xs text-muted-foreground">{t("customizeFilters")}</p>
|
||||||
|
|
||||||
|
{/* Users */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">{t("users")}</label>
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<Input
|
||||||
|
value={userInput}
|
||||||
|
onChange={(e) => setUserInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === "Enter" && addUser()}
|
||||||
|
placeholder={t("userPlaceholder")}
|
||||||
|
className="h-7 text-xs flex-1"
|
||||||
|
/>
|
||||||
|
<Button size="sm" variant="outline" className="h-7 px-2" onClick={addUser}>
|
||||||
|
<Plus className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{users.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{users.map((user) => (
|
||||||
|
<Badge key={user} variant="secondary" className="text-xs gap-1 pr-1">
|
||||||
|
@{user}
|
||||||
|
<button onClick={() => removeUser(user)} className="hover:text-destructive">
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Categories */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">{t("categories")}</label>
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<Input
|
||||||
|
value={categoryInput}
|
||||||
|
onChange={(e) => setCategoryInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === "Enter" && addCategory()}
|
||||||
|
placeholder={t("categoryPlaceholder")}
|
||||||
|
className="h-7 text-xs flex-1"
|
||||||
|
/>
|
||||||
|
<Button size="sm" variant="outline" className="h-7 px-2" onClick={addCategory}>
|
||||||
|
<Plus className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{categories.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<Badge key={cat} variant="secondary" className="text-xs gap-1 pr-1">
|
||||||
|
{cat}
|
||||||
|
<button onClick={() => removeCategory(cat)} className="hover:text-destructive">
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tags */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium">{t("tags")}</label>
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<Input
|
||||||
|
value={tagInput}
|
||||||
|
onChange={(e) => setTagInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === "Enter" && addTag()}
|
||||||
|
placeholder={t("tagPlaceholder")}
|
||||||
|
className="h-7 text-xs flex-1"
|
||||||
|
/>
|
||||||
|
<Button size="sm" variant="outline" className="h-7 px-2" onClick={addTag}>
|
||||||
|
<Plus className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<Badge key={tag} variant="secondary" className="text-xs gap-1 pr-1">
|
||||||
|
{tag}
|
||||||
|
<button onClick={() => removeTag(tag)} className="hover:text-destructive">
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user