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";
|
||||
|
||||
export interface ConfigData {
|
||||
|
|
|
@ -2,19 +2,19 @@ import { promiseState } from "async";
|
|||
import { Chat, Message, User } from "grammy_types";
|
||||
import { JobData, Queue, Worker } from "kvmq";
|
||||
import createOpenApiClient from "openapi_fetch";
|
||||
import { delay } from "std/async";
|
||||
import { decode, encode } from "std/encoding/base64";
|
||||
import { getLogger } from "std/log";
|
||||
import { delay } from "std/async/delay.ts";
|
||||
import { decode, encode } from "std/encoding/base64.ts";
|
||||
import { getLogger } from "std/log/mod.ts";
|
||||
import { ulid } from "ulid";
|
||||
import { bot } from "../bot/mod.ts";
|
||||
import { SdError } from "../sd/SdError.ts";
|
||||
import { PngInfo } from "../sd/parsePngInfo.ts";
|
||||
import * as SdApi from "../sd/sdApi.ts";
|
||||
import { PngInfo } from "../bot/parsePngInfo.ts";
|
||||
import { formatOrdinal } from "../utils/formatOrdinal.ts";
|
||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||
import { SdError } from "./SdError.ts";
|
||||
import { getConfig, SdInstanceData } from "./config.ts";
|
||||
import { db, fs } from "./db.ts";
|
||||
import { SdGenerationInfo } from "./generationStore.ts";
|
||||
import * as SdApi from "./sdApi.ts";
|
||||
import { uploadQueue } from "./uploadQueue.ts";
|
||||
|
||||
const logger = () => getLogger();
|
||||
|
|
|
@ -3,8 +3,8 @@ import { InputFile, InputMediaBuilder } from "grammy";
|
|||
import { bold, fmt } from "grammy_parse_mode";
|
||||
import { Chat, Message, User } from "grammy_types";
|
||||
import { Queue } from "kvmq";
|
||||
import { format } from "std/fmt/duration";
|
||||
import { getLogger } from "std/log";
|
||||
import { format } from "std/fmt/duration.ts";
|
||||
import { getLogger } from "std/log/mod.ts";
|
||||
import { bot } from "../bot/mod.ts";
|
||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||
import { db, fs } from "./db.ts";
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CommandContext } from "grammy";
|
||||
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 { generationStore } from "../app/generationStore.ts";
|
||||
import { ErisContext, logger } from "./mod.ts";
|
||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||
import { ErisContext, logger } from "./mod.ts";
|
||||
|
||||
export async function broadcastCommand(ctx: CommandContext<ErisContext>) {
|
||||
if (!ctx.from?.username) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { CommandContext } from "grammy";
|
||||
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 { generationQueue } from "../app/generationQueue.ts";
|
||||
import { parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||
import { ErisContext, logger } from "./mod.ts";
|
||||
import { parsePngInfo, PngInfo } from "./parsePngInfo.ts";
|
||||
|
||||
type QuestionState = { fileId?: string; params?: Partial<PngInfo> };
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Api, Bot, Context, RawApi, session, SessionFlavor } from "grammy";
|
||||
import { FileFlavor, hydrateFiles } from "grammy_files";
|
||||
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 { formatUserChat } from "../utils/formatUserChat.ts";
|
||||
import { broadcastCommand } from "./broadcastCommand.ts";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CommandContext } from "grammy";
|
||||
import { bold, fmt } from "grammy_parse_mode";
|
||||
import { StatelessQuestion } from "grammy_stateless_question";
|
||||
import { getPngInfo, parsePngInfo } from "../sd/parsePngInfo.ts";
|
||||
import { ErisContext } from "./mod.ts";
|
||||
import { getPngInfo, parsePngInfo } from "./parsePngInfo.ts";
|
||||
|
||||
export const pnginfoQuestion = new StatelessQuestion<ErisContext>(
|
||||
"pnginfo",
|
||||
|
|
|
@ -2,9 +2,9 @@ import { CommandContext } from "grammy";
|
|||
import { StatelessQuestion } from "grammy_stateless_question";
|
||||
import { getConfig } from "../app/config.ts";
|
||||
import { generationQueue } from "../app/generationQueue.ts";
|
||||
import { getPngInfo, parsePngInfo, PngInfo } from "../sd/parsePngInfo.ts";
|
||||
import { formatUserChat } from "../utils/formatUserChat.ts";
|
||||
import { ErisContext, logger } from "./mod.ts";
|
||||
import { getPngInfo, parsePngInfo, PngInfo } from "./parsePngInfo.ts";
|
||||
|
||||
export const txt2imgQuestion = new StatelessQuestion<ErisContext>(
|
||||
"txt2img",
|
||||
|
|
|
@ -2,20 +2,26 @@
|
|||
"tasks": {
|
||||
"start": "deno run --unstable --allow-env --allow-read --allow-write --allow-net main.ts"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
},
|
||||
"fmt": {
|
||||
"lineWidth": 100
|
||||
},
|
||||
"imports": {
|
||||
"std/log": "https://deno.land/std@0.201.0/log/mod.ts",
|
||||
"std/async": "https://deno.land/std@0.201.0/async/mod.ts",
|
||||
"std/fmt/duration": "https://deno.land/std@0.202.0/fmt/duration.ts",
|
||||
"std/collections": "https://deno.land/std@0.202.0/collections/mod.ts",
|
||||
"std/encoding/base64": "https://deno.land/std@0.202.0/encoding/base64.ts",
|
||||
"std/dotenv/": "https://deno.land/std@0.201.0/dotenv/",
|
||||
"std/log/": "https://deno.land/std@0.201.0/log/",
|
||||
"std/async/": "https://deno.land/std@0.201.0/async/",
|
||||
"std/fmt/": "https://deno.land/std@0.202.0/fmt/",
|
||||
"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",
|
||||
"ulid": "https://deno.land/x/ulid@v0.3.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",
|
||||
"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_types": "https://lib.deno.dev/x/grammy_types@3/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",
|
||||
"png_chunks_extract": "https://esm.sh/png-chunks-extract@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 { handlers, setup } from "std/log";
|
||||
import "std/dotenv/load.ts";
|
||||
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 { bot } from "./bot/mod.ts";
|
||||
|
||||
// setup logging
|
||||
setup({
|
||||
handlers: {
|
||||
console: new handlers.ConsoleHandler("DEBUG"),
|
||||
console: new ConsoleHandler("DEBUG"),
|
||||
},
|
||||
loggers: {
|
||||
default: { level: "DEBUG", handlers: ["console"] },
|
||||
},
|
||||
});
|
||||
|
||||
// run parts of the app
|
||||
await Promise.all([
|
||||
bot.start(),
|
||||
runAllTasks(),
|
||||
serveApi(),
|
||||
]);
|
||||
|
|
Loading…
Reference in New Issue