feat: include directive for condense_text endpoint
This commit is contained in:
parent
d1aa5106f5
commit
a49c5d9851
7 changed files with 46 additions and 57 deletions
26
README.md
26
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
|
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.
|
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
|
|
|
||||||
|
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
31
src/index.ts
31
src/index.ts
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue