feat: show MCP URLs and API access info on public collection/agent pages
- Always show McpUrlSection on public collection pages (not just owners) - Add note for non-owners about providing their own credentials - Add API usage example for non-owners on collections - Add AgentApiSection component for public agent pages - Show conversation API endpoint and usage example - Make Chat button available to everyone on public agent pages
This commit is contained in:
parent
23d5159b28
commit
839e1b3bef
2 changed files with 182 additions and 25 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import Link from 'next/link';
|
||||
import { notFound, useParams } from 'next/navigation';
|
||||
|
|
@ -71,6 +72,113 @@ interface PublicAgent {
|
|||
} | null;
|
||||
}
|
||||
|
||||
function AgentApiSection({
|
||||
agentId,
|
||||
provider,
|
||||
isOwner,
|
||||
}: {
|
||||
agentId: string;
|
||||
provider: string;
|
||||
isOwner: boolean;
|
||||
}) {
|
||||
const [showExample, setShowExample] = useState(false);
|
||||
|
||||
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
|
||||
const apiUrl = `${baseUrl}/api/agents/${agentId}/conversation`;
|
||||
|
||||
const apiExampleSnippet = `// Create a conversation and send a message
|
||||
const createConvo = await fetch("${apiUrl}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer YOUR_TPMJS_API_KEY"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "My Conversation"
|
||||
})
|
||||
});
|
||||
const { data: { conversation } } = await createConvo.json();
|
||||
|
||||
// Send a message (include providerApiKey for non-owners)
|
||||
const response = await fetch(\`${apiUrl}/\${conversation.id}\`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer YOUR_TPMJS_API_KEY"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: "Hello!",
|
||||
providerApiKey: "YOUR_${provider.toUpperCase()}_API_KEY", // Required for non-owners
|
||||
env: {
|
||||
// Your env vars for any tools the agent uses
|
||||
"SOME_API_KEY": "your-key-here"
|
||||
}
|
||||
})
|
||||
});`;
|
||||
|
||||
return (
|
||||
<section className="p-4 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 border border-primary/20 rounded-xl">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<div className="p-1.5 bg-primary/10 rounded-lg">
|
||||
<Icon icon="link" className="w-4 h-4 text-primary" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-foreground">API Access</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="group">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="text-xs font-medium text-foreground-secondary uppercase tracking-wide">
|
||||
Conversation API
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 px-3 py-2 bg-surface border border-border rounded-lg font-mono text-sm text-foreground-secondary overflow-x-auto">
|
||||
{apiUrl}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Note for non-owners */}
|
||||
{!isOwner && (
|
||||
<div className="mt-4 p-3 bg-warning/10 border border-warning/20 rounded-lg">
|
||||
<p className="text-sm text-warning-foreground">
|
||||
<Icon icon="info" className="w-4 h-4 inline mr-1" />
|
||||
You'll need to provide your own <strong>{provider}</strong> API key via the{' '}
|
||||
<code className="font-mono text-xs bg-surface px-1 rounded">providerApiKey</code> field,
|
||||
plus any tool credentials via the{' '}
|
||||
<code className="font-mono text-xs bg-surface px-1 rounded">env</code> parameter.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-border/50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowExample(!showExample)}
|
||||
className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
|
||||
>
|
||||
<Icon icon={showExample ? 'chevronDown' : 'chevronRight'} className="w-4 h-4" />
|
||||
<span>Show API usage example</span>
|
||||
</button>
|
||||
|
||||
{showExample && (
|
||||
<div className="mt-3">
|
||||
<CodeBlock language="typescript" code={apiExampleSnippet} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mt-3 text-xs text-foreground-tertiary">
|
||||
<Link href="/docs/tutorials/agents" className="text-primary hover:underline">
|
||||
Learn more about the Agent Conversation API
|
||||
</Link>
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PrettyAgentDetailPage(): React.ReactElement {
|
||||
const params = useParams();
|
||||
const rawUsername = params.username as string;
|
||||
|
|
@ -154,14 +262,12 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
|
|||
<div className="flex items-center gap-2">
|
||||
<LikeButton entityType="agent" entityId={agent.id} initialCount={agent.likeCount} />
|
||||
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} />
|
||||
{isOwner && (
|
||||
<Link href={`/${username}/agents/${uid}/chat`}>
|
||||
<Button>
|
||||
<Icon icon="message" className="w-4 h-4 mr-2" />
|
||||
Chat
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Link href={`/${username}/agents/${uid}/chat`}>
|
||||
<Button>
|
||||
<Icon icon="message" className="w-4 h-4 mr-2" />
|
||||
Chat
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -185,10 +291,8 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
|
|||
<span>Temperature: {agent.temperature}</span>
|
||||
</div>
|
||||
|
||||
{/* Fork CTA for non-owners */}
|
||||
{!isOwner && (
|
||||
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} variant="full" />
|
||||
)}
|
||||
{/* API Access - Available to everyone (non-owners must provide their own credentials) */}
|
||||
<AgentApiSection agentId={agent.id} provider={agent.provider} isOwner={!!isOwner} />
|
||||
|
||||
{/* System Prompt */}
|
||||
{agent.systemPrompt && (
|
||||
|
|
|
|||
|
|
@ -74,9 +74,18 @@ interface PublicCollection {
|
|||
useCasesGeneratedAt: string | null;
|
||||
}
|
||||
|
||||
function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
||||
function McpUrlSection({
|
||||
username,
|
||||
slug,
|
||||
isOwner,
|
||||
}: {
|
||||
username: string;
|
||||
slug: string;
|
||||
isOwner: boolean;
|
||||
}) {
|
||||
const [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
|
||||
const [showConfig, setShowConfig] = useState(false);
|
||||
const [showApiExample, setShowApiExample] = useState(false);
|
||||
|
||||
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
|
||||
const httpUrl = `${baseUrl}/api/mcp/${username}/${slug}/http`;
|
||||
|
|
@ -100,6 +109,28 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
|||
}
|
||||
}`;
|
||||
|
||||
const apiExampleSnippet = `// Call a tool with your own credentials
|
||||
const response = await fetch("${httpUrl}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer YOUR_TPMJS_API_KEY"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "tool-name",
|
||||
arguments: { /* tool args */ },
|
||||
env: {
|
||||
// Your env vars for the tools
|
||||
"API_KEY": "your-key-here"
|
||||
}
|
||||
},
|
||||
id: 1
|
||||
})
|
||||
});`;
|
||||
|
||||
return (
|
||||
<section className="p-4 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 border border-primary/20 rounded-xl">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
|
|
@ -159,8 +190,20 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Note for non-owners */}
|
||||
{!isOwner && (
|
||||
<div className="mt-4 p-3 bg-warning/10 border border-warning/20 rounded-lg">
|
||||
<p className="text-sm text-warning-foreground">
|
||||
<Icon icon="info" className="w-4 h-4 inline mr-1" />
|
||||
You'll need to provide your own API keys for any tools that require them. Pass
|
||||
credentials via the <code className="font-mono text-xs bg-surface px-1 rounded">env</code>{' '}
|
||||
parameter in your API calls.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Config snippet toggle */}
|
||||
<div className="mt-4 pt-4 border-t border-border/50">
|
||||
<div className="mt-4 pt-4 border-t border-border/50 space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfig(!showConfig)}
|
||||
|
|
@ -175,6 +218,25 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
|||
<CodeBlock language="json" code={configSnippet} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isOwner && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowApiExample(!showApiExample)}
|
||||
className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
|
||||
>
|
||||
<Icon icon={showApiExample ? 'chevronDown' : 'chevronRight'} className="w-4 h-4" />
|
||||
<span>Show API usage example</span>
|
||||
</button>
|
||||
|
||||
{showApiExample && (
|
||||
<div className="mt-3">
|
||||
<CodeBlock language="typescript" code={apiExampleSnippet} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mt-3 text-xs text-foreground-tertiary">
|
||||
|
|
@ -310,17 +372,8 @@ export default function PrettyCollectionDetailPage(): React.ReactElement {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* MCP Server URLs - Only shown for owner */}
|
||||
{isOwner ? (
|
||||
<McpUrlSection username={username} slug={collection.slug} />
|
||||
) : (
|
||||
<ForkButton
|
||||
type="collection"
|
||||
sourceId={collection.id}
|
||||
sourceName={collection.name}
|
||||
variant="full"
|
||||
/>
|
||||
)}
|
||||
{/* MCP Server URLs - Available to everyone (non-owners must provide their own credentials) */}
|
||||
<McpUrlSection username={username} slug={collection.slug} isOwner={!!isOwner} />
|
||||
|
||||
{/* Tools */}
|
||||
{collection.tools.length > 0 ? (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue