@huaweidevtools/aishell-dsh-brand 0.0.5 → 0.0.7
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
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
//#endregion
|
|
27
27
|
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
28
28
|
const react = __toESM(require("react"));
|
|
29
|
+
const react_dom = __toESM(require("react-dom"));
|
|
29
30
|
|
|
30
31
|
//#region \0dsh-css:/home/eushing/opensource/aishell-dsh-plugins/packages/aishell-brand/src/client/brand.module.css.mjs
|
|
31
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}";
|
|
@@ -277,6 +278,7 @@ const SUGGESTIONS = [
|
|
|
277
278
|
{
|
|
278
279
|
id: "inventory",
|
|
279
280
|
icon: "cloud",
|
|
281
|
+
hue: { tile: "var(--dsw-static-blue-400)" },
|
|
280
282
|
titleKey: "suggestion.inventory.title",
|
|
281
283
|
glossKey: "suggestion.inventory.gloss",
|
|
282
284
|
promptKey: "suggestion.inventory.prompt"
|
|
@@ -284,6 +286,7 @@ const SUGGESTIONS = [
|
|
|
284
286
|
{
|
|
285
287
|
id: "deploy",
|
|
286
288
|
icon: "deploy",
|
|
289
|
+
hue: { tile: "var(--dsw-static-green-500)" },
|
|
287
290
|
titleKey: "suggestion.deploy.title",
|
|
288
291
|
glossKey: "suggestion.deploy.gloss",
|
|
289
292
|
promptKey: "suggestion.deploy.prompt"
|
|
@@ -291,6 +294,7 @@ const SUGGESTIONS = [
|
|
|
291
294
|
{
|
|
292
295
|
id: "enterprise",
|
|
293
296
|
icon: "architecture",
|
|
297
|
+
hue: { tile: "rgb(139, 118, 246)" },
|
|
294
298
|
titleKey: "suggestion.enterprise.title",
|
|
295
299
|
glossKey: "suggestion.enterprise.gloss",
|
|
296
300
|
promptKey: "suggestion.enterprise.prompt"
|
|
@@ -298,6 +302,7 @@ const SUGGESTIONS = [
|
|
|
298
302
|
{
|
|
299
303
|
id: "billing",
|
|
300
304
|
icon: "cost",
|
|
305
|
+
hue: { tile: "var(--dsw-static-amber-600)" },
|
|
301
306
|
titleKey: "suggestion.billing.title",
|
|
302
307
|
glossKey: "suggestion.billing.gloss",
|
|
303
308
|
promptKey: "suggestion.billing.prompt"
|
|
@@ -483,11 +488,290 @@ function ringStep(pos, delta, n) {
|
|
|
483
488
|
};
|
|
484
489
|
}
|
|
485
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
|
+
|
|
486
648
|
//#endregion
|
|
487
649
|
//#region src/client/SuggestionCards.tsx
|
|
488
650
|
/** How many cards a full-width strip shows at once. */
|
|
489
651
|
const VISIBLE = 3;
|
|
490
|
-
/**
|
|
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
|
+
*/
|
|
491
775
|
function Card({ suggestion, onPick, t }) {
|
|
492
776
|
const [hover, setHover] = (0, react.useState)(false);
|
|
493
777
|
const [focus, setFocus] = (0, react.useState)(false);
|
|
@@ -513,39 +797,50 @@ function Card({ suggestion, onPick, t }) {
|
|
|
513
797
|
flex: `0 0 calc((100cqw - ${VISIBLE - 1} * var(--dsh-suggestion-gap)) / ${VISIBLE})`,
|
|
514
798
|
display: "flex",
|
|
515
799
|
alignItems: "flex-start",
|
|
516
|
-
gap:
|
|
800
|
+
gap: 12,
|
|
517
801
|
boxSizing: "border-box",
|
|
518
802
|
minWidth: 0,
|
|
519
|
-
padding: "
|
|
803
|
+
padding: "14px",
|
|
520
804
|
textAlign: "left",
|
|
521
805
|
font: "inherit",
|
|
522
806
|
cursor: "pointer",
|
|
523
807
|
color: "var(--dsw-alias-label-primary)",
|
|
524
|
-
background: lit ?
|
|
525
|
-
border:
|
|
526
|
-
borderRadius:
|
|
527
|
-
transition: "background
|
|
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",
|
|
528
812
|
outline: focus ? "2px solid var(--dsw-alias-state-business-primary)" : "none",
|
|
529
813
|
outlineOffset: 2
|
|
530
814
|
},
|
|
531
815
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
816
|
+
"aria-hidden": true,
|
|
532
817
|
style: {
|
|
533
|
-
|
|
534
|
-
|
|
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)"
|
|
535
827
|
},
|
|
536
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuggestionIconArt, {
|
|
828
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuggestionIconArt, {
|
|
829
|
+
icon: suggestion.icon,
|
|
830
|
+
size: 17
|
|
831
|
+
})
|
|
537
832
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
538
833
|
style: {
|
|
539
834
|
display: "flex",
|
|
540
835
|
flexDirection: "column",
|
|
541
|
-
gap:
|
|
836
|
+
gap: 3,
|
|
542
837
|
minWidth: 0
|
|
543
838
|
},
|
|
544
839
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
545
840
|
style: {
|
|
546
841
|
fontSize: "var(--dsh-content-font-size-secondary, 13px)",
|
|
547
|
-
fontWeight:
|
|
548
|
-
lineHeight: "
|
|
842
|
+
fontWeight: 600,
|
|
843
|
+
lineHeight: "19px",
|
|
549
844
|
overflow: "hidden",
|
|
550
845
|
textOverflow: "ellipsis",
|
|
551
846
|
whiteSpace: "nowrap"
|
|
@@ -554,7 +849,7 @@ function Card({ suggestion, onPick, t }) {
|
|
|
554
849
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
555
850
|
style: {
|
|
556
851
|
fontSize: "12px",
|
|
557
|
-
lineHeight: "
|
|
852
|
+
lineHeight: "17px",
|
|
558
853
|
color: "var(--dsw-alias-label-tertiary)",
|
|
559
854
|
display: "-webkit-box",
|
|
560
855
|
WebkitLineClamp: 2,
|
|
@@ -620,8 +915,40 @@ function SuggestionCards(props) {
|
|
|
620
915
|
const trackRef = (0, react.useRef)(null);
|
|
621
916
|
const posRef = (0, react.useRef)(ringStart(n));
|
|
622
917
|
const stepRef = (0, react.useRef)(0);
|
|
623
|
-
const [animating, setAnimating] = (0, react.useState)(true);
|
|
624
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)";
|
|
625
952
|
/** Place the track at a position with no transition. */
|
|
626
953
|
const snapTo = (0, react.useCallback)((pos) => {
|
|
627
954
|
const track = trackRef.current;
|
|
@@ -633,16 +960,24 @@ function SuggestionCards(props) {
|
|
|
633
960
|
const slideTo = (0, react.useCallback)((pos) => {
|
|
634
961
|
const track = trackRef.current;
|
|
635
962
|
if (track === null) return;
|
|
636
|
-
track.style.transition =
|
|
963
|
+
track.style.transition = `transform ${SLIDE_MS}ms ${SLIDE_EASING}`;
|
|
637
964
|
track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
|
|
638
965
|
}, []);
|
|
966
|
+
const autoplayTimerRef = (0, react.useRef)(0);
|
|
967
|
+
const latestStepRef = (0, react.useRef)(() => {});
|
|
639
968
|
/**
|
|
640
|
-
*
|
|
969
|
+
* Schedule the next auto-rotation tick one full interval from now,
|
|
970
|
+
* replacing any tick already pending.
|
|
641
971
|
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
* apart, and so a breakpoint change needs no constant updated here.
|
|
972
|
+
* Called by every `step`, which is what makes the cadence immune to a tick
|
|
973
|
+
* landing mid-slide.
|
|
645
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
|
+
}, []);
|
|
646
981
|
/**
|
|
647
982
|
* Re-measure one step and re-apply the current position.
|
|
648
983
|
*
|
|
@@ -667,17 +1002,11 @@ function SuggestionCards(props) {
|
|
|
667
1002
|
if (!(step$1 > 0)) return false;
|
|
668
1003
|
const changed = step$1 !== stepRef.current;
|
|
669
1004
|
stepRef.current = step$1;
|
|
670
|
-
if (changed)
|
|
671
|
-
setAnimating(false);
|
|
672
|
-
snapTo(posRef.current);
|
|
673
|
-
window.requestAnimationFrame(() => {
|
|
674
|
-
setAnimating(true);
|
|
675
|
-
});
|
|
676
|
-
}
|
|
1005
|
+
if (changed) snapTo(posRef.current);
|
|
677
1006
|
return true;
|
|
678
1007
|
}, [snapTo]);
|
|
679
1008
|
(0, react.useEffect)(() => {
|
|
680
|
-
if (!blank || !ring) return;
|
|
1009
|
+
if (!blank || !ring || mount === null) return;
|
|
681
1010
|
let cancelled = false;
|
|
682
1011
|
const settle = () => {
|
|
683
1012
|
if (cancelled) return;
|
|
@@ -686,11 +1015,9 @@ function SuggestionCards(props) {
|
|
|
686
1015
|
return;
|
|
687
1016
|
}
|
|
688
1017
|
posRef.current = ringStart(n);
|
|
689
|
-
setAnimating(false);
|
|
690
1018
|
snapTo(posRef.current);
|
|
691
1019
|
window.requestAnimationFrame(() => {
|
|
692
1020
|
if (cancelled) return;
|
|
693
|
-
setAnimating(true);
|
|
694
1021
|
setReady(true);
|
|
695
1022
|
const viewport = viewportRef.current;
|
|
696
1023
|
if (viewport !== null) {
|
|
@@ -712,56 +1039,85 @@ function SuggestionCards(props) {
|
|
|
712
1039
|
blank,
|
|
713
1040
|
ring,
|
|
714
1041
|
n,
|
|
1042
|
+
mount,
|
|
715
1043
|
remeasure,
|
|
716
1044
|
snapTo
|
|
717
1045
|
]);
|
|
718
1046
|
/**
|
|
719
|
-
* Apply whichever rewind the settling position owes
|
|
720
|
-
*
|
|
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.
|
|
721
1056
|
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
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.
|
|
726
1062
|
*/
|
|
727
|
-
const onSettle = (0, react.useCallback)(() => {
|
|
1063
|
+
const onSettle = (0, react.useCallback)((event) => {
|
|
728
1064
|
if (!ring) return;
|
|
1065
|
+
if (event.target !== trackRef.current) return;
|
|
1066
|
+
if (event.propertyName !== "transform") return;
|
|
729
1067
|
const owed = rewindTo(posRef.current, n);
|
|
730
1068
|
if (owed === null) return;
|
|
731
1069
|
posRef.current = owed;
|
|
732
|
-
setAnimating(false);
|
|
733
1070
|
snapTo(owed);
|
|
734
|
-
window.requestAnimationFrame(() => {
|
|
735
|
-
setAnimating(true);
|
|
736
|
-
});
|
|
737
1071
|
}, [
|
|
738
1072
|
ring,
|
|
739
1073
|
n,
|
|
740
1074
|
snapTo
|
|
741
1075
|
]);
|
|
742
|
-
/** One arrow
|
|
1076
|
+
/** One step of the ring, from an arrow or from auto-rotation. */
|
|
743
1077
|
const step = (0, react.useCallback)((delta) => {
|
|
744
1078
|
if (!ring || !ready) return;
|
|
1079
|
+
bumpAutoplay();
|
|
745
1080
|
const { from, to } = ringStep(posRef.current, delta, n);
|
|
746
1081
|
if (from !== posRef.current) {
|
|
747
1082
|
posRef.current = from;
|
|
748
|
-
setAnimating(false);
|
|
749
1083
|
snapTo(from);
|
|
750
1084
|
}
|
|
751
1085
|
posRef.current = to;
|
|
752
|
-
|
|
753
|
-
setAnimating(true);
|
|
754
|
-
slideTo(to);
|
|
755
|
-
});
|
|
1086
|
+
slideTo(to);
|
|
756
1087
|
}, [
|
|
757
1088
|
ring,
|
|
758
1089
|
ready,
|
|
759
1090
|
n,
|
|
760
1091
|
snapTo,
|
|
761
|
-
slideTo
|
|
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
|
|
762
1117
|
]);
|
|
763
1118
|
if (!blank) return null;
|
|
764
1119
|
if (n === 0) return null;
|
|
1120
|
+
if (mount === null) return null;
|
|
765
1121
|
const cards = ring ? Array.from({ length: ringLength(n) }, (_, i) => SUGGESTIONS[i % n]) : SUGGESTIONS;
|
|
766
1122
|
const strip = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
767
1123
|
ref: viewportRef,
|
|
@@ -781,9 +1137,7 @@ function SuggestionCards(props) {
|
|
|
781
1137
|
alignItems: "stretch",
|
|
782
1138
|
gap: "var(--dsh-suggestion-gap)",
|
|
783
1139
|
width: "max-content",
|
|
784
|
-
willChange: "transform"
|
|
785
|
-
transition: animating ? "transform 320ms cubic-bezier(.22,.61,.36,1)" : "none",
|
|
786
|
-
transform: "translate3d(0, 0, 0)"
|
|
1140
|
+
willChange: "transform"
|
|
787
1141
|
},
|
|
788
1142
|
children: cards.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
789
1143
|
suggestion,
|
|
@@ -792,14 +1146,27 @@ function SuggestionCards(props) {
|
|
|
792
1146
|
}, `${i}:${suggestion.id}`))
|
|
793
1147
|
})
|
|
794
1148
|
});
|
|
795
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
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
|
+
},
|
|
796
1163
|
style: {
|
|
797
|
-
["--dsh-suggestion-gap"]: "
|
|
1164
|
+
["--dsh-suggestion-gap"]: "10px",
|
|
798
1165
|
display: "flex",
|
|
799
1166
|
alignItems: "center",
|
|
800
1167
|
gap: 6,
|
|
801
1168
|
minWidth: 0,
|
|
802
|
-
margin: "
|
|
1169
|
+
margin: "12px 0 22px"
|
|
803
1170
|
},
|
|
804
1171
|
children: [
|
|
805
1172
|
ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
|
|
@@ -818,7 +1185,7 @@ function SuggestionCards(props) {
|
|
|
818
1185
|
}
|
|
819
1186
|
})
|
|
820
1187
|
]
|
|
821
|
-
});
|
|
1188
|
+
}), mount);
|
|
822
1189
|
}
|
|
823
1190
|
|
|
824
1191
|
//#endregion
|
|
@@ -1,40 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Example-prompt cards for the New-Session hero.
|
|
3
3
|
*
|
|
4
|
-
* ## Where this renders, and why there
|
|
4
|
+
* ## Where this renders, and why it takes a portal to get there
|
|
5
5
|
*
|
|
6
|
-
* `conversation.input.dock
|
|
7
|
-
* the input card.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* The entry occupies `conversation.input.dock`, the band between the hero
|
|
7
|
+
* workspace row and the input card. That is where the strip is MOUNTED — but
|
|
8
|
+
* not where it is DRAWN: the product wants the cards directly under the hero
|
|
9
|
+
* headline, and the workspace row sits between the two.
|
|
10
|
+
*
|
|
11
|
+
* Measured on the live page (1280×900):
|
|
12
|
+
*
|
|
13
|
+
* hero (headline) y=288 h=74
|
|
14
|
+
* hero workspace row y=374 h=28
|
|
15
|
+
* conversation.input.dock y=411 h=80
|
|
16
|
+
* the input card y=491
|
|
17
|
+
*
|
|
18
|
+
* The row cannot be crossed with a margin — it would be covered — and its DOM
|
|
19
|
+
* is a sibling subtree, unreachable from the dock. So the strip renders
|
|
20
|
+
* through `createPortal` into the hero's own content div, the empty `_body`
|
|
21
|
+
* the official shell leaves for exactly this. See `hero-content.ts` for how
|
|
22
|
+
* that mount is found and why it fails closed.
|
|
23
|
+
*
|
|
24
|
+
* The dock entry is still what supplies the slot props (`useSession`,
|
|
25
|
+
* `inputActions`, `t`), so the registration is unchanged; the portal only
|
|
26
|
+
* relocates the output. What the dock band itself renders is nothing.
|
|
12
27
|
*
|
|
13
28
|
* ## When it renders
|
|
14
29
|
*
|
|
15
|
-
* The band
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* ##
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* the
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
30
|
+
* The dock band renders in BOTH the hero and the docked composer (it only
|
|
31
|
+
* needs a live input zone), so the gate is `session.blank` — the same
|
|
32
|
+
* condition the account chip next door uses. `blank` is true from the moment a
|
|
33
|
+
* New Session opens until the first turn is accepted: exactly the window the
|
|
34
|
+
* cards exist for. Once the first message is sent the strip renders nothing
|
|
35
|
+
* and the composer takes over.
|
|
36
|
+
*
|
|
37
|
+
* ## Why it does not look like the input card
|
|
38
|
+
*
|
|
39
|
+
* The first version read as a second input box, and the reason was measurable
|
|
40
|
+
* rather than a matter of taste. Both surfaces were built from the same two
|
|
41
|
+
* ingredients the shell reserves for a composer:
|
|
42
|
+
*
|
|
43
|
+
* - the same neutral one step apart — the card was `bg-module-platform`
|
|
44
|
+
* (`#353638` in dark) and the composer `specific-input-major` (`#2c2c2e`),
|
|
45
|
+
* both rungs of one ramp, so the two read as siblings;
|
|
46
|
+
* - the same box-shadow token. `--dsw-elevation-soft` expands to
|
|
47
|
+
* `var(--dsw-elevation-stroke), …`, and that stroke is a
|
|
48
|
+
* `0 0 0 .5px` ring — so "no border" had not removed the outline at all,
|
|
49
|
+
* it had only moved it into the shadow. Both surfaces carry a hairline.
|
|
50
|
+
*
|
|
51
|
+
* The fix is to use the shell's vocabulary for a CHOICE rather than for a
|
|
52
|
+
* FIELD. Every pickable surface in the product — the option list in the
|
|
53
|
+
* question card (`border-radius: var(--dsw-radius-md)`, `background: 0 0`),
|
|
54
|
+
* the command and trigger lists, the portalled menu — draws itself the same
|
|
55
|
+
* way: a transparent ground, a `radius-md` corner, and a fill that appears
|
|
56
|
+
* only on hover or selection. A field, by contrast, is an opaque well inset
|
|
57
|
+
* into the page.
|
|
58
|
+
*
|
|
59
|
+
* So a card here is:
|
|
60
|
+
*
|
|
61
|
+
* - TRANSLUCENT, not opaque and not empty. The shell explains this exact
|
|
62
|
+
* choice for `SegmentedControl`: a translucent fill "reads as a place
|
|
63
|
+
* rather than a second button". Opaque is what made the card a well;
|
|
64
|
+
* fully transparent is invisible on a white page, where the light theme
|
|
65
|
+
* would leave nothing but floating text. The tint is the shell's own
|
|
66
|
+
* interactive pair — `interactive-bg-hover` at rest, `interactive-bg-active`
|
|
67
|
+
* on hover — so hovering reads as the same material intensifying;
|
|
68
|
+
* - `radius-md` (12px), the choice radius, against the composer's 28px
|
|
69
|
+
* `radius-panel`;
|
|
70
|
+
* - no shadow at rest. The shell gives an elevated surface a shadow only
|
|
71
|
+
* when it FLOATS over other content (a menu, a hovercard, a modal); a
|
|
72
|
+
* card sitting inline in the page gets none, so neither does this.
|
|
73
|
+
*
|
|
74
|
+
* The icon tile keeps an accent tint — `Tag`'s info tone and the plan
|
|
75
|
+
* package's chip both paint `color-mix(… business-primary 10%, transparent)`
|
|
76
|
+
* — on a square with `radius-sm`, matching those chips' geometry.
|
|
77
|
+
*
|
|
78
|
+
* ## Auto-rotation
|
|
79
|
+
*
|
|
80
|
+
* The strip shows three of four cards, so the fourth would otherwise be
|
|
81
|
+
* invisible to anyone who does not touch the arrows. It advances one card
|
|
82
|
+
* every {@link AUTOPLAY_INTERVAL_MS}, pausing on hover, on keyboard focus, in
|
|
83
|
+
* a background tab, and under `prefers-reduced-motion`. The policy itself
|
|
84
|
+
* lives in `autoplay.ts`, kept pure so the list of reasons to stop is testable
|
|
85
|
+
* rather than buried in an effect.
|
|
38
86
|
*
|
|
39
87
|
* @module @huaweidevtools/aishell-dsh-brand/client/SuggestionCards
|
|
40
88
|
*/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-rotation policy for the example-prompt strip.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the component and free of DOM, for the same reason
|
|
5
|
+
* `carousel.ts` is: the DECISION is what needs pinning down, and a decision
|
|
6
|
+
* buried in an effect can only be checked by watching a browser.
|
|
7
|
+
*
|
|
8
|
+
* ## Why the strip advances on its own
|
|
9
|
+
*
|
|
10
|
+
* The strip shows three of four cards, so a card is always off screen and a
|
|
11
|
+
* reader who never touches the arrows never learns it exists. Rotation is what
|
|
12
|
+
* makes the set self-evident. The cost is that content moves without being
|
|
13
|
+
* asked to, which is why every reason to stop is collected here rather than
|
|
14
|
+
* decided piecemeal at the call site.
|
|
15
|
+
*
|
|
16
|
+
* ## Every reason to stop
|
|
17
|
+
*
|
|
18
|
+
* - `engaged` — the pointer is over the strip or focus is inside it. Someone
|
|
19
|
+
* reaching for a card must not have it slide out from under the cursor;
|
|
20
|
+
* this is the pause the feature was asked for, and `focus` is the half of
|
|
21
|
+
* it that covers a reader who never uses a pointer.
|
|
22
|
+
* - `reducedMotion` — the reader asked the platform for less movement. An
|
|
23
|
+
* element that moves by itself for more than five seconds is exactly what
|
|
24
|
+
* `prefers-reduced-motion` exists to suppress, and a hover pause cannot help
|
|
25
|
+
* the reader who cannot hover. Honouring it is what keeps the feature from
|
|
26
|
+
* being motion no one consented to.
|
|
27
|
+
* - `visible` — the tab is hidden. A background tab still fires timers, so
|
|
28
|
+
* without this the strip would churn through slides nobody can see and land
|
|
29
|
+
* somewhere arbitrary on return.
|
|
30
|
+
* - `ready` — the ring has not been measured yet. Advancing before the step
|
|
31
|
+
* is known would move the track by a wrong distance; see the measurement
|
|
32
|
+
* note in `SuggestionCards.tsx`.
|
|
33
|
+
* - `ring` — three cards or fewer all fit, so there is nowhere to advance to.
|
|
34
|
+
* A strip with nothing to scroll must not appear to scroll.
|
|
35
|
+
*
|
|
36
|
+
* @module @huaweidevtools/aishell-dsh-brand/client/autoplay
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* How long each card is shown before the strip advances.
|
|
40
|
+
*
|
|
41
|
+
* Fixed rather than configurable: this is a hint at what the product can do,
|
|
42
|
+
* not a reading surface, and a setting nobody asked for is a setting nobody
|
|
43
|
+
* finds.
|
|
44
|
+
*/
|
|
45
|
+
export declare const AUTOPLAY_INTERVAL_MS = 1500;
|
|
46
|
+
/** Everything that can hold the strip still. */
|
|
47
|
+
export interface AutoplayInputs {
|
|
48
|
+
/** More cards than fit, so there is somewhere to advance to. */
|
|
49
|
+
readonly ring: boolean;
|
|
50
|
+
/** The ring has been measured and the track can be moved a known distance. */
|
|
51
|
+
readonly ready: boolean;
|
|
52
|
+
/** The pointer is over the strip, or focus is inside it. */
|
|
53
|
+
readonly engaged: boolean;
|
|
54
|
+
/** The reader asked for reduced motion. */
|
|
55
|
+
readonly reducedMotion: boolean;
|
|
56
|
+
/** The tab is on screen. */
|
|
57
|
+
readonly visible: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether the strip should be advancing on its own right now.
|
|
61
|
+
*
|
|
62
|
+
* Expressed as one predicate rather than a chain of early returns inside the
|
|
63
|
+
* timer, so that adding a reason to stop is a change to this list and its
|
|
64
|
+
* test — not a change to the effect that schedules the ticks.
|
|
65
|
+
*/
|
|
66
|
+
export declare function autoplayActive(inputs: AutoplayInputs): boolean;
|
|
67
|
+
//# sourceMappingURL=autoplay.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mount point the suggestion strip renders into, inside the hero.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a portal, and not CSS placement
|
|
5
|
+
*
|
|
6
|
+
* The strip is a `conversation.input.dock` entry, and that band renders BELOW
|
|
7
|
+
* the hero workspace row in the composer stack. Measured on the live page
|
|
8
|
+
* (1280×900, DSH 0.2.x):
|
|
9
|
+
*
|
|
10
|
+
* hero (headline) y=288 h=74
|
|
11
|
+
* hero workspace row y=374 h=28
|
|
12
|
+
* conversation.input.dock y=411 h=80
|
|
13
|
+
* the input card y=491
|
|
14
|
+
*
|
|
15
|
+
* The product wants the cards directly under the headline, and the workspace
|
|
16
|
+
* row sits between. A negative margin cannot cross it — the row would be
|
|
17
|
+
* covered — and the row's own subtree is not reachable from the dock, since
|
|
18
|
+
* the two are separate children of the composer stack.
|
|
19
|
+
*
|
|
20
|
+
* The hero, however, already renders an empty content div for exactly this
|
|
21
|
+
* purpose. Its stack is: headline → (the "Powered by" label) → `_body`, where
|
|
22
|
+
* `_body` is `flex-direction: column` and left empty by the official shell.
|
|
23
|
+
* That is the hero's content area, and putting the cards there is using the
|
|
24
|
+
* slot as intended rather than fighting the layout.
|
|
25
|
+
*
|
|
26
|
+
* ## How the mount is found
|
|
27
|
+
*
|
|
28
|
+
* By the hero's own class suffix, from the headline outward — `[class*="_body"]`
|
|
29
|
+
* alone is ambiguous (the conversation root has a `_body` too, and the live
|
|
30
|
+
* page has two). So the search starts at a `[class*="_headline"]`, steps to its
|
|
31
|
+
* parent (the hero stack), and takes that stack's own `_body` child. The
|
|
32
|
+
* headline is unique on the page and is what makes the walk unambiguous.
|
|
33
|
+
*
|
|
34
|
+
* Children are walked directly rather than with `:scope >`. The two are
|
|
35
|
+
* equivalent here, and the direct walk keeps these helpers working against the
|
|
36
|
+
* hand-rolled DOM stub the specs use (which has no selector engine) instead of
|
|
37
|
+
* leaving the module's real logic untested.
|
|
38
|
+
*
|
|
39
|
+
* The classes are CSS-module hashes (`pXSMma_body`), hence the suffix match.
|
|
40
|
+
* A base bump that renames them fails closed: {@link findHeroContent} returns
|
|
41
|
+
* null, the portal renders nothing, and the arrows and cards simply do not
|
|
42
|
+
* appear — the strip is never left in the wrong parent.
|
|
43
|
+
*
|
|
44
|
+
* @module @huaweidevtools/aishell-dsh-brand/client/hero-content
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* The hero's content area, or `null` when the hero is not on screen.
|
|
48
|
+
*
|
|
49
|
+
* Returns null rather than throwing: a New Session that has not painted its
|
|
50
|
+
* hero yet is a normal state (and the strip retries on the next frame), not an
|
|
51
|
+
* error.
|
|
52
|
+
*/
|
|
53
|
+
export declare function findHeroContent(target: Document): HTMLElement | null;
|
|
54
|
+
/**
|
|
55
|
+
* A container for the strip inside the hero content area.
|
|
56
|
+
*
|
|
57
|
+
* The hero body is `display: block` and zero-height, so the strip cannot rely
|
|
58
|
+
* on it for layout and must not inherit that block context. The wrapper is
|
|
59
|
+
* created here rather than rendered by React into the official div, because
|
|
60
|
+
* React does not own that node — it owns only what the portal puts inside.
|
|
61
|
+
*/
|
|
62
|
+
export declare function ensureHeroMount(content: HTMLElement): HTMLElement;
|
|
63
|
+
/**
|
|
64
|
+
* Remove the mount this module created, if it is still there.
|
|
65
|
+
*
|
|
66
|
+
* Only the mount is removed — never the host div, which belongs to the shell
|
|
67
|
+
* and is reused on the next New Session.
|
|
68
|
+
*/
|
|
69
|
+
export declare function removeHeroMount(content: HTMLElement): void;
|
|
70
|
+
//# sourceMappingURL=hero-content.d.ts.map
|
|
@@ -44,6 +44,8 @@ export interface Suggestion {
|
|
|
44
44
|
readonly id: string;
|
|
45
45
|
/** Icon key resolved by {@link SuggestionIconArt}. */
|
|
46
46
|
readonly icon: SuggestionIcon;
|
|
47
|
+
/** The tile colour, measured to work in both themes. See {@link SuggestionHue}. */
|
|
48
|
+
readonly hue: SuggestionHue;
|
|
47
49
|
/** Dictionary key for the card's one-line title. */
|
|
48
50
|
readonly titleKey: AishellBrandLocaleKey;
|
|
49
51
|
/** Dictionary key for the line under the title. */
|
|
@@ -53,6 +55,58 @@ export interface Suggestion {
|
|
|
53
55
|
}
|
|
54
56
|
/** The icon set this strip can draw. Keys are local to this plugin. */
|
|
55
57
|
export type SuggestionIcon = 'cloud' | 'deploy' | 'architecture' | 'cost';
|
|
58
|
+
/**
|
|
59
|
+
* The tile colour for one card, as the shell's own token names.
|
|
60
|
+
*
|
|
61
|
+
* ## Why each card carries its own hue
|
|
62
|
+
*
|
|
63
|
+
* The first version drew every icon in one accent, on the reasoning that four
|
|
64
|
+
* hues beside the brand mark would read as noise. That was a guess, and the
|
|
65
|
+
* shell contradicts it: `FileTypeIcon` gives every item in a row its own hue
|
|
66
|
+
* (`code` blue, `excel` green, `folder` amber, `image` violet), which is the
|
|
67
|
+
* same situation — a row of small glyphs read at a glance — and it chooses
|
|
68
|
+
* per-item colour. Colour is what makes such a row scannable rather than a
|
|
69
|
+
* wall of identical tiles.
|
|
70
|
+
*
|
|
71
|
+
* ## These are STATIC tokens, not the semantic ones
|
|
72
|
+
*
|
|
73
|
+
* `--dsw-alias-state-success-primary` and friends mean "the operation
|
|
74
|
+
* succeeded" / "this needs attention". A card about cost is not a warning, so
|
|
75
|
+
* borrowing the warning colour would be a lie the palette tells. The `static`
|
|
76
|
+
* family is the one intended for decorative identity, which is exactly what a
|
|
77
|
+
* card's hue is — and `FileTypeIcon` reaches for the same family.
|
|
78
|
+
*
|
|
79
|
+
* ## The values were chosen by measurement, in both themes
|
|
80
|
+
*
|
|
81
|
+
* Violet is the exception: the design platform ships NO violet token, so
|
|
82
|
+
* `FileTypeIcon` defines one locally (`rgb(139, 118, 246)`) for its own
|
|
83
|
+
* artwork. The same value is reused here rather than inventing a second one,
|
|
84
|
+
* and it is the only literal colour in this plugin.
|
|
85
|
+
*
|
|
86
|
+
* The four were picked to clear three bars at once, in both themes:
|
|
87
|
+
*
|
|
88
|
+
* - glyph on its tile >= 4.5 — measured 5.42 at worst (violet);
|
|
89
|
+
* - tile against the card >= 1.6 — measured 2.05 at worst;
|
|
90
|
+
* - worst pairwise colour distance >= 30 (CIE76 dE) — measured 38.7.
|
|
91
|
+
*
|
|
92
|
+
* The last bar is the one that matters for "four colours, not one blur": an
|
|
93
|
+
* obvious-looking pick like blue + violet measures only 17.5 apart, which is
|
|
94
|
+
* why this is measured rather than eyeballed.
|
|
95
|
+
*/
|
|
96
|
+
export interface SuggestionHue {
|
|
97
|
+
/**
|
|
98
|
+
* The tile fill. A SOLID hue, with the glyph in `--dsw-alias-label-primary-
|
|
99
|
+
* foreground` on top.
|
|
100
|
+
*
|
|
101
|
+
* A tint of the hue was the first attempt and it measured badly: a glyph
|
|
102
|
+
* drawn in a hue on top of a tint of that same hue has almost nothing to
|
|
103
|
+
* separate them, so the contrast was 1.8–2.8 — and RAISING the tint made it
|
|
104
|
+
* worse, since more of the hue behind the glyph means less difference from
|
|
105
|
+
* it. A solid tile puts the glyph on full-strength colour instead, which
|
|
106
|
+
* measured 5.4–9.9 for the same hues.
|
|
107
|
+
*/
|
|
108
|
+
readonly tile: string;
|
|
109
|
+
}
|
|
56
110
|
/**
|
|
57
111
|
* The cards, in display order.
|
|
58
112
|
*
|
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.
|
|
4
|
+
"version": "0.0.7",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -51,7 +51,9 @@
|
|
|
51
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
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",
|
|
53
53
|
"@types/react": "~18.3.1",
|
|
54
|
+
"@types/react-dom": "~18.3.1",
|
|
54
55
|
"react": "^18.2.0",
|
|
56
|
+
"react-dom": "^18.2.0",
|
|
55
57
|
"tsdown": "^0.10.0",
|
|
56
58
|
"typescript": "^5.7.0"
|
|
57
59
|
},
|