Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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.
2 changes: 1 addition & 1 deletion apphosting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ env:
- variable: MAX_REQUESTS_HOURLY
value: "50"
- variable: SITE_ORIGIN
value: https://examples.genkit.dev
value: https://genkit.dev/examples
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
7 changes: 4 additions & 3 deletions src/app/api/og/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { NextRequest } from "next/server";

export const runtime = "edge";

// Edge runtime doesn't support all Node.js APIs, so we inline the constant
const SITE_ORIGIN = process.env.SITE_ORIGIN || "http://localhost:3000/examples";

export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
Expand All @@ -39,9 +42,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}`;
}