diff --git a/.gitignore b/.gitignore index 2570a77..0d86e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.aider* .env* !.env.vault .venv diff --git a/src/admin/web/manage.ts b/src/admin/web/manage.ts index 76e8308..296eb78 100644 --- a/src/admin/web/manage.ts +++ b/src/admin/web/manage.ts @@ -14,6 +14,7 @@ import { UserSchema, UserTokenCounts, } from "../../shared/users/schema"; +import { getLastNImages } from "../../shared/file-storage/image-history"; const router = Router(); @@ -221,6 +222,18 @@ router.post("/maintenance", (req, res) => { flash.message = `All users' token usage records reset.`; break; } + case "downloadImageMetadata": { + const data = JSON.stringify({ + exportedAt: new Date().toISOString(), + generations: getLastNImages() + }, null, 2); + res.setHeader( + "Content-Disposition", + `attachment; filename=image-metadata-${new Date().toISOString()}.json` + ); + res.setHeader("Content-Type", "application/json"); + return res.send(data); + } default: { throw new HttpError(400, "Invalid action"); } diff --git a/src/admin/web/views/admin_index.ejs b/src/admin/web/views/admin_index.ejs index 3dd52f1..96bd59a 100644 --- a/src/admin/web/views/admin_index.ejs +++ b/src/admin/web/views/admin_index.ejs @@ -50,6 +50,13 @@
<% } %> + <% if (imageGenerationEnabled) { %> + + <% } %> diff --git a/src/admin/web/views/admin_list-users.ejs b/src/admin/web/views/admin_list-users.ejs index d259d2e..3526c44 100644 --- a/src/admin/web/views/admin_list-users.ejs +++ b/src/admin/web/views/admin_list-users.ejs @@ -6,7 +6,7 @@ <% } else { %> -User | diff --git a/src/info-page.ts b/src/info-page.ts index cb038ee..1bb9157 100644 --- a/src/info-page.ts +++ b/src/info-page.ts @@ -190,6 +190,7 @@ function buildRecentImageSection() { `; } html += ``; + html += `` return html; } diff --git a/src/proxy/middleware/response/save-image.ts b/src/proxy/middleware/response/save-image.ts index efca911..cf2dd15 100644 --- a/src/proxy/middleware/response/save-image.ts +++ b/src/proxy/middleware/response/save-image.ts @@ -19,10 +19,9 @@ export const saveImage: ProxyResHandlerWithBody = async ( } if (body.data) { - const baseUrl = req.protocol + "://" + req.get("host"); const prompt = body.data[0].revised_prompt ?? req.body.prompt; const res = await mirrorGeneratedImage( - baseUrl, + req, prompt, body as OpenAIImageGenerationResult ); diff --git a/src/shared/api-schemas/openai.ts b/src/shared/api-schemas/openai.ts index db5e543..92195eb 100644 --- a/src/shared/api-schemas/openai.ts +++ b/src/shared/api-schemas/openai.ts @@ -52,7 +52,7 @@ export const OpenAIV1ChatCompletionSchema = z .number() .int() .nullish() - .default(OPENAI_OUTPUT_MAX) + .default(Math.min(OPENAI_OUTPUT_MAX, 4096)) .transform((v) => Math.min(v ?? OPENAI_OUTPUT_MAX, OPENAI_OUTPUT_MAX)), frequency_penalty: z.number().optional().default(0), presence_penalty: z.number().optional().default(0), diff --git a/src/shared/file-storage/image-history.ts b/src/shared/file-storage/image-history.ts index 54a3215..796ca72 100644 --- a/src/shared/file-storage/image-history.ts +++ b/src/shared/file-storage/image-history.ts @@ -1,15 +1,18 @@ -const IMAGE_HISTORY_SIZE = 30; +const IMAGE_HISTORY_SIZE = 10000; const imageHistory = new Array
---|
Model Family | diff --git a/src/user/routes.ts b/src/user/routes.ts index 2009f31..28d273b 100644 --- a/src/user/routes.ts +++ b/src/user/routes.ts @@ -1,8 +1,10 @@ import express, { Router } from "express"; import { injectCsrfToken, checkCsrfToken } from "../shared/inject-csrf"; +import { browseImagesRouter } from "./web/browse-images"; import { selfServiceRouter } from "./web/self-service"; import { injectLocals } from "../shared/inject-locals"; import { withSession } from "../shared/with-session"; +import { config } from "../config"; const userRouter = Router(); @@ -13,7 +15,9 @@ userRouter.use( userRouter.use(withSession); userRouter.use(injectCsrfToken, checkCsrfToken); userRouter.use(injectLocals); - +if (config.showRecentImages) { + userRouter.use(browseImagesRouter); +} userRouter.use(selfServiceRouter); userRouter.use( diff --git a/src/user/web/browse-images.ts b/src/user/web/browse-images.ts new file mode 100644 index 0000000..c73ab75 --- /dev/null +++ b/src/user/web/browse-images.ts @@ -0,0 +1,54 @@ +import express, { Request, Response } from "express"; +import { getLastNImages } from "../../shared/file-storage/image-history"; +import { paginate } from "../../shared/utils"; +import { ipLimiter } from "../../proxy/rate-limit"; + +const IMAGES_PER_PAGE = 24; + +const metadataCacheTTL = 1000 * 60 * 3; +let metadataCache: unknown | null = null; +let metadataCacheValid = 0; + +const handleImageHistoryPage = (req: Request, res: Response) => { + const page = parseInt(req.query.page as string) || 1; + const allImages = getLastNImages().reverse(); + const { items, pageCount } = paginate(allImages, page, IMAGES_PER_PAGE); + + res.render("image_history", { + images: items, + pagination: { + currentPage: page, + totalPages: pageCount, + }, + }); +}; + +const handleMetadataRequest = (_req: Request, res: Response) => { + res.setHeader("Cache-Control", "public, max-age=180"); + res.setHeader("Content-Type", "application/json"); + res.setHeader( + "Content-Disposition", + `attachment; filename="image-metadata-${new Date().toISOString()}.json"` + ); + if (new Date().getTime() - metadataCacheValid < metadataCacheTTL) { + return res.status(200).json(metadataCache); + } + + const images = getLastNImages().map(({ prompt, url }) => ({ url, prompt })); + const metadata = { + exportedAt: new Date().toISOString(), + totalImages: images.length, + images, + }; + metadataCache = metadata; + metadataCacheValid = new Date().getTime(); + res.status(200).json(metadata); +}; + +export const browseImagesRouter = express.Router(); +browseImagesRouter.get("/image-history", handleImageHistoryPage); +browseImagesRouter.get( + "/image-history/metadata", + ipLimiter, + handleMetadataRequest +); diff --git a/src/user/web/views/image_history.ejs b/src/user/web/views/image_history.ejs new file mode 100644 index 0000000..f61c9db --- /dev/null +++ b/src/user/web/views/image_history.ejs @@ -0,0 +1,71 @@ +<%- include("partials/shared_header", { title: "Image History" }) %> +
---|