feat: cleanup, status codes, Bearer auth, env vars
This commit is contained in:
parent
0c09378dff
commit
d8b1870059
7 changed files with 73 additions and 55 deletions
|
|
@ -3,3 +3,5 @@
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
**Note:** This does not support video captioning, or uploading larger media for processing yet like the source cloud function.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
|
||||||
summary: "Condense a given text based on a directive",
|
summary: "Condense a given text based on a directive",
|
||||||
security: [
|
security: [
|
||||||
{
|
{
|
||||||
apiKey: [],
|
bearerAuth: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
request: {
|
request: {
|
||||||
|
|
@ -67,8 +67,7 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await c.var.gemini.models.generateContent({
|
const res = await c.var.gemini.models.generateContent({
|
||||||
// * Original cloud function used "gemini-2.0-flash", but I think the lite version should work fine too.
|
model: c.env.GEMINI_MODEL,
|
||||||
model: "gemini-2.0-flash-lite",
|
|
||||||
contents: [{
|
contents: [{
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
|
|
@ -79,14 +78,16 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
config: {
|
config: {
|
||||||
temperature: 0.2,
|
temperature: c.env.GEMINI_CONDENSE_TEMPERATURE,
|
||||||
maxOutputTokens: 1024,
|
maxOutputTokens: c.env.GEMINI_CONDENSE_MAX_OUTPUT_TOKENS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const condensedText = res.candidates?.[0]?.content?.parts?.[0]
|
const condensedText = res.candidates?.[0]?.content?.parts?.[0]
|
||||||
?.text;
|
?.text;
|
||||||
if (!condensedText) {
|
if (!condensedText) {
|
||||||
|
c.status(502); // Bad response from upstream API resulting in "Bad Gateway" status
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Failed to condense text.",
|
error: "Failed to condense text.",
|
||||||
|
|
@ -95,12 +96,15 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
altText: condensedText,
|
text: condensedText,
|
||||||
|
tokens: res.usageMetadata.totalTokenCount ?? 0,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
c.status(500);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: e,
|
error: e,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export class GenerateEndpoint extends OpenAPIRoute {
|
||||||
summary: "Generates alt text for a given image.",
|
summary: "Generates alt text for a given image.",
|
||||||
security: [
|
security: [
|
||||||
{
|
{
|
||||||
apiKey: [],
|
bearerAuth: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
request: {
|
request: {
|
||||||
|
|
@ -145,17 +145,16 @@ export class GenerateEndpoint extends OpenAPIRoute {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await c.var.gemini.models.generateContent({
|
const res = await c.var.gemini.models.generateContent({
|
||||||
// * Original cloud function used "gemini-2.0-flash", but I think the lite version should work fine too.
|
model: c.env.GEMINI_MODEL,
|
||||||
model: "gemini-2.0-flash-lite",
|
|
||||||
contents: [
|
contents: [
|
||||||
{ text: systemInstructions },
|
{ text: systemInstructions },
|
||||||
{ inlineData: { mimeType: mimeType, data: base64Data } },
|
{ inlineData: { mimeType: mimeType, data: base64Data } },
|
||||||
],
|
],
|
||||||
config: {
|
config: {
|
||||||
temperature: 0.2, // Lower temperature for more deterministic output
|
temperature: c.env.GEMINI_GENERATE_TEMPERATURE,
|
||||||
maxOutputTokens: 2048, // Allow for longer descriptions if needed
|
maxOutputTokens: c.env.GEMINI_GENERATE_MAX_OUTPUT_TOKENS,
|
||||||
topP: 0.95,
|
topP: c.env.GEMINI_GENERATE_TOP_P,
|
||||||
topK: 64,
|
topK: c.env.GEMINI_GENERATE_TOP_K,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -170,12 +169,13 @@ export class GenerateEndpoint extends OpenAPIRoute {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
altText: generatedText,
|
text: generatedText,
|
||||||
|
tokens: res.usageMetadata.totalTokenCount ?? 0,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: e,
|
error: e,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
src/index.ts
29
src/index.ts
|
|
@ -16,21 +16,12 @@ const rootPath = "/api/altText/";
|
||||||
app.use(
|
app.use(
|
||||||
"*",
|
"*",
|
||||||
cors({
|
cors({
|
||||||
origin: (origin) => {
|
origin: [
|
||||||
const allowedOrigins = [
|
"https://indexx.dev",
|
||||||
"https://indexx.dev",
|
"chrome-extension://",
|
||||||
"moz-extension://",
|
"safari-web-extension://",
|
||||||
"chrome-extension://",
|
"moz-extension://",
|
||||||
];
|
],
|
||||||
|
|
||||||
if (
|
|
||||||
origin &&
|
|
||||||
allowedOrigins.some((allowed) => origin.startsWith(allowed))
|
|
||||||
) {
|
|
||||||
return origin;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||||
allowHeaders: ["*"],
|
allowHeaders: ["*"],
|
||||||
maxAge: 600,
|
maxAge: 600,
|
||||||
|
|
@ -51,10 +42,10 @@ const openapi = fromHono(app, {
|
||||||
openapi_url: rootPath + "openapi.json",
|
openapi_url: rootPath + "openapi.json",
|
||||||
});
|
});
|
||||||
|
|
||||||
openapi.registry.registerComponent("securitySchemes", "apiKey", {
|
openapi.registry.registerComponent("securitySchemes", "bearerAuth", {
|
||||||
type: "apiKey",
|
type: "http",
|
||||||
name: "Authorization",
|
scheme: "bearer",
|
||||||
in: "header",
|
bearerFormat: "API key",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define Middlewares
|
// Define Middlewares
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,35 @@ export async function authMiddleware(
|
||||||
c: Context<{ Bindings: Env; Variables: Variables }>,
|
c: Context<{ Bindings: Env; Variables: Variables }>,
|
||||||
next: Next,
|
next: Next,
|
||||||
) {
|
) {
|
||||||
const authToken = c.req.header("Authorization");
|
if (!c.env.AUTH_TOKEN) {
|
||||||
|
|
||||||
if (!authToken) {
|
|
||||||
c.status(401);
|
|
||||||
return c.json({
|
return c.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "No authentication token provided.",
|
error: "Authentication token is not specified in worker secrets.",
|
||||||
});
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authToken !== c.env.AUTH_TOKEN) {
|
const authToken = c.req.header("Authorization");
|
||||||
c.status(403);
|
|
||||||
|
if (!authToken || !authToken.startsWith("Bearer ")) {
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error:
|
||||||
|
"Authentication token missing or malformed. Expected 'Bearer <token>'.",
|
||||||
|
},
|
||||||
|
401,
|
||||||
|
{
|
||||||
|
"WWW-Authenticate": "Bearer",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authToken.split(" ")[1];
|
||||||
|
if (token !== c.env.AUTH_TOKEN) {
|
||||||
return c.json({
|
return c.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: "Authentication token provided is invalid.",
|
error: "Authentication token provided is invalid.",
|
||||||
});
|
}, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
|
|
||||||
19
src/types.ts
19
src/types.ts
|
|
@ -1,6 +1,4 @@
|
||||||
import { DateTime, Str } from "chanfana";
|
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import { z } from "zod";
|
|
||||||
import type { GoogleGenAI } from "@google/genai";
|
import type { GoogleGenAI } from "@google/genai";
|
||||||
|
|
||||||
export type AppContext = Context<{ Bindings: Env; Variables: Variables }>;
|
export type AppContext = Context<{ Bindings: Env; Variables: Variables }>;
|
||||||
|
|
@ -11,6 +9,15 @@ export interface Env {
|
||||||
ABSOLUTE_MAX_LENGTH: number;
|
ABSOLUTE_MAX_LENGTH: number;
|
||||||
MAX_DIRECT_BLOB_SIZE: number;
|
MAX_DIRECT_BLOB_SIZE: number;
|
||||||
|
|
||||||
|
GEMINI_MODEL: string;
|
||||||
|
GEMINI_GENERATE_TEMPERATURE: number;
|
||||||
|
GEMINI_GENERATE_MAX_OUTPUT_TOKENS: number;
|
||||||
|
GEMINI_GENERATE_TOP_P: number;
|
||||||
|
GEMINI_GENERATE_TOP_K: number;
|
||||||
|
|
||||||
|
GEMINI_CONDENSE_TEMPERATURE: number;
|
||||||
|
GEMINI_CONDENSE_MAX_OUTPUT_TOKENS: number;
|
||||||
|
|
||||||
// Secrets
|
// Secrets
|
||||||
AUTH_TOKEN: string;
|
AUTH_TOKEN: string;
|
||||||
GEMINI_API_KEY: string;
|
GEMINI_API_KEY: string;
|
||||||
|
|
@ -19,11 +26,3 @@ export interface Env {
|
||||||
export type Variables = {
|
export type Variables = {
|
||||||
gemini: GoogleGenAI;
|
gemini: GoogleGenAI;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Task = z.object({
|
|
||||||
name: Str({ example: "lorem" }),
|
|
||||||
slug: Str(),
|
|
||||||
description: Str({ required: false }),
|
|
||||||
completed: z.boolean().default(false),
|
|
||||||
due_date: DateTime(),
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,15 @@
|
||||||
"vars": {
|
"vars": {
|
||||||
"MAX_ALT_TEXT_LENGTH": 2000,
|
"MAX_ALT_TEXT_LENGTH": 2000,
|
||||||
"ABSOLUTE_MAX_LENGTH": 5000,
|
"ABSOLUTE_MAX_LENGTH": 5000,
|
||||||
"MAX_DIRECT_BLOB_SIZE": 5242880
|
"MAX_DIRECT_BLOB_SIZE": 5242880,
|
||||||
|
|
||||||
|
"GEMINI_MODEL": "gemini-2.0-flash-lite",
|
||||||
|
"GEMINI_GENERATE_TEMPERATURE": 0.2,
|
||||||
|
"GEMINI_GENERATE_MAX_OUTPUT_TOKENS": 2048,
|
||||||
|
"GEMINI_GENERATE_TOP_P": 0.95,
|
||||||
|
"GEMINI_GENERATE_TOP_K": 64,
|
||||||
|
|
||||||
|
"GEMINI_CONDENSE_TEMPERATURE": 0.2,
|
||||||
|
"GEMINI_CONDENSE_MAX_OUTPUT_TOKENS": 1024
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue