In this lab, you'll create an app that generates recipes from pictures of baked goods using AI. You'll learn about:
- Working with Ionic components
- Handling images
- Using AI services
- Creating Angular services
Here's how our app will be structured:
graph TD
A[Baking App] --> B[Home Page]
B --> C[UI Components]
B --> D[AI Integration]
C --> E[Image Selection]
C --> F[Recipe Display]
D --> G[Image Processing]
D --> H[Gemini AI Service]
H --> I[Recipe Generation]
This diagram shows how data moves through our application:
graph LR
A[Template] -->|User Input| B[Component]
B -->|Image Selection| C[Image Processing]
C -->|Base64| D[AI Service]
D -->|Recipe| B
B -->|Display| A
ionic start w7-lab-ai blank --type=angularSelect 'Yes' when asked about using Angular standalone components.
cd w7-lab-ai
npm install @google/generative-aiAppend the following lines to the bottom of the .gitignore file. This is very important, as it prevents your API key from being saved to GitHub where bad actors could use it maliciously.
# Environment files
/src/environments/environment.tsUpdate the src/environments/environment.ts file with your API key.
export const environment = {
production: false,
apiKey: 'YOUR-API-KEY-HERE' // Your instructor will provide this
};You can get an API key from Google AI Studio
sequenceDiagram
participant U as User
participant C as Component
participant S as Service
participant AI as Gemini AI
U->>C: Select Image
U->>C: Enter Prompt
U->>C: Click Generate
C->>S: Process Image
S->>AI: Send Request
AI->>S: Return Recipe
S->>C: Return Text
C->>U: Display Recipe
Open src/app/home/home.page.ts and replace its contents with:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonHeader, IonToolbar, IonTitle, IonContent,
IonGrid, IonRow, IonCol, IonCard, IonCardContent,
IonCardHeader, IonCardTitle, IonItem, IonLabel,
IonButton, IonIcon, IonProgressBar, IonText,
IonRadioGroup, IonRadio, IonImg, IonTextarea,
IonRippleEffect } from '@ionic/angular/standalone';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { environment } from '../../environments/environment';
@Component({
selector: 'app-home',
templateUrl: './home.page.html',
standalone: true,
imports: [
// TODO: Add all the Ionic components from the imports above
// HINT: Copy each component name from the imports list
CommonModule,
FormsModule,
// YOUR CODE HERE
]
})
export class HomePage {
// TODO: Add default prompt
// HINT: Something like "Provide a recipe for these baked goods"
prompt = '';
output = '';
isLoading = false;
availableImages = [
{ url: 'assets/images/baked_goods_1.jpg', label: 'Baked Good 1' },
{ url: 'assets/images/baked_goods_2.jpg', label: 'Baked Good 2' },
{ url: 'assets/images/baked_goods_3.jpg', label: 'Baked Good 3' }
];
selectedImage = this.availableImages[0].url;
get formattedOutput() {
return this.output.replace(/\n/g, '<br>');
}
selectImage(url: string) {
// TODO: Set the selectedImage property
// HINT: this.selectedImage = url;
}
}Replace src/app/home/home.page.html with:
<ion-header>
<ion-toolbar color="primary">
<ion-title>Baking with Gemini</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<form (ngSubmit)="onSubmit()">
<!-- Image selection grid -->
<ion-grid>
<ion-row>
@for (img of availableImages; track img.url) {
<ion-col size="12" sizeMd="4">
<ion-card class="ion-activatable" (click)="selectImage(img.url)">
<!-- TODO: Add ion-img -->
<!-- HINT:
<ion-img [src]="img.url"></ion-img>
-->
<ion-ripple-effect></ion-ripple-effect>
<ion-card-content>
<!-- TODO: Add radio button group -->
<!-- HINT:
<ion-radio-group [(ngModel)]="selectedImage" name="imageSelect">
<ion-item lines="none">
<ion-radio [value]="img.url" slot="start"></ion-radio>
<ion-label>{{ img.label }}</ion-label>
</ion-item>
</ion-radio-group>
-->
</ion-card-content>
</ion-card>
</ion-col>
}
</ion-row>
</ion-grid>
<ion-card>
<ion-card-content>
<!-- TODO: Add textarea -->
<!-- HINT:
<ion-textarea
[(ngModel)]="prompt"
name="prompt"
label="Instructions"
labelPlacement="floating"
[autoGrow]="true"
rows="2"
class="ion-margin-bottom"
></ion-textarea>
-->
<ion-button
type="submit"
expand="block"
class="ion-margin-top"
[disabled]="isLoading"
>
<ion-icon name="cafe-outline" slot="start"></ion-icon>
{{ isLoading ? 'Generating...' : 'Generate Recipe' }}
</ion-button>
</ion-card-content>
</ion-card>
@if (output) {
<ion-card>
<ion-card-header>
<ion-card-title>Generated Recipe</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-text [innerHTML]="formattedOutput"></ion-text>
</ion-card-content>
</ion-card>
}
@if (isLoading) {
<ion-progress-bar type="indeterminate"></ion-progress-bar>
}
</form>
</ion-content>- Create folder:
src/assets/images/ - Add the provided images:
baked_goods_1.jpgbaked_goods_2.jpgbaked_goods_3.jpg
stateDiagram-v2
[*] --> Idle
Idle --> Loading: Click Generate
Loading --> Processing: Image Converted
Processing --> DisplayingRecipe: AI Response
DisplayingRecipe --> Idle: New Selection
Loading --> Error: Fail
Error --> Idle: Try Again
Add this method to your HomePage class:
async onSubmit() {
if (this.isLoading) return;
this.isLoading = true;
try {
const response = await fetch(this.selectedImage);
const blob = await response.blob();
const base64data = await new Promise<string>((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string);
reader.readAsDataURL(blob);
});
const base64String = base64data.split(',')[1];
// TODO: Add Gemini AI code here
// HINT: Follow these steps:
// 1. Create the AI client
// 2. Get the model
// 3. Call generateContent
// 4. Update this.output
} catch (e) {
this.output = `Error: ${e instanceof Error ? e.message : 'Something went wrong'}`;
}
this.isLoading = false;
}Add this code in the TODO section of onSubmit:
const genAI = new GoogleGenerativeAI(environment.apiKey);
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
const result = await model.generateContent({
contents: [{
role: 'user',
parts: [
{
inlineData: {
mimeType: 'image/jpeg',
data: base64String
}
},
{ text: this.prompt }
]
}]
});
this.output = result.response.text();graph TD
A[Component] --> B[GeminiAiService]
B --> C[Image Processing]
B --> D[AI Integration]
C --> E[Base64 Conversion]
D --> F[Recipe Generation]
F --> A
- Create new service called
gemini-aiand place it in the services folder:
ionic generate service services/gemini-aiimport { Injectable } from '@angular/core';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class GeminiAiService {
private readonly MODEL_NAME = 'gemini-1.5-flash';
async getImageAsBase64(imageUrl: string): Promise<string> {
// TODO: Move image conversion code here
// HINT: Copy the code from your component that:
// 1. Fetches the image
// 2. Converts to blob
// 3. Converts to base64
// 4. Returns the base64 string
}
async generateRecipe(imageBase64: string, prompt: string): Promise<string> {
try {
// TODO: Move AI generation code here
// HINT: Copy the code that:
// 1. Creates the AI client
// 2. Gets the model
// 3. Calls generateContent
// 4. Returns the response text
} catch (error) {
throw new Error('Failed to generate recipe');
}
}
}- Update your component to use the service:
// Add to imports
import { GeminiAiService } from '../services/gemini-ai.service';
// Add constructor
constructor(private geminiService: GeminiAiService) {}
// Update onSubmit method
async onSubmit() {
if (this.isLoading) return;
this.isLoading = true;
try {
// TODO: Use service methods
// HINT:
// const base64Image = await this.geminiService.getImageAsBase64(this.selectedImage);
// this.output = await this.geminiService.generateRecipe(base64Image, this.prompt);
} catch (e) {
this.output = `Error: ${e instanceof Error ? e.message : 'Something went wrong'}`;
}
this.isLoading = false;
}- Run your app:
ionic serve- Check that:
- Images display correctly
- You can select different images
- The generate button works
- You get recipe output
graph TD
A[Common Issues] --> B[Image Issues]
A --> C[AI Issues]
A --> D[Service Issues]
B --> E[Check Assets Path]
B --> F[Verify Filenames]
C --> G[Verify API Key]
C --> H[Check Console]
D --> I[Check Imports]
D --> J[Verify Constructor]
- If images don't show:
- Check the assets folder path
- Check image filenames
- If AI doesn't work:
- Check the API key
- Check browser console for errors
- If service doesn't work:
- Check import paths
- Make sure constructor is added
- Add a "Copy Recipe" button
- Add loading animations
- Add error handling
- Add 3 more images
- Creating an Ionic app
- Working with AI APIs
- Using Angular services
- Handling images and base64 conversion
Remember:
- Test frequently
- Check console for errors
- Ask for help when stuck