workshop/packages/iago/server.ts
Chris Wanstrath d16cadad4d deployment
2025-06-18 15:41:30 -07:00

173 lines
4.6 KiB
TypeScript

import { $ } from "bun"
import { Hono } from "hono"
import { openai, createFile } from "./src/openai"
import { serveStatic } from "hono/bun"
import fs from "fs"
const CAMERA = process.env.IAGO_CAMERA || "Set IAGO_CAMERA environment variable"
const PROMPT = "What text do you see in this image?"
const IMAGE_PATH = "./photo.jpg"
const app = new Hono()
// Serve static files from public directory
app.use("/*", serveStatic({ root: "./public" }))
app.get("/", (c) => {
return c.json({ message: "Hello World" })
})
// you know
app.get("/status", async (c) => {
if (!(await checkImagesnap()))
return c.json({ status: "imagesnap not found", devices: [], camera: CAMERA })
const devices = await getDevices()
return c.json({ devices, camera: CAMERA, status: devices.includes(CAMERA) ? "camera found" : "camera not found" })
})
// take a picture with the camera
app.get("/capture", async (c) => {
try {
await runImagesnap()
const image = await Bun.file(IMAGE_PATH).arrayBuffer()
return new Response(image, {
headers: { "Content-Type": "image/jpeg" },
})
} catch (err: any) {
return c.json({ error: err.message }, 500)
}
})
// capture and analyze image
app.get("/analyze", async (c) => {
try {
await runImagesnap()
const fileId = await createFile(IMAGE_PATH)
const result = await openai.responses.create({
model: "gpt-4o",
input: [
{
role: "user",
content: [
{ type: "input_text", text: PROMPT },
{ type: "input_image", file_id: fileId, detail: "high" }
]
}
]
})
return c.json({ result: result.output_text })
} catch (err: any) {
return c.json({ error: err.message }, 500)
}
})
// capture and analyze image, return HTML
app.get("/analyze.html", async (c) => {
try {
await runImagesnap()
const fileId = await createFile(IMAGE_PATH)
const result = await openai.responses.create({
model: "gpt-4o",
input: [
{
role: "user",
content: [
{ type: "input_text", text: "This image contains a corkboard with index cards on it. Please return the text content of each index card followed by a line break, and nothing else." },
{ type: "input_image", file_id: fileId, detail: "high" }
]
}
]
})
return c.html(`<h2>corkboard</h2><ul>${result.output_text.split("\n").map(line => `<li>${line}</li>`).join("")}</ul>`)
} catch (err: any) {
return c.json({ error: err.message }, 500)
}
})
app.get("/speak", async (c) => {
const mp3 = await openai.audio.speech.create({
model: "gpt-4o-mini-tts",
voice: "ash",
instructions: "Speak in an upbeat and goofy tone!",
input: "I put the MAN in MAN-UH!"
})
const buffer = Buffer.from(await mp3.arrayBuffer())
return new Response(buffer, {
headers: {
"Content-Type": "audio/mpeg",
"Content-Disposition": "inline; filename=speak.mp3"
},
})
})
app.get("/talk", async (c) => {
return c.redirect("/talk.html")
})
app.post("/transcribe", async (c) => {
try {
const formData = await c.req.formData()
const audioFile = formData.get("audio") as File
if (!audioFile) {
return c.json({ error: "No audio file provided" }, 400)
}
// Convert File to Buffer
const arrayBuffer = await audioFile.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
// Create a temporary file
const tempPath = `./temp_audio_${Date.now()}.webm`
await Bun.write(tempPath, buffer)
const transcription = await openai.audio.transcriptions.create({
file: Bun.file(tempPath),
model: "gpt-4o-transcribe",
response_format: "text",
})
// Clean up temporary file
await fs.promises.unlink(tempPath)
console.log(transcription)
return c.json({ transcription })
} catch (err: any) {
console.error("Transcription error:", err)
return c.json({ error: err.message }, 500)
}
})
// Capture image using Bun.spawn
async function runImagesnap(): Promise<void> {
const proc = await $`imagesnap -d ${CAMERA} ${IMAGE_PATH}`
if (proc.exitCode !== 0) throw new Error("imagesnap failed")
}
// Check if imagesnap exists using Bun.spawnSync
async function checkImagesnap(): Promise<boolean> {
const result = await $`which imagesnap`
return result.exitCode === 0
}
// Get devices using imagesnap
async function getDevices(): Promise<string[]> {
const result = await $`imagesnap -l`
return result.stdout.toString().split("\n").filter(line => line.startsWith("=> ")).map(line => line.replace("=> ", ""))
}
export default {
port: 3000,
fetch: app.fetch,
idleTimeout: 255
}