opncrafter
Module 10 of 10: Generative UI

Capstone Project: Stock Analysis Dashboard

Capstone Project: Stock Analysis Dashboard

Jan 3, 2026 β€’ 30 min read

Congratulations! You have learned the theory. Now let's build the application. In this final module, we combine everything into a single AI Agent: The Financial Analyst.

1. Project Requirements

The application must satisfy these user stories:

  • Search: "Compare AAPL and GOOGL revenue" β†’ Renders a Multi-Line Chart.
  • Analysis: "Why did the price drop?" β†’ Renders a Text Summary streaming via RSC.
  • Voice: User can speak to the dashboard to add new symbols.
  • Action: "Buy 10 shares" β†’ Renders a Confirmation Form β†’ Executes Transaction.

2. The Architecture Stack

Tech Stack

  • Framework: Next.js 14 (App Router)
  • AI SDK: Vercel AI SDK 3.0 (RSC)
  • Model: GPT-4o-mini (Fast & Cheap) or GPT-4o (Smart)
  • Components: Recharts, Radix UI, Tailwind
  • Database: Postgres (Prisma) for Chat History

Step 1: The Setup

npx create-next-app@latest financial-agent
npm install ai openai zod recharts lucide-react clsx tailwind-merge framer-motion

Step 2: The AI State Definition

We define our state to hold a history of `ReactNode` (UI) and `CoreMessage` (LLM context). We also sync this state to a database so the user can refresh the page.

// app/action.tsx
import { createAI, getMutableAIState } from 'ai/rsc';

export const AI = createAI({
  initialAIState: [],
  initialUIState: [],
  actions: { 
    submitUserMessage,
    confirmPurchase 
  },
  onSetAIState: async ({ state, done }) => {
    'use server';
    if (done) {
      await saveChatToDB(state);
    }
  }
});

Step 3: The Brain (submitUserMessage)

This is the orchestration layer. It uses `render()` (or `streamUI()` in newer versions) to map tools to components.

export async function submitUserMessage(content: string) {
  'use server';
  const aiState = getMutableAIState();
  aiState.update([...aiState.get(), { role: 'user', content }]);

  const ui = await render({
    model: 'gpt-4o',
    provider: openai,
    messages: aiState.get(),
    tools: {
      get_stock_history: {
        description: 'Get stock history for specific symbols',
        parameters: z.object({ symbols: z.array(z.string()) }),
        generate: async function* ({ symbols }) {
           yield <BotCard><Skeleton /></BotCard>;
           const data = await fetchValues(symbols);
           return <BotCard><StockChart data={data} /></BotCard>;
        }
      },
      buy_stock: {
        description: 'Purchase stock',
        parameters: z.object({ symbol: z.string(), quantity: z.number() }),
        generate: async function* ({ symbol, quantity }) {
            // Return a FORM, not a confirmation
            return <BotCard><PurchaseForm symbol={symbol} quantity={quantity} /></BotCard>;
        }
      }
    }
  });

  return { id: Date.now(), display: ui };
}

Step 4: Deployment

Deploy to Vercel. Ensure you set your `OPENAI_API_KEY`. Because we use RSC, we can easily deploy to Edge (with some limitations) or Serverless (Node.js). For heavy data fetching, Node.js runtime is preferred.

Conclusion

You have now built a Generative UI application. You understand that the future of interfaces isn't about pointing and clickingβ€”it's about intent. Your interface creates itself to meet the user's needs in real-time.

Course Complete! πŸŽ“

You are now a Generative UI Engineer.