feat: improve web examples

pull/715/head
Travis Fischer 2025-06-24 20:06:20 -05:00
rodzic 7759aa43d3
commit 7b5f7e8f97
10 zmienionych plików z 391 dodań i 89 usunięć

Wyświetl plik

@ -79,7 +79,10 @@ export function registerV1CreateDeployment(
if (!project) {
// Used for testing e2e fixtures in the development marketplace
const isPrivate = !(user.username === 'dev' && env.isDev)
const isPrivate = !(
(user.username === 'dev' && env.isDev) ||
user.username === 'agentic'
)
// Upsert the project if it doesn't already exist
// The typecast is necessary here because we're not populating the

Wyświetl plik

@ -63,7 +63,10 @@ export function registerV1CreateProject(
)
// Used for testing e2e fixtures in the development marketplace
const isPrivate = !(user.username === 'dev' && env.isDev)
const isPrivate = !(
(user.username === 'dev' && env.isDev) ||
user.username === 'agentic'
)
const [project] = await db
.insert(schema.projects)

Wyświetl plik

@ -1,7 +1,5 @@
import { sha256 } from '@agentic/platform-core'
export async function createConsumerToken(): Promise<string> {
const hash = await sha256()
return hash.slice(0, 24)
return sha256()
}

Wyświetl plik

@ -1,11 +1,10 @@
import Link from 'next/link'
import { DemandSideCTA } from '@/components/demand-side-cta'
import { ExampleUsage } from '@/components/example-usage'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { githubUrl, twitterUrl } from '@/lib/config'
import { ExampleUsage } from './example-usage'
export default function TheBestDamnLandingPageEver() {
return (
<>

Wyświetl plik

@ -1,9 +1,21 @@
import { toJsxRuntime } from 'hast-util-to-jsx-runtime'
import { Fragment, type JSX, useEffect, useState } from 'react'
import { CheckIcon, CopyIcon } from 'lucide-react'
import {
Fragment,
type JSX,
useCallback,
useEffect,
useRef,
useState
} from 'react'
import { jsx, jsxs } from 'react/jsx-runtime'
import { type BundledLanguage, codeToHast } from 'shiki/bundle/web'
import { toastError } from '@/lib/notifications'
import { cn } from '@/lib/utils'
import { LoadingIndicator } from './loading-indicator'
import { Button } from './ui/button'
export async function highlight({
code,
@ -51,10 +63,62 @@ export function CodeBlock({
className?: string
}) {
const [nodes, setNodes] = useState(initial)
const [isCopied, setIsCopied] = useState(false)
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
void highlight({ code, lang, theme, className }).then(setNodes)
}, [code, lang, theme, className])
void highlight({
code,
lang,
theme,
className: 'rounded-sm w-full text-wrap p-4 text-sm'
}).then(setNodes)
}, [code, lang, theme])
return nodes ?? <LoadingIndicator />
const onCopy = useCallback(() => {
;(async () => {
try {
await navigator.clipboard.writeText(code)
setIsCopied(true)
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
timeoutRef.current = null
}
timeoutRef.current = setTimeout(() => {
timeoutRef.current = null
setIsCopied(false)
}, 2000)
} catch {
setIsCopied(true)
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
timeoutRef.current = null
}
void toastError('Error copying code to clipboard')
}
})()
}, [code, timeoutRef])
return (
<div className={cn('relative group rounded-sm w-full', className)}>
{nodes ? (
<>
{nodes}
<Button
variant='outline'
className='absolute top-4 right-4 px-2.5! opacity-0 group-hover:opacity-100 group-hover:duration-0 transition-opacity duration-150'
onClick={onCopy}
>
{isCopied ? <CheckIcon /> : <CopyIcon />}
</Button>
</>
) : (
<LoadingIndicator />
)}
</div>
)
}

Wyświetl plik

@ -2,11 +2,13 @@
import { useLocalStorage } from 'react-use'
import { useAgentic } from '@/components/agentic-provider'
import { CodeBlock } from '@/components/code-block'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
defaultConfig,
type DeveloperConfig,
getCodeForDeveloperConfig,
type HTTPTarget,
httpTargetLabels,
httpTargets,
@ -23,16 +25,85 @@ import {
tsFrameworkTargetLabels,
tsFrameworkTargets
} from '@/lib/developer-config'
import { useQuery } from '@/lib/query-client'
import { LoadingIndicator } from './loading-indicator'
export function ExampleUsage() {
const ctx = useAgentic()
const [config, setConfig] = useLocalStorage<DeveloperConfig>(
'config',
defaultConfig
)
// TODO: make this configurable
// TODO: allow to take the project and/or consumer in as props
// TODO: need a way of fetching a project and target deployment; same as in `AgenticToolClient.fromIdentifier` (currently only supports latest)
const projectIdentifier = '@agentic/search'
// Load the public project
const {
data: project,
isLoading,
isError
} = useQuery({
queryKey: ['project', projectIdentifier],
queryFn: () =>
ctx!.api.getPublicProjectByIdentifier({
projectIdentifier,
populate: ['lastPublishedDeployment']
}),
enabled: !!ctx
})
// If the user is authenticated, check if they have an active subscription to
// this project
// TODO: use consumer for apiKey
// const {
// data: consumer,
// isLoading: isConsumerLoading
// // isError: isConsumerError
// } = useQuery({
// queryKey: [
// 'project',
// projectIdentifier,
// 'user',
// ctx?.api.authSession?.user.id
// ],
// queryFn: () =>
// ctx!.api.getConsumerByProjectIdentifier({
// projectIdentifier
// }),
// enabled: !!ctx?.isAuthenticated
// })
if (isLoading || !config) {
return <LoadingIndicator className='w-full max-w-3xl' />
}
// TODO: allow to target a specific deployment
const deployment = project?.lastPublishedDeployment
if (isError || !project || !deployment) {
return (
<div>
Error loading project. Please refresh the page or contact{' '}
<a href='mailto:support@agentic.so'>support@agentic.so</a>.
</div>
)
}
const codeSnippet = getCodeForDeveloperConfig({
config,
project,
deployment,
identifier: projectIdentifier
})
return (
<Tabs
defaultValue={config!.target}
defaultValue={config.target}
onValueChange={(value) =>
setConfig({
...defaultConfig,
@ -40,7 +111,7 @@ export function ExampleUsage() {
target: value as Target
})
}
className='w-full max-w-2xl'
className='w-full max-w-3xl'
>
<TabsList>
{targets.map((target) => (
@ -52,7 +123,7 @@ export function ExampleUsage() {
<TabsContent value='mcp' className='w-full'>
<Tabs
defaultValue={config!.mcpClientTarget}
defaultValue={config.mcpClientTarget}
onValueChange={(value) =>
setConfig({
...defaultConfig,
@ -80,11 +151,7 @@ export function ExampleUsage() {
value={mcpClientTarget}
className='w-full'
>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='json'
className='p-4 rounded-sm w-full'
/>
<CodeBlock code={codeSnippet.code} lang={codeSnippet.lang} />
</TabsContent>
))}
</Tabs>
@ -92,7 +159,7 @@ export function ExampleUsage() {
<TabsContent value='typescript' className='w-full'>
<Tabs
defaultValue={config!.tsFrameworkTarget ?? 'ai'}
defaultValue={config.tsFrameworkTarget ?? 'ai'}
onValueChange={(value) =>
setConfig({
...defaultConfig,
@ -102,12 +169,12 @@ export function ExampleUsage() {
}
className='w-full'
>
<TabsList className='h-auto flex-wrap'>
<TabsList className='w-full h-auto flex-wrap'>
{tsFrameworkTargets.map((framework) => (
<TabsTrigger
key={framework}
value={framework}
className='cursor-pointer'
className='cursor-pointer text-xs!'
>
{tsFrameworkTargetLabels[framework]}
</TabsTrigger>
@ -116,11 +183,7 @@ export function ExampleUsage() {
{tsFrameworkTargets.map((framework) => (
<TabsContent key={framework} value={framework} className='w-full'>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='ts'
className='p-4 rounded-sm w-full'
/>
<CodeBlock code={codeSnippet.code} lang={codeSnippet.lang} />
</TabsContent>
))}
</Tabs>
@ -128,7 +191,7 @@ export function ExampleUsage() {
<TabsContent value='python' className='w-full'>
<Tabs
defaultValue={config!.pyFrameworkTarget}
defaultValue={config.pyFrameworkTarget}
onValueChange={(value) =>
setConfig({
...defaultConfig,
@ -152,11 +215,7 @@ export function ExampleUsage() {
{pyFrameworkTargets.map((framework) => (
<TabsContent key={framework} value={framework} className='w-full'>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='py'
className='p-4 rounded-sm w-full'
/>
<CodeBlock code={codeSnippet.code} lang={codeSnippet.lang} />
</TabsContent>
))}
</Tabs>
@ -164,7 +223,7 @@ export function ExampleUsage() {
<TabsContent value='http' className='w-full'>
<Tabs
defaultValue={config!.httpTarget}
defaultValue={config.httpTarget}
onValueChange={(value) =>
setConfig({
...defaultConfig,
@ -188,11 +247,7 @@ export function ExampleUsage() {
{httpTargets.map((httpTarget) => (
<TabsContent key={httpTarget} value={httpTarget} className='w-full'>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='bash'
className='p-4 rounded-sm w-full'
/>
<CodeBlock code={codeSnippet.code} lang={codeSnippet.lang} />
</TabsContent>
))}
</Tabs>

Wyświetl plik

@ -5,6 +5,7 @@
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0 auto;
}
.fill {

Wyświetl plik

@ -1,4 +1,5 @@
import type { Deployment, Project } from '@agentic/platform-types'
import type { BundledLanguage } from 'shiki/bundle/web'
import { assert } from '@agentic/platform-core'
import { gatewayBaseUrl } from './config'
@ -24,7 +25,7 @@ export const httpTargets: (keyof typeof httpTargetLabels)[] = Object.keys(
export type HTTPTarget = (typeof httpTargets)[number]
export const mcpClientTargetLabels = {
any: 'Any MCP Client',
url: 'MCP Server URL',
'claude-desktop': 'Claude Desktop',
raycast: 'Raycast',
cursor: 'Cursor',
@ -41,8 +42,8 @@ export const tsFrameworkTargetLabels = {
'openai-chat': 'OpenAI Chat',
'openai-responses': 'OpenAI Responses',
langchain: 'LangChain',
mastra: 'Mastra',
llamaindex: 'LlamaIndex',
mastra: 'Mastra',
'firebase-genkit': 'Firebase GenKit',
xsai: 'xsAI'
} as const
@ -69,19 +70,25 @@ export type DeveloperConfig = {
export const defaultConfig: DeveloperConfig = {
target: 'typescript',
mcpClientTarget: 'any',
mcpClientTarget: 'url',
tsFrameworkTarget: 'ai',
pyFrameworkTarget: 'openai',
httpTarget: 'curl'
}
export type CodeSnippet = {
code: string
lang: BundledLanguage
// install?: string // TODO
}
export function getCodeForDeveloperConfig(opts: {
config: DeveloperConfig
project: Project
deployment: Deployment
identifier: string
tool?: string
}): string {
}): CodeSnippet {
const { config } = opts
switch (config.target) {
@ -90,8 +97,7 @@ export function getCodeForDeveloperConfig(opts: {
case 'typescript':
return getCodeForTSFrameworkConfig(opts)
case 'python':
return 'Python support is coming soon...'
// return getCodeForPythonFrameworkConfig(opts)
return getCodeForPythonFrameworkConfig(opts)
case 'http':
return getCodeForHTTPConfig(opts)
}
@ -101,21 +107,27 @@ export function getCodeForMCPClientConfig({
identifier
}: {
identifier: string
}): string {
return `${gatewayBaseUrl}/${identifier}/mcp`
}): CodeSnippet {
return {
code: `${gatewayBaseUrl}/${identifier}/mcp`,
lang: 'bash'
}
}
export function getCodeForTSFrameworkConfig({
config,
identifier
identifier,
prompt = 'What is the latest news about AI?'
}: {
config: DeveloperConfig
identifier: string
}): string {
prompt?: string
}): CodeSnippet {
switch (config.tsFrameworkTarget) {
case 'ai':
return `
import { createAISDKTools } from '@agentic/ai'
return {
code: `
import { createAISDKTools } from '@agentic/ai-sdk'
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { openai } from '@ai-sdk/openai'
import { generateText } from 'ai'
@ -126,66 +138,221 @@ const result = await generateText({
model: openai('gpt-4o-mini'),
tools: createAISDKTools(searchTool),
toolChoice: 'required',
temperature: 0,
system: 'You are a helpful assistant. Be as concise as possible.',
prompt: 'What is the latest news about AI?'
prompt: '${prompt}'
})
console.log(result.toolResults[0])
`.trim()
`.trim(),
lang: 'ts'
}
case 'openai-chat':
return `
return {
code: `
import { AgenticToolClient } from '@agentic/platform-tool-client'
import OpenAI from 'openai'
const openai = new OpenAI()
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
// This example uses OpenAI's Chat Completions API
const res = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: 'You are a helpful assistant. Be as concise as possible.'
}
{
role: 'user',
content: 'What is the latest news about AI?'
content: '${prompt}'
}
],
model: 'gpt-4o-mini',
temperature: 0,
tools: searchTool.functions.toolSpecs,
tool_choice: 'required'
})
const message = res.choices[0]!.message!
const toolCall = message.tool_calls![0]!
const tool = searchTool.functions.get(toolCall.function.name)!
const toolResult = await tool(toolCall.function.arguments)
const toolCall = message.tool_calls![0]!.function!
const toolResult = await searchTool.callTool(toolCall.name, toolCall.arguments)
console.log(toolResult)
`.trim()
}
`.trim(),
lang: 'ts'
}
return ''
case 'openai-responses':
return {
code: `
import { AgenticToolClient } from '@agentic/platform-tool-client'
import OpenAI from 'openai'
const openai = new OpenAI()
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
// This example uses OpenAI's newer Responses API
const res = await openai.responses.create({
model: 'gpt-4o-mini',
tools: searchTool.functions.responsesToolSpecs,
tool_choice: 'required',
input: [
{
role: 'user',
content: '${prompt}'
}
]
})
const toolCall = res.output[0]
const toolResult = await searchTool.callTool(toolCall.name, toolCall.arguments)
console.log(toolResult)
`.trim(),
lang: 'ts'
}
case 'langchain':
return {
code: `
import { createLangChainTools } from '@agentic/langchain'
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { ChatPromptTemplate } from '@langchain/core/prompts'
import { ChatOpenAI } from '@langchain/openai'
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents'
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const agent = createToolCallingAgent({
llm: new ChatOpenAI({ model: 'gpt-4o-mini' }),
tools: createLangChainTools(searchTool),
prompt: ChatPromptTemplate.fromMessages([
['placeholder', '{chat_history}'],
['human', '{input}'],
['placeholder', '{agent_scratchpad}']
])
})
const agentExecutor = new AgentExecutor({ agent, tools })
const result = await agentExecutor.invoke({
input: '${prompt}'
})
console.log(result.output)
`.trim(),
lang: 'ts'
}
case 'llamaindex':
return {
code: `
import { createLlamaIndexTools } from '@agentic/llamaindex'
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { openai } from '@llamaindex/openai'
import { agent } from '@llamaindex/workflow'
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const exampleAgent = agent({
llm: openai({ model: 'gpt-4o-mini', temperature: 0 }),
tools: createLlamaIndexTools(searchTool)
})
const response = await exampleAgent.run(
'${prompt}'
)
console.log(response.data.result)
`.trim(),
lang: 'ts'
}
case 'mastra':
return {
code: `
import { createMastraTools } from '@agentic/mastra'
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { openai } from '@ai-sdk/openai'
import { Agent } from '@mastra/core/agent'
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const exampleAgent = new Agent({
name: 'Example Agent',
model: openai('gpt-4o-mini') as any,
instructions: 'You are a helpful assistant. Be as concise as possible.',
tools: createMastraTools(searchTool)
})
const res = await exampleAgent.generate(
'${prompt}'
)
console.log(res.text)`.trim(),
lang: 'ts'
}
case 'firebase-genkit':
return {
code: `
import { createGenkitTools } from '@agentic/genkit'
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { genkit } from 'genkit'
import { gpt4oMini, openAI } from 'genkitx-openai'
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const ai = genkit({
plugins: [openAI()]
})
const result = await ai.generate({
model: gpt4oMini,
tools: createGenkitTools(ai, searchTool),
prompt: '${prompt}'
})
console.log(result)`.trim(),
lang: 'ts'
}
case 'xsai':
return {
code: `
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { createXSAITools } from '@agentic/xsai'
import { generateText } from 'xsai'
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const result = await generateText({
apiKey: process.env.OPENAI_API_KEY!,
baseURL: 'https://api.openai.com/v1/',
model: 'gpt-4o-mini',
tools: await createXSAITools(searchTool),
toolChoice: 'required',
messages: [
{
role: 'user',
content: '${prompt}'
}
]
})
console.log(JSON.stringify(result, null, 2))`.trim(),
lang: 'ts'
}
}
}
// export function getCodeForPythonFrameworkConfig({
// config,
// project,
// deployment,
// tool
// }: {
// config: DeveloperConfig
// project: Project
// deployment: Deployment
// identifier: string
// tool?: string
// }): string {
// return ''
// }
export function getCodeForPythonFrameworkConfig(_opts: {
config: DeveloperConfig
project: Project
deployment: Deployment
identifier: string
tool?: string
}): CodeSnippet {
return {
code: 'Python SDK is coming soon. For now, use the MCP or HTTP examples',
lang: 'md'
}
}
export function getCodeForHTTPConfig({
config,
@ -197,7 +364,7 @@ export function getCodeForHTTPConfig({
deployment: Deployment
identifier: string
tool?: string
}): string {
}): CodeSnippet {
tool ??= deployment.tools[0]?.name
assert(tool, 'tool is required')
// TODO: need a way of getting example tool args
@ -206,9 +373,15 @@ export function getCodeForHTTPConfig({
switch (config.httpTarget) {
case 'curl':
return `curl -X POST -H "Content-Type: application/json" -d '{"query": "example google search"}' ${url}`
return {
code: `curl -X POST -H "Content-Type: application/json" -d '{"query": "example google search"}' ${url}`,
lang: 'bash'
}
case 'httpie':
return `http -j ${url} query='example google search'`
return {
code: `http -j ${url} query='example google search'`,
lang: 'bash'
}
}
}

Wyświetl plik

@ -73,6 +73,12 @@ export class AgenticToolClient extends AIFunctionsProvider {
return this._functions
}
async callTool(toolName: string, args: string | Record<string, any>) {
const tool = this.functions.get(toolName)
assert(tool, `Tool "${toolName}" not found`)
return tool(typeof args === 'string' ? args : JSON.stringify(args))
}
/**
* Creates an Agentic tool client from a project or deployment identifier.
*
@ -84,8 +90,6 @@ export class AgenticToolClient extends AIFunctionsProvider {
* @example
* ```ts
* const searchTool = await AgenticToolClient.fromIdentifier('@agentic/search')
* const searchToolV1 = await AgenticToolClient.fromIdentifier('@agentic/search@v1.0.0')
* const searchToolLatest = await AgenticToolClient.fromIdentifier('@agentic/search@latest')
* ```
*/
static async fromIdentifier(

Wyświetl plik

@ -43,6 +43,8 @@
- consider using [neon serverless driver](https://orm.drizzle.team/docs/connect-neon) for production
- can this also be used locally?
- may need to update our `drizzle-orm` fork
- simplify `AgenticToolClient` and only require one package per TS LLM SDK
- `createAISDKToolsFromIdentifier(projectIdentifier)`
## TODO: Post-MVP