I'm trying to make a AI to generate UI components based on the user input such as vercel did in their latest chat example here. Mixtral supports function calling but it seems that it is not working as it should with the render
function from vercel AI SDK: import { render } from "ai/rsc";
In the following code, I tried to do as in the documentation and to follow their actual repository from the AI chat example.
import "server-only";
import { Message } from "@/components/message";
import { getMutableAIState, render, createAI } from "ai/rsc";
import OpenAI from "openai";
import { PiSpinnerGap } from "react-icons/pi";
import { z } from "zod";
import { sleep } from "@/lib/utils";
const togetherai = new OpenAI({
apiKey: process.env.TOGETHER_AI_API_KEY,
baseURL: "https://api.together.xyz/v1",
});
//export const runtime = "edge";
async function getWeather() {
return {weather: "30º"}
}
async function submitUserMessage(input: string) {
"use server";
const aiState = getMutableAIState();
// Update AI state with new message.
aiState.update([
...aiState.get(),
{
role: "user",
content: input,
},
]);
const ui = render({
provider: togetherai,
model: "mistralai/Mixtral-8x7B-Instruct-v0.1",
messages: [
{
role: "system",
content: `You are a helpful assistant that can access external functions when the user asks for. Your name is MistralAI.
If the user asks for the weather and passes the location, call \`get_weather_info\` to show current weather at that location.`,
},
{
role: "user",
content: input,
},
],
text: ({ content, done }) => {
if (done) {
aiState.done([
...aiState.get(),
{
role: "assistant",
content,
},
]);
}
return (
<div className="flex w-full">
<Message from="ai">{content}</Message>
</div>
);
},
initial: (
<div>
<PiSpinnerGap className="animate-spin text-muted" size={25} />
</div>
),
functions: {
getWeatherInfo: {
description:
"Get the information for the weather according to a certain location.",
parameters: z
.object({
location: z
.string()
.describe("The location to get the weather from."),
})
.required(),
render: async function* ({ location }) {
yield (
<div>
<PiSpinnerGap className="animate-spin text-muted" size={25} />
</div>
);
//can call from other func to get information from an external api
const weatherInfo = await getWeather();
await sleep(1000);
aiState.done([
...aiState.get(),
{
role: "function",
name: "getWeatherInfo",
content: JSON.stringify(weatherInfo),
},
]);
return (
<div className="bg-red-500">
<h1>
The weather in <span className="font-bold">{location}</span>
</h1>
<div>is {weatherInfo.weather}</div>
</div>
);
},
},
},
});
return {
id: Date.now(),
display: ui,
};
}
// Define the initial state of the AI. It can be any JSON object.
const initialAIState: {
role: "user" | "assistant" | "system" | "function";
content: string;
id?: string;
name?: string;
}[] = [];
// The initial UI state that the client will keep track of, which contains the message IDs and their UI nodes.
const initialUIState: {
id: number;
display: React.ReactNode;
}[] = [];
export const AI = createAI({
actions: {
submitUserMessage,
},
initialAIState,
initialUIState,
});
But it is giving me the following error:
The problem only occurs when the AI actually tries to call the function:
Here is the client side
import { AI } from "../action";
import { GenerativeUIChat } from "./fragments/generative-ui-chat";
export default function GenerativeUIPage() {
return (
<AI>
<GenerativeUIChat />
</AI>
);
}
Here is the GenerativeUIChat
component:
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { PiPaperPlaneTilt, PiRobotThin } from "react-icons/pi";
import { useUIState, useAIState, useActions } from "ai/rsc";
import { useState } from "react";
import { AI } from "@/app/action";
import { Message } from "@/components/message";
export function GenerativeUIChat() {
const [messages, setMessages] = useUIState<typeof AI>();
const { submitUserMessage } = useActions<typeof AI>();
// const [aiState, setAIState] = useAIState<typeof AI>();
const [input, setInput] = useState<string>('');
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// Add user message to UI state
setMessages((curr) => [
...curr,
{
id: Date.now(),
display: (
<div className="flex w-full justify-end">
<Message from="user">{input}</Message>
</div>
),
},
]);
// Submit and get response message
const responseMessage = await submitUserMessage(input);
setMessages((currentMessages) => [...currentMessages, responseMessage]);
setInput('');
}
return (
<div className="flex flex-col w-full max-w-xl px-4 h-[calc(100vh-4rem)] justify-between items-center mx-auto">
<div className="flex flex-col w-full max-w-xl max-h-[calc(100%-4.5rem)] pt-6">
<span className="w-full text-center text-sm text-muted">Mistral</span>
{messages.length === 0 ? (
<div className="flex flex-col gap-8 w-full items-center">
<span className="text-2xl font-semibold text-center">
Start a conversation with the AI.
</span>
<PiRobotThin size={100} />
</div>
) : (
<div
className={cn(
"[&::-webkit-scrollbar]:w-[0.35rem] [&::-webkit-scrollbar-track]:bg-accent [&::-webkit-scrollbar-thumb]:bg-primary [&::-webkit-scrollbar-thumb]:rounded-lg [&::-webkit-scrollbar-thumb:hover]:bg-primary/50",
"p-2 px-6 pr-3 flex flex-col gap-4 border border-input rounded-lg mb-2 overflow-auto shadow-sm shadow-black/30 transition duration-300 hover:shadow-lg"
)}
>
{
// View messages in UI state
messages.map((message) => message.display)
}
</div>
)}
</div>
<form className="w-full" onSubmit={handleSubmit}>
<div className="flex gap-2 w-full py-4">
<Input
className="p-2 border border-input rounded shadow-sm bg-background"
value={input}
placeholder="Say something..."
onChange={(event) => {
setInput(event.target.value);
}}
/>
<Button size="icon">
<PiPaperPlaneTilt size={20} />
</Button>
</div>
</form>
</div>
);
}
Any solutions or sugestions are welcome 😁.
I've solved this problem, if anybody have trouble with this you can check out my repository here and see the documentation about Generative UI
.
The main problem that I had was with the streaming, which is now disabled and works fine, you can also use Vercel's Google Gemini Chatbot as a source of information like I did.