From 8c8e996c8770eb066552962e5436702e87cf5045 Mon Sep 17 00:00:00 2001 From: Saboteur7 Date: Sun, 18 May 2025 15:23:02 +0800 Subject: [PATCH 1/2] feat: web channel optimization --- channel/web/chat.html | 900 +++++++++++++++++++++++++++++---- channel/web/static/favicon.ico | Bin 0 -> 4286 bytes channel/web/static/logo.jpg | Bin 0 -> 21917 bytes channel/web/web_channel.py | 135 ++++- 4 files changed, 906 insertions(+), 129 deletions(-) create mode 100644 channel/web/static/favicon.ico create mode 100644 channel/web/static/logo.jpg diff --git a/channel/web/chat.html b/channel/web/chat.html index 5afd911..73881ca 100644 --- a/channel/web/chat.html +++ b/channel/web/chat.html @@ -3,163 +3,849 @@ - Chat + AI Assistant + + + + + + + + + + -
-
-
- - +
+ + +
+
+ + +
AI 助手
+
+ +
+ +
+

AI 助手

+

我可以回答问题、提供信息或者帮助您完成各种任务

+ +
+
+
解释复杂概念
+
用简单的语言解释量子计算
+
+
+
创意写作
+
写一个关于未来城市的短篇故事
+
+
+
编程帮助
+
如何用Python创建一个简单的网络爬虫?
+
+
+
生活建议
+
推荐一些提高工作效率的方法
+
+
+
+ +
+ +
+
+ + +
+
\ No newline at end of file diff --git a/channel/web/static/favicon.ico b/channel/web/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d21d81239be36ef01b371b76a23585a11a3e931b GIT binary patch literal 4286 zcmeHL>r+!l6u;%bp%@ImtZUX z9`2;RSTD_IW+`9H+m>gSF`A9ggZba++~{$tAJ|3J&bR5)rxi@wSE+((T<_AMK?hx# zJWHiTwyvzm34`-XoGyQ}F@vc)>zP8DZUla!Jd6rz%>c1#-nQeewusxPt+|UNdJ3o5D5O2tW z+*~oY;a)5jIA48nH=P*TZ;*jpftdtTE@$G~>{nnd{UUY$NC+Q-1Zwo`}0s&3g6n zfL-ggWPX`*sllM%;j9qn0RHIPbaH}@diK)pr*8>rV%!-`4C`4aTgcMIu~P;j44E%__`Rsp$Z;N|X&v^>M9lI60CPis|4`ZhO?2vbM_&0q^P z@S1uT)E&;~zv5k#XM()%e9Z#Oq)gc-a&hs_fk)5(t?w`am(bwfP%1#nxrE%N^~=0} zme*tq5Ra?=?@jr?o!X`@YkTwq@V8A}GVsHWeSPnfGx|u+YHN(#Y;rp^-^(-oql>3m zklA0@0}MdU;@ox{d~s>~wAd^4A_p1=chhV-XelPa7z=;vW1!klNoPimaP8%{+$$H5 z+b#YJS`63pSBQG8Sf&<09fz7j=B$2yv%7|R=I;@7$T&kU>I>j!Kkq&5J;b_=n2Ylq z^XwD#p|sPg&+|Li5%{@8zw`s@JiIONZ#%yiH@ODIy*tHq0CHtL-(kLYvByef$vo~S z?zud>xkpZ{)t+lLXenPM+f+sOW^b{-%D>-wLvSB~{jvr#%P#i~KLC5W!*^a-x3mK_ z=$~`Hiuwv?2HDXIo4@fjiJI$0Yfq_L_P~X3dPZR0QBN)7*$%=xV)VaS_tkNnk$?EN z0`AhQY_J1$0P4tY_6gqcR=#JzIAApH1>Vr)I@tIs?a<4DP58ZTFYf{Q{{a72{~Tp+ B1DF5+ literal 0 HcmV?d00001 diff --git a/channel/web/static/logo.jpg b/channel/web/static/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..589a12ace8ecbca5036025b4761550e1c3a48817 GIT binary patch literal 21917 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�bGxbJX5~S!dVK*Zc~8B$b4@=_ zgrmwhzf0Erm8{zfvjAFz(0H`V;l5xlsm^fXDnkJ9rJ5Tba<2_oK|f=pP2M3YsR<28 zVJ@7yIY-Fevy#1^V>R2-u0laXG6JrRe1bd`Gy}Zn65umDb$x)eJGh-vB8a&Dh3rx~Xz2Z4_Wny;naA77 zdvTt4{E?5#s;y@<4AXwJNrMwB8@IJ%ml?IU*{ZY=_!to;nc9;)S3$TZS*eoix2iMs zW&}zimM}-bi`bHk7xZt2P50<4DFFh&!ZHy|GC*yU`_q=Z<+j6FU2cVe#uB@4JW=*7OYg5iqqoY3qfA6*OR^&8!DqoO^ z)G%}|Gw`-xBH?x3*tyvkR3~3RvpS1YJh(i^1$3YaQ)fEj!jVNQNI0N+lDOWzijm2^ z5iaOi9gStu&N*?$M_hMGslj!B3fo270Ny@$8JTGfj(rNeGq~QPV+Mbg>HL6Z$CJ4C zBU%hxS9)<^9@8b}G7*wB2XksY*S(=W7k@OcH7a<_inXK(vXMVS??&LjpuR0?9NwNEt zaY@M`QbtrJ`d?Jk(z|_y zn$lm2bz4ebR3SGsCI8+rTtTgo@p9!;M`^b^-G7%Qg>3!0+6`R0)lw`{ZKa%Er{Y1> zxD8ys^Q2B^U13rVX}NImaPt>jJ1!(E`t`p%+fT7e)8X0WdB~vWQ;Q z>>M(luMNy3gWTY748jBQHJe42aX>QoqB~c_(tMq;tgsRc& z6Q$%lOntQB+MN4{2ii(Nbu5rCP;BYs4~$!fFS3ovZRpI+Pd6-GSZ4;FdI5Y~&gfKbuR@$v%>MA&OuLmGW&0yvg#*=ZzELcDfVM9T;{ zvDxU8UNyIjOz;H8JLBK6zNc%FRYL7gD-V$17C3VG`-c{KnJATAQZxM{QKhuYDH2uU z5A)cB#>vh?Hq`^rO~;58Z+~q4U^Uc8?(=;6UbuBQzfT0fl*)*fVm>a^-`y|~q&xf$ z)KFEhP9(=ojbZMtqhpZy3%k4dW`pW|_`@)ugR?$!e#rH$7Jgr2Qn2 zDl5!R$Aei+iL|N3j}_=asGw?)vZB@A*IE6QxZ{?;E>coXv2}$5cL3f)BjrBCm$p;? zYjeH%hq0IzBRtuY{*5JaW!ls9Gq*H`*u=nSB93TYC92Fq4;25S&-$yhoy~D3OV`+? zz{b-59IMe7gXFB@bS!Z{_f1v|1DT#95zg*)d?ZUXb5VrDx5?lPyX;Nzd-y3 z+Hlb`BHuAsa45T@j}nIAJ^FEQbDghEPeffr_=f1DMm`D}!u8Ic8vfN=6j@Xvv;&mf zkd`T4j`&7GaRIr;@3oqn4^H61|NMF6@Y0*5ONydgQ{ALMgwP)eKFJpP)!hm4Pn$mQYI6vaKyIryr z9WTkpEwDH2Zs7#P{U+)$_kexa``s6Uz@R-unVC7HeZowy)bTHPdF^bU;#~Z0q`D*c z#7pM05W?Wjw}K#5e~?doPM#`o;BY}+gbMy^jPK zu^Dlj=uz8W=ZxM?1J31&;&Io&XZS~Q0-rXIjutWI=;Lf?k`_4$t5P+OK@%AaPs|HS z$TNyUY0nt>8!$)SvD{|`)n<|PuYlHvTkao@ibb9HJ$A0{)$J*{Eu6CsQ(2Z6Mv8SAl+ zSyuBFZudevP^Ikkkzyh^N*>%+@>R?Jse9tN z3x>5ty}svI75%iQN?*qzE+3ED&Xib-`c40*Y46yL6|HYRJzX5*Nx^MV3-Itmzms)Mlukoj1(d2Y*QK9{+R^##Cc6 z#0I|*m*8-Lg*O(f^==i25H;HFCL2rA%f-GO9u9ngJ&y>%)=e;z*=`y_$WS(WW{F!k z%6u!h#Y@WoWQ68>rWz&PKVy6R0V`7iDyt=jF&J^SUuV(C_xq#BGC)mly#xpwb-7Z{Bv#WMhuxeY*hEr z%^T@H96bXdSgFh%=zVV~IbKntWh`$g8}$Vw4KnLHB0P#t{^einM;)w}ru*cSw^knH z48wg95mJms=FIc^Jaii$Ed)VmlM4TOr$R$~c9a-kki23w zwJ%LowQ?iqJ);98lhC*r^$NV34!-qR-- z>6AmvBS5(WP9Al(l3&=NS2KPn0)H+7DatMv+C;nRXQgA>R;VC04T8w*hq!xqKZig0 z9Qdjezw6SXn;Q(N-wuS-tpzL8SuCIT1~b8pcm?`rGu}} zV3?6vib>t!7h^vau&I}pomC3tW;}|2gZp7_kEjE$R(`y{C5C&hDi4pnbCX3PEwyJY z1bKjzF~3CDp6ke$6_?hk$0Cp{gY1$ZcbXC!%2%Zs=4Iw2nlE}_>&*L$$#Cn()acpv zE5};vlyc5|`a3(LYW~F=pIKz&G7k;e5KB+sCLh3BljGmqxEIevFnZPR3BdI8mp7ZU zQb_BRfl67qWdpi6r0DL!aVm&hB(NAl3gXR29z}PIb-Zk0-BxMo7 zV=Q*WX!nl*_&MNh1}o`~QH<^tWtzZi$4Q`A`=l8FCt?BDmkWLp#!grBN=w*)_e^Xf{hf8783Vxcti(&g^pz*T8JfJqkl&pJqZakJMpYwdtq;o9^)E1hG>s(k2q0INuj0eD+U?d#0 zInC~>XGb%UB{|8HLok@#NfM(#@qYBiL2T)t}vPXeF8S zC@}JKmw|g|r08@(=FljH>6W5}tNC;$>XtMyS-2-CgvKdZaLQN#(A$8?7?C@k=0coB zghLoxH^bA5L65ngIx6(fQkr_S+O8%$lvma;8sh!y^LsBl%@5XE{g;I^S4t>A;Mva~ znN;ZM@|NNnVm)|@`-UBNt#o~+W%E6W4=h;2F5BEgzb)9qEveG3M|MR^%~c3W1$oCl zw#lNkQiENuHxE_ZKCn>wPJhg_z#jZD3s^KT9kAevpAb~<2`5He+jljR@aDJ{KL>kX z8P7_1|0t2Z=vC!Bx*mSHe!rjS!x4yXbz8vCAtw!4ox}z$cSNueq2Q@<@{SjmDtIeC z0`tC!?^OS@L)B$M$!(feq!f(2BACQzv$B&N+-uFzpS9SO50R^zadqJ)JT_jq7TOGl zTemgTw3i!T0(#oxuwceFEZWUyd~-wk2i?3yyAL=)w7E{$k_+gnb|(Fk5P z%0y`tFrFZaS(>8C~!HO~BtcikO^81;Nww7E&=i|CdWrX`-tT{puVkbpYD$+tGAFSOTNqZV}w-5EOOCw@xGhvA45$YayqBdOPflx#)#ik*w0_}%nDPH_(sSBN*(_0inI~9 zLDB*IR&&2-{>Tl;bOuUgD}xikv;s1u9X~Q_m3QIH?8VcgST!ixQnzn^B=b(u_6_cR zHK*X(z<)2a^o_o5C%SQ-CsTMQE{ZDoNf{-j?$MkM!(w`&4caSV=1$8%oEyNV@`V`( z|CPJ&5s{E%eY)kQdSCQz{!BwNRwlmU#yMHV+uzqJnko7V21=u%tWhWf70;-=0{pRF zhAAq&1N%6}cEP=Sbb>l+Qb_sA$m*cuFYm4Ao6J&FW{i-EpExDh@9}q;#la)PvR@zM ziR_Z89rx;6!M~Jg!GFR4t(fFA-A8_AoVc+Pua=g^$D9amy#uCmcsLVr&IIzOFXSY?Z4O80(i%(*AJ=KD)!_+At3FmDR3 zQ^-XLds$C~H}|!&V6ZA-9tm6=@GTgTHdc=f&8Yxck)iwS`L|cbv&vl5S!wa!71=S! zAZkM!P2*e!DWLn-!%IolkWE1=!y-u2E7MZux!tkb(+o4)!>p`rF1zfh!kHHVy#oI| zgH@t+b4B{5NL7szsn|*MOuvVtTkK!5& zyuZ(4yWdz!`Db)_vImUDSw{7(da784-G!0B@jL%Oc~RF#my!<571m53E;5;I ztl>IjcnR?=!FG>++M8Y8Nf+kwB>k~L3wfGzC^I$Ti_lB-+--=ckZpT*PYW;|ga0`{ z=9DsOc3sx+55bucBvvg%Gx{1d$=xlPq5j7K@=?hXixQrOAakoO(*KH%CE047@^+{7 z+nXh%75WT8A}W9QQf=TY+k0f&f4&r?+(AjaG+f2;hc3QE!541C)aCM!K@*Pc(p)`Q zj5Qu^IsZejD(&x=aKl{jO9n$+E8!?L2yuAfxb(kXbTR#g#ij3IMhEKk+hhnNa+2gx z0c0MI+XZ+mc0fbzqWw=M&QtLL&33-&-WuGkQK_`NlP4u|q@<~nAs~UDr=MFW zh->|OZ`H@5x!}(%3S{U_Rc83(+LsF6^=|OgV{5fsFINQ(J_~&r6Do| zIVRx9o(3;X6+QHuHAJEIX(H3^jTrZd#t`wsL7m-WRfQ~G-d_v{5)mHFa-@tkQOFtJ z-$Vlj9`uIOV51;LQ0GH4&eb;gdIhs|oc7_OHRhDI44hC#F4yGV+^1&_NMt?b+Emun zClKvsA~wDbF0P^%b1_m4cVjOX9I>MN(MbSlR(A5{Zmcz zqKc0jzdcVcB_+cd&3d_it(5#waxqmfk)TGPW!LL#Xk)O^5{1#K#(TXW$*gh<+DS_2 z!M`Zn3Q;BVzO2J%P(~)7<~g8ms!ut6fW0V`=u!L}#|);MHmyzL+0k_D>;kNLoSW~7 z5xnK#QgRB8u4qS$b!F-f%+RouKgRoqK)_`)4@82MOt!LKI2HU3#FcZpMsGU! z$>6h8uwY)ReD%XkO=s%LFhJ3|f>6}!VZYy8>ANpwF?|f^EE!We*5C>U{Yx=R&C#>r-)9qD*^^()J5j&+mjz~O;7Z+f=wupeI4aV7%ya+`Ild>b zm5|6>3n_j00woc#Fg)R7o3c*SJiSaVcKqzz&gnS-V>x<18P!EL$gc5K80+b@i!6Pm zcjc?awh!CE=D*GJx1|xM$@tPs(OABwd6?u^AcP{`cmO-yOb-8&HrEJ(0~dQw=GxhM z_T$<4+mfk0Ak}j{bNVd3To+3Cwyz}3bG;`}g4wLSJ2RhLZG@bq!#knPi%Gi)Aw!lK21lB6&fvm-)Uh)n;gli`EKf1bG9didh` z*XG}Sb=#_b(t0yni;Waf9*?#tF!(uC!(b8na zV5-TOlgn%oP^fmyiu|z5xfaoJhgCu)l;K`v{`b>3vggz+bCf!yVJS?=td^}bAvt`> z^$dnzdns>#$cl`bTK+^K*R09=JHqS(T#%M`QVl|iCmG8$DeK(tzj=@>_irj-xFgGR z)fq`m%VfJQ)FPI&M;7+&`5iGYqm2iqY3`pKDxs>ow7@aFSvqdo(@F=eA!)hU1+$9@ zo6D^jsZP>Ns^|_!2k}oWQb*cGa?yH+*Df93$mt>|%Q$L`mg%RZgp*h^W-5lF;LGmH zNG*WU^M(uwauhLG)v!K^D3ZaUMtlT7jJFS-TiUq-29xZ7M7+~{AqOE%QG42og2qJ0 zu4ZFu3zd!Osa%30J3PnI3g(8@(TEP}kmX4iV+Ao<8lVFNMyy82qm|QbYAi_%a^6zx zCKEc37OQy~v3y1dz>SO?cP~iK?`%0-%5`lzj3UH0Dih+W>#ieD4gPd`znG zSKha*!^6nL{~0%}UmeK$k8tvGi!)ss-^;}n>W#C9VPK4XV0!h*93fr3J8~MMs891F z_<1|nNDXqXIZCuMt6%12JQM28sg7H_vGZRB)){!6c~g(#VO#*%)0$ z-M=u2G9AK+u&n!9KodoOw!_>{&8qMxrFx1RsE1ae*^Wk5Y4+L_5wa*xpN#bJS!wpX zES=~H$kywWuO*}*rnGa6uwI^gu*C@KLpXQNkXF^r)PV4NYLITO=@O%bX535x*AGmc zgg1{F=>zGE-T4w7U?%f5!&0KSK-OD>9rbFa&jBS1qo>mNIIs6ft2m&+kVMrM=cX~< zN7fVB$a2Frl|AaFdS@fzIQ>ta*!A3HCfcNW7Yaejk!+uNx~=xGGP=+(kY#`4qt;9< zG(-s}w&`KOM|axCRAy++i%#NOrr+|c&phdAl&7w9iac5TP%g})gv*kT?!nuO$9YHv z>HGAJWix*yeT?NoWcXbQWvqqGEx#G>cYZ=e^1q7h)!X`O@IsO+7Us^QC~-Gv?ZqcN zb?MWLu@+IFdE~Ad&yqbhZZu!J3|bNN`T=tu`Sz+*36MQo$yVBm>$~;yMk-0eZHb_=7;~?|@ zci{(ho_Z!E0Vh&M?RW7rP)>WO=f)HXFr717ov*iT;4Tm)9;>)uX>Y}ir1mqANpi02 z6zxBQD`&VVhw>j^h8WIgRkrr-D!u>tTuf^T*BE$0TL3Fy9F}Ne8Lo|QD)o7*^^(6( zB!E-#Pf1w2*gyT}Ga_F$h6MW$uy80UM0T_Sz64GyGXYw(Qcmp8o4WSY_$bv!KoZxY zGSd*&1w9#?XZsn(*j_wY=;y)`Qw8H{4sKej)Y|$+y#}QW5K#~P%%45IW8}BYM1|W7 zr6zx#Xa6k$Y{IL!jHiJFFNH3wOfMw$&!5W|vPr~q50a-Yt`g4vV=}mVE&q_I z73t^04bcz*q8WHeI0mkqCiwGW^x3;Z&#xXl9H*OSf%Ft9e{{z&79tfBB60;o#lrT9 zguqQOddb;H++xH54QH7D2Uh7dqVzkqagH*|^Ic-|WAOsPzh>?o$04`1!Gn%&p{pRp zTIIV*7-HxJ!kxtx#A}N=zoelhw)dvnJ|lRYrk;SXkw06)Ioah zEq$6#J?>3c4)wKlCYTs!&?-bk{LCX|3puvH6ZS>{AAJ__y2+bubBC%LVQ|Q1i>4=* zFolw~QJhhEU=H27bNR%N@&umM{3v}zDhF%=+N%4N^^5-7iTGFAvZusEoV^a!=*Lg% z`ZtQ$fE*5H^y7Wn2eC#GKZw-jc>YChD9Bc}(P<(kRF<^WFZn)N{LO)sxUyHg6tFKoN*MF=10ux`;|>Mr!>(G5cM>+K>2}IV%*WLwl-7=2Tg3` zpnz7hY&ry8u~atbpEfODB5&_PhN=4?89tj~y%^!#tW3<+EkOKb&_DNyRsCno)=uc~ z0-v4mi-$Q`(UQr3-~v7exh(YX_X6uLPCS7av~S|UT-#@}@4P@KZCGgPTFjW1KBZ2% z9hI@B+j(I;FCo7+rAiZ6a2={4q!}U9IM@Sh>dX6>U}moB%Bs}lSz?n`V$h<3OQLfF z$;82Wq;e2+sUNqI_r8aLwt@~( zrTw5bPr^Viopz4Ym_n4>>mwK}EEZe-3;8f6n2YHlkZyqzm-&3NLb}SHH2I$t{t%9p z@qdg91#D6DC&=4rk9K^!pwHNC1vVz|LHbQV z0<%TayMCsPTGt5fW>3DnMbLAsbMdQ+=7-BtXy29_a2g)^1eE@7BCR3{M8_x zH{k2N9OBG%J-17QMyJy2b3s(kGBfx+U<3H?eRWML-uKLtuyDGw-9LEMZI!GZO_M2$ zbin~7`a9~#T{nwW&%TBMHqliY{YcBCVR2i;U~Dp43!0lg#I#}iQaB{{6?vY38 z_A$FI3Bfxg(@abR_#HiWZ919fPD|n}K{M2fVOMst!ItK9UDWJ*iToQ}AQzDCA(3TQ zDNB_XQ<3?sZAi>^1O_;CQq1Qp(ss(KDm8VqwQpY+z#G$lbDD76ziiY*fc3)miDG zw+~T8%CteAOWarE%S6wXdYgVLB1;GXwZ7kJbyhn-*GI3y{blFD(XZ1IrMlyUI%ytx z-fnsA(=|$yU$G@r63ffamN{wjZsS|zwh%|Ngw2QGrx~$0dX%AL;++O`GFuR!KoDnJ z`xLZW2ZJKRndG~D9ZU^aD~L>eABMJQnr>jhJ!b8^e(RL=v2bfPE`PSl20gRFq#jn| z%B#*~U(6K?FDYv2fLSR0TDV#nbHUn5L;!O2jq>31SO+@nYLt_LTozDRDYAU&KoJ2& z1P>tF>3V!v(9Gr}0zY%2`snn(u#9ev@0_QCGWtK=wi%`T+eJNPq>hK3lJ$r~>)fTZ<6M%%(qnNM^cpHy%%ANw`r`oU*jor@AmPJ4ldX^AMAny~3MMz%@+`Fq zfXR-^Km2t+;MS~B@NJ%4Hi*i3e2iAu2oURB^z!D<%a-yDF)mrto0nN*7{G}GlI2wC zAA%z7k{Wvmw!da#gK>L;v@Pg-QDLC)%6dF~q^aiG^7}O`5*kfM!u}XwRfUMPT$Y|u zSS>xUQX&?u$y$JRJJJGwi9Fg#{xbO;&q!e^(}sK#_CfZcS1xA2X_uD&ckoi}-v~TZ zwf<;IdqL_sN)YdBrh-c3>|^;Oj1q_}Z@u|bj#VyDhW_8SJ0Q?=Hp{dWJNd7 zkVKG@lg=1@8j*pQ2+a(|F`})~WbEr{6RTFX5StsTC&AR&;^`e?LSyKQg}wvW8qjD~6Z~*90acOWJKd6~dWSbt|A=M>*L^K*=<4=t*1N7hba)?i zlW3H4_xt#JtlrgW??01*cXN=yOwB{#oqe_3ei9X|^dE?I|sqM$a4+7JEY*r0{w&BzK>g6Ys|& z#hq4H9QIGUxYZ;LE!er@Of80Yq&((!Ez(E52Vq$*Z_7rpb5LFJAdO@37d~>TDe3TC z3eJ}7@_sHh zWNHV|Vz0nLzhF_6d8~r|A~zw;kvW2T zSD_E^>o>}ER_e%CXq8!XB9=rO&vY*;btrW=Tc!JO7y@d$zzENkVKc4RrX{91*X>Q%pB;%P&EYLOQxxy;yB;{qFIf}EE^+S z4C!Z62hsBHXDhSuZ0U}cxO8Awk-4&(2{QJQRX&1d3awG=2qwp9Y-a`rL<4Jdo`ymIzpaXf(l ztfl9A%c5kRuNz_#2XNWTh3{dZ9J|C!6aR8jy@&Qa@`TnhTDHZ{-lRKQ0=^dF!c7W0 zAfisbG|IS4+h)2rF zlyW`(#8|iidFqMt8-Hb?>;7o&>0&uY&(sLAwd(O67&kjwv5_xP6xXY{4z*d&gKga* zja%~jK>Bl7>%qGOuVF^0>r7-`h;^c#&Yy{Fey-9>OV&*dAI`Z7N@boXoFeA>SnObV zA>jSO=~ZzhWM}rre%BS3i|h!JwlYPYE)7=^@#!HwfN>cP4!`cdZvpt`+|+8(`xtv5 zEkm{z0gMRsO2_0sDRf0HSI&+%>tRud%B+7x4|Tk?_H%Qzq=|9JZj3^frk#5>3@+=L zN1BK2=c`0A8^a{Rn*t9IWy7$aMDu>$pjIp!(aMZM5OE{}kUOWT%;#=2c5sbK$2Df1 zUvc%;K%0YQ_E7Sa()mrbuR!UE@AC|%&W(3f`xC7O8fUKFZt?nIKrY*PecCi;KVwb& zo`20ys0SpVyCTsuJshl0i{#gaE)5}?d!qn-Ry!3;v<#Cr1buS8n6?G-2v2{-bGRX; z))a1E&*(Uw^P1o2D^uuFz3Q0HTawmO;QgRinTQ;I@k<6r5Jn7U1V)i-I34f)AzHmS zVXAhv=H&7|{B}q-a^$ysY$`OKNE(rqN@m`5Q%FVQ<(>Y0oX5Uslo`bck(o5d= z=cjCyZRbMH@iC0L9JKFIBz9i4lwQu4YfEcop^5Q}-8AKYwR7hGP<`%Uc-(XDJ@<9ac|V^ynY=g*CdCTxQy#K%$fd|qGMA`8_vA+qUqd%gP1!~*|ANUA z9XIU1p_lLXyW&Lo8y;n8#C*QQiUSiB__rjGC%%A)R=*?2 zB>mUeh}*Bn2~GtnA;IH#0sZ)z`@#V7za|Kq;T~WJUDn|%5JpyzW`FwoU7%GiW2p(% z*w^-n_|R&*?kWaM#Ap?{R*tYz+CpQp0cK#TjzA-hWx4{UWvknmNS}=!O&LvtGqVYUlVJKU8?q?~#sAQGGtisNz^d3W?@Ai}dQpoZ zt-$f8i~%&0kkftpy>E5GHFCaMZSC!EZW;T=VP7c;<~a5Y3X7*4uB_E=4|M`J?ptLD zC1)CF5Sjk?v6YM{cR*zOv&GIxs#0YVMt=4O z*rftoODFCG-*|}`eKy3S3c`D@iC@3L9F&(8Ye?omkl%@iW5Ui$Eqq9!Hp^VsN=&cWdKfa95?6mHhL7(s%1m zYSuA5Lk~nVjr$WPbyhcLOwIfEfq>fG=axUJKt$et2-X7Cip>@n=-z*?FcktzH!@BY z*DfuzjuYZ`DRWWE{#-4f$D=No9LOK1_q4s_M9x3UTv@=>l<#*FyRk&t!@g#0AUWxM zGL(Dx8S8U?`8L5KMuS}nPvL*j3o~3^AZ$**dJMqVv4jDun3~7bpY-S(d0e zf6E_|Kr@a1Gtq;Eyn=Edc%TS$=8#^QNcTH$>>_|F@qXsjQ2?uU?%Lt-*}H4ls8&h$ z1e$SH$XKf1!)nT%&Cxmw$xMW(ete1ffipj1znHCYAdKHACXQ}8#h2NGgBHfsd5|Yp z_e3Eg`x-2GkC%b(;qGJUmY~`*x_09 zVqn+@5W?T*#UTyB`!e40LiDDn&MuG4tGl{k#xfC0&clU4>LAGQjRccUS@j?5+13lR z%^GC2&emv`c5r!luqhe}mI$~TpV!mz0;f1V`gYZVY-o47!~rh`ucg|y zbFKEbxQ~{v!ti79y~iBCEpn1(JZ_Yp_-F5wet}_rP86>!fcx9d-u!Y|)GoZKoc!~` zc!Po7(h)NQ2Tr0EIx2A^N5qmy=N;++gnU0gj3nR1&Q>Q^lD=O39`7r!BjtKuHwC)r za|GuYx~=m_ls-O&EZnGd!w_SlcJ6O0{0Bo|WOCPY2V3LDNYE^)PM8+axNML1#N=JF<$#m|JObJRro0ZsyRLt&pqQ9i59BKPnNoWZCi< z)9c~WeI;Sl+pA?{_u(;oZG*72V`Nlg&ngx+3gDZN+zL`1_Q5ewpC43^<-fb7%hBiO z`5M;8PXLYVx!Q{Cn%ki2bhAn;T!CuAYYgv?9d1W^ZNF^wyfkk+ofS%$7;a7r6=h@} zeg{_&hbN1JTCu7Tvy_&N^!mHX`MFy3sDEI^{#zCOI$0cm|}- ztBUL8@cis_BT;R1S1z;JoXrX0 zsaZw`y4~>5so9LOvD6yyAiy3M`=$C+V0LrtydKTZVprM&j2YFGU>=@-|JxoxNu)H^HzFH8NZDZ7!^nxUI z2htH)-G!ij_~d!G+k0u7NH%p#84KBBrMa*0f}1XrSTbgn^PaK*LKj+bm!~Az3fl=* zflPacW?Pz~4H5Ezj}(-Jf<0O2OQ@4&4I2yRJv};T*|*9XbZ9cTo39+mKr>rb>##}I z8RC}wd3wExk7C-=^2~d-&uONcp#t{Przk_LLjA4*%)kKUCCzDeFuiS6fS!S1ra($E zpFE)#aHhoxrf8@ew@m+FEoJqqgYcE+rK<)}pa*kKfC|N|jD?$UyGe9)4|YZojYOfe zhy5_BU;J|!gYWKRPXZ64eks05)N6KL5daW+9tPUYl2obJQ+4W5M0BQF*XkAY8e^c? zghuZzK+4lC+`iH+q$b(9uB1}UWxZL!6Zo3x!4|tJ_$i+`6Z4$JPAz`hzXW*XJ^vx3 zT0QPan&COD=$U&{&(-6JuuJ-H+%7~C44VVJdc(9|n5ZOI;uP!{L@Y%M^M;A@z!bL- zf(FIe_^(`|ZAx2X9OEo;&lF=X1W&eY40~l(V%T_BiZs_k|OLLzRpb zpw!U?_d*vO7dAgH;_!bPzgyy_tNykSS>m2Na2A`OTTYI{dUuQ!2;-K!1u1>%Z{Dtz z<3LH+Q{c*Q5%xJ{8E>>HIdsW zYcjCPo5mAVm`I2*F_b>r=vCPkMp2fcpd%_bG0l5Yg(?95p#{3uXz-x9sx9yjFiYs#QQ{*anLtd)zO?JNS*jcZh!(soWS7I%%C-+pw9^x)EuG4vhjM{> zaTb@Sw93A|1DGlH*T`ENe4#(ljPou{rmkcFdT8zRhr2+F&%d@3n_w{gHv6MZT`|1R zE}Jh?jyp_d-jv{R@lS|ihLyW!UUZogmke8YB~g9}g0Kx*4KOSK7DSG1Kgq3{Hgum; zZ=VJXLoR|jvzo~aAfxMwMhb!jg26?s5gXkC8o#!yG;hZAd{0!N^LuiiBePZAGHUi; z3f@^ZEbL-?+d6V>0n|2tznoS}Ym#3Se~rMiHV+8L0y!e08V6ewD6TKa$8d|+7t>sS zwmt`>HsgU2=~_oJ+@Z=c5g8(=q^ltMxwkT=!JbM$WuC>}M*}suYE&DC&nP<-`BVg> zovJ&?jhs_a{4F$`jWQ0Z09**6b<{sUD(=}-DoZ%JJUsf&>eP1h?AqmuZQ9W6B#n)T zpM1mkaiY7gXSr`xtPWh*#!Y!58t|N`t?ll@HPRudu;s$XFp^#^ODXEw{07w1tzS`q zz5e%<&8NZK)nh$az5r8m!B>fVgL(K??Fa3Ujsm#TYFePmkfL3^`*Eb$M6Q{MeEI4s z*ifGf)~*_9H~n_>xGS=;?9YMqqt`rAw@>b8SgIr|-4cUyw%EWOf68Rlo=1n+1-Iqc zjBY0?c-#4|v^zWn?Q8vS=RwW0o=agYx{k$=?p9Q$AM~nkM^l{t5Vb!N%PKjCDk*No zuLJgxjr*t;L4^_7+q%>)f?)Gm+i~#jtScUCWI__1erQ-;_QnDC2}%Sy)AWps=~Ci8 z>xV#3O;u0Qw54?zYbbz?Nsay?Nmqv= z7QqSQE!Y6J2sA-j#OhqMmr(QbA2y%mCjq?&ufwtBzKCwYA6F_k&Wd#`MJfaFm4w~% z88a!WTh=b&g|lGU8GC#8zSzyNRK~t#HxXoi+hgnE9XCZnut?gy8|ucFq?Xxon*}^D zeJC<6otG<$J>N2 z6epnX*I9HL$s3^0_y2(Q-*CzEV&1yXJ2_0SnoC4tNIw7y5QFV2mfI|RS# zx_N8qyk)%AO>Gd1?eHaKlQZX%J7il=-YvT{59SOLF>x~($Vu|2`}5S)U(Kc;Y455t z{WB|8iPEH5H31#z8InX$d&I}8kh z0vS|2tm`q7TrWQI*H=XLTom&HZeNu8*?Np@_n8Fh(ivp#Dc1VLx!zb=J+1_wIw^$i zDQ$8(jfay~@Y z3oapC83H-cceMx3MK{joqYGeb98Z$OM{d44BBhGcT&0mWZ1N-^01IXV1du9aPu`RC n1P%;!frI})^MBi5iApc Date: Sun, 18 May 2025 16:56:50 +0800 Subject: [PATCH 2/2] feat: web ui channel update --- channel/web/chat.html | 522 ++++++++++++++++++++++++++++--------- channel/web/web_channel.py | 135 +++------- 2 files changed, 435 insertions(+), 222 deletions(-) diff --git a/channel/web/chat.html b/channel/web/chat.html index 73881ca..5186a80 100644 --- a/channel/web/chat.html +++ b/channel/web/chat.html @@ -190,12 +190,19 @@ .bot-container { background-color: var(--bot-msg-bg); - border-bottom: 1px solid var(--border-color); + /* border-bottom: 1px solid var(--border-color); */ width: 100%; - margin: 0 -20px; + margin: 0; padding: 20px; } + .user-container { + width: 100%; + margin: 0; + padding: 20px; + border-bottom: 1px solid var(--border-color); + } + .avatar { width: 30px; height: 30px; @@ -222,12 +229,30 @@ flex: 1; line-height: 1.6; padding-top: 2px; + text-align: left; } .message { width: 100%; word-wrap: break-word; white-space: pre-wrap; + line-height: 1.3; + } + + .message p { + margin-top: 0.5em; + margin-bottom: 0.5em; + } + + .message p:empty { + margin: 0; + line-height: 0.7em; + } + + .message p:empty::before { + content: ""; + display: inline-block; + height: 0.7em; } .message pre { @@ -319,8 +344,9 @@ #send { position: absolute; + top: 0; right: 10px; - bottom: 10px; + height: 100%; background-color: transparent; border: none; color: var(--primary-color); @@ -330,7 +356,6 @@ align-items: center; justify-content: center; width: 32px; - height: 32px; border-radius: 4px; transition: background-color 0.2s; } @@ -484,6 +509,68 @@ color: #d4d4d4 !important; } } + + .typing-indicator { + display: inline-flex; + align-items: center; + margin-left: 0; + justify-content: flex-start; + width: auto; + position: relative; + top: -5px; + left: -10px; + } + + .typing-indicator span { + height: 8px; + width: 8px; + margin: 0 2px; + background-color: var(--text-light); + border-radius: 50%; + display: inline-block; + opacity: 0.4; + } + + .typing-indicator span:nth-child(1) { + animation: pulse 1s infinite; + } + + .typing-indicator span:nth-child(2) { + animation: pulse 1s infinite 0.2s; + } + + .typing-indicator span:nth-child(3) { + animation: pulse 1s infinite 0.4s; + } + + @keyframes pulse { + 0% { + opacity: 0.4; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.2); + } + 100% { + opacity: 0.4; + transform: scale(1); + } + } + + .history-divider { + padding: 10px; + color: var(--text-light); + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 10px; + } + + .history-item.active { + background-color: rgba(255, 255, 255, 0.1); + font-weight: bold; + } @@ -530,12 +617,12 @@
编程帮助
-
如何用Python创建一个简单的网络爬虫?
+
如何用Python写一个简单的网络爬虫
-
+
@@ -564,41 +651,10 @@ const newChatButton = document.getElementById('new-chat'); const chatHistory = document.getElementById('chat-history'); - // 在页面顶部添加这些变量和函数 + // 简化变量,只保留用户ID let userId = 'user_' + Math.random().toString(36).substring(2, 10); - let currentSessionId = 'session_' + Date.now(); + let currentSessionId = 'default_session'; // 使用固定会话ID - // 轮询获取消息 - function pollMessages() { - fetch(`/poll/${userId}`) - .then(response => response.json()) - .then(messages => { - if (messages && messages.length > 0) { - console.log('Received messages via polling:', messages); - - // 隐藏欢迎屏幕 - document.getElementById('welcome-screen').style.display = 'none'; - - // 处理每条消息 - messages.forEach(message => { - addBotMessage(message.content, new Date(message.timestamp * 1000)); - }); - } - - // 继续轮询 - setTimeout(pollMessages, 1000); - }) - .catch(error => { - console.error('Polling error:', error); - setTimeout(pollMessages, 3000); - }); - } - - // 启动轮询 - document.addEventListener('DOMContentLoaded', function() { - pollMessages(); - }); - // 自动调整文本区域高度 input.addEventListener('input', function() { this.style.height = 'auto'; @@ -623,60 +679,127 @@ sidebar.classList.toggle('active'); }); - // 处理新对话按钮 + // 处理新对话按钮 - 创建新的用户ID和清空当前对话 newChatButton.addEventListener('click', function() { + // 生成新的用户ID + userId = 'user_' + Math.random().toString(36).substring(2, 10); + console.log('New conversation started with user ID:', userId); + + // 清空聊天记录 + clearChat(); + }); + + // 清空聊天记录并显示欢迎屏幕 + function clearChat() { // 清空消息区域 - while (messagesDiv.firstChild) { - if (messagesDiv.firstChild.id === 'welcome-screen') { - break; - } - messagesDiv.removeChild(messagesDiv.firstChild); - } + messagesDiv.innerHTML = ''; - // 显示欢迎屏幕 - welcomeScreen.style.display = 'flex'; + // 创建欢迎屏幕 + const newWelcomeScreen = document.createElement('div'); + newWelcomeScreen.id = 'welcome-screen'; + newWelcomeScreen.innerHTML = ` +

AI 助手

+

我可以回答问题、提供信息或者帮助您完成各种任务

+ +
+
+
解释复杂概念
+
用简单的语言解释量子计算
+
+
+
创意写作
+
写一个关于未来城市的短篇故事
+
+
+
编程帮助
+
如何用Python写一个简单的网络爬虫
+
+
+ `; - // 创建新会话 - currentSessionId = 'session_' + Date.now(); + // 设置样式 + newWelcomeScreen.style.display = 'flex'; + newWelcomeScreen.style.flexDirection = 'column'; + newWelcomeScreen.style.alignItems = 'center'; + newWelcomeScreen.style.justifyContent = 'center'; + newWelcomeScreen.style.height = '100%'; + newWelcomeScreen.style.textAlign = 'center'; + newWelcomeScreen.style.padding = '20px'; + + // 添加到DOM + messagesDiv.appendChild(newWelcomeScreen); + + // 绑定示例卡片事件 + newWelcomeScreen.querySelectorAll('.example-card').forEach(card => { + card.addEventListener('click', function() { + const exampleText = this.querySelector('.example-text').textContent; + input.value = exampleText; + input.dispatchEvent(new Event('input')); + input.focus(); + }); + }); + + // 清空localStorage中的消息 - 使用用户ID作为键 + localStorage.setItem(`chatMessages_${userId}`, JSON.stringify([])); // 在移动设备上关闭侧边栏 if (window.innerWidth <= 768) { sidebar.classList.remove('active'); } - - // 添加到历史记录 - addToHistory('新对话', currentSessionId); - }); + } - // 添加到历史记录 - function addToHistory(title, sessionId) { - const historyItem = document.createElement('div'); - historyItem.className = 'history-item'; - historyItem.dataset.sessionId = sessionId; - historyItem.innerHTML = ` - - ${title} - `; - - // 点击加载对话 - historyItem.addEventListener('click', function() { - // 这里可以实现加载历史对话的功能 - // 在实际应用中,您需要存储和检索历史消息 - - // 在移动设备上关闭侧边栏 - if (window.innerWidth <= 768) { - sidebar.classList.remove('active'); - } - }); - - // 添加到历史记录顶部 - if (chatHistory.firstChild) { - chatHistory.insertBefore(historyItem, chatHistory.firstChild); - } else { - chatHistory.appendChild(historyItem); + // 从localStorage加载消息 - 使用用户ID作为键 + function loadMessagesFromLocalStorage() { + try { + return JSON.parse(localStorage.getItem(`chatMessages_${userId}`) || '[]'); + } catch (error) { + console.error('Error loading messages from localStorage:', error); + return []; } } + // 保存消息到localStorage - 使用用户ID作为键 + function saveMessageToLocalStorage(message) { + try { + const messages = loadMessagesFromLocalStorage(); + messages.push(message); + localStorage.setItem(`chatMessages_${userId}`, JSON.stringify(messages)); + } catch (error) { + console.error('Error saving message to localStorage:', error); + } + } + + // 初始化代码 + document.addEventListener('DOMContentLoaded', function() { + // 移除原始欢迎屏幕 + const originalWelcomeScreen = document.getElementById('welcome-screen'); + if (originalWelcomeScreen) { + originalWelcomeScreen.remove(); + } + + // 清空消息区域,确保不会重复显示消息 + messagesDiv.innerHTML = ''; + + // 加载消息 + const messages = loadMessagesFromLocalStorage(); + + if (messages.length === 0) { + // 如果没有消息,显示欢迎屏幕 + clearChat(); + } else { + // 显示现有消息 + messages.forEach(msg => { + if (msg.role === 'user') { + // 使用不保存到localStorage的版本显示消息 + displayUserMessage(msg.content, new Date(msg.timestamp)); + } else if (msg.role === 'assistant') { + // 使用不保存到localStorage的版本显示消息 + displayBotMessage(msg.content, new Date(msg.timestamp)); + } + }); + } + }); + // 发送按钮点击事件 sendButton.onclick = function() { sendMessage(); @@ -707,14 +830,20 @@ const userMessage = input.value.trim(); if (userMessage) { // 隐藏欢迎屏幕 - welcomeScreen.style.display = 'none'; + const welcomeScreenElement = document.getElementById('welcome-screen'); + if (welcomeScreenElement) { + welcomeScreenElement.remove(); + } const timestamp = new Date(); // 添加用户消息到界面 addUserMessage(userMessage, timestamp); - // 发送到服务器 + // 添加一个等待中的机器人消息 + const loadingContainer = addLoadingMessage(); + + // 发送到服务器并等待响应 fetch('/message', { method: 'POST', headers: { @@ -726,80 +855,194 @@ timestamp: timestamp.toISOString(), session_id: currentSessionId }) - }).then(response => { + }) + .then(response => { if (!response.ok) { - console.error('Failed to send message'); + throw new Error('Failed to send message'); } - }).catch(error => { + return response.json(); + }) + .then(data => { + // 移除加载消息 + if (loadingContainer.parentNode) { + messagesDiv.removeChild(loadingContainer); + } + + // 添加AI回复 + if (data.reply) { + addBotMessage(data.reply, new Date()); + } + }) + .catch(error => { console.error('Error sending message:', error); + // 移除加载消息 + if (loadingContainer.parentNode) { + messagesDiv.removeChild(loadingContainer); + } + // 显示错误消息 + addBotMessage("抱歉,发生了错误,请稍后再试。", new Date()); }); // 清空输入框并重置高度 input.value = ''; input.style.height = '52px'; sendButton.disabled = true; - - // 如果这是第一条消息,添加到历史记录 - const firstMessageInSession = !messagesDiv.querySelector('.message-container'); - if (firstMessageInSession) { - // 使用消息的前20个字符作为标题 - const title = userMessage.length > 20 ? - userMessage.substring(0, 20) + '...' : - userMessage; - addToHistory(title, currentSessionId); - } } } + // 添加加载中的消息 + function addLoadingMessage() { + const botContainer = document.createElement('div'); + botContainer.className = 'bot-container loading-container'; + + const messageContainer = document.createElement('div'); + messageContainer.className = 'message-container'; + + messageContainer.innerHTML = ` +
+ +
+
+
+
+ + + +
+
+
+ `; + + botContainer.appendChild(messageContainer); + messagesDiv.appendChild(botContainer); + scrollToBottom(); + + return botContainer; + } + // 格式化消息内容(处理Markdown和代码高亮) function formatMessage(content) { // 配置 marked 以使用 highlight.js marked.setOptions({ - highlight: function(code, lang) { - if (lang && hljs.getLanguage(lang)) { - return hljs.highlight(code, { language: lang }).value; + highlight: function(code, language) { + if (language && hljs.getLanguage(language)) { + try { + return hljs.highlight(code, { language: language }).value; + } catch (e) { + console.error('Error highlighting code:', e); + return code; + } } - return hljs.highlightAuto(code).value; + return code; }, - breaks: true, // 启用换行符转换为
- gfm: true // 启用 GitHub 风格的 Markdown + breaks: true, // 启用换行符转换为
+ gfm: true, // 启用 GitHub 风格的 Markdown + headerIds: true, // 为标题生成ID + mangle: false, // 不转义内联HTML + sanitize: false, // 不净化输出 + smartLists: true, // 使用更智能的列表行为 + smartypants: false, // 不使用更智能的标点符号 + xhtml: false // 不使用自闭合标签 }); - // 使用 marked 解析 Markdown - return marked.parse(content); + try { + // 使用 marked 解析 Markdown + const parsed = marked.parse(content); + return parsed; + } catch (e) { + console.error('Error parsing markdown:', e); + // 如果解析失败,至少确保换行符正确显示 + return content.replace(/\n/g, '
'); + } } // 添加消息后应用代码高亮 function applyHighlighting() { - document.querySelectorAll('pre code').forEach((block) => { - hljs.highlightBlock(block); + try { + document.querySelectorAll('pre code').forEach((block) => { + // 手动应用高亮 + const language = block.className.replace('language-', ''); + if (language && hljs.getLanguage(language)) { + try { + hljs.highlightBlock(block); + } catch (e) { + console.error('Error highlighting block:', e); + } + } else { + hljs.highlightAuto(block); + } + }); + } catch (e) { + console.error('Error applying code highlighting:', e); + } + } + + // 添加用户消息的函数 (保存到localStorage) + function addUserMessage(content, timestamp) { + // 显示消息 + displayUserMessage(content, timestamp); + + // 保存到localStorage + saveMessageToLocalStorage({ + role: 'user', + content: content, + timestamp: timestamp.getTime() }); } - // 更新添加消息的函数 - function addUserMessage(content, timestamp) { + // 添加机器人消息的函数 (保存到localStorage) + function addBotMessage(content, timestamp) { + // 显示消息 + displayBotMessage(content, timestamp); + + // 保存到localStorage + saveMessageToLocalStorage({ + role: 'assistant', + content: content, + timestamp: timestamp.getTime() + }); + } + + // 只显示用户消息而不保存到localStorage + function displayUserMessage(content, timestamp) { + const userContainer = document.createElement('div'); + userContainer.className = 'user-container'; + const messageContainer = document.createElement('div'); messageContainer.className = 'message-container'; + // 安全地格式化消息 + let formattedContent; + try { + formattedContent = formatMessage(content); + } catch (e) { + console.error('Error formatting user message:', e); + formattedContent = `

${content.replace(/\n/g, '
')}

`; + } + messageContainer.innerHTML = `
-
${formatMessage(content)}
+
${formattedContent}
${formatTimestamp(timestamp)}
`; - messagesDiv.appendChild(messageContainer); - applyHighlighting(); // 应用代码高亮 + userContainer.appendChild(messageContainer); + messagesDiv.appendChild(userContainer); + + // 应用代码高亮 + setTimeout(() => { + applyHighlighting(); + }, 0); + scrollToBottom(); } - // 添加机器人消息 - function addBotMessage(content, timestamp) { - console.log('Adding bot message:', content, timestamp); - + // 只显示机器人消息而不保存到localStorage + function displayBotMessage(content, timestamp) { const botContainer = document.createElement('div'); botContainer.className = 'bot-container'; @@ -811,19 +1054,64 @@ timestamp = new Date(); } + // 安全地格式化消息 + let formattedContent; + try { + formattedContent = formatMessage(content); + } catch (e) { + console.error('Error formatting bot message:', e); + formattedContent = `

${content.replace(/\n/g, '
')}

`; + } + messageContainer.innerHTML = `
-
${formatMessage(content)}
+
${formattedContent}
${formatTimestamp(timestamp)}
`; botContainer.appendChild(messageContainer); messagesDiv.appendChild(botContainer); - applyHighlighting(); // 应用代码高亮 + + // 使用setTimeout确保DOM已更新,并延长等待时间 + setTimeout(() => { + try { + // 直接对新添加的消息应用高亮 + const codeBlocks = botContainer.querySelectorAll('pre code'); + codeBlocks.forEach(block => { + // 确保代码块有正确的类 + if (!block.classList.contains('hljs')) { + block.classList.add('hljs'); + } + + // 尝试获取语言 + let language = ''; + block.classList.forEach(cls => { + if (cls.startsWith('language-')) { + language = cls.replace('language-', ''); + } + }); + + // 应用高亮 + if (language && hljs.getLanguage(language)) { + try { + hljs.highlightBlock(block); + } catch (e) { + console.error('Error highlighting specific language:', e); + hljs.highlightAuto(block); + } + } else { + hljs.highlightAuto(block); + } + }); + } catch (e) { + console.error('Error in delayed highlighting:', e); + } + }, 100); // 增加延迟以确保DOM完全更新 + scrollToBottom(); } diff --git a/channel/web/web_channel.py b/channel/web/web_channel.py index ee7b96d..c63571e 100644 --- a/channel/web/web_channel.py +++ b/channel/web/web_channel.py @@ -2,7 +2,7 @@ import sys import time import web import json -from queue import Queue +from queue import Queue, Empty from bridge.context import * from bridge.reply import Reply, ReplyType from channel.chat_channel import ChatChannel, check_prefix @@ -58,91 +58,29 @@ class WebChannel(ChatChannel): logger.warning(f"Web channel doesn't support {reply.type} yet") return - if reply.type == ReplyType.IMAGE: - from PIL import Image - - image_storage = reply.content - image_storage.seek(0) - img = Image.open(image_storage) - print("") - img.show() - elif reply.type == ReplyType.IMAGE_URL: - import io - - import requests - from PIL import Image - - img_url = reply.content - pic_res = requests.get(img_url, stream=True) - image_storage = io.BytesIO() - for block in pic_res.iter_content(1024): - image_storage.write(block) - image_storage.seek(0) - img = Image.open(image_storage) - print(img_url) - img.show() - else: - print(reply.content) - # 获取用户ID user_id = context.get("receiver", None) if not user_id: logger.error("No receiver found in context, cannot send message") return - # 确保用户有对应的消息队列 - if user_id not in self.message_queues: - self.message_queues[user_id] = Queue() - logger.debug(f"Created message queue for user {user_id}") - - # 将消息放入对应用户的队列 - message_data = { - "type": str(reply.type), - "content": reply.content, - "timestamp": time.time() # 使用 Unix 时间戳 - } - self.message_queues[user_id].put(message_data) - logger.debug(f"Message queued for user {user_id}: {reply.content[:30]}...") + # 检查是否有响应队列 + response_queue = context.get("response_queue", None) + if response_queue: + # 直接将响应放入队列 + response_data = { + "type": str(reply.type), + "content": reply.content, + "timestamp": time.time() + } + response_queue.put(response_data) + logger.debug(f"Response sent to queue for user {user_id}") + else: + logger.warning(f"No response queue found for user {user_id}, response dropped") except Exception as e: logger.error(f"Error in send method: {e}") - def sse_handler(self, user_id): - """ - Handle Server-Sent Events (SSE) for real-time communication. - """ - web.header('Content-Type', 'text/event-stream') - web.header('Cache-Control', 'no-cache') - web.header('Connection', 'keep-alive') - - logger.debug(f"SSE connection established for user {user_id}") - - # 确保用户有消息队列 - if user_id not in self.message_queues: - self.message_queues[user_id] = Queue() - logger.debug(f"Created new message queue for user {user_id}") - - try: - while True: - try: - # 发送心跳 - yield f": heartbeat\n\n" - - # 非阻塞方式获取消息 - if user_id in self.message_queues and not self.message_queues[user_id].empty(): - message = self.message_queues[user_id].get_nowait() - logger.debug(f"Sending message to user {user_id}: {message}") - data = json.dumps(message) - yield f"data: {data}\n\n" - logger.debug(f"Message sent to user {user_id}") - time.sleep(0.5) - except Exception as e: - logger.error(f"SSE Error for user {user_id}: {str(e)}") - break - finally: - # 清理资源 - logger.debug(f"SSE connection closed for user {user_id}") - def post_message(self): """ Handle incoming messages from users via POST request. @@ -167,7 +105,7 @@ class WebChannel(ChatChannel): msg_id=msg_id, content=prompt, from_user_id=user_id, - to_user_id="Chatgpt", # 明确指定接收者 + to_user_id="Chatgpt", other_user_id=user_id ) @@ -175,13 +113,24 @@ class WebChannel(ChatChannel): if not context: return json.dumps({"status": "error", "message": "Failed to process message"}) + # 创建一个响应队列 + response_queue = Queue() + # 确保上下文包含必要的信息 context["isgroup"] = False - context["receiver"] = user_id # 添加接收者信息,用于send方法中识别用户 - context["session_id"] = session_id # 添加会话ID + context["receiver"] = user_id + context["session_id"] = user_id + context["response_queue"] = response_queue + # 发送消息到处理队列 self.produce(context) - return json.dumps({"status": "success", "message": "Message received"}) + + # 等待响应,最多等待30秒 + try: + response = response_queue.get(timeout=30) + return json.dumps({"status": "success", "reply": response["content"]}) + except Empty: + return json.dumps({"status": "error", "message": "Response timeout"}) except Exception as e: logger.error(f"Error processing message: {e}") @@ -203,31 +152,14 @@ class WebChannel(ChatChannel): logger.info(f"Created static directory: {static_dir}") urls = ( - '/sse/(.+)', 'SSEHandler', - '/poll/(.+)', 'PollHandler', '/message', 'MessageHandler', '/chat', 'ChatHandler', - '/assets/(.*)', 'AssetsHandler', # 匹配 /static/任何路径 + '/assets/(.*)', 'AssetsHandler', # 匹配 /assets/任何路径 ) port = conf().get("web_port", 9899) app = web.application(urls, globals(), autoreload=False) web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) - def poll_messages(self, user_id): - """Poll for new messages.""" - messages = [] - - if user_id in self.message_queues: - while not self.message_queues[user_id].empty(): - messages.append(self.message_queues[user_id].get_nowait()) - - return json.dumps(messages) - - -class SSEHandler: - def GET(self, user_id): - return WebChannel().sse_handler(user_id) - class MessageHandler: def POST(self): @@ -242,13 +174,6 @@ class ChatHandler: return f.read() -# 添加轮询处理器 -class PollHandler: - def GET(self, user_id): - web.header('Content-Type', 'application/json') - return WebChannel().poll_messages(user_id) - - class AssetsHandler: def GET(self, file_path): # 修改默认参数 try: