feat(app): add MCP feature to user profile page
This commit is contained in:
@@ -62,6 +62,8 @@ export default defineConfig({
|
|||||||
aiSearch: true,
|
aiSearch: true,
|
||||||
// Enable AI-powered generation features (requires OPENAI_API_KEY)
|
// Enable AI-powered generation features (requires OPENAI_API_KEY)
|
||||||
aiGeneration: true,
|
aiGeneration: true,
|
||||||
|
// Enable MCP (Model Context Protocol) features including API key generation
|
||||||
|
mcp: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Homepage customization
|
// Homepage customization
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="28" height="28" rx="4" fill="black" style="fill:black;fill-opacity:1;"/>
|
||||||
|
<path d="M10.5724 15.2565C10.5724 17.5025 9.6613 19.3778 8.17805 21.1047H11.6319L11.6319 22.7786H6.33459V21.1895C7.95557 19.3566 8.58065 17.8628 8.58065 15.2565L10.5724 15.2565Z" fill="white" style="fill:white;fill-opacity:1;"/>
|
||||||
|
<path d="M17.4276 15.2565C17.4276 17.5025 18.3387 19.3778 19.822 21.1047H16.3681V22.7786H21.6654V21.1895C20.0444 19.3566 19.4194 17.8628 19.4194 15.2565H17.4276Z" fill="white" style="fill:white;fill-opacity:1;"/>
|
||||||
|
<path d="M10.5724 12.7435C10.5724 10.4975 9.66131 8.62224 8.17807 6.89532L11.6319 6.89532V5.22137L6.33461 5.22137V6.81056C7.95558 8.64343 8.58066 10.1373 8.58066 12.7435L10.5724 12.7435Z" fill="white" style="fill:white;fill-opacity:1;"/>
|
||||||
|
<path d="M17.4276 12.7435C17.4276 10.4975 18.3387 8.62224 19.822 6.89532L16.3681 6.89532L16.3681 5.22138L21.6654 5.22138V6.81056C20.0445 8.64343 19.4194 10.1373 19.4194 12.7435H17.4276Z" fill="white" style="fill:white;fill-opacity:1;"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -7,6 +7,7 @@ import { getPromptUrl } from "@/lib/urls";
|
|||||||
import { Calendar, ArrowBigUp, FileText, Settings, GitPullRequest, Clock, Check, X, Pin, BadgeCheck, Users, ShieldCheck } from "lucide-react";
|
import { Calendar, ArrowBigUp, FileText, Settings, GitPullRequest, Clock, Check, X, Pin, BadgeCheck, Users, ShieldCheck } from "lucide-react";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
|
import config from "@/../prompts.config";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -326,7 +327,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
|||||||
</div>
|
</div>
|
||||||
{/* Actions - desktop only */}
|
{/* Actions - desktop only */}
|
||||||
<div className="hidden md:flex items-center gap-2 shrink-0">
|
<div className="hidden md:flex items-center gap-2 shrink-0">
|
||||||
<McpServerPopup initialUsers={[user.username]} />
|
{config.features.mcp !== false && <McpServerPopup initialUsers={[user.username]} />}
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<Button variant="outline" size="sm" asChild>
|
<Button variant="outline" size="sm" asChild>
|
||||||
<Link href="/settings">
|
<Link href="/settings">
|
||||||
@@ -363,7 +364,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
|
|||||||
|
|
||||||
{/* Actions - mobile only */}
|
{/* Actions - mobile only */}
|
||||||
<div className="md:hidden flex gap-2">
|
<div className="md:hidden flex gap-2">
|
||||||
<McpServerPopup initialUsers={[user.username]} />
|
{config.features.mcp !== false && <McpServerPopup initialUsers={[user.username]} />}
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<Button variant="outline" size="sm" asChild className="flex-1">
|
<Button variant="outline" size="sm" asChild className="flex-1">
|
||||||
<Link href="/settings">
|
<Link href="/settings">
|
||||||
|
|||||||
@@ -0,0 +1,260 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Download, Copy, Check } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useBranding } from "@/components/providers/branding-provider";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
interface AssetCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
bgClass: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
downloadUrl: string;
|
||||||
|
filename: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AssetCard({ title, description, bgClass, children, downloadUrl, filename }: AssetCardProps) {
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(downloadUrl);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Download failed:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border rounded-lg overflow-hidden">
|
||||||
|
<div className={`${bgClass} h-40 flex items-center justify-center p-8`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<div className="p-4 flex items-center justify-between bg-background">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-sm">{title}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">{description}</p>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm" onClick={handleDownload}>
|
||||||
|
<Download className="h-3 w-3 mr-1" />
|
||||||
|
SVG
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColorCardProps {
|
||||||
|
color: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ColorCard({ color, name, description }: ColorCardProps) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
await navigator.clipboard.writeText(color);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleCopy}
|
||||||
|
className="flex items-center justify-between p-3 rounded-md transition-colors hover:opacity-80"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
>
|
||||||
|
<div className="text-left">
|
||||||
|
<p className="font-mono text-sm font-medium" style={{ color: isLight(color) ? "#000" : "#fff" }}>
|
||||||
|
{color}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs opacity-80" style={{ color: isLight(color) ? "#000" : "#fff" }}>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-4 w-4" style={{ color: isLight(color) ? "#000" : "#fff" }} />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4 opacity-60" style={{ color: isLight(color) ? "#000" : "#fff" }} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLight(color: string): boolean {
|
||||||
|
const hex = color.replace("#", "");
|
||||||
|
const r = parseInt(hex.substr(0, 2), 16);
|
||||||
|
const g = parseInt(hex.substr(2, 2), 16);
|
||||||
|
const b = parseInt(hex.substr(4, 2), 16);
|
||||||
|
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||||
|
return brightness > 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BrandAssetsPage() {
|
||||||
|
const branding = useBranding();
|
||||||
|
|
||||||
|
// Redirect if using clone branding
|
||||||
|
if (branding.useCloneBranding) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container max-w-4xl py-10">
|
||||||
|
<h1 className="text-3xl font-bold mb-2">Brand Assets</h1>
|
||||||
|
<p className="text-muted-foreground mb-10">
|
||||||
|
Official logos, colors, and brand guidelines for {branding.name}. Free to use for press, partnerships, and community projects.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-10">
|
||||||
|
{/* Logos Section */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Logos</h2>
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
{/* Logo for light backgrounds */}
|
||||||
|
<AssetCard
|
||||||
|
title="Logo"
|
||||||
|
description="For light backgrounds"
|
||||||
|
bgClass="bg-gray-100"
|
||||||
|
downloadUrl="/logo.svg"
|
||||||
|
filename="prompts-chat-logo.svg"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/logo.svg"
|
||||||
|
alt="prompts.chat logo"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="h-20 w-auto"
|
||||||
|
/>
|
||||||
|
</AssetCard>
|
||||||
|
|
||||||
|
{/* Logo for dark backgrounds */}
|
||||||
|
<AssetCard
|
||||||
|
title="Logo"
|
||||||
|
description="For dark backgrounds"
|
||||||
|
bgClass="bg-gray-900"
|
||||||
|
downloadUrl="/logo-dark.svg"
|
||||||
|
filename="prompts-chat-logo-dark.svg"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/logo-dark.svg"
|
||||||
|
alt="prompts.chat logo dark"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="h-20 w-auto"
|
||||||
|
/>
|
||||||
|
</AssetCard>
|
||||||
|
|
||||||
|
{/* Logo with text - light */}
|
||||||
|
<AssetCard
|
||||||
|
title="Logo with Name"
|
||||||
|
description="For light backgrounds"
|
||||||
|
bgClass="bg-gray-100"
|
||||||
|
downloadUrl="/logo.svg"
|
||||||
|
filename="prompts-chat-logo.svg"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Image
|
||||||
|
src="/logo.svg"
|
||||||
|
alt="prompts.chat logo"
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
className="h-12 w-auto"
|
||||||
|
/>
|
||||||
|
<span className="text-2xl font-bold text-gray-900">{branding.name}</span>
|
||||||
|
</div>
|
||||||
|
</AssetCard>
|
||||||
|
|
||||||
|
{/* Logo with text - dark */}
|
||||||
|
<AssetCard
|
||||||
|
title="Logo with Name"
|
||||||
|
description="For dark backgrounds"
|
||||||
|
bgClass="bg-gray-900"
|
||||||
|
downloadUrl="/logo-dark.svg"
|
||||||
|
filename="prompts-chat-logo-dark.svg"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Image
|
||||||
|
src="/logo-dark.svg"
|
||||||
|
alt="prompts.chat logo dark"
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
className="h-12 w-auto"
|
||||||
|
/>
|
||||||
|
<span className="text-2xl font-bold text-white">{branding.name}</span>
|
||||||
|
</div>
|
||||||
|
</AssetCard>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Colors Section */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Brand Colors</h2>
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">Click to copy hex value</p>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<ColorCard
|
||||||
|
color="#000000"
|
||||||
|
name="Primary"
|
||||||
|
description="Primary brand color"
|
||||||
|
/>
|
||||||
|
<ColorCard
|
||||||
|
color="#ffffff"
|
||||||
|
name="Background"
|
||||||
|
description="Light background"
|
||||||
|
/>
|
||||||
|
<ColorCard
|
||||||
|
color="#6366f1"
|
||||||
|
name="Accent"
|
||||||
|
description="Indigo accent color"
|
||||||
|
/>
|
||||||
|
<ColorCard
|
||||||
|
color="#71717a"
|
||||||
|
name="Muted"
|
||||||
|
description="Muted text color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Usage Guidelines */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Usage Guidelines</h2>
|
||||||
|
<div className="prose prose-neutral dark:prose-invert max-w-none">
|
||||||
|
<ul className="list-disc list-inside text-muted-foreground space-y-2">
|
||||||
|
<li>Do not stretch, distort, or rotate the logo</li>
|
||||||
|
<li>Maintain adequate spacing around the logo</li>
|
||||||
|
<li>Use the dark logo on light backgrounds and vice versa</li>
|
||||||
|
<li>Do not add effects like shadows or gradients to the logo</li>
|
||||||
|
<li>The logo should be clearly visible against the background</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* License */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-semibold mb-4">License</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
The {branding.name} brand assets are provided under{" "}
|
||||||
|
<a
|
||||||
|
href="https://creativecommons.org/publicdomain/zero/1.0/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
CC0 1.0 Universal
|
||||||
|
</a>
|
||||||
|
. You are free to use these assets for any purpose without attribution.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
|
import config from "@/../prompts.config";
|
||||||
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";
|
||||||
@@ -139,7 +140,7 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
|
|||||||
<span>{category._count.subscribers} subscribers</span>
|
<span>{category._count.subscribers} subscribers</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<McpServerPopup initialCategories={[slug]} />
|
{config.features.mcp !== false && <McpServerPopup initialCategories={[slug]} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+162
-166
@@ -10,10 +10,11 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { McpConfigTabs } from "@/components/mcp/mcp-config-tabs";
|
import { McpConfigTabs } from "@/components/mcp/mcp-config-tabs";
|
||||||
|
import config from "@/../prompts.config";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "API Documentation - prompts.chat",
|
title: "API Documentation - prompts.chat",
|
||||||
description: "MCP-first API for searching and discovering AI prompts programmatically",
|
description: "API for searching and discovering AI prompts programmatically",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function ApiDocsPage() {
|
export default async function ApiDocsPage() {
|
||||||
@@ -25,83 +26,87 @@ export default async function ApiDocsPage() {
|
|||||||
<div className="container max-w-4xl py-10">
|
<div className="container max-w-4xl py-10">
|
||||||
<h1 className="text-2xl font-bold mb-2">API Documentation</h1>
|
<h1 className="text-2xl font-bold mb-2">API Documentation</h1>
|
||||||
<p className="text-muted-foreground mb-8">
|
<p className="text-muted-foreground mb-8">
|
||||||
prompts.chat provides an MCP-first API for searching and discovering AI prompts programmatically.
|
{config.features.mcp !== false
|
||||||
Use the MCP endpoint directly with any MCP-compatible client, or make standard HTTP requests.
|
? "prompts.chat provides an MCP-first API for searching and discovering AI prompts programmatically. Use the MCP endpoint directly with any MCP-compatible client, or make standard HTTP requests."
|
||||||
|
: "prompts.chat provides an API for searching and discovering AI prompts programmatically."
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-10">
|
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-10">
|
||||||
{/* MCP Overview */}
|
{config.features.mcp !== false && (
|
||||||
<section className="space-y-4">
|
<>
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
{/* MCP Overview */}
|
||||||
<Zap className="h-5 w-5" />
|
<section className="space-y-4">
|
||||||
MCP-First API
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
</h2>
|
<Zap className="h-5 w-5" />
|
||||||
<p className="text-muted-foreground">
|
MCP-First API
|
||||||
Our API is built on the{" "}
|
</h2>
|
||||||
<Link
|
|
||||||
href="https://modelcontextprotocol.io"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline hover:text-foreground"
|
|
||||||
>
|
|
||||||
Model Context Protocol (MCP)
|
|
||||||
</Link>
|
|
||||||
, enabling seamless integration with AI assistants, IDEs, and automation tools.
|
|
||||||
The same endpoint works for both MCP clients and traditional REST-style requests.
|
|
||||||
</p>
|
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
|
|
||||||
<p className="text-muted-foreground"># MCP Endpoint</p>
|
|
||||||
<p>POST {baseUrl}/api/mcp</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Using with MCP Clients */}
|
|
||||||
<section className="space-y-4">
|
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
|
||||||
<Terminal className="h-5 w-5" />
|
|
||||||
Using with MCP Clients
|
|
||||||
</h2>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Add prompts.chat to your MCP client configuration. Choose your client and connection type below:
|
|
||||||
</p>
|
|
||||||
<McpConfigTabs baseUrl={baseUrl} className="[&_button]:text-sm [&_button]:px-3 [&_button]:py-1.5 [&_pre]:text-sm [&_pre]:p-4" />
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
<strong>Remote</strong> connects directly to prompts.chat API. <strong>Local</strong> runs the MCP server locally via npx.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Authentication */}
|
|
||||||
<section className="space-y-4">
|
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
|
||||||
<Key className="h-5 w-5" />
|
|
||||||
Authentication
|
|
||||||
</h2>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Most API features work without authentication. However, to save prompts via MCP or access your private prompts,
|
|
||||||
you need to authenticate using an API key.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">Generate an API Key</p>
|
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Go to{" "}
|
Our API is built on the{" "}
|
||||||
<Link href="/settings" className="underline hover:text-foreground">
|
<Link
|
||||||
Settings
|
href="https://modelcontextprotocol.io"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
Model Context Protocol (MCP)
|
||||||
</Link>
|
</Link>
|
||||||
{" "}to generate your API key. Keys start with <code className="bg-muted px-1.5 py-0.5 rounded">pchat_</code>.
|
, enabling seamless integration with AI assistants, IDEs, and automation tools.
|
||||||
|
The same endpoint works for both MCP clients and traditional REST-style requests.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
|
||||||
<div>
|
<p className="text-muted-foreground"># MCP Endpoint</p>
|
||||||
<p className="font-medium">Using the API Key</p>
|
<p>POST {baseUrl}/api/mcp</p>
|
||||||
<p className="text-muted-foreground">
|
</div>
|
||||||
Pass your API key via the <code className="bg-muted px-1.5 py-0.5 rounded">PROMPTS_API_KEY</code> header.
|
</section>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
{/* Using with MCP Clients */}
|
||||||
<pre>{`# Remote: HTTP transport with headers
|
<section className="space-y-4">
|
||||||
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<Terminal className="h-5 w-5" />
|
||||||
|
Using with MCP Clients
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Add prompts.chat to your MCP client configuration. Choose your client and connection type below:
|
||||||
|
</p>
|
||||||
|
<McpConfigTabs baseUrl={baseUrl} className="[&_button]:text-sm [&_button]:px-3 [&_button]:py-1.5 [&_pre]:text-sm [&_pre]:p-4" />
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
<strong>Remote</strong> connects directly to prompts.chat API. <strong>Local</strong> runs the MCP server locally via npx.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Authentication */}
|
||||||
|
<section className="space-y-4">
|
||||||
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<Key className="h-5 w-5" />
|
||||||
|
Authentication
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Most API features work without authentication. However, to save prompts via MCP or access your private prompts,
|
||||||
|
you need to authenticate using an API key.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Generate an API Key</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Go to{" "}
|
||||||
|
<Link href="/settings" className="underline hover:text-foreground">
|
||||||
|
Settings
|
||||||
|
</Link>
|
||||||
|
{" "}to generate your API key. Keys start with <code className="bg-muted px-1.5 py-0.5 rounded">pchat_</code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Using the API Key</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Pass your API key via the <code className="bg-muted px-1.5 py-0.5 rounded">PROMPTS_API_KEY</code> header.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
|
<pre>{`# Remote: HTTP transport with headers
|
||||||
"prompts-chat": {
|
"prompts-chat": {
|
||||||
"url": "${baseUrl}/api/mcp",
|
"url": "${baseUrl}/api/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
@@ -123,30 +128,30 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "PROMPTS_API_KEY: pchat_your_api_key_here" \\
|
-H "PROMPTS_API_KEY: pchat_your_api_key_here" \\
|
||||||
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'`}</pre>
|
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'`}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
<strong>Remote (HTTP)</strong> sends requests to prompts.chat with the API key in headers.
|
<strong>Remote (HTTP)</strong> sends requests to prompts.chat with the API key in headers.
|
||||||
<strong> Local (stdio)</strong> runs the MCP server locally via npx with the API key as an environment variable.
|
<strong> Local (stdio)</strong> runs the MCP server locally via npx with the API key as an environment variable.
|
||||||
With authentication, you can use the <code className="bg-muted px-1.5 py-0.5 rounded">save_prompt</code> tool
|
With authentication, you can use the <code className="bg-muted px-1.5 py-0.5 rounded">save_prompt</code> tool
|
||||||
and search results will include your private prompts.
|
and search results will include your private prompts.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* MCP Prompts */}
|
{/* MCP Prompts */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Terminal className="h-5 w-5" />
|
<Terminal className="h-5 w-5" />
|
||||||
MCP Prompts
|
MCP Prompts
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
All public prompts are exposed as native MCP prompts. This allows MCP clients to list
|
All public prompts are exposed as native MCP prompts. This allows MCP clients to list
|
||||||
and use prompts directly via slash commands or prompt pickers. You can filter prompts
|
and use prompts directly via slash commands or prompt pickers. You can filter prompts
|
||||||
by category or tag using URL query parameters.
|
by category or tag using URL query parameters.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`# Filter by users (one or more usernames)
|
<pre>{`# Filter by users (one or more usernames)
|
||||||
"prompts-chat": {
|
"prompts-chat": {
|
||||||
"url": "${baseUrl}/api/mcp?users=f,torvalds"
|
"url": "${baseUrl}/api/mcp?users=f,torvalds"
|
||||||
}
|
}
|
||||||
@@ -165,24 +170,24 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
"prompts-chat": {
|
"prompts-chat": {
|
||||||
"url": "${baseUrl}/api/mcp?users=f&categories=coding&tags=js"
|
"url": "${baseUrl}/api/mcp?users=f&categories=coding&tags=js"
|
||||||
}`}</pre>
|
}`}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
|
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">prompts/list</p>
|
<p className="font-medium">prompts/list</p>
|
||||||
<p className="text-muted-foreground">Browse all available prompts with pagination support.</p>
|
<p className="text-muted-foreground">Browse all available prompts with pagination support.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">prompts/get</p>
|
<p className="font-medium">prompts/get</p>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Retrieve a prompt by ID. Variables ({"${name}"} or {"${name:default}"}) are automatically
|
Retrieve a prompt by ID. Variables ({"${name}"} or {"${name:default}"}) are automatically
|
||||||
substituted with provided arguments.
|
substituted with provided arguments.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`# List prompts
|
<pre>{`# List prompts
|
||||||
curl -X POST ${baseUrl}/api/mcp \\
|
curl -X POST ${baseUrl}/api/mcp \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Accept: application/json, text/event-stream" \\
|
-H "Accept: application/json, text/event-stream" \\
|
||||||
@@ -201,15 +206,15 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
"arguments": { "topic": "AI safety" }
|
"arguments": { "topic": "AI safety" }
|
||||||
}
|
}
|
||||||
}'`}</pre>
|
}'`}</pre>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Available Tools */}
|
{/* Available Tools */}
|
||||||
<section className="space-y-6">
|
<section className="space-y-6">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Box className="h-5 w-5" />
|
<Box className="h-5 w-5" />
|
||||||
Available Tools
|
Available Tools
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* search_prompts Tool */}
|
{/* search_prompts Tool */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -408,17 +413,17 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* MCP Protocol Example */}
|
{/* MCP Protocol Example */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Code className="h-5 w-5" />
|
<Code className="h-5 w-5" />
|
||||||
MCP Protocol Examples
|
MCP Protocol Examples
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="font-medium">Initialize Connection</h3>
|
<h3 className="font-medium">Initialize Connection</h3>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`curl -X POST ${baseUrl}/api/mcp \\
|
<pre>{`curl -X POST ${baseUrl}/api/mcp \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Accept: application/json, text/event-stream" \\
|
-H "Accept: application/json, text/event-stream" \\
|
||||||
-d '{
|
-d '{
|
||||||
@@ -431,13 +436,13 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
"clientInfo": { "name": "my-app", "version": "1.0.0" }
|
"clientInfo": { "name": "my-app", "version": "1.0.0" }
|
||||||
}
|
}
|
||||||
}'`}</pre>
|
}'`}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="font-medium">List Available Tools</h3>
|
<h3 className="font-medium">List Available Tools</h3>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`curl -X POST ${baseUrl}/api/mcp \\
|
<pre>{`curl -X POST ${baseUrl}/api/mcp \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Accept: application/json, text/event-stream" \\
|
-H "Accept: application/json, text/event-stream" \\
|
||||||
-d '{
|
-d '{
|
||||||
@@ -445,13 +450,13 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
"id": 2,
|
"id": 2,
|
||||||
"method": "tools/list"
|
"method": "tools/list"
|
||||||
}'`}</pre>
|
}'`}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="font-medium">Search Prompts</h3>
|
<h3 className="font-medium">Search Prompts</h3>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`curl -X POST ${baseUrl}/api/mcp \\
|
<pre>{`curl -X POST ${baseUrl}/api/mcp \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-H "Accept: application/json, text/event-stream" \\
|
-H "Accept: application/json, text/event-stream" \\
|
||||||
-d '{
|
-d '{
|
||||||
@@ -466,18 +471,18 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}'`}</pre>
|
}'`}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Response Format */}
|
{/* Response Format */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold">Response Format</h2>
|
<h2 className="text-lg font-semibold">Response Format</h2>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
The <code className="bg-muted px-1.5 py-0.5 rounded text-sm">search_prompts</code> tool returns results in the following format:
|
The <code className="bg-muted px-1.5 py-0.5 rounded text-sm">search_prompts</code> tool returns results in the following format:
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`{
|
<pre>{`{
|
||||||
"query": "code review",
|
"query": "code review",
|
||||||
"count": 2,
|
"count": 2,
|
||||||
"prompts": [
|
"prompts": [
|
||||||
@@ -495,14 +500,16 @@ curl -X POST ${baseUrl}/api/mcp \\
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`}</pre>
|
}`}</pre>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* REST API Alternative */}
|
{/* REST API Alternative */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold">REST API Alternative</h2>
|
<h2 className="text-lg font-semibold">REST API</h2>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
For simpler integrations, you can also use the standard REST endpoint:
|
Use the standard REST endpoint to search and retrieve prompts:
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
||||||
<pre>{`# Search prompts via REST
|
<pre>{`# Search prompts via REST
|
||||||
@@ -513,17 +520,6 @@ curl "${baseUrl}/api/prompts/{id}"`}</pre>
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Server Info */}
|
|
||||||
<section className="space-y-4">
|
|
||||||
<h2 className="text-lg font-semibold">Server Information</h2>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Get MCP server information and available tools:
|
|
||||||
</p>
|
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
|
|
||||||
<p>GET {baseUrl}/api/mcp</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Rate Limits */}
|
{/* Rate Limits */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold">Rate Limits</h2>
|
<h2 className="text-lg font-semibold">Rate Limits</h2>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Link from "next/link";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Server, Database, Key, Palette, Globe, Settings, HardDrive, Cpu, BookOpen } from "lucide-react";
|
import { Server, Database, Key, Palette, Globe, Settings, HardDrive, Cpu, BookOpen } from "lucide-react";
|
||||||
import DeepWikiIcon from "@/../public/deepwiki.svg";
|
import DeepWikiIcon from "@/../public/deepwiki.svg";
|
||||||
|
import Context7Icon from "@/../public/context7.svg";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -41,47 +42,83 @@ export default function SelfHostingPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* DeepWiki */}
|
{/* Using Documentation AI-Agents */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-6">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h2 className="text-xl font-bold">Using Documentation AI-Agents</h2>
|
||||||
<Image src={DeepWikiIcon} alt="" width={20} height={20} />
|
|
||||||
DeepWiki
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
</h2>
|
{/* DeepWiki */}
|
||||||
<p className="text-muted-foreground">
|
<div className="space-y-4">
|
||||||
<Link
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
href="https://deepwiki.com/f/awesome-chatgpt-prompts"
|
<Image src={DeepWikiIcon} alt="" width={20} height={20} />
|
||||||
target="_blank"
|
DeepWiki
|
||||||
rel="noopener noreferrer"
|
</h3>
|
||||||
className="underline hover:text-foreground"
|
<p className="text-muted-foreground">
|
||||||
>
|
<Link
|
||||||
DeepWiki
|
href="https://deepwiki.com/f/awesome-chatgpt-prompts"
|
||||||
</Link>
|
target="_blank"
|
||||||
{" "}provides AI-powered documentation and insights for this repository. It automatically generates comprehensive documentation from the codebase, making it easier to understand the project structure, APIs, and implementation details.
|
rel="noopener noreferrer"
|
||||||
</p>
|
className="underline hover:text-foreground"
|
||||||
<ul className="list-disc list-inside text-muted-foreground space-y-1">
|
>
|
||||||
<li>AI-generated documentation from source code</li>
|
DeepWiki
|
||||||
<li>Interactive codebase exploration</li>
|
</Link>
|
||||||
<li>Architecture diagrams and component relationships</li>
|
{" "}provides AI-powered documentation and insights for this repository.
|
||||||
<li>Ask questions about the codebase</li>
|
</p>
|
||||||
</ul>
|
<ul className="list-disc list-inside text-muted-foreground space-y-1 text-sm">
|
||||||
|
<li>AI-generated documentation from source code</li>
|
||||||
|
<li>Interactive codebase exploration</li>
|
||||||
|
<li>Architecture diagrams and component relationships</li>
|
||||||
|
<li>Available as an MCP server</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Context7 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<Image src={Context7Icon} alt="" width={20} height={20} className="rounded" />
|
||||||
|
Context7
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
<Link
|
||||||
|
href="https://context7.com/f/awesome-chatgpt-prompts?tab=chat"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
Context7
|
||||||
|
</Link>
|
||||||
|
{" "}is an AI-powered chat interface for exploring and understanding this repository.
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside text-muted-foreground space-y-1 text-sm">
|
||||||
|
<li>Chat with the codebase using natural language</li>
|
||||||
|
<li>Get answers with code references</li>
|
||||||
|
<li>Understand implementation details quickly</li>
|
||||||
|
<li>Available as an MCP server</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Prerequisites */}
|
{/* Manually */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-6">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h2 className="text-xl font-bold">Manually</h2>
|
||||||
<Database className="h-5 w-5" />
|
|
||||||
Prerequisites
|
|
||||||
</h2>
|
|
||||||
<ul className="list-disc list-inside text-muted-foreground space-y-1">
|
|
||||||
<li>Node.js 18+</li>
|
|
||||||
<li>PostgreSQL database</li>
|
|
||||||
<li>npm or yarn</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Installation */}
|
{/* Prerequisites */}
|
||||||
<section className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold">Quick Start</h2>
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<Database className="h-5 w-5" />
|
||||||
|
Prerequisites
|
||||||
|
</h3>
|
||||||
|
<ul className="list-disc list-inside text-muted-foreground space-y-1">
|
||||||
|
<li>Node.js 18+</li>
|
||||||
|
<li>PostgreSQL database</li>
|
||||||
|
<li>npm or yarn</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Installation */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold">Quick Start</h3>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1 overflow-x-auto">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1 overflow-x-auto">
|
||||||
<p className="text-muted-foreground"># Clone the repository</p>
|
<p className="text-muted-foreground"># Clone the repository</p>
|
||||||
<p>git clone https://github.com/f/awesome-chatgpt-prompts.git</p>
|
<p>git clone https://github.com/f/awesome-chatgpt-prompts.git</p>
|
||||||
@@ -96,14 +133,14 @@ export default function SelfHostingPage() {
|
|||||||
<p className="text-muted-foreground mt-3"># Start development server</p>
|
<p className="text-muted-foreground mt-3"># Start development server</p>
|
||||||
<p>npm run dev</p>
|
<p>npm run dev</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* Environment Variables */}
|
{/* Environment Variables */}
|
||||||
<section className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Key className="h-5 w-5" />
|
<Key className="h-5 w-5" />
|
||||||
Environment Variables
|
Environment Variables
|
||||||
</h2>
|
</h3>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Create a <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env</code> file based on <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env.example</code>:
|
Create a <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env</code> file based on <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env.example</code>:
|
||||||
</p>
|
</p>
|
||||||
@@ -280,14 +317,14 @@ export default function SelfHostingPage() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* Configuration */}
|
{/* Configuration */}
|
||||||
<section className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Palette className="h-5 w-5" />
|
<Palette className="h-5 w-5" />
|
||||||
Configuration (prompts.config.ts)
|
Configuration (prompts.config.ts)
|
||||||
</h2>
|
</h3>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Customize your instance by editing <code className="bg-muted px-1.5 py-0.5 rounded text-sm">prompts.config.ts</code>:
|
Customize your instance by editing <code className="bg-muted px-1.5 py-0.5 rounded text-sm">prompts.config.ts</code>:
|
||||||
</p>
|
</p>
|
||||||
@@ -508,14 +545,14 @@ export default function SelfHostingPage() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* White-label */}
|
{/* White-label */}
|
||||||
<section className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Settings className="h-5 w-5" />
|
<Settings className="h-5 w-5" />
|
||||||
White-Label Mode
|
White-Label Mode
|
||||||
</h2>
|
</h3>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Set <code className="bg-muted px-1.5 py-0.5 rounded text-sm">useCloneBranding = true</code> at the top of the config to:
|
Set <code className="bg-muted px-1.5 py-0.5 rounded text-sm">useCloneBranding = true</code> at the top of the config to:
|
||||||
</p>
|
</p>
|
||||||
@@ -530,14 +567,14 @@ export default function SelfHostingPage() {
|
|||||||
<p className="text-muted-foreground">// prompts.config.ts</p>
|
<p className="text-muted-foreground">// prompts.config.ts</p>
|
||||||
<p>const useCloneBranding = true;</p>
|
<p>const useCloneBranding = true;</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* Production */}
|
{/* Production */}
|
||||||
<section className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<Globe className="h-5 w-5" />
|
<Globe className="h-5 w-5" />
|
||||||
Production Deployment
|
Production Deployment
|
||||||
</h2>
|
</h3>
|
||||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1">
|
<div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1">
|
||||||
<p>npm run build</p>
|
<p>npm run build</p>
|
||||||
<p>npm run start</p>
|
<p>npm run start</p>
|
||||||
@@ -545,11 +582,11 @@ export default function SelfHostingPage() {
|
|||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Deploy to Vercel, Railway, Render, or any Node.js hosting platform. Make sure to set all environment variables in your hosting provider's dashboard.
|
Deploy to Vercel, Railway, Render, or any Node.js hosting platform. Make sure to set all environment variables in your hosting provider's dashboard.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
{/* Support */}
|
{/* Support */}
|
||||||
<section className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-lg font-semibold">Support</h2>
|
<h3 className="text-lg font-semibold">Support</h3>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
For issues and questions, please open a{" "}
|
For issues and questions, please open a{" "}
|
||||||
<Link
|
<Link
|
||||||
@@ -570,7 +607,8 @@ export default function SelfHostingPage() {
|
|||||||
SELF-HOSTING.md
|
SELF-HOSTING.md
|
||||||
</Link>
|
</Link>
|
||||||
{" "}file in the repository.
|
{" "}file in the repository.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export default async function PromptsPage({ searchParams }: PromptsPageProps) {
|
|||||||
{!config.homepage?.useCloneBranding && (
|
{!config.homepage?.useCloneBranding && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<HFDataStudioDropdown aiGenerationEnabled={aiGenerationAvailable} />
|
<HFDataStudioDropdown aiGenerationEnabled={aiGenerationAvailable} />
|
||||||
<McpServerPopup />
|
{config.features.mcp !== false && <McpServerPopup />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button size="sm" className="h-8 text-xs w-full sm:w-auto" asChild>
|
<Button size="sm" className="h-8 text-xs w-full sm:w-auto" asChild>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
|
import config from "@/../prompts.config";
|
||||||
import { ProfileForm } from "@/components/settings/profile-form";
|
import { ProfileForm } from "@/components/settings/profile-form";
|
||||||
import { ApiKeySettings } from "@/components/settings/api-key-settings";
|
import { ApiKeySettings } from "@/components/settings/api-key-settings";
|
||||||
|
|
||||||
@@ -41,10 +42,12 @@ export default async function SettingsPage() {
|
|||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<ProfileForm user={user} />
|
<ProfileForm user={user} />
|
||||||
<ApiKeySettings
|
{config.features.mcp !== false && (
|
||||||
initialApiKey={user.apiKey}
|
<ApiKeySettings
|
||||||
initialPublicByDefault={user.mcpPromptsPublicByDefault}
|
initialApiKey={user.apiKey}
|
||||||
/>
|
initialPublicByDefault={user.mcpPromptsPublicByDefault}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { ArrowLeft, Tag } from "lucide-react";
|
import { ArrowLeft, Tag } from "lucide-react";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
|
import config from "@/../prompts.config";
|
||||||
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";
|
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
|
||||||
@@ -124,7 +125,7 @@ export default async function TagPage({ params, searchParams }: TagPageProps) {
|
|||||||
{total} {t("prompts")}
|
{total} {t("prompts")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<McpServerPopup initialTags={[slug]} />
|
{config.features.mcp !== false && <McpServerPopup initialTags={[slug]} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { useSession, signOut } from "next-auth/react";
|
import { useSession, signOut } from "next-auth/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +16,8 @@ import {
|
|||||||
Globe,
|
Globe,
|
||||||
Moon,
|
Moon,
|
||||||
Sun,
|
Sun,
|
||||||
|
Copy,
|
||||||
|
ExternalLink,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -66,10 +69,33 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
|||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const branding = useBranding();
|
const branding = useBranding();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const user = session?.user;
|
const user = session?.user;
|
||||||
const isAdmin = user?.role === "ADMIN";
|
const isAdmin = user?.role === "ADMIN";
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
const [logoMenuOpen, setLogoMenuOpen] = useState(false);
|
||||||
|
const [logoMenuPosition, setLogoMenuPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const handleLogoContextMenu = (e: React.MouseEvent) => {
|
||||||
|
if (branding.useCloneBranding) return;
|
||||||
|
e.preventDefault();
|
||||||
|
setLogoMenuPosition({ x: e.clientX, y: e.clientY });
|
||||||
|
setLogoMenuOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyLogoSvg = async () => {
|
||||||
|
try {
|
||||||
|
const logoUrl = theme === "dark" ? (branding.logoDark || branding.logo) : branding.logo;
|
||||||
|
if (!logoUrl) return;
|
||||||
|
const response = await fetch(logoUrl);
|
||||||
|
const svgContent = await response.text();
|
||||||
|
await navigator.clipboard.writeText(svgContent);
|
||||||
|
setLogoMenuOpen(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to copy logo:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
@@ -168,7 +194,7 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
|||||||
</Sheet>
|
</Sheet>
|
||||||
|
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link href="/" className="flex gap-2">
|
<Link href="/" className="flex gap-2" onContextMenu={handleLogoContextMenu}>
|
||||||
{branding.logo && (
|
{branding.logo && (
|
||||||
<>
|
<>
|
||||||
<Image
|
<Image
|
||||||
@@ -190,6 +216,34 @@ export function Header({ authProvider = "credentials", allowRegistration = true
|
|||||||
<span className="font-semibold leading-none">{branding.name}</span>
|
<span className="font-semibold leading-none">{branding.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{/* Logo context menu */}
|
||||||
|
{!branding.useCloneBranding && (
|
||||||
|
<DropdownMenu open={logoMenuOpen} onOpenChange={setLogoMenuOpen}>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<span className="sr-only">Logo menu</span>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
left: logoMenuPosition.x,
|
||||||
|
top: logoMenuPosition.y,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem onClick={handleCopyLogoSvg}>
|
||||||
|
<Copy className="mr-2 h-4 w-4" />
|
||||||
|
Copy Logo SVG
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => {
|
||||||
|
router.push("/brand");
|
||||||
|
setLogoMenuOpen(false);
|
||||||
|
}}>
|
||||||
|
<ExternalLink className="mr-2 h-4 w-4" />
|
||||||
|
Brand Assets
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Desktop nav */}
|
{/* Desktop nav */}
|
||||||
<nav className="hidden md:flex items-center gap-1 text-sm">
|
<nav className="hidden md:flex items-center gap-1 text-sm">
|
||||||
{user && (
|
{user && (
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export interface FeaturesConfig {
|
|||||||
tags: boolean;
|
tags: boolean;
|
||||||
aiSearch?: boolean;
|
aiSearch?: boolean;
|
||||||
aiGeneration?: boolean;
|
aiGeneration?: boolean;
|
||||||
|
mcp?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Sponsor {
|
export interface Sponsor {
|
||||||
|
|||||||
Reference in New Issue
Block a user