diff --git a/README.md b/README.md index 2aedc3b..2d063cb 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,5 @@ -# Cloudflare Workers OpenAPI 3.1 +# Bluesky Alt Text Worker -This is a Cloudflare Worker with OpenAPI 3.1 using [chanfana](https://github.com/cloudflare/chanfana) and [Hono](https://github.com/honojs/hono). +A Cloudflare Worker which works in conjunction with https://github.com/indexxing/Bluesky-Alt-Text. All endpoints are based off the original cloud function made by [symmetricalboy](https://github.com/symmetricalboy) [here](https://github.com/symmetricalboy/gen-alt-text/blob/main/functions/index.js), just in Typescript and prepackaged into a Cloudflare Worker environment. -This is an example project made to be used as a quick start into building OpenAPI compliant Workers that generates the -`openapi.json` schema automatically from code and validates the incoming request to the defined parameters or request body. - -## Get started - -1. Sign up for [Cloudflare Workers](https://workers.dev). The free tier is more than enough for most use cases. -2. Clone this project and install dependencies with `npm install` -3. Run `wrangler login` to login to your Cloudflare account in wrangler -4. Run `wrangler deploy` to publish the API to Cloudflare Workers - -## Project structure - -1. Your main router is defined in `src/index.ts`. -2. Each endpoint has its own file in `src/endpoints/`. -3. For more information read the [chanfana documentation](https://chanfana.pages.dev/) and [Hono documentation](https://hono.dev/docs). - -## Development - -1. Run `wrangler dev` to start a local instance of the API. -2. Open `http://localhost:8787/` in your browser to see the Swagger interface where you can try the endpoints. -3. Changes made in the `src/` folder will automatically trigger the server to reload, you only need to refresh the Swagger interface. +Documentation is served at the root of the worker deployment. There is a root path variable specified in the entrypoint file because my setup involves using a worker route wildcard on my custom domain. diff --git a/src/endpoints/condense_text.ts b/src/endpoints/condense_text.ts index 8e054f3..56399af 100644 --- a/src/endpoints/condense_text.ts +++ b/src/endpoints/condense_text.ts @@ -1,10 +1,10 @@ -import { Num, OpenAPIRoute } from "chanfana"; +import { Enumeration, Num, OpenAPIRoute } from "chanfana"; import { z } from "zod"; import { type AppContext } from "../types"; export class CondenseTextEndpoint extends OpenAPIRoute { schema = { - tags: ["AltText"], + tags: ["Processing"], summary: "Condense a given text based on a directive", security: [ { @@ -21,17 +21,11 @@ export class CondenseTextEndpoint extends OpenAPIRoute { required_error: "Text is required for condensation.", }).min(1, "Text cannot be empty."), - directive: z.string({ + mediaType: z.enum(["video", "image"], { description: - "Instructions for condensing the text (e.g., 'Summarize this article', 'Extract keywords').", - required_error: - "A condensation directive is required.", - }).min(1, "Directive cannot be empty."), - targetLength: Num({ - description: - "The approximate target length for the condensed text (e.g., number of sentences, characters, or words).", - default: 200, - }).optional(), + "The type of media being described", + required_error: "Media type is required.", + }), }), }, }, @@ -67,7 +61,9 @@ export class CondenseTextEndpoint extends OpenAPIRoute { async handle(c: AppContext) { const data = await this.getValidatedData(); - const { text, directive, targetLength } = data.body; + const { text, mediaType } = data.body; + + const targetLength = c.env.MAX_ALT_TEXT_LENGTH - 100; try { const res = await c.var.gemini.models.generateContent({ @@ -75,7 +71,10 @@ export class CondenseTextEndpoint extends OpenAPIRoute { model: "gemini-2.0-flash-lite", contents: [{ parts: [ - { text: directive }, + { + text: + `You are an expert at writing concise, informative alt text. Please condense the following ${mediaType} description to be no more than ${targetLength} characters while preserving the most important details. The description needs to be accessible and useful for screen readers:`, + }, { text: text }, ], }], diff --git a/src/endpoints/generate.ts b/src/endpoints/generate.ts index c506d08..a767e43 100644 --- a/src/endpoints/generate.ts +++ b/src/endpoints/generate.ts @@ -48,7 +48,7 @@ By consistently applying these guidelines, you will create alt-text that is info export class GenerateEndpoint extends OpenAPIRoute { schema = { - tags: ["AltText"], + tags: ["Image"], summary: "Generates alt text for a given image.", security: [ { diff --git a/src/index.ts b/src/index.ts index 6888362..54cf8ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,9 @@ import { authMiddleware } from "./middleware/auth"; import { Env, Variables } from "./types"; const app = new Hono<{ Bindings: Env; Variables: Variables }>(); -const root = "/api/altText"; + +// The path which all routes are served from, for easy use with Worker Routes on custom domains. +const rootPath = "/api/altText/"; app.use( "*", @@ -36,26 +38,17 @@ app.use( }), ); -app.use(root + "/generate", authMiddleware); -app.use(root + "/condense_text", authMiddleware); -app.use("*", geminiMiddleware); - const openapi = fromHono(app, { schema: { info: { - title: "Alt Text Generator", + title: "Bluesky Alt Text", description: - "Endpoints for my Bluesky alt text generator browser extension.", + "Endpoints for https://github.com/indexxing/Bluesky-Alt-Text", version: "1.0", }, - servers: [ - { - url: "https://indexx.dev/api/altText", - }, - ], }, - docs_url: root + "/", - openapi_url: root + "/openapi.json", + docs_url: rootPath, + openapi_url: rootPath + "openapi.json", }); openapi.registry.registerComponent("securitySchemes", "apiKey", { @@ -64,7 +57,13 @@ openapi.registry.registerComponent("securitySchemes", "apiKey", { in: "header", }); -openapi.post(root + "/generate", GenerateEndpoint); -openapi.post(root + "/condense_text", CondenseTextEndpoint); +// Define Routes +openapi.post(rootPath + "generate", GenerateEndpoint); +openapi.post(rootPath + "condense_text", CondenseTextEndpoint); + +// Define Middlewares +app.use("*", geminiMiddleware); +app.use(rootPath + "generate", authMiddleware); +app.use(rootPath + "condense_text", authMiddleware); export default app; diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index 98be270..8977aae 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,8 +1,8 @@ import { Context, Next } from "hono"; -import { Env } from "../types"; +import { Env, Variables } from "../types"; export async function authMiddleware( - c: Context<{ Bindings: Env }>, + c: Context<{ Bindings: Env; Variables: Variables }>, next: Next, ) { const authToken = c.req.header("Authorization"); diff --git a/src/types.ts b/src/types.ts index 8cefbc5..2407b79 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,12 @@ import type { GoogleGenAI } from "@google/genai"; export type AppContext = Context<{ Bindings: Env; Variables: Variables }>; export interface Env { + // Environmental Variables + MAX_ALT_TEXT_LENGTH: number; + ABSOLUTE_MAX_LENGTH: number; + MAX_DIRECT_BLOB_SIZE: number; + + // Secrets AUTH_TOKEN: string; GEMINI_API_KEY: string; } diff --git a/wrangler.jsonc b/wrangler.jsonc index 059f2c6..111404f 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,6 +1,6 @@ { "$schema": "node_modules/wrangler/config-schema.json", - "name": "bluesky-alt-text-worker", + "name": "bluesky-alt-text", "main": "src/index.ts", "compatibility_date": "2025-06-07", "observability": { @@ -8,5 +8,10 @@ "logs": { "invocation_logs": true } + }, + "vars": { + "MAX_ALT_TEXT_LENGTH": 2000, + "ABSOLUTE_MAX_LENGTH": 5000, + "MAX_DIRECT_BLOB_SIZE": 5242880 } }