From 6e2033615c16ce34cf2f95610f12eb19b665bb1a Mon Sep 17 00:00:00 2001 From: coso Date: Sun, 11 Jan 2026 14:11:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=9F=B3=E6=95=88?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20(v0.42.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加工具调用音效 (tool-call.mp3) - 添加打字机音效 (typing.mp3) - 在设置页面添加音效开关 - 创建 useSound hook 和 SoundProvider --- package.json | 2 +- public/sounds/tool-call.mp3 | Bin 0 -> 10989 bytes public/sounds/typing.mp3 | Bin 0 -> 4461 bytes src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- src/App.tsx | 47 ++++++------ .../agent/chat/hooks/useAgentChat.ts | 52 +++++++++++++ src/components/settings/GeneralSettings.tsx | 39 +++++++++- src/contexts/SoundProvider.tsx | 16 ++++ src/contexts/soundContext.ts | 10 +++ src/contexts/useSoundContext.ts | 17 +++++ src/hooks/README.md | 1 + src/hooks/index.ts | 2 + src/hooks/useSound.ts | 71 ++++++++++++++++++ 15 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 public/sounds/tool-call.mp3 create mode 100644 public/sounds/typing.mp3 create mode 100644 src/contexts/SoundProvider.tsx create mode 100644 src/contexts/soundContext.ts create mode 100644 src/contexts/useSoundContext.ts create mode 100644 src/hooks/useSound.ts diff --git a/package.json b/package.json index e989ab45b..d99997afa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "proxycast", "private": true, - "version": "0.41.0", + "version": "0.42.0", "type": "module", "repository": { "type": "git", diff --git a/public/sounds/tool-call.mp3 b/public/sounds/tool-call.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..63fe8002ff673fe39014ff80a481e46025078240 GIT binary patch literal 10989 zcmdU#050fW8~`AM0ssiTm7tqwQv?YB z5G{FaW#RjA-~0A|NjN|#1W?16!6PO?n(h!N{`$*@BKaH%58}axF#oEf4mF7)LEROt ziben;U~JSS1gaNi0!u@o?1n-A5JDWbP^e*B2K-@2csRIMgaD28ysbrrH{w^X2}v{$ zj(lb-y6ay>zmQA7K9?tkP@kL3e32)yJ8w(WM3I5ENqLx2zhO`&LZv!?)UQ=FY>Yy2 z%-6xF7~r!$InpHap1(n8FakTtlJWqUGhjX)|C45pNPsO|I?E%3TCWDDeGJ zM$n6LTaxFDG1_o?1vVLGN216OE|BL-ro9{au&{<{K`pozqpg5(1lRG<;*%$JNny%^ z;ZY+v{kXsb=iriV@kCbAy4{Rou;)>C$LGJ+U!_EJbj1(=7?C8LDan%4y}f(VG);b~39h`!XE|wSYcp3L@%7W%Gku1F zd@$>?50)h;&!9Vof){=XG4RMXXo^=utz876dbo`5p#5la^nRXRd(Vamb$D*;?w9x9)8xjjdv6{z@n$hC_(x zFc$)F_T=#F&jl!$KqPHx0KQFcp&iqAKqbDz(V#L}bK)rT+2bcEb% z;hG-{dK+^t_48wW`Az_HkN@A3(~pg%!GWl%Ur3d+B;rq^Twx|uAy+nD#TeT(a-_Y6 zx?$mp+pOOE5{!=Vv=;ftMHchU_EV)50vjsdn;rV{j5fC9M9Bvgb!OAVjSc>mRUGIN zbQBGx4REVRjV9F?5?6!FwJVieSF_z+FOHJHBJV8E>^jFlr%hfR&e@T_IlAXtd1b|>Fq9G2pf!py&jm`(NGBf@0wQ3SmZ%QOqwdrk%kp27Y@Kan;AY67e-2@PSi4SA> zE26T^BqfkyFvZQlHH_bA?z2l0N~__N8fxDVAk;^&3B%j$Po<7t_8lHR2FV2;n`l;$ zi5>k2rSn@XA&A^GRQr3+KO7)ylN$XzM*IEI2rfQkfCq2#7-kQ54Uu5j^-M~f?T*A& zaCxN6;&UP#WA9-qnpALMuFe2ugA0miMXY#y42FGTDr!hhn>GCm`Uf$Qj{T-STRp-xe~?X@jUo9*f47 z4V*LPr^LBD6>zXl8zg;ZExatLAYdtst+#IXCPV_=w*S8O+@x8-`x(#525 zPyo0il4JVCuWvUsf1cV~RfGUI2pE?8^C@*j0bh{|7Q{XOcnt86%t#>tZLfOa8V{Vy znm|yK5fX1Sx)5QvOG4259k*|GX_cZi;MF15>}ixPrR{6`VDFWf7>&-GtUq$CW>{3# z02a%Pn-Rn*PTjdllQ(|wU@Qr?{&@M4W!J3#~0M+OT~@8k;M%kz7iqwD|-EpphyNC>N{)MvyH)K>Gr#33nRoIQ;)*=IE5qa%?34X7#)?3Ce-S}NhMm<@|~g- zBqg0)g(bhfs&go9R*gcmtUS1K=kxB< zberbwjzJdDQ(|%%;(9n7vff^i({A>i`X7gzk0mirpV>*;o(n|toQuz$k|!ej{J)u1 zy2eW65Hzp5q*`RFtE(~?;s9_zdHCCxV-kwdHM?Mu(BHi59-Fk(R0_dV1zTbU?tj;N z6&j=yW~R4i7j3EE7V)XQoO8EhFrYXiW8x$47M6CMnzgs)VR2J?Bp7dz9Z2ZDoW*FN zG_KweD^0JLda#{9@HSo&I}sAHs0Zduv-5@KWUpWzeav{2?ub9C%=tWNHpRo;Tvk)Y zB>E2zZIe9i3R}3lB~9345Q~U&kO>G}ia=!f*FHSV@=G*TO0{hw;2;_5=fWn}haEde z4?nc?r_N<_ zcgsfV%7zxi4k(R_@{-z}$!W5o`770gkcA45^bXT}x4{1O7e3&N|M`;Hk;Yx#A{Q=@ zN~HF1T!_!8Y^_`yC%p7&FGhEmcW3NaxAGVuk9fyjSA(Tgeo(epZcEyBJv+2^P1~SK zD%O=y6gT%sP07yeC{%d6<~h5#cRfx-p5bx=jV`xx!#u?y%Sr^qh&9kmcl)W^o?sK1 zQkD>{w@N$^`I9JdYK)VGO^EPlC{=eyEe{;WH@mD~cbA1BY0mDzFk1YlIcvygRLIbI zq=GwFjgW|hQ>lIuGgZN>ZH1vB*6^~EK-g__2C5f(tz+@4f}4AnD~;LlM-aQgdZD2B zUDl2oji;o8a#1?-+=$9Pp-Gkx?VQ;A?1^UCtWOS);C#GE&cEoQZ{42i9GuYMMAaw1 zdB8%`HhVL^#91n^z|5#9__*eM{8WHE*KiITsw=48ZPM#zDzI2+Y(eXi#IDSHR@nwc zY+E1J6-|tl>yH0Et=;?F*iOarv5@o=WAe8|8~!<4No7^CeRA2g`}nU2A-j?tS!1W4 zHRhDShYvX7i~NQ~(zt=-^6dpE;yp)e_OE`be3E0FyQ(xkN&{$?HeDhi3Q&Yv)7~k2 zG&_*LK!E!7w zVOgcJ4WFWIdbf`g-eCOGR@kGRlt54Tv4HMS8CU6_SJ$jUlX1$_zABRaD=8S2Bc%4` zTMbI}@Mc=L-7Up}>#GRkTrts)PE#E95aQv&i0q{?W@Ckji24aHm2f`?Sne^SaDJjv zrHUN0@a+!$KPoW-ZW%f@bScn+j!d$UQEiJHH+JIn(!gk2Fb)$km2Y&g5V@XEvs;A_ zmu9CF=UmEzyC28+4K&NxOf?3NO-7TYhDtI`|ClTk@|GqAXO$DmBikYhu&Cwb#XZT` zsl*+k1@8I3!yqdbSM778@w8AAzaeDh!TWp26iMR_QcEl05s9y}T&nT1?0i!D=Po`E z6K;>N2G0*O>`pIfBw&*beX12+}GC)pXjI3rjaqCe;%$P@*R@?#U4L7!+M2Y)hkyr zqASrAyVm8n-kUkzC_{9F%p~2#GQ2b0$+6ZrJ6bL~T3~chyl?MpP>{#w2>azm%2nve zlC~n*nEkbkEU!%I`(KQkvp6!0Ft^kcQ7A88&Ox&@E*!gWiYlTHqM-;y6;#t6!Wc>h&pMya^LvO-E z49jb>{SkdjtT+GmMa|DgbgS`E`)WTnMZ3N&nE#_M{o?7C*CF%1xzqC;L4Vixhv&Ra z7pc7=nMp1^8lS|j^G23~e+?Cox#cjN<8=4GK+cYc=z-&@^1XB626Cu-{#5`L(oEOG z4ho6Ik-#760%$2Eluf$m0p0hI&q?k>hI*3R zUFHIC9(Q z2SF2u=+y~Ss{mg;r|}+p@S5A@hbF_ocHqnYy1urR(vrlZ`-_!sg8c>I3q(9sCN_+6AXlm+&`;}%^u6S%oa3qTE_u#^^4@$Mu zzQ|8|mb2$}&I~>348OL1V}&C{mP;yX7)0rSyr3^mjL^JqsGdwxcTiLBNj|mpk)o#8 zloQr`4NsLG4F`0MzNFdLwi}t@@m+WM>(}*+_{Q>#q*0Hi8LH|GRjy<;A4yT)ZQ5-9 zd;a{95LN;9QRBaQ}WSbIrAB(-w7j zjNz*FHESsNcI!@tzn&;C9RR>fkdOu!-H%vG_Rz%8@NtC!#fP93Kc?Rv+75RCRJ7F% zE4Ixq|JjuZ3bft7YFwTC(r|I~-i=&cpL`@qCTuJ@PAsMbUeR~RAWT+FDBu4hB4_KY zzxBis`BY)t{G-o|z!d=^);lIky8P>a(OT4R`&YwRLoJhWH6W~5c|cEYczgXT9!#`! zB*ii3L*CoYrMn5ayR(tmZG+v+oK@Fsn$roRyA0F}J6b$YRq97_7 z*PP%hGj>WGqMbSqeZVF5KtLT~5L$L5Rk-Kh}g(F@?sxpbP@DTK&5xzxq zD)9qu#g|D>DFX;d_En0QbULm3Kn2#Zr^#~)qD+zZ{CPt#f@M?}i0O47vSvNOWQ7pv zq8PEPN7W{BL4>mf^z3dzhXJ5`;#8s>UVII2nZi~N}=w`sc z4!Gjb{rHP1=~7HqYO3;s0Q*AtKhN;m*OfP#s>5SCTNO%Pvw2`7BS48PZ}_C&rWU&J z`bO9IvsnKV0}q+A;~it`&=vt#OPH<2%Y*gP6|;O=EE_P(Qf<5>nldOw!r!EE;~3B) z#8P!&0xOV{SS-x~R}Om8u>dJ%mfLjDe8_TZXE!qkDp+i;mwCr7Kxd2m_1kZ;;kllJ zIUUH+0As8qqsW(4j0u^7(3(0UYk0eA6=~#aS|>AyCog7;R|kw-8^8SwMxp+=nxpH- z3_7T!mb%lueU?JnLj^Hzu(c_PkF9Q1%!=@n5OQ3hdE9wb4>~^+mtBHlp=zt=_F2P1 zFy$wU=Qa`MX0L@|oCLY*dU$#*eC#dYh81Egs?B%`8J$)<-hKo?rM3yec(oY9)p*Ze z6a-$A8tLPq(Ut8J{|!-8z>B|ug=f0U^C!XUSPAMvJnNvMzmpmjh@i|}vKB{pZhBk=?nbT zRjgA`qf6I|P7zfQI8*)PN=y4Ge`g>o_?)2Xm7nZX3#*;p(tL3i7=njeDM{6!P7o_| zb(5L@ZEENEFc6a@m7&UTE&_~0Tcpqc4JU0JGNI>=ch@@d+&_u<_6;(=$CQ8M-#5#m z%9YL=Aa14AFx&BLJ<;^?-~3k@2~Oi1+m57%s&I_S(6Z{QXS#DlLR~{RC`+1>5?8;M zWW8_C*TyWxT}_mea@ja}=AxuJDw{FhFXZfq&7mRPiJd@!u`FX)l~r$`D*VKtjGL32 z&(~zYgwtMy&1fJhC*b_`3rK9~Q=B5fB4qc0P-|dtr=OqYCQM~BOFRSu@P83Nj z*G3m9E9_0hJX=a`UU9;eC1O=W?&T5gIyj2V**;g!OiIfgCt~4**(ndzdohId+C|Le z>}=hl7JgRpY1BjYy?=Qil|%7t-g3fUzJHPbdl220HHJ-y8|HqaCvzqwUpHq?I9L zN8qO|!P1hEKs8{y6AA^A$aC$d(-F zVM*(5hY3opM>Va($?cwsL`q~!*`%Io4w66tf`fF$5L0XC7iJ;VKTzZ0)6;is6XLBz zyRS~FYF_<3!a~=RZlokDqu;!V^w*CN2M&F?DB>fMJq9WpSm;CO_5{JFJ=T~E^J5#? zR66gqO%lwJpY}#^Ahg6I>jNP;E)56-TQb!foiA;8IF-ao9e_V4S5`T}ojmR$)v^mjZT*>j=4fYyo?ul6bqD?Ax%e8; zh05{|SVje3)5P>9Q5edS;9-Z&{`>KEaHa}@cp|zAzyB1TSZHxs8_F!yPMeKXWx417 z0*Pf|IvHe#e&<1P^LlXr+RaA}zq^u1e6T8|paOta6WmHyh|5>&bKP_e3znET6hZ}O z0tD02esF_If6dXrb(e@K3X?;$tg$n(2c~GeMD2|G*^)Od1U_K&HtL1;q911LwRhs z22~Ghn!NYjR!J>)(!i94N70~SMt17X;;o;I`!uc$ca$Q%{oJ&8lqB|)6joe zwz8h_puwD-qAmkJb)rxPZ!M9!*hmB>fS!J$WpHuGSfijzsJ9Jy)~juM42yc|%1mp+x4pgW zhBIv^C*B_uI>zhc%NpZl6HYm7tmDHPG3J0P+n|6$q*x#(A z`74>m$ozCeWZ4nO+QbKQxHF4&P!#|d+2k#^|P<2!bEW+Y95Y$m9L0`v>5 zhq%nhzxX(9?Nu+;DX9TA8)?IlJ9EY5F1$)hZgtbfJbLLMEiVLJSpw$)g`^!P?=78e zq9`P-4A&$^{P6EZbaLG<{+N5jcDh3mcYhEhLa*&RKj z@K~e`F{`>2Wo`qqx~x-R)9Uh4Piy0pLB{Op;fFT~Zf&jNT+v}?r_6VM_=+!%R7S+4 z92p*8-8GFxE0L)K08cXwb8HA@KeUQOX&+6;ns`Huc$k3 zA5|HxvZ;`|vwj*~7KJ9MEhlA(g>;x%3;-ze-+hl+n>QajPqCVa8DbS<`Z>|;N>0># z;aDdV_*)bMLb=p4>;1BYK-X_Lh3g0GV-#Nby*}LWZp|7uslMJfNpPq?Xz&&yQL?_# zj--#M^x&x?3?#Exu@dtXSNX$f7+IS`pr-A^;t9?)t5okb--!s-dcj|Q&)*vfzLOm@ z-4lZfeZhSS?MLGKUb14>xg)+ZmtI-p^@eR09p9y=bb3?x?R{%vSGRuYqIg6$Qt}%| z;d^$n+UleT!Ux57RAPYF@ZMagF}PdOf&FJY-Nn~WZ=Y?1YYTWTIAcC$P#aY%;~EL6 zh-1q9#r<XV~gOe#Q zpsu#ql^ar2U)NZTHa}vhsHm!4Zt}|G z??~nWX6u7eeQ1fHowgocBA62llMgO%^=xk$;B`6wp23`B^}ck|pl+|#uUk;o!5%|! zT=qGlFf~^R$^pCQACCk_N~xQMP$3jYOtoojGjRnP+3XaSWDkRi?=g348IVQt#&Ac**aP-;|eU zXnY$NEU8}JxxlA+L;mD%>jwK_?X9UYnJs(KgYpzxA>{`v62UJ}{DUq>23lJMPHy#S zIvSrk&;QD`-h2xFR=TEL6V!O8C6{<}_aaXH-~6i-2{xi1bAa~(I@rH?iU|OkAsDQ^ z8vqOBm$ah)zsD+Tm_1qDsY;dI;e(<67H*+t)yYhJm3YbV^W5(}S8lqp4O6#WF3nVyJF_ zEopPdM$HiG)z27b2v(VynOsD5jkFT!lyDwhLyXU_W($h5w zxYfGOyGyA+0FZYKY{HjZ#u2B`IFr z zbSuJuHGls_bviT#7icZsZeT_wMZGv z?<4%-70MJDA7Dd==hIOFjGpQBVNy(BhJ`ULYr~{*V&*8KfS!{L=*_l*+`jm=i49VcP)(_ZUlYPm>-mELmhCd zOB}j>yuI5y={|hq!*M=$o6U#1zBXcjfRP|<`^Hi2NhydgapZboA+ZP%$855wewKRk zAWYXlaOanjv<~Mc?>n{RO*1b02H(!}_|Po5r6hXx7`Lnp{DzNfD#GrvxFJ}Cm8foy zA!+p=_8-bV)0{W}3NO?j1-d+rY)peyIIW7q2~^tVAM#=v`jbrG${pwaK*xG$>Ryk@ zD2^5YlBme>6H4PGT_TFjl{>sPr*psOuY&{+%BrjL(^~VgRJcMElkk6CvhvV)BgYw8 zcS*5F$6wxPkYZMei*xG17t=`1-(A*;C0VjPmpPOk4dHuLqGjyufoe|p{(>%@I8q83I{Q!23Mw@1QDy>n zTy(!_gKr6^x|0dlojB8XJ9>^DbY3mu7El6lru1ùZncwt>y>HnmvJ~4G2^m$NT zWguod1WSFFhF8Oy_L`8tT5@o*gK11cQ!t-anIUP2y*7a%zFMQwRfEI}W?21=H;?YC z&bAY{p5G^!ZB|AzzTg1CMS+#E!`q?Ua?y3vtsE@X;(z;g%zN~fDWdb{xI@GL7*I@N zG8`L=3rn`eE3%X`sbxLxb}h0dH*&4Q35& z4Z)8D govi>z9byOI=gtw8Njvubcm3i2&BcfRHx^3%KTM&%r2qf` literal 0 HcmV?d00001 diff --git a/public/sounds/typing.mp3 b/public/sounds/typing.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bfe3833a4ff06f122e27d1402021bb45c79611f0 GIT binary patch literal 4461 zcmd^?c{J2-`^P_K7&BvrA%w!rAcIUpsR?BXiO5bUCQG(#h0-!(-`6Nx*(2H0GAV>G zBD*${EnC_5@cE7J^El`C$Mg5|&-41@zOU=t=iHz7{XU=jzRq<{<0vbzi>IVRA?0VftY`_Upr;{BV1ji;- z-9r(g;7)VJEa#77JSrf#gyv>+);rmD_Ux%zWG@2qqM=CC*`3C>eW;oJ;OP`?cDdC1 zl-mF+RgzKv6DbP$Tt}Wz!~bOC60S?hOyl<6s0MgE6)0*o90-MtR5~v{G{ieW)fGk% zq^(n*MvYmQ>SGVyv{6SvX+WnclFSc+q(g}MJA?g)vaYBH8PHHP7(eV8#x_o6af8lF z6gnA@y1UvtlvXaRh@R$j7MXC%iXLG;{QmoS$A{BnVYs zHxcA0R_PIIju%i!F;TEVxs@^PzcW4CQzpzIgt~c}r=kR5qu3enIcq zu(Sx-m1}fxOy}DW^H~4p?-#Gyy%+@l>NKl;xK`;U>ln+IlC3I@chcp17FiG-3iBI@ z854I9B_>i>qK}8bQEY5l`_#47LRT+IU~uO`H5c@J_DxyL9vPj9zDI1m_Hn?*8rL5)#^?1%<5VBx`L7t*?1QiM_=ru{IOUI6rhB{0$7QX_WX8J(j5X<-v$iV+%;I_ z5+X|55)NqaTKrZa<;xyzw6@8!dCro8AN;N$*sC2-6i<01Nm`|7=ou zkM((3noaN+>JF{C->G!*NSzTeSR2eMpE8&y-q&2uxL0&3r)8MwzdZ*H>U8NsC(7w>bJN`3F&Q%%IF5!uNXJCVy;U-JNcnWGy`h30qg{A`oOJ>5`eMY* zwmZqVET=Rv7oCfDAqGi`#>$MMR6;_xwJoYW#-!EyeUiV;K?eod-&53Ww%Ve;yQRy7 z^v62-3Lc<3sGfkoOa)E0l6@V<>+DeimLc@$ItUi9Wlxu2d2F#EI>bS0oL|OkLk^P# zvs2-|#^3*Pzwl-LJD)StP<|l6Z|ai1bO$)C2jdHKFwsy@!;6bm=wyT+A5S{Nc)Bi$ zIg8u%9|b_4C{uP$VOdX&UK4Fj#>BXF_|U^>z;c$?2@wN`wk@t$hc;pK^4a7UPnI{B zAUlY9>xsk^H@JG3t4+X5tBZ!`*CGTXuR~cM>Ft<8Zk9nyRU3(Zm5XJ`@pN}lTf>;r zzZ+ZmOku;6x0oNh9#vJ){;tlxf#NBdy<+x zU$IW=ka#0PUEv{A^Xd0iHn70+*E zbt0)y@vDtT|1}>$@f+){z<|&GVY}!7z_5)%q`bPYp>8jVNv@z+`t0MJ-qd{`RC2lI z2N7S{-(M=nd|Ar9-b{3V0l_kGy>#wCeesM{Yxo)Cq0PmoYgRrbRp~6Gcw3Sz0126a z^~_a%xZ~I?lOaJYAXk|Rcz(Wk35iji8mVrB&h2TEvOu8y0!`+;u zchIe!qJ|RR)Z;j*cZ1yZZ~ucVKYu<2z|_O6j{JhNpnsR?olWu8o`KHXm`^bvA{B`Z zoy#1?y!_C>?l%WGE5!&O-JRR*FY=9eZFe~yJTICKLN(etG}e(JkQ%_3b+c%<7YocajYERBl&X@IQ$mH{RJO_2Sm8=u}|NIuPK<&J(G$VD=fF$bz zjHMYIH~kRKClHZvD~Ks<|mVeNpet^0>y8U`KM5EWE;-UXKscbXLw5c&G4UWlr*vQVmWOR-rUUN0c? zn^#-!whXY!ET|HyV+gSxbaYG*Uwl6yX4wt;&-)Vy6*KNO9Omy<$wj)58g6jH|ERF% z-H1R(J-xYvAjL2isK(=p=_j1S&LsI;#f=}m(Oxd9)N9t>Av0iE6~ocg1b5`uVuiUc zmx^f`$j*B8KiJ>-Df5W%qDr6aul_6U4h221xwPlz4ere)3EF_V#yLSUM!90{-aD0# zoWTwozRN8k`(@Ho?rU9U=+|TT95wU}F=K8s`T8sx9d9(xY1c}~h=SnSiqagQ4Pb>E zP-fa$!yHDz^uv;fEBv~shNswT$+C^f;_cldtd z5gduhKCi6`@F(?zVbJ(s_^&ig6;p9~xj#GKrODjpu7U?sj^Ft$lV|7EHhd1AiOtJX zCun|}&OLPr;Q*P|eFTErLwbh+fYmAldzK5bX|2}nl>K34>a!`wu@uFFLc9K>gP}Gi z26ChP>L&xERFoTp$ENo>997kc7ReY>_>p=vY4`tF)DE(e$TctdtXR=3?%f5uGV;i^_{);PpD z;23rMW&D%Yq9@L;JF&;{vGaDE{gd3Rk(F%3*q2wZl2B$f_SL_t`x}j>J`bImJjVwKb8?faR;|d$_tzWB^;#_OQF(7fL z=ErY2L_BLJI)l*~OG(t$@rCu}O7mjRFDkjR;e=22+n&#p?>q)!#XRwYHy1p1s$V(z z$6Xj)crI%jgj7rVa&O{KTU>SV?33bc{g<^r{+j zyLjb$#a|U&M&))LJ>s`NPv_vk#o*KZK85;3!%y^DaQ)(^E2h=WQf$YDv4o`$%Z>Mg z>ssUkTXWR1A{hdn2;W`*IRRES#m}x6_W6?_mTlrN8ppP)c|o3@_ILF3ynPJslki{C zXx|;pHpdU&l9U#BgK(*j zn^L|WXS-&eN4QeA&D%XpwiBC{Uluo_OQY-BR$hE*H0=aTpDA`g6Q34Vn!NCANr3Jg zo`v{A5uSO+zk&?2V$#jR#u6JJm7Xn~`(=k8zw`SFs`J^}0)h{E@>fHRer2^FvIgY4JbKFZ+!9v5%Oj6;_Rr;x0a7uK@ShyxhA&jzW@N06z+-jGR7NxFej=DW~Ns%5NsU@z*ZpCLSZ#^n|RSzZ~O=q>s zBuTMT>)Ee^_7?SYayibRHJC!T#NN!VS?Ry0r;eE~mW)M~L4TjJBO<>o*+336ED$+W zPgg1Xwfn%o_(5rO4%+3-T_yLwHn;W(+-mCsuV?5ch7Es={PqAI7=hyElM16rU@xLh z%HtLL;$+(vQ<3xyRI#y4WNU+!RY_UDYt4+$CNaq|w`<45EOpx@zrd!%*sLN{?yG*! zrSV4qEoXto{uM zUI4cMIr{ zDq?wDs01nWDs%f}NUU-nJ81}9YWK?J%uIWbVF!*5pRKgM>!PLK6b*t+le_*C0cf?U zK9OPzXox8ZH|7CRWYvD_1^`|haNs~F3DBSBGM}>~Y=dA&)ne7%qBYox&%y-*6AkD# z4?-iS>&5F!B|H%tG@ykyp1O376JF~g^LcEcdK;j_<>4hZ74dAw9a|>`r6Lp9WRa|a zAC+-aR}wKH_evFh(Ro>zehlo`=k^})(-ur3MbEz5zhPc{> zY_{VZoyIr)LWJYSPCOYwIvHUL0@K4~vXz+kVPwm|I?x+Q<|{X+GCM)O@Ukdyeckv< z9S;k-9D~WpD(p02Mf3SL=qTfF$x?EfgU{v_P@gW - - - {renderAllPages()} - {/* ProxyCast Connect 确认弹窗 */} - {/* _Requirements: 5.2_ */} - - {/* 组件视图调试覆盖层 */} - - - + + + + + {renderAllPages()} + {/* ProxyCast Connect 确认弹窗 */} + {/* _Requirements: 5.2_ */} + + {/* 组件视图调试覆盖层 */} + + + + ); } diff --git a/src/components/agent/chat/hooks/useAgentChat.ts b/src/components/agent/chat/hooks/useAgentChat.ts index a7e28281c..9a51ef681 100644 --- a/src/components/agent/chat/hooks/useAgentChat.ts +++ b/src/components/agent/chat/hooks/useAgentChat.ts @@ -32,6 +32,50 @@ export interface Topic { messagesCount: number; } +// 音效播放器(模块级别单例) +let toolcallAudio: HTMLAudioElement | null = null; +let typewriterAudio: HTMLAudioElement | null = null; +let lastTypewriterTime = 0; +const TYPEWRITER_INTERVAL = 120; + +const initAudio = () => { + if (!toolcallAudio) { + toolcallAudio = new Audio("/sounds/tool-call.mp3"); + toolcallAudio.volume = 1; + toolcallAudio.load(); + } + if (!typewriterAudio) { + typewriterAudio = new Audio("/sounds/typing.mp3"); + typewriterAudio.volume = 0.6; + typewriterAudio.load(); + } +}; + +const getSoundEnabled = (): boolean => { + return localStorage.getItem("proxycast_sound_enabled") === "true"; +}; + +const playToolcallSound = () => { + if (!getSoundEnabled()) return; + initAudio(); + if (toolcallAudio) { + toolcallAudio.currentTime = 0; + toolcallAudio.play().catch(console.error); + } +}; + +const playTypewriterSound = () => { + if (!getSoundEnabled()) return; + const now = Date.now(); + if (now - lastTypewriterTime < TYPEWRITER_INTERVAL) return; + initAudio(); + if (typewriterAudio) { + typewriterAudio.currentTime = 0; + typewriterAudio.play().catch(console.error); + lastTypewriterTime = now; + } +}; + // Helper for localStorage (Persistent across reloads) const loadPersisted = (key: string, defaultValue: T): T => { try { @@ -416,6 +460,10 @@ export function useAgentChat(options: UseAgentChatOptions = {}) { case "text_delta": // 累积文本并实时更新 UI(同时更新 content 和 contentParts) accumulatedContent += data.text; + + // 播放打字机音效 + playTypewriterSound(); + setMessages((prev) => prev.map((msg) => msg.id === assistantMsgId @@ -508,6 +556,10 @@ export function useAgentChat(options: UseAgentChatOptions = {}) { case "tool_start": { // 工具开始执行 - 添加到工具调用列表和 contentParts console.log(`[Tool Start] ${data.tool_name} (${data.tool_id})`); + + // 播放工具调用音效 + playToolcallSound(); + const newToolCall = { id: data.tool_id, name: data.tool_name, diff --git a/src/components/settings/GeneralSettings.tsx b/src/components/settings/GeneralSettings.tsx index fcb988402..d084d5e8d 100644 --- a/src/components/settings/GeneralSettings.tsx +++ b/src/components/settings/GeneralSettings.tsx @@ -3,12 +3,21 @@ * @description 通用设置页面 - 主题、代理、启动行为配置 */ import { useState, useEffect, useCallback } from "react"; -import { Moon, Sun, Monitor, RefreshCw, Info, RotateCcw } from "lucide-react"; +import { + Moon, + Sun, + Monitor, + RefreshCw, + Info, + RotateCcw, + Volume2, +} from "lucide-react"; import { cn, validateProxyUrl } from "@/lib/utils"; import { getConfig, saveConfig, Config } from "@/hooks/useTauri"; import { useOnboardingState } from "@/components/onboarding"; import { LanguageSelector, Language } from "./LanguageSelector"; import { useI18nPatch } from "@/i18n/I18nPatchProvider"; +import { useSoundContext } from "@/contexts/useSoundContext"; type Theme = "light" | "dark" | "system"; @@ -19,6 +28,8 @@ export function GeneralSettings() { const [language, setLanguageState] = useState("zh"); const { resetOnboarding } = useOnboardingState(); const { setLanguage: setI18nLanguage } = useI18nPatch(); + const { soundEnabled, setSoundEnabled, playToolcallSound } = + useSoundContext(); // 重新运行引导 const handleResetOnboarding = useCallback(() => { @@ -217,6 +228,32 @@ export function GeneralSettings() { + {/* 音效 */} +
+
+
+ +
+

音效

+

+ 工具调用和打字时播放提示音 +

+
+
+ { + setSoundEnabled(e.target.checked); + if (e.target.checked) { + playToolcallSound(); + } + }} + className="w-4 h-4 rounded border-gray-300" + /> +
+
+ {/* 启动行为 */}

启动行为

diff --git a/src/contexts/SoundProvider.tsx b/src/contexts/SoundProvider.tsx new file mode 100644 index 000000000..11349f75f --- /dev/null +++ b/src/contexts/SoundProvider.tsx @@ -0,0 +1,16 @@ +/** + * @file SoundProvider.tsx + * @description 音效 Provider 组件 + * @module contexts/SoundProvider + */ + +import { ReactNode } from "react"; +import { useSound } from "../hooks/useSound"; +import { SoundContext } from "./soundContext"; + +export function SoundProvider({ children }: { children: ReactNode }) { + const sound = useSound(); + return ( + {children} + ); +} diff --git a/src/contexts/soundContext.ts b/src/contexts/soundContext.ts new file mode 100644 index 000000000..7aa74b814 --- /dev/null +++ b/src/contexts/soundContext.ts @@ -0,0 +1,10 @@ +/** + * @file soundContext.ts + * @description 音效上下文定义 + * @module contexts/soundContext + */ + +import { createContext } from "react"; +import type { UseSoundReturn } from "../hooks/useSound"; + +export const SoundContext = createContext(null); diff --git a/src/contexts/useSoundContext.ts b/src/contexts/useSoundContext.ts new file mode 100644 index 000000000..4a9b062e8 --- /dev/null +++ b/src/contexts/useSoundContext.ts @@ -0,0 +1,17 @@ +/** + * @file useSoundContext.ts + * @description 音效上下文 Hook + * @module contexts/useSoundContext + */ + +import { useContext } from "react"; +import { SoundContext } from "./soundContext"; +import type { UseSoundReturn } from "../hooks/useSound"; + +export function useSoundContext(): UseSoundReturn { + const context = useContext(SoundContext); + if (!context) { + throw new Error("useSoundContext must be used within a SoundProvider"); + } + return context; +} diff --git a/src/hooks/README.md b/src/hooks/README.md index 0fe14ef73..ca1ef6a1c 100644 --- a/src/hooks/README.md +++ b/src/hooks/README.md @@ -25,6 +25,7 @@ React 自定义 Hooks,封装业务逻辑和状态管理。 - `useProviderState.ts` - Provider 状态 Hook - `useRelayRegistry.ts` - Relay Registry 管理 Hook(Requirements 2.1, 7.2, 7.3) - `useSkills.ts` - 技能管理 Hook +- `useSound.ts` - 音效管理 Hook(工具调用和打字机音效) - `useSwitch.ts` - 开关状态 Hook - `useTauri.ts` - Tauri 通用 Hook - `useWindowResize.ts` - 窗口大小 Hook diff --git a/src/hooks/index.ts b/src/hooks/index.ts index cc6289054..db8444814 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -3,6 +3,8 @@ export { useConfigEvents } from "./useConfigEvents"; export { useOAuthPlugins, useSingleOAuthPlugin } from "./useOAuthPlugins"; export { useDeepLink } from "./useDeepLink"; export { useModelRegistry } from "./useModelRegistry"; +export { useSound } from "./useSound"; +export type { UseSoundReturn } from "./useSound"; export type { ConnectPayload, RelayInfo, diff --git a/src/hooks/useSound.ts b/src/hooks/useSound.ts new file mode 100644 index 000000000..a29d7dd47 --- /dev/null +++ b/src/hooks/useSound.ts @@ -0,0 +1,71 @@ +/** + * @file useSound.ts + * @description 音效管理 Hook,提供工具调用和打字机音效播放功能 + * @module hooks/useSound + * @requires react + */ + +import { useState, useEffect, useCallback, useRef } from "react"; + +const STORAGE_KEY = "proxycast_sound_enabled"; +const SOUND_INTERVAL = 120; // 打字音效间隔 120ms + +export interface UseSoundReturn { + soundEnabled: boolean; + setSoundEnabled: (enabled: boolean) => void; + playToolcallSound: () => void; + playTypewriterSound: () => void; +} + +export function useSound(): UseSoundReturn { + const [soundEnabled, setSoundEnabledState] = useState(() => { + const stored = localStorage.getItem(STORAGE_KEY); + return stored === "true"; + }); + + const toolcallAudioRef = useRef(null); + const typewriterAudioRef = useRef(null); + const lastSoundTimeRef = useRef(0); + + // 初始化音频 + useEffect(() => { + if (!toolcallAudioRef.current) { + toolcallAudioRef.current = new Audio("/sounds/tool-call.mp3"); + toolcallAudioRef.current.volume = 1; + toolcallAudioRef.current.load(); + } + if (!typewriterAudioRef.current) { + typewriterAudioRef.current = new Audio("/sounds/typing.mp3"); + typewriterAudioRef.current.volume = 0.6; + typewriterAudioRef.current.load(); + } + }, []); + + const setSoundEnabled = useCallback((enabled: boolean) => { + setSoundEnabledState(enabled); + localStorage.setItem(STORAGE_KEY, String(enabled)); + }, []); + + const playToolcallSound = useCallback(() => { + if (!soundEnabled || !toolcallAudioRef.current) return; + toolcallAudioRef.current.currentTime = 0; + toolcallAudioRef.current.play().catch(console.error); + }, [soundEnabled]); + + const playTypewriterSound = useCallback(() => { + const now = Date.now(); + if (!soundEnabled || !typewriterAudioRef.current) return; + if (now - lastSoundTimeRef.current > SOUND_INTERVAL) { + typewriterAudioRef.current.currentTime = 0; + typewriterAudioRef.current.play().catch(console.error); + lastSoundTimeRef.current = now; + } + }, [soundEnabled]); + + return { + soundEnabled, + setSoundEnabled, + playToolcallSound, + playTypewriterSound, + }; +}