@huaweidevtools/aishell-dsh-brand 0.0.4 → 0.0.6
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 +998 -2
- package/lib/types/client/SuggestionCards.d.ts +94 -0
- package/lib/types/client/autoplay.d.ts +67 -0
- package/lib/types/client/carousel.d.ts +96 -0
- package/lib/types/client/hero-content.d.ts +70 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +66 -0
- package/lib/types/client/suggestion-icons.d.ts +45 -0
- package/lib/types/client/suggestions.d.ts +118 -0
- package/package.json +10 -6
package/lib/client.js
CHANGED
|
@@ -25,6 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
//#endregion
|
|
27
27
|
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
28
|
+
const react = __toESM(require("react"));
|
|
29
|
+
const react_dom = __toESM(require("react-dom"));
|
|
28
30
|
|
|
29
31
|
//#region \0dsh-css:/home/eushing/opensource/aishell-dsh-plugins/packages/aishell-brand/src/client/brand.module.css.mjs
|
|
30
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}";
|
|
@@ -263,6 +265,990 @@ function installPoweredBy(target) {
|
|
|
263
265
|
};
|
|
264
266
|
}
|
|
265
267
|
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/client/suggestions.ts
|
|
270
|
+
/**
|
|
271
|
+
* The cards, in display order.
|
|
272
|
+
*
|
|
273
|
+
* Order matters only for the ring's starting window: the strip opens on the
|
|
274
|
+
* first {@link VISIBLE} cards, so the first entries are the ones a person
|
|
275
|
+
* sees without pressing an arrow.
|
|
276
|
+
*/
|
|
277
|
+
const SUGGESTIONS = [
|
|
278
|
+
{
|
|
279
|
+
id: "inventory",
|
|
280
|
+
icon: "cloud",
|
|
281
|
+
hue: { tile: "var(--dsw-static-blue-400)" },
|
|
282
|
+
titleKey: "suggestion.inventory.title",
|
|
283
|
+
glossKey: "suggestion.inventory.gloss",
|
|
284
|
+
promptKey: "suggestion.inventory.prompt"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "deploy",
|
|
288
|
+
icon: "deploy",
|
|
289
|
+
hue: { tile: "var(--dsw-static-green-500)" },
|
|
290
|
+
titleKey: "suggestion.deploy.title",
|
|
291
|
+
glossKey: "suggestion.deploy.gloss",
|
|
292
|
+
promptKey: "suggestion.deploy.prompt"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "enterprise",
|
|
296
|
+
icon: "architecture",
|
|
297
|
+
hue: { tile: "rgb(139, 118, 246)" },
|
|
298
|
+
titleKey: "suggestion.enterprise.title",
|
|
299
|
+
glossKey: "suggestion.enterprise.gloss",
|
|
300
|
+
promptKey: "suggestion.enterprise.prompt"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "billing",
|
|
304
|
+
icon: "cost",
|
|
305
|
+
hue: { tile: "var(--dsw-static-amber-600)" },
|
|
306
|
+
titleKey: "suggestion.billing.title",
|
|
307
|
+
glossKey: "suggestion.billing.gloss",
|
|
308
|
+
promptKey: "suggestion.billing.prompt"
|
|
309
|
+
}
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/client/suggestion-icons.tsx
|
|
314
|
+
/** One stroked path with the shared paint attributes. */
|
|
315
|
+
function Stroke({ d }) {
|
|
316
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
317
|
+
d,
|
|
318
|
+
fill: "none",
|
|
319
|
+
stroke: "currentColor",
|
|
320
|
+
strokeWidth: "1.7",
|
|
321
|
+
strokeLinecap: "round",
|
|
322
|
+
strokeLinejoin: "round"
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* A stack of servers: the resource inventory.
|
|
327
|
+
*
|
|
328
|
+
* Three stacked slabs with a status dot each — unmistakably "machines you
|
|
329
|
+
* own", and it shares no silhouette with any other glyph here.
|
|
330
|
+
*/
|
|
331
|
+
function InventoryIcon() {
|
|
332
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
333
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M4 5.5h16v4H4z" }),
|
|
334
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M4 14.5h16v4H4z" }),
|
|
335
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M7 7.5h.01" }),
|
|
336
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M7 16.5h.01" })
|
|
337
|
+
] });
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* A rocket: deploying something so it runs.
|
|
341
|
+
*
|
|
342
|
+
* Chosen over any box-with-arrow because that family is exactly the share
|
|
343
|
+
* glyph. A rocket is a single closed silhouette — nothing in this set can
|
|
344
|
+
* be confused with it.
|
|
345
|
+
*/
|
|
346
|
+
function DeployIcon() {
|
|
347
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
348
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 3c2.8 1.9 4.2 4.6 4.2 7.6 0 2.3-.9 4.3-2.1 5.6H9.9C8.7 14.9 7.8 12.9 7.8 10.6 7.8 7.6 9.2 4.9 12 3Z" }),
|
|
349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.9 16.2 8.4 19.4l2.3-1M14.1 16.2l1.5 3.2-2.3-1" }),
|
|
350
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 9.4a1.3 1.3 0 1 0 0 2.6 1.3 1.3 0 0 0 0-2.6Z" })
|
|
351
|
+
] });
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* A layered sitemap: account hierarchy and network partitions.
|
|
355
|
+
*
|
|
356
|
+
* One node branching to three, drawn as a single tree — reads as "structure"
|
|
357
|
+
* rather than "file" or "folder", which a plain box-and-boxes would.
|
|
358
|
+
*/
|
|
359
|
+
function ArchitectureIcon() {
|
|
360
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 3.5h5v3.4h-5z" }),
|
|
362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M3.5 17.1h5v3.4h-5z" }),
|
|
363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 17.1h5v3.4h-5z" }),
|
|
364
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M15.5 17.1h5v3.4h-5z" }),
|
|
365
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M12 6.9v4.2" }),
|
|
366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M6 17.1v-2.4h12v2.4" })
|
|
367
|
+
] });
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* A receipt with a downward trend: cost review.
|
|
371
|
+
*
|
|
372
|
+
* The torn bottom edge is what makes it a bill rather than a document —
|
|
373
|
+
* without it the shape is a generic page and reads as "export a report".
|
|
374
|
+
*/
|
|
375
|
+
function CostIcon() {
|
|
376
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
377
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M6 3.5h12v17l-2-1.4-2 1.4-2-1.4-2 1.4-2-1.4-2 1.4z" }),
|
|
378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 8.4h5" }),
|
|
379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stroke, { d: "M9.5 12.2h3.2" })
|
|
380
|
+
] });
|
|
381
|
+
}
|
|
382
|
+
/** Resolve one icon key to its glyph. */
|
|
383
|
+
function SuggestionIconArt({ icon, size = 20 }) {
|
|
384
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
385
|
+
"aria-hidden": true,
|
|
386
|
+
width: size,
|
|
387
|
+
height: size,
|
|
388
|
+
viewBox: "0 0 24 24",
|
|
389
|
+
style: { flex: "none" },
|
|
390
|
+
children: [
|
|
391
|
+
icon === "cloud" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InventoryIcon, {}),
|
|
392
|
+
icon === "deploy" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeployIcon, {}),
|
|
393
|
+
icon === "architecture" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArchitectureIcon, {}),
|
|
394
|
+
icon === "cost" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CostIcon, {})
|
|
395
|
+
]
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/client/carousel.ts
|
|
401
|
+
/**
|
|
402
|
+
* Carousel position maths, kept pure so it can be tested without a DOM.
|
|
403
|
+
*
|
|
404
|
+
* The strip is a RING, not a scroll range: the cards form a circle, and an
|
|
405
|
+
* arrow steps one card around it forever. Clicking next from the last
|
|
406
|
+
* position lands on the first, and clicking prev from the first lands on the
|
|
407
|
+
* last — and crucially, every intermediate position exists. A four-card ring
|
|
408
|
+
* showing three at a time has FOUR positions, not two:
|
|
409
|
+
*
|
|
410
|
+
* cards 1 2 3 4, three visible
|
|
411
|
+
* pos 0 → [1 2 3]
|
|
412
|
+
* pos 1 → [2 3 4]
|
|
413
|
+
* pos 2 → [3 4 1] ← the wrap is visible, not a jump back
|
|
414
|
+
* pos 3 → [4 1 2]
|
|
415
|
+
* pos 4 → [1 2 3] same as pos 0
|
|
416
|
+
*
|
|
417
|
+
* A plain scroll container cannot express that: its range is only
|
|
418
|
+
* `content - viewport` (one step, for four cards), and positions 2 and 3 have
|
|
419
|
+
* no place to live. So the card list is rendered {@link COPIES} times over
|
|
420
|
+
* and the window travels through the middle copy, where both neighbours
|
|
421
|
+
* always have content.
|
|
422
|
+
*
|
|
423
|
+
* ## Why the copies, and how the seam stays invisible
|
|
424
|
+
*
|
|
425
|
+
* With the list rendered three times, a window at physical position `p` shows
|
|
426
|
+
* `cards[p .. p+VISIBLE-1]`, and position `2n` shows exactly what position `n`
|
|
427
|
+
* shows. That equivalence is the seam: once a slide settles at `2n`, the
|
|
428
|
+
* track can be placed at `n` in the same frame with transitions off, and
|
|
429
|
+
* nothing on screen changes. The same holds at the other end, where `n-1` and
|
|
430
|
+
* `2n-1` are the same window.
|
|
431
|
+
*
|
|
432
|
+
* The rewind happens only AFTER the slide finishes — never before. Jumping
|
|
433
|
+
* first would put the destination on screen instantly and then animate away
|
|
434
|
+
* from it, so a click at the seam would appear to skip a card. This is the
|
|
435
|
+
* one law in this module that a reader is likely to get backwards.
|
|
436
|
+
*
|
|
437
|
+
* @module @huaweidevtools/aishell-dsh-brand/client/carousel
|
|
438
|
+
*/
|
|
439
|
+
/**
|
|
440
|
+
* How many times the card list is rendered.
|
|
441
|
+
*
|
|
442
|
+
* Three is the minimum that works. The window lives in the middle copy, so it
|
|
443
|
+
* needs one full copy behind it (to show the cards before the start) and one
|
|
444
|
+
* ahead (to show the cards past the end while a slide is finishing at the
|
|
445
|
+
* seam, where the position may briefly reach `2n` and read up to
|
|
446
|
+
* `2n + VISIBLE - 1`).
|
|
447
|
+
*/
|
|
448
|
+
const COPIES = 3;
|
|
449
|
+
/** How many cards to render for a ring of `n`. */
|
|
450
|
+
function ringLength(n) {
|
|
451
|
+
return n * COPIES;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The physical position the ring starts at — the first card of the middle
|
|
455
|
+
* copy. Starting here rather than at 0 is what makes the very first prev
|
|
456
|
+
* click land on a real card instead of an empty track.
|
|
457
|
+
*/
|
|
458
|
+
function ringStart(n) {
|
|
459
|
+
return n;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Where a settled position should be silently rewound to, or `null` when it
|
|
463
|
+
* is already inside the working band.
|
|
464
|
+
*
|
|
465
|
+
* The band is `[n, 2n-1]`. A slide may finish one past either end — `2n`
|
|
466
|
+
* going forward, `n-1` going backward — and both have an exact twin inside
|
|
467
|
+
* the band. See the module note for why this runs after the slide, not
|
|
468
|
+
* before.
|
|
469
|
+
*/
|
|
470
|
+
function rewindTo(pos, n) {
|
|
471
|
+
if (pos >= 2 * n) return n;
|
|
472
|
+
if (pos < n) return 2 * n - 1;
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Resolve one arrow press on a ring.
|
|
477
|
+
*
|
|
478
|
+
* Any rewind still owed from the previous press is settled first, so a person
|
|
479
|
+
* clicking faster than the slide animation cannot walk the window off the
|
|
480
|
+
* rendered copies — the band always absorbs one more step.
|
|
481
|
+
*/
|
|
482
|
+
function ringStep(pos, delta, n) {
|
|
483
|
+
const owed = rewindTo(pos, n);
|
|
484
|
+
const from = owed ?? pos;
|
|
485
|
+
return {
|
|
486
|
+
from,
|
|
487
|
+
to: from + delta
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/client/autoplay.ts
|
|
493
|
+
/**
|
|
494
|
+
* Auto-rotation policy for the example-prompt strip.
|
|
495
|
+
*
|
|
496
|
+
* Kept apart from the component and free of DOM, for the same reason
|
|
497
|
+
* `carousel.ts` is: the DECISION is what needs pinning down, and a decision
|
|
498
|
+
* buried in an effect can only be checked by watching a browser.
|
|
499
|
+
*
|
|
500
|
+
* ## Why the strip advances on its own
|
|
501
|
+
*
|
|
502
|
+
* The strip shows three of four cards, so a card is always off screen and a
|
|
503
|
+
* reader who never touches the arrows never learns it exists. Rotation is what
|
|
504
|
+
* makes the set self-evident. The cost is that content moves without being
|
|
505
|
+
* asked to, which is why every reason to stop is collected here rather than
|
|
506
|
+
* decided piecemeal at the call site.
|
|
507
|
+
*
|
|
508
|
+
* ## Every reason to stop
|
|
509
|
+
*
|
|
510
|
+
* - `engaged` — the pointer is over the strip or focus is inside it. Someone
|
|
511
|
+
* reaching for a card must not have it slide out from under the cursor;
|
|
512
|
+
* this is the pause the feature was asked for, and `focus` is the half of
|
|
513
|
+
* it that covers a reader who never uses a pointer.
|
|
514
|
+
* - `reducedMotion` — the reader asked the platform for less movement. An
|
|
515
|
+
* element that moves by itself for more than five seconds is exactly what
|
|
516
|
+
* `prefers-reduced-motion` exists to suppress, and a hover pause cannot help
|
|
517
|
+
* the reader who cannot hover. Honouring it is what keeps the feature from
|
|
518
|
+
* being motion no one consented to.
|
|
519
|
+
* - `visible` — the tab is hidden. A background tab still fires timers, so
|
|
520
|
+
* without this the strip would churn through slides nobody can see and land
|
|
521
|
+
* somewhere arbitrary on return.
|
|
522
|
+
* - `ready` — the ring has not been measured yet. Advancing before the step
|
|
523
|
+
* is known would move the track by a wrong distance; see the measurement
|
|
524
|
+
* note in `SuggestionCards.tsx`.
|
|
525
|
+
* - `ring` — three cards or fewer all fit, so there is nowhere to advance to.
|
|
526
|
+
* A strip with nothing to scroll must not appear to scroll.
|
|
527
|
+
*
|
|
528
|
+
* @module @huaweidevtools/aishell-dsh-brand/client/autoplay
|
|
529
|
+
*/
|
|
530
|
+
/**
|
|
531
|
+
* How long each card is shown before the strip advances.
|
|
532
|
+
*
|
|
533
|
+
* Fixed rather than configurable: this is a hint at what the product can do,
|
|
534
|
+
* not a reading surface, and a setting nobody asked for is a setting nobody
|
|
535
|
+
* finds.
|
|
536
|
+
*/
|
|
537
|
+
const AUTOPLAY_INTERVAL_MS = 1500;
|
|
538
|
+
/**
|
|
539
|
+
* Whether the strip should be advancing on its own right now.
|
|
540
|
+
*
|
|
541
|
+
* Expressed as one predicate rather than a chain of early returns inside the
|
|
542
|
+
* timer, so that adding a reason to stop is a change to this list and its
|
|
543
|
+
* test — not a change to the effect that schedules the ticks.
|
|
544
|
+
*/
|
|
545
|
+
function autoplayActive(inputs) {
|
|
546
|
+
return inputs.ring && inputs.ready && !inputs.engaged && !inputs.reducedMotion && inputs.visible;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/client/hero-content.ts
|
|
551
|
+
/**
|
|
552
|
+
* The mount point the suggestion strip renders into, inside the hero.
|
|
553
|
+
*
|
|
554
|
+
* ## Why a portal, and not CSS placement
|
|
555
|
+
*
|
|
556
|
+
* The strip is a `conversation.input.dock` entry, and that band renders BELOW
|
|
557
|
+
* the hero workspace row in the composer stack. Measured on the live page
|
|
558
|
+
* (1280×900, DSH 0.2.x):
|
|
559
|
+
*
|
|
560
|
+
* hero (headline) y=288 h=74
|
|
561
|
+
* hero workspace row y=374 h=28
|
|
562
|
+
* conversation.input.dock y=411 h=80
|
|
563
|
+
* the input card y=491
|
|
564
|
+
*
|
|
565
|
+
* The product wants the cards directly under the headline, and the workspace
|
|
566
|
+
* row sits between. A negative margin cannot cross it — the row would be
|
|
567
|
+
* covered — and the row's own subtree is not reachable from the dock, since
|
|
568
|
+
* the two are separate children of the composer stack.
|
|
569
|
+
*
|
|
570
|
+
* The hero, however, already renders an empty content div for exactly this
|
|
571
|
+
* purpose. Its stack is: headline → (the "Powered by" label) → `_body`, where
|
|
572
|
+
* `_body` is `flex-direction: column` and left empty by the official shell.
|
|
573
|
+
* That is the hero's content area, and putting the cards there is using the
|
|
574
|
+
* slot as intended rather than fighting the layout.
|
|
575
|
+
*
|
|
576
|
+
* ## How the mount is found
|
|
577
|
+
*
|
|
578
|
+
* By the hero's own class suffix, from the headline outward — `[class*="_body"]`
|
|
579
|
+
* alone is ambiguous (the conversation root has a `_body` too, and the live
|
|
580
|
+
* page has two). So the search starts at a `[class*="_headline"]`, steps to its
|
|
581
|
+
* parent (the hero stack), and takes that stack's own `_body` child. The
|
|
582
|
+
* headline is unique on the page and is what makes the walk unambiguous.
|
|
583
|
+
*
|
|
584
|
+
* Children are walked directly rather than with `:scope >`. The two are
|
|
585
|
+
* equivalent here, and the direct walk keeps these helpers working against the
|
|
586
|
+
* hand-rolled DOM stub the specs use (which has no selector engine) instead of
|
|
587
|
+
* leaving the module's real logic untested.
|
|
588
|
+
*
|
|
589
|
+
* The classes are CSS-module hashes (`pXSMma_body`), hence the suffix match.
|
|
590
|
+
* A base bump that renames them fails closed: {@link findHeroContent} returns
|
|
591
|
+
* null, the portal renders nothing, and the arrows and cards simply do not
|
|
592
|
+
* appear — the strip is never left in the wrong parent.
|
|
593
|
+
*
|
|
594
|
+
* @module @huaweidevtools/aishell-dsh-brand/client/hero-content
|
|
595
|
+
*/
|
|
596
|
+
/** The first direct child whose class contains `fragment`. */
|
|
597
|
+
function childWithClass(parent, fragment) {
|
|
598
|
+
for (const child of Array.from(parent.children)) if (child.className.includes(fragment)) return child;
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
/** The first direct child carrying `data-<name>`. */
|
|
602
|
+
function childWithData(parent, attribute) {
|
|
603
|
+
for (const child of Array.from(parent.children)) if (child.hasAttribute(attribute)) return child;
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
/** The child marker, so a disposer can find exactly the node it made. */
|
|
607
|
+
const MOUNT_ATTRIBUTE = "data-aishell-suggestions-mount";
|
|
608
|
+
/**
|
|
609
|
+
* The hero's content area, or `null` when the hero is not on screen.
|
|
610
|
+
*
|
|
611
|
+
* Returns null rather than throwing: a New Session that has not painted its
|
|
612
|
+
* hero yet is a normal state (and the strip retries on the next frame), not an
|
|
613
|
+
* error.
|
|
614
|
+
*/
|
|
615
|
+
function findHeroContent(target) {
|
|
616
|
+
if (typeof target.querySelector !== "function") return null;
|
|
617
|
+
const headline = target.querySelector("[class*=\"_headline\"]");
|
|
618
|
+
const stack = headline?.parentElement ?? null;
|
|
619
|
+
if (stack === null) return null;
|
|
620
|
+
return childWithClass(stack, "_body");
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* A container for the strip inside the hero content area.
|
|
624
|
+
*
|
|
625
|
+
* The hero body is `display: block` and zero-height, so the strip cannot rely
|
|
626
|
+
* on it for layout and must not inherit that block context. The wrapper is
|
|
627
|
+
* created here rather than rendered by React into the official div, because
|
|
628
|
+
* React does not own that node — it owns only what the portal puts inside.
|
|
629
|
+
*/
|
|
630
|
+
function ensureHeroMount(content) {
|
|
631
|
+
const existing = childWithData(content, MOUNT_ATTRIBUTE);
|
|
632
|
+
if (existing !== null) return existing;
|
|
633
|
+
const mount = content.ownerDocument.createElement("div");
|
|
634
|
+
mount.setAttribute(MOUNT_ATTRIBUTE, "");
|
|
635
|
+
content.appendChild(mount);
|
|
636
|
+
return mount;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Remove the mount this module created, if it is still there.
|
|
640
|
+
*
|
|
641
|
+
* Only the mount is removed — never the host div, which belongs to the shell
|
|
642
|
+
* and is reused on the next New Session.
|
|
643
|
+
*/
|
|
644
|
+
function removeHeroMount(content) {
|
|
645
|
+
childWithData(content, MOUNT_ATTRIBUTE)?.remove();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region src/client/SuggestionCards.tsx
|
|
650
|
+
/** How many cards a full-width strip shows at once. */
|
|
651
|
+
const VISIBLE = 3;
|
|
652
|
+
/**
|
|
653
|
+
* How much of a card's hue is washed into its own background, in percent.
|
|
654
|
+
*
|
|
655
|
+
* The card's ground is the shell's translucent interactive pair (neutral), and
|
|
656
|
+
* this mixes the card's hue into BOTH ends of that pair so a card carries a
|
|
657
|
+
* trace of its tile's colour without the hover turning into a colour change.
|
|
658
|
+
*
|
|
659
|
+
* 6 is measured rather than picked. Three things constrain it, and the numbers
|
|
660
|
+
* below are computed from the real tokens in both themes:
|
|
661
|
+
*
|
|
662
|
+
* - LEGIBILITY. The gloss text is the binding constraint, not the title. On
|
|
663
|
+
* the light theme's ground the gloss measures 3.33 at 0%, 3.14 at 8%, and
|
|
664
|
+
* falls to 2.90 at 14% — under the 3:1 bar. So the ceiling is ~12%.
|
|
665
|
+
* - VISIBILITY. A tint is not worth its cost below the just-noticeable
|
|
666
|
+
* difference (~2.3 dE). The card moves 3.4–5.1 dE away from its own
|
|
667
|
+
* untinted self at 6%, and only 1.7 dE at 3%, so the floor is ~4%.
|
|
668
|
+
* - THE TILE'S SEPARATION. The tile sits ON this ground, so tinting the
|
|
669
|
+
* ground eats the tile's contrast. Amber is the tightest case: 2.51 at 0%,
|
|
670
|
+
* 2.39 at 6%, 2.31 at 10%. It survives, but this is why the percentage
|
|
671
|
+
* cannot be pushed for looks.
|
|
672
|
+
*
|
|
673
|
+
* 6% is the low end of the safe band, chosen deliberately: the tint is there to
|
|
674
|
+
* warm the card toward its subject, not to become a colour block. It is not a
|
|
675
|
+
* way to tell the cards apart — at 6% two different cards are only 2.4 dE
|
|
676
|
+
* apart, barely at the threshold. The tile is what identifies a card.
|
|
677
|
+
*/
|
|
678
|
+
const HUE_WASH = 6;
|
|
679
|
+
/**
|
|
680
|
+
* The hero's content element, kept current across the hero's lifecycle.
|
|
681
|
+
*
|
|
682
|
+
* The hero mounts, unmounts and is REPLACED on each New Session, so the node
|
|
683
|
+
* found at first paint goes stale. A `MutationObserver` is the only thing that
|
|
684
|
+
* sees a replacement: the observer re-scans, and the scan is cheap because it
|
|
685
|
+
* returns immediately while the current mount is still connected.
|
|
686
|
+
*
|
|
687
|
+
* @param active - whether the strip should be mounted at all.
|
|
688
|
+
* @returns the element to portal into, or `null` while the hero is absent.
|
|
689
|
+
*/
|
|
690
|
+
function useHeroMount(active) {
|
|
691
|
+
const [mount, setMount] = (0, react.useState)(null);
|
|
692
|
+
const mountRef = (0, react.useRef)(null);
|
|
693
|
+
(0, react.useEffect)(() => {
|
|
694
|
+
if (!active) {
|
|
695
|
+
setMount(null);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
let cancelled = false;
|
|
699
|
+
const scan = () => {
|
|
700
|
+
if (cancelled) return;
|
|
701
|
+
const current = mountRef.current;
|
|
702
|
+
if (current !== null && current.isConnected) {
|
|
703
|
+
setMount((seen) => seen === current ? seen : current);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const content = findHeroContent(document);
|
|
707
|
+
if (content === null) {
|
|
708
|
+
mountRef.current = null;
|
|
709
|
+
setMount(null);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const next = ensureHeroMount(content);
|
|
713
|
+
if (next !== mountRef.current) {
|
|
714
|
+
mountRef.current = next;
|
|
715
|
+
setMount(next);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
scan();
|
|
719
|
+
const observer = new MutationObserver(scan);
|
|
720
|
+
observer.observe(document.body, {
|
|
721
|
+
childList: true,
|
|
722
|
+
subtree: true
|
|
723
|
+
});
|
|
724
|
+
return () => {
|
|
725
|
+
cancelled = true;
|
|
726
|
+
observer.disconnect();
|
|
727
|
+
};
|
|
728
|
+
}, [active]);
|
|
729
|
+
(0, react.useEffect)(() => () => {
|
|
730
|
+
const current = mountRef.current;
|
|
731
|
+
mountRef.current = null;
|
|
732
|
+
if (current === null) return;
|
|
733
|
+
removeHeroMount(current.parentElement ?? current);
|
|
734
|
+
}, []);
|
|
735
|
+
return mount;
|
|
736
|
+
}
|
|
737
|
+
/** Whether the reader has asked the platform for reduced motion. */
|
|
738
|
+
function useReducedMotion() {
|
|
739
|
+
const [reduced, setReduced] = (0, react.useState)(false);
|
|
740
|
+
(0, react.useEffect)(() => {
|
|
741
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
742
|
+
setReduced(query.matches);
|
|
743
|
+
const onChange = (event) => {
|
|
744
|
+
setReduced(event.matches);
|
|
745
|
+
};
|
|
746
|
+
query.addEventListener("change", onChange);
|
|
747
|
+
return () => {
|
|
748
|
+
query.removeEventListener("change", onChange);
|
|
749
|
+
};
|
|
750
|
+
}, []);
|
|
751
|
+
return reduced;
|
|
752
|
+
}
|
|
753
|
+
/** Whether the tab is on screen. A hidden tab must not churn through slides. */
|
|
754
|
+
function useTabVisible() {
|
|
755
|
+
const [visible, setVisible] = (0, react.useState)(true);
|
|
756
|
+
(0, react.useEffect)(() => {
|
|
757
|
+
const onVisibility = () => {
|
|
758
|
+
setVisible(document.visibilityState === "visible");
|
|
759
|
+
};
|
|
760
|
+
onVisibility();
|
|
761
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
762
|
+
return () => {
|
|
763
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
764
|
+
};
|
|
765
|
+
}, []);
|
|
766
|
+
return visible;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* One card.
|
|
770
|
+
*
|
|
771
|
+
* A button, so keyboard activation, focus handling and disabled semantics come
|
|
772
|
+
* for free. The icon is a decorative tile marked `aria-hidden`; the title and
|
|
773
|
+
* gloss carry the meaning.
|
|
774
|
+
*/
|
|
775
|
+
function Card({ suggestion, onPick, t }) {
|
|
776
|
+
const [hover, setHover] = (0, react.useState)(false);
|
|
777
|
+
const [focus, setFocus] = (0, react.useState)(false);
|
|
778
|
+
const lit = hover || focus;
|
|
779
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
780
|
+
type: "button",
|
|
781
|
+
onClick: () => {
|
|
782
|
+
onPick(t(suggestion.promptKey));
|
|
783
|
+
},
|
|
784
|
+
onPointerEnter: () => {
|
|
785
|
+
setHover(true);
|
|
786
|
+
},
|
|
787
|
+
onPointerLeave: () => {
|
|
788
|
+
setHover(false);
|
|
789
|
+
},
|
|
790
|
+
onFocus: () => {
|
|
791
|
+
setFocus(true);
|
|
792
|
+
},
|
|
793
|
+
onBlur: () => {
|
|
794
|
+
setFocus(false);
|
|
795
|
+
},
|
|
796
|
+
style: {
|
|
797
|
+
flex: `0 0 calc((100cqw - ${VISIBLE - 1} * var(--dsh-suggestion-gap)) / ${VISIBLE})`,
|
|
798
|
+
display: "flex",
|
|
799
|
+
alignItems: "flex-start",
|
|
800
|
+
gap: 12,
|
|
801
|
+
boxSizing: "border-box",
|
|
802
|
+
minWidth: 0,
|
|
803
|
+
padding: "14px",
|
|
804
|
+
textAlign: "left",
|
|
805
|
+
font: "inherit",
|
|
806
|
+
cursor: "pointer",
|
|
807
|
+
color: "var(--dsw-alias-label-primary)",
|
|
808
|
+
background: lit ? `color-mix(in srgb, ${suggestion.hue.tile} ${HUE_WASH}%, var(--dsw-alias-interactive-bg-active))` : `color-mix(in srgb, ${suggestion.hue.tile} ${HUE_WASH}%, var(--dsw-alias-interactive-bg-hover))`,
|
|
809
|
+
border: "none",
|
|
810
|
+
borderRadius: "var(--dsw-radius-md, 12px)",
|
|
811
|
+
transition: "background-color 120ms ease",
|
|
812
|
+
outline: focus ? "2px solid var(--dsw-alias-state-business-primary)" : "none",
|
|
813
|
+
outlineOffset: 2
|
|
814
|
+
},
|
|
815
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
816
|
+
"aria-hidden": true,
|
|
817
|
+
style: {
|
|
818
|
+
flex: "none",
|
|
819
|
+
display: "grid",
|
|
820
|
+
placeItems: "center",
|
|
821
|
+
width: 32,
|
|
822
|
+
height: 32,
|
|
823
|
+
marginTop: 1,
|
|
824
|
+
borderRadius: "var(--dsw-radius-sm, 8px)",
|
|
825
|
+
background: suggestion.hue.tile,
|
|
826
|
+
color: "var(--dsw-static-neutral-bluish-1000, #0f1115)"
|
|
827
|
+
},
|
|
828
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuggestionIconArt, {
|
|
829
|
+
icon: suggestion.icon,
|
|
830
|
+
size: 17
|
|
831
|
+
})
|
|
832
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
833
|
+
style: {
|
|
834
|
+
display: "flex",
|
|
835
|
+
flexDirection: "column",
|
|
836
|
+
gap: 3,
|
|
837
|
+
minWidth: 0
|
|
838
|
+
},
|
|
839
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
840
|
+
style: {
|
|
841
|
+
fontSize: "var(--dsh-content-font-size-secondary, 13px)",
|
|
842
|
+
fontWeight: 600,
|
|
843
|
+
lineHeight: "19px",
|
|
844
|
+
overflow: "hidden",
|
|
845
|
+
textOverflow: "ellipsis",
|
|
846
|
+
whiteSpace: "nowrap"
|
|
847
|
+
},
|
|
848
|
+
children: t(suggestion.titleKey)
|
|
849
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
850
|
+
style: {
|
|
851
|
+
fontSize: "12px",
|
|
852
|
+
lineHeight: "17px",
|
|
853
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
854
|
+
display: "-webkit-box",
|
|
855
|
+
WebkitLineClamp: 2,
|
|
856
|
+
WebkitBoxOrient: "vertical",
|
|
857
|
+
overflow: "hidden"
|
|
858
|
+
},
|
|
859
|
+
children: t(suggestion.glossKey)
|
|
860
|
+
})]
|
|
861
|
+
})]
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
/** One carousel arrow. */
|
|
865
|
+
function Arrow({ direction, label, onClick }) {
|
|
866
|
+
const [hover, setHover] = (0, react.useState)(false);
|
|
867
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
868
|
+
type: "button",
|
|
869
|
+
"aria-label": label,
|
|
870
|
+
onClick,
|
|
871
|
+
onPointerEnter: () => {
|
|
872
|
+
setHover(true);
|
|
873
|
+
},
|
|
874
|
+
onPointerLeave: () => {
|
|
875
|
+
setHover(false);
|
|
876
|
+
},
|
|
877
|
+
style: {
|
|
878
|
+
flex: "none",
|
|
879
|
+
display: "inline-flex",
|
|
880
|
+
alignItems: "center",
|
|
881
|
+
justifyContent: "center",
|
|
882
|
+
width: 26,
|
|
883
|
+
height: 26,
|
|
884
|
+
padding: 0,
|
|
885
|
+
cursor: "pointer",
|
|
886
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
887
|
+
background: hover ? "var(--dsw-alias-interactive-bg-hover)" : "transparent",
|
|
888
|
+
border: "none",
|
|
889
|
+
borderRadius: 999,
|
|
890
|
+
transition: "background 120ms ease"
|
|
891
|
+
},
|
|
892
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
893
|
+
"aria-hidden": true,
|
|
894
|
+
width: "14",
|
|
895
|
+
height: "14",
|
|
896
|
+
viewBox: "0 0 24 24",
|
|
897
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
898
|
+
d: direction === "prev" ? "M15 5l-7 7 7 7" : "M9 5l7 7-7 7",
|
|
899
|
+
fill: "none",
|
|
900
|
+
stroke: "currentColor",
|
|
901
|
+
strokeWidth: "2",
|
|
902
|
+
strokeLinecap: "round",
|
|
903
|
+
strokeLinejoin: "round"
|
|
904
|
+
})
|
|
905
|
+
})
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
function SuggestionCards(props) {
|
|
909
|
+
const blank = props.useSession((session) => session.blank);
|
|
910
|
+
const setDraft = props.inputActions.setDraft;
|
|
911
|
+
const t = props.t;
|
|
912
|
+
const n = SUGGESTIONS.length;
|
|
913
|
+
const ring = n > VISIBLE;
|
|
914
|
+
const viewportRef = (0, react.useRef)(null);
|
|
915
|
+
const trackRef = (0, react.useRef)(null);
|
|
916
|
+
const posRef = (0, react.useRef)(ringStart(n));
|
|
917
|
+
const stepRef = (0, react.useRef)(0);
|
|
918
|
+
const [ready, setReady] = (0, react.useState)(!ring);
|
|
919
|
+
const [engaged, setEngaged] = (0, react.useState)(false);
|
|
920
|
+
const reducedMotion = useReducedMotion();
|
|
921
|
+
const visible = useTabVisible();
|
|
922
|
+
const mount = useHeroMount(blank);
|
|
923
|
+
/**
|
|
924
|
+
* The slide's own animation, written by this component and nobody else.
|
|
925
|
+
*
|
|
926
|
+
* `transform` and `transition` are BOTH owned by the imperative code below
|
|
927
|
+
* and are deliberately absent from the `style` prop further down. They have
|
|
928
|
+
* to have exactly one owner: a value declared in JSX is only re-applied when
|
|
929
|
+
* React sees it CHANGE, so the first time the effect writes
|
|
930
|
+
* `track.style.transition = 'none'` (or clears it with `''`), React's
|
|
931
|
+
* declared value is gone and React has no reason to write it again. The
|
|
932
|
+
* track then keeps the browser default — `all 0s` — and every "slide" lands
|
|
933
|
+
* instantly with a hard cut. That is a silent failure: positions, wrapping
|
|
934
|
+
* and the ring all still measure correct, so only watching the motion
|
|
935
|
+
* reveals it.
|
|
936
|
+
*
|
|
937
|
+
* A slow, eased curve rather than a short linear one: the strip shows one
|
|
938
|
+
* card leaving and one arriving, and at 320ms with a snappy curve it reads
|
|
939
|
+
* as a jump cut rather than travel.
|
|
940
|
+
*
|
|
941
|
+
* The curve is `cubic-bezier(.33,0,.15,1)`, chosen for its END VELOCITIES
|
|
942
|
+
* and not its looks on a graph. Measured as distance-per-unit-time (1.0
|
|
943
|
+
* being a linear slide) it starts at 0.4, peaks near 2.7, and finishes at
|
|
944
|
+
* 0.05 — eased away from rest and brought gently to rest. The obvious
|
|
945
|
+
* "smooth" pick, `cubic-bezier(.16,.84,.28,1)` (a material-style fast-out),
|
|
946
|
+
* starts at 4.39: it covers 44% of the distance in the first 10% of the
|
|
947
|
+
* time and then crawls. On a row of cards that reads as a snap followed by
|
|
948
|
+
* a stall, which is the opposite of what a carousel wants.
|
|
949
|
+
*/
|
|
950
|
+
const SLIDE_MS = 520;
|
|
951
|
+
const SLIDE_EASING = "cubic-bezier(.33,0,.15,1)";
|
|
952
|
+
/** Place the track at a position with no transition. */
|
|
953
|
+
const snapTo = (0, react.useCallback)((pos) => {
|
|
954
|
+
const track = trackRef.current;
|
|
955
|
+
if (track === null) return;
|
|
956
|
+
track.style.transition = "none";
|
|
957
|
+
track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
|
|
958
|
+
}, []);
|
|
959
|
+
/** Animate the track to a position. */
|
|
960
|
+
const slideTo = (0, react.useCallback)((pos) => {
|
|
961
|
+
const track = trackRef.current;
|
|
962
|
+
if (track === null) return;
|
|
963
|
+
track.style.transition = `transform ${SLIDE_MS}ms ${SLIDE_EASING}`;
|
|
964
|
+
track.style.transform = `translate3d(${-pos * stepRef.current}px, 0, 0)`;
|
|
965
|
+
}, []);
|
|
966
|
+
const autoplayTimerRef = (0, react.useRef)(0);
|
|
967
|
+
const latestStepRef = (0, react.useRef)(() => {});
|
|
968
|
+
/**
|
|
969
|
+
* Schedule the next auto-rotation tick one full interval from now,
|
|
970
|
+
* replacing any tick already pending.
|
|
971
|
+
*
|
|
972
|
+
* Called by every `step`, which is what makes the cadence immune to a tick
|
|
973
|
+
* landing mid-slide.
|
|
974
|
+
*/
|
|
975
|
+
const bumpAutoplay = (0, react.useCallback)(() => {
|
|
976
|
+
window.clearTimeout(autoplayTimerRef.current);
|
|
977
|
+
autoplayTimerRef.current = window.setTimeout(() => {
|
|
978
|
+
latestStepRef.current(1);
|
|
979
|
+
}, AUTOPLAY_INTERVAL_MS);
|
|
980
|
+
}, []);
|
|
981
|
+
/**
|
|
982
|
+
* Re-measure one step and re-apply the current position.
|
|
983
|
+
*
|
|
984
|
+
* The step is read from a real card rather than computed from the viewport
|
|
985
|
+
* width so the `cqw` fraction in `Card`'s flex-basis and this number cannot
|
|
986
|
+
* drift apart.
|
|
987
|
+
*
|
|
988
|
+
* Re-measuring is not optional. The first frame measures BEFORE the cards
|
|
989
|
+
* have their final width: `containerType` and the `cqw` basis settle in a
|
|
990
|
+
* later layout pass, so the first reading is a few percent too large (242px
|
|
991
|
+
* where the settled card is 213px). A step kept from that frame puts every
|
|
992
|
+
* position slightly too far along, which compounds — by the fourth card the
|
|
993
|
+
* window has drifted a whole card and shows two cards plus a gap.
|
|
994
|
+
*/
|
|
995
|
+
const remeasure = (0, react.useCallback)(() => {
|
|
996
|
+
const track = trackRef.current;
|
|
997
|
+
if (track === null) return false;
|
|
998
|
+
const first = track.firstElementChild;
|
|
999
|
+
if (first === null) return false;
|
|
1000
|
+
const gap = Number.parseFloat(getComputedStyle(track).columnGap) || 0;
|
|
1001
|
+
const step$1 = first.getBoundingClientRect().width + gap;
|
|
1002
|
+
if (!(step$1 > 0)) return false;
|
|
1003
|
+
const changed = step$1 !== stepRef.current;
|
|
1004
|
+
stepRef.current = step$1;
|
|
1005
|
+
if (changed) snapTo(posRef.current);
|
|
1006
|
+
return true;
|
|
1007
|
+
}, [snapTo]);
|
|
1008
|
+
(0, react.useEffect)(() => {
|
|
1009
|
+
if (!blank || !ring || mount === null) return;
|
|
1010
|
+
let cancelled = false;
|
|
1011
|
+
const settle = () => {
|
|
1012
|
+
if (cancelled) return;
|
|
1013
|
+
if (!remeasure()) {
|
|
1014
|
+
frame = window.requestAnimationFrame(settle);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
posRef.current = ringStart(n);
|
|
1018
|
+
snapTo(posRef.current);
|
|
1019
|
+
window.requestAnimationFrame(() => {
|
|
1020
|
+
if (cancelled) return;
|
|
1021
|
+
setReady(true);
|
|
1022
|
+
const viewport = viewportRef.current;
|
|
1023
|
+
if (viewport !== null) {
|
|
1024
|
+
observer = new ResizeObserver(() => {
|
|
1025
|
+
remeasure();
|
|
1026
|
+
});
|
|
1027
|
+
observer.observe(viewport);
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
};
|
|
1031
|
+
let frame = window.requestAnimationFrame(settle);
|
|
1032
|
+
let observer;
|
|
1033
|
+
return () => {
|
|
1034
|
+
cancelled = true;
|
|
1035
|
+
window.cancelAnimationFrame(frame);
|
|
1036
|
+
observer?.disconnect();
|
|
1037
|
+
};
|
|
1038
|
+
}, [
|
|
1039
|
+
blank,
|
|
1040
|
+
ring,
|
|
1041
|
+
n,
|
|
1042
|
+
mount,
|
|
1043
|
+
remeasure,
|
|
1044
|
+
snapTo
|
|
1045
|
+
]);
|
|
1046
|
+
/**
|
|
1047
|
+
* Apply whichever rewind the settling position owes.
|
|
1048
|
+
*
|
|
1049
|
+
* Runs when the track reaches its destination, never before the slide:
|
|
1050
|
+
* jumping to the twin position first would put the destination on screen
|
|
1051
|
+
* immediately and then animate away from it, so a click at the seam would
|
|
1052
|
+
* look like it skipped a card. See the module note in carousel.ts.
|
|
1053
|
+
*
|
|
1054
|
+
* The rewind lands on a position showing exactly the cards already on
|
|
1055
|
+
* screen, so `snapTo`'s lack of animation is invisible.
|
|
1056
|
+
*
|
|
1057
|
+
* Only a COMPLETED transition reaches here: an interrupted one fires
|
|
1058
|
+
* `transitioncancel` instead, so there is no partial-position case to
|
|
1059
|
+
* filter out. A person clicking faster than a slide therefore skips this
|
|
1060
|
+
* handler entirely — the rewind they owe is settled by `ringStep` at the
|
|
1061
|
+
* start of their next press, which snaps the same invisible twin.
|
|
1062
|
+
*/
|
|
1063
|
+
const onSettle = (0, react.useCallback)((event) => {
|
|
1064
|
+
if (!ring) return;
|
|
1065
|
+
if (event.target !== trackRef.current) return;
|
|
1066
|
+
if (event.propertyName !== "transform") return;
|
|
1067
|
+
const owed = rewindTo(posRef.current, n);
|
|
1068
|
+
if (owed === null) return;
|
|
1069
|
+
posRef.current = owed;
|
|
1070
|
+
snapTo(owed);
|
|
1071
|
+
}, [
|
|
1072
|
+
ring,
|
|
1073
|
+
n,
|
|
1074
|
+
snapTo
|
|
1075
|
+
]);
|
|
1076
|
+
/** One step of the ring, from an arrow or from auto-rotation. */
|
|
1077
|
+
const step = (0, react.useCallback)((delta) => {
|
|
1078
|
+
if (!ring || !ready) return;
|
|
1079
|
+
bumpAutoplay();
|
|
1080
|
+
const { from, to } = ringStep(posRef.current, delta, n);
|
|
1081
|
+
if (from !== posRef.current) {
|
|
1082
|
+
posRef.current = from;
|
|
1083
|
+
snapTo(from);
|
|
1084
|
+
}
|
|
1085
|
+
posRef.current = to;
|
|
1086
|
+
slideTo(to);
|
|
1087
|
+
}, [
|
|
1088
|
+
ring,
|
|
1089
|
+
ready,
|
|
1090
|
+
n,
|
|
1091
|
+
snapTo,
|
|
1092
|
+
slideTo,
|
|
1093
|
+
bumpAutoplay
|
|
1094
|
+
]);
|
|
1095
|
+
(0, react.useEffect)(() => {
|
|
1096
|
+
latestStepRef.current = step;
|
|
1097
|
+
}, [step]);
|
|
1098
|
+
(0, react.useEffect)(() => {
|
|
1099
|
+
if (!autoplayActive({
|
|
1100
|
+
ring,
|
|
1101
|
+
ready,
|
|
1102
|
+
engaged,
|
|
1103
|
+
reducedMotion,
|
|
1104
|
+
visible
|
|
1105
|
+
})) return;
|
|
1106
|
+
bumpAutoplay();
|
|
1107
|
+
return () => {
|
|
1108
|
+
window.clearTimeout(autoplayTimerRef.current);
|
|
1109
|
+
};
|
|
1110
|
+
}, [
|
|
1111
|
+
ring,
|
|
1112
|
+
ready,
|
|
1113
|
+
engaged,
|
|
1114
|
+
reducedMotion,
|
|
1115
|
+
visible,
|
|
1116
|
+
bumpAutoplay
|
|
1117
|
+
]);
|
|
1118
|
+
if (!blank) return null;
|
|
1119
|
+
if (n === 0) return null;
|
|
1120
|
+
if (mount === null) return null;
|
|
1121
|
+
const cards = ring ? Array.from({ length: ringLength(n) }, (_, i) => SUGGESTIONS[i % n]) : SUGGESTIONS;
|
|
1122
|
+
const strip = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1123
|
+
ref: viewportRef,
|
|
1124
|
+
"data-aishell-suggestions": n,
|
|
1125
|
+
style: {
|
|
1126
|
+
overflow: "hidden",
|
|
1127
|
+
minWidth: 0,
|
|
1128
|
+
flex: "1 1 auto",
|
|
1129
|
+
containerType: "inline-size"
|
|
1130
|
+
},
|
|
1131
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1132
|
+
ref: trackRef,
|
|
1133
|
+
"data-aishell-suggestion-track": ring ? "ring" : "flat",
|
|
1134
|
+
onTransitionEnd: onSettle,
|
|
1135
|
+
style: {
|
|
1136
|
+
display: "flex",
|
|
1137
|
+
alignItems: "stretch",
|
|
1138
|
+
gap: "var(--dsh-suggestion-gap)",
|
|
1139
|
+
width: "max-content",
|
|
1140
|
+
willChange: "transform"
|
|
1141
|
+
},
|
|
1142
|
+
children: cards.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
1143
|
+
suggestion,
|
|
1144
|
+
onPick: setDraft,
|
|
1145
|
+
t
|
|
1146
|
+
}, `${i}:${suggestion.id}`))
|
|
1147
|
+
})
|
|
1148
|
+
});
|
|
1149
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1150
|
+
onPointerEnter: () => {
|
|
1151
|
+
setEngaged(true);
|
|
1152
|
+
},
|
|
1153
|
+
onPointerLeave: () => {
|
|
1154
|
+
setEngaged(false);
|
|
1155
|
+
},
|
|
1156
|
+
onFocus: () => {
|
|
1157
|
+
setEngaged(true);
|
|
1158
|
+
},
|
|
1159
|
+
onBlur: (event) => {
|
|
1160
|
+
const next = event.relatedTarget;
|
|
1161
|
+
if (next === null || !event.currentTarget.contains(next)) setEngaged(false);
|
|
1162
|
+
},
|
|
1163
|
+
style: {
|
|
1164
|
+
["--dsh-suggestion-gap"]: "10px",
|
|
1165
|
+
display: "flex",
|
|
1166
|
+
alignItems: "center",
|
|
1167
|
+
gap: 6,
|
|
1168
|
+
minWidth: 0,
|
|
1169
|
+
margin: "12px 0 22px"
|
|
1170
|
+
},
|
|
1171
|
+
children: [
|
|
1172
|
+
ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
|
|
1173
|
+
direction: "prev",
|
|
1174
|
+
label: t("suggestion.previous"),
|
|
1175
|
+
onClick: () => {
|
|
1176
|
+
step(-1);
|
|
1177
|
+
}
|
|
1178
|
+
}),
|
|
1179
|
+
strip,
|
|
1180
|
+
ring && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Arrow, {
|
|
1181
|
+
direction: "next",
|
|
1182
|
+
label: t("suggestion.next"),
|
|
1183
|
+
onClick: () => {
|
|
1184
|
+
step(1);
|
|
1185
|
+
}
|
|
1186
|
+
})
|
|
1187
|
+
]
|
|
1188
|
+
}), mount);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region src/client/locales.ts
|
|
1193
|
+
/**
|
|
1194
|
+
* aishell brand locale dictionaries.
|
|
1195
|
+
*
|
|
1196
|
+
* The brand plugin owns its own namespace rather than reusing `aishell`
|
|
1197
|
+
* (which `aishell-credentials` registers): a namespace's texts have exactly
|
|
1198
|
+
* one owner, and a duplicate `(ns, locale)` registration throws.
|
|
1199
|
+
*
|
|
1200
|
+
* ## Why content lives in a dictionary
|
|
1201
|
+
*
|
|
1202
|
+
* The suggestion cards are the only strings in this plugin a person reads,
|
|
1203
|
+
* and they are CONTENT rather than interface chrome — a card title, its
|
|
1204
|
+
* one-line gloss, and the prompt a click inserts. They still belong here:
|
|
1205
|
+
* a card that says「云资源查询」to an English reader is worse than no card,
|
|
1206
|
+
* and the prompt it inserts would start an English conversation in Chinese.
|
|
1207
|
+
*
|
|
1208
|
+
* Chinese is the key-set source of truth; English is complete against it and
|
|
1209
|
+
* is also the terminal fallback, so every locale other than `zh` reads
|
|
1210
|
+
* English. That is the framework's own chain (`LOCALE_IDS = ["zh", "en"]`,
|
|
1211
|
+
* with English carrying no fallback) — there is no third dictionary to write.
|
|
1212
|
+
*
|
|
1213
|
+
* @module @huaweidevtools/aishell-dsh-brand/client/locales
|
|
1214
|
+
*/
|
|
1215
|
+
/** Dictionary namespace owned by this plugin. */
|
|
1216
|
+
const NS = "aishell-brand";
|
|
1217
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
1218
|
+
const zh = {
|
|
1219
|
+
"suggestion.previous": "上一个示例",
|
|
1220
|
+
"suggestion.next": "下一个示例",
|
|
1221
|
+
"suggestion.inventory.title": "云资源查询",
|
|
1222
|
+
"suggestion.inventory.gloss": "列出当前账号下的云资源",
|
|
1223
|
+
"suggestion.inventory.prompt": "请列出我的华为云资源清单,按服务分类(计算、存储、网络、数据库等),标出每类资源的数量和所在区域。",
|
|
1224
|
+
"suggestion.deploy.title": "部署方案推荐",
|
|
1225
|
+
"suggestion.deploy.gloss": "按访问量和预算给出可落地的部署路径",
|
|
1226
|
+
"suggestion.deploy.prompt": "我要部署一个轻量级 Web 应用,请对比几种可行的部署方案,说明各自的适用规模、月度成本量级和运维复杂度,并给出你的推荐及理由。",
|
|
1227
|
+
"suggestion.enterprise.title": "企业上云",
|
|
1228
|
+
"suggestion.enterprise.gloss": "多账号、网络分区与权限边界的整体设计",
|
|
1229
|
+
"suggestion.enterprise.prompt": "请为我规划一套云上 Landing Zone 架构,覆盖账号组织结构、网络分区、权限边界与审计要求,并说明各部分的设计取舍。",
|
|
1230
|
+
"suggestion.billing.title": "账单分析",
|
|
1231
|
+
"suggestion.billing.gloss": "分析上个月的云资源支出",
|
|
1232
|
+
"suggestion.billing.prompt": "请分析我上个月的云资源支出构成,找出占比最高的几项服务,指出其中可以优化或降配的部分,并估算调整后能节省多少。"
|
|
1233
|
+
};
|
|
1234
|
+
/** English dictionary. Complete against {@link zh}; also the terminal fallback. */
|
|
1235
|
+
const en = {
|
|
1236
|
+
"suggestion.previous": "Previous example",
|
|
1237
|
+
"suggestion.next": "Next example",
|
|
1238
|
+
"suggestion.inventory.title": "Cloud inventory",
|
|
1239
|
+
"suggestion.inventory.gloss": "List the resources in this account",
|
|
1240
|
+
"suggestion.inventory.prompt": "List the resources in my Huawei Cloud account, grouped by service (compute, storage, network, database), with the count and the region for each group.",
|
|
1241
|
+
"suggestion.deploy.title": "Deployment options",
|
|
1242
|
+
"suggestion.deploy.gloss": "A workable path for your traffic and budget",
|
|
1243
|
+
"suggestion.deploy.prompt": "I want to deploy a lightweight web application. Compare a few viable deployment options, covering the scale each one suits, its order-of-magnitude monthly cost, and its operational complexity, then recommend one and explain why.",
|
|
1244
|
+
"suggestion.enterprise.title": "Landing zone",
|
|
1245
|
+
"suggestion.enterprise.gloss": "Multi-account structure, network and permission boundaries",
|
|
1246
|
+
"suggestion.enterprise.prompt": "Design a cloud landing zone for my organisation, covering the account structure, network partitions, permission boundaries and audit requirements, and explain the trade-offs behind each part.",
|
|
1247
|
+
"suggestion.billing.title": "Billing analysis",
|
|
1248
|
+
"suggestion.billing.gloss": "Break down last month's cloud spend",
|
|
1249
|
+
"suggestion.billing.prompt": "Analyse my cloud spend for last month, identify the services that account for the largest share, point out what could be optimised or downsized, and estimate how much that would save."
|
|
1250
|
+
};
|
|
1251
|
+
|
|
266
1252
|
//#endregion
|
|
267
1253
|
//#region src/client/title-shadow.ts
|
|
268
1254
|
/**
|
|
@@ -683,9 +1669,13 @@ function unbrand(value) {
|
|
|
683
1669
|
|
|
684
1670
|
//#endregion
|
|
685
1671
|
//#region src/client/index.ts
|
|
686
|
-
/** Required
|
|
687
|
-
const inject = ["slots"];
|
|
1672
|
+
/** Required services: the UI slot registry and the locale registry. */
|
|
1673
|
+
const inject = ["slots", "locale"];
|
|
688
1674
|
function apply(ctx) {
|
|
1675
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1676
|
+
zh,
|
|
1677
|
+
en
|
|
1678
|
+
}), "aishell-brand: dictionaries");
|
|
689
1679
|
ctx.slots.inject("sidebar.brand.mark", () => ctx.slots.register({
|
|
690
1680
|
name: "sidebar.brand.mark",
|
|
691
1681
|
priority: -1
|
|
@@ -698,6 +1688,12 @@ function apply(ctx) {
|
|
|
698
1688
|
name: "conversation.hero.brand.mark",
|
|
699
1689
|
priority: -1
|
|
700
1690
|
}, AishellHeroBrandMark));
|
|
1691
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
1692
|
+
name: "conversation.input.dock",
|
|
1693
|
+
id: "aishell-suggestions",
|
|
1694
|
+
order: 20,
|
|
1695
|
+
locale: NS
|
|
1696
|
+
}, SuggestionCards));
|
|
701
1697
|
if (typeof document !== "undefined") {
|
|
702
1698
|
ctx.effect(() => installHeroPatchCss(document), "aishell-brand: hero patch css");
|
|
703
1699
|
ctx.effect(() => installHeroHoverBridge(document), "aishell-brand: hero hover bridge");
|