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 bearerAuth: [],
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 model: c.env.GEMINI_MODEL,
71 contents: [{
72 parts: [
73 {
74 text:
75 `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:`,
76 },
77 { text: text },
78 ],
79 }],
80 config: {
81 temperature: c.env.GEMINI_CONDENSE_TEMPERATURE,
82 maxOutputTokens: c.env.GEMINI_CONDENSE_MAX_OUTPUT_TOKENS,
83 },
84 });
85
86 const condensedText = res.candidates?.[0]?.content?.parts?.[0]
87 ?.text;
88 if (!condensedText) {
89 c.status(502); // Bad response from upstream API resulting in "Bad Gateway" status
90
91 return {
92 success: false,
93 error: "Failed to condense text.",
94 };
95 }
96
97 return {
98 success: true,
99 text: condensedText,
100 tokens: res.usageMetadata.totalTokenCount ?? 0,
101 };
102 } catch (e) {
103 c.status(500);
104
105 return {
106 success: false,
107 error: e,
108 };
109 }
110 }
111}