A Cloudflare Worker which works in conjunction with https://github.com/indexxing/bsky-alt-text

feat: include directive for condense_text endpoint

Index a49c5d98 d1aa5106

Changed files
+46 -57
src
+3 -23
README.md
···
-
# Cloudflare Workers OpenAPI 3.1
-
-
This is a Cloudflare Worker with OpenAPI 3.1 using [chanfana](https://github.com/cloudflare/chanfana) and [Hono](https://github.com/honojs/hono).
-
-
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
+
# Bluesky Alt Text Worker
-
## Project structure
+
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.
-
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.
+13 -14
src/endpoints/condense_text.ts
···
-
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: [
{
···
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.",
+
}),
}),
},
},
···
async handle(c: AppContext) {
const data = await this.getValidatedData<typeof this.schema>();
-
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({
···
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 },
],
}],
+1 -1
src/endpoints/generate.ts
···
export class GenerateEndpoint extends OpenAPIRoute {
schema = {
-
tags: ["AltText"],
+
tags: ["Image"],
summary: "Generates alt text for a given image.",
security: [
{
+15 -16
src/index.ts
···
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(
"*",
···
}),
);
-
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", {
···
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;
+2 -2
src/middleware/auth.ts
···
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");
+6
src/types.ts
···
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;
}
+6 -1
wrangler.jsonc
···
{
"$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": {
···
"logs": {
"invocation_logs": true
}
+
},
+
"vars": {
+
"MAX_ALT_TEXT_LENGTH": 2000,
+
"ABSOLUTE_MAX_LENGTH": 5000,
+
"MAX_DIRECT_BLOB_SIZE": 5242880
}
}