From 82fdd0f21ce993a9f56239aeb9cd8a18fa87edde Mon Sep 17 00:00:00 2001 From: pinks Date: Tue, 26 Sep 2023 12:43:36 +0200 Subject: [PATCH] feat: webui initial impl --- api/mod.ts | 62 ++++++++++++++++++++++++ api/webui/favicon.png | Bin 0 -> 37560 bytes api/webui/index.html | 13 ++++++ api/webui/main.tsx | 78 +++++++++++++++++++++++++++++++ {sd => app}/SdError.ts | 0 app/config.ts | 2 +- app/generationQueue.ts | 12 ++--- {sd => app}/sdApi.ts | 0 app/uploadQueue.ts | 4 +- bot/broadcastCommand.ts | 4 +- bot/img2imgCommand.ts | 4 +- bot/mod.ts | 2 +- {sd => bot}/parsePngInfo.test.ts | 0 {sd => bot}/parsePngInfo.ts | 0 bot/pnginfoCommand.ts | 2 +- bot/txt2imgCommand.ts | 2 +- deno.jsonc => deno.json | 20 +++++--- main.ts | 11 +++-- 18 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 api/mod.ts create mode 100644 api/webui/favicon.png create mode 100644 api/webui/index.html create mode 100644 api/webui/main.tsx rename {sd => app}/SdError.ts (100%) rename {sd => app}/sdApi.ts (100%) rename {sd => bot}/parsePngInfo.test.ts (100%) rename {sd => bot}/parsePngInfo.ts (100%) rename deno.jsonc => deno.json (61%) diff --git a/api/mod.ts b/api/mod.ts new file mode 100644 index 0000000..191c8bd --- /dev/null +++ b/api/mod.ts @@ -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; +} diff --git a/api/webui/favicon.png b/api/webui/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..90ecd66c9628dcae8f3bf5f5cfa7d1887a2dff0b GIT binary patch literal 37560 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:CxZ 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

_*2%%)gWdhzZvvQ)GB9JZ;-UldVg)om zF0|AH)DU#2T)%GPxg|@>%V7#nqjth&%zawN5>|e174Qjg101)1JYYykw^Xz&^C*C0VD`+oSGTm6&stdVtL)_ zXF;GGF-EyB@mq8HK&?E1HZ{}WOE!;owKV;pcVcv3bz#NjvFS;_f`lb^CjeqknTLbH zne4-Ev(2bGx_{I^Ir0`Lb;ybe%kKwB= zcpnn}S*j=twxIQ-t~=`Spnhi#V0@o(Y>FrbY}kk0Y3=XteQMYKe?6juqVCAR;B{x7 zbKcVunKY743MmICMjv0Xzy{*gZn5e1?cQ!E1stU`lic3rRInDiME;HPY-EF9!nC=4x(pYC}`)#9|>8)^A z39hbM`D;kCy991i@NMwDV1B*CzgQ{;O*SGP6lS;)^gP7Pi382`AbxjflQB`>-re>x zSmq)#HAgBt4jh0N&S(5ek*oOMBNQBe_hsZkGz$3l~2B?2JE zuZfk$i69FjGb_y%`GJQ!hx__o*}Hqok7iARK1?pJ+IaQ_e+W=1QoU&dv9?|lHN>^e zX4N0qx#RYU$+-`sBSLGIty=v*PCq9IGE>Un;nAGBeA+^Tq}XsrW8-aOQzM5e%B!x4 zh>!b;6*x4J6Yv@UobK?!Wz?B^J3E?hoSL8C7XdE2it3fmJ7l8eAXq{;GVM3$IhUSf zBx;E{poxVhlS5{z$tL7n*=8rE8alc<-!RxM2svjSo*3(uw}xAB&CQ_4jN3M1B)Jq`x%)K08AT%p5F>*+P2{JOnjJ(F@s8s*tLU8_&5gH76?v@1Y(m?Qzl3zzT~rqP1gyCzci` zJT$ICK)RsO)NlFcJ6Fyb41J(pl(~Avy5Ghm#Y2$7V_Cg@%CKiTX#*cy!=8ipk89Mg z2TH=qmakj)Cy`VXMnmf!is5b93BM-CLjXP4+1h-|#N=30Wm(N#A+eD+V+j|fH&&&Hr#K}vWGijJesJCY zDq{Wyf*Vd4^h@mQ>}Yy<-fG2G$7q>BGi>n$PQDDRi285hbDVoR!T#b)UDg_XV@a*Kk;7t zG03|yah~^XG3xaFT}`dGOwLWT)|9XKQD8LWVla_&VpH7|hYk)xSm4Rz;OJZZ!@c+G zES8ay)a(uM*=c_(QgX80zbOQK<;ZUvc_|=OA1h#VHS3O4{#`f|J=@f^2+c((BY6%`G=g zYo@wtDr@gkMuc4LE)YN>hV^Yp+_|6_nOO+{|WrD1VVzXRb)e`=MH zPLR1DDQu;piyq%J4`UPX6UPUs?{*sI7N9+7>q}a*9<%ol%xIZUC@|x-Ine;kL5-F= z7APJm*oWV-d11#>p^w*3o?ZwYvsB#{w$LC#edeLLA@jt z#NPSQ7Ne=VrMdnl&FpMnRaNZ+%8=kI$hHJ%Uam%RcVQ10p+lWLKN%e#d;uy1O;rWu zx2mGUehgFvBuS}fnNIxB7meTR74E}Nh?T}OMcsIyWaF5h({}ZCcfB=lT-Xmxtiub; zo;W|*@8VxhL_mKE)bJudRH>{w_uTJZ5)~JdfN&r!+Hj&sTzg0tNPdzV$W)`50u(tU zOg=G^- z_*%QwVIOF2y=7*83VqN*PG#*+q{@IBAXJadxhbFtM@B4y^Uh5c7=XR5I0!SkjB~od z&hFM%=Jfi#umLouCw;q5ILpkh(}@ab4rmK+6a2jOyUcSGZK6Q=p?VF6*v;-dUV{St9vG@v#f?pZ!0?|qoS6gpWB@b=HCoX)=&PNd7!_T&X6@-0hDAq4 zlf-|PuYrCr?t&u|LlXL?R+Qf2ov!Jj@tu2id~nS)xbE_jQco{iR{I3xkHoqsW+Xs@ zG=^Dgu+I3_V7vO|=AOo1%0#00y47d=MG_!MLd#9V zX1^$lODBZxvMF*z)cuH`=(YO69(C6nbMtO6qh&@gpKvt88`LMVh33BlQ)tjqC&J2yBy48X|4-OQcm_JUB?84XQS*PVWLu5I`BZI_N3w7n^jk;`l9);}YW zOViMTQwv9}Gu@1AMU3!pSKsSRoee*e`}x5apZ>gGfFy-R7^z(+cL`=8Ni-846x1L& ztuu6VwbtKgG}@<2E0;bZRme6XQE_u{vK?6$-lfN(pV78-wKv{4XSX*ehlHeO6qG&( zNkpY|B1qkE7$qb_fMgkccA^C3fClrFy1VnWDedeIW|=`!>BI*!9jYhCf`$kwB>{Ct zV%nPO+Ladsg$6^AgO)N;3P5QHV$5x!31FNq$ebJ7ziY=Oy%QrxqJlz;SFJhi_j0AY z7{G;he(vz+A?z(pDcjJ--DJK=|>_w zDU1n^sXOhAO&0_O2ZfaXm1}u$A2jtzJE5_x)zETa|K;`S?rkAbWyZ=iYo1Vr zhphk*x&cNlEu##?OCoZO4i0XqZ#?>Ash?k1?eewH$%Fheh^vsTM+TDDcHV#n3k}V$ zj@)Dyi}g#El>SC6ldWa8m{1|44|D)Aw83wf85;k4=cxKYINtG$=(v@sIr-0nr#+ki zo?T7U;3d^2un=7m258U$neFZB{QKnG^vBFHgErX71Ll*-Lh}KNpoSg}LJ~YF$ji!K zS6NlFS)o#fPxRq6PzluvqIEQ5O5w79I zxrw!>D4iZ0+g;zV|5lM$tg2hF?vIi{|6Ed3@E-xqqkYUm&o4AUk?bv?d27xqEPFx{ zAY0A>O}~#$1aTq9U~@S~2l}5G8tH!=@@wZ46Ee(SXHqls35ctKIJ7Vd57ehSDJ=8JXraA94H<8Qek@6fPFTHa{plCV1LO*t zI*P734rmfgrkvObS$AFCKl|js?q{L3M99)5<#(s&WL*#HO%N@lE4L-AP&qu$6t1QdOD!g{d(y|9o35i&924rHaITe6+=SF6l z4;|h8eJjWZ)YYwdQW2!A_!Jn4h4lt912(*A)Gfdb;m95S(g44_qSB{Da&ak6?WpZ{ zuSb3tzGt^s7lzb*KN*-Dc@-+XWx1IJKMe>Ax*8&+kcvUv8t#c8_iossfC097cWi3( zLr9earE>vk23qEm4$OF-PF6tUZ$tWlMYqXPv9vflC1ZV6?TT~7Qa|X8=EY43a7b33 zb8=+#m8ShW@0~Z9tZ{M47eFVVCtyvcv%x@#;3Ph13Ny{bblbs$yRXJ1;nj6(ANLPb zR8!xx7btNMkdeR#1AV2_&hG+0^j$KsT%KQA_Ip36q=Zi>3Ev>QVB`j#Lxs%LP*3kY zL$ed_2g~Il+f#Ab4;t<5hYV5LGb5gyF`=^$L79{sJKtE?W}eqOjzF?93TDwz>Jo8kXhzMqjL0Z zgZX5)(ENO1B{^E^RDY>-Nq%PD#-g&ajecT3DY>~bYm30s&CMS^uxIZzquQyVNQJ5z zqBMV&D*WTPKbu-iZr$l$gH}IqY*rUIO&f6vp^GA|`LJRZC%HFUzZO|u~ ztwr6=P$y~Ejr0#}8J(Hf3fdV>49wU@;-m)isRC&HogPq&#l`7K8Jo(NRd4W=HCYS` z)zzyW4+vJ)QVUJ(%9=SU> zBsw-bFX#78u```}M<~xAtc89zj1rw@{==@Gw!0-(>qu&P&iN5ZiO*QA7LX331I%Hw z8K=f4w+~N@yl=J4EMe3l*wXB3zq&oJ|pEkD1N5AmH-r=@jPdj%WkyH9^JqHGNV;D zRaUa}x2mY{Rc=5dc!{@)_-VTINRBL*J+LeBsckk6t1PKzVT;l2E;(ySX~q4JZh9lOk#pFbmf5MP9Ydpo??OnN zs~VvYjFYN0)S|awsJrJ6Z5_?OGMY?& zIoY|_WEB+L0|Ga478~*ShbhMeK-+Xp2M>HtyD&FcR$lXvDl%*X*8y>-o)YT7{n7ZB z<~VDoX5a5|d-{JiHa74lnZLiRcuDDF zE~)rDhs|N07#-U&HaYq}fSIcrq23%;XrFS+sR(EeYOc_Pq_-8EBbNqHEEr8vQp$*Qp9%jIBUb2_(!Ng-5_bT%|xIXN}bT2fYde@Jxnd6=Qc zbwAkLj?V5FsHw*UkT*T`MrTX=FXCfkij%Uk9*3A@I0Z7zyQZ7a38vHLbo8}0Up8h~ z*qR;@n;Msr_T=pJw0Ur1_tZ=mJz z8H;5)IW}QcaY^Z85`X^`lHAXH!bHx6=6`??s>|8c(0KLe#84wto%}d7I`VwvwcM9X zvCIB*SF%t)vj!)5ZEX)F#Kl%5W@P>rL~260)EK$wBwm89LXnGzqc=BQI&CrTPYsDm zmIel%J2XBxY<4)Xpp{E>p-%HDy`1WR=Agz(a^%$U5L)zunbZJ0H^c>pEiJCBdIH)i z=eZjOG5;`&lHlCW+0j^k&G^{xk<#+2dxIk*E(DlUZ4I195?{2ZQia-e3UJNj^lRNc z9Sbe+w^MIThQ%`Zii1`iXi|x~2Js;n9J8 z#U)ig3W*NC2y>{t11eM?hUCN1APeQ=F3s4?tKD6V4<{ui*T$x$Jq#EQ;&qF}uH$-4 zHB>(}cc^Ee>js$kVIeE#sbKOIf*6n02WEz7AD}%qXgn9R%O$mZ;!v3^r*he<$CSZA zYmgUv+;8ZPW`1G-U0+-K?Sm7&+p^P_+#VVe^*sqJbhlV@&R8__Et6pZ1l(e=I<;f7 z|2r_y{&;d?#=5Aqq+h_;P*UPd2M@LI6xuWWWABd64Bw}>T9IDh+x$4`D!zrB0Uw}0 z9|#h;T%J}}vHT%bR4AF~5s31M9pS?SoUVbk?w_eg)$c;ebya9+*tua55rwLd;0WkE zAmQ|aP4Zk22yyB(a}!h3(=AYS{T2lG>J#Eq&yGn>yc?)8w3DO37z$&0iSRqdd;0${ zvM~La*=)v)xsyI8JsS(poj)gpows+8!sgrbm$uq268B8Q%Y=DF$F{>kx?M!m&20u#!a=hyi z^V%t^#Wdp-3+DKU_~^`xtU_gwG9P@;i6)C#0z&63LyrYuxUUrs`v85+H#S6+Dmfx*Q^m5nzuFvDKjPu=3XqT#QR1;+0_B90 z(7X$&RO*84RA@4*KV&k@cLxQ9_d;oeo-1 z7|nzIJsmep8;yHl=jhe&0on)XuLWpjgaV4WSFL#&WO7wtHzEI?WH|$*sCp@#xOq4= zFQ(n#~vy zM0F$I;sf;8!azWXzkkKDn$>>;MWiH>0?MkfKTAJIJs+bB6tNWely|etJAzdH9(T{t z?RcY2>J6b*n+z2A&$M9J4Z6*@gAW>oVHQna?XPu4kMl-84*kU^SPqH)g=H1X|3?w3 zDr0g73}(cP(i~|5X50~@3*@@@SWEA@!6+3hkJepYoMzBNvWst zRW0zBt>**u7oQ;vg-1f)@%6>UaeHQx5#`WiZPF#18m{FLTn09G;cGhJef1tn#A<_cU=MOnAqZi!m0an$nnVzjz zt2^I>#y)MZy%tuD2vMjiQ!}zIf@n($D0U%Tc3WtCMrBTUjtB%)K$Ub@t$uz+r+o`} z1`5TXN~q6e=L7WD1~hJn-E8ChWwM&A#EdKA)00;N?!X!6@mA0jPv!xE7&+uQHxCS| zU)PxpM`A-m&FM+m`OWP|8|Q7dC>6BNNK4JWK%r8A>@JF%GvA+w$58s>a9O6uMz;)4 z4Zmu22O0bh3d=8%D*pF$S<6qnE6|0SI6IH4D&&es<7Im^wf)Fa(N73mt^tr>^<6+ z7OU=5EEr4~fCuW)`K4uytI=$Z3RQ(IPfSie zPaYT$k0#kIU;{g{8LZ<21M26r`h^dHYGD2|hP!>I@y9mZ)s|5 z@+C=G8JhvcVp@+4-i*naPUt4}ve9m@j|>XZFD zsjKN_z1bWd84|i8DK-5fsazV)U3E^I!vI|YUYgR+{{xgyu{veaM>W3sMe+gqtDh^R z9@xtzMIjbf=Vawy9U2`~4z^hg?Sxy@Jsof9%*Lb9!QtlOCFNJT{3Yk2Wv0S3WXeZ8 z*it{S&8{=O)ZWwhn%-aukBW$16CIa$j?`ZohD02?xqdV-GW1uo!*&QZL%;J0-IeOA zpWbh0O&_3-?L2ac-t2Hi;OeZLg72Hn7HfZB*DG4H@o-FFh-vB4nwxAgzq1WSg9Kf3 zq@;=jT4*R7=YSGFvDrRvdbOpe=@rnXi;f73Tos>`e4gE6*Q)zEo&w&y3pT>a6jBOy zY=?Miuk8c$v0Z;WUs!V(38cC}6&SMAVm6t~c1s%|ePrd*l~+oFmDlUd27eknp(;pB z2Z=-&vHuCWl7W6W%=3n)nmQWavfAyb(VTj~JybkEYV%5#gJ=!}q@S??H zA-xvifieVr@W2L`_}u5!IL7Pa1N1STFt$4KY4lHHz)W~<2TR%qQVp7RdxnPl|E{-L_QN)qL5CAR z-en#+voE~;HLr^g&|mY!{fEtvTcfO4c92X~m6efu3FK`Q(DV!jYQVH^cD_~J*ZCq? z=7aD-4?KKa`P;h&J{f~=`CK9mi9%3Es8q`n6H?BH`oRpB9~6c=oHm1IzIkA1;7ya& zbch8^u;A4v67wxzgl} + + + + + + + Eris + + + + + diff --git a/api/webui/main.tsx b/api/webui/main.tsx new file mode 100644 index 0000000..84be409 --- /dev/null +++ b/api/webui/main.tsx @@ -0,0 +1,78 @@ +/// +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(); + +const trpcClient = trpc.createClient({ + links: [ + httpBatchLink({ + url: "/api/trpc", + }), + ], +}); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + suspense: true, + }, + }, +}); + +createRoot(document.body).render( + + + + + , +); + +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 ( + + {jobs.map((job) => ( +

  • + {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} +
  • + ))} + + ); +} diff --git a/sd/SdError.ts b/app/SdError.ts similarity index 100% rename from sd/SdError.ts rename to app/SdError.ts diff --git a/app/config.ts b/app/config.ts index d3be5b8..d1d0197 100644 --- a/app/config.ts +++ b/app/config.ts @@ -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 { diff --git a/app/generationQueue.ts b/app/generationQueue.ts index 1903d90..b1c2a33 100644 --- a/app/generationQueue.ts +++ b/app/generationQueue.ts @@ -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(); diff --git a/sd/sdApi.ts b/app/sdApi.ts similarity index 100% rename from sd/sdApi.ts rename to app/sdApi.ts diff --git a/app/uploadQueue.ts b/app/uploadQueue.ts index c63a2ea..e49ae07 100644 --- a/app/uploadQueue.ts +++ b/app/uploadQueue.ts @@ -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"; diff --git a/bot/broadcastCommand.ts b/bot/broadcastCommand.ts index 54edcc3..0712472 100644 --- a/bot/broadcastCommand.ts +++ b/bot/broadcastCommand.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) { if (!ctx.from?.username) { diff --git a/bot/img2imgCommand.ts b/bot/img2imgCommand.ts index b4f9d4d..16a27ac 100644 --- a/bot/img2imgCommand.ts +++ b/bot/img2imgCommand.ts @@ -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 }; diff --git a/bot/mod.ts b/bot/mod.ts index 653f939..3239c47 100644 --- a/bot/mod.ts +++ b/bot/mod.ts @@ -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"; diff --git a/sd/parsePngInfo.test.ts b/bot/parsePngInfo.test.ts similarity index 100% rename from sd/parsePngInfo.test.ts rename to bot/parsePngInfo.test.ts diff --git a/sd/parsePngInfo.ts b/bot/parsePngInfo.ts similarity index 100% rename from sd/parsePngInfo.ts rename to bot/parsePngInfo.ts diff --git a/bot/pnginfoCommand.ts b/bot/pnginfoCommand.ts index 1294917..d44ed81 100644 --- a/bot/pnginfoCommand.ts +++ b/bot/pnginfoCommand.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( "pnginfo", diff --git a/bot/txt2imgCommand.ts b/bot/txt2imgCommand.ts index c4f10a0..459e4c0 100644 --- a/bot/txt2imgCommand.ts +++ b/bot/txt2imgCommand.ts @@ -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( "txt2img", diff --git a/deno.jsonc b/deno.json similarity index 61% rename from deno.jsonc rename to deno.json index 3cdfde7..e5a4494 100644 --- a/deno.jsonc +++ b/deno.json @@ -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/" } } diff --git a/main.ts b/main.ts index 1c37ebc..261baec 100644 --- a/main.ts +++ b/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(), ]);