@huaweidevtools/aishell-dsh-brand 0.0.3 → 0.0.5

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,10 @@ 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"));
28
29
 
29
30
  //#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}";
31
+ 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
32
  const tagId = "@huaweidevtools/aishell-dsh-brand//home/eushing/opensource/aishell-dsh-plugins/packages/aishell-brand/src/client/brand.module.css";
32
33
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
33
34
  const tag = document.createElement("style");
@@ -38,8 +39,7 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
38
39
  }
39
40
  var brand_module_css_default = {
40
41
  "mark": "NP1U9q_mark",
41
- "name": "NP1U9q_name",
42
- "version": "NP1U9q_version"
42
+ "name": "NP1U9q_name"
43
43
  };
44
44
 
45
45
  //#endregion
@@ -109,51 +109,77 @@ const HERO_PATCH_CSS = [
109
109
  " margin-top: -4px;",
110
110
  "}"
111
111
  ].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");
112
+ /** Inject the hero headline patch CSS; the disposer removes the tag. */
113
+ function installHeroPatchCss(target) {
114
+ if (typeof target.querySelector !== "function") return () => {};
115
+ if (target.querySelector("style[data-aishell-hero-patch]")) return () => {};
116
+ const tag = target.createElement("style");
116
117
  tag.dataset.aishellHeroPatch = "";
117
118
  tag.textContent = HERO_PATCH_CSS;
118
- document.head.appendChild(tag);
119
+ target.head.appendChild(tag);
120
+ return () => {
121
+ tag.remove();
122
+ };
119
123
  }
120
- injectHeroPatchCss();
121
124
  /**
122
125
  * JS hover bridge: CSS ~ only matches forward siblings, so hovering the
123
126
  * title text cannot trigger the logo's animation (the logo sits before the
124
127
  * title in DOM order). This adds a mouseenter/mouseleave listener on the
125
128
  * title span that toggles a class on the logo to trigger its bob animation.
129
+ *
130
+ * The disposer removes both listeners from every span it bridged, so a reload
131
+ * does not stack a second pair on the same element (the `data-` marker stops a
132
+ * *re-install* from adding them, but it also means a later disposer cannot
133
+ * find them by query alone — hence the bookkeeping below).
126
134
  */
127
- function bridgeHeroHover() {
128
- if (typeof document === "undefined") return;
135
+ function installHeroHoverBridge(target) {
136
+ if (typeof target.querySelector !== "function") return () => {};
137
+ /** Every listener this install added, so the disposer can take them off. */
138
+ const bridged = [];
129
139
  const bridge = (root) => {
130
140
  const groups = root.querySelectorAll("[class*=\"titleGroup\"]");
131
141
  for (const group of groups) {
132
142
  const titleSpan = group.children[0];
133
143
  if (!titleSpan || titleSpan.dataset.aishellHoverBridged === "true") continue;
134
144
  titleSpan.dataset.aishellHoverBridged = "true";
135
- titleSpan.addEventListener("mouseenter", () => {
145
+ const onEnter = () => {
136
146
  const headline = titleSpan.closest("[class*=\"headline\"]");
137
147
  const fish = headline?.querySelector("[class*=\"fish\"]");
138
148
  fish?.classList.add("aishell-logo-hover");
139
- });
140
- titleSpan.addEventListener("mouseleave", () => {
149
+ };
150
+ const onLeave = () => {
141
151
  const headline = titleSpan.closest("[class*=\"headline\"]");
142
152
  const fish = headline?.querySelector("[class*=\"fish\"]");
143
153
  fish?.classList.remove("aishell-logo-hover");
154
+ };
155
+ titleSpan.addEventListener("mouseenter", onEnter);
156
+ titleSpan.addEventListener("mouseleave", onLeave);
157
+ bridged.push({
158
+ element: titleSpan,
159
+ onEnter,
160
+ onLeave
144
161
  });
145
162
  }
146
163
  };
147
- bridge(document);
164
+ bridge(target);
148
165
  const observer = new MutationObserver((mutations) => {
149
166
  for (const m of mutations) for (const node of m.addedNodes) if (node.nodeType === 1) bridge(node);
150
167
  });
151
- observer.observe(document.body, {
168
+ observer.observe(target.body, {
152
169
  childList: true,
153
170
  subtree: true
154
171
  });
172
+ return () => {
173
+ observer.disconnect();
174
+ for (const { element, onEnter, onLeave } of bridged) {
175
+ element.removeEventListener("mouseenter", onEnter);
176
+ element.removeEventListener("mouseleave", onLeave);
177
+ delete element.dataset.aishellHoverBridged;
178
+ element.closest("[class*=\"headline\"]")?.querySelector("[class*=\"fish\"]")?.classList.remove("aishell-logo-hover");
179
+ }
180
+ bridged.length = 0;
181
+ };
155
182
  }
156
- bridgeHeroHover();
157
183
  /**
158
184
  * Render the AI Shell mark with the presentation requested by its host surface.
159
185
  * Used in both the expanded sidebar brand row and the collapsed rail.
@@ -170,18 +196,15 @@ function AishellBrandMark({ size }) {
170
196
  });
171
197
  }
172
198
  /**
173
- * Render the AI Shell name with a version badge in the top-right corner,
174
- * matching DSH's native `.buildVersion` badge style.
199
+ * Render the AI Shell name in the sidebar brand row. The product name itself
200
+ * is drawn by the CSS module's `.name::before` (content: 'AI Shell'), so the
201
+ * markup is a single empty span.
175
202
  */
176
203
  function AishellBrandName() {
177
204
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
178
205
  className: brand_module_css_default.name,
179
206
  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.3"
184
- })
207
+ "aria-label": "AI Shell"
185
208
  });
186
209
  }
187
210
  /**
@@ -203,33 +226,661 @@ function AishellHeroBrandMark({ size, className }) {
203
226
  * Insert "Powered by deepseek-harness" below the hero headline, centered
204
227
  * inside .stack. Uses a MutationObserver because the hero mounts/unmounts
205
228
  * as the user navigates between sessions.
229
+ *
230
+ * The disposer stops the observer and removes every label this install added,
231
+ * so unloading the plugin leaves the hero exactly as it found it.
206
232
  */
207
- function injectPoweredBy() {
208
- if (typeof document === "undefined") return;
233
+ function installPoweredBy(target) {
234
+ if (typeof target.querySelector !== "function") return () => {};
235
+ /** The labels this install inserted, for removal on dispose. */
236
+ const inserted = [];
209
237
  const insert = (root) => {
210
238
  const stacks = root.querySelectorAll("[class*=\"stack\"]");
211
239
  for (const stack of stacks) {
212
240
  if (!stack.querySelector("[class*=\"headline\"]")) continue;
213
241
  if (stack.querySelector("[data-aishell-powered-by]")) continue;
214
- const label = document.createElement("div");
242
+ const label = target.createElement("div");
215
243
  label.dataset.aishellPoweredBy = "";
216
244
  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
245
  label.textContent = "Powered by deepseek-harness";
218
246
  const headline = stack.querySelector("[class*=\"headline\"]");
219
247
  if (headline && headline.nextSibling) stack.insertBefore(label, headline.nextSibling);
220
248
  else stack.appendChild(label);
249
+ inserted.push(label);
221
250
  }
222
251
  };
223
- insert(document);
252
+ insert(target);
224
253
  const observer = new MutationObserver((mutations) => {
225
254
  for (const m of mutations) for (const node of m.addedNodes) if (node.nodeType === 1) insert(node);
226
255
  });
227
- observer.observe(document.body, {
256
+ observer.observe(target.body, {
228
257
  childList: true,
229
258
  subtree: true
230
259
  });
260
+ return () => {
261
+ observer.disconnect();
262
+ for (const label of inserted) label.remove();
263
+ inserted.length = 0;
264
+ };
265
+ }
266
+
267
+ //#endregion
268
+ //#region src/client/suggestions.ts
269
+ /**
270
+ * The cards, in display order.
271
+ *
272
+ * Order matters only for the ring's starting window: the strip opens on the
273
+ * first {@link VISIBLE} cards, so the first entries are the ones a person
274
+ * sees without pressing an arrow.
275
+ */
276
+ const SUGGESTIONS = [
277
+ {
278
+ id: "inventory",
279
+ icon: "cloud",
280
+ titleKey: "suggestion.inventory.title",
281
+ glossKey: "suggestion.inventory.gloss",
282
+ promptKey: "suggestion.inventory.prompt"
283
+ },
284
+ {
285
+ id: "deploy",
286
+ icon: "deploy",
287
+ titleKey: "suggestion.deploy.title",
288
+ glossKey: "suggestion.deploy.gloss",
289
+ promptKey: "suggestion.deploy.prompt"
290
+ },
291
+ {
292
+ id: "enterprise",
293
+ icon: "architecture",
294
+ titleKey: "suggestion.enterprise.title",
295
+ glossKey: "suggestion.enterprise.gloss",
296
+ promptKey: "suggestion.enterprise.prompt"
297
+ },
298
+ {
299
+ id: "billing",
300
+ icon: "cost",
301
+ titleKey: "suggestion.billing.title",
302
+ glossKey: "suggestion.billing.gloss",
303
+ promptKey: "suggestion.billing.prompt"
304
+ }
305
+ ];
306
+
307
+ //#endregion
308
+ //#region src/client/suggestion-icons.tsx
309
+ /** One stroked path with the shared paint attributes. */
310
+ function Stroke({ d }) {
311
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
312
+ d,
313
+ fill: "none",
314
+ stroke: "currentColor",
315
+ strokeWidth: "1.7",
316
+ strokeLinecap: "round",
317
+ strokeLinejoin: "round"
318
+ });
319
+ }
320
+ /**
321
+ * A stack of servers: the resource inventory.
322
+ *
323
+ * Three stacked slabs with a status dot each — unmistakably "machines you
324
+ * own", and it shares no silhouette with any other glyph here.
325
+ */
326
+ function InventoryIcon() {
327
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
328
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M4 5.5h16v4H4z" }),
329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M4 14.5h16v4H4z" }),
330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M7 7.5h.01" }),
331
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M7 16.5h.01" })
332
+ ] });
333
+ }
334
+ /**
335
+ * A rocket: deploying something so it runs.
336
+ *
337
+ * Chosen over any box-with-arrow because that family is exactly the share
338
+ * glyph. A rocket is a single closed silhouette — nothing in this set can
339
+ * be confused with it.
340
+ */
341
+ function DeployIcon() {
342
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
343
+ /* @__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" }),
344
+ /* @__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" }),
345
+ /* @__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" })
346
+ ] });
347
+ }
348
+ /**
349
+ * A layered sitemap: account hierarchy and network partitions.
350
+ *
351
+ * One node branching to three, drawn as a single tree — reads as "structure"
352
+ * rather than "file" or "folder", which a plain box-and-boxes would.
353
+ */
354
+ function ArchitectureIcon() {
355
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
356
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 3.5h5v3.4h-5z" }),
357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M3.5 17.1h5v3.4h-5z" }),
358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 17.1h5v3.4h-5z" }),
359
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M15.5 17.1h5v3.4h-5z" }),
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 6.9v4.2" }),
361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M6 17.1v-2.4h12v2.4" })
362
+ ] });
363
+ }
364
+ /**
365
+ * A receipt with a downward trend: cost review.
366
+ *
367
+ * The torn bottom edge is what makes it a bill rather than a document —
368
+ * without it the shape is a generic page and reads as "export a report".
369
+ */
370
+ function CostIcon() {
371
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
372
+ /* @__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" }),
373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 8.4h5" }),
374
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 12.2h3.2" })
375
+ ] });
376
+ }
377
+ /** Resolve one icon key to its glyph. */
378
+ function SuggestionIconArt({ icon, size = 20 }) {
379
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
380
+ "aria-hidden": true,
381
+ width: size,
382
+ height: size,
383
+ viewBox: "0 0 24 24",
384
+ style: { flex: "none" },
385
+ children: [
386
+ icon === "cloud" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InventoryIcon, {}),
387
+ icon === "deploy" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeployIcon, {}),
388
+ icon === "architecture" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArchitectureIcon, {}),
389
+ icon === "cost" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CostIcon, {})
390
+ ]
391
+ });
392
+ }
393
+
394
+ //#endregion
395
+ //#region src/client/carousel.ts
396
+ /**
397
+ * Carousel position maths, kept pure so it can be tested without a DOM.
398
+ *
399
+ * The strip is a RING, not a scroll range: the cards form a circle, and an
400
+ * arrow steps one card around it forever. Clicking next from the last
401
+ * position lands on the first, and clicking prev from the first lands on the
402
+ * last — and crucially, every intermediate position exists. A four-card ring
403
+ * showing three at a time has FOUR positions, not two:
404
+ *
405
+ * cards 1 2 3 4, three visible
406
+ * pos 0 → [1 2 3]
407
+ * pos 1 → [2 3 4]
408
+ * pos 2 → [3 4 1] ← the wrap is visible, not a jump back
409
+ * pos 3 → [4 1 2]
410
+ * pos 4 → [1 2 3] same as pos 0
411
+ *
412
+ * A plain scroll container cannot express that: its range is only
413
+ * `content - viewport` (one step, for four cards), and positions 2 and 3 have
414
+ * no place to live. So the card list is rendered {@link COPIES} times over
415
+ * and the window travels through the middle copy, where both neighbours
416
+ * always have content.
417
+ *
418
+ * ## Why the copies, and how the seam stays invisible
419
+ *
420
+ * With the list rendered three times, a window at physical position `p` shows
421
+ * `cards[p .. p+VISIBLE-1]`, and position `2n` shows exactly what position `n`
422
+ * shows. That equivalence is the seam: once a slide settles at `2n`, the
423
+ * track can be placed at `n` in the same frame with transitions off, and
424
+ * nothing on screen changes. The same holds at the other end, where `n-1` and
425
+ * `2n-1` are the same window.
426
+ *
427
+ * The rewind happens only AFTER the slide finishes — never before. Jumping
428
+ * first would put the destination on screen instantly and then animate away
429
+ * from it, so a click at the seam would appear to skip a card. This is the
430
+ * one law in this module that a reader is likely to get backwards.
431
+ *
432
+ * @module @huaweidevtools/aishell-dsh-brand/client/carousel
433
+ */
434
+ /**
435
+ * How many times the card list is rendered.
436
+ *
437
+ * Three is the minimum that works. The window lives in the middle copy, so it
438
+ * needs one full copy behind it (to show the cards before the start) and one
439
+ * ahead (to show the cards past the end while a slide is finishing at the
440
+ * seam, where the position may briefly reach `2n` and read up to
441
+ * `2n + VISIBLE - 1`).
442
+ */
443
+ const COPIES = 3;
444
+ /** How many cards to render for a ring of `n`. */
445
+ function ringLength(n) {
446
+ return n * COPIES;
447
+ }
448
+ /**
449
+ * The physical position the ring starts at — the first card of the middle
450
+ * copy. Starting here rather than at 0 is what makes the very first prev
451
+ * click land on a real card instead of an empty track.
452
+ */
453
+ function ringStart(n) {
454
+ return n;
455
+ }
456
+ /**
457
+ * Where a settled position should be silently rewound to, or `null` when it
458
+ * is already inside the working band.
459
+ *
460
+ * The band is `[n, 2n-1]`. A slide may finish one past either end — `2n`
461
+ * going forward, `n-1` going backward — and both have an exact twin inside
462
+ * the band. See the module note for why this runs after the slide, not
463
+ * before.
464
+ */
465
+ function rewindTo(pos, n) {
466
+ if (pos >= 2 * n) return n;
467
+ if (pos < n) return 2 * n - 1;
468
+ return null;
469
+ }
470
+ /**
471
+ * Resolve one arrow press on a ring.
472
+ *
473
+ * Any rewind still owed from the previous press is settled first, so a person
474
+ * clicking faster than the slide animation cannot walk the window off the
475
+ * rendered copies — the band always absorbs one more step.
476
+ */
477
+ function ringStep(pos, delta, n) {
478
+ const owed = rewindTo(pos, n);
479
+ const from = owed ?? pos;
480
+ return {
481
+ from,
482
+ to: from + delta
483
+ };
484
+ }
485
+
486
+ //#endregion
487
+ //#region src/client/SuggestionCards.tsx
488
+ /** How many cards a full-width strip shows at once. */
489
+ const VISIBLE = 3;
490
+ /** One card. */
491
+ function Card({ suggestion, onPick, t }) {
492
+ const [hover, setHover] = (0, react.useState)(false);
493
+ const [focus, setFocus] = (0, react.useState)(false);
494
+ const lit = hover || focus;
495
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
496
+ type: "button",
497
+ onClick: () => {
498
+ onPick(t(suggestion.promptKey));
499
+ },
500
+ onPointerEnter: () => {
501
+ setHover(true);
502
+ },
503
+ onPointerLeave: () => {
504
+ setHover(false);
505
+ },
506
+ onFocus: () => {
507
+ setFocus(true);
508
+ },
509
+ onBlur: () => {
510
+ setFocus(false);
511
+ },
512
+ style: {
513
+ flex: `0 0 calc((100cqw - ${VISIBLE - 1} * var(--dsh-suggestion-gap)) / ${VISIBLE})`,
514
+ display: "flex",
515
+ alignItems: "flex-start",
516
+ gap: 10,
517
+ boxSizing: "border-box",
518
+ minWidth: 0,
519
+ padding: "12px 14px",
520
+ textAlign: "left",
521
+ font: "inherit",
522
+ cursor: "pointer",
523
+ color: "var(--dsw-alias-label-primary)",
524
+ background: lit ? "var(--dsw-alias-interactive-bg-hover)" : "var(--dsw-alias-bg-module-platform)",
525
+ border: `1px solid ${lit ? "var(--dsw-alias-border-l2)" : "var(--dsw-alias-border-l3)"}`,
526
+ borderRadius: 10,
527
+ transition: "background 120ms ease, border-color 120ms ease",
528
+ outline: focus ? "2px solid var(--dsw-alias-state-business-primary)" : "none",
529
+ outlineOffset: 2
530
+ },
531
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
532
+ style: {
533
+ color: "var(--dsw-alias-label-secondary)",
534
+ marginTop: 1
535
+ },
536
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuggestionIconArt, { icon: suggestion.icon })
537
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
538
+ style: {
539
+ display: "flex",
540
+ flexDirection: "column",
541
+ gap: 2,
542
+ minWidth: 0
543
+ },
544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
545
+ style: {
546
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
547
+ fontWeight: 500,
548
+ lineHeight: "20px",
549
+ overflow: "hidden",
550
+ textOverflow: "ellipsis",
551
+ whiteSpace: "nowrap"
552
+ },
553
+ children: t(suggestion.titleKey)
554
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
555
+ style: {
556
+ fontSize: "12px",
557
+ lineHeight: "16px",
558
+ color: "var(--dsw-alias-label-tertiary)",
559
+ display: "-webkit-box",
560
+ WebkitLineClamp: 2,
561
+ WebkitBoxOrient: "vertical",
562
+ overflow: "hidden"
563
+ },
564
+ children: t(suggestion.glossKey)
565
+ })]
566
+ })]
567
+ });
568
+ }
569
+ /** One carousel arrow. */
570
+ function Arrow({ direction, label, onClick }) {
571
+ const [hover, setHover] = (0, react.useState)(false);
572
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
573
+ type: "button",
574
+ "aria-label": label,
575
+ onClick,
576
+ onPointerEnter: () => {
577
+ setHover(true);
578
+ },
579
+ onPointerLeave: () => {
580
+ setHover(false);
581
+ },
582
+ style: {
583
+ flex: "none",
584
+ display: "inline-flex",
585
+ alignItems: "center",
586
+ justifyContent: "center",
587
+ width: 26,
588
+ height: 26,
589
+ padding: 0,
590
+ cursor: "pointer",
591
+ color: "var(--dsw-alias-label-secondary)",
592
+ background: hover ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
593
+ border: "none",
594
+ borderRadius: 999,
595
+ transition: "background 120ms ease"
596
+ },
597
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
598
+ "aria-hidden": true,
599
+ width: "14",
600
+ height: "14",
601
+ viewBox: "0 0 24 24",
602
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
603
+ d: direction === "prev" ? "M15 5l-7 7 7 7" : "M9 5l7 7-7 7",
604
+ fill: "none",
605
+ stroke: "currentColor",
606
+ strokeWidth: "2",
607
+ strokeLinecap: "round",
608
+ strokeLinejoin: "round"
609
+ })
610
+ })
611
+ });
612
+ }
613
+ function SuggestionCards(props) {
614
+ const blank = props.useSession((session) => session.blank);
615
+ const setDraft = props.inputActions.setDraft;
616
+ const t = props.t;
617
+ const n = SUGGESTIONS.length;
618
+ const ring = n > VISIBLE;
619
+ const viewportRef = (0, react.useRef)(null);
620
+ const trackRef = (0, react.useRef)(null);
621
+ const posRef = (0, react.useRef)(ringStart(n));
622
+ const stepRef = (0, react.useRef)(0);
623
+ const [animating, setAnimating] = (0, react.useState)(true);
624
+ const [ready, setReady] = (0, react.useState)(!ring);
625
+ /** Place the track at a position with no transition. */
626
+ const snapTo = (0, react.useCallback)((pos) => {
627
+ const track = trackRef.current;
628
+ if (track === null) return;
629
+ track.style.transition = "none";
630
+ track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
631
+ }, []);
632
+ /** Animate the track to a position. */
633
+ const slideTo = (0, react.useCallback)((pos) => {
634
+ const track = trackRef.current;
635
+ if (track === null) return;
636
+ track.style.transition = "";
637
+ track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
638
+ }, []);
639
+ /**
640
+ * Measure one step, then place the ring at its start.
641
+ *
642
+ * The step is read from a real card rather than computed from the viewport
643
+ * width so the fraction in `Card`'s flex-basis and this number cannot drift
644
+ * apart, and so a breakpoint change needs no constant updated here.
645
+ */
646
+ /**
647
+ * Re-measure one step and re-apply the current position.
648
+ *
649
+ * The step is read from a real card rather than computed from the viewport
650
+ * width so the `cqw` fraction in `Card`'s flex-basis and this number cannot
651
+ * drift apart.
652
+ *
653
+ * Re-measuring is not optional. The first frame measures BEFORE the cards
654
+ * have their final width: `containerType` and the `cqw` basis settle in a
655
+ * later layout pass, so the first reading is a few percent too large (242px
656
+ * where the settled card is 213px). A step kept from that frame puts every
657
+ * position slightly too far along, which compounds — by the fourth card the
658
+ * window has drifted a whole card and shows two cards plus a gap.
659
+ */
660
+ const remeasure = (0, react.useCallback)(() => {
661
+ const track = trackRef.current;
662
+ if (track === null) return false;
663
+ const first = track.firstElementChild;
664
+ if (first === null) return false;
665
+ const gap = Number.parseFloat(getComputedStyle(track).columnGap) || 0;
666
+ const step$1 = first.getBoundingClientRect().width + gap;
667
+ if (!(step$1 > 0)) return false;
668
+ const changed = step$1 !== stepRef.current;
669
+ stepRef.current = step$1;
670
+ if (changed) {
671
+ setAnimating(false);
672
+ snapTo(posRef.current);
673
+ window.requestAnimationFrame(() => {
674
+ setAnimating(true);
675
+ });
676
+ }
677
+ return true;
678
+ }, [snapTo]);
679
+ (0, react.useEffect)(() => {
680
+ if (!blank || !ring) return;
681
+ let cancelled = false;
682
+ const settle = () => {
683
+ if (cancelled) return;
684
+ if (!remeasure()) {
685
+ frame = window.requestAnimationFrame(settle);
686
+ return;
687
+ }
688
+ posRef.current = ringStart(n);
689
+ setAnimating(false);
690
+ snapTo(posRef.current);
691
+ window.requestAnimationFrame(() => {
692
+ if (cancelled) return;
693
+ setAnimating(true);
694
+ setReady(true);
695
+ const viewport = viewportRef.current;
696
+ if (viewport !== null) {
697
+ observer = new ResizeObserver(() => {
698
+ remeasure();
699
+ });
700
+ observer.observe(viewport);
701
+ }
702
+ });
703
+ };
704
+ let frame = window.requestAnimationFrame(settle);
705
+ let observer;
706
+ return () => {
707
+ cancelled = true;
708
+ window.cancelAnimationFrame(frame);
709
+ observer?.disconnect();
710
+ };
711
+ }, [
712
+ blank,
713
+ ring,
714
+ n,
715
+ remeasure,
716
+ snapTo
717
+ ]);
718
+ /**
719
+ * Apply whichever rewind the settling position owes, then re-arm the
720
+ * transition.
721
+ *
722
+ * Runs on `transitionend`, never before the slide: jumping to the twin
723
+ * position first would put the destination on screen immediately and then
724
+ * animate away from it, so a click at the seam would look like it skipped
725
+ * a card. See the module note in carousel.ts.
726
+ */
727
+ const onSettle = (0, react.useCallback)(() => {
728
+ if (!ring) return;
729
+ const owed = rewindTo(posRef.current, n);
730
+ if (owed === null) return;
731
+ posRef.current = owed;
732
+ setAnimating(false);
733
+ snapTo(owed);
734
+ window.requestAnimationFrame(() => {
735
+ setAnimating(true);
736
+ });
737
+ }, [
738
+ ring,
739
+ n,
740
+ snapTo
741
+ ]);
742
+ /** One arrow press. */
743
+ const step = (0, react.useCallback)((delta) => {
744
+ if (!ring || !ready) return;
745
+ const { from, to } = ringStep(posRef.current, delta, n);
746
+ if (from !== posRef.current) {
747
+ posRef.current = from;
748
+ setAnimating(false);
749
+ snapTo(from);
750
+ }
751
+ posRef.current = to;
752
+ window.requestAnimationFrame(() => {
753
+ setAnimating(true);
754
+ slideTo(to);
755
+ });
756
+ }, [
757
+ ring,
758
+ ready,
759
+ n,
760
+ snapTo,
761
+ slideTo
762
+ ]);
763
+ if (!blank) return null;
764
+ if (n === 0) return null;
765
+ const cards = ring ? Array.from({ length: ringLength(n) }, (_, i) => SUGGESTIONS[i % n]) : SUGGESTIONS;
766
+ const strip = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
767
+ ref: viewportRef,
768
+ "data-aishell-suggestions": n,
769
+ style: {
770
+ overflow: "hidden",
771
+ minWidth: 0,
772
+ flex: "1 1 auto",
773
+ containerType: "inline-size"
774
+ },
775
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
776
+ ref: trackRef,
777
+ "data-aishell-suggestion-track": ring ? "ring" : "flat",
778
+ onTransitionEnd: onSettle,
779
+ style: {
780
+ display: "flex",
781
+ alignItems: "stretch",
782
+ gap: "var(--dsh-suggestion-gap)",
783
+ width: "max-content",
784
+ willChange: "transform",
785
+ transition: animating ? "transform 320ms cubic-bezier(.22,.61,.36,1)" : "none",
786
+ transform: "translate3d(0, 0, 0)"
787
+ },
788
+ children: cards.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
789
+ suggestion,
790
+ onPick: setDraft,
791
+ t
792
+ }, `${i}:${suggestion.id}`))
793
+ })
794
+ });
795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
796
+ style: {
797
+ ["--dsh-suggestion-gap"]: "8px",
798
+ display: "flex",
799
+ alignItems: "center",
800
+ gap: 6,
801
+ minWidth: 0,
802
+ margin: "4px 0 8px"
803
+ },
804
+ children: [
805
+ ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
806
+ direction: "prev",
807
+ label: t("suggestion.previous"),
808
+ onClick: () => {
809
+ step(-1);
810
+ }
811
+ }),
812
+ strip,
813
+ ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
814
+ direction: "next",
815
+ label: t("suggestion.next"),
816
+ onClick: () => {
817
+ step(1);
818
+ }
819
+ })
820
+ ]
821
+ });
231
822
  }
232
- injectPoweredBy();
823
+
824
+ //#endregion
825
+ //#region src/client/locales.ts
826
+ /**
827
+ * aishell brand locale dictionaries.
828
+ *
829
+ * The brand plugin owns its own namespace rather than reusing `aishell`
830
+ * (which `aishell-credentials` registers): a namespace's texts have exactly
831
+ * one owner, and a duplicate `(ns, locale)` registration throws.
832
+ *
833
+ * ## Why content lives in a dictionary
834
+ *
835
+ * The suggestion cards are the only strings in this plugin a person reads,
836
+ * and they are CONTENT rather than interface chrome — a card title, its
837
+ * one-line gloss, and the prompt a click inserts. They still belong here:
838
+ * a card that says「云资源查询」to an English reader is worse than no card,
839
+ * and the prompt it inserts would start an English conversation in Chinese.
840
+ *
841
+ * Chinese is the key-set source of truth; English is complete against it and
842
+ * is also the terminal fallback, so every locale other than `zh` reads
843
+ * English. That is the framework's own chain (`LOCALE_IDS = ["zh", "en"]`,
844
+ * with English carrying no fallback) — there is no third dictionary to write.
845
+ *
846
+ * @module @huaweidevtools/aishell-dsh-brand/client/locales
847
+ */
848
+ /** Dictionary namespace owned by this plugin. */
849
+ const NS = "aishell-brand";
850
+ /** Simplified Chinese dictionary (the key-set source of truth). */
851
+ const zh = {
852
+ "suggestion.previous": "上一个示例",
853
+ "suggestion.next": "下一个示例",
854
+ "suggestion.inventory.title": "云资源查询",
855
+ "suggestion.inventory.gloss": "列出当前账号下的云资源",
856
+ "suggestion.inventory.prompt": "请列出我的华为云资源清单,按服务分类(计算、存储、网络、数据库等),标出每类资源的数量和所在区域。",
857
+ "suggestion.deploy.title": "部署方案推荐",
858
+ "suggestion.deploy.gloss": "按访问量和预算给出可落地的部署路径",
859
+ "suggestion.deploy.prompt": "我要部署一个轻量级 Web 应用,请对比几种可行的部署方案,说明各自的适用规模、月度成本量级和运维复杂度,并给出你的推荐及理由。",
860
+ "suggestion.enterprise.title": "企业上云",
861
+ "suggestion.enterprise.gloss": "多账号、网络分区与权限边界的整体设计",
862
+ "suggestion.enterprise.prompt": "请为我规划一套云上 Landing Zone 架构,覆盖账号组织结构、网络分区、权限边界与审计要求,并说明各部分的设计取舍。",
863
+ "suggestion.billing.title": "账单分析",
864
+ "suggestion.billing.gloss": "分析上个月的云资源支出",
865
+ "suggestion.billing.prompt": "请分析我上个月的云资源支出构成,找出占比最高的几项服务,指出其中可以优化或降配的部分,并估算调整后能节省多少。"
866
+ };
867
+ /** English dictionary. Complete against {@link zh}; also the terminal fallback. */
868
+ const en = {
869
+ "suggestion.previous": "Previous example",
870
+ "suggestion.next": "Next example",
871
+ "suggestion.inventory.title": "Cloud inventory",
872
+ "suggestion.inventory.gloss": "List the resources in this account",
873
+ "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.",
874
+ "suggestion.deploy.title": "Deployment options",
875
+ "suggestion.deploy.gloss": "A workable path for your traffic and budget",
876
+ "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.",
877
+ "suggestion.enterprise.title": "Landing zone",
878
+ "suggestion.enterprise.gloss": "Multi-account structure, network and permission boundaries",
879
+ "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.",
880
+ "suggestion.billing.title": "Billing analysis",
881
+ "suggestion.billing.gloss": "Break down last month's cloud spend",
882
+ "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."
883
+ };
233
884
 
234
885
  //#endregion
235
886
  //#region src/client/title-shadow.ts
@@ -651,9 +1302,13 @@ function unbrand(value) {
651
1302
 
652
1303
  //#endregion
653
1304
  //#region src/client/index.ts
654
- /** Required service: the UI slot registry. */
655
- const inject = ["slots"];
1305
+ /** Required services: the UI slot registry and the locale registry. */
1306
+ const inject = ["slots", "locale"];
656
1307
  function apply(ctx) {
1308
+ ctx.effect(() => ctx.locale.register(NS, {
1309
+ zh,
1310
+ en
1311
+ }), "aishell-brand: dictionaries");
657
1312
  ctx.slots.inject("sidebar.brand.mark", () => ctx.slots.register({
658
1313
  name: "sidebar.brand.mark",
659
1314
  priority: -1
@@ -666,7 +1321,16 @@ function apply(ctx) {
666
1321
  name: "conversation.hero.brand.mark",
667
1322
  priority: -1
668
1323
  }, AishellHeroBrandMark));
1324
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
1325
+ name: "conversation.input.dock",
1326
+ id: "aishell-suggestions",
1327
+ order: 20,
1328
+ locale: NS
1329
+ }, SuggestionCards));
669
1330
  if (typeof document !== "undefined") {
1331
+ ctx.effect(() => installHeroPatchCss(document), "aishell-brand: hero patch css");
1332
+ ctx.effect(() => installHeroHoverBridge(document), "aishell-brand: hero hover bridge");
1333
+ ctx.effect(() => installPoweredBy(document), "aishell-brand: powered-by label");
670
1334
  ctx.effect(() => installTitleShadow(document), "aishell-brand: window title shadow");
671
1335
  ctx.effect(() => installFaviconShadow(document), "aishell-brand: favicon shadow");
672
1336
  ctx.effect(() => installRunningMarkShadow(document), "aishell-brand: running mark shadow");
@@ -7,14 +7,42 @@
7
7
  */
8
8
  import type { SidebarBrandMarkOwnerProps } from '@deepseek-ai/dsh-client-ui-sidebar/client';
9
9
  import type { HeroBrandMarkOwnerProps } from '@deepseek-ai/dsh-client-ui-conversation/client';
10
+ /**
11
+ * A disposer undoing one module-level installation.
12
+ *
13
+ * The three helpers below all reach outside React — a `<style>` tag, event
14
+ * listeners, inserted nodes — so none of them is unwound by unmounting a
15
+ * component. They are installed through `ctx.effect` in `index.ts`, which runs
16
+ * the returned disposer on plugin unload. That matters beyond tidiness: the
17
+ * client loader tears a module down with `tearDownEntryFiber` (fiber effects)
18
+ * and `removeOwnedStyles` (only `style[data-plugin]` tags), so a bare
19
+ * `MutationObserver` on `document.body` is reachable by neither and would
20
+ * survive every HMR reload, accumulating one per reload.
21
+ */
22
+ export type BrandDisposer = () => void;
23
+ /** Inject the hero headline patch CSS; the disposer removes the tag. */
24
+ export declare function installHeroPatchCss(target: Document): BrandDisposer;
25
+ /**
26
+ * JS hover bridge: CSS ~ only matches forward siblings, so hovering the
27
+ * title text cannot trigger the logo's animation (the logo sits before the
28
+ * title in DOM order). This adds a mouseenter/mouseleave listener on the
29
+ * title span that toggles a class on the logo to trigger its bob animation.
30
+ *
31
+ * The disposer removes both listeners from every span it bridged, so a reload
32
+ * does not stack a second pair on the same element (the `data-` marker stops a
33
+ * *re-install* from adding them, but it also means a later disposer cannot
34
+ * find them by query alone — hence the bookkeeping below).
35
+ */
36
+ export declare function installHeroHoverBridge(target: Document): BrandDisposer;
10
37
  /**
11
38
  * Render the AI Shell mark with the presentation requested by its host surface.
12
39
  * Used in both the expanded sidebar brand row and the collapsed rail.
13
40
  */
14
41
  export declare function AishellBrandMark({ size }: SidebarBrandMarkOwnerProps): import("react").JSX.Element;
15
42
  /**
16
- * Render the AI Shell name with a version badge in the top-right corner,
17
- * matching DSH's native `.buildVersion` badge style.
43
+ * Render the AI Shell name in the sidebar brand row. The product name itself
44
+ * is drawn by the CSS module's `.name::before` (content: 'AI Shell'), so the
45
+ * markup is a single empty span.
18
46
  */
19
47
  export declare function AishellBrandName(): import("react").JSX.Element;
20
48
  /**
@@ -22,4 +50,13 @@ export declare function AishellBrandName(): import("react").JSX.Element;
22
50
  * className for surrounding mark geometry (e.g. centering).
23
51
  */
24
52
  export declare function AishellHeroBrandMark({ size, className }: HeroBrandMarkOwnerProps): import("react").JSX.Element;
53
+ /**
54
+ * Insert "Powered by deepseek-harness" below the hero headline, centered
55
+ * inside .stack. Uses a MutationObserver because the hero mounts/unmounts
56
+ * as the user navigates between sessions.
57
+ *
58
+ * The disposer stops the observer and removes every label this install added,
59
+ * so unloading the plugin leaves the hero exactly as it found it.
60
+ */
61
+ export declare function installPoweredBy(target: Document): BrandDisposer;
25
62
  //# sourceMappingURL=Brand.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Example-prompt cards for the New-Session hero.
3
+ *
4
+ * ## Where this renders, and why there
5
+ *
6
+ * `conversation.input.dock` is the band between the hero workspace row and
7
+ * the input card. It is a `list` slot, so this entry composes beside
8
+ * whatever else occupies the band instead of competing for a seat — the
9
+ * hero row's own seats (`conversation.hero.workspace`,
10
+ * `conversation.hero.agentPreset`) are `single` and already held by official
11
+ * occupants, which is the trap the account chip next door documents.
12
+ *
13
+ * ## When it renders
14
+ *
15
+ * The band itself renders in both the New-Session hero and the docked
16
+ * session composer — it only needs a live input zone — so the gate is
17
+ * `session.blank`, the same condition the account chip uses. `blank` is true
18
+ * from the moment a New Session opens until the first turn is accepted:
19
+ * exactly the window the cards exist for. Once the first message is sent the
20
+ * strip renders nothing and the composer takes over.
21
+ *
22
+ * ## Carousel
23
+ *
24
+ * Three cards or fewer sit side by side with no controls — a strip with
25
+ * nothing to scroll should not offer scrolling. More than three become a
26
+ * carousel: the row scrolls horizontally, arrows step it one card at a
27
+ * time, and it wraps at both ends. Wrapping rather than disabling the
28
+ * arrow at an end keeps the two controls symmetric (an arrow that is
29
+ * sometimes dead is a control a person has to think about), and it is what
30
+ * the feature was asked for.
31
+ *
32
+ * The arrows hold the scroll container's position in a ref and clamp it
33
+ * themselves rather than reading scrollLeft back from the DOM: the read
34
+ * happens before the browser has applied a pending smooth scroll, so a
35
+ * second click during the animation would compute from a stale position and
36
+ * step backwards. The container's own scroll event re-syncs the ref, so a
37
+ * manual trackpad scroll cannot desynchronize it.
38
+ *
39
+ * @module @huaweidevtools/aishell-dsh-brand/client/SuggestionCards
40
+ */
41
+ import { type ReactNode } from 'react';
42
+ import type { PropsRuntime, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
43
+ /** Full component props: the dock band's owner share + the session kit + `t`. */
44
+ export type SuggestionCardsProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<'aishell-brand'>;
45
+ export declare function SuggestionCards(props: SuggestionCardsProps): ReactNode;
46
+ //# sourceMappingURL=SuggestionCards.d.ts.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Carousel position maths, kept pure so it can be tested without a DOM.
3
+ *
4
+ * The strip is a RING, not a scroll range: the cards form a circle, and an
5
+ * arrow steps one card around it forever. Clicking next from the last
6
+ * position lands on the first, and clicking prev from the first lands on the
7
+ * last — and crucially, every intermediate position exists. A four-card ring
8
+ * showing three at a time has FOUR positions, not two:
9
+ *
10
+ * cards 1 2 3 4, three visible
11
+ * pos 0 → [1 2 3]
12
+ * pos 1 → [2 3 4]
13
+ * pos 2 → [3 4 1] ← the wrap is visible, not a jump back
14
+ * pos 3 → [4 1 2]
15
+ * pos 4 → [1 2 3] same as pos 0
16
+ *
17
+ * A plain scroll container cannot express that: its range is only
18
+ * `content - viewport` (one step, for four cards), and positions 2 and 3 have
19
+ * no place to live. So the card list is rendered {@link COPIES} times over
20
+ * and the window travels through the middle copy, where both neighbours
21
+ * always have content.
22
+ *
23
+ * ## Why the copies, and how the seam stays invisible
24
+ *
25
+ * With the list rendered three times, a window at physical position `p` shows
26
+ * `cards[p .. p+VISIBLE-1]`, and position `2n` shows exactly what position `n`
27
+ * shows. That equivalence is the seam: once a slide settles at `2n`, the
28
+ * track can be placed at `n` in the same frame with transitions off, and
29
+ * nothing on screen changes. The same holds at the other end, where `n-1` and
30
+ * `2n-1` are the same window.
31
+ *
32
+ * The rewind happens only AFTER the slide finishes — never before. Jumping
33
+ * first would put the destination on screen instantly and then animate away
34
+ * from it, so a click at the seam would appear to skip a card. This is the
35
+ * one law in this module that a reader is likely to get backwards.
36
+ *
37
+ * @module @huaweidevtools/aishell-dsh-brand/client/carousel
38
+ */
39
+ /**
40
+ * How many times the card list is rendered.
41
+ *
42
+ * Three is the minimum that works. The window lives in the middle copy, so it
43
+ * needs one full copy behind it (to show the cards before the start) and one
44
+ * ahead (to show the cards past the end while a slide is finishing at the
45
+ * seam, where the position may briefly reach `2n` and read up to
46
+ * `2n + VISIBLE - 1`).
47
+ */
48
+ export declare const COPIES = 3;
49
+ /** How many cards to render for a ring of `n`. */
50
+ export declare function ringLength(n: number): number;
51
+ /**
52
+ * The physical position the ring starts at — the first card of the middle
53
+ * copy. Starting here rather than at 0 is what makes the very first prev
54
+ * click land on a real card instead of an empty track.
55
+ */
56
+ export declare function ringStart(n: number): number;
57
+ /**
58
+ * Where a settled position should be silently rewound to, or `null` when it
59
+ * is already inside the working band.
60
+ *
61
+ * The band is `[n, 2n-1]`. A slide may finish one past either end — `2n`
62
+ * going forward, `n-1` going backward — and both have an exact twin inside
63
+ * the band. See the module note for why this runs after the slide, not
64
+ * before.
65
+ */
66
+ export declare function rewindTo(pos: number, n: number): number | null;
67
+ /** One arrow press, resolved into the physical positions involved. */
68
+ export interface RingStep {
69
+ /**
70
+ * Where the track must be placed with transitions OFF before the slide.
71
+ * Equal to the incoming position when no rewind is due; when it differs,
72
+ * the jump is invisible (both positions show the same window).
73
+ */
74
+ readonly from: number;
75
+ /**
76
+ * Where the slide ends. May sit one past the band's end, in which case the
77
+ * next {@link rewindTo} brings it back.
78
+ */
79
+ readonly to: number;
80
+ }
81
+ /**
82
+ * Resolve one arrow press on a ring.
83
+ *
84
+ * Any rewind still owed from the previous press is settled first, so a person
85
+ * clicking faster than the slide animation cannot walk the window off the
86
+ * rendered copies — the band always absorbs one more step.
87
+ */
88
+ export declare function ringStep(pos: number, delta: number, n: number): RingStep;
89
+ /**
90
+ * One arrow press on a plain strip (fewer cards than fit): step by one and
91
+ * stay inside the scrollable range.
92
+ *
93
+ * @param maxPos - the last meaningful position, `0` when nothing scrolls.
94
+ */
95
+ export declare function clampStep(pos: number, delta: number, maxPos: number): number;
96
+ //# sourceMappingURL=carousel.d.ts.map
@@ -8,7 +8,7 @@
8
8
  * @module @huaweidevtools/aishell-dsh-brand/client
9
9
  */
10
10
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
11
- /** Required service: the UI slot registry. */
11
+ /** Required services: the UI slot registry and the locale registry. */
12
12
  export declare const inject: string[];
13
13
  export declare function apply(ctx: ClientContext): void;
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * aishell brand locale dictionaries.
3
+ *
4
+ * The brand plugin owns its own namespace rather than reusing `aishell`
5
+ * (which `aishell-credentials` registers): a namespace's texts have exactly
6
+ * one owner, and a duplicate `(ns, locale)` registration throws.
7
+ *
8
+ * ## Why content lives in a dictionary
9
+ *
10
+ * The suggestion cards are the only strings in this plugin a person reads,
11
+ * and they are CONTENT rather than interface chrome — a card title, its
12
+ * one-line gloss, and the prompt a click inserts. They still belong here:
13
+ * a card that says「云资源查询」to an English reader is worse than no card,
14
+ * and the prompt it inserts would start an English conversation in Chinese.
15
+ *
16
+ * Chinese is the key-set source of truth; English is complete against it and
17
+ * is also the terminal fallback, so every locale other than `zh` reads
18
+ * English. That is the framework's own chain (`LOCALE_IDS = ["zh", "en"]`,
19
+ * with English carrying no fallback) — there is no third dictionary to write.
20
+ *
21
+ * @module @huaweidevtools/aishell-dsh-brand/client/locales
22
+ */
23
+ /** Dictionary namespace owned by this plugin. */
24
+ export declare const NS: "aishell-brand";
25
+ /** Simplified Chinese dictionary (the key-set source of truth). */
26
+ export declare const zh: {
27
+ readonly 'suggestion.previous': "上一个示例";
28
+ readonly 'suggestion.next': "下一个示例";
29
+ readonly 'suggestion.inventory.title': "云资源查询";
30
+ readonly 'suggestion.inventory.gloss': "列出当前账号下的云资源";
31
+ readonly 'suggestion.inventory.prompt': "请列出我的华为云资源清单,按服务分类(计算、存储、网络、数据库等),标出每类资源的数量和所在区域。";
32
+ readonly 'suggestion.deploy.title': "部署方案推荐";
33
+ readonly 'suggestion.deploy.gloss': "按访问量和预算给出可落地的部署路径";
34
+ readonly 'suggestion.deploy.prompt': "我要部署一个轻量级 Web 应用,请对比几种可行的部署方案,说明各自的适用规模、月度成本量级和运维复杂度,并给出你的推荐及理由。";
35
+ readonly 'suggestion.enterprise.title': "企业上云";
36
+ readonly 'suggestion.enterprise.gloss': "多账号、网络分区与权限边界的整体设计";
37
+ readonly 'suggestion.enterprise.prompt': "请为我规划一套云上 Landing Zone 架构,覆盖账号组织结构、网络分区、权限边界与审计要求,并说明各部分的设计取舍。";
38
+ readonly 'suggestion.billing.title': "账单分析";
39
+ readonly 'suggestion.billing.gloss': "分析上个月的云资源支出";
40
+ readonly 'suggestion.billing.prompt': "请分析我上个月的云资源支出构成,找出占比最高的几项服务,指出其中可以优化或降配的部分,并估算调整后能节省多少。";
41
+ };
42
+ /** English dictionary. Complete against {@link zh}; also the terminal fallback. */
43
+ export declare const en: {
44
+ readonly 'suggestion.previous': "Previous example";
45
+ readonly 'suggestion.next': "Next example";
46
+ readonly 'suggestion.inventory.title': "Cloud inventory";
47
+ readonly 'suggestion.inventory.gloss': "List the resources in this account";
48
+ readonly 'suggestion.inventory.prompt': string;
49
+ readonly 'suggestion.deploy.title': "Deployment options";
50
+ readonly 'suggestion.deploy.gloss': "A workable path for your traffic and budget";
51
+ readonly 'suggestion.deploy.prompt': string;
52
+ readonly 'suggestion.enterprise.title': "Landing zone";
53
+ readonly 'suggestion.enterprise.gloss': "Multi-account structure, network and permission boundaries";
54
+ readonly 'suggestion.enterprise.prompt': string;
55
+ readonly 'suggestion.billing.title': "Billing analysis";
56
+ readonly 'suggestion.billing.gloss': "Break down last month's cloud spend";
57
+ readonly 'suggestion.billing.prompt': string;
58
+ };
59
+ /** Every key, derived from the zh dictionary (the source of truth). */
60
+ export type AishellBrandLocaleKey = keyof typeof zh;
61
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
62
+ interface LocaleNamespaceMap {
63
+ 'aishell-brand': AishellBrandLocaleKey;
64
+ }
65
+ }
66
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Inline SVG artwork for the suggestion cards.
3
+ *
4
+ * Inline rather than an icon dependency: the strip draws four shapes, and a
5
+ * dependency would add a package, a version to track, and a bundle face to
6
+ * keep in step with the host.
7
+ *
8
+ * Every path is drawn on a 24×24 viewBox with `currentColor`, so a card
9
+ * decides the colour by setting `color` and nothing here hard-codes a theme
10
+ * value.
11
+ *
12
+ * ## Shape rules, learned by getting them wrong
13
+ *
14
+ * The first draft drew "deploy" as a box with an arrow leaving it — which
15
+ * reads as SHARE or export, the one meaning this card must not carry. A
16
+ * glyph in a row of four is read at a glance and out of context, so each one
17
+ * has to be recognisable on its own:
18
+ *
19
+ * - nothing may resemble the share glyph (box + outward arrow) or the
20
+ * external-link glyph (box + arrow through a corner);
21
+ * - every shape is closed and filled-ish in silhouette, because outlines
22
+ * at 20px in a row of four blur into each other;
23
+ * - no glyph carries more than three strokes, so they stay distinguishable
24
+ * at the size they are actually drawn.
25
+ *
26
+ * These rules are not machine-checked: rendering the glyphs needs
27
+ * `react-dom/server`, which this repo does not depend on, and vitest here
28
+ * collects only `.spec.ts` (not `.tsx`). The browser harness in
29
+ * `scripts/render-check/` is what actually puts them on screen.
30
+ *
31
+ * @module @huaweidevtools/aishell-dsh-brand/client/suggestion-icons
32
+ */
33
+ import type { ReactNode } from 'react';
34
+ import type { SuggestionIcon } from './suggestions.ts';
35
+ /** Props shared by every glyph. */
36
+ interface IconProps {
37
+ /** Rendered size in px; the cards use 20. */
38
+ readonly size?: number;
39
+ }
40
+ /** Resolve one icon key to its glyph. */
41
+ export declare function SuggestionIconArt({ icon, size, }: IconProps & {
42
+ readonly icon: SuggestionIcon;
43
+ }): ReactNode;
44
+ export {};
45
+ //# sourceMappingURL=suggestion-icons.d.ts.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The New-Session example cards, and the shapes they need.
3
+ *
4
+ * One array drives the whole strip: add or reorder a card by editing
5
+ * `SUGGESTIONS` and adding its three keys to `locales.ts`, and nothing else.
6
+ * The row renders whatever it is given — three or fewer display side by side
7
+ * with no arrows, more than three become a carousel.
8
+ *
9
+ * ## This file holds no copy
10
+ *
11
+ * Every string a person reads — the card title, its one-line gloss, and the
12
+ * prompt a click inserts — lives in `locales.ts`, addressed by the keys here.
13
+ * A card whose text were inlined would be readable in exactly one language,
14
+ * and the prompt it inserted would start a conversation in that language no
15
+ * matter what the reader's locale was.
16
+ *
17
+ * The three keys per card are separate rather than one blob because the
18
+ * surfaces differ: the card shows `title` and `gloss` (both clamped — one
19
+ * line and two lines — so a long string ellipsizes instead of breaking the
20
+ * row), while `prompt` goes to the composer and is never displayed on the
21
+ * card. A prompt that only restates its title makes the click pointless;
22
+ * written as a pair, the prompt carries the context and the expected output
23
+ * that make the first message worth sending without editing.
24
+ *
25
+ * ## Sizing, when editing copy
26
+ *
27
+ * Three cards share one row, which at the default column leaves roughly 150px
28
+ * per title — about seven CJK glyphs, or noticeably more Latin ones. Measured
29
+ * against the shipped set: every title fits at 560px and above, and all four
30
+ * clip below ~460px. Keep them within a couple of characters of each other —
31
+ * the longest one is otherwise the only card that ever shows an ellipsis,
32
+ * which reads as a bug rather than as a long name.
33
+ *
34
+ * @module @huaweidevtools/aishell-dsh-brand/client/suggestions
35
+ */
36
+ import type { AishellBrandLocaleKey } from './locales.ts';
37
+ /** One example card: what it looks like, and what a click inserts. */
38
+ export interface Suggestion {
39
+ /**
40
+ * Stable identity for React keys and for the carousel's position maths.
41
+ * Not shown to anyone — it names the dictionary key prefix, so renaming it
42
+ * means renaming three keys in `locales.ts`.
43
+ */
44
+ readonly id: string;
45
+ /** Icon key resolved by {@link SuggestionIconArt}. */
46
+ readonly icon: SuggestionIcon;
47
+ /** Dictionary key for the card's one-line title. */
48
+ readonly titleKey: AishellBrandLocaleKey;
49
+ /** Dictionary key for the line under the title. */
50
+ readonly glossKey: AishellBrandLocaleKey;
51
+ /** Dictionary key for the text inserted into the composer. */
52
+ readonly promptKey: AishellBrandLocaleKey;
53
+ }
54
+ /** The icon set this strip can draw. Keys are local to this plugin. */
55
+ export type SuggestionIcon = 'cloud' | 'deploy' | 'architecture' | 'cost';
56
+ /**
57
+ * The cards, in display order.
58
+ *
59
+ * Order matters only for the ring's starting window: the strip opens on the
60
+ * first {@link VISIBLE} cards, so the first entries are the ones a person
61
+ * sees without pressing an arrow.
62
+ */
63
+ export declare const SUGGESTIONS: readonly Suggestion[];
64
+ //# sourceMappingURL=suggestions.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@huaweidevtools/aishell-dsh-brand",
3
3
  "description": "AI Shell brand reskin for DSH web — replaces sidebar and hero brand marks with AI Shell SVG",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -30,7 +30,8 @@
30
30
  "inject": [
31
31
  "@deepseek-ai/dsh-client-ui-renderer",
32
32
  "@deepseek-ai/dsh-client-ui-sidebar",
33
- "@deepseek-ai/dsh-client-ui-conversation"
33
+ "@deepseek-ai/dsh-client-ui-conversation",
34
+ "@deepseek-ai/dsh-client-locale"
34
35
  ],
35
36
  "platform": "web"
36
37
  },
@@ -44,10 +45,11 @@
44
45
  },
45
46
  "devDependencies": {
46
47
  "@deepseek-ai/cordis": "^4.0.4",
47
- "@deepseek-ai/dsh-client-ui-conversation": ">=0.2.0-0 <0.3.0-0",
48
- "@deepseek-ai/dsh-client-ui-renderer": ">=0.2.0-0 <0.3.0-0",
49
- "@deepseek-ai/dsh-client-ui-sidebar": ">=0.2.0-0 <0.3.0-0",
50
- "@deepseek-ai/dsh-client-ui-slots": ">=0.2.0-0 <0.3.0-0",
48
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
49
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
50
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
51
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
52
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1",
51
53
  "@types/react": "~18.3.1",
52
54
  "react": "^18.2.0",
53
55
  "tsdown": "^0.10.0",