fix: vercel
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
SUPABASE_SERVICE_ROLE_KEY=
|
||||
OPENAI_API_KEY=
|
||||
6
Les11-AI-SDK/lesbestanden/polderfest-ai-finished/.gitignore
vendored
Normal file
6
Les11-AI-SDK/lesbestanden/polderfest-ai-finished/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.next
|
||||
.env
|
||||
.env.local
|
||||
*.log
|
||||
.DS_Store
|
||||
14
Les11-AI-SDK/lesbestanden/polderfest-ai-finished/README.md
Normal file
14
Les11-AI-SDK/lesbestanden/polderfest-ai-finished/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Les 11 — Polderfest AI SDK Finished
|
||||
|
||||
Praat met je data: chat-interface over de Polderfest line-up via de Vercel AI SDK. Run schema.sql + seed-script + start dev server.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
cp .env.example .env.local
|
||||
# vul de .env.local in met je eigen keys
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000.
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
|
||||
export default function Chat() {
|
||||
const { messages, input, handleInputChange, handleSubmit } = useChat();
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2 max-h-[60vh] overflow-y-auto rounded border p-3">
|
||||
{messages.map((m) => (
|
||||
<div key={m.id} className={m.role === "user" ? "text-right" : ""}>
|
||||
<span className="inline-block rounded-lg bg-zinc-100 px-3 py-2 text-sm">{m.content}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="flex gap-2">
|
||||
<input value={input} onChange={handleInputChange} className="flex-1 rounded border p-2" placeholder="Stel een vraag..." />
|
||||
<button className="rounded bg-blue-600 px-4 py-2 text-white">Verstuur</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { streamText, convertToModelMessages } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { createSupabaseServerClient } from "@/lib/supabase-server";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json();
|
||||
const supabase = await createSupabaseServerClient();
|
||||
const { data: bands } = await supabase.from("bands").select("name, genre, country, stage, time");
|
||||
const context = (bands ?? []).map((b) => `${b.name} (${b.genre}, ${b.country}) — ${b.stage} ${b.time}`).join("\n");
|
||||
|
||||
const result = streamText({
|
||||
model: openai("gpt-4o-mini"),
|
||||
system: `Je bent een hulpvaardige festival-roadie voor Polderfest 2027.\nLine-up:\n${context}`,
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@import 'tailwindcss';
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = { title: "Polderfest 2027", description: "Polderfest 2027" };
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="nl">
|
||||
<body className="min-h-screen bg-white text-zinc-900 antialiased">
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import Chat from "./Chat";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="mx-auto max-w-2xl p-8">
|
||||
<h1 className="text-4xl font-bold mb-2">Polderfest 2027</h1>
|
||||
<p className="text-zinc-600 mb-6">Stel vragen over de 500 bands van het festival.</p>
|
||||
<Chat />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createServerClient } from "@supabase/ssr";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export async function createSupabaseServerClient() {
|
||||
const cookieStore = await cookies();
|
||||
return createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
cookies: {
|
||||
getAll() { return cookieStore.getAll(); },
|
||||
setAll() {},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
2
Les11-AI-SDK/lesbestanden/polderfest-ai-finished/next-env.d.ts
vendored
Normal file
2
Les11-AI-SDK/lesbestanden/polderfest-ai-finished/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { NextConfig } from 'next';
|
||||
const config: NextConfig = {};
|
||||
export default config;
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "polderfest-ai-finished",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.0.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"@supabase/supabase-js": "^2.46.0",
|
||||
"@supabase/ssr": "^0.6.0",
|
||||
"ai": "^6.0.0",
|
||||
"@ai-sdk/openai": "^2.0.0",
|
||||
"@ai-sdk/react": "^2.0.0",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"postcss": "^8.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default { plugins: { '@tailwindcss/postcss': {} } };
|
||||
@@ -0,0 +1,8 @@
|
||||
create table if not exists bands (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
name text not null,
|
||||
genre text,
|
||||
country text,
|
||||
stage text,
|
||||
time text
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Seed-script voor Polderfest 2027.
|
||||
* Run: pnpm tsx scripts/seed-polderfest.ts
|
||||
*/
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
const supabase = createClient(url, serviceKey);
|
||||
|
||||
const genres = ["indie", "punk", "techno", "folk", "hiphop", "metal", "jazz", "ambient", "house", "drum&bass"];
|
||||
const countries = ["NL", "BE", "DE", "FR", "UK", "US", "ES", "IT", "SE", "DK"];
|
||||
const stages = ["Main", "Tent A", "Tent B", "Acoustic Field", "Late Night"];
|
||||
|
||||
const bands = Array.from({ length: 500 }, (_, i) => ({
|
||||
name: `Band ${i + 1} of the ${["Polder", "North Sea", "Lowland", "Delta"][i % 4]}`,
|
||||
genre: genres[i % genres.length],
|
||||
country: countries[i % countries.length],
|
||||
stage: stages[i % stages.length],
|
||||
time: `${10 + (i % 14)}:00`,
|
||||
}));
|
||||
|
||||
const { error } = await supabase.from("bands").insert(bands);
|
||||
if (error) console.error(error); else console.log("Seeded 500 bands");
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user