From eb6b7e3bea0046e022204821390ba6e7a858b01c Mon Sep 17 00:00:00 2001 From: Jack Hogan Date: Thu, 10 Jul 2025 23:05:34 -0400 Subject: [PATCH 1/2] Added grok mode --- .envrc | 1 + bun.lockb | Bin 81904 -> 82290 bytes package.json | 1 + shell.nix | 11 +++ src/commands/summarize.ts | 97 +----------------------- src/events/message_create/grok.ts | 31 ++++++++ src/events/message_create/index.ts | 2 + src/utils/summarization.ts | 118 +++++++++++++++++++++++++++++ 8 files changed, 167 insertions(+), 94 deletions(-) create mode 100644 .envrc create mode 100644 shell.nix create mode 100644 src/events/message_create/grok.ts create mode 100644 src/utils/summarization.ts diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/bun.lockb b/bun.lockb index 717ff7339e28df0d194c408622805c11bf09b7f3..c8476fc99ba8d2df8f24de41d35b9efdad176034 100755 GIT binary patch delta 15412 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

wd&H0r7#GMb-(3 z4_x5{Ctho912?V^@fAApj;Npz@l8a0;2tDxBH{x#Yob#;Of}%jCLz8_PVp#}PeOc? z5g)jD)M+x}1GiwZQ{Y_#xVc4$ugHm)c=L)7UoqkXw}_I85g)jf#ZIw=>cK59L3|}n zK~!0S_@*E}aLcIQ6vPK^!xX1@ihcyQt`zZ=Iz=T_l_I{Wh;OQspYEq3zG;XL+)A=e zLww*0r#Zzc+6Hc18R9E*iq%w5hWN@6AGkH7l_NfIv&x;~1*!p8HXZRzcj9GF`Ej50YSb2xKx$pew)|5IcI$-Nd#!(~UWrH^9L5tSMRb0MeTr7M6 z;6t7~^huu2di*%V*IIye_|!QC;Oj+zWqi_I4)C=OU>P4KqXE9E0G9DVG!DR3q5Khd z7V`163&7WUfIH=rYiEG34FJpd7~~J|wGrU9d~)TJDPQtAjnDCmO&LG4u^u0#2{IPH zs>^UJxJWL1O_< zA7?8P;0$5R`IVRw1>nV!avN=X*l!-PXfz;CjbJL0tP32aEt*08cEQXik6! zi6h?{WqB0JBRzO2mvlVpc%5<^)|J`f4mjVOS4~TqoPJ&RFz+byMLA zPBo{L74rb8{7cHiB@Oeia2q)U+?F97lXj(y`TGHGqc2ph@Qk1kyr&WFh*Ksblw;01 zlL6@n^K4uWlc&?3GLNCGP|DC?n%R&I$T8ybbPxFqkMc~j%yWeG9sp*UW!Wk7oL#PW zn{>8GS!U=#!?P3Fm=_fr;1o(3?-sl;So z>EV1-@RE=QIWkT&&uE!C*?}CMg{WhhG%OvJPA)>7bU^CJ&aaODFDDHfWRKaHRFcui z4rHgE{I8W|Kx~7D?J0nV%XG3rkD5D_iX644Q=VgcqmDUB&-UyzceW0A5m*bn06Yi$ z0bm=vY0N#uV;EkB)3U9z{_)*|-fcM_<0H>cf zw^E=4C<3n8anZl-{lBv1%6TCj{8yZ-_)YKr8=T_|dJZ9Ha&?2OLU|SpXMx8|;(3%g znl%8YbrdiXki&8#84r(VCT?zs(DOeXBN)yC{wqV|Ik!9mnah!{19+zX`YdgToYN_d z$zM&*4zC`$wt4k%CZ!(N%glMCUeA0OJHW~>0r5af;No`NaE-n~``&0z7vK0Sm=6ts z*huB)D5@A49n~!=Izgej?ZI}Q^S-EVyW);H)4%H03)L}PjnzWUcjU**Z{?PB&0Nra zOV1=#ToT5|XYzBr#|AH(Jz@N;CVI<+s5l%AY5fk(ES!<2jR_b)vAMrflsrsYIMd7dJ7rissJ#!f9> zenwb)&$chSoQzzr3XZEA;*lR3y4GFTf7j|m(^PS=k^aQw4yKx&vG{+hm(X5*HMpp< z&$zGcf9$IFuN$~NX3jHT+sOH5tVpGbH#NMbseCimF25~oQTBlS)@MI@S``nNZm_Pl zbXS~Re!chlrt`yIZ1+l2b`<}?ToI-3(uA89?P(PT-uRwSacc*bSWiFv3L=VyVk{J& zp_RKdyZrnwZfS?u`ThQazPd%lCPqbf>$Z#Dg`s0~0b24S!Yu)D3wEF1+lsr3*RA|S z;nkXWnYLGJbZ>Q>(XkhIOsi#uU4Gnn&Zp0SulhV5(2$m&=x^1rc)I~z>ajacY^S2# zruS?_w+kP3?bhP2_W`p?k6C`|n3K8n-2p)lErxm1y))#qCk-z_M(ok-@&n0FoBrIb z?T2UH(Puy`4=yK5i~p5ObdObjrDwFPq0@V`5c$bvRqWIsj`s`m*Dc5JtZl4Pz*{l) zG(TfO9F1==#C7W1TUv;ep7EGiG0H(2^;VjwqYZCq3GxfbRh0?fJ^x&@>Q{Rm{;+dRsIIQhLizEf zUs=(nwReZ#jhgPrA=Za2kRI4;v&*kHCp~A4-Ic!H8wv?fz(W*wp{Xf>bZDjhOkQbUULA!@t)bZ*b9%a^R}_{=#~#A9oKs!~d;_Gxzc z$=)|5^-pfhtAC=Q{&|{pK@&UZi+!3nLYvNL;uN*sui)Sxp?L zB_C^|o_Zb8#P<~awWfN9P?w`N)gD5VzqF~zA>{trrVa-=XcNjV4Z#IHciIz3@6s{7mtOV%4FcRQ-<4FiamE4W@-hf*UwBDU2>2wo%yOV7%x| zI-F*vf}JzHk$xDOcKL;I)AAW#-*sf`?PlPZYTRoqn>UT>Zd`RhkJ6qVVSFn3P2(b3 z?48Z~kUm7wiXJ&TXy#xyjX09l;HbE%_J*%9sPr~1lUNbpEdefnpo#Cg?MW1sOxW+ z--r8WFUvhN*#CK%mN>aAH?AQi};+<@c&qrT*5|q%46jkGOV$xQ?VS=nNu-X!TzT`*38ZL+K?)9 zM}}SAaDTBt=*cs|=Ffx2hHP%U*A0cCVoVKvkD@<)7;G4pO+1&5G2}yIXyG~AHT$5k zvlY;`kJ9Y&FC9Lf?tZSkYeun|cZ`(By#0;c$*{${iaC|dK#dW>95jBOOzAZ)T^+P* zGLS>fKbAi|X1cCv<*}h(vl2Y^Xk$5Dm+))Ud1YVYDsPcS6OX|4744Ds-{61I>Nh6h z40`ty4Kwfp?#9tT@8LIw!WdPL9+?qeXzJptA5hd zuq0YsZ;O|IS#YV;r+VJV<~YIeafOc&@^25C96gX=tgnm)1Cu!eu0Ixe z9*E4}a{WMqPqJ}rQU6yDC&u3V&vNnqBMZj7_{HvigJU|Lb@X$5A-#*^orgaDBqjV? z-pZ1`&BtG8tVUVSoL&CaP43d3>Zwav=Z%@nM|NW-8#C179O1E7+_26r|L!Ml>ai{h z_d8sEe1_4V0&Z?B|L@M_>sS6S^J-{#Zc_Gt=T>A)D^ItKN1Lk;8^#D5JG93yl<}iP z)jwbREAP3RRiCr}>Kw!wB;g&0@fvH!=lO@{23%-kyp@T?Nmu@L<(h-b%U6v*R)AMJ z-SGwm&r*0r5Ovws;gu_RP{lP+!K*M_O?s5%6;917j=H;SN?!5A>keY>)F99NAAQA`$3^yb!VGSY)qGE;- zfujNnuDFC%WOP80MWTWT>fi{dY>uM_1{HB;@HxW!|L!hx0hw{$H}8AjH_!g~`_!pA zb?Pitb*nqgH+QS&F`!LA8@M#B5oqaTYb-4c^-aOopq_H)U?S-m zP|7`1U(*_ET190c3bd+c1KW_&Kx;KNX_HM9p9HMx9z;kdvQrC zxfN)CP!2HH;U|K&0H5#BCK#0t#Dk~ak_n?nO&$wxTFVYSB1d)aiDudz=7`OL^D*Fy zKsk(3KMXwmo;rSXpfI3m2ILthr6*NljhKo6R8eXOjHTA=K`HerEbRa~1C;zJgq+5G z3`!^ZgRVys`R`za>OpphqVh<`A_cBLeQ&1ZAIuxT=`2cRz zTZ1s7s`4yuY+r-Um{*mX(cY#4h4B~_##KIu1`fREDto}cLZBV^TPIAPJf=+34uNN+ zjya0dDp?60wpTs`%9P9k#lOnYxN*S2pp0A(P#V6vv#oa_Xa#gu8fc(rpLWqSI9EA7 z-WL22lpSNxi;80t?A%^16^9@f3%O08G-wVe=geqO8onLYaGZGXbhI=A@n?=3B-t~5 z@|3diQ%g1NOrqWKS_m>SKcFKcu>cgdR_ZCXXGg#@1LJwkLVuIhn2jg^tR2P)?o}pd27B)6R7xP!7-* z{b+D;Pg_10l>L9`ZqKd5plsg)%5j#raVoyYO!J@u)>+F3bIaO0O60if{KhZpZx7KI zN_8WjzD;UzuP?EUeR`S%aGxpFxW6E^xDS!oP@nq|>}(jg(j}8((%rS-`h%+{lNzVH zQ|oD3H*lJqZj`Q%k=Q0a{h$Pz_}pDXH0=ho=S z?sI>D6^X5n#A36 zl~WWrLnWo-E_LFo`p>-0|2OnVJwe-7MYM+*DSXS;h}d z<%F4qUa^rr@+;cH=edyLuHbkYnUjug?IHV7bG>|~FPaiJT z(LUp)a5)i;@M8`#oCu&d-Q5TanAt?co22X4Np)+V>p|3A!@1!43Z+3T={HLt#%HW- zEvsU(T=lRfZnOl3pw!1IJz~|Zg=j;mZsRkiwUOeOETg84#I?XUiaZ9el1W;0r95qNKWmPoE{VsQnz< z%TUV?Unfl)Bc<&#jg_5bRmUvXAynj9P082%d!9ro3$iXI5woK@&gY(nZQR}-P-nc5AaR|u+~1;-Gt98EeQj4w8=yGX zVsMkK@VO+mi%%abfi6B{LpNE~CCk+qdo=>zCDRo^37Lt@bZtT@)2h1?J9{6AZ<47O zOKrT*cr;1k60%&Mp?Z`h+BcbDWcM{FArf6ObyH%y`dr1>!Wiqwk9FBNLf9SFpj0ZSqBGr{(>3jC<-cnjxFW09?|&?1UyGSY&FLDuFbg zvBf8=(t0$*mc`#v>mt?VPiZm38lZ%q77f1v{C%-#Bq*<7%66=G>-s6GqXlbRBW+D7 z$xn}nWiKpDtF|3$agpLThKEP19?J;QE7JK>YyCP~wOA`W1Zl{u#UqK<^;48SAZV8D zh=6td4`>As@!Y$u>*r9)Bgxi9O6PL`x;+@+Map_?TGmC%b}UdW1i)ml_~AI}X!N8I z;1x`%0PEYjNXd@@TtG3vi0LGV37raO*KM`Pi8PEe*15j=~!1m`Ee+scz!1nzBgZ+B4R1eOT=LUyL=uLh-RfgS^D?@Jzl~=)~i+f0}G#nBtr9=F9 z?AZ)%6S(M`{d%SZZqAjlH;2kzaJ|IL%@t2>sN9k3*L|`Z+%9nOL;ZMcE*}d2hQdE^ zzmnKt@NXFW8|KFo@F8#q!S%}X>wTpv5B}xBKXCmd;}-aL3;esquluDI+!x@ohx_$x zSvDO04Tpc=21>tt_?Hj=^8I>_dufVSllg;2Zfr}pH*Kd))DEK!D{(;LE^H%tGEBw3FuaA`7;C6wFFZAo9q`VOR z6~aGog%Ud&{*8uzqy2a@bO_u*aJ|O(^&+Vn1OLXrKX3ubxDEc@2LEpJ>*J&r+!x@o zi~M@2EGvS4Meq;Y1nD;x{*8ryWBvLh`3~G!aQOkhK3S>*@Gk)Wiv4(&8CDGcis2u) z>Ea#-|Hi?;aelmu+6-A7r6NGetnLV zkB5Kb;UBoU5<3C@O@MzB{QBK;2;4z%y(aqgc~UhI{!N5`;Ho5J68xJ4|0enM`BDq+ z3vk(Ge*HdKRtEpd;2*gArQc-uHyQp-_Unt}J8);g6Y1unka zuRkH>Uw>K-fjbDU*X@4&S*f}m{@o7$z*S4eEciDI{>}31 ztECp)7vQq*m~VWiZ5(mWXZps8^Sc=vL+%_JV%L15>){)7?)|oX`1aL2B;XXc)}~kV zoLp9zAos3G>7kuFZ(nR#3Gn2lPGXAZ`HRmeyq*RqQ%{bsX;Hxg=MsR|vjAIoro#&n z>v|4%_U3u8vsI&MtDHKXA-g$seDYymo*(f}!MbQH>v-I6YSn1kI;W23N1P9NKCXLQ z;&J+Z=dAcV%IwS&?PAoidV^EPb7KlBc$noefpR=q_eX&`Hac}-pgitV2cNtsM?+Xg z9WU~453FqFR8on0G#l!~I^}tZTbEa#AuZG7*Xus(&Bx4KU??yQ;Nxg4z?d;cU4VEX z0q6=aMtC`+B>{|0m~2?@?ZB~U1fpP_py@dO+%_HtO$4R@Z^`NP5!bzk(te-@*ah%G z;CbM;z#M>2Ebl-QACc04Bp?RZi8?;VZ2^W$_VeC~z9 z2ABoh1I!2R0_Fk%;5Oh+U=AhTQ-#QT+jQw#wvHnahDZmjo$6134&VI1XiU0P2h7 zo<$*6+7-5PT6hzn6q61Clu}h6EJx*R9|ov)QdOq+syqVaeDWCI>ZBSJ4WOQjlAS1A z0H`*mfT3kbX#ndPAr2la!?3fzH4tbaz=%?AG(cHvJ*iHLvZGSWw6K#(0Xq}QF!rHh zRY$%UPy?|(*ntv9){(EU24ctx0CrY6r@#b28A6`zDorf^R9$c?r$c6%(~q($MJ$(F z^-lh01_|BnlsQQ#cn2`cDXT%r&vxom3M(C68C5q(ouRDbDr0{}@W=df$Kc{*1D73h z9PBykIXG7Zm5`?ll`y=N{WVYkP>xZ&7ocnvz!jnN(J{7jLRYA8ssU7f?n5K%lw#$% zGI9ahlmSYn0Ckj8`M-G1P#+BrlCk$wHMsIjS*mz7_&<_k+8zR!E@dRW=735cy$&|& z4z7PHRRbwO#na1R1@w$<&jQZ?PXjA~CxC|m>f*9xO4PEWyfTRO%9%${ew34E1+W~T z5I5%sfW^QffRp8ZfQs({<^$Z00|3L%-Hlss888v}8C&iJJ8nfOI*kKH0-Rh75w~15 zFCImi2L3M@S8?$V!Ufj-GvgRS=9|%Bm}%(afU0{8<=Ft+6rMtv&O8asV*W4A6_s6F z$Mgh8;@_4c=6^VF(a++)6ouf)l@DENg0cQtfD`p{le8kZ1H-A5sh>?of~%qcpkgi` zhEmC~T@Cm+%0CtQ)u>Macr^O@W!!M}IcE`h=ashgP?E1+Iq5Abn=mPU^5}8zPnfgz zb+d)f&_{Gj$Vf=UW-Vc_ns~^JeYGe`eI2%_YuhDDTAtWp;>FPlNU4wGo|@S5&1v)ICmDK5LLyV3^_Tayn|K{^ z5>o2RyIpyoeVVs#1T0EUNKHtF)Le<#VS3dUd#eu>ohUs!KL%rUg8;^&mT(E|NY~Y< zyebNwC^toG(qtBDgh7+E2CvO|Lx}jr9 z8Z$HJ4Kqr8o><(fVqw`!tudFZ#RE%EHDCOO$E!Y9T&#EPU;K4#YqTdNB_tuWR)1q> zOBn#MpB$ze=aDV;eoI;YhR2}JRv}Ps*II0Au$Q;LPSB%U!NA;s-FDgdK3pDu)8kcN zmz6Eso%Q(QjU5dg`Qhx-;-&ZBO#K=;^`_}n-=<}L+PbFRT}R*HBu-CEgIuY!-DTpt zupM8UQR>UK?%Sfj>{Pg9i!H@Q@q_HSMT;1M?5m%l*g;S-7DI&qNe_l z7rv}BGe+hd(}W(*E4A{Tsu7HLBjqaY`UrYa@%^GW8y^ z8u!5x{Ut{}it;Rp|Hh0`-%)lLe75PN>hn`ozeH_iq$KR|7_Uc4$(J7ESftGQ#$%j~ zl*hmH=uKt)H>Tc6*6*P=z8|>P z90GaUWBk=CTfg$?9@%@w#Ov$R=#wowKA>g#UQ?eYJD{;j-uuwhAC`f8lfCM@*tNHm zjvAY@*_Wc*?$gNd5NvvuZOST3 z*khb%EfYWSSOZ$BY$Z;`KN#Ec**Fy=P2Tm`!#l2zl&SA#J2jUGshG%!_aeROJKV$N z%}>6*#`F8S@LP^K`s}hDZdIPM-^4rnwg_~cWU^DXrj9;dvLR#l>LiqBdcwJctuFT5 zYSkM1iTaFp!P=Pr8aZI|Y|D(Kgmk>NvodXGm%Z%lgv*@wqwT>QEiPzrCWk#@oo&uk zuv08u4n}&_SHf?`zkGb_oHri-DX$%0)Qk(Vz?n?Wl;LEtGRyg5&xBx?ZJQsEMF*0d zP*^p_o~z{8r=A~eN)0^dr-&zGhA;Te~Uy`vvc-z^RPdt4+TAD8=}+i>y1Mrsa5<^szHh zj1S{w`^R>J=}diTd^i$+s9JQ+#2>wSA6CQw(MbHYggbT6M(AvA7X-#`wzFpK46^T7q}@r02dCFwU1t)dUTD&c1hK8~Y3GdlwE`x9GLs@MqRJgWCBCG6@qR#r{cf{J3CzJGDV> zMyYSNQzv+5C;p-TyWHK=GN4_v_x0e(WnAjT1+z40TePS7#Zfjd5a&X3jI`~t#p~8l z@=vs0MpDfgQ%-(vdexWl$-Uo7zHM>B`MO{1AlIFs+qtS6RIQAdWQ+6K0zF0DZ*4eU+`<2l`mQvDV>1eWL!u)X?o!Bf^k+b*%WIyBs;; z@v3j!J9?k4Oepjh)(P~MW+&BbRy*y*dvb6*?ES?GaO%aUnWmg^)^X-%orav1WSx2N zF*6S?K}Qy65jsZ_C&`x_Jn%QVy5oiOBWa%wtaE|!fA{NeExLbtEVM)WA02|r&ywJ+ zI$qxU*3=uxN!;t|O|Saj8c*-7?eq4MrFitN+tsk4^T=c!n{wrqznIR9DweNMR{P4u zv-xuM*GTB!%%7RC|IFEX!B3mL(Cdyr$xf@a7e!ruFZy@vIF~#p)$J_7dr{q)n7`7@ zv%NzHAK$q{E7OBBZ~sL6%k#}!;_5T>#!sFys-z@g@|e@@Qv`sWzk2QAC6jH)p8m57T`~7Tw|)AT_@&x(8=_VK;XUENdP f)@L+1SKy2`)c0VX{@Cvt>-udCf49S}Z%F@d!UM@| diff --git a/package.json b/package.json index f4555f4..4fd6905 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "bufferutil": "^4.0.8", "dayjs": "^1.11.13", "discord.js": "^14.16.3", + "fastest-levenshtein": "^1.0.16", "groq-sdk": "^0.9.1", "human-interval": "^2.0.1", "node-cron": "^3.0.3", diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..7945534 --- /dev/null +++ b/shell.nix @@ -0,0 +1,11 @@ +let + pkgs = import {}; +in +pkgs.mkShell { + buildInputs = with pkgs; [ + bun + corepack_latest + nodejs_latest + eslint + ]; + } diff --git a/src/commands/summarize.ts b/src/commands/summarize.ts index bd61951..7c47367 100644 --- a/src/commands/summarize.ts +++ b/src/commands/summarize.ts @@ -6,12 +6,7 @@ import { type ChatInputCommandInteraction, type PublicThreadChannel, } from "discord.js"; -import Groq from "groq-sdk"; -import human from "human-interval"; - -import { env } from "../env"; - -const groq = new Groq({ apiKey: env.GROQ_API_KEY }); +import { summarize } from "../utils/summarization"; dayjs.extend(relativeTime); @@ -37,28 +32,13 @@ export async function command(interaction: ChatInputCommandInteraction) { const { options } = interaction; const topic = options.getString("topic"); - const timeframe = options.getString("timeframe") ?? "1 hour"; + const timeframe = options.getString("timeframe"); if (!topic) { await interaction.reply("Please provide a topic to summarize"); return; } - const timeframeMs = human(timeframe); - - if (!timeframeMs) { - await interaction.reply("Invalid timeframe provided"); - return; - } - - const date = new Date(Date.now() - timeframeMs); - const formatted = dayjs(date).fromNow(); - - await interaction.reply({ - content: `Summarizing messages related to ${topic} from ${formatted}.`, - flags: MessageFlags.Ephemeral, - }); - const isChannel = interaction.channel; if (!isChannel) { @@ -66,76 +46,5 @@ export async function command(interaction: ChatInputCommandInteraction) { return; } - const snowflake = ( - (BigInt(date.valueOf()) - BigInt(1420070400000)) << - BigInt(22) - ).toString(); - - const messages = await interaction.channel.messages.fetch({ - limit: 100, - after: snowflake, - }); - - const corpus = messages - .reverse() - .map( - (message) => - `[${message.author.displayName} ${new Date(message.createdTimestamp).toISOString()}] ${message.content}`, - ) - .join("\n"); - - const content = ` -${corpus} - - -Using the above message corpus, generate a bulleted summary of anything relevant to the following topic: **${topic}**. Mention specific things people said and anything useful to document. Pull all details relevant to ${topic}. - -When reading a message, the first part is the username and the second part is the timestamp. For example, [User A 2021-08-01T00:00:00.000Z]. - -Avoid pinging users, only use their username (e.g. Ray said ...). Follow all markdown rules relevant to Discord. - -Use an analytical tone. Include relevant details. For example, "User A mentioned that they were going to the store. User B responded with a question about the store's location." -Include as much detail as possible. At the end summarize any conclusions or decisions made. -`.trim(); - - const response = await groq.chat.completions.create({ - messages: [ - { - role: "user", - content, - }, - ], - model: "llama-3.3-70b-versatile", - }); - - const thread = (await interaction.channel.threads.create({ - name: `Summary of ${topic} from ${formatted}`, - autoArchiveDuration: 60, - reason: `Summarizing messages related to ${topic} from ${formatted}.`, - })) as PublicThreadChannel; - - const message = response.choices[0].message; - - if (!message.content) { - console.error("No content"); - await thread.send("Error: No content"); - return; - } - - if (message.content.length > 2000) { - const chunks = message.content.match(/[\s\S]{1,2000}/g); - - if (!chunks) { - console.error("No chunks"); - await thread.send("Error: No chunks"); - return; - } - - for (const chunk of chunks) { - console.log(chunk); - await thread.send(chunk); - } - } else { - await thread.send(message.content); - } + await summarize(timeframe, topic, interaction); } diff --git a/src/events/message_create/grok.ts b/src/events/message_create/grok.ts new file mode 100644 index 0000000..1ec4df3 --- /dev/null +++ b/src/events/message_create/grok.ts @@ -0,0 +1,31 @@ +import { TextChannel, type Message } from "discord.js"; +import { distance } from "fastest-levenshtein"; +import { summarize } from "../../utils/summarization"; + +const ASSISTANT_TRIGGER = "grok"; +const SUMMARIZE_TRIGGER = "summary"; + +export default async function handler(message: Message) { + if (message.author.bot) return; + if (message.channel.isDMBased()) return; + if (!(message.channel instanceof TextChannel)) return; + + // message.startThread(options); + + const parts = message.content.split(" "); + + if (parts.length < 2 || !parts[0].startsWith("@")) return; + + const [ref, invocation, ...time] = parts; + + const refs = ref.substring(1); + if (distance(ASSISTANT_TRIGGER, refs) > 3) return; + + if (distance(SUMMARIZE_TRIGGER, invocation) > 5) return; + + await summarize( + time.length > 0 ? time.join(" ") : null, + null, + message as Message, + ); +} diff --git a/src/events/message_create/index.ts b/src/events/message_create/index.ts index cd96676..10a50ac 100644 --- a/src/events/message_create/index.ts +++ b/src/events/message_create/index.ts @@ -5,6 +5,7 @@ import dashboard from "./dashboard"; import evergreenIt from "./evergreen-it"; import voiceMessageTranscription from "./voice-transcription"; import welcomer from "./welcomer"; +import grok from "./grok"; export const eventType = Events.MessageCreate; export { @@ -13,4 +14,5 @@ export { evergreenIt, voiceMessageTranscription, welcomer, + grok, }; diff --git a/src/utils/summarization.ts b/src/utils/summarization.ts new file mode 100644 index 0000000..34496c5 --- /dev/null +++ b/src/utils/summarization.ts @@ -0,0 +1,118 @@ +import dayjs from "dayjs"; +import { + ChatInputCommandInteraction, + Message, + MessageFlags, + MessagePayload, + TextChannel, + type GuildTextBasedChannel, + type MessageReplyOptions, + type OmitPartialGroupDMChannel, + type PublicThreadChannel, + type TextBasedChannel, +} from "discord.js"; +import human from "human-interval"; +import Groq from "groq-sdk"; +import { env } from "../env"; +const groq = new Groq({ apiKey: env.GROQ_API_KEY }); + +export async function summarize( + timeframe: string | null, + top: string | null, + replyable: Message | ChatInputCommandInteraction, +) { + const timeframeMs = human(timeframe ?? "1 hour"); + const topic = + top || "whatever the most common theme of the previous messages is"; + const displayTopic = top || "WHATEVER"; + + if (!timeframeMs) { + await replyable.reply("Invalid timeframe provided"); + return; + } + + const date = new Date(Date.now() - timeframeMs); + const formatted = dayjs(date).fromNow(); + + if (replyable instanceof ChatInputCommandInteraction) { + await replyable.reply({ + content: `Summarizing messages related to ${topic} from ${formatted}.`, + flags: MessageFlags.Ephemeral, + }); + } + + const snowflake = ( + (BigInt(date.valueOf()) - BigInt(1420070400000)) << + BigInt(22) + ).toString(); + + const channel = replyable.channel as TextChannel; + + const messages = await channel.messages.fetch({ + limit: 100, + after: snowflake, + }); + + const corpus = messages + .reverse() + .map( + (message) => + `[${message.author.displayName} ${new Date(message.createdTimestamp).toISOString()}] ${message.content}`, + ) + .join("\n"); + + const content = ` + ${corpus} + + + Using the above message corpus, generate a bulleted summary of anything relevant to the following topic: **${topic}**. Mention specific things people said and anything useful to document. Pull all details relevant to ${topic}. + + When reading a message, the first part is the username and the second part is the timestamp. For example, [User A 2021-08-01T00:00:00.000Z]. + + Avoid pinging users, only use their username (e.g. Ray said ...). Follow all markdown rules relevant to Discord. + + Use an analytical tone. Include relevant details. For example, "User A mentioned that they were going to the store. User B responded with a question about the store's location." + Include as much detail as possible. At the end summarize any conclusions or decisions made. + `.trim(); + + const response = await groq.chat.completions.create({ + messages: [ + { + role: "user", + content, + }, + ], + model: "llama-3.3-70b-versatile", + }); + + const thread = (await channel.threads.create({ + name: `Summary of ${displayTopic} from ${formatted}`, + autoArchiveDuration: 60, + reason: `Summarizing messages related to ${displayTopic} from ${formatted}.`, + })) as PublicThreadChannel; + + const message = response.choices[0].message; + + if (!message.content) { + console.error("No content"); + await thread.send("Error: No content"); + return; + } + + if (message.content.length > 2000) { + const chunks = message.content.match(/[\s\S]{1,2000}/g); + + if (!chunks) { + console.error("No chunks"); + await thread.send("Error: No chunks"); + return; + } + + for (const chunk of chunks) { + console.log(chunk); + await thread.send(chunk); + } + } else { + await thread.send(message.content); + } +} From 40bd535c71afba5d51cb367018928faf912a12e1 Mon Sep 17 00:00:00 2001 From: Jack Hogan Date: Thu, 10 Jul 2025 23:13:54 -0400 Subject: [PATCH 2/2] Is this real --- src/events/message_create/grok.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/events/message_create/grok.ts b/src/events/message_create/grok.ts index 1ec4df3..e53c364 100644 --- a/src/events/message_create/grok.ts +++ b/src/events/message_create/grok.ts @@ -12,16 +12,20 @@ export default async function handler(message: Message) { // message.startThread(options); - const parts = message.content.split(" "); + const result = message.content.replace(/\s+/g, " ").trim(); + const parts = result.split(" "); if (parts.length < 2 || !parts[0].startsWith("@")) return; - const [ref, invocation, ...time] = parts; + const [ref, invocation, ...time1] = parts; + + const isThisReal = message.content.match(/is\s+this\s+real/); + const time = isThisReal ? time1.slice(2) : time1; const refs = ref.substring(1); if (distance(ASSISTANT_TRIGGER, refs) > 3) return; - if (distance(SUMMARIZE_TRIGGER, invocation) > 5) return; + if (distance(SUMMARIZE_TRIGGER, invocation) > 5 && !isThisReal) return; await summarize( time.length > 0 ? time.join(" ") : null,