Skip to content

Commit

Permalink
insult my car
Browse files Browse the repository at this point in the history
  • Loading branch information
andrioid committed Nov 7, 2024
1 parent 4b8e698 commit aca0691
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 71 deletions.
60 changes: 0 additions & 60 deletions src/actions/hello/ai.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineAction } from "astro:actions";
import { z } from "astro:schema";
import { hello } from "./hello";
import { insults } from "./insults";

export const server = {
contactMe: defineAction({
Expand All @@ -13,5 +13,5 @@ export const server = {
return `Hello, ${input.email}!`;
},
}),
hello,
insults,
};
61 changes: 61 additions & 0 deletions src/actions/insults/cars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
type CarBrand = {
name: string;
models: string[];
};

export const CAR_BRANDS_WITH_MODELS: CarBrand[] = [
{ name: "Toyota", models: ["Camry", "Corolla", "RAV4"] },
{ name: "Ford", models: ["Mustang", "F-150", "Explorer"] },
{ name: "Honda", models: ["Civic", "Accord", "CR-V"] },
{ name: "Chevrolet", models: ["Silverado", "Malibu", "Equinox"] },
{ name: "Nissan", models: ["Altima", "Rogue", "Sentra"] },
{ name: "Volkswagen", models: ["Golf", "Jetta", "Tiguan"] },
{ name: "Hyundai", models: ["Elantra", "Tucson", "Santa Fe"] },
{ name: "BMW", models: ["3 Series", "5 Series", "X5"] },
{ name: "Mercedes-Benz", models: ["C-Class", "E-Class", "GLC"] },
{ name: "Audi", models: ["A4", "Q5", "A6"] },
{ name: "Tesla", models: ["Model 3", "Model S", "Model X"] },
{ name: "Ferrari", models: ["488 GTB", "F8 Tributo", "Portofino"] },
{ name: "Lamborghini", models: ["Huracán", "Aventador", "Urus"] },
];
export const CAR_BRANDS = [
"Toyota",
"Ford",
"Honda",
"Chevrolet",
"Nissan",
"Volkswagen",
"Hyundai",
"BMW",
"Mercedes-Benz",
"Audi",
"Tesla",
"Ferrari",
"Volvo",
"Porsche",
"Lamborghini",
"Peugeot",
"Renault",
"KIA",
"Lexus",
] as const;

export function insultCarPrompt(brand: string) {
return [
`You are a popular standup comic, focused on car jokes`,
//`You are creative and full of sarcasm and wit`,
`Make up a joke or insult about the car brand ${brand} or its' typical owners'.`,
].join(". ");
}

export function insultCarImagePrompt(insult: string, brand: string) {
const prompt = [
`Summarize the following joke about ${brand}: "${insult}"`,
"Caricature art-style",
"stunning shot, beautiful cityscape, people",
//"Cartoon art-style",
//"Humorous, stunning shot, lively",
//"No text, No titles, No quotes",
];
return prompt.join(". ");
}
60 changes: 60 additions & 0 deletions src/actions/insults/country-ai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { OpenAI } from "openai";

const client = new OpenAI({
baseURL: "https://api.model.box/v1",
apiKey: import.meta.env.MODEL_BOX_API_KEY,
});

export function insultCountryPrompt(country: string) {
return [
`You are a travel journalist knowledgeable of all countries of the world`,
`You are creative and full of sarcasm and wit`,
`Make up a random fact about ${country}`,
].join(". ");
}

export function insultCountryImagePrompt(insult: string, country: string) {
const prompt = [
`Summarize the following joke about ${country}: "${insult}"`,
"stunning shot, beautiful nature, people",
"Caricature art-style",
"No text, No titles, No quotes",
];
return prompt.join(". ");
}

export async function promptAI(prompt: string): Promise<string> {
const data = await client.chat.completions.create({
//model: "meta-llama/llama-3.2-3b-instruct",
model: "deepseek/deepseek-chat",

messages: [
{
role: "user",
content: prompt,
},
],
temperature: 1.5,
max_tokens: 150,
max_completion_tokens: 100,
});

const msg = data.choices[0].message?.content;
if (!msg)
throw new Error("No response from AI", {
cause: data,
});
return msg;
}

export async function aiImage(prompt: string): Promise<string | undefined> {
const data = await client.images.generate({
prompt,
n: 1,
size: "512x512",
model: "black-forest-labs/flux-schnell",
});

const img = data.data[0];
return img.url;
}
File renamed without changes.
File renamed without changes.
36 changes: 31 additions & 5 deletions src/actions/hello/index.ts → src/actions/insults/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { defineAction } from "astro:actions";
import { z } from "astro:schema";
import { CAR_BRANDS, insultCarImagePrompt, insultCarPrompt } from "./cars";
import { getCountryFromIP, getIPfromHeaders } from "./country";
import { aiImage, greetingPrompt, imagePrompt, promptAI } from "./ai";
import {
aiImage,
insultCountryImagePrompt,
insultCountryPrompt,
promptAI,
} from "./country-ai";
import { COUNTRIES } from "./country-list";

export const hello = {
getGreeting: defineAction({
export const insults = {
insultMyCountry: defineAction({
input: z.object({
country: z.enum(COUNTRIES),
}),
Expand All @@ -22,10 +28,10 @@ export const hello = {
insult:
"I couldn't figure out where you're from. So no insult for you.",
};
const insult = await promptAI(greetingPrompt(country));
const insult = await promptAI(insultCountryPrompt(country));
let img = undefined;
try {
img = await aiImage(imagePrompt(insult, country));
img = await aiImage(insultCountryImagePrompt(insult, country));
} catch (err) {
console.error("Image generation failed", err);
}
Expand Down Expand Up @@ -62,4 +68,24 @@ export const hello = {
}
},
}),
insultMyCar: defineAction({
input: z.object({
brand: z.enum(CAR_BRANDS),
}),
accept: "form",
handler: async (input, ctx) => {
const insult = await promptAI(insultCarPrompt(input.brand));
let img = undefined;
try {
img = await aiImage(insultCarImagePrompt(insult, input.brand));
} catch (err) {
console.error("Image generation failed", err);
}

return {
insult,
image: img,
};
},
}),
};
79 changes: 79 additions & 0 deletions src/pages/insult-my-car.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
const prerender = false;
import { actions } from "astro:actions";
import { CAR_BRANDS } from "~/actions/insults/cars";
import Layout from "~/layouts/Layout.astro";
import PageContainer from "~/layouts/page-container.astro";
---

<Layout title="Insult my car">
<PageContainer>
<div
class="p-8 bg-white rounded-md md:min-w-[40rem] self-center text-gray-900 max-w-xl"
>
<h1>Insult my car</h1>
<p>This is a silly parody experiment. Don't take it seriously.</p>
<div class="mt-4">
<form method="post" action={actions.insults.insultMyCar}>
<select
name="brand"
id="country"
class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-andri focus:border-andri"
>
{CAR_BRANDS.map((brand) => <option value={brand}>{brand}</option>)}
</select>
<div class="mt-4">
<button
class="rounded-full bg-andri py-1 px-4 font-medium text-white disabled:bg-gray-100 disabled:text-gray-300"
>Bring it!</button
>
</div>
</form>
</div>
<div id="results" class="hidden mt-6 rounded-md p-4 border border-andri">
</div>
<img
class="hidden mt-4 rounded-md border border-andri"
id="result_image"
/>
</div>
</PageContainer>
</Layout>
<script>
import { actions } from "astro:actions";

const form = document.querySelector("form");
form?.addEventListener("submit", async (event) => {
event.preventDefault();
const button = document.querySelector("button");
const results = document.querySelector("#results");
const resultImageEl = document.querySelector(
"#result_image",
) as HTMLImageElement;
if (!button || !results) return; // No reason to continue

button.setAttribute("disabled", "disabled");
results.classList.add("hidden");
resultImageEl.classList.add("hidden");
resultImageEl.src = "";

const formData = new FormData(form);
const { error, data } = await actions.insults.insultMyCar(formData);

// Show alert pop-up with greeting from action

button.removeAttribute("disabled");

if (error || data === undefined) {
results.innerHTML = "💩";
results.classList.remove("hidden");
return;
}
results.innerHTML = data.insult;
if (data.image) {
resultImageEl.src = data.image;
}
results.classList.remove("hidden");
resultImageEl.classList.remove("hidden");
});
</script>
8 changes: 4 additions & 4 deletions src/pages/insult-my-country.astro
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
import { actions } from "astro:actions";
import { COUNTRIES } from "~/actions/hello/country-list";
import { COUNTRIES } from "~/actions/insults/country-list";
import Layout from "~/layouts/Layout.astro";
import PageContainer from "~/layouts/page-container.astro";
const prerender = false;
const { data } = await Astro.callAction(actions.hello.getCountry, {});
const { data } = await Astro.callAction(actions.insults.getCountry, {});
const defaultCountry = data ?? undefined;
---

Expand Down Expand Up @@ -57,7 +57,7 @@ const defaultCountry = data ?? undefined;

<script>
import { actions } from "astro:actions";
import { COUNTRIES } from "~/actions/hello/country-list";
import { COUNTRIES } from "~/actions/insults/country-list";

const button = document.querySelector("button");
const results = document.querySelector("#results");
Expand All @@ -82,7 +82,7 @@ const defaultCountry = data ?? undefined;
resultImageEl.src = "";
// Show alert pop-up with greeting from action

const { data, error } = await actions.hello.getGreeting({
const { data, error } = await actions.insults.insultMyCountry({
country,
});
button.removeAttribute("disabled");
Expand Down

0 comments on commit aca0691

Please sign in to comment.