Files
zclaw_openfang/desktop/coverage/src/components/ChatArea.tsx.html
iven d0c6319fc1
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
feat: 添加ESLint和Prettier配置并优化代码结构
style: 格式化代码文件并修复样式问题

docs: 新增部署文档和系统要求文档

test: 更新测试截图和覆盖率报告

refactor: 重构SchedulerPanel加载状态逻辑

ci: 添加lint和format脚本到package.json

build: 更新依赖项并添加开发工具

chore: 添加验证报告和上线审查计划
2026-03-26 08:02:23 +08:00

2206 lines
123 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src/components/ChatArea.tsx</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../prettify.css" />
<link rel="stylesheet" href="../../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(../../sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="../../index.html">All files</a> / <a href="index.html">src/components</a> ChatArea.tsx</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/512</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/512</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a>
<a name='L226'></a><a href='#L226'>226</a>
<a name='L227'></a><a href='#L227'>227</a>
<a name='L228'></a><a href='#L228'>228</a>
<a name='L229'></a><a href='#L229'>229</a>
<a name='L230'></a><a href='#L230'>230</a>
<a name='L231'></a><a href='#L231'>231</a>
<a name='L232'></a><a href='#L232'>232</a>
<a name='L233'></a><a href='#L233'>233</a>
<a name='L234'></a><a href='#L234'>234</a>
<a name='L235'></a><a href='#L235'>235</a>
<a name='L236'></a><a href='#L236'>236</a>
<a name='L237'></a><a href='#L237'>237</a>
<a name='L238'></a><a href='#L238'>238</a>
<a name='L239'></a><a href='#L239'>239</a>
<a name='L240'></a><a href='#L240'>240</a>
<a name='L241'></a><a href='#L241'>241</a>
<a name='L242'></a><a href='#L242'>242</a>
<a name='L243'></a><a href='#L243'>243</a>
<a name='L244'></a><a href='#L244'>244</a>
<a name='L245'></a><a href='#L245'>245</a>
<a name='L246'></a><a href='#L246'>246</a>
<a name='L247'></a><a href='#L247'>247</a>
<a name='L248'></a><a href='#L248'>248</a>
<a name='L249'></a><a href='#L249'>249</a>
<a name='L250'></a><a href='#L250'>250</a>
<a name='L251'></a><a href='#L251'>251</a>
<a name='L252'></a><a href='#L252'>252</a>
<a name='L253'></a><a href='#L253'>253</a>
<a name='L254'></a><a href='#L254'>254</a>
<a name='L255'></a><a href='#L255'>255</a>
<a name='L256'></a><a href='#L256'>256</a>
<a name='L257'></a><a href='#L257'>257</a>
<a name='L258'></a><a href='#L258'>258</a>
<a name='L259'></a><a href='#L259'>259</a>
<a name='L260'></a><a href='#L260'>260</a>
<a name='L261'></a><a href='#L261'>261</a>
<a name='L262'></a><a href='#L262'>262</a>
<a name='L263'></a><a href='#L263'>263</a>
<a name='L264'></a><a href='#L264'>264</a>
<a name='L265'></a><a href='#L265'>265</a>
<a name='L266'></a><a href='#L266'>266</a>
<a name='L267'></a><a href='#L267'>267</a>
<a name='L268'></a><a href='#L268'>268</a>
<a name='L269'></a><a href='#L269'>269</a>
<a name='L270'></a><a href='#L270'>270</a>
<a name='L271'></a><a href='#L271'>271</a>
<a name='L272'></a><a href='#L272'>272</a>
<a name='L273'></a><a href='#L273'>273</a>
<a name='L274'></a><a href='#L274'>274</a>
<a name='L275'></a><a href='#L275'>275</a>
<a name='L276'></a><a href='#L276'>276</a>
<a name='L277'></a><a href='#L277'>277</a>
<a name='L278'></a><a href='#L278'>278</a>
<a name='L279'></a><a href='#L279'>279</a>
<a name='L280'></a><a href='#L280'>280</a>
<a name='L281'></a><a href='#L281'>281</a>
<a name='L282'></a><a href='#L282'>282</a>
<a name='L283'></a><a href='#L283'>283</a>
<a name='L284'></a><a href='#L284'>284</a>
<a name='L285'></a><a href='#L285'>285</a>
<a name='L286'></a><a href='#L286'>286</a>
<a name='L287'></a><a href='#L287'>287</a>
<a name='L288'></a><a href='#L288'>288</a>
<a name='L289'></a><a href='#L289'>289</a>
<a name='L290'></a><a href='#L290'>290</a>
<a name='L291'></a><a href='#L291'>291</a>
<a name='L292'></a><a href='#L292'>292</a>
<a name='L293'></a><a href='#L293'>293</a>
<a name='L294'></a><a href='#L294'>294</a>
<a name='L295'></a><a href='#L295'>295</a>
<a name='L296'></a><a href='#L296'>296</a>
<a name='L297'></a><a href='#L297'>297</a>
<a name='L298'></a><a href='#L298'>298</a>
<a name='L299'></a><a href='#L299'>299</a>
<a name='L300'></a><a href='#L300'>300</a>
<a name='L301'></a><a href='#L301'>301</a>
<a name='L302'></a><a href='#L302'>302</a>
<a name='L303'></a><a href='#L303'>303</a>
<a name='L304'></a><a href='#L304'>304</a>
<a name='L305'></a><a href='#L305'>305</a>
<a name='L306'></a><a href='#L306'>306</a>
<a name='L307'></a><a href='#L307'>307</a>
<a name='L308'></a><a href='#L308'>308</a>
<a name='L309'></a><a href='#L309'>309</a>
<a name='L310'></a><a href='#L310'>310</a>
<a name='L311'></a><a href='#L311'>311</a>
<a name='L312'></a><a href='#L312'>312</a>
<a name='L313'></a><a href='#L313'>313</a>
<a name='L314'></a><a href='#L314'>314</a>
<a name='L315'></a><a href='#L315'>315</a>
<a name='L316'></a><a href='#L316'>316</a>
<a name='L317'></a><a href='#L317'>317</a>
<a name='L318'></a><a href='#L318'>318</a>
<a name='L319'></a><a href='#L319'>319</a>
<a name='L320'></a><a href='#L320'>320</a>
<a name='L321'></a><a href='#L321'>321</a>
<a name='L322'></a><a href='#L322'>322</a>
<a name='L323'></a><a href='#L323'>323</a>
<a name='L324'></a><a href='#L324'>324</a>
<a name='L325'></a><a href='#L325'>325</a>
<a name='L326'></a><a href='#L326'>326</a>
<a name='L327'></a><a href='#L327'>327</a>
<a name='L328'></a><a href='#L328'>328</a>
<a name='L329'></a><a href='#L329'>329</a>
<a name='L330'></a><a href='#L330'>330</a>
<a name='L331'></a><a href='#L331'>331</a>
<a name='L332'></a><a href='#L332'>332</a>
<a name='L333'></a><a href='#L333'>333</a>
<a name='L334'></a><a href='#L334'>334</a>
<a name='L335'></a><a href='#L335'>335</a>
<a name='L336'></a><a href='#L336'>336</a>
<a name='L337'></a><a href='#L337'>337</a>
<a name='L338'></a><a href='#L338'>338</a>
<a name='L339'></a><a href='#L339'>339</a>
<a name='L340'></a><a href='#L340'>340</a>
<a name='L341'></a><a href='#L341'>341</a>
<a name='L342'></a><a href='#L342'>342</a>
<a name='L343'></a><a href='#L343'>343</a>
<a name='L344'></a><a href='#L344'>344</a>
<a name='L345'></a><a href='#L345'>345</a>
<a name='L346'></a><a href='#L346'>346</a>
<a name='L347'></a><a href='#L347'>347</a>
<a name='L348'></a><a href='#L348'>348</a>
<a name='L349'></a><a href='#L349'>349</a>
<a name='L350'></a><a href='#L350'>350</a>
<a name='L351'></a><a href='#L351'>351</a>
<a name='L352'></a><a href='#L352'>352</a>
<a name='L353'></a><a href='#L353'>353</a>
<a name='L354'></a><a href='#L354'>354</a>
<a name='L355'></a><a href='#L355'>355</a>
<a name='L356'></a><a href='#L356'>356</a>
<a name='L357'></a><a href='#L357'>357</a>
<a name='L358'></a><a href='#L358'>358</a>
<a name='L359'></a><a href='#L359'>359</a>
<a name='L360'></a><a href='#L360'>360</a>
<a name='L361'></a><a href='#L361'>361</a>
<a name='L362'></a><a href='#L362'>362</a>
<a name='L363'></a><a href='#L363'>363</a>
<a name='L364'></a><a href='#L364'>364</a>
<a name='L365'></a><a href='#L365'>365</a>
<a name='L366'></a><a href='#L366'>366</a>
<a name='L367'></a><a href='#L367'>367</a>
<a name='L368'></a><a href='#L368'>368</a>
<a name='L369'></a><a href='#L369'>369</a>
<a name='L370'></a><a href='#L370'>370</a>
<a name='L371'></a><a href='#L371'>371</a>
<a name='L372'></a><a href='#L372'>372</a>
<a name='L373'></a><a href='#L373'>373</a>
<a name='L374'></a><a href='#L374'>374</a>
<a name='L375'></a><a href='#L375'>375</a>
<a name='L376'></a><a href='#L376'>376</a>
<a name='L377'></a><a href='#L377'>377</a>
<a name='L378'></a><a href='#L378'>378</a>
<a name='L379'></a><a href='#L379'>379</a>
<a name='L380'></a><a href='#L380'>380</a>
<a name='L381'></a><a href='#L381'>381</a>
<a name='L382'></a><a href='#L382'>382</a>
<a name='L383'></a><a href='#L383'>383</a>
<a name='L384'></a><a href='#L384'>384</a>
<a name='L385'></a><a href='#L385'>385</a>
<a name='L386'></a><a href='#L386'>386</a>
<a name='L387'></a><a href='#L387'>387</a>
<a name='L388'></a><a href='#L388'>388</a>
<a name='L389'></a><a href='#L389'>389</a>
<a name='L390'></a><a href='#L390'>390</a>
<a name='L391'></a><a href='#L391'>391</a>
<a name='L392'></a><a href='#L392'>392</a>
<a name='L393'></a><a href='#L393'>393</a>
<a name='L394'></a><a href='#L394'>394</a>
<a name='L395'></a><a href='#L395'>395</a>
<a name='L396'></a><a href='#L396'>396</a>
<a name='L397'></a><a href='#L397'>397</a>
<a name='L398'></a><a href='#L398'>398</a>
<a name='L399'></a><a href='#L399'>399</a>
<a name='L400'></a><a href='#L400'>400</a>
<a name='L401'></a><a href='#L401'>401</a>
<a name='L402'></a><a href='#L402'>402</a>
<a name='L403'></a><a href='#L403'>403</a>
<a name='L404'></a><a href='#L404'>404</a>
<a name='L405'></a><a href='#L405'>405</a>
<a name='L406'></a><a href='#L406'>406</a>
<a name='L407'></a><a href='#L407'>407</a>
<a name='L408'></a><a href='#L408'>408</a>
<a name='L409'></a><a href='#L409'>409</a>
<a name='L410'></a><a href='#L410'>410</a>
<a name='L411'></a><a href='#L411'>411</a>
<a name='L412'></a><a href='#L412'>412</a>
<a name='L413'></a><a href='#L413'>413</a>
<a name='L414'></a><a href='#L414'>414</a>
<a name='L415'></a><a href='#L415'>415</a>
<a name='L416'></a><a href='#L416'>416</a>
<a name='L417'></a><a href='#L417'>417</a>
<a name='L418'></a><a href='#L418'>418</a>
<a name='L419'></a><a href='#L419'>419</a>
<a name='L420'></a><a href='#L420'>420</a>
<a name='L421'></a><a href='#L421'>421</a>
<a name='L422'></a><a href='#L422'>422</a>
<a name='L423'></a><a href='#L423'>423</a>
<a name='L424'></a><a href='#L424'>424</a>
<a name='L425'></a><a href='#L425'>425</a>
<a name='L426'></a><a href='#L426'>426</a>
<a name='L427'></a><a href='#L427'>427</a>
<a name='L428'></a><a href='#L428'>428</a>
<a name='L429'></a><a href='#L429'>429</a>
<a name='L430'></a><a href='#L430'>430</a>
<a name='L431'></a><a href='#L431'>431</a>
<a name='L432'></a><a href='#L432'>432</a>
<a name='L433'></a><a href='#L433'>433</a>
<a name='L434'></a><a href='#L434'>434</a>
<a name='L435'></a><a href='#L435'>435</a>
<a name='L436'></a><a href='#L436'>436</a>
<a name='L437'></a><a href='#L437'>437</a>
<a name='L438'></a><a href='#L438'>438</a>
<a name='L439'></a><a href='#L439'>439</a>
<a name='L440'></a><a href='#L440'>440</a>
<a name='L441'></a><a href='#L441'>441</a>
<a name='L442'></a><a href='#L442'>442</a>
<a name='L443'></a><a href='#L443'>443</a>
<a name='L444'></a><a href='#L444'>444</a>
<a name='L445'></a><a href='#L445'>445</a>
<a name='L446'></a><a href='#L446'>446</a>
<a name='L447'></a><a href='#L447'>447</a>
<a name='L448'></a><a href='#L448'>448</a>
<a name='L449'></a><a href='#L449'>449</a>
<a name='L450'></a><a href='#L450'>450</a>
<a name='L451'></a><a href='#L451'>451</a>
<a name='L452'></a><a href='#L452'>452</a>
<a name='L453'></a><a href='#L453'>453</a>
<a name='L454'></a><a href='#L454'>454</a>
<a name='L455'></a><a href='#L455'>455</a>
<a name='L456'></a><a href='#L456'>456</a>
<a name='L457'></a><a href='#L457'>457</a>
<a name='L458'></a><a href='#L458'>458</a>
<a name='L459'></a><a href='#L459'>459</a>
<a name='L460'></a><a href='#L460'>460</a>
<a name='L461'></a><a href='#L461'>461</a>
<a name='L462'></a><a href='#L462'>462</a>
<a name='L463'></a><a href='#L463'>463</a>
<a name='L464'></a><a href='#L464'>464</a>
<a name='L465'></a><a href='#L465'>465</a>
<a name='L466'></a><a href='#L466'>466</a>
<a name='L467'></a><a href='#L467'>467</a>
<a name='L468'></a><a href='#L468'>468</a>
<a name='L469'></a><a href='#L469'>469</a>
<a name='L470'></a><a href='#L470'>470</a>
<a name='L471'></a><a href='#L471'>471</a>
<a name='L472'></a><a href='#L472'>472</a>
<a name='L473'></a><a href='#L473'>473</a>
<a name='L474'></a><a href='#L474'>474</a>
<a name='L475'></a><a href='#L475'>475</a>
<a name='L476'></a><a href='#L476'>476</a>
<a name='L477'></a><a href='#L477'>477</a>
<a name='L478'></a><a href='#L478'>478</a>
<a name='L479'></a><a href='#L479'>479</a>
<a name='L480'></a><a href='#L480'>480</a>
<a name='L481'></a><a href='#L481'>481</a>
<a name='L482'></a><a href='#L482'>482</a>
<a name='L483'></a><a href='#L483'>483</a>
<a name='L484'></a><a href='#L484'>484</a>
<a name='L485'></a><a href='#L485'>485</a>
<a name='L486'></a><a href='#L486'>486</a>
<a name='L487'></a><a href='#L487'>487</a>
<a name='L488'></a><a href='#L488'>488</a>
<a name='L489'></a><a href='#L489'>489</a>
<a name='L490'></a><a href='#L490'>490</a>
<a name='L491'></a><a href='#L491'>491</a>
<a name='L492'></a><a href='#L492'>492</a>
<a name='L493'></a><a href='#L493'>493</a>
<a name='L494'></a><a href='#L494'>494</a>
<a name='L495'></a><a href='#L495'>495</a>
<a name='L496'></a><a href='#L496'>496</a>
<a name='L497'></a><a href='#L497'>497</a>
<a name='L498'></a><a href='#L498'>498</a>
<a name='L499'></a><a href='#L499'>499</a>
<a name='L500'></a><a href='#L500'>500</a>
<a name='L501'></a><a href='#L501'>501</a>
<a name='L502'></a><a href='#L502'>502</a>
<a name='L503'></a><a href='#L503'>503</a>
<a name='L504'></a><a href='#L504'>504</a>
<a name='L505'></a><a href='#L505'>505</a>
<a name='L506'></a><a href='#L506'>506</a>
<a name='L507'></a><a href='#L507'>507</a>
<a name='L508'></a><a href='#L508'>508</a>
<a name='L509'></a><a href='#L509'>509</a>
<a name='L510'></a><a href='#L510'>510</a>
<a name='L511'></a><a href='#L511'>511</a>
<a name='L512'></a><a href='#L512'>512</a>
<a name='L513'></a><a href='#L513'>513</a>
<a name='L514'></a><a href='#L514'>514</a>
<a name='L515'></a><a href='#L515'>515</a>
<a name='L516'></a><a href='#L516'>516</a>
<a name='L517'></a><a href='#L517'>517</a>
<a name='L518'></a><a href='#L518'>518</a>
<a name='L519'></a><a href='#L519'>519</a>
<a name='L520'></a><a href='#L520'>520</a>
<a name='L521'></a><a href='#L521'>521</a>
<a name='L522'></a><a href='#L522'>522</a>
<a name='L523'></a><a href='#L523'>523</a>
<a name='L524'></a><a href='#L524'>524</a>
<a name='L525'></a><a href='#L525'>525</a>
<a name='L526'></a><a href='#L526'>526</a>
<a name='L527'></a><a href='#L527'>527</a>
<a name='L528'></a><a href='#L528'>528</a>
<a name='L529'></a><a href='#L529'>529</a>
<a name='L530'></a><a href='#L530'>530</a>
<a name='L531'></a><a href='#L531'>531</a>
<a name='L532'></a><a href='#L532'>532</a>
<a name='L533'></a><a href='#L533'>533</a>
<a name='L534'></a><a href='#L534'>534</a>
<a name='L535'></a><a href='#L535'>535</a>
<a name='L536'></a><a href='#L536'>536</a>
<a name='L537'></a><a href='#L537'>537</a>
<a name='L538'></a><a href='#L538'>538</a>
<a name='L539'></a><a href='#L539'>539</a>
<a name='L540'></a><a href='#L540'>540</a>
<a name='L541'></a><a href='#L541'>541</a>
<a name='L542'></a><a href='#L542'>542</a>
<a name='L543'></a><a href='#L543'>543</a>
<a name='L544'></a><a href='#L544'>544</a>
<a name='L545'></a><a href='#L545'>545</a>
<a name='L546'></a><a href='#L546'>546</a>
<a name='L547'></a><a href='#L547'>547</a>
<a name='L548'></a><a href='#L548'>548</a>
<a name='L549'></a><a href='#L549'>549</a>
<a name='L550'></a><a href='#L550'>550</a>
<a name='L551'></a><a href='#L551'>551</a>
<a name='L552'></a><a href='#L552'>552</a>
<a name='L553'></a><a href='#L553'>553</a>
<a name='L554'></a><a href='#L554'>554</a>
<a name='L555'></a><a href='#L555'>555</a>
<a name='L556'></a><a href='#L556'>556</a>
<a name='L557'></a><a href='#L557'>557</a>
<a name='L558'></a><a href='#L558'>558</a>
<a name='L559'></a><a href='#L559'>559</a>
<a name='L560'></a><a href='#L560'>560</a>
<a name='L561'></a><a href='#L561'>561</a>
<a name='L562'></a><a href='#L562'>562</a>
<a name='L563'></a><a href='#L563'>563</a>
<a name='L564'></a><a href='#L564'>564</a>
<a name='L565'></a><a href='#L565'>565</a>
<a name='L566'></a><a href='#L566'>566</a>
<a name='L567'></a><a href='#L567'>567</a>
<a name='L568'></a><a href='#L568'>568</a>
<a name='L569'></a><a href='#L569'>569</a>
<a name='L570'></a><a href='#L570'>570</a>
<a name='L571'></a><a href='#L571'>571</a>
<a name='L572'></a><a href='#L572'>572</a>
<a name='L573'></a><a href='#L573'>573</a>
<a name='L574'></a><a href='#L574'>574</a>
<a name='L575'></a><a href='#L575'>575</a>
<a name='L576'></a><a href='#L576'>576</a>
<a name='L577'></a><a href='#L577'>577</a>
<a name='L578'></a><a href='#L578'>578</a>
<a name='L579'></a><a href='#L579'>579</a>
<a name='L580'></a><a href='#L580'>580</a>
<a name='L581'></a><a href='#L581'>581</a>
<a name='L582'></a><a href='#L582'>582</a>
<a name='L583'></a><a href='#L583'>583</a>
<a name='L584'></a><a href='#L584'>584</a>
<a name='L585'></a><a href='#L585'>585</a>
<a name='L586'></a><a href='#L586'>586</a>
<a name='L587'></a><a href='#L587'>587</a>
<a name='L588'></a><a href='#L588'>588</a>
<a name='L589'></a><a href='#L589'>589</a>
<a name='L590'></a><a href='#L590'>590</a>
<a name='L591'></a><a href='#L591'>591</a>
<a name='L592'></a><a href='#L592'>592</a>
<a name='L593'></a><a href='#L593'>593</a>
<a name='L594'></a><a href='#L594'>594</a>
<a name='L595'></a><a href='#L595'>595</a>
<a name='L596'></a><a href='#L596'>596</a>
<a name='L597'></a><a href='#L597'>597</a>
<a name='L598'></a><a href='#L598'>598</a>
<a name='L599'></a><a href='#L599'>599</a>
<a name='L600'></a><a href='#L600'>600</a>
<a name='L601'></a><a href='#L601'>601</a>
<a name='L602'></a><a href='#L602'>602</a>
<a name='L603'></a><a href='#L603'>603</a>
<a name='L604'></a><a href='#L604'>604</a>
<a name='L605'></a><a href='#L605'>605</a>
<a name='L606'></a><a href='#L606'>606</a>
<a name='L607'></a><a href='#L607'>607</a>
<a name='L608'></a><a href='#L608'>608</a>
<a name='L609'></a><a href='#L609'>609</a>
<a name='L610'></a><a href='#L610'>610</a>
<a name='L611'></a><a href='#L611'>611</a>
<a name='L612'></a><a href='#L612'>612</a>
<a name='L613'></a><a href='#L613'>613</a>
<a name='L614'></a><a href='#L614'>614</a>
<a name='L615'></a><a href='#L615'>615</a>
<a name='L616'></a><a href='#L616'>616</a>
<a name='L617'></a><a href='#L617'>617</a>
<a name='L618'></a><a href='#L618'>618</a>
<a name='L619'></a><a href='#L619'>619</a>
<a name='L620'></a><a href='#L620'>620</a>
<a name='L621'></a><a href='#L621'>621</a>
<a name='L622'></a><a href='#L622'>622</a>
<a name='L623'></a><a href='#L623'>623</a>
<a name='L624'></a><a href='#L624'>624</a>
<a name='L625'></a><a href='#L625'>625</a>
<a name='L626'></a><a href='#L626'>626</a>
<a name='L627'></a><a href='#L627'>627</a>
<a name='L628'></a><a href='#L628'>628</a>
<a name='L629'></a><a href='#L629'>629</a>
<a name='L630'></a><a href='#L630'>630</a>
<a name='L631'></a><a href='#L631'>631</a>
<a name='L632'></a><a href='#L632'>632</a>
<a name='L633'></a><a href='#L633'>633</a>
<a name='L634'></a><a href='#L634'>634</a>
<a name='L635'></a><a href='#L635'>635</a>
<a name='L636'></a><a href='#L636'>636</a>
<a name='L637'></a><a href='#L637'>637</a>
<a name='L638'></a><a href='#L638'>638</a>
<a name='L639'></a><a href='#L639'>639</a>
<a name='L640'></a><a href='#L640'>640</a>
<a name='L641'></a><a href='#L641'>641</a>
<a name='L642'></a><a href='#L642'>642</a>
<a name='L643'></a><a href='#L643'>643</a>
<a name='L644'></a><a href='#L644'>644</a>
<a name='L645'></a><a href='#L645'>645</a>
<a name='L646'></a><a href='#L646'>646</a>
<a name='L647'></a><a href='#L647'>647</a>
<a name='L648'></a><a href='#L648'>648</a>
<a name='L649'></a><a href='#L649'>649</a>
<a name='L650'></a><a href='#L650'>650</a>
<a name='L651'></a><a href='#L651'>651</a>
<a name='L652'></a><a href='#L652'>652</a>
<a name='L653'></a><a href='#L653'>653</a>
<a name='L654'></a><a href='#L654'>654</a>
<a name='L655'></a><a href='#L655'>655</a>
<a name='L656'></a><a href='#L656'>656</a>
<a name='L657'></a><a href='#L657'>657</a>
<a name='L658'></a><a href='#L658'>658</a>
<a name='L659'></a><a href='#L659'>659</a>
<a name='L660'></a><a href='#L660'>660</a>
<a name='L661'></a><a href='#L661'>661</a>
<a name='L662'></a><a href='#L662'>662</a>
<a name='L663'></a><a href='#L663'>663</a>
<a name='L664'></a><a href='#L664'>664</a>
<a name='L665'></a><a href='#L665'>665</a>
<a name='L666'></a><a href='#L666'>666</a>
<a name='L667'></a><a href='#L667'>667</a>
<a name='L668'></a><a href='#L668'>668</a>
<a name='L669'></a><a href='#L669'>669</a>
<a name='L670'></a><a href='#L670'>670</a>
<a name='L671'></a><a href='#L671'>671</a>
<a name='L672'></a><a href='#L672'>672</a>
<a name='L673'></a><a href='#L673'>673</a>
<a name='L674'></a><a href='#L674'>674</a>
<a name='L675'></a><a href='#L675'>675</a>
<a name='L676'></a><a href='#L676'>676</a>
<a name='L677'></a><a href='#L677'>677</a>
<a name='L678'></a><a href='#L678'>678</a>
<a name='L679'></a><a href='#L679'>679</a>
<a name='L680'></a><a href='#L680'>680</a>
<a name='L681'></a><a href='#L681'>681</a>
<a name='L682'></a><a href='#L682'>682</a>
<a name='L683'></a><a href='#L683'>683</a>
<a name='L684'></a><a href='#L684'>684</a>
<a name='L685'></a><a href='#L685'>685</a>
<a name='L686'></a><a href='#L686'>686</a>
<a name='L687'></a><a href='#L687'>687</a>
<a name='L688'></a><a href='#L688'>688</a>
<a name='L689'></a><a href='#L689'>689</a>
<a name='L690'></a><a href='#L690'>690</a>
<a name='L691'></a><a href='#L691'>691</a>
<a name='L692'></a><a href='#L692'>692</a>
<a name='L693'></a><a href='#L693'>693</a>
<a name='L694'></a><a href='#L694'>694</a>
<a name='L695'></a><a href='#L695'>695</a>
<a name='L696'></a><a href='#L696'>696</a>
<a name='L697'></a><a href='#L697'>697</a>
<a name='L698'></a><a href='#L698'>698</a>
<a name='L699'></a><a href='#L699'>699</a>
<a name='L700'></a><a href='#L700'>700</a>
<a name='L701'></a><a href='#L701'>701</a>
<a name='L702'></a><a href='#L702'>702</a>
<a name='L703'></a><a href='#L703'>703</a>
<a name='L704'></a><a href='#L704'>704</a>
<a name='L705'></a><a href='#L705'>705</a>
<a name='L706'></a><a href='#L706'>706</a>
<a name='L707'></a><a href='#L707'>707</a>
<a name='L708'></a><a href='#L708'>708</a></td><td class="line-coverage quiet"><span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js"><span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" ><span class="branch-0 cbranch-no" title="branch not covered" >import { useState, useEffect, useRef, useCallback, useMemo, type MutableRefObject, type RefObject, type CSSProperties } from 'react';</span></span></span>
import { motion, AnimatePresence } from 'framer-motion';
import { List, type ListImperativeAPI } from 'react-window';
import { useChatStore, Message } from '../store/chatStore';
import { useConnectionStore } from '../store/connectionStore';
import { useAgentStore } from '../store/agentStore';
import { useConfigStore } from '../store/configStore';
import { Paperclip, ChevronDown, Terminal, SquarePen, ArrowUp, MessageSquare, Download, Copy, Check } from 'lucide-react';
import { Button, EmptyState, MessageListSkeleton, LoadingDots } from './ui';
import { listItemVariants, defaultTransition, fadeInVariants } from '../lib/animations';
import { FirstConversationPrompt } from './FirstConversationPrompt';
import { MessageSearch } from './MessageSearch';
import { OfflineIndicator } from './OfflineIndicator';
import {
useVirtualizedMessages,
type VirtualizedMessageItem
} from '../lib/message-virtualization';
&nbsp;
// Default heights for virtualized messages
<span class="cstat-no" title="statement not covered" >const DEFAULT_MESSAGE_HEIGHTS: Record&lt;string, number&gt; = {</span>
<span class="cstat-no" title="statement not covered" > user: 80,</span>
<span class="cstat-no" title="statement not covered" > assistant: 150,</span>
<span class="cstat-no" title="statement not covered" > tool: 120,</span>
<span class="cstat-no" title="statement not covered" > hand: 120,</span>
<span class="cstat-no" title="statement not covered" > workflow: 100,</span>
<span class="cstat-no" title="statement not covered" > system: 60,</span>
<span class="cstat-no" title="statement not covered" >};</span>
&nbsp;
// Threshold for enabling virtualization (messages count)
<span class="cstat-no" title="statement not covered" >const VIRTUALIZATION_THRESHOLD = 100;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >export function ChatArea() {</span>
<span class="cstat-no" title="statement not covered" > const {</span>
<span class="cstat-no" title="statement not covered" > messages, currentAgent, isStreaming, isLoading, currentModel,</span>
<span class="cstat-no" title="statement not covered" > sendMessage: sendToGateway, setCurrentModel, initStreamListener,</span>
<span class="cstat-no" title="statement not covered" > newConversation,</span>
<span class="cstat-no" title="statement not covered" > } = useChatStore();</span>
<span class="cstat-no" title="statement not covered" > const connectionState = useConnectionStore((s) =&gt; s.connectionState);</span>
<span class="cstat-no" title="statement not covered" > const clones = useAgentStore((s) =&gt; s.clones);</span>
<span class="cstat-no" title="statement not covered" > const models = useConfigStore((s) =&gt; s.models);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const [input, setInput] = useState('');</span>
<span class="cstat-no" title="statement not covered" > const [showModelPicker, setShowModelPicker] = useState(false);</span>
<span class="cstat-no" title="statement not covered" > const scrollRef = useRef&lt;HTMLDivElement&gt;(null);</span>
<span class="cstat-no" title="statement not covered" > const textareaRef = useRef&lt;HTMLTextAreaElement&gt;(null);</span>
<span class="cstat-no" title="statement not covered" > const messageRefs = useRef&lt;Map&lt;string, HTMLDivElement&gt;&gt;(new Map());</span>
&nbsp;
// Convert messages to virtualization format
<span class="cstat-no" title="statement not covered" > const virtualizedMessages: VirtualizedMessageItem[] = useMemo(</span>
<span class="cstat-no" title="statement not covered" > () =&gt; messages.map((msg) =&gt; ({</span>
<span class="cstat-no" title="statement not covered" > id: msg.id,</span>
<span class="cstat-no" title="statement not covered" > height: DEFAULT_MESSAGE_HEIGHTS[msg.role] ?? 100,</span>
<span class="cstat-no" title="statement not covered" > role: msg.role,</span>
<span class="cstat-no" title="statement not covered" > })),</span>
<span class="cstat-no" title="statement not covered" > [messages]</span>
<span class="cstat-no" title="statement not covered" > );</span>
&nbsp;
// Use virtualization hook
<span class="cstat-no" title="statement not covered" > const {</span>
<span class="cstat-no" title="statement not covered" > listRef,</span>
<span class="cstat-no" title="statement not covered" > getHeight,</span>
<span class="cstat-no" title="statement not covered" > setHeight,</span>
<span class="cstat-no" title="statement not covered" > scrollToBottom,</span>
<span class="cstat-no" title="statement not covered" > } = useVirtualizedMessages(virtualizedMessages, DEFAULT_MESSAGE_HEIGHTS);</span>
&nbsp;
// Whether to use virtualization
<span class="cstat-no" title="statement not covered" > const useVirtualization = messages.length &gt;= VIRTUALIZATION_THRESHOLD;</span>
&nbsp;
// Get current clone for first conversation prompt
<span class="cstat-no" title="statement not covered" > const currentClone = useMemo(() =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (!currentAgent) return null;</span>
<span class="cstat-no" title="statement not covered" > return clones.find((c) =&gt; c.id === currentAgent.id) || null;</span>
<span class="cstat-no" title="statement not covered" > }, [currentAgent, clones]);</span>
&nbsp;
// Check if should show first conversation prompt
<span class="cstat-no" title="statement not covered" > const showFirstPrompt = messages.length === 0 &amp;&amp; currentClone &amp;&amp; !currentClone.onboardingCompleted;</span>
&nbsp;
// Handle suggestion click from first conversation prompt
<span class="cstat-no" title="statement not covered" > const handleSelectSuggestion = (text: string) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > setInput(text);</span>
<span class="cstat-no" title="statement not covered" > textareaRef.current?.focus();</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
// Auto-resize textarea
<span class="cstat-no" title="statement not covered" > const adjustTextarea = useCallback(() =&gt; {</span>
<span class="cstat-no" title="statement not covered" > const el = textareaRef.current;</span>
<span class="cstat-no" title="statement not covered" > if (el) {</span>
<span class="cstat-no" title="statement not covered" > el.style.height = 'auto';</span>
<span class="cstat-no" title="statement not covered" > el.style.height = Math.min(el.scrollHeight, 160) + 'px';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }, []);</span>
&nbsp;
// Init agent stream listener on mount
<span class="cstat-no" title="statement not covered" > useEffect(() =&gt; {</span>
<span class="cstat-no" title="statement not covered" > const unsub = initStreamListener();</span>
<span class="cstat-no" title="statement not covered" > return unsub;</span>
<span class="cstat-no" title="statement not covered" > }, []);</span>
&nbsp;
// Auto-scroll to bottom on new messages
<span class="cstat-no" title="statement not covered" > useEffect(() =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (scrollRef.current &amp;&amp; !useVirtualization) {</span>
<span class="cstat-no" title="statement not covered" > scrollRef.current.scrollTop = scrollRef.current.scrollHeight;</span>
<span class="cstat-no" title="statement not covered" > } else if (useVirtualization &amp;&amp; messages.length &gt; 0) {</span>
<span class="cstat-no" title="statement not covered" > scrollToBottom();</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }, [messages, useVirtualization, scrollToBottom]);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const handleSend = () =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (!input.trim() || isStreaming) return;</span>
// Allow sending in offline mode - message will be queued
<span class="cstat-no" title="statement not covered" > sendToGateway(input);</span>
<span class="cstat-no" title="statement not covered" > setInput('');</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const handleKeyDown = (e: React.KeyboardEvent) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (e.key === 'Enter' &amp;&amp; !e.shiftKey) {</span>
<span class="cstat-no" title="statement not covered" > e.preventDefault();</span>
<span class="cstat-no" title="statement not covered" > handleSend();</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const connected = connectionState === 'connected';</span>
&nbsp;
// Navigate to a specific message by ID
<span class="cstat-no" title="statement not covered" > const handleNavigateToMessage = useCallback((messageId: string) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > const messageEl = messageRefs.current.get(messageId);</span>
<span class="cstat-no" title="statement not covered" > if (messageEl &amp;&amp; scrollRef.current) {</span>
<span class="cstat-no" title="statement not covered" > messageEl.scrollIntoView({ behavior: 'smooth', block: 'center' });</span>
// Add highlight effect
<span class="cstat-no" title="statement not covered" > messageEl.classList.add('ring-2', 'ring-orange-400', 'ring-offset-2');</span>
<span class="cstat-no" title="statement not covered" > setTimeout(() =&gt; {</span>
<span class="cstat-no" title="statement not covered" > messageEl.classList.remove('ring-2', 'ring-orange-400', 'ring-offset-2');</span>
<span class="cstat-no" title="statement not covered" > }, 2000);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }, []);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return (</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="flex flex-col h-full"&gt;</span>
{/* Header */}
{/* Header */}
<span class="cstat-no" title="statement not covered" > &lt;div className="h-14 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 flex-shrink-0 bg-white dark:bg-gray-900"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="flex items-center gap-2"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;h2 className="font-semibold text-gray-900 dark:text-gray-100"&gt;{currentAgent?.name || 'ZCLAW'}&lt;/h2&gt;</span>
<span class="cstat-no" title="statement not covered" > {isStreaming ? (</span>
<span class="cstat-no" title="statement not covered" > &lt;span className="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;span className="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full thinking-dot"&gt;&lt;/span&gt;</span>
正在输入中
<span class="cstat-no" title="statement not covered" > &lt;/span&gt;</span>
) : (
<span class="cstat-no" title="statement not covered" > &lt;span className={`text-xs flex items-center gap-1 ${connected ? 'text-green-500' : 'text-gray-500 dark:text-gray-400'}`}&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;span className={`w-1.5 h-1.5 rounded-full ${connected ? 'bg-green-400' : 'bg-gray-300 dark:bg-gray-600'}`}&gt;&lt;/span&gt;</span>
<span class="cstat-no" title="statement not covered" > {connected ? 'Gateway 已连接' : 'Gateway 未连接'}</span>
<span class="cstat-no" title="statement not covered" > &lt;/span&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="flex items-center gap-2"&gt;</span>
{/* Offline indicator in header */}
<span class="cstat-no" title="statement not covered" > &lt;OfflineIndicator compact /&gt;</span>
<span class="cstat-no" title="statement not covered" > {messages.length &gt; 0 &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;MessageSearch onNavigateToMessage={handleNavigateToMessage} /&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > {messages.length &gt; 0 &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;Button</span>
<span class="cstat-no" title="statement not covered" > variant="ghost"</span>
<span class="cstat-no" title="statement not covered" > size="sm"</span>
<span class="cstat-no" title="statement not covered" > onClick={newConversation}</span>
<span class="cstat-no" title="statement not covered" > title="新对话"</span>
<span class="cstat-no" title="statement not covered" > aria-label="开始新对话"</span>
<span class="cstat-no" title="statement not covered" > className="flex items-center gap-1.5 text-gray-500 dark:text-gray-400 hover:text-orange-600 dark:hover:text-orange-400 hover:bg-orange-50 dark:hover:bg-orange-900/20"</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;SquarePen className="w-3.5 h-3.5" /&gt;</span>
新对话
<span class="cstat-no" title="statement not covered" > &lt;/Button&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
&nbsp;
{/* Messages */}
<span class="cstat-no" title="statement not covered" > &lt;div ref={scrollRef} className="flex-1 overflow-y-auto custom-scrollbar bg-white dark:bg-gray-900"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;AnimatePresence mode="popLayout"&gt;</span>
{/* Loading skeleton */}
<span class="cstat-no" title="statement not covered" > {isLoading &amp;&amp; messages.length === 0 &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;motion.div</span>
<span class="cstat-no" title="statement not covered" > key="loading-skeleton"</span>
<span class="cstat-no" title="statement not covered" > variants={fadeInVariants}</span>
<span class="cstat-no" title="statement not covered" > initial="initial"</span>
<span class="cstat-no" title="statement not covered" > animate="animate"</span>
<span class="cstat-no" title="statement not covered" > exit="exit"</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;MessageListSkeleton count={3} /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/motion.div&gt;</span>
)}
&nbsp;
{/* Empty state */}
<span class="cstat-no" title="statement not covered" > {!isLoading &amp;&amp; messages.length === 0 &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;motion.div</span>
<span class="cstat-no" title="statement not covered" > key="empty-state"</span>
<span class="cstat-no" title="statement not covered" > variants={fadeInVariants}</span>
<span class="cstat-no" title="statement not covered" > initial="initial"</span>
<span class="cstat-no" title="statement not covered" > animate="animate"</span>
<span class="cstat-no" title="statement not covered" > exit="exit"</span>
&gt;
<span class="cstat-no" title="statement not covered" > {showFirstPrompt &amp;&amp; currentClone ? (</span>
<span class="cstat-no" title="statement not covered" > &lt;FirstConversationPrompt</span>
<span class="cstat-no" title="statement not covered" > clone={currentClone}</span>
<span class="cstat-no" title="statement not covered" > onSelectSuggestion={handleSelectSuggestion}</span>
<span class="cstat-no" title="statement not covered" > /&gt;</span>
) : (
<span class="cstat-no" title="statement not covered" > &lt;EmptyState</span>
<span class="cstat-no" title="statement not covered" > icon={&lt;MessageSquare className="w-8 h-8" /&gt;}</span>
<span class="cstat-no" title="statement not covered" > title="Welcome to ZCLAW"</span>
<span class="cstat-no" title="statement not covered" > description={connected ? 'Send a message to start the conversation.' : 'Please connect to Gateway first in Settings.'}</span>
<span class="cstat-no" title="statement not covered" > /&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/motion.div&gt;</span>
)}
&nbsp;
{/* Virtualized list for large message counts, smooth scroll for small counts */}
<span class="cstat-no" title="statement not covered" > {useVirtualization &amp;&amp; messages.length &gt; 0 ? (</span>
<span class="cstat-no" title="statement not covered" > &lt;VirtualizedMessageList</span>
<span class="cstat-no" title="statement not covered" > messages={messages}</span>
<span class="cstat-no" title="statement not covered" > listRef={listRef}</span>
<span class="cstat-no" title="statement not covered" > getHeight={getHeight}</span>
<span class="cstat-no" title="statement not covered" > onHeightChange={setHeight}</span>
<span class="cstat-no" title="statement not covered" > messageRefs={messageRefs}</span>
<span class="cstat-no" title="statement not covered" > /&gt;</span>
) : (
<span class="cstat-no" title="statement not covered" > messages.map((message) =&gt; (</span>
<span class="cstat-no" title="statement not covered" > &lt;motion.div</span>
<span class="cstat-no" title="statement not covered" > key={message.id}</span>
<span class="cstat-no" title="statement not covered" > ref={(el) =&gt; { if (el) messageRefs.current.set(message.id, el); }}</span>
<span class="cstat-no" title="statement not covered" > variants={listItemVariants}</span>
<span class="cstat-no" title="statement not covered" > initial="hidden"</span>
<span class="cstat-no" title="statement not covered" > animate="visible"</span>
<span class="cstat-no" title="statement not covered" > layout</span>
<span class="cstat-no" title="statement not covered" > transition={defaultTransition}</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;MessageBubble message={message} /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/motion.div&gt;</span>
<span class="cstat-no" title="statement not covered" > ))</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/AnimatePresence&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
&nbsp;
{/* Input */}
<span class="cstat-no" title="statement not covered" > &lt;div className="border-t border-gray-100 dark:border-gray-800 p-4 bg-white dark:bg-gray-900"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="max-w-4xl mx-auto"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="relative flex items-end gap-2 bg-gray-50 dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 p-2 focus-within:border-orange-300 dark:focus-within:border-orange-600 focus-within:ring-2 focus-within:ring-orange-100 dark:focus-within:ring-orange-900/30 transition-all"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;Button</span>
<span class="cstat-no" title="statement not covered" > variant="ghost"</span>
<span class="cstat-no" title="statement not covered" > size="sm"</span>
<span class="cstat-no" title="statement not covered" > className="p-2 text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"</span>
<span class="cstat-no" title="statement not covered" > aria-label="添加附件"</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;Paperclip className="w-5 h-5" /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/Button&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="flex-1 py-1"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;textarea</span>
<span class="cstat-no" title="statement not covered" > ref={textareaRef}</span>
<span class="cstat-no" title="statement not covered" > value={input}</span>
<span class="cstat-no" title="statement not covered" > onChange={(e) =&gt; { setInput(e.target.value); adjustTextarea(); }}</span>
<span class="cstat-no" title="statement not covered" > onKeyDown={handleKeyDown}</span>
<span class="cstat-no" title="statement not covered" > placeholder={</span>
<span class="cstat-no" title="statement not covered" > isStreaming</span>
<span class="cstat-no" title="statement not covered" > ? 'Agent 正在回复...'</span>
<span class="cstat-no" title="statement not covered" > : `发送给 ${currentAgent?.name || 'ZCLAW'}${!connected ? ' (离线模式)' : ''}`</span>
}
<span class="cstat-no" title="statement not covered" > disabled={isStreaming}</span>
<span class="cstat-no" title="statement not covered" > rows={1}</span>
<span class="cstat-no" title="statement not covered" > className="w-full bg-transparent border-none focus:outline-none text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 disabled:opacity-50 resize-none leading-relaxed mt-1"</span>
<span class="cstat-no" title="statement not covered" > style={{ minHeight: '24px', maxHeight: '160px' }}</span>
<span class="cstat-no" title="statement not covered" > /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="flex items-center gap-2 pr-2 pb-1 relative"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;Button</span>
<span class="cstat-no" title="statement not covered" > variant="ghost"</span>
<span class="cstat-no" title="statement not covered" > size="sm"</span>
<span class="cstat-no" title="statement not covered" > onClick={() =&gt; setShowModelPicker(!showModelPicker)}</span>
<span class="cstat-no" title="statement not covered" > className="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700"</span>
<span class="cstat-no" title="statement not covered" > aria-label="选择模型"</span>
<span class="cstat-no" title="statement not covered" > aria-expanded={showModelPicker}</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;span&gt;{currentModel}&lt;/span&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;ChevronDown className="w-3 h-3" /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/Button&gt;</span>
<span class="cstat-no" title="statement not covered" > {showModelPicker &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="absolute bottom-full right-8 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 min-w-[160px] max-h-48 overflow-y-auto z-10"&gt;</span>
<span class="cstat-no" title="statement not covered" > {models.length &gt; 0 ? (</span>
<span class="cstat-no" title="statement not covered" > models.map((model) =&gt; (</span>
<span class="cstat-no" title="statement not covered" > &lt;button</span>
<span class="cstat-no" title="statement not covered" > key={model.id}</span>
<span class="cstat-no" title="statement not covered" > onClick={() =&gt; { setCurrentModel(model.id); setShowModelPicker(false); }}</span>
<span class="cstat-no" title="statement not covered" > className={`w-full text-left px-3 py-2 text-xs hover:bg-gray-50 dark:hover:bg-gray-700 ${model.id === currentModel ? 'text-orange-600 dark:text-orange-400 font-medium' : 'text-gray-700 dark:text-gray-300'}`}</span>
&gt;
<span class="cstat-no" title="statement not covered" > {model.name}</span>
<span class="cstat-no" title="statement not covered" > &lt;/button&gt;</span>
<span class="cstat-no" title="statement not covered" > ))</span>
) : (
<span class="cstat-no" title="statement not covered" > &lt;div className="px-3 py-2 text-xs text-gray-400"&gt;</span>
<span class="cstat-no" title="statement not covered" > {connected ? '加载中...' : '未连接 Gateway'}</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;Button</span>
<span class="cstat-no" title="statement not covered" > variant="primary"</span>
<span class="cstat-no" title="statement not covered" > size="sm"</span>
<span class="cstat-no" title="statement not covered" > onClick={handleSend}</span>
<span class="cstat-no" title="statement not covered" > disabled={isStreaming || !input.trim()}</span>
<span class="cstat-no" title="statement not covered" > className="w-8 h-8 rounded-full p-0 flex items-center justify-center bg-orange-500 hover:bg-orange-600 text-white disabled:opacity-50"</span>
<span class="cstat-no" title="statement not covered" > aria-label="发送消息"</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;ArrowUp className="w-4 h-4 text-white" /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/Button&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="text-center mt-2 text-xs text-gray-500 dark:text-gray-400"&gt;</span>
Agent 在本地运行,内容由 AI 生成
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
);
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
/** Code block with copy and download functionality */
<span class="cstat-no" title="statement not covered" >function CodeBlock({ code, language, index }: { code: string; language: string; index: number }) {</span>
<span class="cstat-no" title="statement not covered" > const [copied, setCopied] = useState(false);</span>
<span class="cstat-no" title="statement not covered" > const [downloading, setDownloading] = useState(false);</span>
&nbsp;
// Infer filename from language or content
<span class="cstat-no" title="statement not covered" > const inferFilename = (): string =&gt; {</span>
<span class="cstat-no" title="statement not covered" > const extMap: Record&lt;string, string&gt; = {</span>
<span class="cstat-no" title="statement not covered" > javascript: 'js', typescript: 'ts', python: 'py', rust: 'rs',</span>
<span class="cstat-no" title="statement not covered" > go: 'go', java: 'java', cpp: 'cpp', c: 'c', csharp: 'cs',</span>
<span class="cstat-no" title="statement not covered" > html: 'html', css: 'css', scss: 'scss', json: 'json',</span>
<span class="cstat-no" title="statement not covered" > yaml: 'yaml', yml: 'yaml', xml: 'xml', sql: 'sql',</span>
<span class="cstat-no" title="statement not covered" > shell: 'sh', bash: 'sh', powershell: 'ps1',</span>
<span class="cstat-no" title="statement not covered" > markdown: 'md', md: 'md', dockerfile: 'dockerfile',</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
// Check if language contains a filename (e.g., ```app.tsx)
<span class="cstat-no" title="statement not covered" > if (language.includes('.') || language.includes('/')) {</span>
<span class="cstat-no" title="statement not covered" > return language;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
// Check for common patterns in code
<span class="cstat-no" title="statement not covered" > const codeLower = code.toLowerCase();</span>
<span class="cstat-no" title="statement not covered" > if (codeLower.includes('&lt;!doctype html') || codeLower.includes('&lt;html')) {</span>
<span class="cstat-no" title="statement not covered" > return 'index.html';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > if (codeLower.includes('package.json') || (codeLower.includes('"name"') &amp;&amp; codeLower.includes('"version"'))) {</span>
<span class="cstat-no" title="statement not covered" > return 'package.json';</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > if (codeLower.startsWith('{') &amp;&amp; (codeLower.includes('"import"') || codeLower.includes('"export"'))) {</span>
<span class="cstat-no" title="statement not covered" > return 'config.json';</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
// Use language extension
<span class="cstat-no" title="statement not covered" > const ext = extMap[language.toLowerCase()] || language.toLowerCase();</span>
<span class="cstat-no" title="statement not covered" > return `code-${index + 1}.${ext || 'txt'}`;</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const handleCopy = async () =&gt; {</span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > await navigator.clipboard.writeText(code);</span>
<span class="cstat-no" title="statement not covered" > setCopied(true);</span>
<span class="cstat-no" title="statement not covered" > setTimeout(() =&gt; setCopied(false), 2000);</span>
<span class="cstat-no" title="statement not covered" > } catch (err) {</span>
<span class="cstat-no" title="statement not covered" > console.error('Failed to copy:', err);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const handleDownload = () =&gt; {</span>
<span class="cstat-no" title="statement not covered" > setDownloading(true);</span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > const filename = inferFilename();</span>
<span class="cstat-no" title="statement not covered" > const blob = new Blob([code], { type: 'text/plain;charset=utf-8' });</span>
<span class="cstat-no" title="statement not covered" > const url = URL.createObjectURL(blob);</span>
<span class="cstat-no" title="statement not covered" > const a = document.createElement('a');</span>
<span class="cstat-no" title="statement not covered" > a.href = url;</span>
<span class="cstat-no" title="statement not covered" > a.download = filename;</span>
<span class="cstat-no" title="statement not covered" > document.body.appendChild(a);</span>
<span class="cstat-no" title="statement not covered" > a.click();</span>
<span class="cstat-no" title="statement not covered" > document.body.removeChild(a);</span>
<span class="cstat-no" title="statement not covered" > URL.revokeObjectURL(url);</span>
<span class="cstat-no" title="statement not covered" > } catch (err) {</span>
<span class="cstat-no" title="statement not covered" > console.error('Failed to download:', err);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > setTimeout(() =&gt; setDownloading(false), 500);</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return (</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="relative group my-2"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;pre className="bg-gray-900 text-gray-100 rounded-lg p-3 overflow-x-auto text-xs font-mono leading-relaxed"&gt;</span>
<span class="cstat-no" title="statement not covered" > {language &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="text-gray-500 text-[10px] mb-1 uppercase flex items-center justify-between"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;span&gt;{language}&lt;/span&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;code&gt;{code}&lt;/code&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/pre&gt;</span>
{/* Action buttons - show on hover */}
<span class="cstat-no" title="statement not covered" > &lt;div className="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;button</span>
<span class="cstat-no" title="statement not covered" > onClick={handleCopy}</span>
<span class="cstat-no" title="statement not covered" > className="p-1.5 bg-gray-700 hover:bg-gray-600 rounded text-gray-300 hover:text-white transition-colors"</span>
<span class="cstat-no" title="statement not covered" > title="复制代码"</span>
&gt;
<span class="cstat-no" title="statement not covered" > {copied ? &lt;Check className="w-3.5 h-3.5 text-green-400" /&gt; : &lt;Copy className="w-3.5 h-3.5" /&gt;}</span>
<span class="cstat-no" title="statement not covered" > &lt;/button&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;button</span>
<span class="cstat-no" title="statement not covered" > onClick={handleDownload}</span>
<span class="cstat-no" title="statement not covered" > className="p-1.5 bg-gray-700 hover:bg-gray-600 rounded text-gray-300 hover:text-white transition-colors"</span>
<span class="cstat-no" title="statement not covered" > title="下载文件"</span>
<span class="cstat-no" title="statement not covered" > disabled={downloading}</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;Download className={`w-3.5 h-3.5 ${downloading ? 'animate-pulse' : ''}`} /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/button&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
);
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
/** Lightweight markdown renderer — handles code blocks, inline code, bold, italic, links */
&nbsp;
<span class="cstat-no" title="statement not covered" >function sanitizeUrl(url: string): string {</span>
<span class="cstat-no" title="statement not covered" > const safeProtocols = ['http:', 'https:', 'mailto:'];</span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > const parsed = new URL(url, window.location.origin);</span>
<span class="cstat-no" title="statement not covered" > if (safeProtocols.includes(parsed.protocol)) {</span>
<span class="cstat-no" title="statement not covered" > return parsed.href;</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > } catch {</span>
// Invalid URL
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > return '#';</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >function renderMarkdown(text: string): React.ReactNode[] {</span>
<span class="cstat-no" title="statement not covered" > const nodes: React.ReactNode[] = [];</span>
<span class="cstat-no" title="statement not covered" > const lines = text.split('\n');</span>
<span class="cstat-no" title="statement not covered" > let i = 0;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > while (i &lt; lines.length) {</span>
<span class="cstat-no" title="statement not covered" > const line = lines[i];</span>
&nbsp;
// Fenced code block
<span class="cstat-no" title="statement not covered" > if (line.startsWith('```')) {</span>
<span class="cstat-no" title="statement not covered" > const lang = line.slice(3).trim();</span>
<span class="cstat-no" title="statement not covered" > const codeLines: string[] = [];</span>
<span class="cstat-no" title="statement not covered" > i++;</span>
<span class="cstat-no" title="statement not covered" > while (i &lt; lines.length &amp;&amp; !lines[i].startsWith('```')) {</span>
<span class="cstat-no" title="statement not covered" > codeLines.push(lines[i]);</span>
<span class="cstat-no" title="statement not covered" > i++;</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > i++; // skip closing ```</span>
<span class="cstat-no" title="statement not covered" > nodes.push(</span>
<span class="cstat-no" title="statement not covered" > &lt;CodeBlock key={nodes.length} code={codeLines.join('\n')} language={lang} index={nodes.length} /&gt;</span>
<span class="cstat-no" title="statement not covered" > );</span>
<span class="cstat-no" title="statement not covered" > continue;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
// Normal line — parse inline markdown
<span class="cstat-no" title="statement not covered" > nodes.push(</span>
<span class="cstat-no" title="statement not covered" > &lt;span key={nodes.length}&gt;</span>
<span class="cstat-no" title="statement not covered" > {i &gt; 0 &amp;&amp; lines[i - 1] !== undefined &amp;&amp; !nodes[nodes.length - 1]?.toString().includes('pre') &amp;&amp; '\n'}</span>
<span class="cstat-no" title="statement not covered" > {renderInline(line)}</span>
<span class="cstat-no" title="statement not covered" > &lt;/span&gt;</span>
<span class="cstat-no" title="statement not covered" > );</span>
<span class="cstat-no" title="statement not covered" > i++;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return nodes;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >function renderInline(text: string): React.ReactNode[] {</span>
<span class="cstat-no" title="statement not covered" > const parts: React.ReactNode[] = [];</span>
// Pattern: **bold**, *italic*, `code`, [text](url)
<span class="cstat-no" title="statement not covered" > const regex = /(\*\*(.+?)\*\*)|(\*(.+?)\*)|(`(.+?)`)|(\[(.+?)\]\((.+?)\))/g;</span>
<span class="cstat-no" title="statement not covered" > let lastIndex = 0;</span>
<span class="cstat-no" title="statement not covered" > let match: RegExpExecArray | null;</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > while ((match = regex.exec(text)) !== null) {</span>
// Text before match
<span class="cstat-no" title="statement not covered" > if (match.index &gt; lastIndex) {</span>
<span class="cstat-no" title="statement not covered" > parts.push(text.slice(lastIndex, match.index));</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (match[1]) {</span>
// **bold**
<span class="cstat-no" title="statement not covered" > parts.push(&lt;strong key={parts.length} className="font-semibold"&gt;{match[2]}&lt;/strong&gt;);</span>
<span class="cstat-no" title="statement not covered" > } else if (match[3]) {</span>
// *italic*
<span class="cstat-no" title="statement not covered" > parts.push(&lt;em key={parts.length}&gt;{match[4]}&lt;/em&gt;);</span>
<span class="cstat-no" title="statement not covered" > } else if (match[5]) {</span>
// `code`
<span class="cstat-no" title="statement not covered" > parts.push(</span>
<span class="cstat-no" title="statement not covered" > &lt;code key={parts.length} className="bg-gray-100 dark:bg-gray-700 text-orange-700 dark:text-orange-400 px-1 py-0.5 rounded text-[0.85em] font-mono"&gt;</span>
<span class="cstat-no" title="statement not covered" > {match[6]}</span>
<span class="cstat-no" title="statement not covered" > &lt;/code&gt;</span>
<span class="cstat-no" title="statement not covered" > );</span>
<span class="cstat-no" title="statement not covered" > } else if (match[7]) {</span>
// [text](url) - 使用 sanitizeUrl 防止 XSS
<span class="cstat-no" title="statement not covered" > parts.push(</span>
<span class="cstat-no" title="statement not covered" > &lt;a key={parts.length} href={sanitizeUrl(match[9])} target="_blank" rel="noopener noreferrer"</span>
<span class="cstat-no" title="statement not covered" > className="text-orange-600 dark:text-orange-400 underline hover:text-orange-700 dark:hover:text-orange-300"&gt;{match[8]}&lt;/a&gt;</span>
<span class="cstat-no" title="statement not covered" > );</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > lastIndex = match.index + match[0].length;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > if (lastIndex &lt; text.length) {</span>
<span class="cstat-no" title="statement not covered" > parts.push(text.slice(lastIndex));</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return parts.length &gt; 0 ? parts : [text];</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" >function MessageBubble({ message }: { message: Message }) {</span>
<span class="cstat-no" title="statement not covered" > if (message.role === 'tool') {</span>
<span class="cstat-no" title="statement not covered" > return (</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="ml-12 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className="flex items-center gap-2 text-gray-500 dark:text-gray-400 mb-1"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;Terminal className="w-3.5 h-3.5" /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;span className="font-semibold"&gt;{message.toolName || 'tool'}&lt;/span&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > {message.toolInput &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;pre className="text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-900 rounded p-2 mb-1 overflow-x-auto"&gt;{message.toolInput}&lt;/pre&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > {message.content &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;pre className="text-green-700 dark:text-green-400 bg-white dark:bg-gray-900 rounded p-2 overflow-x-auto"&gt;{message.content}&lt;/pre&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
);
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > const isUser = message.role === 'user';</span>
&nbsp;
// 思考中状态streaming 且内容为空时显示思考指示器
<span class="cstat-no" title="statement not covered" > const isThinking = message.streaming &amp;&amp; !message.content;</span>
&nbsp;
// Download message as Markdown file
<span class="cstat-no" title="statement not covered" > const handleDownloadMessage = () =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (!message.content) return;</span>
<span class="cstat-no" title="statement not covered" > const timestamp = new Date().toISOString().slice(0, 10);</span>
<span class="cstat-no" title="statement not covered" > const filename = `message-${timestamp}.md`;</span>
<span class="cstat-no" title="statement not covered" > const blob = new Blob([message.content], { type: 'text/markdown;charset=utf-8' });</span>
<span class="cstat-no" title="statement not covered" > const url = URL.createObjectURL(blob);</span>
<span class="cstat-no" title="statement not covered" > const a = document.createElement('a');</span>
<span class="cstat-no" title="statement not covered" > a.href = url;</span>
<span class="cstat-no" title="statement not covered" > a.download = filename;</span>
<span class="cstat-no" title="statement not covered" > document.body.appendChild(a);</span>
<span class="cstat-no" title="statement not covered" > a.click();</span>
<span class="cstat-no" title="statement not covered" > document.body.removeChild(a);</span>
<span class="cstat-no" title="statement not covered" > URL.revokeObjectURL(url);</span>
<span class="cstat-no" title="statement not covered" > };</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return (</span>
<span class="cstat-no" title="statement not covered" > &lt;div className={`flex gap-4 ${isUser ? 'justify-end' : ''}`}&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div</span>
<span class="cstat-no" title="statement not covered" > className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${isUser ? 'bg-gray-200 dark:bg-gray-600 text-gray-600 dark:text-gray-200 order-last' : 'agent-avatar text-white'}`}</span>
&gt;
<span class="cstat-no" title="statement not covered" > {isUser ? '用' : 'Z'}</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className={isUser ? 'max-w-2xl' : 'flex-1 max-w-3xl'}&gt;</span>
<span class="cstat-no" title="statement not covered" > {isThinking ? (</span>
// Thinking indicator
<span class="cstat-no" title="statement not covered" > &lt;div className="flex items-center gap-2 px-4 py-3 text-gray-500 dark:text-gray-400"&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;LoadingDots /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;span className="text-sm"&gt;Thinking...&lt;/span&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
) : (
<span class="cstat-no" title="statement not covered" > &lt;div className={`p-4 shadow-sm ${isUser ? 'chat-bubble-user shadow-md' : 'chat-bubble-assistant'} relative group`}&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;div className={`leading-relaxed whitespace-pre-wrap ${isUser ? 'text-white' : 'text-gray-700 dark:text-gray-200'}`}&gt;</span>
<span class="cstat-no" title="statement not covered" > {message.content</span>
<span class="cstat-no" title="statement not covered" > ? (isUser ? message.content : renderMarkdown(message.content))</span>
<span class="cstat-no" title="statement not covered" > : '...'}</span>
<span class="cstat-no" title="statement not covered" > {message.streaming &amp;&amp; &lt;span className="inline-block w-1.5 h-4 bg-orange-500 animate-pulse ml-0.5 align-text-bottom rounded-sm" /&gt;}</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > {message.error &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;p className="text-xs text-red-500 mt-2"&gt;{message.error}&lt;/p&gt;</span>
)}
{/* Download button for AI messages - show on hover */}
<span class="cstat-no" title="statement not covered" > {!isUser &amp;&amp; message.content &amp;&amp; !message.streaming &amp;&amp; (</span>
<span class="cstat-no" title="statement not covered" > &lt;button</span>
<span class="cstat-no" title="statement not covered" > onClick={handleDownloadMessage}</span>
<span class="cstat-no" title="statement not covered" > className="absolute top-2 right-2 p-1.5 bg-gray-200/80 dark:bg-gray-700/80 hover:bg-gray-300 dark:hover:bg-gray-600 rounded text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors opacity-0 group-hover:opacity-100"</span>
<span class="cstat-no" title="statement not covered" > title="下载为 Markdown"</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;Download className="w-3.5 h-3.5" /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/button&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
)}
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
);
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
// === Virtualized Message Components ===
&nbsp;
interface VirtualizedMessageRowProps {
message: Message;
onHeightChange: (height: number) =&gt; void;
messageRefs: MutableRefObject&lt;Map&lt;string, HTMLDivElement&gt;&gt;;
}
&nbsp;
/**
* Single row in the virtualized list.
* Measures actual height after render and reports back.
*/
<span class="cstat-no" title="statement not covered" >function VirtualizedMessageRow({</span>
<span class="cstat-no" title="statement not covered" > message,</span>
<span class="cstat-no" title="statement not covered" > onHeightChange,</span>
<span class="cstat-no" title="statement not covered" > messageRefs,</span>
<span class="cstat-no" title="statement not covered" > style,</span>
<span class="cstat-no" title="statement not covered" > ariaAttributes,</span>
<span class="cstat-no" title="statement not covered" >}: VirtualizedMessageRowProps &amp; {</span>
style: CSSProperties;
ariaAttributes: {
'aria-posinset': number;
'aria-setsize': number;
role: 'listitem';
};
<span class="cstat-no" title="statement not covered" >}) {</span>
<span class="cstat-no" title="statement not covered" > const rowRef = useRef&lt;HTMLDivElement&gt;(null);</span>
&nbsp;
// Measure height after mount
<span class="cstat-no" title="statement not covered" > useEffect(() =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (rowRef.current) {</span>
<span class="cstat-no" title="statement not covered" > const height = rowRef.current.getBoundingClientRect().height;</span>
<span class="cstat-no" title="statement not covered" > if (height &gt; 0) {</span>
<span class="cstat-no" title="statement not covered" > onHeightChange(height);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }, [message.content, message.streaming, onHeightChange]);</span>
&nbsp;
<span class="cstat-no" title="statement not covered" > return (</span>
<span class="cstat-no" title="statement not covered" > &lt;div</span>
<span class="cstat-no" title="statement not covered" > ref={(el) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > if (el) {</span>
<span class="cstat-no" title="statement not covered" > (rowRef as MutableRefObject&lt;HTMLDivElement | null&gt;).current = el;</span>
<span class="cstat-no" title="statement not covered" > messageRefs.current.set(message.id, el);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }}</span>
<span class="cstat-no" title="statement not covered" > style={style}</span>
<span class="cstat-no" title="statement not covered" > className="py-3"</span>
<span class="cstat-no" title="statement not covered" > {...ariaAttributes}</span>
&gt;
<span class="cstat-no" title="statement not covered" > &lt;MessageBubble message={message} /&gt;</span>
<span class="cstat-no" title="statement not covered" > &lt;/div&gt;</span>
);
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
interface VirtualizedMessageListProps {
messages: Message[];
listRef: RefObject&lt;ListImperativeAPI | null&gt;;
getHeight: (id: string, role: string) =&gt; number;
onHeightChange: (id: string, height: number) =&gt; void;
messageRefs: MutableRefObject&lt;Map&lt;string, HTMLDivElement&gt;&gt;;
}
&nbsp;
/**
* Virtualized message list for efficient rendering of large message counts.
* Uses react-window's List with dynamic height measurement.
*/
<span class="cstat-no" title="statement not covered" >function VirtualizedMessageList({</span>
<span class="cstat-no" title="statement not covered" > messages,</span>
<span class="cstat-no" title="statement not covered" > listRef,</span>
<span class="cstat-no" title="statement not covered" > getHeight,</span>
<span class="cstat-no" title="statement not covered" > onHeightChange,</span>
<span class="cstat-no" title="statement not covered" > messageRefs,</span>
<span class="cstat-no" title="statement not covered" >}: VirtualizedMessageListProps) {</span>
// Row component for react-window v2
<span class="cstat-no" title="statement not covered" > const RowComponent = (props: {</span>
ariaAttributes: {
'aria-posinset': number;
'aria-setsize': number;
role: 'listitem';
};
index: number;
style: CSSProperties;
}) =&gt; (
<span class="cstat-no" title="statement not covered" > &lt;VirtualizedMessageRow</span>
<span class="cstat-no" title="statement not covered" > message={messages[props.index]}</span>
<span class="cstat-no" title="statement not covered" > onHeightChange={(h) =&gt; onHeightChange(messages[props.index].id, h)}</span>
<span class="cstat-no" title="statement not covered" > messageRefs={messageRefs}</span>
<span class="cstat-no" title="statement not covered" > style={props.style}</span>
<span class="cstat-no" title="statement not covered" > ariaAttributes={props.ariaAttributes}</span>
<span class="cstat-no" title="statement not covered" > /&gt;</span>
);
&nbsp;
<span class="cstat-no" title="statement not covered" > return (</span>
<span class="cstat-no" title="statement not covered" > &lt;List</span>
<span class="cstat-no" title="statement not covered" > listRef={listRef}</span>
<span class="cstat-no" title="statement not covered" > rowComponent={RowComponent}</span>
<span class="cstat-no" title="statement not covered" > rowProps={{}}</span>
<span class="cstat-no" title="statement not covered" > rowHeight={(index: number) =&gt; getHeight(messages[index].id, messages[index].role)}</span>
<span class="cstat-no" title="statement not covered" > rowCount={messages.length}</span>
<span class="cstat-no" title="statement not covered" > defaultHeight={500}</span>
<span class="cstat-no" title="statement not covered" > overscanCount={5}</span>
<span class="cstat-no" title="statement not covered" > className="focus:outline-none"</span>
<span class="cstat-no" title="statement not covered" > /&gt;</span>
);
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-25T14:02:06.489Z
</div>
<script src="../../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../../sorter.js"></script>
<script src="../../block-navigation.js"></script>
</body>
</html>