Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Genkit by Example

This repo contains the source code for the examples at [examples.genkit.dev](https://examples.genkit.dev). It is intended as an educational resource for developers wanting to add GenAI to their applications using Genkit.
This repo contains the source code for the examples at [genkit.dev/examples](https://genkit.dev/examples). It is intended as an educational resource for developers wanting to add GenAI to their applications using Genkit.
6 changes: 5 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
basePath: '/examples',
images: {
loader: 'custom',
loaderFile: './src/lib/image-loader.ts',
},
};

export default nextConfig;
3 changes: 2 additions & 1 deletion src/app/action-context/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ import React, { useContext } from "react";
import Chat from "@/components/chat";
import DemoConfig from "@/lib/demo-config";
import CodeBlock from "@/components/code-block";
import { withBasePath } from "@/lib/constants";

export default function ActionContextDemoApp() {
const { config } = useContext(DemoConfig);

return (
<Chat
endpoint="/action-context/api"
endpoint={withBasePath("/action-context/api")}
renderPart={(part, info) => {
if (info.message.role === "system") return <></>;
switch (part.toolResponse?.name) {
Expand Down
5 changes: 2 additions & 3 deletions src/app/api/og/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { ImageResponse } from "@vercel/og";
import { NextRequest } from "next/server";
import { SITE_ORIGIN } from "@/lib/constants";

export const runtime = "edge";

Expand All @@ -39,9 +40,7 @@ export async function GET(req: NextRequest) {
padding: "40px",
color: "white",
fontFamily: "Nunito Sans", // Use Nunito Sans font (default)
backgroundImage: `url(${
process.env.SITE_ORIGIN || "http://localhost:3000"
}/og_bg.png)`,
backgroundImage: `url(${SITE_ORIGIN}/og_bg.png)`,
}}
>
<h1
Expand Down
3 changes: 2 additions & 1 deletion src/app/chatbot-hitl/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Answer, Question } from "./schema";
import type { MessageData, Part, ToolResponsePart } from "genkit";
import useAgent from "@/lib/use-agent";
import QuestionForm from "./question-form";
import { withBasePath } from "@/lib/constants";

function findResponse(
request: Part,
Expand All @@ -18,7 +19,7 @@ function findResponse(
}

export default function HitlChatbotApp() {
const agent = useAgent({ endpoint: "/chatbot-hitl/api" });
const agent = useAgent({ endpoint: withBasePath("/chatbot-hitl/api") });

return (
<Chat
Expand Down
3 changes: 2 additions & 1 deletion src/app/chatbot-simple/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import Demo from "@/components/demo";
import SimpleChatbotConfig from "./config";
import Chat from "@/components/chat";
import { demoMetadata } from "@/lib/demo-metadata";
import { withBasePath } from "@/lib/constants";

export const generateMetadata = demoMetadata("chatbot-simple");

export default async function Page() {
return (
<Demo id="chatbot-simple" Config={SimpleChatbotConfig}>
<Chat endpoint="/chatbot-simple/api" />
<Chat endpoint={withBasePath("/chatbot-simple/api")} />
</Demo>
);
}
7 changes: 4 additions & 3 deletions src/app/image-analysis/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import ImageField from "./image-field";
import HighlightArea from "./highlight-area";
import DemoConfig from "@/lib/demo-config";
import useAgent from "@/lib/use-agent";
import { withBasePath } from "@/lib/constants";

function fileToDataUri(file: File): Promise<string> {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -57,8 +58,8 @@ export default function ImageAnalysisApp() {

// Sample images
const sampleImages = [
{ name: "Bird in Tree", path: "/image-analysis/bird_in_tree.png" },
{ name: "Desk Items", path: "/image-analysis/desk_items.jpg" },
{ name: "Bird in Tree", path: withBasePath("/image-analysis/bird_in_tree.png") },
{ name: "Desk Items", path: withBasePath("/image-analysis/desk_items.jpg") },
];

function borderColor(o: Partial<ImageObject>) {
Expand All @@ -80,7 +81,7 @@ export default function ImageAnalysisApp() {
const analysis = await simplePost<
{ imageUrl: string; system: any },
Analysis
>("/image-analysis/api", {
>(withBasePath("/image-analysis/api"), {
system: config?.system,
imageUrl: await fileToDataUri(file),
});
Expand Down
3 changes: 2 additions & 1 deletion src/app/mcp/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import React from "react";
import Chat from "@/components/chat";
import { Sun, Cloud, CloudRain, CloudSnow } from "lucide-react";
import Dice from "./dice-roll";
import { withBasePath } from "@/lib/constants";

function WeatherResponse({
temperature,
Expand Down Expand Up @@ -78,7 +79,7 @@ function DiceResponse({ output }: { output: number }) {
export default function ToolCallingChatbotApp() {
return (
<Chat
endpoint="/mcp/api"
endpoint={withBasePath("/mcp/api")}
renderPart={(part) => {
if (part.toolResponse?.name === "getWeather")
return <WeatherResponse {...(part.toolResponse.output as any)} />;
Expand Down
5 changes: 2 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ import { demos } from "@/data";
import { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { SITE_ORIGIN } from "@/lib/constants";

export const metadata: Metadata = {
title: "Add GenAI to your web/mobile app",
description:
"Pre-built examples of using Genkit to build a variety of AI-powered features for Next.js applications.",
openGraph: {
images: [
`${
process.env.SITE_ORIGIN || "http://localhost:3000"
}/api/og?title=Practical%20GenAI%20 Demos`,
`${SITE_ORIGIN}/api/og?title=Practical%20GenAI%20 Demos`,
],
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/app/robots.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
User-Agent: *
Allow: /

Sitemap: https://examples.genkit.dev/sitemap.xml
Sitemap: https://genkit.dev/examples/sitemap.xml
6 changes: 4 additions & 2 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { demos } from "@/data";
import type { MetadataRoute } from "next";
import { SITE_ORIGIN } from "@/lib/constants";

const latestUpdate = demos
.map((d) => d.added)
Expand All @@ -8,15 +9,16 @@ const latestUpdate = demos
.at(-1);

export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = SITE_ORIGIN;
return [
{
url: "https://examples.genkit.dev",
url: baseUrl,
lastModified: latestUpdate,
changeFrequency: "daily",
priority: 1,
},
...demos.map((d) => ({
url: `https://examples.genkit.dev/${d.id}`,
url: `${baseUrl}/${d.id}`,
lastModified: d.added,
changeFrequency: "weekly" as const,
priority: 0.8,
Expand Down
3 changes: 2 additions & 1 deletion src/app/structured-output/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { CharacterSheet } from "./schema";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import React from "react";
import { withBasePath } from "@/lib/constants";

export default function StructuredOutputApp() {
const [prompt, setPrompt] = useState<string>("");
Expand All @@ -47,7 +48,7 @@ export default function StructuredOutputApp() {
const stream = post<
{ prompt: string },
{ output: Partial<CharacterSheet> }
>("/structured-output/api", {
>(withBasePath("/structured-output/api"), {
prompt,
});
for await (const chunk of stream) {
Expand Down
3 changes: 2 additions & 1 deletion src/app/tool-calling/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import React from "react";
import Chat from "@/components/chat";
import { Sun, Cloud, CloudRain, CloudSnow } from "lucide-react";
import Dice from "./dice-roll";
import { withBasePath } from "@/lib/constants";

function WeatherResponse({
temperature,
Expand Down Expand Up @@ -78,7 +79,7 @@ function DiceResponse({ output }: { output: number }) {
export default function ToolCallingChatbotApp() {
return (
<Chat
endpoint="/tool-calling/api"
endpoint={withBasePath("/tool-calling/api")}
renderPart={(part) => {
if (part.toolResponse?.name === "getWeather")
return <WeatherResponse {...(part.toolResponse.output as any)} />;
Expand Down
40 changes: 40 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Base path for the application - must match next.config.ts basePath
*/
export const BASE_PATH = "/examples";

/**
* Default base URL for the application (used in development)
* In production, SITE_ORIGIN env var should be set in apphosting.yaml
*/
export const DEFAULT_BASE_URL = `http://localhost:3000${BASE_PATH}`;

/**
* Get the site origin (production or development)
*/
export const SITE_ORIGIN = process.env.SITE_ORIGIN || DEFAULT_BASE_URL;

/**
* Helper to create paths with the basePath prefix
* Use this for any hardcoded paths that need the basePath added
*/
export function withBasePath(path: string): string {
return `${BASE_PATH}${path}`;
}

5 changes: 2 additions & 3 deletions src/lib/demo-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { findDemo } from "@/data";
import { Metadata } from "next";
import { SITE_ORIGIN } from "./constants";

export function demoMetadata(id: string): () => Promise<Metadata> {
const demo = findDemo(id);
Expand All @@ -26,9 +27,7 @@ export function demoMetadata(id: string): () => Promise<Metadata> {
description: demo.description,
openGraph: {
images: [
`${process.env.SITE_ORIGIN || "http://localhost:3000"}/api/og?title=${
demo.name
}`,
`${SITE_ORIGIN}/api/og?title=${demo.name}`,
],
description: demo.description,
title: `Genkit by Example - ${demo.name}`,
Expand Down
24 changes: 24 additions & 0 deletions src/lib/image-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* TODO: Ran into issues with builtin NextJS image optimization serving the wrong image after configuring custom basePath.
* Custom loader lets us control that but turns off the automatic optimziation.
*/
export default function imageLoader({ src }: { src: string }) {
return `/examples${src}`;
}