feat: include directive for condense_text endpoint

This commit is contained in:
Index 2025-06-07 23:16:47 -05:00
parent d1aa5106f5
commit a49c5d9851
7 changed files with 46 additions and 57 deletions

View file

@ -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.

View file

@ -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<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({
@ -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 },
],
}],

View file

@ -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: [
{

View file

@ -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;

View file

@ -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");

View file

@ -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;
}

View file

@ -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
}
}