Today we're going to see how to start with a basic AI chatbot and add in tools and custom UI components. For this tutorial we'll add a "music search tool" and a "music miniplayer" to give a chatbot musical abilities in about ten minutes.
But first, here's quick demo:
(Link to the code: jamesmurdza/my-music-player)
1. Create a New Tambo App
We'll start by creating a new Tambo app (Tambo is an open source generative UI framework):
npx tambo create-app my-music-player
cd my-music-player
npx tambo init
Once you have your Tambo project set up, you can delete a few of the template files from the example:
rm src/components/ui/card-data.tsx src/services/population-stats.ts
2. Define Music Types
We'll be using the Deezer API to search for music, so let's define a few types which we'll use later.
Put these into src/lib/types.ts
:
import { z } from "zod";
// This schema is used both the server function output and the props of the music card component:
export const songSchema = z.object({
artist: z.string().describe("Artist name"),
title : z.string().describe("Title"),
album: z.string().describe("Album name"),
duration: z.number().describe("Duration in seconds"),
preview: z.string().describe("Preview URL (30 seconds)"),
link: z.string().describe("Full song link"),
albumCover: z.string().optional().describe("Album cover URL"),
});
export type Song = z.infer<typeof songSchema>;
// This schema defines the server function input
export const searchMusicInputSchema = z.object({
query: z
.string()
.describe("Music search query (song title, artist name, or genre)"),
});
export type SearchMusicInput = z.infer<typeof searchMusicInputSchema>;
// This schema defines the server function input and output
export const searchMusicSchema = z.function().args(searchMusicInputSchema).returns(z.array(songSchema));
3. Create a Music Search Service
Now, let's define a NextJS server function to query the Deezer API. We'll use this later as a tool for the chatbot.
Save this to src/services/music-search.ts
:
"use server";
import { SearchMusicInput, Song } from "@/lib/types";
export async function searchMusic({ query }: SearchMusicInput): Promise<Song[]> {
const response = await fetch(
`https://api.deezer.com/search?q=${encodeURIComponent(query)}&limit=10`
);
const data = await response.json();
if (data.error) throw new Error(data.error);
// Map Deezer API results to MusicSearchResult type
return (data.data || []).map((item: any) => ({
...item,
artist: item.artist?.name || "",
album: item.album?.title || "",
albumCover: item.album?.cover || ""
}));
}
Note that we are using the same types which we defined in step 2.
4. Build the Music Miniplayer Component
For the next step, we'll design a music miniplayer React Component that looks something like this:
Put this in src/components/ui/music-card.tsx
:
import { z } from "zod";
import { useState, useEffect, useRef } from "react";
import { songSchema } from "@/lib/types";
export type MusicCardProps = z.infer<typeof songSchema>;
export function MusicCard({
title,
artist,
album,
preview,
link,
albumCover,
}: MusicCardProps) {
// ...
}
You can find the full code for this component here. Note that again, we are using the types from step 2.
5. Register Tools and Components with Tambo
The final step is to integrate both our music search tool and our miniplayer component into the AI chat template. That can be done simply by registering these elements (and their type definitions) with Tambo.
To do that, replace the entire contents of src/lib/tambo.ts
with this code:
"use client";
// Central configuration file for Tambo components and tools
// Read more about Tambo at https://tambo.co/docs
import { TamboComponent, TamboTool } from "@tambo-ai/react";
import { MusicCard } from "@/components/ui/music-card";
import { searchMusic } from "@/services/music-search";
import { songSchema, searchMusicSchema } from "@/lib/types";
// Tambo tools registered for AI use.
export const tools: TamboTool[] = [
{
name: "searchMusic",
description:
"Searches for music by song title, artist name, or any music-related query.",
tool: searchMusic,
toolSchema: searchMusicSchema,
},
];
// Tambo components registered for AI use.
export const components: TamboComponent[] = [
{
name: "MusicCard",
description: "A component that plays a song from Deezer.",
component: MusicCard,
propsSchema: songSchema,
},
];
Conclusion
That's it! Your app should work now (or let me know ๐). There's a lot more that can be added, such as a loading state for the miniplayer component and any additional tools or components you can think of.
Join the Tambo Discord to see more projects you can build with Tambo!
Top comments (1)
James, this is such a cool use of tambo! Thanks for sharing it.