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 { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||||
import { Button } from '@tpmjs/ui/Button/Button';
|
import { Button } from '@tpmjs/ui/Button/Button';
|
||||||
|
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
|
||||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { notFound, useParams } from 'next/navigation';
|
import { notFound, useParams } from 'next/navigation';
|
||||||
|
|
@ -71,6 +72,113 @@ interface PublicAgent {
|
||||||
} | null;
|
} | 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 {
|
export default function PrettyAgentDetailPage(): React.ReactElement {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const rawUsername = params.username as string;
|
const rawUsername = params.username as string;
|
||||||
|
|
@ -154,14 +262,12 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LikeButton entityType="agent" entityId={agent.id} initialCount={agent.likeCount} />
|
<LikeButton entityType="agent" entityId={agent.id} initialCount={agent.likeCount} />
|
||||||
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} />
|
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} />
|
||||||
{isOwner && (
|
<Link href={`/${username}/agents/${uid}/chat`}>
|
||||||
<Link href={`/${username}/agents/${uid}/chat`}>
|
<Button>
|
||||||
<Button>
|
<Icon icon="message" className="w-4 h-4 mr-2" />
|
||||||
<Icon icon="message" className="w-4 h-4 mr-2" />
|
Chat
|
||||||
Chat
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -185,10 +291,8 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
|
||||||
<span>Temperature: {agent.temperature}</span>
|
<span>Temperature: {agent.temperature}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Fork CTA for non-owners */}
|
{/* API Access - Available to everyone (non-owners must provide their own credentials) */}
|
||||||
{!isOwner && (
|
<AgentApiSection agentId={agent.id} provider={agent.provider} isOwner={!!isOwner} />
|
||||||
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} variant="full" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* System Prompt */}
|
{/* System Prompt */}
|
||||||
{agent.systemPrompt && (
|
{agent.systemPrompt && (
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,18 @@ interface PublicCollection {
|
||||||
useCasesGeneratedAt: string | null;
|
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 [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
|
||||||
const [showConfig, setShowConfig] = useState(false);
|
const [showConfig, setShowConfig] = useState(false);
|
||||||
|
const [showApiExample, setShowApiExample] = useState(false);
|
||||||
|
|
||||||
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
|
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
|
||||||
const httpUrl = `${baseUrl}/api/mcp/${username}/${slug}/http`;
|
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 (
|
return (
|
||||||
<section className="p-4 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 border border-primary/20 rounded-xl">
|
<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="flex items-center gap-2 mb-4">
|
||||||
|
|
@ -159,8 +190,20 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
||||||
</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 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 */}
|
{/* 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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowConfig(!showConfig)}
|
onClick={() => setShowConfig(!showConfig)}
|
||||||
|
|
@ -175,6 +218,25 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
|
||||||
<CodeBlock language="json" code={configSnippet} />
|
<CodeBlock language="json" code={configSnippet} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<p className="mt-3 text-xs text-foreground-tertiary">
|
<p className="mt-3 text-xs text-foreground-tertiary">
|
||||||
|
|
@ -310,17 +372,8 @@ export default function PrettyCollectionDetailPage(): React.ReactElement {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MCP Server URLs - Only shown for owner */}
|
{/* MCP Server URLs - Available to everyone (non-owners must provide their own credentials) */}
|
||||||
{isOwner ? (
|
<McpUrlSection username={username} slug={collection.slug} isOwner={!!isOwner} />
|
||||||
<McpUrlSection username={username} slug={collection.slug} />
|
|
||||||
) : (
|
|
||||||
<ForkButton
|
|
||||||
type="collection"
|
|
||||||
sourceId={collection.id}
|
|
||||||
sourceName={collection.name}
|
|
||||||
variant="full"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tools */}
|
{/* Tools */}
|
||||||
{collection.tools.length > 0 ? (
|
{collection.tools.length > 0 ? (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue