Compare commits
2 Commits
11d8a66c18
...
4f2371fa8b
Author | SHA1 | Date |
---|---|---|
pinks | 4f2371fa8b | |
pinks | f020499f4d |
|
@ -1,7 +1,7 @@
|
||||||
import { route } from "reroute";
|
import { route } from "reroute";
|
||||||
import { serveSpa } from "serve_spa";
|
import { serveSpa } from "serve_spa";
|
||||||
import { serveApi } from "./serveApi.ts";
|
import { serveApi } from "./serveApi.ts";
|
||||||
import { fromFileUrl } from "std/path/mod.ts"
|
import { fromFileUrl } from "std/path/mod.ts";
|
||||||
|
|
||||||
export async function serveUi() {
|
export async function serveUi() {
|
||||||
const server = Deno.serve({ port: 5999 }, (request) =>
|
const server = Deno.serve({ port: 5999 }, (request) =>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ulid } from "ulid";
|
||||||
export const sessions = new Map<string, Session>();
|
export const sessions = new Map<string, Session>();
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
userId?: number;
|
userId?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sessionsRoute = createPathFilter({
|
export const sessionsRoute = createPathFilter({
|
||||||
|
@ -24,7 +24,7 @@ export const sessionsRoute = createPathFilter({
|
||||||
GET: createEndpoint(
|
GET: createEndpoint(
|
||||||
{ query: null, body: null },
|
{ query: null, body: null },
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const id = params.sessionId;
|
const id = params.sessionId!;
|
||||||
const session = sessions.get(id);
|
const session = sessions.get(id);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return { status: 401, body: { type: "text/plain", data: "Session not found" } };
|
return { status: 401, body: { type: "text/plain", data: "Session not found" } };
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const usersRoute = createPathFilter({
|
||||||
GET: createEndpoint(
|
GET: createEndpoint(
|
||||||
{ query: null, body: null },
|
{ query: null, body: null },
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const chat = await bot.api.getChat(params.userId);
|
const chat = await bot.api.getChat(params.userId!);
|
||||||
if (chat.type !== "private") {
|
if (chat.type !== "private") {
|
||||||
throw new Error("Chat is not private");
|
throw new Error("Chat is not private");
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ export const usersRoute = createPathFilter({
|
||||||
GET: createEndpoint(
|
GET: createEndpoint(
|
||||||
{ query: null, body: null },
|
{ query: null, body: null },
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const chat = await bot.api.getChat(params.userId);
|
const chat = await bot.api.getChat(params.userId!);
|
||||||
if (chat.type !== "private") {
|
if (chat.type !== "private") {
|
||||||
throw new Error("Chat is not private");
|
throw new Error("Chat is not private");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
workerInstanceStore,
|
workerInstanceStore,
|
||||||
} from "../app/workerInstanceStore.ts";
|
} from "../app/workerInstanceStore.ts";
|
||||||
import { getAuthHeader } from "../utils/getAuthHeader.ts";
|
import { getAuthHeader } from "../utils/getAuthHeader.ts";
|
||||||
|
import { omitUndef } from "../utils/omitUndef.ts";
|
||||||
import { withUser } from "./withUser.ts";
|
import { withUser } from "./withUser.ts";
|
||||||
|
|
||||||
export type WorkerData = Omit<WorkerInstance, "sdUrl" | "sdAuth"> & {
|
export type WorkerData = Omit<WorkerInstance, "sdUrl" | "sdAuth"> & {
|
||||||
|
@ -105,7 +106,7 @@ export const workersRoute = createPathFilter({
|
||||||
GET: createEndpoint(
|
GET: createEndpoint(
|
||||||
{ query: null, body: null },
|
{ query: null, body: null },
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const workerInstance = await workerInstanceStore.getById(params.workerId);
|
const workerInstance = await workerInstanceStore.getById(params.workerId!);
|
||||||
if (!workerInstance) {
|
if (!workerInstance) {
|
||||||
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
||||||
}
|
}
|
||||||
|
@ -126,7 +127,7 @@ export const workersRoute = createPathFilter({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ params, query, body }) => {
|
async ({ params, query, body }) => {
|
||||||
const workerInstance = await workerInstanceStore.getById(params.workerId);
|
const workerInstance = await workerInstanceStore.getById(params.workerId!);
|
||||||
if (!workerInstance) {
|
if (!workerInstance) {
|
||||||
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
||||||
}
|
}
|
||||||
|
@ -136,7 +137,7 @@ export const workersRoute = createPathFilter({
|
||||||
JSON.stringify(body.data)
|
JSON.stringify(body.data)
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
await workerInstance.update(body.data);
|
await workerInstance.update(omitUndef(body.data));
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { type: "application/json", data: await getWorkerData(workerInstance) },
|
body: { type: "application/json", data: await getWorkerData(workerInstance) },
|
||||||
|
@ -152,7 +153,7 @@ export const workersRoute = createPathFilter({
|
||||||
body: null,
|
body: null,
|
||||||
},
|
},
|
||||||
async ({ params, query }) => {
|
async ({ params, query }) => {
|
||||||
const workerInstance = await workerInstanceStore.getById(params.workerId);
|
const workerInstance = await workerInstanceStore.getById(params.workerId!);
|
||||||
if (!workerInstance) {
|
if (!workerInstance) {
|
||||||
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
||||||
}
|
}
|
||||||
|
@ -169,7 +170,7 @@ export const workersRoute = createPathFilter({
|
||||||
GET: createEndpoint(
|
GET: createEndpoint(
|
||||||
{ query: null, body: null },
|
{ query: null, body: null },
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const workerInstance = await workerInstanceStore.getById(params.workerId);
|
const workerInstance = await workerInstanceStore.getById(params.workerId!);
|
||||||
if (!workerInstance) {
|
if (!workerInstance) {
|
||||||
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
||||||
}
|
}
|
||||||
|
@ -200,7 +201,7 @@ export const workersRoute = createPathFilter({
|
||||||
GET: createEndpoint(
|
GET: createEndpoint(
|
||||||
{ query: null, body: null },
|
{ query: null, body: null },
|
||||||
async ({ params }) => {
|
async ({ params }) => {
|
||||||
const workerInstance = await workerInstanceStore.getById(params.workerId);
|
const workerInstance = await workerInstanceStore.getById(params.workerId!);
|
||||||
if (!workerInstance) {
|
if (!workerInstance) {
|
||||||
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
return { status: 404, body: { type: "text/plain", data: `Worker not found` } };
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { PngInfo } from "../bot/parsePngInfo.ts";
|
||||||
import { formatOrdinal } from "../utils/formatOrdinal.ts";
|
import { formatOrdinal } from "../utils/formatOrdinal.ts";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
import { getAuthHeader } from "../utils/getAuthHeader.ts";
|
import { getAuthHeader } from "../utils/getAuthHeader.ts";
|
||||||
|
import { omitUndef } from "../utils/omitUndef.ts";
|
||||||
import { SdError } from "./SdError.ts";
|
import { SdError } from "./SdError.ts";
|
||||||
import { getConfig } from "./config.ts";
|
import { getConfig } from "./config.ts";
|
||||||
import { db, fs } from "./db.ts";
|
import { db, fs } from "./db.ts";
|
||||||
|
@ -161,7 +162,7 @@ async function processGenerationJob(
|
||||||
|
|
||||||
// reduce size if worker can't handle the resolution
|
// reduce size if worker can't handle the resolution
|
||||||
const size = limitSize(
|
const size = limitSize(
|
||||||
{ ...config.defaultParams, ...state.task.params },
|
omitUndef({ ...config.defaultParams, ...state.task.params }),
|
||||||
1024 * 1024,
|
1024 * 1024,
|
||||||
);
|
);
|
||||||
function limitSize(
|
function limitSize(
|
||||||
|
@ -182,18 +183,18 @@ async function processGenerationJob(
|
||||||
// start generating the image
|
// start generating the image
|
||||||
const responsePromise = state.task.type === "txt2img"
|
const responsePromise = state.task.type === "txt2img"
|
||||||
? workerSdClient.POST("/sdapi/v1/txt2img", {
|
? workerSdClient.POST("/sdapi/v1/txt2img", {
|
||||||
body: {
|
body: omitUndef({
|
||||||
...config.defaultParams,
|
...config.defaultParams,
|
||||||
...state.task.params,
|
...state.task.params,
|
||||||
...size,
|
...size,
|
||||||
negative_prompt: state.task.params.negative_prompt
|
negative_prompt: state.task.params.negative_prompt
|
||||||
? state.task.params.negative_prompt
|
? state.task.params.negative_prompt
|
||||||
: config.defaultParams?.negative_prompt,
|
: config.defaultParams?.negative_prompt,
|
||||||
},
|
}),
|
||||||
})
|
})
|
||||||
: state.task.type === "img2img"
|
: state.task.type === "img2img"
|
||||||
? workerSdClient.POST("/sdapi/v1/img2img", {
|
? workerSdClient.POST("/sdapi/v1/img2img", {
|
||||||
body: {
|
body: omitUndef({
|
||||||
...config.defaultParams,
|
...config.defaultParams,
|
||||||
...state.task.params,
|
...state.task.params,
|
||||||
...size,
|
...size,
|
||||||
|
@ -209,7 +210,7 @@ async function processGenerationJob(
|
||||||
).then((resp) => resp.arrayBuffer()),
|
).then((resp) => resp.arrayBuffer()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
}),
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { db } from "./db.ts";
|
||||||
export interface GenerationSchema {
|
export interface GenerationSchema {
|
||||||
from: User;
|
from: User;
|
||||||
chat: Chat;
|
chat: Chat;
|
||||||
sdInstanceId?: string; // TODO: change to workerInstanceKey
|
sdInstanceId?: string | undefined; // TODO: change to workerInstanceKey
|
||||||
info?: SdGenerationInfo;
|
info?: SdGenerationInfo | undefined;
|
||||||
startDate?: Date;
|
startDate?: Date | undefined;
|
||||||
endDate?: Date;
|
endDate?: Date | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,19 +1,42 @@
|
||||||
|
export interface KvMemoizeOptions<A extends Deno.KvKey, R> {
|
||||||
|
/**
|
||||||
|
* The time in milliseconds until the cached result expires.
|
||||||
|
*/
|
||||||
|
expireIn?: ((result: R, ...args: A) => number) | number | undefined;
|
||||||
|
/**
|
||||||
|
* Whether to recalculate the result if it was already cached.
|
||||||
|
*
|
||||||
|
* Runs whenever the result is retrieved from the cache.
|
||||||
|
*/
|
||||||
|
shouldRecalculate?: ((result: R, ...args: A) => boolean) | undefined;
|
||||||
|
/**
|
||||||
|
* Whether to cache the result after computing it.
|
||||||
|
*
|
||||||
|
* Runs whenever a new result is computed.
|
||||||
|
*/
|
||||||
|
shouldCache?: ((result: R, ...args: A) => boolean) | undefined;
|
||||||
|
/**
|
||||||
|
* Override the default KV store functions.
|
||||||
|
*/
|
||||||
|
override?: {
|
||||||
|
set: (
|
||||||
|
key: Deno.KvKey,
|
||||||
|
args: A,
|
||||||
|
value: R,
|
||||||
|
options: { expireIn?: number },
|
||||||
|
) => Promise<void>;
|
||||||
|
get: (key: Deno.KvKey, args: A) => Promise<R | undefined>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memoizes the function result in KV storage.
|
* Memoizes the function result in KV store.
|
||||||
*/
|
*/
|
||||||
export function kvMemoize<A extends Deno.KvKey, R>(
|
export function kvMemoize<A extends Deno.KvKey, R>(
|
||||||
db: Deno.Kv,
|
db: Deno.Kv,
|
||||||
key: Deno.KvKey,
|
key: Deno.KvKey,
|
||||||
fn: (...args: A) => Promise<R>,
|
fn: (...args: A) => Promise<R>,
|
||||||
options?: {
|
options?: KvMemoizeOptions<A, R>,
|
||||||
expireIn?: number | ((result: R, ...args: A) => number);
|
|
||||||
shouldRecalculate?: (result: R, ...args: A) => boolean;
|
|
||||||
shouldCache?: (result: R, ...args: A) => boolean;
|
|
||||||
override?: {
|
|
||||||
set: (key: Deno.KvKey, args: A, value: R, options: { expireIn?: number }) => Promise<void>;
|
|
||||||
get: (key: Deno.KvKey, args: A) => Promise<R | undefined>;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
): (...args: A) => Promise<R> {
|
): (...args: A) => Promise<R> {
|
||||||
return async (...args) => {
|
return async (...args) => {
|
||||||
const cachedResult = options?.override?.get
|
const cachedResult = options?.override?.get
|
||||||
|
@ -34,9 +57,9 @@ export function kvMemoize<A extends Deno.KvKey, R>(
|
||||||
|
|
||||||
if (options?.shouldCache?.(result, ...args) ?? (result != null)) {
|
if (options?.shouldCache?.(result, ...args) ?? (result != null)) {
|
||||||
if (options?.override?.set) {
|
if (options?.override?.set) {
|
||||||
await options.override.set(key, args, result, { expireIn });
|
await options.override.set(key, args, result, expireIn != null ? { expireIn } : {});
|
||||||
} else {
|
} else {
|
||||||
await db.set([...key, ...args], result, { expireIn });
|
await db.set([...key, ...args], result, expireIn != null ? { expireIn } : {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ export async function processUploadQueue() {
|
||||||
// send caption in separate message if it couldn't fit
|
// send caption in separate message if it couldn't fit
|
||||||
if (caption.text.length > 1024 && caption.text.length <= 4096) {
|
if (caption.text.length > 1024 && caption.text.length <= 4096) {
|
||||||
await bot.api.sendMessage(state.chat.id, caption.text, {
|
await bot.api.sendMessage(state.chat.id, caption.text, {
|
||||||
reply_to_message_id: resultMessages[0].message_id,
|
reply_to_message_id: resultMessages[0]!.message_id,
|
||||||
allow_sending_without_reply: true,
|
allow_sending_without_reply: true,
|
||||||
entities: caption.entities,
|
entities: caption.entities,
|
||||||
maxWait: 60,
|
maxWait: 60,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { generationQueue } from "../app/generationQueue.ts";
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
|
import { omitUndef } from "../utils/omitUndef.ts";
|
||||||
import { ErisContext } from "./mod.ts";
|
import { ErisContext } from "./mod.ts";
|
||||||
|
|
||||||
export async function cancelCommand(ctx: ErisContext) {
|
export async function cancelCommand(ctx: ErisContext) {
|
||||||
|
@ -7,8 +8,11 @@ export async function cancelCommand(ctx: ErisContext) {
|
||||||
.filter((job) => job.lockUntil < new Date())
|
.filter((job) => job.lockUntil < new Date())
|
||||||
.filter((j) => j.state.from.id === ctx.from?.id);
|
.filter((j) => j.state.from.id === ctx.from?.id);
|
||||||
for (const job of userJobs) await generationQueue.deleteJob(job.id);
|
for (const job of userJobs) await generationQueue.deleteJob(job.id);
|
||||||
await ctx.reply(`Cancelled ${userJobs.length} jobs`, {
|
await ctx.reply(
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
`Cancelled ${userJobs.length} jobs`,
|
||||||
allow_sending_without_reply: true,
|
omitUndef({
|
||||||
});
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
allow_sending_without_reply: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
43
bot/mod.ts
43
bot/mod.ts
|
@ -6,6 +6,7 @@ import { error, info, warning } from "std/log/mod.ts";
|
||||||
import { sessions } from "../api/sessionsRoute.ts";
|
import { sessions } from "../api/sessionsRoute.ts";
|
||||||
import { getConfig, setConfig } from "../app/config.ts";
|
import { getConfig, setConfig } from "../app/config.ts";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
|
import { omitUndef } from "../utils/omitUndef.ts";
|
||||||
import { broadcastCommand } from "./broadcastCommand.ts";
|
import { broadcastCommand } from "./broadcastCommand.ts";
|
||||||
import { cancelCommand } from "./cancelCommand.ts";
|
import { cancelCommand } from "./cancelCommand.ts";
|
||||||
import { img2imgCommand, img2imgQuestion } from "./img2imgCommand.ts";
|
import { img2imgCommand, img2imgQuestion } from "./img2imgCommand.ts";
|
||||||
|
@ -19,11 +20,11 @@ interface SessionData {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErisChatData {
|
interface ErisChatData {
|
||||||
language?: string;
|
language?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErisUserData {
|
interface ErisUserData {
|
||||||
params?: Record<string, string>;
|
params?: Record<string, string> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ErisContext =
|
export type ErisContext =
|
||||||
|
@ -94,10 +95,13 @@ bot.use(async (ctx, next) => {
|
||||||
await next();
|
await next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
try {
|
||||||
await ctx.reply(`Handling update failed: ${err}`, {
|
await ctx.reply(
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
`Handling update failed: ${err}`,
|
||||||
allow_sending_without_reply: true,
|
omitUndef({
|
||||||
});
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
allow_sending_without_reply: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -122,24 +126,33 @@ bot.command("start", async (ctx) => {
|
||||||
const id = ctx.match.trim();
|
const id = ctx.match.trim();
|
||||||
const session = sessions.get(id);
|
const session = sessions.get(id);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
await ctx.reply("Login failed: Invalid session ID", {
|
await ctx.reply(
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
"Login failed: Invalid session ID",
|
||||||
});
|
omitUndef({
|
||||||
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
session.userId = ctx.from?.id;
|
session.userId = ctx.from?.id;
|
||||||
sessions.set(id, session);
|
sessions.set(id, session);
|
||||||
info(`User ${formatUserChat(ctx)} logged in`);
|
info(`User ${formatUserChat(ctx)} logged in`);
|
||||||
// TODO: show link to web ui
|
// TODO: show link to web ui
|
||||||
await ctx.reply("Login successful! You can now return to the WebUI.", {
|
await ctx.reply(
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
"Login successful! You can now return to the WebUI.",
|
||||||
});
|
omitUndef({
|
||||||
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.reply("Hello! Use the /txt2img command to generate an image", {
|
await ctx.reply(
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
"Hello! Use the /txt2img command to generate an image",
|
||||||
});
|
omitUndef({
|
||||||
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
bot.command("txt2img", txt2imgCommand);
|
bot.command("txt2img", txt2imgCommand);
|
||||||
|
|
|
@ -25,7 +25,11 @@ interface PngInfoExtra extends PngInfo {
|
||||||
upscale?: number;
|
upscale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parsePngInfo(pngInfo: string, baseParams?: Partial<PngInfo>, shouldParseSeed?: boolean): Partial<PngInfo> {
|
export function parsePngInfo(
|
||||||
|
pngInfo: string,
|
||||||
|
baseParams?: Partial<PngInfo>,
|
||||||
|
shouldParseSeed?: boolean,
|
||||||
|
): Partial<PngInfo> {
|
||||||
const tags = pngInfo.split(/[,;]+|\.+\s|\n/u);
|
const tags = pngInfo.split(/[,;]+|\.+\s|\n/u);
|
||||||
let part: "prompt" | "negative_prompt" | "params" = "prompt";
|
let part: "prompt" | "negative_prompt" | "params" = "prompt";
|
||||||
const params: Partial<PngInfoExtra> = {};
|
const params: Partial<PngInfoExtra> = {};
|
||||||
|
@ -34,7 +38,7 @@ export function parsePngInfo(pngInfo: string, baseParams?: Partial<PngInfo>, sho
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
const paramValuePair = tag.trim().match(/^(\w+\s*\w*):\s+(.*)$/u);
|
const paramValuePair = tag.trim().match(/^(\w+\s*\w*):\s+(.*)$/u);
|
||||||
if (paramValuePair) {
|
if (paramValuePair) {
|
||||||
const [, param, value] = paramValuePair;
|
const [_match, param = "", value = ""] = paramValuePair;
|
||||||
switch (param.replace(/\s+/u, "").toLowerCase()) {
|
switch (param.replace(/\s+/u, "").toLowerCase()) {
|
||||||
case "positiveprompt":
|
case "positiveprompt":
|
||||||
case "positive":
|
case "positive":
|
||||||
|
@ -67,7 +71,7 @@ export function parsePngInfo(pngInfo: string, baseParams?: Partial<PngInfo>, sho
|
||||||
case "size":
|
case "size":
|
||||||
case "resolution": {
|
case "resolution": {
|
||||||
part = "params";
|
part = "params";
|
||||||
const [width, height] = value.trim()
|
const [width = 0, height = 0] = value.trim()
|
||||||
.split(/\s*[x,]\s*/u, 2)
|
.split(/\s*[x,]\s*/u, 2)
|
||||||
.map((v) => v.trim())
|
.map((v) => v.trim())
|
||||||
.map(Number);
|
.map(Number);
|
||||||
|
@ -103,9 +107,11 @@ export function parsePngInfo(pngInfo: string, baseParams?: Partial<PngInfo>, sho
|
||||||
part = "params";
|
part = "params";
|
||||||
if (shouldParseSeed) {
|
if (shouldParseSeed) {
|
||||||
const seed = Number(value.trim());
|
const seed = Number(value.trim());
|
||||||
params.seed = seed;
|
if (Number.isFinite(seed)) {
|
||||||
break;
|
params.seed = seed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case "model":
|
case "model":
|
||||||
case "modelhash":
|
case "modelhash":
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { CommandContext } from "grammy";
|
import { CommandContext } from "grammy";
|
||||||
import { bold, fmt } from "grammy_parse_mode";
|
import { bold, fmt } from "grammy_parse_mode";
|
||||||
import { StatelessQuestion } from "grammy_stateless_question";
|
import { StatelessQuestion } from "grammy_stateless_question";
|
||||||
|
import { omitUndef } from "../utils/omitUndef.ts";
|
||||||
import { ErisContext } from "./mod.ts";
|
import { ErisContext } from "./mod.ts";
|
||||||
import { getPngInfo, parsePngInfo } from "./parsePngInfo.ts";
|
import { getPngInfo, parsePngInfo } from "./parsePngInfo.ts";
|
||||||
|
|
||||||
|
@ -23,11 +24,13 @@ async function pnginfo(ctx: ErisContext, includeRepliedTo: boolean): Promise<voi
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
"Please send me a PNG file." +
|
"Please send me a PNG file." +
|
||||||
pnginfoQuestion.messageSuffixMarkdown(),
|
pnginfoQuestion.messageSuffixMarkdown(),
|
||||||
{
|
omitUndef(
|
||||||
reply_markup: { force_reply: true, selective: true },
|
{
|
||||||
parse_mode: "Markdown",
|
reply_markup: { force_reply: true, selective: true },
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
parse_mode: "Markdown",
|
||||||
},
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
} as const,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,8 +49,11 @@ async function pnginfo(ctx: ErisContext, includeRepliedTo: boolean): Promise<voi
|
||||||
params.width && params.height ? fmt`${bold("Size")}: ${params.width}x${params.height}` : "",
|
params.width && params.height ? fmt`${bold("Size")}: ${params.width}x${params.height}` : "",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.reply(paramsText.text, {
|
await ctx.reply(
|
||||||
entities: paramsText.entities,
|
paramsText.text,
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
omitUndef({
|
||||||
});
|
entities: paramsText.entities,
|
||||||
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import { CommandContext } from "grammy";
|
import { CommandContext } from "grammy";
|
||||||
import { bold, fmt } from "grammy_parse_mode";
|
import { bold, fmt } from "grammy_parse_mode";
|
||||||
import { activeGenerationWorkers, generationQueue } from "../app/generationQueue.ts";
|
import { activeGenerationWorkers, generationQueue } from "../app/generationQueue.ts";
|
||||||
import { getFlagEmoji } from "../utils/getFlagEmoji.ts";
|
|
||||||
import { ErisContext } from "./mod.ts";
|
|
||||||
import { workerInstanceStore } from "../app/workerInstanceStore.ts";
|
import { workerInstanceStore } from "../app/workerInstanceStore.ts";
|
||||||
|
import { getFlagEmoji } from "../utils/getFlagEmoji.ts";
|
||||||
|
import { omitUndef } from "../utils/omitUndef.ts";
|
||||||
|
import { ErisContext } from "./mod.ts";
|
||||||
|
|
||||||
export async function queueCommand(ctx: CommandContext<ErisContext>) {
|
export async function queueCommand(ctx: CommandContext<ErisContext>) {
|
||||||
let formattedMessage = await getMessageText();
|
let formattedMessage = await getMessageText();
|
||||||
const queueMessage = await ctx.replyFmt(formattedMessage, {
|
const queueMessage = await ctx.replyFmt(
|
||||||
disable_notification: true,
|
formattedMessage,
|
||||||
reply_to_message_id: ctx.message?.message_id,
|
omitUndef({
|
||||||
});
|
disable_notification: true,
|
||||||
|
reply_to_message_id: ctx.message?.message_id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
handleFutureUpdates().catch(() => undefined);
|
handleFutureUpdates().catch(() => undefined);
|
||||||
|
|
||||||
async function getMessageText() {
|
async function getMessageText() {
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
"start": "deno run --unstable --allow-env --allow-read --allow-write --allow-net main.ts"
|
"start": "deno run --unstable --allow-env --allow-read --allow-write --allow-net main.ts"
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react"
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"noUncheckedIndexedAccess": true
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"lineWidth": 100
|
"lineWidth": 100
|
||||||
|
|
|
@ -34,7 +34,8 @@ export function AppHeader(
|
||||||
);
|
);
|
||||||
|
|
||||||
const bot = useSWR(
|
const bot = useSWR(
|
||||||
['bot',"GET",{}] as const, (args) => fetchApi(...args).then(handleResponse),
|
["bot", "GET", {}] as const,
|
||||||
|
(args) => fetchApi(...args).then(handleResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
const userPhoto = useSWR(
|
const userPhoto = useSWR(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { cx } from "@twind/core";
|
import { cx } from "@twind/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
function CounterDigit(props: { value: number; transitionDurationMs?: number }) {
|
function CounterDigit(props: { value: number; transitionDurationMs?: number | undefined }) {
|
||||||
const { value, transitionDurationMs = 1500 } = props;
|
const { value, transitionDurationMs = 1500 } = props;
|
||||||
const rads = -(Math.floor(value) % 1_000_000) * 2 * Math.PI * 0.1;
|
const rads = -(Math.floor(value) % 1_000_000) * 2 * Math.PI * 0.1;
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ const CounterText = (props: { children: React.ReactNode }) => (
|
||||||
export function Counter(props: {
|
export function Counter(props: {
|
||||||
value: number;
|
value: number;
|
||||||
digits: number;
|
digits: number;
|
||||||
fractionDigits?: number;
|
fractionDigits?: number | undefined;
|
||||||
transitionDurationMs?: number;
|
transitionDurationMs?: number | undefined;
|
||||||
className?: string;
|
className?: string | undefined;
|
||||||
postfix?: string;
|
postfix?: string | undefined;
|
||||||
}) {
|
}) {
|
||||||
const { value, digits, fractionDigits = 0, transitionDurationMs, className, postfix } = props;
|
const { value, digits, fractionDigits = 0, transitionDurationMs, className, postfix } = props;
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function WorkersPage(props: { sessionId?: string }) {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<dialog
|
<dialog
|
||||||
className="dialog"
|
className="dialog animate-pop-in backdrop-animate-fade-in"
|
||||||
ref={createWorkerModalRef}
|
ref={createWorkerModalRef}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
|
@ -159,7 +159,7 @@ export function WorkersPage(props: { sessionId?: string }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkerListItem(props: { worker: WorkerData; sessionId?: string }) {
|
function WorkerListItem(props: { worker: WorkerData; sessionId: string | undefined }) {
|
||||||
const { worker, sessionId } = props;
|
const { worker, sessionId } = props;
|
||||||
const editWorkerModalRef = useRef<HTMLDialogElement>(null);
|
const editWorkerModalRef = useRef<HTMLDialogElement>(null);
|
||||||
const deleteWorkerModalRef = useRef<HTMLDialogElement>(null);
|
const deleteWorkerModalRef = useRef<HTMLDialogElement>(null);
|
||||||
|
@ -231,7 +231,7 @@ function WorkerListItem(props: { worker: WorkerData; sessionId?: string }) {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<dialog
|
<dialog
|
||||||
className="dialog"
|
className="dialog animate-pop-in backdrop-animate-fade-in"
|
||||||
ref={editWorkerModalRef}
|
ref={editWorkerModalRef}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
|
@ -293,7 +293,7 @@ function WorkerListItem(props: { worker: WorkerData; sessionId?: string }) {
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog
|
<dialog
|
||||||
className="dialog"
|
className="dialog animate-pop-in backdrop-animate-fade-in"
|
||||||
ref={deleteWorkerModalRef}
|
ref={deleteWorkerModalRef}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
|
|
21
ui/twind.ts
21
ui/twind.ts
|
@ -27,14 +27,11 @@ injectGlobal`
|
||||||
rgba(0, 0, 0, 0.1) 14px,
|
rgba(0, 0, 0, 0.1) 14px,
|
||||||
rgba(0, 0, 0, 0.1) 28px
|
rgba(0, 0, 0, 0.1) 28px
|
||||||
);
|
);
|
||||||
animation: bg-stripes-scroll 0.5s linear infinite;
|
animation: bg-scroll 0.5s linear infinite;
|
||||||
background-size: 40px 40px;
|
background-size: 40px 40px;
|
||||||
}
|
}
|
||||||
@keyframes bg-stripes-scroll {
|
@keyframes bg-scroll {
|
||||||
0% {
|
to {
|
||||||
background-position: 0 40px;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 40px 0;
|
background-position: 40px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,11 +60,11 @@ injectGlobal`
|
||||||
opacity 0s;
|
opacity 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-fade-in {
|
.backdrop-animate-fade-in::backdrop {
|
||||||
animation: fade-in 0.3s ease-out forwards;
|
animation: fade-in 0.3s ease-out forwards;
|
||||||
}
|
}
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
0% {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,13 +73,13 @@ injectGlobal`
|
||||||
animation: pop-in 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
animation: pop-in 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
||||||
}
|
}
|
||||||
@keyframes pop-in {
|
@keyframes pop-in {
|
||||||
0% {
|
from {
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.link {
|
.link {
|
||||||
@apply text-sky-600 dark:text-sky-500 rounded-sm focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-sky-600 dark:focus:outline-sky-500;
|
@apply text-sky-600 dark:text-sky-500 rounded-sm focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-sky-600 dark:focus:outline-sky-500;
|
||||||
|
@ -134,8 +131,8 @@ injectGlobal`
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
@apply animate-pop-in backdrop:animate-fade-in overflow-hidden overflow-y-auto rounded-md
|
@apply overflow-hidden overflow-y-auto rounded-md
|
||||||
bg-zinc-100 text-zinc-900 shadow-lg backdrop:bg-black/20 dark:bg-zinc-800 dark:text-zinc-100;
|
bg-zinc-100 text-zinc-900 shadow-lg backdrop:bg-black/30 dark:bg-zinc-800 dark:text-zinc-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { Chat, User } from "grammy_types";
|
import { Chat, User } from "grammy_types";
|
||||||
|
|
||||||
export function formatUserChat(
|
export function formatUserChat(
|
||||||
ctx: { from?: User; chat?: Chat; workerInstanceKey?: string },
|
ctx: {
|
||||||
|
from?: User | undefined;
|
||||||
|
chat?: Chat | undefined;
|
||||||
|
workerInstanceKey?: string | undefined;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const msg: string[] = [];
|
const msg: string[] = [];
|
||||||
if (ctx.from) {
|
if (ctx.from) {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Removes all undefined properties from an object.
|
||||||
|
*/
|
||||||
|
export function omitUndef<O extends object | undefined>(object: O):
|
||||||
|
& { [K in keyof O as undefined extends O[K] ? never : K]: O[K] }
|
||||||
|
& { [K in keyof O as undefined extends O[K] ? K : never]?: O[K] & ({} | null) } {
|
||||||
|
if (object == undefined) return object as never;
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(object).filter(([, v]) => v !== undefined),
|
||||||
|
) as never;
|
||||||
|
}
|
Loading…
Reference in New Issue