import { $ } from "bun"
import { Hono } from "hono"
import { openai, createFile } from "./src/openai"
import { serveStatic } from "hono/bun"
import fs from "fs"
const PROMPT = `
This image contains a corkboard with index cards on it - let's focus on that.
Please return the text content of each index card followed by a single line break, and nothing else.
Some humans write in ALL CAPS. Keep technical acronyms (DB, AI, HTML) in ALL CAPS, but convert everything else to Titlecase, Please.
Once you have gathered all the text, please scan it to make sure it looks like a human wrote it.
eg "It 's Steve Y'all !" => "It's Steve Y'all!"
`
const CAMERA = process.env.IAGO_CAMERA || "Set IAGO_CAMERA environment variable"
const IMAGE_PATH = "/tmp/iago.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)
}
})
// return the last captured image
app.get("/last.jpg", async (c) => {
const image = await Bun.file(IMAGE_PATH).arrayBuffer()
return new Response(image, {
headers: { "Content-Type": "image/jpeg" },
})
})
// capture and analyze image
app.get("/analyze", async (c) => {
try {
return c.json({ result: await analyze() })
} catch (err: any) {
return c.json({ error: err.message }, 500)
}
})
// capture and analyze image, return HTML
app.get("/analyze.html", async (c) => {
try {
const result = await analyze()
return c.html(`
corkboard
${result.split("\n").filter(line => line.trim()).map(line => `- ${line}
`).join("")}
`)
} 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)
}
})
async function analyze(): Promise {
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 result.output_text
}
// Capture image using Bun.spawn
async function runImagesnap(): Promise {
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 {
const result = await $`which imagesnap`
return result.exitCode === 0
}
// Get devices using imagesnap
async function getDevices(): Promise {
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
}