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

View file

@ -1,10 +1,10 @@
import { Num, OpenAPIRoute } from "chanfana"; import { Enumeration, Num, OpenAPIRoute } from "chanfana";
import { z } from "zod"; import { z } from "zod";
import { type AppContext } from "../types"; import { type AppContext } from "../types";
export class CondenseTextEndpoint extends OpenAPIRoute { export class CondenseTextEndpoint extends OpenAPIRoute {
schema = { schema = {
tags: ["AltText"], tags: ["Processing"],
summary: "Condense a given text based on a directive", summary: "Condense a given text based on a directive",
security: [ security: [
{ {
@ -21,17 +21,11 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
required_error: required_error:
"Text is required for condensation.", "Text is required for condensation.",
}).min(1, "Text cannot be empty."), }).min(1, "Text cannot be empty."),
directive: z.string({ mediaType: z.enum(["video", "image"], {
description: description:
"Instructions for condensing the text (e.g., 'Summarize this article', 'Extract keywords').", "The type of media being described",
required_error: required_error: "Media type is required.",
"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(),
}), }),
}, },
}, },
@ -67,7 +61,9 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
async handle(c: AppContext) { async handle(c: AppContext) {
const data = await this.getValidatedData<typeof this.schema>(); 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 { try {
const res = await c.var.gemini.models.generateContent({ const res = await c.var.gemini.models.generateContent({
@ -75,7 +71,10 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
model: "gemini-2.0-flash-lite", model: "gemini-2.0-flash-lite",
contents: [{ contents: [{
parts: [ 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 }, { 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 { export class GenerateEndpoint extends OpenAPIRoute {
schema = { schema = {
tags: ["AltText"], tags: ["Image"],
summary: "Generates alt text for a given image.", summary: "Generates alt text for a given image.",
security: [ security: [
{ {

View file

@ -9,7 +9,9 @@ import { authMiddleware } from "./middleware/auth";
import { Env, Variables } from "./types"; import { Env, Variables } from "./types";
const app = new Hono<{ Bindings: Env; Variables: Variables }>(); 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(
"*", "*",
@ -36,26 +38,17 @@ app.use(
}), }),
); );
app.use(root + "/generate", authMiddleware);
app.use(root + "/condense_text", authMiddleware);
app.use("*", geminiMiddleware);
const openapi = fromHono(app, { const openapi = fromHono(app, {
schema: { schema: {
info: { info: {
title: "Alt Text Generator", title: "Bluesky Alt Text",
description: description:
"Endpoints for my Bluesky alt text generator browser extension.", "Endpoints for https://github.com/indexxing/Bluesky-Alt-Text",
version: "1.0", version: "1.0",
}, },
servers: [
{
url: "https://indexx.dev/api/altText",
},
],
}, },
docs_url: root + "/", docs_url: rootPath,
openapi_url: root + "/openapi.json", openapi_url: rootPath + "openapi.json",
}); });
openapi.registry.registerComponent("securitySchemes", "apiKey", { openapi.registry.registerComponent("securitySchemes", "apiKey", {
@ -64,7 +57,13 @@ openapi.registry.registerComponent("securitySchemes", "apiKey", {
in: "header", in: "header",
}); });
openapi.post(root + "/generate", GenerateEndpoint); // Define Routes
openapi.post(root + "/condense_text", CondenseTextEndpoint); 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; export default app;

View file

@ -1,8 +1,8 @@
import { Context, Next } from "hono"; import { Context, Next } from "hono";
import { Env } from "../types"; import { Env, Variables } from "../types";
export async function authMiddleware( export async function authMiddleware(
c: Context<{ Bindings: Env }>, c: Context<{ Bindings: Env; Variables: Variables }>,
next: Next, next: Next,
) { ) {
const authToken = c.req.header("Authorization"); 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 type AppContext = Context<{ Bindings: Env; Variables: Variables }>;
export interface Env { export interface Env {
// Environmental Variables
MAX_ALT_TEXT_LENGTH: number;
ABSOLUTE_MAX_LENGTH: number;
MAX_DIRECT_BLOB_SIZE: number;
// Secrets
AUTH_TOKEN: string; AUTH_TOKEN: string;
GEMINI_API_KEY: string; GEMINI_API_KEY: string;
} }

View file

@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/wrangler/config-schema.json", "$schema": "node_modules/wrangler/config-schema.json",
"name": "bluesky-alt-text-worker", "name": "bluesky-alt-text",
"main": "src/index.ts", "main": "src/index.ts",
"compatibility_date": "2025-06-07", "compatibility_date": "2025-06-07",
"observability": { "observability": {
@ -8,5 +8,10 @@
"logs": { "logs": {
"invocation_logs": true "invocation_logs": true
} }
},
"vars": {
"MAX_ALT_TEXT_LENGTH": 2000,
"ABSOLUTE_MAX_LENGTH": 5000,
"MAX_DIRECT_BLOB_SIZE": 5242880
} }
} }