@huaweidevtools/aishell-dsh-brand 0.0.1-beta.2 → 0.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -25,9 +25,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  //#endregion
27
27
  const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
28
+ const react = __toESM(require("react"));
29
+ const react_dom = __toESM(require("react-dom"));
28
30
 
29
31
  //#region \0dsh-css:/home/eushing/opensource/aishell-dsh-plugins/packages/aishell-brand/src/client/brand.module.css.mjs
30
- const css = ".NP1U9q_mark{background:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDQgNDQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI0NC4wMDAwMDAiIGhlaWdodD0iNDQuMDAwMDAwIiBmaWxsPSJub25lIj4KCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnRfbGluZWFyXzAiIHgxPSIzOC41IiB4Mj0iMi4xOTk5OTY5NSIgeTE9IjEuOTA3MzQ4NjNlLTA2IiB5Mj0iMzkuNTk5OTkwOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSJyZ2IoNTMsMTM3LDI1NSkiIG9mZnNldD0iMCIgc3RvcC1vcGFjaXR5PSIxIiAvPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSJyZ2IoMTY5LDM4LDI1NSkiIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIxIiAvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8cmVjdCBpZD0iaWNvbl9BSVNoZWxsIiB3aWR0aD0iNDQuMDAwMDAwIiBoZWlnaHQ9IjQ0LjAwMDAwMCIgeD0iMC4wMDAwMDAiIHk9IjAuMDAwMDAwIiBmaWxsPSJyZ2IoMjU1LDI1NSwyNTUpIiBmaWxsLW9wYWNpdHk9IjAiIC8+Cgk8cGF0aCBpZD0i5ZCI5bm2IiBkPSJNMzcuNDU3NiAwLjI5OTczOEMzNy4zNzc1IDAuMTM5ODc3IDM3LjI4NjEgMC4wNDI4MTk2IDM3LjE4MzIgMC4wMDg1NjM5M0MzNy4wOTE3IC0wLjAxNDI3MzIgMzcuMDA2IDAuMDA4NTYzOTQgMzYuOTI1OSAwLjA3NzA3NTRDMzYuODQ1OSAwLjE0NTU4NyAzNi43ODg3IDAuMjE5ODA4IDM2Ljc1NDUgMC4yOTk3MzhDMzYuNDU3MiAxLjg2NDA4IDM1LjczNjkgMy4yMTcxOCAzNC41OTM3IDQuMzU5MDRDMzMuNDYxOCA1LjQ4OTQ4IDMyLjA5NTYgNi4yMjAyNyAzMC40OTUgNi41NTE0MUMzMC4zOTIxIDYuNTc0MjQgMzAuMzE3OCA2LjY0Mjc1IDMwLjI3MiA2Ljc1Njk0QzMwLjIzNzcgNi44NzExMyAzMC4yNDM1IDYuOTczODkgMzAuMjg5MiA3LjA2NTI0QzMwLjM0NjQgNy4xNTY1OSAzMC40MTUgNy4yMDIyNiAzMC40OTUgNy4yMDIyNkMzMS4yMDc4IDcuMzUyOTYgMzEuODc3OSA3LjU4NzU5IDMyLjUwNTUgNy45MDYxOEwzMi41MDU1IDcuOTA2MkwzMi41MDU2IDcuOTA2MjNDMzMuMjU3IDguMjg3NzEgMzMuOTQ3MyA4Ljc4OTU1IDM0LjU3NjUgOS40MTE3NkMzNS43MzEyIDEwLjU0MjIgMzYuNDU3MiAxMS44ODk2IDM2Ljc1NDUgMTMuNDUzOUMzNi44MTE2IDEzLjU5MSAzNi44OTc0IDEzLjY4MjMgMzcuMDExNyAxMy43MjhDMzcuMTE0NiAxMy43NjIyIDM3LjIwNjEgMTMuNzU2NSAzNy4yODYxIDEzLjcxMDlDMzcuMzc3NSAxMy42NTM4IDM3LjQzNDcgMTMuNTY4MSAzNy40NTc2IDEzLjQ1MzlDMzcuNzg5MSAxMS44ODk2IDM4LjUxNTEgMTAuNTQyMiAzOS42MzU1IDkuNDExNzZDNDAuNzY3NCA4LjI2OTkgNDIuMTI3OSA3LjUzMzQgNDMuNzE3IDcuMjAyMjZDNDMuODE5OSA3LjE3OTQzIDQzLjkgNy4xMjIzMyA0My45NTcxIDcuMDMwOTlDNDQuMDE0MyA2LjkyODIyIDQ0LjAxNDMgNi44MzExNiA0My45NTcxIDYuNzM5ODFDNDMuOSA2LjYzNzA1IDQzLjgxOTkgNi41NzQyNCA0My43MTcgNi41NTE0MUM0Mi4xNTA3IDYuMjIwMjcgNDAuNzk1OSA1LjQ4OTQ4IDM5LjY1MjcgNC4zNTkwNEMzOC41MjA4IDMuMjE3MTggMzcuNzg5MSAxLjg2NDA4IDM3LjQ1NzYgMC4yOTk3MzhaTTQyLjYyNSAxNi40OTk5TDQyLjYyNSAzM0M0Mi42MjUgMzMuOTQ5MyA0Mi40NTcyIDM0LjgyOSA0Mi4xMjE2IDM1LjYzOTJDNDEuNzg2IDM2LjQ0OTUgNDEuMjgyNiAzNy4xOTAyIDQwLjYxMTQgMzcuODYxNEMzOS45NDAyIDM4LjUzMjYgMzkuMTk5NCAzOS4wMzYgMzguMzg5MiAzOS4zNzE2QzM3LjU3OSAzOS43MDcyIDM2LjY5OTIgMzkuODc1IDM1Ljc1IDM5Ljg3NUwxMC45OTk5IDM5Ljg3NUw4LjI0OTk1IDM5Ljg3NUM3LjMwMDczIDM5Ljg3NSA2LjQyMTAyIDM5LjcwNzIgNS42MTA4MSAzOS4zNzE2QzQuODAwNTggMzkuMDM2IDQuMDU5ODUgMzguNTMyNiAzLjM4ODYzIDM3Ljg2MTRDMi43MTc0MiAzNy4xOTAyIDIuMjE0MDIgMzYuNDQ5NCAxLjg3ODQxIDM1LjYzOTJDMS41NDI4IDM0LjgyOSAxLjM3NSAzMy45NDkzIDEuMzc1IDMzTDEuMzc1IDExQzEuMzc1IDEwLjA1MDggMS41NDI4IDkuMTcxMDYgMS44Nzg0IDguMzYwODNMMS44Nzg0MSA4LjM2MDgyQzIuMjE0MDIgNy41NTA1OSAyLjcxNzQzIDYuODA5ODcgMy4zODg2NCA2LjEzODY2QzQuMDU5ODUgNS40Njc0NSA0LjgwMDU3IDQuOTY0MDQgNS42MTA3OSA0LjYyODQzTDUuNjEwOCA0LjYyODQzQzYuNDIxMDMgNC4yOTI4MiA3LjMwMDc2IDQuMTI1MDIgOC4yNSA0LjEyNTAyTDI3LjQ5OTkgNC4xMjVDMjguMjY5OSA0LjEyNSAyOC44NzQ5IDQuNzMgMjguODc0OSA1LjVDMjguODc0OSA2LjI3IDI4LjI2OTkgNi44NzUgMjcuNDk5OSA2Ljg3NUw4LjI1IDYuODc1MDJDNy42ODA0NSA2Ljg3NTAyIDcuMTUyNjEgNi45NzU3IDYuNjY2NDggNy4xNzcwNkM2LjE4MDM0IDcuMzc4NDMgNS43MzU5MSA3LjY4MDQ3IDUuMzMzMTggOC4wODMyQzQuOTMwNDYgOC40ODU5MyA0LjYyODQxIDguOTMwMzYgNC40MjcwNSA5LjQxNjQ5QzQuMjI1NjggOS45MDI2MyA0LjEyNSAxMC40MzA1IDQuMTI1IDExTDQuMTI1IDMzQzQuMTI1IDMzLjU2OTYgNC4yMjU2OCAzNC4wOTc0IDQuNDI3MDUgMzQuNTgzNUM0LjYyODQxIDM1LjA2OTcgNC45MzA0NSAzNS41MTQxIDUuMzMzMTcgMzUuOTE2OEM1LjczNTg4IDM2LjMxOTUgNi4xODAyOCAzNi42MjE2IDYuNjY2MzkgMzYuODIyOUM3LjE1MjU2IDM3LjAyNDMgNy42ODA0MiAzNy4xMjUgOC4yNDk5OSAzNy4xMjVMMTAuOTk5OSAzNy4xMjVMMzUuNzUgMzcuMTI1QzM2LjMxOTUgMzcuMTI1IDM2Ljg0NzQgMzcuMDI0MyAzNy4zMzM1IDM2LjgyM0MzNy44MTk3IDM2LjYyMTYgMzguMjY0MSAzNi4zMTk2IDM4LjY2NjggMzUuOTE2OEMzOS4wNjk1IDM1LjUxNDEgMzkuMzcxNiAzNS4wNjk3IDM5LjU3MyAzNC41ODM1QzM5Ljc3NDMgMzQuMDk3NCAzOS44NzUgMzMuNTY5NiAzOS44NzUgMzNMMzkuODc1IDE2LjQ5OTlDMzkuODc1IDE1LjcyOTkgNDAuNDggMTUuMTI0OSA0MS4yNSAxNS4xMjQ5QzQyLjAyIDE1LjEyNDkgNDIuNjI1IDE1LjcyOTkgNDIuNjI1IDE2LjQ5OTlaTTEwLjAyNzcgMTIuNzc3N0MxMC41NzIyIDEyLjIzMzMgMTEuNDI3OCAxMi4yMzMzIDExLjk3MjMgMTIuNzc3N0wyMC4yMjIyIDIxLjAyNzdDMjAuNDkwNyAyMS4yOTYyIDIwLjYyNDkgMjEuNjIwMyAyMC42MjQ5IDIyQzIwLjYyNDkgMjIuMzc5NyAyMC40OTA3IDIyLjcwMzcgMjAuMjIyMiAyMi45NzIyTDExLjk3MjIgMzEuMjIyM0MxMS40Mjc3IDMxLjc2NjggMTAuNTcyMSAzMS43NjY4IDEwLjAyNzcgMzEuMjIyM0M5LjQ4MzE4IDMwLjY3NzkgOS40ODMxNyAyOS44MjIzIDEwLjAyNzYgMjkuMjc3OEwxNy4zMDU0IDIyTDEwLjAyNzcgMTQuNzIyM0M5LjQ4MzI1IDE0LjE3NzggOS40ODMyNiAxMy4zMjIyIDEwLjAyNzcgMTIuNzc3N1pNMjAuNjI1IDI4Ljg3NUMyMC42MjUgMjguMTA1IDIxLjIzIDI3LjUgMjIgMjcuNUwzMyAyNy41QzMzLjc3IDI3LjUgMzQuMzc1IDI4LjEwNSAzNC4zNzUgMjguODc1QzM0LjM3NSAyOS42NDUgMzMuNzcgMzAuMjUgMzMgMzAuMjVMMjIgMzAuMjVDMjEuMjMgMzAuMjUgMjAuNjI1IDI5LjY0NSAyMC42MjUgMjguODc1WiIgZmlsbD0icmdiKDAsMCwwKSIgZmlsbC1vcGFjaXR5PSIwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIC8+Cgk8cGF0aCBpZD0i5ZCI5bm2IiBkPSJNMzcuNDU3NiAwLjI5OTczOEMzNy4zNzc1IDAuMTM5ODc3IDM3LjI4NjEgMC4wNDI4MTk2IDM3LjE4MzIgMC4wMDg1NjM5M0MzNy4wOTE3IC0wLjAxNDI3MzIgMzcuMDA2IDAuMDA4NTYzOTQgMzYuOTI1OSAwLjA3NzA3NTRDMzYuODQ1OSAwLjE0NTU4NyAzNi43ODg3IDAuMjE5ODA4IDM2Ljc1NDUgMC4yOTk3MzhDMzYuNDU3MiAxLjg2NDA4IDM1LjczNjkgMy4yMTcxOCAzNC41OTM3IDQuMzU5MDRDMzMuNDYxOCA1LjQ4OTQ4IDMyLjA5NTYgNi4yMjAyNyAzMC40OTUgNi41NTE0MUMzMC4zOTIxIDYuNTc0MjQgMzAuMzE3OCA2LjY0Mjc1IDMwLjI3MiA2Ljc1Njk0QzMwLjIzNzcgNi44NzExMyAzMC4yNDM1IDYuOTczODkgMzAuMjg5MiA3LjA2NTI0QzMwLjM0NjQgNy4xNTY1OSAzMC40MTUgNy4yMDIyNiAzMC40OTUgNy4yMDIyNkMzMS4yMDc4IDcuMzUyOTYgMzEuODc3OSA3LjU4NzU5IDMyLjUwNTUgNy45MDYxOEwzMi41MDU1IDcuOTA2MkwzMi41MDU2IDcuOTA2MjNDMzMuMjU3IDguMjg3NzEgMzMuOTQ3MyA4Ljc4OTU1IDM0LjU3NjUgOS40MTE3NkMzNS43MzEyIDEwLjU0MjIgMzYuNDU3MiAxMS44ODk2IDM2Ljc1NDUgMTMuNDUzOUMzNi44MTE2IDEzLjU5MSAzNi44OTc0IDEzLjY4MjMgMzcuMDExNyAxMy43MjhDMzcuMTE0NiAxMy43NjIyIDM3LjIwNjEgMTMuNzU2NSAzNy4yODYxIDEzLjcxMDlDMzcuMzc3NSAxMy42NTM4IDM3LjQzNDcgMTMuNTY4MSAzNy40NTc2IDEzLjQ1MzlDMzcuNzg5MSAxMS44ODk2IDM4LjUxNTEgMTAuNTQyMiAzOS42MzU1IDkuNDExNzZDNDAuNzY3NCA4LjI2OTkgNDIuMTI3OSA3LjUzMzQgNDMuNzE3IDcuMjAyMjZDNDMuODE5OSA3LjE3OTQzIDQzLjkgNy4xMjIzMyA0My45NTcxIDcuMDMwOTlDNDQuMDE0MyA2LjkyODIyIDQ0LjAxNDMgNi44MzExNiA0My45NTcxIDYuNzM5ODFDNDMuOSA2LjYzNzA1IDQzLjgxOTkgNi41NzQyNCA0My43MTcgNi41NTE0MUM0Mi4xNTA3IDYuMjIwMjcgNDAuNzk1OSA1LjQ4OTQ4IDM5LjY1MjcgNC4zNTkwNEMzOC41MjA4IDMuMjE3MTggMzcuNzg5MSAxLjg2NDA4IDM3LjQ1NzYgMC4yOTk3MzhaTTQyLjYyNSAxNi40OTk5TDQyLjYyNSAzM0M0Mi42MjUgMzMuOTQ5MyA0Mi40NTcyIDM0LjgyOSA0Mi4xMjE2IDM1LjYzOTJDNDEuNzg2IDM2LjQ0OTUgNDEuMjgyNiAzNy4xOTAyIDQwLjYxMTQgMzcuODYxNEMzOS45NDAyIDM4LjUzMjYgMzkuMTk5NCAzOS4wMzYgMzguMzg5MiAzOS4zNzE2QzM3LjU3OSAzOS43MDcyIDM2LjY5OTIgMzkuODc1IDM1Ljc1IDM5Ljg3NUwxMC45OTk5IDM5Ljg3NUw4LjI0OTk1IDM5Ljg3NUM3LjMwMDczIDM5Ljg3NSA2LjQyMTAyIDM5LjcwNzIgNS42MTA4MSAzOS4zNzE2QzQuODAwNTggMzkuMDM2IDQuMDU5ODUgMzguNTMyNiAzLjM4ODYzIDM3Ljg2MTRDMi43MTc0MiAzNy4xOTAyIDIuMjE0MDIgMzYuNDQ5NCAxLjg3ODQxIDM1LjYzOTJDMS41NDI4IDM0LjgyOSAxLjM3NSAzMy45NDkzIDEuMzc1IDMzTDEuMzc1IDExQzEuMzc1IDEwLjA1MDggMS41NDI4IDkuMTcxMDYgMS44Nzg0IDguMzYwODNMMS44Nzg0MSA4LjM2MDgyQzIuMjE0MDIgNy41NTA1OSAyLjcxNzQzIDYuODA5ODcgMy4zODg2NCA2LjEzODY2QzQuMDU5ODUgNS40Njc0NSA0LjgwMDU3IDQuOTY0MDQgNS42MTA3OSA0LjYyODQzTDUuNjEwOCA0LjYyODQzQzYuNDIxMDMgNC4yOTI4MiA3LjMwMDc2IDQuMTI1MDIgOC4yNSA0LjEyNTAyTDI3LjQ5OTkgNC4xMjVDMjguMjY5OSA0LjEyNSAyOC44NzQ5IDQuNzMgMjguODc0OSA1LjVDMjguODc0OSA2LjI3IDI4LjI2OTkgNi44NzUgMjcuNDk5OSA2Ljg3NUw4LjI1IDYuODc1MDJDNy42ODA0NSA2Ljg3NTAyIDcuMTUyNjEgNi45NzU3IDYuNjY2NDggNy4xNzcwNkM2LjE4MDM0IDcuMzc4NDMgNS43MzU5MSA3LjY4MDQ3IDUuMzMzMTggOC4wODMyQzQuOTMwNDYgOC40ODU5MyA0LjYyODQxIDguOTMwMzYgNC40MjcwNSA5LjQxNjQ5QzQuMjI1NjggOS45MDI2MyA0LjEyNSAxMC40MzA1IDQuMTI1IDExTDQuMTI1IDMzQzQuMTI1IDMzLjU2OTYgNC4yMjU2OCAzNC4wOTc0IDQuNDI3MDUgMzQuNTgzNUM0LjYyODQxIDM1LjA2OTcgNC45MzA0NSAzNS41MTQxIDUuMzMzMTcgMzUuOTE2OEM1LjczNTg4IDM2LjMxOTUgNi4xODAyOCAzNi42MjE2IDYuNjY2MzkgMzYuODIyOUM3LjE1MjU2IDM3LjAyNDMgNy42ODA0MiAzNy4xMjUgOC4yNDk5OSAzNy4xMjVMMTAuOTk5OSAzNy4xMjVMMzUuNzUgMzcuMTI1QzM2LjMxOTUgMzcuMTI1IDM2Ljg0NzQgMzcuMDI0MyAzNy4zMzM1IDM2LjgyM0MzNy44MTk3IDM2LjYyMTYgMzguMjY0MSAzNi4zMTk2IDM4LjY2NjggMzUuOTE2OEMzOS4wNjk1IDM1LjUxNDEgMzkuMzcxNiAzNS4wNjk3IDM5LjU3MyAzNC41ODM1QzM5Ljc3NDMgMzQuMDk3NCAzOS44NzUgMzMuNTY5NiAzOS44NzUgMzNMMzkuODc1IDE2LjQ5OTlDMzkuODc1IDE1LjcyOTkgNDAuNDggMTUuMTI0OSA0MS4yNSAxNS4xMjQ5QzQyLjAyIDE1LjEyNDkgNDIuNjI1IDE1LjcyOTkgNDIuNjI1IDE2LjQ5OTlaTTEwLjAyNzcgMTIuNzc3N0MxMC41NzIyIDEyLjIzMzMgMTEuNDI3OCAxMi4yMzMzIDExLjk3MjMgMTIuNzc3N0wyMC4yMjIyIDIxLjAyNzdDMjAuNDkwNyAyMS4yOTYyIDIwLjYyNDkgMjEuNjIwMyAyMC42MjQ5IDIyQzIwLjYyNDkgMjIuMzc5NyAyMC40OTA3IDIyLjcwMzcgMjAuMjIyMiAyMi45NzIyTDExLjk3MjIgMzEuMjIyM0MxMS40Mjc3IDMxLjc2NjggMTAuNTcyMSAzMS43NjY4IDEwLjAyNzcgMzEuMjIyM0M5LjQ4MzE4IDMwLjY3NzkgOS40ODMxNyAyOS44MjIzIDEwLjAyNzYgMjkuMjc3OEwxNy4zMDU0IDIyTDEwLjAyNzcgMTQuNzIyM0M5LjQ4MzI1IDE0LjE3NzggOS40ODMyNiAxMy4zMjIyIDEwLjAyNzcgMTIuNzc3N1pNMjAuNjI1IDI4Ljg3NUMyMC42MjUgMjguMTA1IDIxLjIzIDI3LjUgMjIgMjcuNUwzMyAyNy41QzMzLjc3IDI3LjUgMzQuMzc1IDI4LjEwNSAzNC4zNzUgMjguODc1QzM0LjM3NSAyOS42NDUgMzMuNzcgMzAuMjUgMzMgMzAuMjVMMjIgMzAuMjVDMjEuMjMgMzAuMjUgMjAuNjI1IDI5LjY0NSAyMC42MjUgMjguODc1WiIgZmlsbD0idXJsKCNwYWludF9saW5lYXJfMCkiIGZpbGwtcnVsZT0iZXZlbm9kZCIgLz4KPC9zdmc+Cg==) 50%/contain no-repeat;display:inline-block}.NP1U9q_name{vertical-align:middle;align-items:center;gap:6px;min-width:0;height:24px;display:inline-flex}.NP1U9q_name:before{content:\"AI Shell\";letter-spacing:.04em;color:var(--dsw-alias-label-primary,#1a1a1a);white-space:nowrap;font-size:18px;font-weight:600;line-height:24px}.NP1U9q_version{height:14px;color:var(--dsw-alias-label-primary-inverted,#fff);background:var(--dsw-alias-label-primary,#1a1a1a);white-space:nowrap;border-radius:3px;flex:none;align-items:center;margin-top:-8px;padding:0 4px;font-size:8px;font-weight:500;line-height:14px;display:inline-flex}";
32
+ const css = ".NP1U9q_mark{background:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDQgNDQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI0NC4wMDAwMDAiIGhlaWdodD0iNDQuMDAwMDAwIiBmaWxsPSJub25lIj4KCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnRfbGluZWFyXzAiIHgxPSIzOC41IiB4Mj0iMi4xOTk5OTY5NSIgeTE9IjEuOTA3MzQ4NjNlLTA2IiB5Mj0iMzkuNTk5OTkwOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSJyZ2IoNTMsMTM3LDI1NSkiIG9mZnNldD0iMCIgc3RvcC1vcGFjaXR5PSIxIiAvPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSJyZ2IoMTY5LDM4LDI1NSkiIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIxIiAvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8cmVjdCBpZD0iaWNvbl9BSVNoZWxsIiB3aWR0aD0iNDQuMDAwMDAwIiBoZWlnaHQ9IjQ0LjAwMDAwMCIgeD0iMC4wMDAwMDAiIHk9IjAuMDAwMDAwIiBmaWxsPSJyZ2IoMjU1LDI1NSwyNTUpIiBmaWxsLW9wYWNpdHk9IjAiIC8+Cgk8cGF0aCBpZD0i5ZCI5bm2IiBkPSJNMzcuNDU3NiAwLjI5OTczOEMzNy4zNzc1IDAuMTM5ODc3IDM3LjI4NjEgMC4wNDI4MTk2IDM3LjE4MzIgMC4wMDg1NjM5M0MzNy4wOTE3IC0wLjAxNDI3MzIgMzcuMDA2IDAuMDA4NTYzOTQgMzYuOTI1OSAwLjA3NzA3NTRDMzYuODQ1OSAwLjE0NTU4NyAzNi43ODg3IDAuMjE5ODA4IDM2Ljc1NDUgMC4yOTk3MzhDMzYuNDU3MiAxLjg2NDA4IDM1LjczNjkgMy4yMTcxOCAzNC41OTM3IDQuMzU5MDRDMzMuNDYxOCA1LjQ4OTQ4IDMyLjA5NTYgNi4yMjAyNyAzMC40OTUgNi41NTE0MUMzMC4zOTIxIDYuNTc0MjQgMzAuMzE3OCA2LjY0Mjc1IDMwLjI3MiA2Ljc1Njk0QzMwLjIzNzcgNi44NzExMyAzMC4yNDM1IDYuOTczODkgMzAuMjg5MiA3LjA2NTI0QzMwLjM0NjQgNy4xNTY1OSAzMC40MTUgNy4yMDIyNiAzMC40OTUgNy4yMDIyNkMzMS4yMDc4IDcuMzUyOTYgMzEuODc3OSA3LjU4NzU5IDMyLjUwNTUgNy45MDYxOEwzMi41MDU1IDcuOTA2MkwzMi41MDU2IDcuOTA2MjNDMzMuMjU3IDguMjg3NzEgMzMuOTQ3MyA4Ljc4OTU1IDM0LjU3NjUgOS40MTE3NkMzNS43MzEyIDEwLjU0MjIgMzYuNDU3MiAxMS44ODk2IDM2Ljc1NDUgMTMuNDUzOUMzNi44MTE2IDEzLjU5MSAzNi44OTc0IDEzLjY4MjMgMzcuMDExNyAxMy43MjhDMzcuMTE0NiAxMy43NjIyIDM3LjIwNjEgMTMuNzU2NSAzNy4yODYxIDEzLjcxMDlDMzcuMzc3NSAxMy42NTM4IDM3LjQzNDcgMTMuNTY4MSAzNy40NTc2IDEzLjQ1MzlDMzcuNzg5MSAxMS44ODk2IDM4LjUxNTEgMTAuNTQyMiAzOS42MzU1IDkuNDExNzZDNDAuNzY3NCA4LjI2OTkgNDIuMTI3OSA3LjUzMzQgNDMuNzE3IDcuMjAyMjZDNDMuODE5OSA3LjE3OTQzIDQzLjkgNy4xMjIzMyA0My45NTcxIDcuMDMwOTlDNDQuMDE0MyA2LjkyODIyIDQ0LjAxNDMgNi44MzExNiA0My45NTcxIDYuNzM5ODFDNDMuOSA2LjYzNzA1IDQzLjgxOTkgNi41NzQyNCA0My43MTcgNi41NTE0MUM0Mi4xNTA3IDYuMjIwMjcgNDAuNzk1OSA1LjQ4OTQ4IDM5LjY1MjcgNC4zNTkwNEMzOC41MjA4IDMuMjE3MTggMzcuNzg5MSAxLjg2NDA4IDM3LjQ1NzYgMC4yOTk3MzhaTTQyLjYyNSAxNi40OTk5TDQyLjYyNSAzM0M0Mi42MjUgMzMuOTQ5MyA0Mi40NTcyIDM0LjgyOSA0Mi4xMjE2IDM1LjYzOTJDNDEuNzg2IDM2LjQ0OTUgNDEuMjgyNiAzNy4xOTAyIDQwLjYxMTQgMzcuODYxNEMzOS45NDAyIDM4LjUzMjYgMzkuMTk5NCAzOS4wMzYgMzguMzg5MiAzOS4zNzE2QzM3LjU3OSAzOS43MDcyIDM2LjY5OTIgMzkuODc1IDM1Ljc1IDM5Ljg3NUwxMC45OTk5IDM5Ljg3NUw4LjI0OTk1IDM5Ljg3NUM3LjMwMDczIDM5Ljg3NSA2LjQyMTAyIDM5LjcwNzIgNS42MTA4MSAzOS4zNzE2QzQuODAwNTggMzkuMDM2IDQuMDU5ODUgMzguNTMyNiAzLjM4ODYzIDM3Ljg2MTRDMi43MTc0MiAzNy4xOTAyIDIuMjE0MDIgMzYuNDQ5NCAxLjg3ODQxIDM1LjYzOTJDMS41NDI4IDM0LjgyOSAxLjM3NSAzMy45NDkzIDEuMzc1IDMzTDEuMzc1IDExQzEuMzc1IDEwLjA1MDggMS41NDI4IDkuMTcxMDYgMS44Nzg0IDguMzYwODNMMS44Nzg0MSA4LjM2MDgyQzIuMjE0MDIgNy41NTA1OSAyLjcxNzQzIDYuODA5ODcgMy4zODg2NCA2LjEzODY2QzQuMDU5ODUgNS40Njc0NSA0LjgwMDU3IDQuOTY0MDQgNS42MTA3OSA0LjYyODQzTDUuNjEwOCA0LjYyODQzQzYuNDIxMDMgNC4yOTI4MiA3LjMwMDc2IDQuMTI1MDIgOC4yNSA0LjEyNTAyTDI3LjQ5OTkgNC4xMjVDMjguMjY5OSA0LjEyNSAyOC44NzQ5IDQuNzMgMjguODc0OSA1LjVDMjguODc0OSA2LjI3IDI4LjI2OTkgNi44NzUgMjcuNDk5OSA2Ljg3NUw4LjI1IDYuODc1MDJDNy42ODA0NSA2Ljg3NTAyIDcuMTUyNjEgNi45NzU3IDYuNjY2NDggNy4xNzcwNkM2LjE4MDM0IDcuMzc4NDMgNS43MzU5MSA3LjY4MDQ3IDUuMzMzMTggOC4wODMyQzQuOTMwNDYgOC40ODU5MyA0LjYyODQxIDguOTMwMzYgNC40MjcwNSA5LjQxNjQ5QzQuMjI1NjggOS45MDI2MyA0LjEyNSAxMC40MzA1IDQuMTI1IDExTDQuMTI1IDMzQzQuMTI1IDMzLjU2OTYgNC4yMjU2OCAzNC4wOTc0IDQuNDI3MDUgMzQuNTgzNUM0LjYyODQxIDM1LjA2OTcgNC45MzA0NSAzNS41MTQxIDUuMzMzMTcgMzUuOTE2OEM1LjczNTg4IDM2LjMxOTUgNi4xODAyOCAzNi42MjE2IDYuNjY2MzkgMzYuODIyOUM3LjE1MjU2IDM3LjAyNDMgNy42ODA0MiAzNy4xMjUgOC4yNDk5OSAzNy4xMjVMMTAuOTk5OSAzNy4xMjVMMzUuNzUgMzcuMTI1QzM2LjMxOTUgMzcuMTI1IDM2Ljg0NzQgMzcuMDI0MyAzNy4zMzM1IDM2LjgyM0MzNy44MTk3IDM2LjYyMTYgMzguMjY0MSAzNi4zMTk2IDM4LjY2NjggMzUuOTE2OEMzOS4wNjk1IDM1LjUxNDEgMzkuMzcxNiAzNS4wNjk3IDM5LjU3MyAzNC41ODM1QzM5Ljc3NDMgMzQuMDk3NCAzOS44NzUgMzMuNTY5NiAzOS44NzUgMzNMMzkuODc1IDE2LjQ5OTlDMzkuODc1IDE1LjcyOTkgNDAuNDggMTUuMTI0OSA0MS4yNSAxNS4xMjQ5QzQyLjAyIDE1LjEyNDkgNDIuNjI1IDE1LjcyOTkgNDIuNjI1IDE2LjQ5OTlaTTEwLjAyNzcgMTIuNzc3N0MxMC41NzIyIDEyLjIzMzMgMTEuNDI3OCAxMi4yMzMzIDExLjk3MjMgMTIuNzc3N0wyMC4yMjIyIDIxLjAyNzdDMjAuNDkwNyAyMS4yOTYyIDIwLjYyNDkgMjEuNjIwMyAyMC42MjQ5IDIyQzIwLjYyNDkgMjIuMzc5NyAyMC40OTA3IDIyLjcwMzcgMjAuMjIyMiAyMi45NzIyTDExLjk3MjIgMzEuMjIyM0MxMS40Mjc3IDMxLjc2NjggMTAuNTcyMSAzMS43NjY4IDEwLjAyNzcgMzEuMjIyM0M5LjQ4MzE4IDMwLjY3NzkgOS40ODMxNyAyOS44MjIzIDEwLjAyNzYgMjkuMjc3OEwxNy4zMDU0IDIyTDEwLjAyNzcgMTQuNzIyM0M5LjQ4MzI1IDE0LjE3NzggOS40ODMyNiAxMy4zMjIyIDEwLjAyNzcgMTIuNzc3N1pNMjAuNjI1IDI4Ljg3NUMyMC42MjUgMjguMTA1IDIxLjIzIDI3LjUgMjIgMjcuNUwzMyAyNy41QzMzLjc3IDI3LjUgMzQuMzc1IDI4LjEwNSAzNC4zNzUgMjguODc1QzM0LjM3NSAyOS42NDUgMzMuNzcgMzAuMjUgMzMgMzAuMjVMMjIgMzAuMjVDMjEuMjMgMzAuMjUgMjAuNjI1IDI5LjY0NSAyMC42MjUgMjguODc1WiIgZmlsbD0icmdiKDAsMCwwKSIgZmlsbC1vcGFjaXR5PSIwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIC8+Cgk8cGF0aCBpZD0i5ZCI5bm2IiBkPSJNMzcuNDU3NiAwLjI5OTczOEMzNy4zNzc1IDAuMTM5ODc3IDM3LjI4NjEgMC4wNDI4MTk2IDM3LjE4MzIgMC4wMDg1NjM5M0MzNy4wOTE3IC0wLjAxNDI3MzIgMzcuMDA2IDAuMDA4NTYzOTQgMzYuOTI1OSAwLjA3NzA3NTRDMzYuODQ1OSAwLjE0NTU4NyAzNi43ODg3IDAuMjE5ODA4IDM2Ljc1NDUgMC4yOTk3MzhDMzYuNDU3MiAxLjg2NDA4IDM1LjczNjkgMy4yMTcxOCAzNC41OTM3IDQuMzU5MDRDMzMuNDYxOCA1LjQ4OTQ4IDMyLjA5NTYgNi4yMjAyNyAzMC40OTUgNi41NTE0MUMzMC4zOTIxIDYuNTc0MjQgMzAuMzE3OCA2LjY0Mjc1IDMwLjI3MiA2Ljc1Njk0QzMwLjIzNzcgNi44NzExMyAzMC4yNDM1IDYuOTczODkgMzAuMjg5MiA3LjA2NTI0QzMwLjM0NjQgNy4xNTY1OSAzMC40MTUgNy4yMDIyNiAzMC40OTUgNy4yMDIyNkMzMS4yMDc4IDcuMzUyOTYgMzEuODc3OSA3LjU4NzU5IDMyLjUwNTUgNy45MDYxOEwzMi41MDU1IDcuOTA2MkwzMi41MDU2IDcuOTA2MjNDMzMuMjU3IDguMjg3NzEgMzMuOTQ3MyA4Ljc4OTU1IDM0LjU3NjUgOS40MTE3NkMzNS43MzEyIDEwLjU0MjIgMzYuNDU3MiAxMS44ODk2IDM2Ljc1NDUgMTMuNDUzOUMzNi44MTE2IDEzLjU5MSAzNi44OTc0IDEzLjY4MjMgMzcuMDExNyAxMy43MjhDMzcuMTE0NiAxMy43NjIyIDM3LjIwNjEgMTMuNzU2NSAzNy4yODYxIDEzLjcxMDlDMzcuMzc3NSAxMy42NTM4IDM3LjQzNDcgMTMuNTY4MSAzNy40NTc2IDEzLjQ1MzlDMzcuNzg5MSAxMS44ODk2IDM4LjUxNTEgMTAuNTQyMiAzOS42MzU1IDkuNDExNzZDNDAuNzY3NCA4LjI2OTkgNDIuMTI3OSA3LjUzMzQgNDMuNzE3IDcuMjAyMjZDNDMuODE5OSA3LjE3OTQzIDQzLjkgNy4xMjIzMyA0My45NTcxIDcuMDMwOTlDNDQuMDE0MyA2LjkyODIyIDQ0LjAxNDMgNi44MzExNiA0My45NTcxIDYuNzM5ODFDNDMuOSA2LjYzNzA1IDQzLjgxOTkgNi41NzQyNCA0My43MTcgNi41NTE0MUM0Mi4xNTA3IDYuMjIwMjcgNDAuNzk1OSA1LjQ4OTQ4IDM5LjY1MjcgNC4zNTkwNEMzOC41MjA4IDMuMjE3MTggMzcuNzg5MSAxLjg2NDA4IDM3LjQ1NzYgMC4yOTk3MzhaTTQyLjYyNSAxNi40OTk5TDQyLjYyNSAzM0M0Mi42MjUgMzMuOTQ5MyA0Mi40NTcyIDM0LjgyOSA0Mi4xMjE2IDM1LjYzOTJDNDEuNzg2IDM2LjQ0OTUgNDEuMjgyNiAzNy4xOTAyIDQwLjYxMTQgMzcuODYxNEMzOS45NDAyIDM4LjUzMjYgMzkuMTk5NCAzOS4wMzYgMzguMzg5MiAzOS4zNzE2QzM3LjU3OSAzOS43MDcyIDM2LjY5OTIgMzkuODc1IDM1Ljc1IDM5Ljg3NUwxMC45OTk5IDM5Ljg3NUw4LjI0OTk1IDM5Ljg3NUM3LjMwMDczIDM5Ljg3NSA2LjQyMTAyIDM5LjcwNzIgNS42MTA4MSAzOS4zNzE2QzQuODAwNTggMzkuMDM2IDQuMDU5ODUgMzguNTMyNiAzLjM4ODYzIDM3Ljg2MTRDMi43MTc0MiAzNy4xOTAyIDIuMjE0MDIgMzYuNDQ5NCAxLjg3ODQxIDM1LjYzOTJDMS41NDI4IDM0LjgyOSAxLjM3NSAzMy45NDkzIDEuMzc1IDMzTDEuMzc1IDExQzEuMzc1IDEwLjA1MDggMS41NDI4IDkuMTcxMDYgMS44Nzg0IDguMzYwODNMMS44Nzg0MSA4LjM2MDgyQzIuMjE0MDIgNy41NTA1OSAyLjcxNzQzIDYuODA5ODcgMy4zODg2NCA2LjEzODY2QzQuMDU5ODUgNS40Njc0NSA0LjgwMDU3IDQuOTY0MDQgNS42MTA3OSA0LjYyODQzTDUuNjEwOCA0LjYyODQzQzYuNDIxMDMgNC4yOTI4MiA3LjMwMDc2IDQuMTI1MDIgOC4yNSA0LjEyNTAyTDI3LjQ5OTkgNC4xMjVDMjguMjY5OSA0LjEyNSAyOC44NzQ5IDQuNzMgMjguODc0OSA1LjVDMjguODc0OSA2LjI3IDI4LjI2OTkgNi44NzUgMjcuNDk5OSA2Ljg3NUw4LjI1IDYuODc1MDJDNy42ODA0NSA2Ljg3NTAyIDcuMTUyNjEgNi45NzU3IDYuNjY2NDggNy4xNzcwNkM2LjE4MDM0IDcuMzc4NDMgNS43MzU5MSA3LjY4MDQ3IDUuMzMzMTggOC4wODMyQzQuOTMwNDYgOC40ODU5MyA0LjYyODQxIDguOTMwMzYgNC40MjcwNSA5LjQxNjQ5QzQuMjI1NjggOS45MDI2MyA0LjEyNSAxMC40MzA1IDQuMTI1IDExTDQuMTI1IDMzQzQuMTI1IDMzLjU2OTYgNC4yMjU2OCAzNC4wOTc0IDQuNDI3MDUgMzQuNTgzNUM0LjYyODQxIDM1LjA2OTcgNC45MzA0NSAzNS41MTQxIDUuMzMzMTcgMzUuOTE2OEM1LjczNTg4IDM2LjMxOTUgNi4xODAyOCAzNi42MjE2IDYuNjY2MzkgMzYuODIyOUM3LjE1MjU2IDM3LjAyNDMgNy42ODA0MiAzNy4xMjUgOC4yNDk5OSAzNy4xMjVMMTAuOTk5OSAzNy4xMjVMMzUuNzUgMzcuMTI1QzM2LjMxOTUgMzcuMTI1IDM2Ljg0NzQgMzcuMDI0MyAzNy4zMzM1IDM2LjgyM0MzNy44MTk3IDM2LjYyMTYgMzguMjY0MSAzNi4zMTk2IDM4LjY2NjggMzUuOTE2OEMzOS4wNjk1IDM1LjUxNDEgMzkuMzcxNiAzNS4wNjk3IDM5LjU3MyAzNC41ODM1QzM5Ljc3NDMgMzQuMDk3NCAzOS44NzUgMzMuNTY5NiAzOS44NzUgMzNMMzkuODc1IDE2LjQ5OTlDMzkuODc1IDE1LjcyOTkgNDAuNDggMTUuMTI0OSA0MS4yNSAxNS4xMjQ5QzQyLjAyIDE1LjEyNDkgNDIuNjI1IDE1LjcyOTkgNDIuNjI1IDE2LjQ5OTlaTTEwLjAyNzcgMTIuNzc3N0MxMC41NzIyIDEyLjIzMzMgMTEuNDI3OCAxMi4yMzMzIDExLjk3MjMgMTIuNzc3N0wyMC4yMjIyIDIxLjAyNzdDMjAuNDkwNyAyMS4yOTYyIDIwLjYyNDkgMjEuNjIwMyAyMC42MjQ5IDIyQzIwLjYyNDkgMjIuMzc5NyAyMC40OTA3IDIyLjcwMzcgMjAuMjIyMiAyMi45NzIyTDExLjk3MjIgMzEuMjIyM0MxMS40Mjc3IDMxLjc2NjggMTAuNTcyMSAzMS43NjY4IDEwLjAyNzcgMzEuMjIyM0M5LjQ4MzE4IDMwLjY3NzkgOS40ODMxNyAyOS44MjIzIDEwLjAyNzYgMjkuMjc3OEwxNy4zMDU0IDIyTDEwLjAyNzcgMTQuNzIyM0M5LjQ4MzI1IDE0LjE3NzggOS40ODMyNiAxMy4zMjIyIDEwLjAyNzcgMTIuNzc3N1pNMjAuNjI1IDI4Ljg3NUMyMC42MjUgMjguMTA1IDIxLjIzIDI3LjUgMjIgMjcuNUwzMyAyNy41QzMzLjc3IDI3LjUgMzQuMzc1IDI4LjEwNSAzNC4zNzUgMjguODc1QzM0LjM3NSAyOS42NDUgMzMuNzcgMzAuMjUgMzMgMzAuMjVMMjIgMzAuMjVDMjEuMjMgMzAuMjUgMjAuNjI1IDI5LjY0NSAyMC42MjUgMjguODc1WiIgZmlsbD0idXJsKCNwYWludF9saW5lYXJfMCkiIGZpbGwtcnVsZT0iZXZlbm9kZCIgLz4KPC9zdmc+Cg==) 50%/contain no-repeat;display:inline-block}.NP1U9q_name{vertical-align:middle;align-items:center;min-width:0;height:24px;display:inline-flex}.NP1U9q_name:before{content:\"AI Shell\";letter-spacing:.04em;color:var(--dsw-alias-label-primary,#1a1a1a);white-space:nowrap;font-size:18px;font-weight:600;line-height:24px}";
31
33
  const tagId = "@huaweidevtools/aishell-dsh-brand//home/eushing/opensource/aishell-dsh-plugins/packages/aishell-brand/src/client/brand.module.css";
32
34
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
33
35
  const tag = document.createElement("style");
@@ -38,8 +40,7 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
38
40
  }
39
41
  var brand_module_css_default = {
40
42
  "mark": "NP1U9q_mark",
41
- "name": "NP1U9q_name",
42
- "version": "NP1U9q_version"
43
+ "name": "NP1U9q_name"
43
44
  };
44
45
 
45
46
  //#endregion
@@ -109,51 +110,77 @@ const HERO_PATCH_CSS = [
109
110
  " margin-top: -4px;",
110
111
  "}"
111
112
  ].join("\n");
112
- /** Inject the hero headline patch CSS once at module load. */
113
- function injectHeroPatchCss() {
114
- if (typeof document === "undefined" || document.querySelector("style[data-aishell-hero-patch]")) return;
115
- const tag = document.createElement("style");
113
+ /** Inject the hero headline patch CSS; the disposer removes the tag. */
114
+ function installHeroPatchCss(target) {
115
+ if (typeof target.querySelector !== "function") return () => {};
116
+ if (target.querySelector("style[data-aishell-hero-patch]")) return () => {};
117
+ const tag = target.createElement("style");
116
118
  tag.dataset.aishellHeroPatch = "";
117
119
  tag.textContent = HERO_PATCH_CSS;
118
- document.head.appendChild(tag);
120
+ target.head.appendChild(tag);
121
+ return () => {
122
+ tag.remove();
123
+ };
119
124
  }
120
- injectHeroPatchCss();
121
125
  /**
122
126
  * JS hover bridge: CSS ~ only matches forward siblings, so hovering the
123
127
  * title text cannot trigger the logo's animation (the logo sits before the
124
128
  * title in DOM order). This adds a mouseenter/mouseleave listener on the
125
129
  * title span that toggles a class on the logo to trigger its bob animation.
130
+ *
131
+ * The disposer removes both listeners from every span it bridged, so a reload
132
+ * does not stack a second pair on the same element (the `data-` marker stops a
133
+ * *re-install* from adding them, but it also means a later disposer cannot
134
+ * find them by query alone — hence the bookkeeping below).
126
135
  */
127
- function bridgeHeroHover() {
128
- if (typeof document === "undefined") return;
136
+ function installHeroHoverBridge(target) {
137
+ if (typeof target.querySelector !== "function") return () => {};
138
+ /** Every listener this install added, so the disposer can take them off. */
139
+ const bridged = [];
129
140
  const bridge = (root) => {
130
141
  const groups = root.querySelectorAll("[class*=\"titleGroup\"]");
131
142
  for (const group of groups) {
132
143
  const titleSpan = group.children[0];
133
144
  if (!titleSpan || titleSpan.dataset.aishellHoverBridged === "true") continue;
134
145
  titleSpan.dataset.aishellHoverBridged = "true";
135
- titleSpan.addEventListener("mouseenter", () => {
146
+ const onEnter = () => {
136
147
  const headline = titleSpan.closest("[class*=\"headline\"]");
137
148
  const fish = headline?.querySelector("[class*=\"fish\"]");
138
149
  fish?.classList.add("aishell-logo-hover");
139
- });
140
- titleSpan.addEventListener("mouseleave", () => {
150
+ };
151
+ const onLeave = () => {
141
152
  const headline = titleSpan.closest("[class*=\"headline\"]");
142
153
  const fish = headline?.querySelector("[class*=\"fish\"]");
143
154
  fish?.classList.remove("aishell-logo-hover");
155
+ };
156
+ titleSpan.addEventListener("mouseenter", onEnter);
157
+ titleSpan.addEventListener("mouseleave", onLeave);
158
+ bridged.push({
159
+ element: titleSpan,
160
+ onEnter,
161
+ onLeave
144
162
  });
145
163
  }
146
164
  };
147
- bridge(document);
165
+ bridge(target);
148
166
  const observer = new MutationObserver((mutations) => {
149
167
  for (const m of mutations) for (const node of m.addedNodes) if (node.nodeType === 1) bridge(node);
150
168
  });
151
- observer.observe(document.body, {
169
+ observer.observe(target.body, {
152
170
  childList: true,
153
171
  subtree: true
154
172
  });
173
+ return () => {
174
+ observer.disconnect();
175
+ for (const { element, onEnter, onLeave } of bridged) {
176
+ element.removeEventListener("mouseenter", onEnter);
177
+ element.removeEventListener("mouseleave", onLeave);
178
+ delete element.dataset.aishellHoverBridged;
179
+ element.closest("[class*=\"headline\"]")?.querySelector("[class*=\"fish\"]")?.classList.remove("aishell-logo-hover");
180
+ }
181
+ bridged.length = 0;
182
+ };
155
183
  }
156
- bridgeHeroHover();
157
184
  /**
158
185
  * Render the AI Shell mark with the presentation requested by its host surface.
159
186
  * Used in both the expanded sidebar brand row and the collapsed rail.
@@ -170,18 +197,15 @@ function AishellBrandMark({ size }) {
170
197
  });
171
198
  }
172
199
  /**
173
- * Render the AI Shell name with a version badge in the top-right corner,
174
- * matching DSH's native `.buildVersion` badge style.
200
+ * Render the AI Shell name in the sidebar brand row. The product name itself
201
+ * is drawn by the CSS module's `.name::before` (content: 'AI Shell'), so the
202
+ * markup is a single empty span.
175
203
  */
176
204
  function AishellBrandName() {
177
205
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
178
206
  className: brand_module_css_default.name,
179
207
  role: "img",
180
- "aria-label": "AI Shell",
181
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
182
- className: brand_module_css_default.version,
183
- children: "0.0.1-beta.2"
184
- })
208
+ "aria-label": "AI Shell"
185
209
  });
186
210
  }
187
211
  /**
@@ -203,33 +227,1027 @@ function AishellHeroBrandMark({ size, className }) {
203
227
  * Insert "Powered by deepseek-harness" below the hero headline, centered
204
228
  * inside .stack. Uses a MutationObserver because the hero mounts/unmounts
205
229
  * as the user navigates between sessions.
230
+ *
231
+ * The disposer stops the observer and removes every label this install added,
232
+ * so unloading the plugin leaves the hero exactly as it found it.
206
233
  */
207
- function injectPoweredBy() {
208
- if (typeof document === "undefined") return;
234
+ function installPoweredBy(target) {
235
+ if (typeof target.querySelector !== "function") return () => {};
236
+ /** The labels this install inserted, for removal on dispose. */
237
+ const inserted = [];
209
238
  const insert = (root) => {
210
239
  const stacks = root.querySelectorAll("[class*=\"stack\"]");
211
240
  for (const stack of stacks) {
212
241
  if (!stack.querySelector("[class*=\"headline\"]")) continue;
213
242
  if (stack.querySelector("[data-aishell-powered-by]")) continue;
214
- const label = document.createElement("div");
243
+ const label = target.createElement("div");
215
244
  label.dataset.aishellPoweredBy = "";
216
245
  label.style.cssText = "text-align:center;font-size:11px;font-weight:400;line-height:16px;color:var(--dsw-alias-label-quaternary, #999);user-select:none;white-space:nowrap;";
217
246
  label.textContent = "Powered by deepseek-harness";
218
247
  const headline = stack.querySelector("[class*=\"headline\"]");
219
248
  if (headline && headline.nextSibling) stack.insertBefore(label, headline.nextSibling);
220
249
  else stack.appendChild(label);
250
+ inserted.push(label);
221
251
  }
222
252
  };
223
- insert(document);
253
+ insert(target);
224
254
  const observer = new MutationObserver((mutations) => {
225
255
  for (const m of mutations) for (const node of m.addedNodes) if (node.nodeType === 1) insert(node);
226
256
  });
227
- observer.observe(document.body, {
257
+ observer.observe(target.body, {
228
258
  childList: true,
229
259
  subtree: true
230
260
  });
261
+ return () => {
262
+ observer.disconnect();
263
+ for (const label of inserted) label.remove();
264
+ inserted.length = 0;
265
+ };
266
+ }
267
+
268
+ //#endregion
269
+ //#region src/client/suggestions.ts
270
+ /**
271
+ * The cards, in display order.
272
+ *
273
+ * Order matters only for the ring's starting window: the strip opens on the
274
+ * first {@link VISIBLE} cards, so the first entries are the ones a person
275
+ * sees without pressing an arrow.
276
+ */
277
+ const SUGGESTIONS = [
278
+ {
279
+ id: "inventory",
280
+ icon: "cloud",
281
+ hue: { tile: "var(--dsw-static-blue-400)" },
282
+ titleKey: "suggestion.inventory.title",
283
+ glossKey: "suggestion.inventory.gloss",
284
+ promptKey: "suggestion.inventory.prompt"
285
+ },
286
+ {
287
+ id: "deploy",
288
+ icon: "deploy",
289
+ hue: { tile: "var(--dsw-static-green-500)" },
290
+ titleKey: "suggestion.deploy.title",
291
+ glossKey: "suggestion.deploy.gloss",
292
+ promptKey: "suggestion.deploy.prompt"
293
+ },
294
+ {
295
+ id: "enterprise",
296
+ icon: "architecture",
297
+ hue: { tile: "rgb(139, 118, 246)" },
298
+ titleKey: "suggestion.enterprise.title",
299
+ glossKey: "suggestion.enterprise.gloss",
300
+ promptKey: "suggestion.enterprise.prompt"
301
+ },
302
+ {
303
+ id: "billing",
304
+ icon: "cost",
305
+ hue: { tile: "var(--dsw-static-amber-600)" },
306
+ titleKey: "suggestion.billing.title",
307
+ glossKey: "suggestion.billing.gloss",
308
+ promptKey: "suggestion.billing.prompt"
309
+ }
310
+ ];
311
+
312
+ //#endregion
313
+ //#region src/client/suggestion-icons.tsx
314
+ /** One stroked path with the shared paint attributes. */
315
+ function Stroke({ d }) {
316
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
317
+ d,
318
+ fill: "none",
319
+ stroke: "currentColor",
320
+ strokeWidth: "1.7",
321
+ strokeLinecap: "round",
322
+ strokeLinejoin: "round"
323
+ });
324
+ }
325
+ /**
326
+ * A stack of servers: the resource inventory.
327
+ *
328
+ * Three stacked slabs with a status dot each — unmistakably "machines you
329
+ * own", and it shares no silhouette with any other glyph here.
330
+ */
331
+ function InventoryIcon() {
332
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M4 5.5h16v4H4z" }),
334
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M4 14.5h16v4H4z" }),
335
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M7 7.5h.01" }),
336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M7 16.5h.01" })
337
+ ] });
338
+ }
339
+ /**
340
+ * A rocket: deploying something so it runs.
341
+ *
342
+ * Chosen over any box-with-arrow because that family is exactly the share
343
+ * glyph. A rocket is a single closed silhouette — nothing in this set can
344
+ * be confused with it.
345
+ */
346
+ function DeployIcon() {
347
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 3c2.8 1.9 4.2 4.6 4.2 7.6 0 2.3-.9 4.3-2.1 5.6H9.9C8.7 14.9 7.8 12.9 7.8 10.6 7.8 7.6 9.2 4.9 12 3Z" }),
349
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.9 16.2 8.4 19.4l2.3-1M14.1 16.2l1.5 3.2-2.3-1" }),
350
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 9.4a1.3 1.3 0 1 0 0 2.6 1.3 1.3 0 0 0 0-2.6Z" })
351
+ ] });
352
+ }
353
+ /**
354
+ * A layered sitemap: account hierarchy and network partitions.
355
+ *
356
+ * One node branching to three, drawn as a single tree — reads as "structure"
357
+ * rather than "file" or "folder", which a plain box-and-boxes would.
358
+ */
359
+ function ArchitectureIcon() {
360
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 3.5h5v3.4h-5z" }),
362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M3.5 17.1h5v3.4h-5z" }),
363
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 17.1h5v3.4h-5z" }),
364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M15.5 17.1h5v3.4h-5z" }),
365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 6.9v4.2" }),
366
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M6 17.1v-2.4h12v2.4" })
367
+ ] });
368
+ }
369
+ /**
370
+ * A receipt with a downward trend: cost review.
371
+ *
372
+ * The torn bottom edge is what makes it a bill rather than a document —
373
+ * without it the shape is a generic page and reads as "export a report".
374
+ */
375
+ function CostIcon() {
376
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M6 3.5h12v17l-2-1.4-2 1.4-2-1.4-2 1.4-2-1.4-2 1.4z" }),
378
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 8.4h5" }),
379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 12.2h3.2" })
380
+ ] });
381
+ }
382
+ /** Resolve one icon key to its glyph. */
383
+ function SuggestionIconArt({ icon, size = 20 }) {
384
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
385
+ "aria-hidden": true,
386
+ width: size,
387
+ height: size,
388
+ viewBox: "0 0 24 24",
389
+ style: { flex: "none" },
390
+ children: [
391
+ icon === "cloud" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InventoryIcon, {}),
392
+ icon === "deploy" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeployIcon, {}),
393
+ icon === "architecture" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArchitectureIcon, {}),
394
+ icon === "cost" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CostIcon, {})
395
+ ]
396
+ });
231
397
  }
232
- injectPoweredBy();
398
+
399
+ //#endregion
400
+ //#region src/client/carousel.ts
401
+ /**
402
+ * Carousel position maths, kept pure so it can be tested without a DOM.
403
+ *
404
+ * The strip is a RING, not a scroll range: the cards form a circle, and an
405
+ * arrow steps one card around it forever. Clicking next from the last
406
+ * position lands on the first, and clicking prev from the first lands on the
407
+ * last — and crucially, every intermediate position exists. A four-card ring
408
+ * showing three at a time has FOUR positions, not two:
409
+ *
410
+ * cards 1 2 3 4, three visible
411
+ * pos 0 → [1 2 3]
412
+ * pos 1 → [2 3 4]
413
+ * pos 2 → [3 4 1] ← the wrap is visible, not a jump back
414
+ * pos 3 → [4 1 2]
415
+ * pos 4 → [1 2 3] same as pos 0
416
+ *
417
+ * A plain scroll container cannot express that: its range is only
418
+ * `content - viewport` (one step, for four cards), and positions 2 and 3 have
419
+ * no place to live. So the card list is rendered {@link COPIES} times over
420
+ * and the window travels through the middle copy, where both neighbours
421
+ * always have content.
422
+ *
423
+ * ## Why the copies, and how the seam stays invisible
424
+ *
425
+ * With the list rendered three times, a window at physical position `p` shows
426
+ * `cards[p .. p+VISIBLE-1]`, and position `2n` shows exactly what position `n`
427
+ * shows. That equivalence is the seam: once a slide settles at `2n`, the
428
+ * track can be placed at `n` in the same frame with transitions off, and
429
+ * nothing on screen changes. The same holds at the other end, where `n-1` and
430
+ * `2n-1` are the same window.
431
+ *
432
+ * The rewind happens only AFTER the slide finishes — never before. Jumping
433
+ * first would put the destination on screen instantly and then animate away
434
+ * from it, so a click at the seam would appear to skip a card. This is the
435
+ * one law in this module that a reader is likely to get backwards.
436
+ *
437
+ * @module @huaweidevtools/aishell-dsh-brand/client/carousel
438
+ */
439
+ /**
440
+ * How many times the card list is rendered.
441
+ *
442
+ * Three is the minimum that works. The window lives in the middle copy, so it
443
+ * needs one full copy behind it (to show the cards before the start) and one
444
+ * ahead (to show the cards past the end while a slide is finishing at the
445
+ * seam, where the position may briefly reach `2n` and read up to
446
+ * `2n + VISIBLE - 1`).
447
+ */
448
+ const COPIES = 3;
449
+ /** How many cards to render for a ring of `n`. */
450
+ function ringLength(n) {
451
+ return n * COPIES;
452
+ }
453
+ /**
454
+ * The physical position the ring starts at — the first card of the middle
455
+ * copy. Starting here rather than at 0 is what makes the very first prev
456
+ * click land on a real card instead of an empty track.
457
+ */
458
+ function ringStart(n) {
459
+ return n;
460
+ }
461
+ /**
462
+ * Where a settled position should be silently rewound to, or `null` when it
463
+ * is already inside the working band.
464
+ *
465
+ * The band is `[n, 2n-1]`. A slide may finish one past either end — `2n`
466
+ * going forward, `n-1` going backward — and both have an exact twin inside
467
+ * the band. See the module note for why this runs after the slide, not
468
+ * before.
469
+ */
470
+ function rewindTo(pos, n) {
471
+ if (pos >= 2 * n) return n;
472
+ if (pos < n) return 2 * n - 1;
473
+ return null;
474
+ }
475
+ /**
476
+ * Resolve one arrow press on a ring.
477
+ *
478
+ * Any rewind still owed from the previous press is settled first, so a person
479
+ * clicking faster than the slide animation cannot walk the window off the
480
+ * rendered copies — the band always absorbs one more step.
481
+ */
482
+ function ringStep(pos, delta, n) {
483
+ const owed = rewindTo(pos, n);
484
+ const from = owed ?? pos;
485
+ return {
486
+ from,
487
+ to: from + delta
488
+ };
489
+ }
490
+
491
+ //#endregion
492
+ //#region src/client/autoplay.ts
493
+ /**
494
+ * Auto-rotation policy for the example-prompt strip.
495
+ *
496
+ * Kept apart from the component and free of DOM, for the same reason
497
+ * `carousel.ts` is: the DECISION is what needs pinning down, and a decision
498
+ * buried in an effect can only be checked by watching a browser.
499
+ *
500
+ * ## Why the strip advances on its own
501
+ *
502
+ * The strip shows three of four cards, so a card is always off screen and a
503
+ * reader who never touches the arrows never learns it exists. Rotation is what
504
+ * makes the set self-evident. The cost is that content moves without being
505
+ * asked to, which is why every reason to stop is collected here rather than
506
+ * decided piecemeal at the call site.
507
+ *
508
+ * ## Every reason to stop
509
+ *
510
+ * - `engaged` — the pointer is over the strip or focus is inside it. Someone
511
+ * reaching for a card must not have it slide out from under the cursor;
512
+ * this is the pause the feature was asked for, and `focus` is the half of
513
+ * it that covers a reader who never uses a pointer.
514
+ * - `reducedMotion` — the reader asked the platform for less movement. An
515
+ * element that moves by itself for more than five seconds is exactly what
516
+ * `prefers-reduced-motion` exists to suppress, and a hover pause cannot help
517
+ * the reader who cannot hover. Honouring it is what keeps the feature from
518
+ * being motion no one consented to.
519
+ * - `visible` — the tab is hidden. A background tab still fires timers, so
520
+ * without this the strip would churn through slides nobody can see and land
521
+ * somewhere arbitrary on return.
522
+ * - `ready` — the ring has not been measured yet. Advancing before the step
523
+ * is known would move the track by a wrong distance; see the measurement
524
+ * note in `SuggestionCards.tsx`.
525
+ * - `ring` — three cards or fewer all fit, so there is nowhere to advance to.
526
+ * A strip with nothing to scroll must not appear to scroll.
527
+ *
528
+ * @module @huaweidevtools/aishell-dsh-brand/client/autoplay
529
+ */
530
+ /**
531
+ * How long each card is shown before the strip advances.
532
+ *
533
+ * Fixed rather than configurable: this is a hint at what the product can do,
534
+ * not a reading surface, and a setting nobody asked for is a setting nobody
535
+ * finds.
536
+ */
537
+ const AUTOPLAY_INTERVAL_MS = 1500;
538
+ /**
539
+ * Whether the strip should be advancing on its own right now.
540
+ *
541
+ * Expressed as one predicate rather than a chain of early returns inside the
542
+ * timer, so that adding a reason to stop is a change to this list and its
543
+ * test — not a change to the effect that schedules the ticks.
544
+ */
545
+ function autoplayActive(inputs) {
546
+ return inputs.ring && inputs.ready && !inputs.engaged && !inputs.reducedMotion && inputs.visible;
547
+ }
548
+
549
+ //#endregion
550
+ //#region src/client/hero-content.ts
551
+ /**
552
+ * The mount point the suggestion strip renders into, inside the hero.
553
+ *
554
+ * ## Why a portal, and not CSS placement
555
+ *
556
+ * The strip is a `conversation.input.dock` entry, and that band renders BELOW
557
+ * the hero workspace row in the composer stack. Measured on the live page
558
+ * (1280×900, DSH 0.2.x):
559
+ *
560
+ * hero (headline) y=288 h=74
561
+ * hero workspace row y=374 h=28
562
+ * conversation.input.dock y=411 h=80
563
+ * the input card y=491
564
+ *
565
+ * The product wants the cards directly under the headline, and the workspace
566
+ * row sits between. A negative margin cannot cross it — the row would be
567
+ * covered — and the row's own subtree is not reachable from the dock, since
568
+ * the two are separate children of the composer stack.
569
+ *
570
+ * The hero, however, already renders an empty content div for exactly this
571
+ * purpose. Its stack is: headline → (the "Powered by" label) → `_body`, where
572
+ * `_body` is `flex-direction: column` and left empty by the official shell.
573
+ * That is the hero's content area, and putting the cards there is using the
574
+ * slot as intended rather than fighting the layout.
575
+ *
576
+ * ## How the mount is found
577
+ *
578
+ * By the hero's own class suffix, from the headline outward — `[class*="_body"]`
579
+ * alone is ambiguous (the conversation root has a `_body` too, and the live
580
+ * page has two). So the search starts at a `[class*="_headline"]`, steps to its
581
+ * parent (the hero stack), and takes that stack's own `_body` child. The
582
+ * headline is unique on the page and is what makes the walk unambiguous.
583
+ *
584
+ * Children are walked directly rather than with `:scope >`. The two are
585
+ * equivalent here, and the direct walk keeps these helpers working against the
586
+ * hand-rolled DOM stub the specs use (which has no selector engine) instead of
587
+ * leaving the module's real logic untested.
588
+ *
589
+ * The classes are CSS-module hashes (`pXSMma_body`), hence the suffix match.
590
+ * A base bump that renames them fails closed: {@link findHeroContent} returns
591
+ * null, the portal renders nothing, and the arrows and cards simply do not
592
+ * appear — the strip is never left in the wrong parent.
593
+ *
594
+ * @module @huaweidevtools/aishell-dsh-brand/client/hero-content
595
+ */
596
+ /** The first direct child whose class contains `fragment`. */
597
+ function childWithClass(parent, fragment) {
598
+ for (const child of Array.from(parent.children)) if (child.className.includes(fragment)) return child;
599
+ return null;
600
+ }
601
+ /** The first direct child carrying `data-<name>`. */
602
+ function childWithData(parent, attribute) {
603
+ for (const child of Array.from(parent.children)) if (child.hasAttribute(attribute)) return child;
604
+ return null;
605
+ }
606
+ /** The child marker, so a disposer can find exactly the node it made. */
607
+ const MOUNT_ATTRIBUTE = "data-aishell-suggestions-mount";
608
+ /**
609
+ * The hero's content area, or `null` when the hero is not on screen.
610
+ *
611
+ * Returns null rather than throwing: a New Session that has not painted its
612
+ * hero yet is a normal state (and the strip retries on the next frame), not an
613
+ * error.
614
+ */
615
+ function findHeroContent(target) {
616
+ if (typeof target.querySelector !== "function") return null;
617
+ const headline = target.querySelector("[class*=\"_headline\"]");
618
+ const stack = headline?.parentElement ?? null;
619
+ if (stack === null) return null;
620
+ return childWithClass(stack, "_body");
621
+ }
622
+ /**
623
+ * A container for the strip inside the hero content area.
624
+ *
625
+ * The hero body is `display: block` and zero-height, so the strip cannot rely
626
+ * on it for layout and must not inherit that block context. The wrapper is
627
+ * created here rather than rendered by React into the official div, because
628
+ * React does not own that node — it owns only what the portal puts inside.
629
+ */
630
+ function ensureHeroMount(content) {
631
+ const existing = childWithData(content, MOUNT_ATTRIBUTE);
632
+ if (existing !== null) return existing;
633
+ const mount = content.ownerDocument.createElement("div");
634
+ mount.setAttribute(MOUNT_ATTRIBUTE, "");
635
+ content.appendChild(mount);
636
+ return mount;
637
+ }
638
+ /**
639
+ * Remove the mount this module created, if it is still there.
640
+ *
641
+ * Only the mount is removed — never the host div, which belongs to the shell
642
+ * and is reused on the next New Session.
643
+ */
644
+ function removeHeroMount(content) {
645
+ childWithData(content, MOUNT_ATTRIBUTE)?.remove();
646
+ }
647
+
648
+ //#endregion
649
+ //#region src/client/SuggestionCards.tsx
650
+ /** How many cards a full-width strip shows at once. */
651
+ const VISIBLE = 3;
652
+ /**
653
+ * How much of a card's hue is washed into its own background, in percent.
654
+ *
655
+ * The card's ground is the shell's translucent interactive pair (neutral), and
656
+ * this mixes the card's hue into BOTH ends of that pair so a card carries a
657
+ * trace of its tile's colour without the hover turning into a colour change.
658
+ *
659
+ * 6 is measured rather than picked. Three things constrain it, and the numbers
660
+ * below are computed from the real tokens in both themes:
661
+ *
662
+ * - LEGIBILITY. The gloss text is the binding constraint, not the title. On
663
+ * the light theme's ground the gloss measures 3.33 at 0%, 3.14 at 8%, and
664
+ * falls to 2.90 at 14% — under the 3:1 bar. So the ceiling is ~12%.
665
+ * - VISIBILITY. A tint is not worth its cost below the just-noticeable
666
+ * difference (~2.3 dE). The card moves 3.4–5.1 dE away from its own
667
+ * untinted self at 6%, and only 1.7 dE at 3%, so the floor is ~4%.
668
+ * - THE TILE'S SEPARATION. The tile sits ON this ground, so tinting the
669
+ * ground eats the tile's contrast. Amber is the tightest case: 2.51 at 0%,
670
+ * 2.39 at 6%, 2.31 at 10%. It survives, but this is why the percentage
671
+ * cannot be pushed for looks.
672
+ *
673
+ * 6% is the low end of the safe band, chosen deliberately: the tint is there to
674
+ * warm the card toward its subject, not to become a colour block. It is not a
675
+ * way to tell the cards apart — at 6% two different cards are only 2.4 dE
676
+ * apart, barely at the threshold. The tile is what identifies a card.
677
+ */
678
+ const HUE_WASH = 6;
679
+ /**
680
+ * The hero's content element, kept current across the hero's lifecycle.
681
+ *
682
+ * The hero mounts, unmounts and is REPLACED on each New Session, so the node
683
+ * found at first paint goes stale. A `MutationObserver` is the only thing that
684
+ * sees a replacement: the observer re-scans, and the scan is cheap because it
685
+ * returns immediately while the current mount is still connected.
686
+ *
687
+ * @param active - whether the strip should be mounted at all.
688
+ * @returns the element to portal into, or `null` while the hero is absent.
689
+ */
690
+ function useHeroMount(active) {
691
+ const [mount, setMount] = (0, react.useState)(null);
692
+ const mountRef = (0, react.useRef)(null);
693
+ (0, react.useEffect)(() => {
694
+ if (!active) {
695
+ setMount(null);
696
+ return;
697
+ }
698
+ let cancelled = false;
699
+ const scan = () => {
700
+ if (cancelled) return;
701
+ const current = mountRef.current;
702
+ if (current !== null && current.isConnected) {
703
+ setMount((seen) => seen === current ? seen : current);
704
+ return;
705
+ }
706
+ const content = findHeroContent(document);
707
+ if (content === null) {
708
+ mountRef.current = null;
709
+ setMount(null);
710
+ return;
711
+ }
712
+ const next = ensureHeroMount(content);
713
+ if (next !== mountRef.current) {
714
+ mountRef.current = next;
715
+ setMount(next);
716
+ }
717
+ };
718
+ scan();
719
+ const observer = new MutationObserver(scan);
720
+ observer.observe(document.body, {
721
+ childList: true,
722
+ subtree: true
723
+ });
724
+ return () => {
725
+ cancelled = true;
726
+ observer.disconnect();
727
+ };
728
+ }, [active]);
729
+ (0, react.useEffect)(() => () => {
730
+ const current = mountRef.current;
731
+ mountRef.current = null;
732
+ if (current === null) return;
733
+ removeHeroMount(current.parentElement ?? current);
734
+ }, []);
735
+ return mount;
736
+ }
737
+ /** Whether the reader has asked the platform for reduced motion. */
738
+ function useReducedMotion() {
739
+ const [reduced, setReduced] = (0, react.useState)(false);
740
+ (0, react.useEffect)(() => {
741
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
742
+ setReduced(query.matches);
743
+ const onChange = (event) => {
744
+ setReduced(event.matches);
745
+ };
746
+ query.addEventListener("change", onChange);
747
+ return () => {
748
+ query.removeEventListener("change", onChange);
749
+ };
750
+ }, []);
751
+ return reduced;
752
+ }
753
+ /** Whether the tab is on screen. A hidden tab must not churn through slides. */
754
+ function useTabVisible() {
755
+ const [visible, setVisible] = (0, react.useState)(true);
756
+ (0, react.useEffect)(() => {
757
+ const onVisibility = () => {
758
+ setVisible(document.visibilityState === "visible");
759
+ };
760
+ onVisibility();
761
+ document.addEventListener("visibilitychange", onVisibility);
762
+ return () => {
763
+ document.removeEventListener("visibilitychange", onVisibility);
764
+ };
765
+ }, []);
766
+ return visible;
767
+ }
768
+ /**
769
+ * One card.
770
+ *
771
+ * A button, so keyboard activation, focus handling and disabled semantics come
772
+ * for free. The icon is a decorative tile marked `aria-hidden`; the title and
773
+ * gloss carry the meaning.
774
+ */
775
+ function Card({ suggestion, onPick, t }) {
776
+ const [hover, setHover] = (0, react.useState)(false);
777
+ const [focus, setFocus] = (0, react.useState)(false);
778
+ const lit = hover || focus;
779
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
780
+ type: "button",
781
+ onClick: () => {
782
+ onPick(t(suggestion.promptKey));
783
+ },
784
+ onPointerEnter: () => {
785
+ setHover(true);
786
+ },
787
+ onPointerLeave: () => {
788
+ setHover(false);
789
+ },
790
+ onFocus: () => {
791
+ setFocus(true);
792
+ },
793
+ onBlur: () => {
794
+ setFocus(false);
795
+ },
796
+ style: {
797
+ flex: `0 0 calc((100cqw - ${VISIBLE - 1} * var(--dsh-suggestion-gap)) / ${VISIBLE})`,
798
+ display: "flex",
799
+ alignItems: "flex-start",
800
+ gap: 12,
801
+ boxSizing: "border-box",
802
+ minWidth: 0,
803
+ padding: "14px",
804
+ textAlign: "left",
805
+ font: "inherit",
806
+ cursor: "pointer",
807
+ color: "var(--dsw-alias-label-primary)",
808
+ background: lit ? `color-mix(in srgb, ${suggestion.hue.tile} ${HUE_WASH}%, var(--dsw-alias-interactive-bg-active))` : `color-mix(in srgb, ${suggestion.hue.tile} ${HUE_WASH}%, var(--dsw-alias-interactive-bg-hover))`,
809
+ border: "none",
810
+ borderRadius: "var(--dsw-radius-md, 12px)",
811
+ transition: "background-color 120ms ease",
812
+ outline: focus ? "2px solid var(--dsw-alias-state-business-primary)" : "none",
813
+ outlineOffset: 2
814
+ },
815
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
816
+ "aria-hidden": true,
817
+ style: {
818
+ flex: "none",
819
+ display: "grid",
820
+ placeItems: "center",
821
+ width: 32,
822
+ height: 32,
823
+ marginTop: 1,
824
+ borderRadius: "var(--dsw-radius-sm, 8px)",
825
+ background: suggestion.hue.tile,
826
+ color: "var(--dsw-static-neutral-bluish-1000, #0f1115)"
827
+ },
828
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuggestionIconArt, {
829
+ icon: suggestion.icon,
830
+ size: 17
831
+ })
832
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
833
+ style: {
834
+ display: "flex",
835
+ flexDirection: "column",
836
+ gap: 3,
837
+ minWidth: 0
838
+ },
839
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
840
+ style: {
841
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
842
+ fontWeight: 600,
843
+ lineHeight: "19px",
844
+ overflow: "hidden",
845
+ textOverflow: "ellipsis",
846
+ whiteSpace: "nowrap"
847
+ },
848
+ children: t(suggestion.titleKey)
849
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
850
+ style: {
851
+ fontSize: "12px",
852
+ lineHeight: "17px",
853
+ color: "var(--dsw-alias-label-tertiary)",
854
+ display: "-webkit-box",
855
+ WebkitLineClamp: 2,
856
+ WebkitBoxOrient: "vertical",
857
+ overflow: "hidden"
858
+ },
859
+ children: t(suggestion.glossKey)
860
+ })]
861
+ })]
862
+ });
863
+ }
864
+ /** One carousel arrow. */
865
+ function Arrow({ direction, label, onClick }) {
866
+ const [hover, setHover] = (0, react.useState)(false);
867
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
868
+ type: "button",
869
+ "aria-label": label,
870
+ onClick,
871
+ onPointerEnter: () => {
872
+ setHover(true);
873
+ },
874
+ onPointerLeave: () => {
875
+ setHover(false);
876
+ },
877
+ style: {
878
+ flex: "none",
879
+ display: "inline-flex",
880
+ alignItems: "center",
881
+ justifyContent: "center",
882
+ width: 26,
883
+ height: 26,
884
+ padding: 0,
885
+ cursor: "pointer",
886
+ color: "var(--dsw-alias-label-secondary)",
887
+ background: hover ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
888
+ border: "none",
889
+ borderRadius: 999,
890
+ transition: "background 120ms ease"
891
+ },
892
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
893
+ "aria-hidden": true,
894
+ width: "14",
895
+ height: "14",
896
+ viewBox: "0 0 24 24",
897
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
898
+ d: direction === "prev" ? "M15 5l-7 7 7 7" : "M9 5l7 7-7 7",
899
+ fill: "none",
900
+ stroke: "currentColor",
901
+ strokeWidth: "2",
902
+ strokeLinecap: "round",
903
+ strokeLinejoin: "round"
904
+ })
905
+ })
906
+ });
907
+ }
908
+ function SuggestionCards(props) {
909
+ const blank = props.useSession((session) => session.blank);
910
+ const setDraft = props.inputActions.setDraft;
911
+ const t = props.t;
912
+ const n = SUGGESTIONS.length;
913
+ const ring = n > VISIBLE;
914
+ const viewportRef = (0, react.useRef)(null);
915
+ const trackRef = (0, react.useRef)(null);
916
+ const posRef = (0, react.useRef)(ringStart(n));
917
+ const stepRef = (0, react.useRef)(0);
918
+ const [ready, setReady] = (0, react.useState)(!ring);
919
+ const [engaged, setEngaged] = (0, react.useState)(false);
920
+ const reducedMotion = useReducedMotion();
921
+ const visible = useTabVisible();
922
+ const mount = useHeroMount(blank);
923
+ /**
924
+ * The slide's own animation, written by this component and nobody else.
925
+ *
926
+ * `transform` and `transition` are BOTH owned by the imperative code below
927
+ * and are deliberately absent from the `style` prop further down. They have
928
+ * to have exactly one owner: a value declared in JSX is only re-applied when
929
+ * React sees it CHANGE, so the first time the effect writes
930
+ * `track.style.transition = 'none'` (or clears it with `''`), React's
931
+ * declared value is gone and React has no reason to write it again. The
932
+ * track then keeps the browser default — `all 0s` — and every "slide" lands
933
+ * instantly with a hard cut. That is a silent failure: positions, wrapping
934
+ * and the ring all still measure correct, so only watching the motion
935
+ * reveals it.
936
+ *
937
+ * A slow, eased curve rather than a short linear one: the strip shows one
938
+ * card leaving and one arriving, and at 320ms with a snappy curve it reads
939
+ * as a jump cut rather than travel.
940
+ *
941
+ * The curve is `cubic-bezier(.33,0,.15,1)`, chosen for its END VELOCITIES
942
+ * and not its looks on a graph. Measured as distance-per-unit-time (1.0
943
+ * being a linear slide) it starts at 0.4, peaks near 2.7, and finishes at
944
+ * 0.05 — eased away from rest and brought gently to rest. The obvious
945
+ * "smooth" pick, `cubic-bezier(.16,.84,.28,1)` (a material-style fast-out),
946
+ * starts at 4.39: it covers 44% of the distance in the first 10% of the
947
+ * time and then crawls. On a row of cards that reads as a snap followed by
948
+ * a stall, which is the opposite of what a carousel wants.
949
+ */
950
+ const SLIDE_MS = 520;
951
+ const SLIDE_EASING = "cubic-bezier(.33,0,.15,1)";
952
+ /** Place the track at a position with no transition. */
953
+ const snapTo = (0, react.useCallback)((pos) => {
954
+ const track = trackRef.current;
955
+ if (track === null) return;
956
+ track.style.transition = "none";
957
+ track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
958
+ }, []);
959
+ /** Animate the track to a position. */
960
+ const slideTo = (0, react.useCallback)((pos) => {
961
+ const track = trackRef.current;
962
+ if (track === null) return;
963
+ track.style.transition = `transform ${SLIDE_MS}ms ${SLIDE_EASING}`;
964
+ track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
965
+ }, []);
966
+ const autoplayTimerRef = (0, react.useRef)(0);
967
+ const latestStepRef = (0, react.useRef)(() => {});
968
+ /**
969
+ * Schedule the next auto-rotation tick one full interval from now,
970
+ * replacing any tick already pending.
971
+ *
972
+ * Called by every `step`, which is what makes the cadence immune to a tick
973
+ * landing mid-slide.
974
+ */
975
+ const bumpAutoplay = (0, react.useCallback)(() => {
976
+ window.clearTimeout(autoplayTimerRef.current);
977
+ autoplayTimerRef.current = window.setTimeout(() => {
978
+ latestStepRef.current(1);
979
+ }, AUTOPLAY_INTERVAL_MS);
980
+ }, []);
981
+ /**
982
+ * Re-measure one step and re-apply the current position.
983
+ *
984
+ * The step is read from a real card rather than computed from the viewport
985
+ * width so the `cqw` fraction in `Card`'s flex-basis and this number cannot
986
+ * drift apart.
987
+ *
988
+ * Re-measuring is not optional. The first frame measures BEFORE the cards
989
+ * have their final width: `containerType` and the `cqw` basis settle in a
990
+ * later layout pass, so the first reading is a few percent too large (242px
991
+ * where the settled card is 213px). A step kept from that frame puts every
992
+ * position slightly too far along, which compounds — by the fourth card the
993
+ * window has drifted a whole card and shows two cards plus a gap.
994
+ */
995
+ const remeasure = (0, react.useCallback)(() => {
996
+ const track = trackRef.current;
997
+ if (track === null) return false;
998
+ const first = track.firstElementChild;
999
+ if (first === null) return false;
1000
+ const gap = Number.parseFloat(getComputedStyle(track).columnGap) || 0;
1001
+ const step$1 = first.getBoundingClientRect().width + gap;
1002
+ if (!(step$1 > 0)) return false;
1003
+ const changed = step$1 !== stepRef.current;
1004
+ stepRef.current = step$1;
1005
+ if (changed) snapTo(posRef.current);
1006
+ return true;
1007
+ }, [snapTo]);
1008
+ (0, react.useEffect)(() => {
1009
+ if (!blank || !ring || mount === null) return;
1010
+ let cancelled = false;
1011
+ const settle = () => {
1012
+ if (cancelled) return;
1013
+ if (!remeasure()) {
1014
+ frame = window.requestAnimationFrame(settle);
1015
+ return;
1016
+ }
1017
+ posRef.current = ringStart(n);
1018
+ snapTo(posRef.current);
1019
+ window.requestAnimationFrame(() => {
1020
+ if (cancelled) return;
1021
+ setReady(true);
1022
+ const viewport = viewportRef.current;
1023
+ if (viewport !== null) {
1024
+ observer = new ResizeObserver(() => {
1025
+ remeasure();
1026
+ });
1027
+ observer.observe(viewport);
1028
+ }
1029
+ });
1030
+ };
1031
+ let frame = window.requestAnimationFrame(settle);
1032
+ let observer;
1033
+ return () => {
1034
+ cancelled = true;
1035
+ window.cancelAnimationFrame(frame);
1036
+ observer?.disconnect();
1037
+ };
1038
+ }, [
1039
+ blank,
1040
+ ring,
1041
+ n,
1042
+ mount,
1043
+ remeasure,
1044
+ snapTo
1045
+ ]);
1046
+ /**
1047
+ * Apply whichever rewind the settling position owes.
1048
+ *
1049
+ * Runs when the track reaches its destination, never before the slide:
1050
+ * jumping to the twin position first would put the destination on screen
1051
+ * immediately and then animate away from it, so a click at the seam would
1052
+ * look like it skipped a card. See the module note in carousel.ts.
1053
+ *
1054
+ * The rewind lands on a position showing exactly the cards already on
1055
+ * screen, so `snapTo`'s lack of animation is invisible.
1056
+ *
1057
+ * Only a COMPLETED transition reaches here: an interrupted one fires
1058
+ * `transitioncancel` instead, so there is no partial-position case to
1059
+ * filter out. A person clicking faster than a slide therefore skips this
1060
+ * handler entirely — the rewind they owe is settled by `ringStep` at the
1061
+ * start of their next press, which snaps the same invisible twin.
1062
+ */
1063
+ const onSettle = (0, react.useCallback)((event) => {
1064
+ if (!ring) return;
1065
+ if (event.target !== trackRef.current) return;
1066
+ if (event.propertyName !== "transform") return;
1067
+ const owed = rewindTo(posRef.current, n);
1068
+ if (owed === null) return;
1069
+ posRef.current = owed;
1070
+ snapTo(owed);
1071
+ }, [
1072
+ ring,
1073
+ n,
1074
+ snapTo
1075
+ ]);
1076
+ /** One step of the ring, from an arrow or from auto-rotation. */
1077
+ const step = (0, react.useCallback)((delta) => {
1078
+ if (!ring || !ready) return;
1079
+ bumpAutoplay();
1080
+ const { from, to } = ringStep(posRef.current, delta, n);
1081
+ if (from !== posRef.current) {
1082
+ posRef.current = from;
1083
+ snapTo(from);
1084
+ }
1085
+ posRef.current = to;
1086
+ slideTo(to);
1087
+ }, [
1088
+ ring,
1089
+ ready,
1090
+ n,
1091
+ snapTo,
1092
+ slideTo,
1093
+ bumpAutoplay
1094
+ ]);
1095
+ (0, react.useEffect)(() => {
1096
+ latestStepRef.current = step;
1097
+ }, [step]);
1098
+ (0, react.useEffect)(() => {
1099
+ if (!autoplayActive({
1100
+ ring,
1101
+ ready,
1102
+ engaged,
1103
+ reducedMotion,
1104
+ visible
1105
+ })) return;
1106
+ bumpAutoplay();
1107
+ return () => {
1108
+ window.clearTimeout(autoplayTimerRef.current);
1109
+ };
1110
+ }, [
1111
+ ring,
1112
+ ready,
1113
+ engaged,
1114
+ reducedMotion,
1115
+ visible,
1116
+ bumpAutoplay
1117
+ ]);
1118
+ if (!blank) return null;
1119
+ if (n === 0) return null;
1120
+ if (mount === null) return null;
1121
+ const cards = ring ? Array.from({ length: ringLength(n) }, (_, i) => SUGGESTIONS[i % n]) : SUGGESTIONS;
1122
+ const strip = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1123
+ ref: viewportRef,
1124
+ "data-aishell-suggestions": n,
1125
+ style: {
1126
+ overflow: "hidden",
1127
+ minWidth: 0,
1128
+ flex: "1 1 auto",
1129
+ containerType: "inline-size"
1130
+ },
1131
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1132
+ ref: trackRef,
1133
+ "data-aishell-suggestion-track": ring ? "ring" : "flat",
1134
+ onTransitionEnd: onSettle,
1135
+ style: {
1136
+ display: "flex",
1137
+ alignItems: "stretch",
1138
+ gap: "var(--dsh-suggestion-gap)",
1139
+ width: "max-content",
1140
+ willChange: "transform"
1141
+ },
1142
+ children: cards.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
1143
+ suggestion,
1144
+ onPick: setDraft,
1145
+ t
1146
+ }, `${i}:${suggestion.id}`))
1147
+ })
1148
+ });
1149
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1150
+ onPointerEnter: () => {
1151
+ setEngaged(true);
1152
+ },
1153
+ onPointerLeave: () => {
1154
+ setEngaged(false);
1155
+ },
1156
+ onFocus: () => {
1157
+ setEngaged(true);
1158
+ },
1159
+ onBlur: (event) => {
1160
+ const next = event.relatedTarget;
1161
+ if (next === null || !event.currentTarget.contains(next)) setEngaged(false);
1162
+ },
1163
+ style: {
1164
+ ["--dsh-suggestion-gap"]: "10px",
1165
+ display: "flex",
1166
+ alignItems: "center",
1167
+ gap: 6,
1168
+ minWidth: 0,
1169
+ margin: "12px 0 22px"
1170
+ },
1171
+ children: [
1172
+ ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
1173
+ direction: "prev",
1174
+ label: t("suggestion.previous"),
1175
+ onClick: () => {
1176
+ step(-1);
1177
+ }
1178
+ }),
1179
+ strip,
1180
+ ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
1181
+ direction: "next",
1182
+ label: t("suggestion.next"),
1183
+ onClick: () => {
1184
+ step(1);
1185
+ }
1186
+ })
1187
+ ]
1188
+ }), mount);
1189
+ }
1190
+
1191
+ //#endregion
1192
+ //#region src/client/locales.ts
1193
+ /**
1194
+ * aishell brand locale dictionaries.
1195
+ *
1196
+ * The brand plugin owns its own namespace rather than reusing `aishell`
1197
+ * (which `aishell-credentials` registers): a namespace's texts have exactly
1198
+ * one owner, and a duplicate `(ns, locale)` registration throws.
1199
+ *
1200
+ * ## Why content lives in a dictionary
1201
+ *
1202
+ * The suggestion cards are the only strings in this plugin a person reads,
1203
+ * and they are CONTENT rather than interface chrome — a card title, its
1204
+ * one-line gloss, and the prompt a click inserts. They still belong here:
1205
+ * a card that says「云资源查询」to an English reader is worse than no card,
1206
+ * and the prompt it inserts would start an English conversation in Chinese.
1207
+ *
1208
+ * Chinese is the key-set source of truth; English is complete against it and
1209
+ * is also the terminal fallback, so every locale other than `zh` reads
1210
+ * English. That is the framework's own chain (`LOCALE_IDS = ["zh", "en"]`,
1211
+ * with English carrying no fallback) — there is no third dictionary to write.
1212
+ *
1213
+ * @module @huaweidevtools/aishell-dsh-brand/client/locales
1214
+ */
1215
+ /** Dictionary namespace owned by this plugin. */
1216
+ const NS = "aishell-brand";
1217
+ /** Simplified Chinese dictionary (the key-set source of truth). */
1218
+ const zh = {
1219
+ "suggestion.previous": "上一个示例",
1220
+ "suggestion.next": "下一个示例",
1221
+ "suggestion.inventory.title": "云资源查询",
1222
+ "suggestion.inventory.gloss": "列出当前账号下的云资源",
1223
+ "suggestion.inventory.prompt": "请列出我的华为云资源清单,按服务分类(计算、存储、网络、数据库等),标出每类资源的数量和所在区域。",
1224
+ "suggestion.deploy.title": "部署方案推荐",
1225
+ "suggestion.deploy.gloss": "按访问量和预算给出可落地的部署路径",
1226
+ "suggestion.deploy.prompt": "我要部署一个轻量级 Web 应用,请对比几种可行的部署方案,说明各自的适用规模、月度成本量级和运维复杂度,并给出你的推荐及理由。",
1227
+ "suggestion.enterprise.title": "企业上云",
1228
+ "suggestion.enterprise.gloss": "多账号、网络分区与权限边界的整体设计",
1229
+ "suggestion.enterprise.prompt": "请为我规划一套云上 Landing Zone 架构,覆盖账号组织结构、网络分区、权限边界与审计要求,并说明各部分的设计取舍。",
1230
+ "suggestion.billing.title": "账单分析",
1231
+ "suggestion.billing.gloss": "分析上个月的云资源支出",
1232
+ "suggestion.billing.prompt": "请分析我上个月的云资源支出构成,找出占比最高的几项服务,指出其中可以优化或降配的部分,并估算调整后能节省多少。"
1233
+ };
1234
+ /** English dictionary. Complete against {@link zh}; also the terminal fallback. */
1235
+ const en = {
1236
+ "suggestion.previous": "Previous example",
1237
+ "suggestion.next": "Next example",
1238
+ "suggestion.inventory.title": "Cloud inventory",
1239
+ "suggestion.inventory.gloss": "List the resources in this account",
1240
+ "suggestion.inventory.prompt": "List the resources in my Huawei Cloud account, grouped by service (compute, storage, network, database), with the count and the region for each group.",
1241
+ "suggestion.deploy.title": "Deployment options",
1242
+ "suggestion.deploy.gloss": "A workable path for your traffic and budget",
1243
+ "suggestion.deploy.prompt": "I want to deploy a lightweight web application. Compare a few viable deployment options, covering the scale each one suits, its order-of-magnitude monthly cost, and its operational complexity, then recommend one and explain why.",
1244
+ "suggestion.enterprise.title": "Landing zone",
1245
+ "suggestion.enterprise.gloss": "Multi-account structure, network and permission boundaries",
1246
+ "suggestion.enterprise.prompt": "Design a cloud landing zone for my organisation, covering the account structure, network partitions, permission boundaries and audit requirements, and explain the trade-offs behind each part.",
1247
+ "suggestion.billing.title": "Billing analysis",
1248
+ "suggestion.billing.gloss": "Break down last month's cloud spend",
1249
+ "suggestion.billing.prompt": "Analyse my cloud spend for last month, identify the services that account for the largest share, point out what could be optimised or downsized, and estimate how much that would save."
1250
+ };
233
1251
 
234
1252
  //#endregion
235
1253
  //#region src/client/title-shadow.ts
@@ -314,11 +1332,350 @@ function installTitleShadow(target) {
314
1332
  };
315
1333
  }
316
1334
 
1335
+ //#endregion
1336
+ //#region src/client/aishell-mark.ts
1337
+ /**
1338
+ * The AI Shell mark as an inline source, shared by the runtime brand shadows.
1339
+ *
1340
+ * The slot occupants (Brand.tsx) draw the mark through a CSS module whose
1341
+ * url() the build inlines, but the runtime shadows need the bytes as a JS
1342
+ * string: the favicon shadow writes them into a link href, and the running
1343
+ * status mark shadow writes them into injected patch CSS. This module is
1344
+ * that one source, so the asset and the shadows cannot drift apart.
1345
+ * tests/aishell-mark.spec.ts pins the constant against assets/aishell.svg.
1346
+ *
1347
+ * @module @huaweidevtools/aishell-dsh-brand/client/aishell-mark
1348
+ */
1349
+ /** The AI Shell mark, verbatim assets/aishell.svg (the spec pins this). */
1350
+ const AISHELL_MARK_SVG = `<svg viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="44.000000" height="44.000000" fill="none">
1351
+ <defs>
1352
+ <linearGradient id="paint_linear_0" x1="38.5" x2="2.19999695" y1="1.90734863e-06" y2="39.5999908" gradientUnits="userSpaceOnUse">
1353
+ <stop stop-color="rgb(53,137,255)" offset="0" stop-opacity="1" />
1354
+ <stop stop-color="rgb(169,38,255)" offset="1" stop-opacity="1" />
1355
+ </linearGradient>
1356
+ </defs>
1357
+ <rect id="icon_AIShell" width="44.000000" height="44.000000" x="0.000000" y="0.000000" fill="rgb(255,255,255)" fill-opacity="0" />
1358
+ <path id="合并" d="M37.4576 0.299738C37.3775 0.139877 37.2861 0.0428196 37.1832 0.00856393C37.0917 -0.0142732 37.006 0.00856394 36.9259 0.0770754C36.8459 0.145587 36.7887 0.219808 36.7545 0.299738C36.4572 1.86408 35.7369 3.21718 34.5937 4.35904C33.4618 5.48948 32.0956 6.22027 30.495 6.55141C30.3921 6.57424 30.3178 6.64275 30.272 6.75694C30.2377 6.87113 30.2435 6.97389 30.2892 7.06524C30.3464 7.15659 30.415 7.20226 30.495 7.20226C31.2078 7.35296 31.8779 7.58759 32.5055 7.90618L32.5055 7.9062L32.5056 7.90623C33.257 8.28771 33.9473 8.78955 34.5765 9.41176C35.7312 10.5422 36.4572 11.8896 36.7545 13.4539C36.8116 13.591 36.8974 13.6823 37.0117 13.728C37.1146 13.7622 37.2061 13.7565 37.2861 13.7109C37.3775 13.6538 37.4347 13.5681 37.4576 13.4539C37.7891 11.8896 38.5151 10.5422 39.6355 9.41176C40.7674 8.2699 42.1279 7.5334 43.717 7.20226C43.8199 7.17943 43.9 7.12233 43.9571 7.03099C44.0143 6.92822 44.0143 6.83116 43.9571 6.73981C43.9 6.63705 43.8199 6.57424 43.717 6.55141C42.1507 6.22027 40.7959 5.48948 39.6527 4.35904C38.5208 3.21718 37.7891 1.86408 37.4576 0.299738ZM42.625 16.4999L42.625 33C42.625 33.9493 42.4572 34.829 42.1216 35.6392C41.786 36.4495 41.2826 37.1902 40.6114 37.8614C39.9402 38.5326 39.1994 39.036 38.3892 39.3716C37.579 39.7072 36.6992 39.875 35.75 39.875L10.9999 39.875L8.24995 39.875C7.30073 39.875 6.42102 39.7072 5.61081 39.3716C4.80058 39.036 4.05985 38.5326 3.38863 37.8614C2.71742 37.1902 2.21402 36.4494 1.87841 35.6392C1.5428 34.829 1.375 33.9493 1.375 33L1.375 11C1.375 10.0508 1.5428 9.17106 1.8784 8.36083L1.87841 8.36082C2.21402 7.55059 2.71743 6.80987 3.38864 6.13866C4.05985 5.46745 4.80057 4.96404 5.61079 4.62843L5.6108 4.62843C6.42103 4.29282 7.30076 4.12502 8.25 4.12502L27.4999 4.125C28.2699 4.125 28.8749 4.73 28.8749 5.5C28.8749 6.27 28.2699 6.875 27.4999 6.875L8.25 6.87502C7.68045 6.87502 7.15261 6.9757 6.66648 7.17706C6.18034 7.37843 5.73591 7.68047 5.33318 8.0832C4.93046 8.48593 4.62841 8.93036 4.42705 9.41649C4.22568 9.90263 4.125 10.4305 4.125 11L4.125 33C4.125 33.5696 4.22568 34.0974 4.42705 34.5835C4.62841 35.0697 4.93045 35.5141 5.33317 35.9168C5.73588 36.3195 6.18028 36.6216 6.66639 36.8229C7.15256 37.0243 7.68042 37.125 8.24999 37.125L10.9999 37.125L35.75 37.125C36.3195 37.125 36.8474 37.0243 37.3335 36.823C37.8197 36.6216 38.2641 36.3196 38.6668 35.9168C39.0695 35.5141 39.3716 35.0697 39.573 34.5835C39.7743 34.0974 39.875 33.5696 39.875 33L39.875 16.4999C39.875 15.7299 40.48 15.1249 41.25 15.1249C42.02 15.1249 42.625 15.7299 42.625 16.4999ZM10.0277 12.7777C10.5722 12.2333 11.4278 12.2333 11.9723 12.7777L20.2222 21.0277C20.4907 21.2962 20.6249 21.6203 20.6249 22C20.6249 22.3797 20.4907 22.7037 20.2222 22.9722L11.9722 31.2223C11.4277 31.7668 10.5721 31.7668 10.0277 31.2223C9.48318 30.6779 9.48317 29.8223 10.0276 29.2778L17.3054 22L10.0277 14.7223C9.48325 14.1778 9.48326 13.3222 10.0277 12.7777ZM20.625 28.875C20.625 28.105 21.23 27.5 22 27.5L33 27.5C33.77 27.5 34.375 28.105 34.375 28.875C34.375 29.645 33.77 30.25 33 30.25L22 30.25C21.23 30.25 20.625 29.645 20.625 28.875Z" fill="rgb(0,0,0)" fill-opacity="0" fill-rule="evenodd" />
1359
+ <path id="合并" d="M37.4576 0.299738C37.3775 0.139877 37.2861 0.0428196 37.1832 0.00856393C37.0917 -0.0142732 37.006 0.00856394 36.9259 0.0770754C36.8459 0.145587 36.7887 0.219808 36.7545 0.299738C36.4572 1.86408 35.7369 3.21718 34.5937 4.35904C33.4618 5.48948 32.0956 6.22027 30.495 6.55141C30.3921 6.57424 30.3178 6.64275 30.272 6.75694C30.2377 6.87113 30.2435 6.97389 30.2892 7.06524C30.3464 7.15659 30.415 7.20226 30.495 7.20226C31.2078 7.35296 31.8779 7.58759 32.5055 7.90618L32.5055 7.9062L32.5056 7.90623C33.257 8.28771 33.9473 8.78955 34.5765 9.41176C35.7312 10.5422 36.4572 11.8896 36.7545 13.4539C36.8116 13.591 36.8974 13.6823 37.0117 13.728C37.1146 13.7622 37.2061 13.7565 37.2861 13.7109C37.3775 13.6538 37.4347 13.5681 37.4576 13.4539C37.7891 11.8896 38.5151 10.5422 39.6355 9.41176C40.7674 8.2699 42.1279 7.5334 43.717 7.20226C43.8199 7.17943 43.9 7.12233 43.9571 7.03099C44.0143 6.92822 44.0143 6.83116 43.9571 6.73981C43.9 6.63705 43.8199 6.57424 43.717 6.55141C42.1507 6.22027 40.7959 5.48948 39.6527 4.35904C38.5208 3.21718 37.7891 1.86408 37.4576 0.299738ZM42.625 16.4999L42.625 33C42.625 33.9493 42.4572 34.829 42.1216 35.6392C41.786 36.4495 41.2826 37.1902 40.6114 37.8614C39.9402 38.5326 39.1994 39.036 38.3892 39.3716C37.579 39.7072 36.6992 39.875 35.75 39.875L10.9999 39.875L8.24995 39.875C7.30073 39.875 6.42102 39.7072 5.61081 39.3716C4.80058 39.036 4.05985 38.5326 3.38863 37.8614C2.71742 37.1902 2.21402 36.4494 1.87841 35.6392C1.5428 34.829 1.375 33.9493 1.375 33L1.375 11C1.375 10.0508 1.5428 9.17106 1.8784 8.36083L1.87841 8.36082C2.21402 7.55059 2.71743 6.80987 3.38864 6.13866C4.05985 5.46745 4.80057 4.96404 5.61079 4.62843L5.6108 4.62843C6.42103 4.29282 7.30076 4.12502 8.25 4.12502L27.4999 4.125C28.2699 4.125 28.8749 4.73 28.8749 5.5C28.8749 6.27 28.2699 6.875 27.4999 6.875L8.25 6.87502C7.68045 6.87502 7.15261 6.9757 6.66648 7.17706C6.18034 7.37843 5.73591 7.68047 5.33318 8.0832C4.93046 8.48593 4.62841 8.93036 4.42705 9.41649C4.22568 9.90263 4.125 10.4305 4.125 11L4.125 33C4.125 33.5696 4.22568 34.0974 4.42705 34.5835C4.62841 35.0697 4.93045 35.5141 5.33317 35.9168C5.73588 36.3195 6.18028 36.6216 6.66639 36.8229C7.15256 37.0243 7.68042 37.125 8.24999 37.125L10.9999 37.125L35.75 37.125C36.3195 37.125 36.8474 37.0243 37.3335 36.823C37.8197 36.6216 38.2641 36.3196 38.6668 35.9168C39.0695 35.5141 39.3716 35.0697 39.573 34.5835C39.7743 34.0974 39.875 33.5696 39.875 33L39.875 16.4999C39.875 15.7299 40.48 15.1249 41.25 15.1249C42.02 15.1249 42.625 15.7299 42.625 16.4999ZM10.0277 12.7777C10.5722 12.2333 11.4278 12.2333 11.9723 12.7777L20.2222 21.0277C20.4907 21.2962 20.6249 21.6203 20.6249 22C20.6249 22.3797 20.4907 22.7037 20.2222 22.9722L11.9722 31.2223C11.4277 31.7668 10.5721 31.7668 10.0277 31.2223C9.48318 30.6779 9.48317 29.8223 10.0276 29.2778L17.3054 22L10.0277 14.7223C9.48325 14.1778 9.48326 13.3222 10.0277 12.7777ZM20.625 28.875C20.625 28.105 21.23 27.5 22 27.5L33 27.5C33.77 27.5 34.375 28.105 34.375 28.875C34.375 29.645 33.77 30.25 33 30.25L22 30.25C21.23 30.25 20.625 29.645 20.625 28.875Z" fill="url(#paint_linear_0)" fill-rule="evenodd" />
1360
+ </svg>`;
1361
+ /**
1362
+ * The mark as a self-contained data URI.
1363
+ *
1364
+ * URL-encoded rather than base64 (shorter, and the gradient reads on both
1365
+ * light and dark themes, which is what lets one URI serve every surface).
1366
+ */
1367
+ const AISHELL_MARK_URI = `data:image/svg+xml,${encodeURIComponent(AISHELL_MARK_SVG)}`;
1368
+
1369
+ //#endregion
1370
+ //#region src/client/favicon-shadow.ts
1371
+ /** The official product names the manifest shadow rewrites. */
1372
+ const OFFICIAL_MANIFEST_NAMES = {
1373
+ name: "DeepSeek Harness",
1374
+ short_name: "DSH"
1375
+ };
1376
+ /** The branded names that shadow them. */
1377
+ const BRANDED_MANIFEST_NAMES = {
1378
+ name: "AI Shell",
1379
+ short_name: "AI Shell"
1380
+ };
1381
+ /**
1382
+ * Rewrite a manifest document's display names in place.
1383
+ *
1384
+ * Only the two fields the dist actually ships are touched, and only when
1385
+ * they carry their official values — anything else in the document (icons,
1386
+ * start_url, scope, display) passes through untouched, and a manifest the
1387
+ * base customized keeps its customization.
1388
+ */
1389
+ function rebrandManifest(manifest) {
1390
+ const branded = { ...manifest };
1391
+ for (const key of Object.keys(OFFICIAL_MANIFEST_NAMES)) if (branded[key] === OFFICIAL_MANIFEST_NAMES[key]) branded[key] = BRANDED_MANIFEST_NAMES[key];
1392
+ return branded;
1393
+ }
1394
+ /** The href every icon link is shadowed with (exposed for the spec). */
1395
+ const BRANDED_ICON_HREF = AISHELL_MARK_URI;
1396
+ /**
1397
+ * The rel token list of a link node, or undefined when the node is not a
1398
+ * link (comment nodes reach the same iterator).
1399
+ *
1400
+ * Duck-typed on the attribute rather than `instanceof HTMLLinkElement` so
1401
+ * the shadow works identically in the browser and under the specs' DOM
1402
+ * stub, which provides the attribute surface without the constructor.
1403
+ */
1404
+ function linkRels(node) {
1405
+ if (node.nodeType !== Node.ELEMENT_NODE) return void 0;
1406
+ const rel = node.getAttribute("rel");
1407
+ if (rel === null) return void 0;
1408
+ return rel.split(/\s+/);
1409
+ }
1410
+ /**
1411
+ * Install the favicon shadow on a document.
1412
+ *
1413
+ * Rewrites every current icon link's href to the AI Shell mark (dropping
1414
+ * the media attribute so the one gradient serves both themes), shadows the
1415
+ * manifest names when the manifest is reachable, and observes the head so
1416
+ * re-inserted links are covered too. Returns the disposer: every touched
1417
+ * link is restored to its original href and media, the manifest link to
1418
+ * its original href, and the Blob URL is revoked, so an unload leaves the
1419
+ * document exactly as it was found.
1420
+ */
1421
+ function installFaviconShadow(target) {
1422
+ if (typeof target.querySelector !== "function") return () => {};
1423
+ const iconRestores = [];
1424
+ let manifestLink;
1425
+ let manifestHref;
1426
+ let blobUrl;
1427
+ const shadowIcon = (link) => {
1428
+ if (iconRestores.some((restore) => restore.link === link)) return;
1429
+ iconRestores.push({
1430
+ link,
1431
+ href: link.getAttribute("href") ?? "",
1432
+ media: link.getAttribute("media") ?? ""
1433
+ });
1434
+ link.setAttribute("href", BRANDED_ICON_HREF);
1435
+ link.removeAttribute("media");
1436
+ };
1437
+ const shadowManifest = (link) => {
1438
+ if (manifestLink !== void 0) return;
1439
+ const href = link.getAttribute("href");
1440
+ if (href === null) return;
1441
+ manifestLink = link;
1442
+ const url = new URL(href, target.baseURI);
1443
+ fetch(url).then((response) => response.ok ? response.text() : Promise.reject(new Error(`HTTP ${response.status}`))).then((text) => {
1444
+ if (manifestLink !== link) return;
1445
+ const parsed = JSON.parse(text);
1446
+ if (typeof parsed !== "object" || parsed === null) throw new Error("manifest is not an object");
1447
+ const branded = rebrandManifest(parsed);
1448
+ const blob = new Blob([JSON.stringify(branded)], { type: "application/manifest+json" });
1449
+ blobUrl = URL.createObjectURL(blob);
1450
+ manifestHref = href;
1451
+ link.setAttribute("href", blobUrl);
1452
+ }).catch(() => {
1453
+ if (manifestLink === link) manifestLink = void 0;
1454
+ });
1455
+ };
1456
+ const sweep$1 = (root) => {
1457
+ for (const link of root.querySelectorAll("link")) {
1458
+ const rels = linkRels(link);
1459
+ if (rels === void 0) continue;
1460
+ if (rels.includes("icon")) shadowIcon(link);
1461
+ else if (rels.includes("manifest")) shadowManifest(link);
1462
+ }
1463
+ };
1464
+ sweep$1(target.head);
1465
+ const observer = new MutationObserver((mutations) => {
1466
+ for (const mutation of mutations) for (const node of mutation.addedNodes) {
1467
+ const rels = linkRels(node);
1468
+ if (rels === void 0) continue;
1469
+ const element = node;
1470
+ if (rels.includes("icon")) shadowIcon(element);
1471
+ else if (rels.includes("manifest")) shadowManifest(element);
1472
+ }
1473
+ });
1474
+ observer.observe(target.head, { childList: true });
1475
+ return () => {
1476
+ observer.disconnect();
1477
+ for (const { link, href, media } of iconRestores) {
1478
+ link.setAttribute("href", href);
1479
+ if (media !== "") link.setAttribute("media", media);
1480
+ else link.removeAttribute("media");
1481
+ }
1482
+ if (manifestLink !== void 0 && manifestHref !== void 0) manifestLink.setAttribute("href", manifestHref);
1483
+ manifestLink = void 0;
1484
+ if (blobUrl !== void 0) URL.revokeObjectURL(blobUrl);
1485
+ };
1486
+ }
1487
+
1488
+ //#endregion
1489
+ //#region src/client/running-mark-shadow.ts
1490
+ /** Marker attribute the patch style element carries (exposed for the spec). */
1491
+ const RUNNING_PATCH_MARKER = "data-aishell-running-mark";
1492
+ /**
1493
+ * The patch CSS. The whale layers are hidden unconditionally — the static
1494
+ * fallback too, because the mark replaces both — while the mark's own
1495
+ * animation is guarded so reduced motion keeps it still.
1496
+ */
1497
+ const RUNNING_MARK_PATCH_CSS = [
1498
+ "[data-chat-running] [class*=\"runningWhaleAnimated\"],",
1499
+ "[data-chat-running] [class*=\"runningWhaleStill\"] {",
1500
+ " display: none;",
1501
+ "}",
1502
+ "[data-chat-running] [class*=\"runningIcon\"] {",
1503
+ ` background: url("${AISHELL_MARK_URI}") center / contain no-repeat;`,
1504
+ "}",
1505
+ "@keyframes aishell-running-bob {",
1506
+ " 0%, 100% { transform: none; opacity: 1; }",
1507
+ " 50% { transform: translateY(-0.7px); opacity: 0.82; }",
1508
+ "}",
1509
+ "@media (prefers-reduced-motion: no-preference) {",
1510
+ " [data-chat-running] [class*=\"runningIcon\"] {",
1511
+ " animation: aishell-running-bob 1.2s ease-in-out infinite;",
1512
+ " }",
1513
+ "}"
1514
+ ].join("\n");
1515
+ /**
1516
+ * Install the running-mark shadow on a document.
1517
+ *
1518
+ * Injects the patch CSS once (guarded by the marker attribute so a second
1519
+ * install is a no-op) and returns the disposer that removes the element.
1520
+ */
1521
+ function installRunningMarkShadow(target) {
1522
+ if (typeof target.querySelector !== "function") return () => {};
1523
+ const existing = target.querySelector(`style[${RUNNING_PATCH_MARKER}]`);
1524
+ if (existing !== null) return () => {
1525
+ existing.remove();
1526
+ };
1527
+ const tag = target.createElement("style");
1528
+ tag.setAttribute(RUNNING_PATCH_MARKER, "");
1529
+ tag.textContent = RUNNING_MARK_PATCH_CSS;
1530
+ target.head.appendChild(tag);
1531
+ return () => {
1532
+ tag.remove();
1533
+ };
1534
+ }
1535
+
1536
+ //#endregion
1537
+ //#region src/client/running-text-shadow.ts
1538
+ /** Official zh phrase, the prefix of both the bare and the duration forms. */
1539
+ const OFFICIAL_ZH = "深度求索中";
1540
+ /** Branded zh phrase (final copy: 思考中 / 思考中,用时 {duration} ···). */
1541
+ const BRANDED_ZH = "思考中";
1542
+ /** Official en phrase, the prefix of both the bare and the duration forms. */
1543
+ const OFFICIAL_EN = "Deep diving";
1544
+ /** Branded en phrase (final copy: Thinking / Thinking for {duration} ···). */
1545
+ const BRANDED_EN = "Thinking";
1546
+ /** The replacements, in order. */
1547
+ const REPLACEMENTS = [[OFFICIAL_ZH, BRANDED_ZH], [OFFICIAL_EN, BRANDED_EN]];
1548
+ /**
1549
+ * Rewrite the running-line copy inside a value.
1550
+ *
1551
+ * Every occurrence of either official phrase is replaced; a value without
1552
+ * them is returned as-is — the shadow never invents content.
1553
+ */
1554
+ function rebrandRunningText(value) {
1555
+ let branded = value;
1556
+ for (const [official, replacement] of REPLACEMENTS) branded = branded.split(official).join(replacement);
1557
+ return branded;
1558
+ }
1559
+ /** Whether the node carries the running row's stable anchor attribute.
1560
+ *
1561
+ * Duck-typed on the attribute surface rather than `instanceof Element` so
1562
+ * the shadow works identically in the browser and under the specs' DOM
1563
+ * stub, which provides the attribute methods without the constructor. */
1564
+ function isRunningRow(node) {
1565
+ return node.nodeType === Node.ELEMENT_NODE && node.hasAttribute("data-chat-running");
1566
+ }
1567
+ /**
1568
+ * Rewrite one text node and the shimmer attribute of its parent.
1569
+ *
1570
+ * The attribute matters as much as the node: TextShimmer's decorative
1571
+ * layer draws `content: attr(data-shimmer-text)` rather than the node's
1572
+ * own text, so leaving it stale resurfaces the official copy every time
1573
+ * the highlight sweeps.
1574
+ */
1575
+ function rewriteTextNode(node) {
1576
+ const branded = rebrandRunningText(node.data);
1577
+ if (branded === node.data) return;
1578
+ node.data = branded;
1579
+ const host = node.parentElement;
1580
+ if (host !== null && host.hasAttribute("data-shimmer-text")) host.setAttribute("data-shimmer-text", rebrandRunningText(host.getAttribute("data-shimmer-text") ?? ""));
1581
+ }
1582
+ /**
1583
+ * Sweep a subtree for official copy, rewriting what it finds.
1584
+ *
1585
+ * The attribute-only pass matters too: TextShimmer sets the attribute to
1586
+ * the generated string while the text node itself renders null, so some
1587
+ * re-renders change only the attribute.
1588
+ */
1589
+ function sweep(target, root) {
1590
+ const walker = target.createTreeWalker(root, NodeFilter.SHOW_TEXT);
1591
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) rewriteTextNode(node);
1592
+ for (const host of root.querySelectorAll("[data-shimmer-text]")) {
1593
+ const value = host.getAttribute("data-shimmer-text") ?? "";
1594
+ const branded = rebrandRunningText(value);
1595
+ if (branded !== value) host.setAttribute("data-shimmer-text", branded);
1596
+ }
1597
+ }
1598
+ /**
1599
+ * Install the running-text shadow on a document.
1600
+ *
1601
+ * Sweeps every mounted [data-chat-running] row once, then observes the
1602
+ * document body for new rows (childList) and their text/attribute
1603
+ * changes (characterData, data-shimmer-text) so the per-second re-renders
1604
+ * stay covered for as long as the row exists. Returns the disposer: the
1605
+ * observer stops and every live row is restored to its official copy.
1606
+ */
1607
+ function installRunningTextShadow(target) {
1608
+ if (typeof target.querySelector !== "function" || typeof MutationObserver !== "function") return () => {};
1609
+ const rewriteRow = (row) => {
1610
+ sweep(target, row);
1611
+ };
1612
+ for (const row of target.querySelectorAll("[data-chat-running]")) rewriteRow(row);
1613
+ const observer = new MutationObserver((mutations) => {
1614
+ for (const mutation of mutations) {
1615
+ if (mutation.type === "characterData") {
1616
+ const node = mutation.target;
1617
+ if (node.parentElement !== null && node.parentElement.closest("[data-chat-running]") !== null) rewriteTextNode(node);
1618
+ continue;
1619
+ }
1620
+ if (mutation.type === "attributes") {
1621
+ const host = mutation.target;
1622
+ if (host.nodeType === Node.ELEMENT_NODE) {
1623
+ const element = host;
1624
+ if (element.closest("[data-chat-running]") !== null) {
1625
+ const value = element.getAttribute("data-shimmer-text") ?? "";
1626
+ const branded = rebrandRunningText(value);
1627
+ if (branded !== value) element.setAttribute("data-shimmer-text", branded);
1628
+ }
1629
+ }
1630
+ continue;
1631
+ }
1632
+ for (const node of mutation.addedNodes) if (node.nodeType === Node.ELEMENT_NODE) {
1633
+ if (isRunningRow(node)) rewriteRow(node);
1634
+ else if (node.parentElement !== null && node.parentElement.closest("[data-chat-running]") !== null) sweep(target, node.parentElement);
1635
+ } else if (node.nodeType === Node.TEXT_NODE && node.parentElement !== null) {
1636
+ if (node.parentElement.closest("[data-chat-running]") !== null) rewriteTextNode(node);
1637
+ }
1638
+ }
1639
+ });
1640
+ observer.observe(target.body, {
1641
+ childList: true,
1642
+ subtree: true,
1643
+ characterData: true,
1644
+ attributeFilter: ["data-shimmer-text"]
1645
+ });
1646
+ return () => {
1647
+ observer.disconnect();
1648
+ for (const row of target.querySelectorAll("[data-chat-running]")) {
1649
+ const restore = (host) => {
1650
+ if (host.hasAttribute("data-shimmer-text")) host.setAttribute("data-shimmer-text", unbrand(host.getAttribute("data-shimmer-text") ?? ""));
1651
+ };
1652
+ restore(row);
1653
+ for (const host of row.querySelectorAll("[data-shimmer-text]")) restore(host);
1654
+ const walker = target.createTreeWalker(row, NodeFilter.SHOW_TEXT);
1655
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
1656
+ const text = node;
1657
+ const restored = unbrand(text.data);
1658
+ if (restored !== text.data) text.data = restored;
1659
+ }
1660
+ }
1661
+ };
1662
+ }
1663
+ /** The inverse rewrite, so the disposer restores the official copy exactly. */
1664
+ function unbrand(value) {
1665
+ let official = value;
1666
+ for (const [from, to] of REPLACEMENTS) official = official.split(to).join(from);
1667
+ return official;
1668
+ }
1669
+
317
1670
  //#endregion
318
1671
  //#region src/client/index.ts
319
- /** Required service: the UI slot registry. */
320
- const inject = ["slots"];
1672
+ /** Required services: the UI slot registry and the locale registry. */
1673
+ const inject = ["slots", "locale"];
321
1674
  function apply(ctx) {
1675
+ ctx.effect(() => ctx.locale.register(NS, {
1676
+ zh,
1677
+ en
1678
+ }), "aishell-brand: dictionaries");
322
1679
  ctx.slots.inject("sidebar.brand.mark", () => ctx.slots.register({
323
1680
  name: "sidebar.brand.mark",
324
1681
  priority: -1
@@ -331,7 +1688,21 @@ function apply(ctx) {
331
1688
  name: "conversation.hero.brand.mark",
332
1689
  priority: -1
333
1690
  }, AishellHeroBrandMark));
334
- if (typeof document !== "undefined") ctx.effect(() => installTitleShadow(document), "aishell-brand: window title shadow");
1691
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
1692
+ name: "conversation.input.dock",
1693
+ id: "aishell-suggestions",
1694
+ order: 20,
1695
+ locale: NS
1696
+ }, SuggestionCards));
1697
+ if (typeof document !== "undefined") {
1698
+ ctx.effect(() => installHeroPatchCss(document), "aishell-brand: hero patch css");
1699
+ ctx.effect(() => installHeroHoverBridge(document), "aishell-brand: hero hover bridge");
1700
+ ctx.effect(() => installPoweredBy(document), "aishell-brand: powered-by label");
1701
+ ctx.effect(() => installTitleShadow(document), "aishell-brand: window title shadow");
1702
+ ctx.effect(() => installFaviconShadow(document), "aishell-brand: favicon shadow");
1703
+ ctx.effect(() => installRunningMarkShadow(document), "aishell-brand: running mark shadow");
1704
+ ctx.effect(() => installRunningTextShadow(document), "aishell-brand: running text shadow");
1705
+ }
335
1706
  }
336
1707
 
337
1708
  //#endregion