A Cloudflare Worker which works in conjunction with https://github.com/indexxing/bsky-alt-text
1import { Enumeration, Num, OpenAPIRoute } from "chanfana";
2import { z } from "zod";
3import { type AppContext } from "../types";
4
5export class CondenseTextEndpoint extends OpenAPIRoute {
6 schema = {
7 tags: ["Processing"],
8 summary: "Condense a given text based on a directive",
9 security: [
10 {
11 apiKey: [],
12 },
13 ],
14 request: {
15 body: {
16 content: {
17 "application/json": {
18 schema: z.object({
19 text: z.string({
20 description: "The text to be condensed.",
21 required_error:
22 "Text is required for condensation.",
23 }).min(1, "Text cannot be empty."),
24 mediaType: z.enum(["video", "image"], {
25 description:
26 "The type of media being described",
27 required_error: "Media type is required.",
28 }),
29 }),
30 },
31 },
32 },
33 },
34 responses: {
35 "200": {
36 description: "Returns the condensed text",
37 content: {
38 "application/json": {
39 schema: z.object({
40 success: z.boolean(),
41 condensedText: z.string().nullable(),
42 error: z.string().optional(),
43 }),
44 },
45 },
46 },
47 "500": {
48 description:
49 "Internal Server Error - Issue with Cloud Function or API call",
50 content: {
51 "application/json": {
52 schema: z.object({
53 success: z.boolean(),
54 message: z.string(),
55 }),
56 },
57 },
58 },
59 },
60 };
61
62 async handle(c: AppContext) {
63 const data = await this.getValidatedData<typeof this.schema>();
64 const { text, mediaType } = data.body;
65
66 const targetLength = c.env.MAX_ALT_TEXT_LENGTH - 100;
67
68 try {
69 const res = await c.var.gemini.models.generateContent({
70 // * Original cloud function used "gemini-2.0-flash", but I think the lite version should work fine too.
71 model: "gemini-2.0-flash-lite",
72 contents: [{
73 parts: [
74 {
75 text:
76 `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:`,
77 },
78 { text: text },
79 ],
80 }],
81 config: {
82 temperature: 0.2,
83 maxOutputTokens: 1024,
84 },
85 });
86
87 const condensedText = res.candidates?.[0]?.content?.parts?.[0]
88 ?.text;
89 if (!condensedText) {
90 return {
91 success: false,
92 error: "Failed to condense text.",
93 };
94 }
95
96 return {
97 success: true,
98 altText: condensedText,
99 };
100 } catch (e) {
101 return {
102 success: false,
103 message: e,
104 };
105 }
106 }
107}