From f4e7b5e1a7d210990ef7f6795de1dfa02ce9fb07 Mon Sep 17 00:00:00 2001 From: nameless Date: Thu, 9 Nov 2023 23:25:14 +0000 Subject: [PATCH 01/11] change txt2img seed behaviour will ignore seed only on /txt2img or new requests --- bot/txt2imgCommand.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bot/txt2imgCommand.ts b/bot/txt2imgCommand.ts index 5a2be61..39f7277 100644 --- a/bot/txt2imgCommand.ts +++ b/bot/txt2imgCommand.ts @@ -64,13 +64,22 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea } const repliedToText = repliedToMsg?.text || repliedToMsg?.caption; - if (includeRepliedTo && repliedToText) { + const isReply = includeRepliedTo && repliedToText; + + if (isReply) { // TODO: remove bot command from replied to text params = parsePngInfo(repliedToText, params); } params = parsePngInfo(match, params, true); + if (isReply) { + const parsedInfo = parsePngInfo(repliedToText, undefined, true); + if (parsedInfo.prompt !== params.prompt) { + params.seed = parsedInfo.seed ?? -1; + } + } + if (!params.prompt) { await ctx.reply( "Please tell me what you want to see." + -- 2.43.4 From 73810d3204eb95a28abf10e45dcf04c4480ca592 Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 01:39:59 +0000 Subject: [PATCH 02/11] fix: use exifreader for png info - replace pnginfo module - allow jpeg for tag extraction - rename pnginfo to getprompt --- bot/mod.ts | 4 ++-- bot/parsePngInfo.ts | 26 ++++++++++++++++++-------- bot/pnginfoCommand.ts | 4 ++-- bot/txt2imgCommand.ts | 2 +- deno.json | 3 +-- deno.lock | 4 ++-- 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/bot/mod.ts b/bot/mod.ts index ad3b35b..7052f5d 100644 --- a/bot/mod.ts +++ b/bot/mod.ts @@ -115,7 +115,7 @@ bot.api.setMyDescription( bot.api.setMyCommands([ { command: "txt2img", description: "Generate image from text" }, { command: "img2img", description: "Generate image from image" }, - { command: "pnginfo", description: "Show generation parameters of an image" }, + { command: "getprompt", description: "Try to extract prompt from raw file" }, { command: "queue", description: "Show the current queue" }, { command: "cancel", description: "Cancel all your requests" }, ]); @@ -160,7 +160,7 @@ bot.use(txt2imgQuestion.middleware()); bot.command("img2img", img2imgCommand); bot.use(img2imgQuestion.middleware()); -bot.command("pnginfo", pnginfoCommand); +bot.command("getprompt", pnginfoCommand); bot.use(pnginfoQuestion.middleware()); bot.command("queue", queueCommand); diff --git a/bot/parsePngInfo.ts b/bot/parsePngInfo.ts index 9bf2d40..b751fb3 100644 --- a/bot/parsePngInfo.ts +++ b/bot/parsePngInfo.ts @@ -1,12 +1,22 @@ -import { decode } from "png_chunk_text"; -import extractChunks from "png_chunks_extract"; +import * as ExifReader from "exifreader"; -export function getPngInfo(pngData: Uint8Array): string | undefined { - return extractChunks(pngData) - .filter((chunk) => chunk.name === "tEXt") - .map((chunk) => decode(chunk.data)) - .find((textChunk) => textChunk.keyword === "parameters") - ?.text; +export function getPngInfo(pngData: ArrayBuffer): string | undefined { + const image = ExifReader.load(pngData); + + if (image.UserComment && image.UserComment.value) { + // JPEG image + return String.fromCharCode.apply( + 0, + (image.UserComment.value as number[]).filter((char: number) => char != 0), + ) + .replace("UNICODE", ""); + } else if (image.parameters && image.parameters.description) { + // PNG image + return image.parameters.description; + } else { + // Unknown image type + return undefined; + } } export interface PngInfo { diff --git a/bot/pnginfoCommand.ts b/bot/pnginfoCommand.ts index bf54230..20c2be9 100644 --- a/bot/pnginfoCommand.ts +++ b/bot/pnginfoCommand.ts @@ -20,7 +20,7 @@ async function pnginfo(ctx: ErisContext, includeRepliedTo: boolean): Promise resp.arrayBuffer()); - const params = parsePngInfo(getPngInfo(new Uint8Array(buffer)) ?? ""); + const params = parsePngInfo(getPngInfo(buffer) ?? "Nothing found.", undefined, true); const paramsText = fmt([ `${params.prompt}\n`, diff --git a/bot/txt2imgCommand.ts b/bot/txt2imgCommand.ts index 39f7277..1863728 100644 --- a/bot/txt2imgCommand.ts +++ b/bot/txt2imgCommand.ts @@ -60,7 +60,7 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea if (includeRepliedTo && repliedToMsg?.document?.mime_type === "image/png") { const file = await ctx.api.getFile(repliedToMsg.document.file_id); const buffer = await fetch(file.getUrl()).then((resp) => resp.arrayBuffer()); - params = parsePngInfo(getPngInfo(new Uint8Array(buffer)) ?? "", params); + params = parsePngInfo(getPngInfo(buffer) ?? "", params); } const repliedToText = repliedToMsg?.text || repliedToMsg?.caption; diff --git a/deno.json b/deno.json index b23b395..1b838a6 100644 --- a/deno.json +++ b/deno.json @@ -11,6 +11,7 @@ "async": "https://deno.land/x/async@v2.0.2/mod.ts", "date-fns": "https://cdn.skypack.dev/date-fns@2.30.0?dts", "date-fns/utc": "https://cdn.skypack.dev/@date-fns/utc@1.1.0?dts", + "exifreader": "https://esm.sh/exifreader@4.14.1", "file_type": "https://esm.sh/file-type@18.5.0", "grammy": "https://lib.deno.dev/x/grammy@1/mod.ts", "grammy_autoquote": "https://lib.deno.dev/x/grammy_autoquote@1/mod.ts", @@ -23,8 +24,6 @@ "kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts", "kvmq": "https://deno.land/x/kvmq@v0.3.0/mod.ts", "openapi_fetch": "https://esm.sh/openapi-fetch@0.7.6", - "png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0", - "png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0", "react": "https://esm.sh/react@18.2.0?dev", "react-dom/client": "https://esm.sh/react-dom@18.2.0/client?external=react&dev", "react-flip-move": "https://esm.sh/react-flip-move@3.0.5?external=react&dev", diff --git a/deno.lock b/deno.lock index ab58e99..81245d0 100644 --- a/deno.lock +++ b/deno.lock @@ -303,10 +303,9 @@ "https://esm.sh/@grammyjs/types@2.12.1": "eebe448d3bf3d4fdaeacee50e31b9e3947ce965d2591f1036e5c0273cb7aec36", "https://esm.sh/@twind/core@1.1.3": "bf3e64c13de95b061a721831bf2aed322f6e7335848b06d805168c3212686c1d", "https://esm.sh/@twind/preset-tailwind@1.1.4": "29f5e4774c344ff08d2636e3d86382060a8b40d6bce1c158b803df1823c2804c", + "https://esm.sh/exifreader@4.14.1": "d0f21973393b0d1a6ed329dac8fcfb2f87ce47fe40b8172e205e7d6d85790bb6", "https://esm.sh/file-type@18.5.0": "f01f6eddb05d365925545e26bd449f934dc042bead882b2795c35c4f48d690a3", "https://esm.sh/openapi-fetch@0.7.6": "43eff6df93e773801cb6ade02b0f47c05d0bfe2fb044adbf2b94fa74ff30d35b", - "https://esm.sh/png-chunk-text@1.0.0": "08beb86f31b5ff70240650fe095b6c4e4037e5c1d5917f9338e7633cae680356", - "https://esm.sh/png-chunks-extract@1.0.0": "da06bbd3c08199d72ab16354abe5ffd2361cb891ccbb44d757a0a0a4fbfa12b5", "https://esm.sh/react-dom@18.2.0/client?external=react&dev": "2eb39c339720d727591fd55fb44ffcb6f14b06812af0a71e7a2268185b5b6e73", "https://esm.sh/react-flip-move@3.0.5?external=react&dev": "4390c0777a0bec583d3e6cb5e4b33831ac937d670d894a20e4f192ce8cd21bae", "https://esm.sh/react-intl@6.4.7?external=react&dev": "60e68890e2c5ef3c02d37a89c53e056b1bbd1c8467a7aae62f0b634abc7a8a5f", @@ -333,6 +332,7 @@ "https://esm.sh/v133/@remix-run/router@1.9.0/X-ZS9yZWFjdA/denonext/router.development.mjs": "97f96f031a7298b0afbbadb23177559ee9b915314d7adf0b9c6a8d7f452c70e7", "https://esm.sh/v133/client-only@0.0.1/X-ZS9yZWFjdA/denonext/client-only.development.mjs": "b7efaef2653f7f628d084e24a4d5a857c9cd1a5e5060d1d9957e185ee98c8a28", "https://esm.sh/v133/crc-32@0.3.0/denonext/crc-32.mjs": "92bbd96cd5a92e45267cf4b3d3928b9355d16963da1ba740637fb10f1daca590", + "https://esm.sh/v133/exifreader@4.14.1/denonext/exifreader.mjs": "691e1c1d1337ccaf092bf39115fdac56bf69e93259d04bb03985e918202317ab", "https://esm.sh/v133/file-type@18.5.0/denonext/file-type.mjs": "785cac1bc363448647871e1b310422e01c9cfb7b817a68690710b786d3598311", "https://esm.sh/v133/hoist-non-react-statics@3.3.2/X-ZS9yZWFjdA/denonext/hoist-non-react-statics.development.mjs": "fcafac9e3c33810f18ecb43dfc32ce80efc88adc63d3f49fd7ada0665d2146c6", "https://esm.sh/v133/ieee754@1.2.1/denonext/ieee754.mjs": "9ec2806065f50afcd4cf3f3f2f38d93e777a92a5954dda00d219f57d4b24832f", -- 2.43.4 From ac63e39373d258b2a8f957413d1d77cf2435e23e Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 01:49:06 +0000 Subject: [PATCH 03/11] feat: admin priority ignore queue limits and assign higher priority on admin jobs --- bot/txt2imgCommand.ts | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/bot/txt2imgCommand.ts b/bot/txt2imgCommand.ts index 1863728..b09847a 100644 --- a/bot/txt2imgCommand.ts +++ b/bot/txt2imgCommand.ts @@ -6,6 +6,7 @@ import { generationQueue } from "../app/generationQueue.ts"; import { formatUserChat } from "../utils/formatUserChat.ts"; import { ErisContext } from "./mod.ts"; import { getPngInfo, parsePngInfo, PngInfo } from "./parsePngInfo.ts"; +import { adminStore } from "../app/adminStore.ts"; export const txt2imgQuestion = new StatelessQuestion( "txt2img", @@ -29,6 +30,7 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea } const config = await getConfig(); + let priority = 0; if (config.pausedReason != null) { await ctx.reply(`I'm paused: ${config.pausedReason || "No reason given"}`, { @@ -37,20 +39,26 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea return; } - const jobs = await generationQueue.getAllJobs(); - if (jobs.length >= config.maxJobs) { - await ctx.reply(`The queue is full. Try again later. (Max queue size: ${config.maxJobs})`, { - reply_to_message_id: ctx.message?.message_id, - }); - return; - } + const [admin] = await adminStore.getBy("tgUserId", { value: ctx.from.id }); - const userJobs = jobs.filter((job) => job.state.from.id === ctx.message?.from?.id); - if (userJobs.length >= config.maxUserJobs) { - await ctx.reply(`You already have ${userJobs.length} jobs in queue. Try again later.`, { - reply_to_message_id: ctx.message?.message_id, - }); - return; + if (admin) { + priority = 1; + } else { + const jobs = await generationQueue.getAllJobs(); + if (jobs.length >= config.maxJobs) { + await ctx.reply(`The queue is full. Try again later. (Max queue size: ${config.maxJobs})`, { + reply_to_message_id: ctx.message?.message_id, + }); + return; + } + + const userJobs = jobs.filter((job) => job.state.from.id === ctx.message?.from?.id); + if (userJobs.length >= config.maxUserJobs) { + await ctx.reply(`You already have ${userJobs.length} jobs in the queue. Try again later.`, { + reply_to_message_id: ctx.message?.message_id, + }); + return; + } } let params: Partial = {}; @@ -103,7 +111,7 @@ async function txt2img(ctx: ErisContext, match: string, includeRepliedTo: boolea chat: ctx.message.chat, requestMessage: ctx.message, replyMessage: replyMessage, - }, { retryCount: 3, retryDelayMs: 10_000 }); + }, { retryCount: 3, retryDelayMs: 10_000, priority: priority }); debug(`Generation (txt2img) enqueued for ${formatUserChat(ctx.message)}`); } -- 2.43.4 From 84ad11b709b94ce4b74d0517c7719bd8920d4216 Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 02:01:12 +0000 Subject: [PATCH 04/11] refactor: webui adjustments - remove unnecessary margin in admins and workers page - use rounded-md instead of rounded-xl for queue page - output "No worker/admin/jobs" as item in a list --- ui/AdminsPage.tsx | 8 ++++-- ui/QueuePage.tsx | 61 ++++++++++++++++++++++++++-------------------- ui/WorkersPage.tsx | 8 ++++-- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/ui/AdminsPage.tsx b/ui/AdminsPage.tsx index aa98707..c9d39f5 100644 --- a/ui/AdminsPage.tsx +++ b/ui/AdminsPage.tsx @@ -48,14 +48,18 @@ export function AdminsPage(props: { sessionId: string | null }) { {getAdmins.data?.length ? ( -
    +
      {getAdmins.data.map((admin) => ( ))}
    ) : getAdmins.data?.length === 0 - ?

    No admins

    + ? ( +
  • +

    No admins.

    +
  • + ) : getAdmins.error ?

    Loading admins failed

    :
    } diff --git a/ui/QueuePage.tsx b/ui/QueuePage.tsx index 3b11590..694724a 100644 --- a/ui/QueuePage.tsx +++ b/ui/QueuePage.tsx @@ -15,36 +15,43 @@ export function QueuePage() { return ( - {getJobs.data?.map((job) => ( -
  • - - {job.place}. - - {getFlagEmoji(job.state.from.language_code)} - {job.state.from.first_name} {job.state.from.last_name} - {job.state.from.username - ? ( - - @{job.state.from.username} - - ) - : null} - - {job.state.progress != null && - } - - - {job.state.workerInstanceKey} - -
  • - ))} + {getJobs.data && getJobs.data.length === 0 + ?
  • Queue is empty.
  • + : ( + getJobs.data?.map((job) => ( +
  • + {job.place}. + {getFlagEmoji(job.state.from.language_code)} + {job.state.from.first_name} {job.state.from.last_name} + {job.state.from.username + ? ( + + @{job.state.from.username} + + ) + : null} + + {job.state.progress != null && ( + + )} + + + {job.state.workerInstanceKey} + +
  • + )) + )}
    ); } diff --git a/ui/WorkersPage.tsx b/ui/WorkersPage.tsx index 0310cfe..2428a37 100644 --- a/ui/WorkersPage.tsx +++ b/ui/WorkersPage.tsx @@ -31,14 +31,18 @@ export function WorkersPage(props: { sessionId: string | null }) { <> {getWorkers.data?.length ? ( -
      +
        {getWorkers.data?.map((worker) => ( ))}
      ) : getWorkers.data?.length === 0 - ?

      No workers

      + ? ( +
    • +

      No workers.

      +
    • + ) : getWorkers.error ?

      Loading workers failed

      :
      } -- 2.43.4 From e270a3ab1f6d7b8ef6b60837dd8ba6cb9709d105 Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 01:55:08 +0000 Subject: [PATCH 05/11] refactor: optimize jobs endpoint output --- api/jobsRoute.ts | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/api/jobsRoute.ts b/api/jobsRoute.ts index cc85982..e7811c4 100644 --- a/api/jobsRoute.ts +++ b/api/jobsRoute.ts @@ -4,12 +4,29 @@ import { generationQueue } from "../app/generationQueue.ts"; export const jobsRoute = createMethodFilter({ GET: createEndpoint( { query: null, body: null }, - async () => ({ - status: 200, - body: { - type: "application/json", - data: await generationQueue.getAllJobs(), - }, - }), + async () => { + const allJobs = await generationQueue.getAllJobs(); + const filteredJobsData = allJobs.map((job) => ({ + id: job.id, + place: job.place, + state: { + from: { + language_code: job.state.from.language_code, + first_name: job.state.from.first_name, + last_name: job.state.from.last_name, + username: job.state.from.username, + }, + progress: job.state.progress, + workerInstanceKey: job.state.workerInstanceKey, + }, + })); + return { + status: 200, + body: { + type: "application/json", + data: filteredJobsData, + }, + }; + }, ), }); -- 2.43.4 From 5b61576315399f40b3d84f4c05203206d7e5643b Mon Sep 17 00:00:00 2001 From: nameless Date: Thu, 9 Nov 2023 23:21:21 +0000 Subject: [PATCH 06/11] fix: withUser -> withAdmin --- api/adminsRoute.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/adminsRoute.ts b/api/adminsRoute.ts index cb7fa3a..9666c7c 100644 --- a/api/adminsRoute.ts +++ b/api/adminsRoute.ts @@ -109,12 +109,12 @@ export const adminsRoute = createPathFilter({ }, }, async ({ params, query }) => { - const deletedAdminEntry = await adminStore.getById(params.adminId!); - if (!deletedAdminEntry) { - return { status: 404, body: { type: "text/plain", data: `Admin not found` } }; - } - const deletedAdminUser = await getUser(deletedAdminEntry.value.tgUserId); - return withUser(query, async (chat) => { + return withAdmin(query, async (chat) => { + const deletedAdminEntry = await adminStore.getById(params.adminId!); + if (!deletedAdminEntry) { + return { status: 404, body: { type: "text/plain", data: `Admin not found` } }; + } + const deletedAdminUser = await getUser(deletedAdminEntry.value.tgUserId); await deletedAdminEntry.delete(); info(`User ${chat.first_name} demoted user ${deletedAdminUser.first_name} from admin`); return { -- 2.43.4 From af7b3703216aae1c2e44170f1c367a1f53d2ff6a Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 10:45:39 +0000 Subject: [PATCH 07/11] feat: tagcount endpoint removes tagCountMap from user endpoint, returns it only via admin-only endpoint --- api/statsRoute.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/api/statsRoute.ts b/api/statsRoute.ts index 8bd1f76..09f814e 100644 --- a/api/statsRoute.ts +++ b/api/statsRoute.ts @@ -5,6 +5,7 @@ import { generationStore } from "../app/generationStore.ts"; import { globalStats } from "../app/globalStats.ts"; import { getUserDailyStats } from "../app/userDailyStatsStore.ts"; import { getUserStats } from "../app/userStatsStore.ts"; +import { withAdmin } from "./withUser.ts"; const STATS_INTERVAL_MIN = 3; @@ -91,7 +92,6 @@ export const statsRoute = createPathFilter({ data: { imageCount: stats.imageCount, pixelCount: stats.pixelCount, - tagCountMap: stats.tagCountMap, timestamp: stats.timestamp, }, }, @@ -99,6 +99,27 @@ export const statsRoute = createPathFilter({ }, ), }), + "users/{userId}/tagcount": createMethodFilter({ + GET: createEndpoint( + { query: { sessionId: { type: "string" } }, body: null }, + async ({ params, query }) => { + return withAdmin(query, async () => { + const userId = Number(params.userId); + const stats = await getUserStats(userId); + return { + status: 200, + body: { + type: "application/json", + data: { + tagCountMap: stats.tagCountMap, + timestamp: stats.timestamp, + }, + }, + }; + }); + }, + ), + }), "users/{userId}/daily/{year}/{month}/{day}": createMethodFilter({ GET: createEndpoint( { query: null, body: null }, -- 2.43.4 From d22848691ceac43bb04d389a5193e2428eb47c5f Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 02:47:44 +0000 Subject: [PATCH 08/11] feat: remove url from error --- app/generationQueue.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/generationQueue.ts b/app/generationQueue.ts index 49700d0..43cc106 100644 --- a/app/generationQueue.ts +++ b/app/generationQueue.ts @@ -70,7 +70,8 @@ export async function processGenerationQueue() { return response; }) .catch((error) => { - workerInstance.update({ lastError: { message: error.message, time: Date.now() } }) + const cleanedErrorMessage = error.message.replace(/url \([^)]+\)/, ""); + workerInstance.update({ lastError: { message: cleanedErrorMessage, time: Date.now() } }) .catch(() => undefined); debug(`Worker ${workerInstance.value.key} is down: ${error}`); }); -- 2.43.4 From 8ced57c175907df653f8ec7b5460145781e352d0 Mon Sep 17 00:00:00 2001 From: nameless Date: Fri, 10 Nov 2023 10:21:35 +0000 Subject: [PATCH 09/11] refactor: move sendChatAction resolves pinks/eris#18 --- app/generationQueue.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/generationQueue.ts b/app/generationQueue.ts index 43cc106..318a622 100644 --- a/app/generationQueue.ts +++ b/app/generationQueue.ts @@ -241,8 +241,6 @@ async function processGenerationJob( if (progressResponse.data.progress > state.progress) { state.progress = progressResponse.data.progress; await updateJob({ state: state }); - await bot.api.sendChatAction(state.chat.id, "upload_photo", { maxAttempts: 1 }) - .catch(() => undefined); await bot.api.editMessageText( state.replyMessage.chat.id, state.replyMessage.message_id, @@ -298,6 +296,10 @@ async function processGenerationJob( { maxAttempts: 1 }, ).catch(() => undefined); + // send upload photo action + await bot.api.sendChatAction(state.chat.id, "upload_photo", { maxAttempts: 1 }) + .catch(() => undefined); + debug(`Generation finished for ${formatUserChat(state)}`); } -- 2.43.4 From 602a63f2c988dae6b36910a04cd238ad951f01f0 Mon Sep 17 00:00:00 2001 From: nameless Date: Sat, 11 Nov 2023 00:11:35 +0000 Subject: [PATCH 10/11] feat: user daily stats --- app/userDailyStatsStore.ts | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/userDailyStatsStore.ts b/app/userDailyStatsStore.ts index 63e1751..485fdeb 100644 --- a/app/userDailyStatsStore.ts +++ b/app/userDailyStatsStore.ts @@ -2,6 +2,8 @@ import { JsonSchema, jsonType } from "t_rest/server"; import { kvMemoize } from "./kvMemoize.ts"; import { db } from "./db.ts"; import { generationStore } from "./generationStore.ts"; +import { hoursToMilliseconds, isSameDay, minutesToMilliseconds } from "date-fns"; +import { UTCDateMini } from "date-fns/utc"; export const userDailyStatsSchema = { type: "object", @@ -19,6 +21,36 @@ export const getUserDailyStats = kvMemoize( db, ["userDailyStats"], async (userId: number, year: number, month: number, day: number): Promise => { - throw new Error("Not implemented"); + let imageCount = 0; + let pixelCount = 0; + + for await ( + const generation of generationStore.listBy("fromId", { + before: new Date(new Date(year, month - 1, day).getTime() + 24 * 60 * 60 * 1000), + after: new Date(year, month - 1, day), + value: userId, + }) + ) { + imageCount++; + pixelCount += (generation.value.info?.width ?? 0) * (generation.value.info?.height ?? 0); + } + + return { + imageCount, + pixelCount, + timestamp: Date.now(), + }; + }, + { + // expire in 1 minute if was calculated on the same day, otherwise 7-14 days. + expireIn: (result, year, month, day) => { + const requestDate = new UTCDateMini(year, month - 1, day); + const calculatedDate = new UTCDateMini(result.timestamp); + return isSameDay(requestDate, calculatedDate) + ? minutesToMilliseconds(1) + : hoursToMilliseconds(24 * 7 + Math.random() * 24 * 7); + }, + // should cache if the stats are non-zero + shouldCache: (result) => result.imageCount > 0 || result.pixelCount > 0, }, ); -- 2.43.4 From 2f62c17e32a1021041102c103be4e6d92455e37e Mon Sep 17 00:00:00 2001 From: nameless Date: Sun, 12 Nov 2023 02:33:35 +0000 Subject: [PATCH 11/11] review fixes --- app/userDailyStatsStore.ts | 4 ++-- bot/mod.ts | 4 ++-- bot/parsePngInfo.ts | 28 +++++++++++++++------------- bot/pnginfoCommand.ts | 11 +++++++++-- ui/AdminsPage.tsx | 8 +++++--- ui/WorkersPage.tsx | 8 +++++--- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/app/userDailyStatsStore.ts b/app/userDailyStatsStore.ts index 485fdeb..64d7446 100644 --- a/app/userDailyStatsStore.ts +++ b/app/userDailyStatsStore.ts @@ -26,8 +26,8 @@ export const getUserDailyStats = kvMemoize( for await ( const generation of generationStore.listBy("fromId", { - before: new Date(new Date(year, month - 1, day).getTime() + 24 * 60 * 60 * 1000), - after: new Date(year, month - 1, day), + after: new Date(Date.UTC(year, month - 1, day)), + before: new Date(Date.UTC(year, month - 1, day + 1)), value: userId, }) ) { diff --git a/bot/mod.ts b/bot/mod.ts index 7052f5d..6f7fdda 100644 --- a/bot/mod.ts +++ b/bot/mod.ts @@ -115,7 +115,7 @@ bot.api.setMyDescription( bot.api.setMyCommands([ { command: "txt2img", description: "Generate image from text" }, { command: "img2img", description: "Generate image from image" }, - { command: "getprompt", description: "Try to extract prompt from raw file" }, + { command: "pnginfo", description: "Try to extract prompt from raw file" }, { command: "queue", description: "Show the current queue" }, { command: "cancel", description: "Cancel all your requests" }, ]); @@ -160,7 +160,7 @@ bot.use(txt2imgQuestion.middleware()); bot.command("img2img", img2imgCommand); bot.use(img2imgQuestion.middleware()); -bot.command("getprompt", pnginfoCommand); +bot.command("pnginfo", pnginfoCommand); bot.use(pnginfoQuestion.middleware()); bot.command("queue", queueCommand); diff --git a/bot/parsePngInfo.ts b/bot/parsePngInfo.ts index b751fb3..ad3174f 100644 --- a/bot/parsePngInfo.ts +++ b/bot/parsePngInfo.ts @@ -1,22 +1,24 @@ import * as ExifReader from "exifreader"; export function getPngInfo(pngData: ArrayBuffer): string | undefined { - const image = ExifReader.load(pngData); + const info = ExifReader.load(pngData); - if (image.UserComment && image.UserComment.value) { + if (info.UserComment?.value && Array.isArray(info.UserComment.value)) { // JPEG image - return String.fromCharCode.apply( - 0, - (image.UserComment.value as number[]).filter((char: number) => char != 0), - ) - .replace("UNICODE", ""); - } else if (image.parameters && image.parameters.description) { - // PNG image - return image.parameters.description; - } else { - // Unknown image type - return undefined; + return String.fromCharCode( + ...info.UserComment.value + .filter((char): char is number => typeof char == "number") + .filter((char) => char !== 0), + ).replace("UNICODE", ""); } + + if (info.parameters?.description) { + // PNG image + return info.parameters.description; + } + + // Unknown image type + return undefined; } export interface PngInfo { diff --git a/bot/pnginfoCommand.ts b/bot/pnginfoCommand.ts index 20c2be9..ed41fcf 100644 --- a/bot/pnginfoCommand.ts +++ b/bot/pnginfoCommand.ts @@ -22,7 +22,7 @@ async function pnginfo(ctx: ErisContext, includeRepliedTo: boolean): Promise resp.arrayBuffer()); - const params = parsePngInfo(getPngInfo(buffer) ?? "Nothing found.", undefined, true); + const info = getPngInfo(buffer); + if (!info) { + return await ctx.reply( + "No info found in file.", + omitUndef({ reply_to_message_id: ctx.message?.message_id }), + ); + } + const params = parsePngInfo(info, undefined, true); const paramsText = fmt([ `${params.prompt}\n`, diff --git a/ui/AdminsPage.tsx b/ui/AdminsPage.tsx index c9d39f5..4e76406 100644 --- a/ui/AdminsPage.tsx +++ b/ui/AdminsPage.tsx @@ -56,9 +56,11 @@ export function AdminsPage(props: { sessionId: string | null }) { ) : getAdmins.data?.length === 0 ? ( -
    • -

      No admins.

      -
    • +
        +
      • +

        No admins.

        +
      • +
      ) : getAdmins.error ?

      Loading admins failed

      diff --git a/ui/WorkersPage.tsx b/ui/WorkersPage.tsx index 2428a37..33cfd0a 100644 --- a/ui/WorkersPage.tsx +++ b/ui/WorkersPage.tsx @@ -39,9 +39,11 @@ export function WorkersPage(props: { sessionId: string | null }) { ) : getWorkers.data?.length === 0 ? ( -
    • -

      No workers.

      -
    • +
        +
      • +

        No workers.

        +
      • +
      ) : getWorkers.error ?

      Loading workers failed

      -- 2.43.4