feat: webui initial impl
This commit is contained in:
parent
70a9b1180d
commit
82fdd0f21c
|
@ -0,0 +1,62 @@
|
||||||
|
import { initTRPC } from "@trpc/server";
|
||||||
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||||
|
import { serveDir } from "std/http/file_server.ts";
|
||||||
|
import { transform } from "swc";
|
||||||
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
|
|
||||||
|
const t = initTRPC.create();
|
||||||
|
|
||||||
|
export const appRouter = t.router({
|
||||||
|
ping: t.procedure.query(() => "pong"),
|
||||||
|
getAllGenerationJobs: t.procedure.query(() => {
|
||||||
|
return generationQueue.getAllJobs();
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter;
|
||||||
|
|
||||||
|
const webuiRoot = new URL("./webui/", import.meta.url);
|
||||||
|
|
||||||
|
export async function serveApi() {
|
||||||
|
const server = Deno.serve({ port: 8000 }, async (request) => {
|
||||||
|
const requestPath = new URL(request.url).pathname;
|
||||||
|
const filePath = webuiRoot.pathname + requestPath;
|
||||||
|
const fileExt = filePath.split("/").pop()?.split(".").pop()?.toLowerCase();
|
||||||
|
const fileExists = await Deno.stat(filePath).then((stat) => stat.isFile).catch(() => false);
|
||||||
|
|
||||||
|
if (requestPath.startsWith("/api/trpc/")) {
|
||||||
|
return fetchRequestHandler({
|
||||||
|
endpoint: "/api/trpc",
|
||||||
|
req: request,
|
||||||
|
router: appRouter,
|
||||||
|
createContext: () => ({}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileExists) {
|
||||||
|
if (fileExt === "ts" || fileExt === "tsx") {
|
||||||
|
const file = await Deno.readTextFile(filePath);
|
||||||
|
const result = await transform(file, {
|
||||||
|
jsc: {
|
||||||
|
parser: {
|
||||||
|
syntax: "typescript",
|
||||||
|
tsx: fileExt === "tsx",
|
||||||
|
},
|
||||||
|
target: "es2022",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return new Response(result.code, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/javascript",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serveDir(request, {
|
||||||
|
fsRoot: webuiRoot.pathname,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.finished;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="/favicon.png" type="image/png">
|
||||||
|
<script type="module" src="/main.tsx"></script>
|
||||||
|
<title>Eris</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,78 @@
|
||||||
|
/// <reference lib="dom" />
|
||||||
|
import { QueryClient, QueryClientProvider } from "https://esm.sh/@tanstack/react-query@4.35.3";
|
||||||
|
import { httpBatchLink } from "https://esm.sh/@trpc/client@10.38.4/links/httpBatchLink";
|
||||||
|
import { createTRPCReact } from "https://esm.sh/@trpc/react-query@10.38.4";
|
||||||
|
import { defineConfig, injectGlobal, install, tw } from "https://esm.sh/@twind/core@1.1.3";
|
||||||
|
import presetTailwind from "https://esm.sh/@twind/preset-tailwind@1.1.4";
|
||||||
|
import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
|
||||||
|
import FlipMove from "https://esm.sh/react-flip-move@3.0.5";
|
||||||
|
import React from "https://esm.sh/react@18.2.0";
|
||||||
|
import type { AppRouter } from "../mod.ts";
|
||||||
|
|
||||||
|
const twConfig = defineConfig({
|
||||||
|
presets: [presetTailwind()],
|
||||||
|
});
|
||||||
|
|
||||||
|
install(twConfig);
|
||||||
|
|
||||||
|
injectGlobal`
|
||||||
|
html {
|
||||||
|
@apply h-full bg-white text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply flex min-h-full flex-col items-stretch;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const trpc = createTRPCReact<AppRouter>();
|
||||||
|
|
||||||
|
const trpcClient = trpc.createClient({
|
||||||
|
links: [
|
||||||
|
httpBatchLink({
|
||||||
|
url: "/api/trpc",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
suspense: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
createRoot(document.body).render(
|
||||||
|
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<App />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</trpc.Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const allJobs = trpc.getAllGenerationJobs.useQuery(undefined, { refetchInterval: 1000 });
|
||||||
|
const processingJobs = (allJobs.data ?? [])
|
||||||
|
.filter((job) => new Date(job.lockUntil) > new Date()).map((job) => ({ ...job, index: 0 }));
|
||||||
|
const waitingJobs = (allJobs.data ?? [])
|
||||||
|
.filter((job) => new Date(job.lockUntil) <= new Date())
|
||||||
|
.map((job, index) => ({ ...job, index: index + 1 }));
|
||||||
|
const jobs = [...processingJobs, ...waitingJobs];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlipMove
|
||||||
|
typeName={"ul"}
|
||||||
|
className={tw("p-4")}
|
||||||
|
enterAnimation="fade"
|
||||||
|
leaveAnimation="fade"
|
||||||
|
>
|
||||||
|
{jobs.map((job) => (
|
||||||
|
<li key={job.id.join("/")} className={tw("")}>
|
||||||
|
{job.index}. {job.state.from.first_name} {job.state.from.last_name}{" "}
|
||||||
|
{job.state.from.username} {job.state.from.language_code}{" "}
|
||||||
|
{((job.state.progress ?? 0) * 100).toFixed(0)}% {job.state.sdInstanceId}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</FlipMove>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import * as SdApi from "../sd/sdApi.ts";
|
import * as SdApi from "./sdApi.ts";
|
||||||
import { db } from "./db.ts";
|
import { db } from "./db.ts";
|
||||||
|
|
||||||
export interface ConfigData {
|
export interface ConfigData {
|
||||||
|
|
|
@ -2,19 +2,19 @@ import { promiseState } from "async";
|
||||||
import { Chat, Message, User } from "grammy_types";
|
import { Chat, Message, User } from "grammy_types";
|
||||||
import { JobData, Queue, Worker } from "kvmq";
|
import { JobData, Queue, Worker } from "kvmq";
|
||||||
import createOpenApiClient from "openapi_fetch";
|
import createOpenApiClient from "openapi_fetch";
|
||||||
import { delay } from "std/async";
|
import { delay } from "std/async/delay.ts";
|
||||||
import { decode, encode } from "std/encoding/base64";
|
import { decode, encode } from "std/encoding/base64.ts";
|
||||||
import { getLogger } from "std/log";
|
import { getLogger } from "std/log/mod.ts";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
import { bot } from "../bot/mod.ts";
|
import { bot } from "../bot/mod.ts";
|
||||||
import { SdError } from "../sd/SdError.ts";
|
import { PngInfo } from "../bot/parsePngInfo.ts";
|
||||||
import { PngInfo } from "../sd/parsePngInfo.ts";
|
|
||||||
import * as SdApi from "../sd/sdApi.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 { SdError } from "./SdError.ts";
|
||||||
import { getConfig, SdInstanceData } from "./config.ts";
|
import { getConfig, SdInstanceData } from "./config.ts";
|
||||||
import { db, fs } from "./db.ts";
|
import { db, fs } from "./db.ts";
|
||||||
import { SdGenerationInfo } from "./generationStore.ts";
|
import { SdGenerationInfo } from "./generationStore.ts";
|
||||||
|
import * as SdApi from "./sdApi.ts";
|
||||||
import { uploadQueue } from "./uploadQueue.ts";
|
import { uploadQueue } from "./uploadQueue.ts";
|
||||||
|
|
||||||
const logger = () => getLogger();
|
const logger = () => getLogger();
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { InputFile, InputMediaBuilder } from "grammy";
|
||||||
import { bold, fmt } from "grammy_parse_mode";
|
import { bold, fmt } from "grammy_parse_mode";
|
||||||
import { Chat, Message, User } from "grammy_types";
|
import { Chat, Message, User } from "grammy_types";
|
||||||
import { Queue } from "kvmq";
|
import { Queue } from "kvmq";
|
||||||
import { format } from "std/fmt/duration";
|
import { format } from "std/fmt/duration.ts";
|
||||||
import { getLogger } from "std/log";
|
import { getLogger } from "std/log/mod.ts";
|
||||||
import { bot } from "../bot/mod.ts";
|
import { bot } from "../bot/mod.ts";
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
import { db, fs } from "./db.ts";
|
import { db, fs } from "./db.ts";
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { CommandContext } from "grammy";
|
import { CommandContext } from "grammy";
|
||||||
import { bold, fmt, FormattedString } from "grammy_parse_mode";
|
import { bold, fmt, FormattedString } from "grammy_parse_mode";
|
||||||
import { distinctBy } from "std/collections";
|
import { distinctBy } from "std/collections/distinct_by.ts";
|
||||||
import { getConfig } from "../app/config.ts";
|
import { getConfig } from "../app/config.ts";
|
||||||
import { generationStore } from "../app/generationStore.ts";
|
import { generationStore } from "../app/generationStore.ts";
|
||||||
import { ErisContext, logger } from "./mod.ts";
|
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
|
import { ErisContext, logger } from "./mod.ts";
|
||||||
|
|
||||||
export async function broadcastCommand(ctx: CommandContext<ErisContext>) {
|
export async function broadcastCommand(ctx: CommandContext<ErisContext>) {
|
||||||
if (!ctx.from?.username) {
|
if (!ctx.from?.username) {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { CommandContext } from "grammy";
|
import { CommandContext } from "grammy";
|
||||||
import { StatelessQuestion } from "grammy_stateless_question";
|
import { StatelessQuestion } from "grammy_stateless_question";
|
||||||
import { maxBy } from "std/collections";
|
import { maxBy } from "std/collections/max_by.ts";
|
||||||
import { getConfig } from "../app/config.ts";
|
import { getConfig } from "../app/config.ts";
|
||||||
import { generationQueue } from "../app/generationQueue.ts";
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
import { parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
import { ErisContext, logger } from "./mod.ts";
|
import { ErisContext, logger } from "./mod.ts";
|
||||||
|
import { parsePngInfo, PngInfo } from "./parsePngInfo.ts";
|
||||||
|
|
||||||
type QuestionState = { fileId?: string; params?: Partial<PngInfo> };
|
type QuestionState = { fileId?: string; params?: Partial<PngInfo> };
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Api, Bot, Context, RawApi, session, SessionFlavor } from "grammy";
|
import { Api, Bot, Context, RawApi, session, SessionFlavor } from "grammy";
|
||||||
import { FileFlavor, hydrateFiles } from "grammy_files";
|
import { FileFlavor, hydrateFiles } from "grammy_files";
|
||||||
import { hydrateReply, ParseModeFlavor } from "grammy_parse_mode";
|
import { hydrateReply, ParseModeFlavor } from "grammy_parse_mode";
|
||||||
import { getLogger } from "std/log";
|
import { getLogger } from "std/log/mod.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 { broadcastCommand } from "./broadcastCommand.ts";
|
import { broadcastCommand } from "./broadcastCommand.ts";
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
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 { getPngInfo, parsePngInfo } from "../sd/parsePngInfo.ts";
|
|
||||||
import { ErisContext } from "./mod.ts";
|
import { ErisContext } from "./mod.ts";
|
||||||
|
import { getPngInfo, parsePngInfo } from "./parsePngInfo.ts";
|
||||||
|
|
||||||
export const pnginfoQuestion = new StatelessQuestion<ErisContext>(
|
export const pnginfoQuestion = new StatelessQuestion<ErisContext>(
|
||||||
"pnginfo",
|
"pnginfo",
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { CommandContext } from "grammy";
|
||||||
import { StatelessQuestion } from "grammy_stateless_question";
|
import { StatelessQuestion } from "grammy_stateless_question";
|
||||||
import { getConfig } from "../app/config.ts";
|
import { getConfig } from "../app/config.ts";
|
||||||
import { generationQueue } from "../app/generationQueue.ts";
|
import { generationQueue } from "../app/generationQueue.ts";
|
||||||
import { getPngInfo, parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
|
||||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||||
import { ErisContext, logger } from "./mod.ts";
|
import { ErisContext, logger } from "./mod.ts";
|
||||||
|
import { getPngInfo, parsePngInfo, PngInfo } from "./parsePngInfo.ts";
|
||||||
|
|
||||||
export const txt2imgQuestion = new StatelessQuestion<ErisContext>(
|
export const txt2imgQuestion = new StatelessQuestion<ErisContext>(
|
||||||
"txt2img",
|
"txt2img",
|
||||||
|
|
|
@ -2,20 +2,26 @@
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"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": {
|
||||||
|
"jsx": "react"
|
||||||
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"lineWidth": 100
|
"lineWidth": 100
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"std/log": "https://deno.land/std@0.201.0/log/mod.ts",
|
"std/dotenv/": "https://deno.land/std@0.201.0/dotenv/",
|
||||||
"std/async": "https://deno.land/std@0.201.0/async/mod.ts",
|
"std/log/": "https://deno.land/std@0.201.0/log/",
|
||||||
"std/fmt/duration": "https://deno.land/std@0.202.0/fmt/duration.ts",
|
"std/async/": "https://deno.land/std@0.201.0/async/",
|
||||||
"std/collections": "https://deno.land/std@0.202.0/collections/mod.ts",
|
"std/fmt/": "https://deno.land/std@0.202.0/fmt/",
|
||||||
"std/encoding/base64": "https://deno.land/std@0.202.0/encoding/base64.ts",
|
"std/collections/": "https://deno.land/std@0.202.0/collections/",
|
||||||
|
"std/encoding/": "https://deno.land/std@0.202.0/encoding/",
|
||||||
|
"std/http/": "https://deno.land/std@0.202.0/http/",
|
||||||
"async": "https://deno.land/x/async@v2.0.2/mod.ts",
|
"async": "https://deno.land/x/async@v2.0.2/mod.ts",
|
||||||
"ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
|
"ulid": "https://deno.land/x/ulid@v0.3.0/mod.ts",
|
||||||
"indexed_kv": "https://deno.land/x/indexed_kv@v0.4.0/mod.ts",
|
"indexed_kv": "https://deno.land/x/indexed_kv@v0.4.0/mod.ts",
|
||||||
"kvmq": "https://deno.land/x/kvmq@v0.2.0/mod.ts",
|
"kvmq": "https://deno.land/x/kvmq@v0.2.0/mod.ts",
|
||||||
"kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts",
|
"kvfs": "https://deno.land/x/kvfs@v0.1.0/mod.ts",
|
||||||
|
"swc": "https://deno.land/x/swc@0.2.1/mod.ts",
|
||||||
"grammy": "https://lib.deno.dev/x/grammy@1/mod.ts",
|
"grammy": "https://lib.deno.dev/x/grammy@1/mod.ts",
|
||||||
"grammy_types": "https://lib.deno.dev/x/grammy_types@3/mod.ts",
|
"grammy_types": "https://lib.deno.dev/x/grammy_types@3/mod.ts",
|
||||||
"grammy_autoquote": "https://lib.deno.dev/x/grammy_autoquote@1/mod.ts",
|
"grammy_autoquote": "https://lib.deno.dev/x/grammy_autoquote@1/mod.ts",
|
||||||
|
@ -25,6 +31,8 @@
|
||||||
"file_type": "https://esm.sh/file-type@18.5.0",
|
"file_type": "https://esm.sh/file-type@18.5.0",
|
||||||
"png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0",
|
"png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0",
|
||||||
"png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0",
|
"png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0",
|
||||||
"openapi_fetch": "https://esm.sh/openapi-fetch@0.7.6"
|
"openapi_fetch": "https://esm.sh/openapi-fetch@0.7.6",
|
||||||
|
"@trpc/server": "https://esm.sh/@trpc/server@10.38.4",
|
||||||
|
"@trpc/server/": "https://esm.sh/@trpc/server@10.38.4/"
|
||||||
}
|
}
|
||||||
}
|
}
|
11
main.ts
11
main.ts
|
@ -1,18 +1,23 @@
|
||||||
import "https://deno.land/std@0.201.0/dotenv/load.ts";
|
import "std/dotenv/load.ts";
|
||||||
import { handlers, setup } from "std/log";
|
import { ConsoleHandler } from "std/log/handlers.ts";
|
||||||
|
import { setup } from "std/log/mod.ts";
|
||||||
|
import { serveApi } from "./api/mod.ts";
|
||||||
import { runAllTasks } from "./app/mod.ts";
|
import { runAllTasks } from "./app/mod.ts";
|
||||||
import { bot } from "./bot/mod.ts";
|
import { bot } from "./bot/mod.ts";
|
||||||
|
|
||||||
|
// setup logging
|
||||||
setup({
|
setup({
|
||||||
handlers: {
|
handlers: {
|
||||||
console: new handlers.ConsoleHandler("DEBUG"),
|
console: new ConsoleHandler("DEBUG"),
|
||||||
},
|
},
|
||||||
loggers: {
|
loggers: {
|
||||||
default: { level: "DEBUG", handlers: ["console"] },
|
default: { level: "DEBUG", handlers: ["console"] },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// run parts of the app
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
bot.start(),
|
bot.start(),
|
||||||
runAllTasks(),
|
runAllTasks(),
|
||||||
|
serveApi(),
|
||||||
]);
|
]);
|
||||||
|
|
Loading…
Reference in New Issue