fix: vercel

This commit is contained in:
2026-06-09 18:02:04 +02:00
parent ee69da1665
commit c023192113
85 changed files with 10266 additions and 3008 deletions

View File

@@ -0,0 +1,4 @@
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
OPENAI_API_KEY=

View File

@@ -0,0 +1,6 @@
node_modules
.next
.env
.env.local
*.log
.DS_Store

View 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.

View File

@@ -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>
);
}

View File

@@ -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();
}

View File

@@ -0,0 +1 @@
@import 'tailwindcss';

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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() {},
},
}
);
}

View File

@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

View File

@@ -0,0 +1,3 @@
import type { NextConfig } from 'next';
const config: NextConfig = {};
export default config;

View File

@@ -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"
}
}

View File

@@ -0,0 +1 @@
export default { plugins: { '@tailwindcss/postcss': {} } };

View File

@@ -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
);

View File

@@ -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");

View File

@@ -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"
]
}