En AI-driven webbapplikation byggd med Next.js och TypeScript som hjälper användare att hitta filmer baserat på naturliga språkprompter.
Appen använder artificiell intelligens för att analysera användarens preferenser och ge personliga filmrekommendationer.
- Next.js 14 – React-ramverk med App Router
- TypeScript – Typsäkerhet genom hela applikationen
- Google Gemini 1.5 Flash – För filmrekommendationer
- Delade TypeScript-typer – Konsistenta datastrukturer mellan frontend och backend
/types/
└── shared.ts # Delade TypeScript-typer
/app/
├── api/generate/
│ └── route.ts # API-rutter för AI-integration
├── page.tsx # Huvudsida med sökformulär
└── layout.tsx # Layout-komponent
/lib/
└── client.ts # Typade API-anrop
/validation/
└── schemas.ts # Zod-schema för validering
Projektet använder starka typer för att säkerställa konsistens:
// Filmstruktur
interface Movie {
title: string;
year: number;
genre: string;
}
// API-förfrågan
interface PromptRequest {
prompt: string;
}
// AI-svar med rekommendationer
interface AIResponse {
recommendations: Movie[];
final_recommendation: Movie;
}
// Komplett API-respons med felhantering
interface MovieResponse {
response: boolean;
parsedOutPut?: AIResponse;
error?: string;
}
// OMDB API-svar för filmdetaljer
interface OmdbMovieDetails {
Poster?: string;
Plot?: string;
Director?: string;
Actors?: string;
imdbRating?: string;
}
// UI-state hantering med diskriminerad union
type UiState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'error', message: string }
| { status: 'success', movies: Movie[], finalPick: Movie | null };
- Klona projektet
git clone https://github.com/Dantilldev/ai-movie-recommender.git
cd ai-movie-finder
- Installera dependencies
npm install
- Konfigurera miljövariabler
cp .env.example .env.local
Fyll i nödvändiga API-nycklar:
[AI_API_KEY]=your_api_key_here
- Starta utvecklingsservern
npm run dev
- Öppna applikationen Navigera till http://localhost:3000
- Skriv din filmpreferens - Beskriv vad du är i stämning för (t.ex. "Jag vill se en spännande sci-fi film från 90-talet")
- Få rekommendationer - AI:n analyserar din förfrågan och ger flera filmförslag
- Se huvudrekommendationen - Få en särskilt framhävd film som bäst matchar dina preferenser
Skicka en prompt och få filmrekommendationer.
{
"prompt": "Jag vill se en rolig komedi för helgmys"
}
{
"response": true,
"parsedOutPut": {
"recommendations": [
{
"title": "The Grand Budapest Hotel",
"year": 2014,
"genre": "Comedy"
}
],
"final_recommendation": {
"title": "The Grand Budapest Hotel",
"year": 2014,
"genre": "Comedy"
}
}
}
Applikationen använder en diskriminerad union-typ för att hantera följande UI-tillstånd:
// UI-state för applikationen
export type UiState =
| {status: "idle"}
| {status: "loading"}
| {status: "error"; message: string}
| {status: "success"; movies: Movie[]; finalPick: Movie | null};Detta gör att vi kan hantera alla tillstånd på ett typesäkert sätt:
- idle - Starttillstånd med fokuserat textfält och uppmuntrande text
- loading - Visar spinner och "Generating..." under AI-bearbetning
- success - Visar rekommendationer och final pick
- error - Visar felmeddelande med "Try again"-knapp för återhämtning
- Automatisk fokus på textfält när sidan laddas
- Loading spinner för visuell feedback
- Felhantering med tydliga meddelanden
- Responsiv design som fungerar på alla enheter
- Gradient-bakgrund för filmtemakänsla
Projektet använder Zod för att validera data och säkerställa att inkommande API-förfrågningar matchar de definierade TypeScript-typerna.
Exempel (schemas.ts):
import {z} from "zod";
// Zod-schema för validering av en film
export const MovieSchema = z.object({
title: z.string(),
year: z.number(),
genre: z.string(),
});
// Zod-schema för validering av prompt
export const PromptRequestSchema = z.object({
prompt: z.string(),
});
// Zod-schema för validering av API-svar
export const AIResponseSchema = z.object({
recommendations: z.array(MovieSchema),
final_recommendation: MovieSchema,
});npm run type-check # TypeScript-kontroll
npm run lint # Linting
- Schema/typer: [Namn] - Definierade delade TypeScript-interfaces
- API-integratör: [Namn] - Implementerade Gemini API-integration
- UI-byggare: [Namn] - Skapade komponenter och Tailwind-design
- State/UX: [Namn] - Implementerade UI-states och användarflöde
- Test/Docs: [Selena Oerding] - README, demo-script och dokumentation
Detta projekt är skapat som en del av en gruppuppgift i Next.js och TypeScript.
- Intro & Setup (1 min)
- Live Demo (3–4 min)
- Kodgenomgång (2–3 min)
- Q&A (valfritt)
"Hej! Vi ska presentera vårt AI Movie Finder-projekt – en Next.js-app med TypeScript som använder Google Gemini för att ge personliga filmrekommendationer."
- Tema: AI-driven filmrekommenderare
- Teknologier: Next.js 14, TypeScript, Google Gemini API, Tailwind CSS
- Fokus: Delade typer mellan frontend och backend
"Vi var 5 personer med olika roller:"
- Schema/typer: Definierade våra interfaces
- API-integratör: Kopplade upp Gemini
- UI-byggare: Skapade den snygga designen
- State/UX: Fixade alla UI-states
- Test/Docs: README och detta demo
- Öppna
http://localhost:3000 - Visa UI:
- "Notera den cinematiska gradient-bakgrunden"
- "Textfältet får automatiskt fokus för smidig UX"
- "Vi har en 'Show favorites'-knapp för sparade filmer"
- Skriv:
"Jag vill se en rolig komedi för helgmys" - Klicka Get
- Visa loading state:
- "Notera loading spinner och 'Generating...'-texten"
- Visa resultat:
- "Vi får flera rekommendationer i listan"
- "Plus en 'Final Pick' som AI:n anser bäst matchar"
- "Varje film har titel, år och genre – våra TypeScript-typer"
- Skriv:
"En mörk thriller från 2000-talet som utspelar sig i framtiden" - Visa process och diskutera resultat:
- "AI:n förstår flera kriterier samtidigt – genre, tidsperiod, setting"
- Klicka hjärt-ikonen på en film
- Gå till Show favorites ✅
- Visa favoritlistan
- Simulera fel (koppla ner internet eller ogiltig API-nyckel)
- Visa error state:
- "Tydligt felmeddelande med 'Try again'-knapp"
Visa /types/shared.ts:
// Filmstruktur
export interface Movie {
title: string;
year: number;
genre: string;
}
// API-strukturer
export interface PromptRequest {
prompt: string;
}
export interface AIResponse {
recommendations: Movie[];
final_recommendation: Movie;
}
export interface MovieResponse {
response: boolean;
parsedOutPut?: AIResponse;
error?: string;
}
// OMDB API respons
export interface OmdbMovieDetails {
Poster?: string;
Plot?: string;
Director?: string;
Actors?: string;
imdbRating?: string;
}
// UI-state hantering
export type UiState =
| {status: "idle"}
| {status: "loading"}
| {status: "error"; message: string}
| {status: "success"; movies: Movie[]; finalPick: Movie | null};Visa /app/api/generate/route.ts (om möjligt):
- POST-rutt som tar
PromptRequest - Validering av input med Zod
- Anrop till Gemini API
- Validering av AI-svar med Zod
- Returnerar typad
MovieResponse
// Exempel på faktisk API-rutt (förenklad)
import {GoogleGenerativeAI} from "@google/generative-ai";
import {AIResponseSchema, PromptRequestSchema} from "@/validation/schemas";
export async function POST(request: Request) {
try {
// Validera inkommande data
const body = await request.json();
const validBody = PromptRequestSchema.parse(body);
const prompt = validBody.prompt || "";
// Anropa AI:n
const result = await model.generateContent(prompt);
const cleaned = result.response
.text()
.replace(/```[a-z]*|```/g, "")
.trim();
// Parsa och validera svaret
const parsed = JSON.parse(cleaned);
const validData = AIResponseSchema.parse(parsed);
// Returnera framgångsrikt svar
return Response.json({
response: true,
parsedOutPut: validData,
});
} catch (error) {
// Felhantering
return Response.json(
{
response: false,
error: "Kunde inte hämta filmer",
},
{status: 500}
);
}
}Visa huvuddelar av /app/page.tsx:
// State management med ui state union
const [prompt, setPrompt] = useState("");
const [uiState, setUiState] = useState<UiState>({status: "idle"});
// Hantering av UI-state vid API-anrop
const handleGenerate = async () => {
setUiState({status: "loading"});
try {
const response = await fetchMovieRec(prompt);
if (!response.response) {
setUiState({
status: "error",
message: response.error || "Something went wrong",
});
return;
}
setUiState({
status: "success",
movies: response.parsedOutPut.recommendations,
finalPick: response.parsedOutPut.final_recommendation,
});
} catch (err) {
setUiState({
status: "error",
message: "Something went wrong. Please try again.",
});
}
};"Typad UI-state med diskriminerad union för säkerhet"
Genom att använda diskriminerad union för UI-states får vi en tydlig och typesäker hantering:
{/* Idle state */}
{uiState.status === 'idle' && (
<p>Enter your preferences above to get movie recommendations!</p>
)}
{/* Loading state */}
{uiState.status === 'loading' && (
<span className="loading-spinner"></span>
)}
{/* Error state */}
{uiState.status === 'error' && (
<div className="error-message">
<span>{uiState.message}</span>
<button>Try again</button>
</div>
)}
{/* Success state */}
{uiState.status === 'success' && (
<>
<div className="movies-list">
{uiState.movies.map(movie => (/* Movie rendering */)}
</div>
{uiState.finalPick && (/* Final pick rendering */)}
</>
)}"Vad gjorde vi bra med TypeScript?"
- ✅ Delade typer – Samma interfaces används i frontend och backend.
- ✅ Zod runtime-validering –
PromptRequestSchemaochAIResponseSchemasäkerställer korrekt data. - ✅ Diskriminerad union – UiState-typen ger typsäker hantering av applikationens tillstånd.
- ✅ Props typing – Komponenter som MovieDetails har typade props (MovieDetailsProps).
- ✅ Typade API-anrop – Typsäker kommunikation med både Gemini och OMDB API.
- ✅ Eliminering av any – OmdbMovieDetails-interfacet ersätter
anyför bättre typsäkerhet.