From f070299c26581b62d69c82f45fea88fca29cdecc Mon Sep 17 00:00:00 2001 From: techartdev Date: Sat, 21 Feb 2026 00:12:15 +0200 Subject: [PATCH] Update version to 0.1.42 and add browser voice language integration with fixes for voice replies and configuration options --- CHANGELOG.md | 9 ++ custom_components/openclaw/__init__.py | 8 +- .../__pycache__/__init__.cpython-312.pyc | Bin 33604 -> 33783 bytes .../__pycache__/config_flow.cpython-312.pyc | Bin 17818 -> 18228 bytes .../__pycache__/const.cpython-312.pyc | Bin 3312 -> 3773 bytes custom_components/openclaw/config_flow.py | 136 ++++++++++-------- custom_components/openclaw/const.py | 39 +++++ custom_components/openclaw/manifest.json | 2 +- custom_components/openclaw/strings.json | 3 +- .../openclaw/translations/en.json | 3 +- .../openclaw/www/openclaw-chat-card.js | 23 ++- www/openclaw-chat-card.js | 2 +- 12 files changed, 155 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4256fca..5658a6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to the OpenClaw Home Assistant Integration will be documented in this file. +## [0.1.42] - 2026-02-21 + +### Added +- Added integration option `browser_voice_language` (shown when `voice_provider` is `browser`) to explicitly control browser STT/TTS language. + +### Fixed +- Browser voice provider now applies integration-configured browser voice language for both listening and spoken replies. +- Voice replies are no longer spoken for one-shot/manual voice sends unless continuous voice mode is actively running. + ## [0.1.41] - 2026-02-20 ### Fixed diff --git a/custom_components/openclaw/__init__.py b/custom_components/openclaw/__init__.py index bddc0d9..b207b15 100644 --- a/custom_components/openclaw/__init__.py +++ b/custom_components/openclaw/__init__.py @@ -58,6 +58,7 @@ from .const import ( CONF_WAKE_WORD, CONF_WAKE_WORD_ENABLED, CONF_ALLOW_BRAVE_WEBSPEECH, + CONF_BROWSER_VOICE_LANGUAGE, CONF_VOICE_PROVIDER, CONTEXT_STRATEGY_TRUNCATE, DEFAULT_CONTEXT_MAX_CHARS, @@ -67,6 +68,7 @@ from .const import ( DEFAULT_WAKE_WORD, DEFAULT_WAKE_WORD_ENABLED, DEFAULT_ALLOW_BRAVE_WEBSPEECH, + DEFAULT_BROWSER_VOICE_LANGUAGE, DEFAULT_VOICE_PROVIDER, DOMAIN, EVENT_MESSAGE_RECEIVED, @@ -90,7 +92,7 @@ _CARD_PATH = Path(__file__).parent / "www" / _CARD_FILENAME # URL at which the card JS is served (registered via register_static_path) _CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}" # Versioned URL used for Lovelace resource registration to avoid stale browser cache -_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.41" +_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.42" OpenClawConfigEntry = ConfigEntry @@ -808,6 +810,10 @@ def _async_register_websocket_api(hass: HomeAssistant) -> None: CONF_VOICE_PROVIDER, DEFAULT_VOICE_PROVIDER, ), + CONF_BROWSER_VOICE_LANGUAGE: options.get( + CONF_BROWSER_VOICE_LANGUAGE, + DEFAULT_BROWSER_VOICE_LANGUAGE, + ), "language": hass.config.language, }, ) diff --git a/custom_components/openclaw/__pycache__/__init__.cpython-312.pyc b/custom_components/openclaw/__pycache__/__init__.cpython-312.pyc index 4a5a231434f381c3edcb1259652583bb1f829121..a531c0850a42c8125541c4e6b7d2d30cd0710f00 100644 GIT binary patch delta 4803 zcmbVPdvH|M8Q;6l>~69lCL0nGLJ~sALKFyZ1(J}2fW`y@31JhsY;tds4f_b^-arzA ziHg{Vqqaxui&h;w3UwTyYiAs-b)0JZ2c@DS+6#`_@qx9DL146ucKZF!4Q%4r{?QBh z-S3?5JKyV^?>l#2Jz)L%F01c&TAIf~&lhJN3TJCS^W{jsV{@O7I-{+@*67yYR>`7d zC|QxV=(gZCLHi@YXnU|d+7axKELMx^>U#m}r#|YAXC!kbC@a~)oyzoJh-BSr=au=s zKk=fX%=tOPUW-Ky0fUN?tA-x6{L&g!RrOJeGJ~jYH7D3ZD>M2|@+Zo&_y_js{HNAb z=@dqGnm)~sT4zljp0gH7@A5*MUor%q;>&DvU9qH&$*T_9X1d}@mQeq>ZHoG7_9^Pu z+lyTNV#R6RKb3XJK3n=b{3?N8Qxk%by7Wr$YEj>$0P-EMu*xwrgDKZ4Gxytr8odkX zt@A!d-spg`Kq;ir9%Z3YMDM-IBBhw#gK7bBX79H@Ghr(@q*N#+#2Ho=D@&9)`yIhs zl%>itrIe@9zUN*TRxbz74El$A<==0=t4m3j0YQ*Kb^)B849C{fBMnYew( zHrt|RmspsSj)OPo-)6bbdiP6-GUriATA28o>ynh8SJTwEPF~yGv~_b`v)tOWp{7o5 zsBWxpsjjc%C){7oDyXenSKZRE<-f=zUi7S#q>98P?|IwwdJ2xsB``disw--DXdt4q zO8#AXhqDILIXt5_hwq)X+usV&X252^7C;OCZd!vwC2}m$nDJ-pXa|u-c1RnFb;$#< zNIay-{gIH~9cNLEK_S};sDzlUKih&nj>fZ=_McVK~*nu%>H<18!)Bo2lfDt`6#~ji1V`kQDBk zu}ONJZ=3O&w3W}wo4sl+Exh7=NKs^UQ0*Gf<4o?0cbbm3kp!CuF_+xXR9|1$Y}msw z3a^9Tnpajo2#!0Z?v?fUE;VMPfK)o-ONpMD z7wy^D5W^+18;%1yje2`e|7l5QCGXZQ)o#f-s ?3 z6VMossR28Cgk*<(6TV)zZa^PkDuV=joFL#92PMiTpZtzC{KL^X+tLy#7OkT7v*9npx=Dzv*wZb)zrib)!m%K|IVuwk1%JFCSKnEZ@ zab*5S4(S1Y|Dsz7Gb{dR?nN6vTd{qKFtrb|&k-0IX3=!f(brWutgBI`g9&E>KJ(x_ zz@v+Qla3l=_g;ZTiP9yNUa5h1R|dSva&P62R34|g>8@&%4)d3*KB2qdsp=}PI1D*h z+{VvW7uLWF!=W-3XND`@uZQC?jV%CwB{;S)?R`0h%K+B`mIIaoHUQT1Eo&D`EBKGr zRu{ur_6mVvr`%}lNst~hvhFY)fGYm^+Kr_K#Lftw=niJxO>!RmPkE?jxpbC4Tyul- zO-yugqb8q1`nsk`8cNjHK5gG2q7j8&0Ew$9HKxlwsxEi+hIF}?I!l}l$v~C$LhdvH z9np)Rg&Pi&nLOH5MO51j8#YOAC4RMGxij@`l5L!5r~JdFV9qBr5k6-lMGnKQQ_;(@ zP*i29eDlT|R|~%ipq(&gvx*Z3V~zOtHcmflNrHviVTSmbsZmL+T6W29&*+Y|0m zHN)E*(&UMe*$US2`j&uH%XhcTkm~r|Ei=lYpZyzPgVE^(8d@Cc+-#6Lzyu8NH(O3g z2NF-Wx}+V5r!nB1RP3mI!-1mG*wxI^34jH|tHh(BaLhb}xa34uge5c-(~{U9z;d42 zwwWyLY+GxU-s4XOi=E;)U*^YyrIkWCc8t2S&cgj>F-)iHNoS!hUEFChqpON(;XNvQ z71sU<5L?~G7q;i8&7n9AM?Y#&D*kl)x8%XEJMNICT{Xz&gMTMq zz9TU2I%sf3LxXZejWOt(+MzNb3Wz6uvZG8|0j&W^?6O9rWg_M;;27X`D``k0V$AZ9 zxA^fDZ+Xj4+RNF2#6RTUOWrbQBMA#L__0vt&Em)lzh1$lhzxR+BZq4Ul0}dumglY=27RTCP0Q#7fN8%HXe+CZV zW6tCI$ZAIaA**zTzrfyIEmk!^;6(!ZED)VCgJ*w77VTqlF(}q`5V+tK?_X`c}#MaQR3h~f}{!43U7m>T{fD#@}PZWS}f9q zZxbi0(ApmA>evI1z;6m31AIiF<>=;;p~dXy2CxJo0cO5 zV01!N7XXbmgF*k4WUSvCbf#0x+H3sK-Un+hrdTq{4{bftez^T?)`GKH6&D<~>#iB6 z*}R8pAG_({o8HM^_(uN1@j01kzDtg5w}&BzffUnPx-iM3gC*s#dD*9gyYzPIL*<3l zv7zmgI&tdcuP^@6;Lb(D-No=!0rV1luLH`^4!DSJu#z_peYL&7;d$4WH*TZ%2U!*4 z4tkF}Ex!D57pN4A$M=<+s0%iW(|5sZcRI&YX?T$e*Ky4|_OdtZWrwm(E~tCkzRnC} z;saR((#j7G=Z=coV?ToQObSwLBpQ-=clklJ07T^{ZydA3GokN)%nn2UWZMs1(UoH+ zO*k+`8E?3yWE6dt;meaP0Cf(OFe4O7pP?>EZ&K`r9}u&fT`>$+a`E?PhqEH|9n(H# zNBNhxILwU`0bYjrt7?YbVgc>`_KAybA`N`b$lS$ZyTvL|q#|viLkLQ=iSMQI4n8`P z=e!R#?&Ob;bV$4ScO#pmW4vkKm=sH#+&5pUsRMT%U<&}hSdA2Qus^Qh<9|Ko@X2Vn zspK2tJv}NjQe^SnB+KT} f$Lcw674$dbqdfPHnZA{l<11YacH3WU65)RUsE*E# delta 4560 zcmbVPYj9N69lvLv+1+GAAb~tKB!n1P5(p3i;bD@^0|H{mgM_dIZZ>;vV8gz8?goi)aCyskEFH!ZSt}(>d@Qpi5{GJohNG;biWZ>C|L+ynE?VS_~(Hw1O_9 z_l%jnL$s1ErzNl&rYpR39vtqYRdhZ)_a`f)E2$gy4$xI}0Xz@V)wC3zBSJ-4l8VtG z<6KFZSu8ONl$OJrwN*N5IPyw-vE>Cq7RCQ&y+!y<+qF1xR1vZyzAE*)aaJSP$L0ZO zc2$W{rO!7IjIn!p-L!7v;ZIF_IBPRfv>>!1v>~+fx{QrxKP(T%dB$4?;^o^iT`N6! z)`4&@fM$$FqO1#6ZX@&IE{f7@1L0uAN9D+F)d=r}g@vq_zmS=2-UV;Z$IoOgFq39} zJExFz@|@hIq?d2ZZ6#;;levE&9h~OPCAIv=d9DJXrxV34K(SVNTmnSxj2&*gt%0)I{vWs+h3a#3Zv zFvbJ~g@wY7uZlXzzvC@)bBRT`+Q*~C1x`UF;wFfmD6Sz^{$X(e8ROp+Ke+4>;CCBz zGch(`xJ6d>DB&3U8S4HiLPC@7@vrY`B&qzHd+Hl>kDDW5#cg88fp*yGQ-{L-KxEO{ zf!F|37*en&0o*o`qswn_H5cI;@+R4bs zNQH1EZku<@Sc(Odkj;+4t7ez$J#7tgi)V8ai{SY%!cp!nb(534y)?EEJ%d7y`C%2 z6gd!6LX6?zQ2@77uL90rs`zEP$n!&ZhEN{gy{yJTHtH zO`a>{J^rbu)*%v|jm({Vd0k;WTA`U0#v)9!MxwDmB&@P>Jg>qNV*v76hOdwLnWsb4`f{!#sE%UNXb@qz{yxU{BW6WJR-(s0t0CA<+GIvXkUkn*PW`DK0h z{w@HN*$rF^b!Igd0V@*a37r59d2D?vITydQeuX9VJkT~zn!-z3yyP-J)H0LU`SF$w z`amc|X+q~5TooY{AUwcH>kP*wTna-j^MY1a)@%6w8-!mW%tW}tTUy&Hui?^+@HxT+ z0tSrz3!w(Uou%8JN?^l;VCH99v+ui!1b1?+S)m+ROrgw35rrkV zMF7`;9L)it2vD6{RIRw0Fb$K5&}PWWV2t_vFWpWLOt=*f$3d!D8&N#R28bFsS4W@@GGhu3_yX5qXlb`cO60# z0=Al(4j`OPfL`=hkptnecus$Z@m|qVcLvzx2aIMD6H44H63Dze{okSpgp2%CXe|Wn zMyP}I7XzJUiv>ao%z;p}*=-jzYztD0m|^#^?;AH&$eJPF@9C23n>?GR`WaH}Ll&Hw ztP^M$>R=LoF_Nv<^xyFW;S#?PIRKV)MxQm1i~L)5VW;5QfCR4qz_)@Jy(wUx>f5QP z&ETpSpXi9@P?a#1^<+TNp<2!EkA%aDNQGt$sXKK$wt@*Nj!O|(nA+8`F7g$BB-RQ8 zwJRIAL-X&;LFG3%{cP8d` zq(Ha4X`c6Bfzony}(3MY20@*kEt^6J`lRh_h;!%S6u3bL7-=!x=SSRM}9r|G#P9Dti_Q?xy(qI18{&^KX zv`KJbyXo-}tt%oW`u96AJH+Q4$g@0*)Q5T9fo>AuPaS9_r}^~*qa+w-2TMtA8=f`- zXer8IG@{}M`dZvu$A34HV-epuUHs#bl96BF?(+!85F+5EW@-2DQbIn~k1mH0cH_3u z6AWq&C5+PaX_|{?c?dY@H7ou_r1-+jgKobRlZ#w)EGCF6Jv0sIfcTzfD8 diff --git a/custom_components/openclaw/__pycache__/config_flow.cpython-312.pyc b/custom_components/openclaw/__pycache__/config_flow.cpython-312.pyc index c5b61b7503df71925d7da0909598ebcc7c686368..943f45b350a7e3671692d1cf94e0e3c6fd7e3aa0 100644 GIT binary patch delta 3400 zcma);drVu`8NlzceJ|L?*kA(&Y|Jyn1jkTPLNQKw6DJfRAX&@Wxs-cL9Keo_FLX&r zj8ar3=~SAWY=JIInyFF~k#4kZyG@-mrJb~FohCM@7F}6ur2SL%FRBvlp>^B&jeB*-S2$od!2L77G3PBcpfsp*zwL9-sLllA1#o?OYepkCK@k>8_Q^ilNR=pI5P(a+S) z$X3|a+tcZ9?d$zke@CDHaIdee!{6P~({-q&tAiBrxz@Y~_w`5X_#)&eYVYW5In+Jy zkO>A04-%z0I~2ReEI2PvFt+y`<$V1ObFqc1Kl#x2Be zF6E!5t@F{B&BJLl+ZP3&p|0rf3P(`MGhm+$VDaKb(Wb%&WNAE%8%Uphp998#vj9*Y zhG2LbAO}!EeqnEEg-D8SNEAase<1imNI|0tn#6~|;yD1T&j->|6yXdCT3_;^g*FvA zcudtlj6PTNzFGh?>?4Jx-nuamWk6gB(ga|VLJ@|?0#Z;8r_siGJR0ql(qjHN)!dIR zmR?14dV9+|k&*nM{ADWJE2?rVbPa}K5l?gX9;-jA`83^%r=!nQe1uR9aa5M;p%%?pJbnq}^MET9igq{@3`)bYB(ix8^Hj+Oj9b9wEIIC~Lenl5BbBxN2Ku~e1+Vu%c)D1nHtFh3x-fX z0$e59U4{Az5Lx}(h#HVP(sltn()AWRb(oNKJQ-jZwuj>oZBFW#~>yCia4B&f! zvj9kNlf%;>vy3M}0_e!)-NyyiagyXV44_kFwBbY^8xf=rt0)q6&X9jKl+?~`SWk8* ztfvANxn~$`6OWk+Gz$7E$djHY{76Oq<1Q+e*v2HqbHK?P+5Ah$?aC1=3Gxrqp8E z=ytl+o|M7DRxCFqk|xZiG+Sp?zb9c6pDS0gEjK56zBz3Wc#1pPMy^}TCE5)!;eOfj zOlinJy^T4Ss2cy6BJjDIqS>J4@SCJ}k2Rub0>OYxcN!cW_$>-&S^Na}6)3&~V5dh3 zNN&Ilz)t|MlyDW`r+|DwC13$?3-C6e06-^)Q&henE{9ebppc_`7&}2M0#pO+fE@sK z@Hjwn0ZIYIfSrJ=fSVMXj#VgF$qGwj>{Rt1!=cH5C}Bm2pNCscScTN>eLU?7>~n*P zg!b-^*vhUQm_M+x-LvBDU$zY_rRT2+oWV5PeXcuZs=jM=T+`3%W0snjrS2?K49;YUWfvT=XX?SG|ww8GY{B6O5R$r4IqmWnMUBvV!;t#-yNwVSO{ zDa-C!ool?BPLaTw%(Jxq*N(qYy_D--HrA|;qWV(ZSz+}U;xg=SX#b>lu0^uBEX!r# zg0PaebJ^rx&EgE^n8|!O<3dKv;=UXIIx%Zq%vw)V+H>jjXJvn>PB&z(TIe_Uv$5=t z#|aXn|3?@um^1t9_CJoC8hjd-4`|pOgWmz^U9OWd zN=2uD-v`YHfS&<=4q&&<9gsc*Oas_81J@P1v%U{&sft{0u|%Fk`~bRJ;9gZT>7F)! zjb~EaZ^I3DLVs{bK2khY3>O`(tq-DU0C4}E(1m6E@XkO`PIa=M8@PJ#BD)^nqoNaa zIy2&bFRVv~Znn4BzZcu4zNC)*g|Y1nx!J~c?qj1@bbkK}6ZCeL{C-T25e^@qP}0Ui zBO`&}h>~`U4q`0uJgZz0$YQG%-5|ehZ4-{L=p?)C>qz_vsR2bTcxy)_xtj)%aD2kq ze^V=T9|k-PV83nb$A#VIY_2!YzmncG9ugOsE&7q(Nbsu4n8s(7TipUI)J zzj7qfUhOJHs=smn7x%$rbst%7k7NxXG=S!I+~=6^_tleRsAET_mHXJ5+g7XoxCT-F E7f&SgY5)KL delta 3044 zcma);YfMx}6oBvS-n+mqu*kA3un%6V%Pyc5Q9vG|;3_GCkJhwWyFu<1S8=z_z4$=Q zV(X)geS}V0@llP9Rw-(=tBF5M6E*3drqM>5((R`8hd)f3HbiWyZE8AaF1rgpTIa`i z&zU)M=FHqPj|=akgZoh0H)&~lj+WIo2mA;7({fStyekcP5vSoKe%&T$npmW|A*9x< zs243_+Ae-YgP0?x)9)NHS2WUZqnIaV(C=Kb2Nh1Ar{NZGV&*PRG)ZYv&FQQmeyo2J zYcbQ7Y{@vJ9jk37!)Q9X9M)@YBjgDGs%=DBrl$5buWxyKOVfPc;-&4&Tbk!D4fhBR z6b|bKcvMMR4XcDX)PXmA*5Kz&Q>hryNn8A~BI7Em;A-N@SZnq|y;j0l7N}GJ1vikB z8O6dfs&|L4XB^PD=7Rk^Kr=;zez)`D=H8mZ|;RN zi;p1GK+Ze8D3v^L?xAvMvd27$=8p_X*T>zinDsXQeR2-o5&mlORfNjPPM6aFv0yfI zh@5kkqdxMp%Sk(HORAAMTvtMPf}7eF*x%uxdxPK-r*L0Bn(SKr#X{ z0O@N3BCa(C@o(6cDq0`3EOFW>-0B&9PL2uPiZB(ilKpFCi*oBUMnLGG|_>SZB) zAJn`Hc#ncynUO{A%qUyApDGc9*r9azGJl09-qNe|KVXUfjp5@D8?lO)bF9_b&}iVj-(A8fu4@;SHi_JGc}%V z(5Rz`=&+>p-~jHWid%!vKpo!}3`m2hZIH)%iLc6v&X8?Y?bEhHr=5Uz0J{M&z#xZr zfZPDs58VV@W)kBx0h^d}9q_Ci6_olc5* zkA0u;{Xwnj`)sYX#$++2OB1`r{K+JF z&g@V>?GoYr0l$KeQqg~xp5y+;G%bgZK}*tV9Na8Rn4YwRD6*(rG|0U@|+tPJuWG_#6P~il}$3UC1& z01v+4obq99uMIH8pNk z5w*>nXl32MxqtIv`FPXGmSZjFoR_?}oJ}_^&GEkF!=cy^)1$V^$2)K8-*hX#`qu2l zHylfDT9(GU{nwDs7g*dXlrtqBzb9%de=LOfkQH&OSy5ZfM60;7oYJ_}w5Y9OqLp<& zozB44Q)M?CRW~iwBO6i8q|DKfwl+6?MBp-o+jF+>dZ>l2)E(^V@&~$zv$+JFAd8xtbQ1GMK5qW!wMa^rq_k2C`hsN3 z%bpK&z+f3*IUopNmp8l7*vd{^?MQOnhM?HfEj8d9)R?X~CufsK^BYU@k>*eCe=#zT ltoMeDixAQel>WiN_rXXH`P*BXp2=Oy%x)^xUh^Qze*qy~uzCOh diff --git a/custom_components/openclaw/__pycache__/const.cpython-312.pyc b/custom_components/openclaw/__pycache__/const.cpython-312.pyc index 231a41e6ca4399339007096edd0f5cfe95585f2f..be114a0e7ac94588d6a329b4f52865f36cd8f913 100644 GIT binary patch delta 811 zcmZ`%OHUI~6rO1rNhHX^2tsLj6exurDDU^9oxWRJ(uX8xGZ{ETTWDs;%oKGt-MKZ$ z!Wv^@{0Y{rE0=C?qoghkD>qH}171)TF1(BTo%?;?Ip@Awy`P-$rL*%c$HvDW_43zy z9QX5jaXajPgDd1Y?iE)!ojk1dVITFOgB;k;&`JI1A}0=z3kQ`UWtavIY`rW^ans;B zL+Zs58p2T;#xZiEhemLmM$t=SI6)qqq;d3-7l9^liY9TIeCQ{@8Jfabn#MWu<2=pa zLXo4{_dG7v6#flIb8l_9bQ8_9Xqgspg%)v@mT--hah+ChgH~~qHQb^#+@^I5&;|x+ z6Ghs>5N%_a0vMqnMoGjNg)mNGOi%=q6vY(9@Dar^{Vd^4S{_kT4O3Q4WAH%J8wRiQ z51ZY4y7t6Sbm>GZ?<-P8R*O}+sGMEeytOvDYHHqLy_+51684G^j9P-CLLqkp(V!)e z0->-a9Ow|qGSYxB)5<2q^OkUI!p6=JNYaRfO=8WqT0lRCt%^K{6v z%^b>**s+8o4U&wsN+70hAirw~vJN@cx1>OvZKhO)czUYY6U=6IbK$Ji^*9q3!%`}c zjuryZaIVp5_uKePV^jFd_dCQ)IwIX#|8>4>{~mCK!jV8BmH&@L?_qb^6wK;zMQPd% zQ*U-jlB}wlDVt^XOp?_2)-KxbpE4W!vYhdY%`H36^UX6~$MZWEuElfCarU9Z@4guF qoO7;Lo7-nQ=X&Z7>c%^<{`B?073bz{tpv|GJ&ntbcE0TetMePVS?wSI delta 345 zcmXw#xk>|J0EG9?1`{QzY}{O%`@Zk{PE@e4P>X47MuN6hHi}Jxow9|3*!T!8mR2GN zmZ|InUm)NMxQb3Od;xjA*#mJaF@0x3?w2wK<(Wl+ITV>kX`2O(AW)!{wdOljs diff --git a/custom_components/openclaw/config_flow.py b/custom_components/openclaw/config_flow.py index 0c17b31..d5ca526 100644 --- a/custom_components/openclaw/config_flow.py +++ b/custom_components/openclaw/config_flow.py @@ -45,7 +45,9 @@ from .const import ( CONF_WAKE_WORD, CONF_WAKE_WORD_ENABLED, CONF_ALLOW_BRAVE_WEBSPEECH, + CONF_BROWSER_VOICE_LANGUAGE, CONF_VOICE_PROVIDER, + BROWSER_VOICE_LANGUAGES, CONTEXT_STRATEGY_CLEAR, CONTEXT_STRATEGY_TRUNCATE, DEFAULT_GATEWAY_HOST, @@ -57,6 +59,7 @@ from .const import ( DEFAULT_WAKE_WORD, DEFAULT_WAKE_WORD_ENABLED, DEFAULT_ALLOW_BRAVE_WEBSPEECH, + DEFAULT_BROWSER_VOICE_LANGUAGE, DEFAULT_VOICE_PROVIDER, DOMAIN, OPENCLAW_CONFIG_REL_PATH, @@ -414,66 +417,73 @@ class OpenClawOptionsFlow(OptionsFlow): return self.async_create_entry(title="", data=user_input) options = self._config_entry.options - return self.async_show_form( - step_id="init", - data_schema=vol.Schema( - { - vol.Optional( - CONF_INCLUDE_EXPOSED_CONTEXT, - default=options.get( - CONF_INCLUDE_EXPOSED_CONTEXT, - DEFAULT_INCLUDE_EXPOSED_CONTEXT, - ), - ): bool, - vol.Optional( - CONF_CONTEXT_MAX_CHARS, - default=options.get( - CONF_CONTEXT_MAX_CHARS, - DEFAULT_CONTEXT_MAX_CHARS, - ), - ): vol.All(int, vol.Range(min=1000, max=200000)), - vol.Optional( - CONF_CONTEXT_STRATEGY, - default=options.get( - CONF_CONTEXT_STRATEGY, - DEFAULT_CONTEXT_STRATEGY, - ), - ): vol.In([CONTEXT_STRATEGY_TRUNCATE, CONTEXT_STRATEGY_CLEAR]), - vol.Optional( - CONF_ENABLE_TOOL_CALLS, - default=options.get( - CONF_ENABLE_TOOL_CALLS, - DEFAULT_ENABLE_TOOL_CALLS, - ), - ): bool, - vol.Optional( - CONF_WAKE_WORD_ENABLED, - default=options.get( - CONF_WAKE_WORD_ENABLED, - DEFAULT_WAKE_WORD_ENABLED, - ), - ): bool, - vol.Optional( - CONF_WAKE_WORD, - default=options.get( - CONF_WAKE_WORD, - DEFAULT_WAKE_WORD, - ), - ): str, - vol.Optional( - CONF_ALLOW_BRAVE_WEBSPEECH, - default=options.get( - CONF_ALLOW_BRAVE_WEBSPEECH, - DEFAULT_ALLOW_BRAVE_WEBSPEECH, - ), - ): bool, - vol.Optional( - CONF_VOICE_PROVIDER, - default=options.get( - CONF_VOICE_PROVIDER, - DEFAULT_VOICE_PROVIDER, - ), - ): vol.In(["browser", "assist_stt"]), - } - ), - ) + selected_provider = options.get(CONF_VOICE_PROVIDER, DEFAULT_VOICE_PROVIDER) + + schema: dict[Any, Any] = { + vol.Optional( + CONF_INCLUDE_EXPOSED_CONTEXT, + default=options.get( + CONF_INCLUDE_EXPOSED_CONTEXT, + DEFAULT_INCLUDE_EXPOSED_CONTEXT, + ), + ): bool, + vol.Optional( + CONF_CONTEXT_MAX_CHARS, + default=options.get( + CONF_CONTEXT_MAX_CHARS, + DEFAULT_CONTEXT_MAX_CHARS, + ), + ): vol.All(int, vol.Range(min=1000, max=200000)), + vol.Optional( + CONF_CONTEXT_STRATEGY, + default=options.get( + CONF_CONTEXT_STRATEGY, + DEFAULT_CONTEXT_STRATEGY, + ), + ): vol.In([CONTEXT_STRATEGY_TRUNCATE, CONTEXT_STRATEGY_CLEAR]), + vol.Optional( + CONF_ENABLE_TOOL_CALLS, + default=options.get( + CONF_ENABLE_TOOL_CALLS, + DEFAULT_ENABLE_TOOL_CALLS, + ), + ): bool, + vol.Optional( + CONF_WAKE_WORD_ENABLED, + default=options.get( + CONF_WAKE_WORD_ENABLED, + DEFAULT_WAKE_WORD_ENABLED, + ), + ): bool, + vol.Optional( + CONF_WAKE_WORD, + default=options.get( + CONF_WAKE_WORD, + DEFAULT_WAKE_WORD, + ), + ): str, + vol.Optional( + CONF_ALLOW_BRAVE_WEBSPEECH, + default=options.get( + CONF_ALLOW_BRAVE_WEBSPEECH, + DEFAULT_ALLOW_BRAVE_WEBSPEECH, + ), + ): bool, + vol.Optional( + CONF_VOICE_PROVIDER, + default=selected_provider, + ): vol.In(["browser", "assist_stt"]), + } + + if selected_provider == "browser": + schema[ + vol.Optional( + CONF_BROWSER_VOICE_LANGUAGE, + default=options.get( + CONF_BROWSER_VOICE_LANGUAGE, + DEFAULT_BROWSER_VOICE_LANGUAGE, + ), + ) + ] = vol.In(BROWSER_VOICE_LANGUAGES) + + return self.async_show_form(step_id="init", data_schema=vol.Schema(schema)) diff --git a/custom_components/openclaw/const.py b/custom_components/openclaw/const.py index b92b1b1..24150e0 100644 --- a/custom_components/openclaw/const.py +++ b/custom_components/openclaw/const.py @@ -32,6 +32,7 @@ CONF_WAKE_WORD_ENABLED = "wake_word_enabled" CONF_WAKE_WORD = "wake_word" CONF_ALLOW_BRAVE_WEBSPEECH = "allow_brave_webspeech" CONF_VOICE_PROVIDER = "voice_provider" +CONF_BROWSER_VOICE_LANGUAGE = "browser_voice_language" DEFAULT_INCLUDE_EXPOSED_CONTEXT = True DEFAULT_CONTEXT_MAX_CHARS = 13000 @@ -41,6 +42,44 @@ DEFAULT_WAKE_WORD_ENABLED = False DEFAULT_WAKE_WORD = "hey openclaw" DEFAULT_ALLOW_BRAVE_WEBSPEECH = False DEFAULT_VOICE_PROVIDER = "browser" +DEFAULT_BROWSER_VOICE_LANGUAGE = "auto" + +BROWSER_VOICE_LANGUAGES: tuple[str, ...] = ( + "auto", + "bg-BG", + "en-US", + "en-GB", + "de-DE", + "fr-FR", + "es-ES", + "it-IT", + "pt-PT", + "pt-BR", + "ru-RU", + "nl-NL", + "pl-PL", + "tr-TR", + "uk-UA", + "cs-CZ", + "ro-RO", + "el-GR", + "sv-SE", + "no-NO", + "da-DK", + "fi-FI", + "hu-HU", + "sk-SK", + "sl-SI", + "hr-HR", + "sr-RS", + "ja-JP", + "ko-KR", + "zh-CN", + "zh-TW", + "ar-SA", + "he-IL", + "hi-IN", +) CONTEXT_STRATEGY_TRUNCATE = "truncate" CONTEXT_STRATEGY_CLEAR = "clear" diff --git a/custom_components/openclaw/manifest.json b/custom_components/openclaw/manifest.json index df68a06..442652a 100644 --- a/custom_components/openclaw/manifest.json +++ b/custom_components/openclaw/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues", "requirements": [], - "version": "0.1.41", + "version": "0.1.42", "dependencies": ["conversation"], "after_dependencies": ["hassio", "lovelace"] } diff --git a/custom_components/openclaw/strings.json b/custom_components/openclaw/strings.json index dab5a33..9c4175c 100644 --- a/custom_components/openclaw/strings.json +++ b/custom_components/openclaw/strings.json @@ -44,7 +44,8 @@ "wake_word_enabled": "Require wake word", "wake_word": "Wake word", "allow_brave_webspeech": "Allow Web Speech in Brave (experimental)", - "voice_provider": "Voice input provider (browser or HA STT)" + "voice_provider": "Voice input provider (browser or HA STT)", + "browser_voice_language": "Browser voice language" } } } diff --git a/custom_components/openclaw/translations/en.json b/custom_components/openclaw/translations/en.json index dab5a33..9c4175c 100644 --- a/custom_components/openclaw/translations/en.json +++ b/custom_components/openclaw/translations/en.json @@ -44,7 +44,8 @@ "wake_word_enabled": "Require wake word", "wake_word": "Wake word", "allow_brave_webspeech": "Allow Web Speech in Brave (experimental)", - "voice_provider": "Voice input provider (browser or HA STT)" + "voice_provider": "Voice input provider (browser or HA STT)", + "browser_voice_language": "Browser voice language" } } } diff --git a/custom_components/openclaw/www/openclaw-chat-card.js b/custom_components/openclaw/www/openclaw-chat-card.js index 8c26ce1..eab0ff9 100644 --- a/custom_components/openclaw/www/openclaw-chat-card.js +++ b/custom_components/openclaw/www/openclaw-chat-card.js @@ -13,7 +13,7 @@ * + subscribes to openclaw_message_received events. */ -const CARD_VERSION = "0.3.5"; +const CARD_VERSION = "0.3.6"; // Max time (ms) to show the thinking indicator before falling back to an error const THINKING_TIMEOUT_MS = 120_000; @@ -68,6 +68,7 @@ class OpenClawChatCard extends HTMLElement { this._voiceNetworkErrorCount = 0; this._pendingResponses = 0; this._speechLangOverride = null; + this._integrationBrowserVoiceLanguage = null; this._integrationVoiceLanguage = null; this._integrationTtsLanguage = null; this._allowBraveWebSpeechIntegration = false; @@ -229,7 +230,7 @@ class OpenClawChatCard extends HTMLElement { this._scrollToBottom(); // In voice mode, speak the response - if (this._isVoiceMode && data.message) { + if (this._isVoiceMode && this._recognition && data.message) { this._speak(data.message); } } @@ -354,6 +355,10 @@ class OpenClawChatCard extends HTMLElement { this._allowBraveWebSpeechIntegration = !!result?.allow_brave_webspeech; this._voiceProviderIntegration = result?.voice_provider === "assist_stt" ? "assist_stt" : "browser"; + this._integrationBrowserVoiceLanguage = + result?.browser_voice_language && result?.browser_voice_language !== "auto" + ? this._normalizeSpeechLanguage(result.browser_voice_language) + : null; this._integrationVoiceLanguage = result?.language ? this._normalizeSpeechLanguage(result.language) : null; @@ -544,6 +549,13 @@ class OpenClawChatCard extends HTMLElement { } const configuredLang = this._config.voice_language; + if (configuredLang) { + return this._normalizeSpeechLanguage(configuredLang); + } + const provider = this._getVoiceProvider(); + if (provider === "browser" && this._integrationBrowserVoiceLanguage) { + return this._normalizeSpeechLanguage(this._integrationBrowserVoiceLanguage); + } const integrationLang = this._integrationVoiceLanguage; const hassLang = this._hass?.locale?.language || this._hass?.selectedLanguage || this._hass?.language; @@ -554,6 +566,13 @@ class OpenClawChatCard extends HTMLElement { _getSpeechSynthesisLanguage() { const configuredLang = this._config.voice_language; + if (configuredLang) { + return this._normalizeSpeechLanguage(configuredLang); + } + const provider = this._getVoiceProvider(); + if (provider === "browser" && this._integrationBrowserVoiceLanguage) { + return this._normalizeSpeechLanguage(this._integrationBrowserVoiceLanguage); + } const preferred = configuredLang || this._integrationTtsLanguage || diff --git a/www/openclaw-chat-card.js b/www/openclaw-chat-card.js index cb2245b..3685489 100644 --- a/www/openclaw-chat-card.js +++ b/www/openclaw-chat-card.js @@ -1,7 +1,7 @@ (async () => { try { if (!customElements.get("openclaw-chat-card")) { - const src = "/openclaw/openclaw-chat-card.js?v=0.1.41"; + const src = "/openclaw/openclaw-chat-card.js?v=0.1.42"; console.info("OpenClaw loader importing", src); await import(src); }