@nextclaw/ui 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
  3. package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
  4. package/dist/assets/chat-page-BRB0N5WW.js +105 -0
  5. package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
  10. package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
  11. package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
  12. package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
  15. package/dist/assets/index-BiE-K8-q.css +1 -0
  16. package/dist/assets/index-D9BAE_L_.js +105 -0
  17. package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-DotaTUHb.js +1 -0
  21. package/dist/assets/plus-BbZqquJe.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
  23. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
  25. package/dist/assets/remote-B8TiUfwW.js +1 -0
  26. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  27. package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
  34. package/dist/assets/use-config-CjRV2hwL.js +1 -0
  35. package/dist/assets/x-D9PXs_xP.js +1 -0
  36. package/dist/index.html +70 -19
  37. package/index.html +55 -5
  38. package/package.json +9 -9
  39. package/src/app/components/app-presenter-provider.tsx +11 -2
  40. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  41. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  42. package/src/app/components/layout/app-layout.tsx +18 -4
  43. package/src/app/components/layout/header.tsx +3 -3
  44. package/src/app/components/layout/page-layout.tsx +2 -2
  45. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  46. package/src/app/components/layout/sidebar-items.tsx +194 -71
  47. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  48. package/src/app/components/layout/sidebar.tsx +340 -83
  49. package/src/app/managers/viewport-layout.manager.ts +10 -0
  50. package/src/app/stores/viewport-layout.store.ts +39 -3
  51. package/src/app/styles/design-system.css +388 -26
  52. package/src/app/test/vitest-setup.ts +31 -0
  53. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  54. package/src/features/agents/components/agents-page.tsx +10 -6
  55. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  56. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  57. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  59. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  60. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  61. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
  62. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  63. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
  64. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  65. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  66. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
  68. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  69. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  70. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
  71. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
  72. package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
  73. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
  79. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  80. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
  81. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  82. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  83. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  84. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  85. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  86. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  87. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  88. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  89. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  90. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  91. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  92. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  93. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  94. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  95. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  96. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  97. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  98. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  99. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  100. package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
  101. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  102. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  103. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  104. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  105. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
  106. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  107. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  108. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  109. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  110. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  111. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  112. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  113. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  114. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  115. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  116. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  117. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  118. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  119. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  120. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  121. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  122. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
  123. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  124. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  125. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  126. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  127. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
  128. package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
  129. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  130. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  131. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  132. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  133. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  134. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  135. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  136. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  137. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  138. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  139. package/src/features/chat/index.ts +0 -1
  140. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  141. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  142. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  143. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  144. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  145. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  146. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  147. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  148. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  149. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  150. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  151. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  152. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  153. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  154. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  155. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  156. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  157. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  158. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  159. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  160. package/src/features/settings/components/config/provider-form.tsx +10 -10
  161. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  162. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  163. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  164. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  165. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  166. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  167. package/src/shared/components/config-split-page.tsx +11 -11
  168. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  169. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  170. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  171. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  172. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  173. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  174. package/src/shared/components/ui/button.tsx +4 -4
  175. package/src/shared/components/ui/card.tsx +4 -4
  176. package/src/shared/components/ui/dialog.tsx +5 -5
  177. package/src/shared/components/ui/input.tsx +1 -1
  178. package/src/shared/components/ui/label.tsx +1 -1
  179. package/src/shared/components/ui/popover.tsx +1 -1
  180. package/src/shared/components/ui/select.tsx +3 -3
  181. package/src/shared/components/ui/switch.tsx +3 -3
  182. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  183. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  184. package/src/shared/components/ui/tabs.tsx +4 -4
  185. package/src/shared/components/ui/textarea.tsx +1 -1
  186. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  187. package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
  188. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  189. package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
  190. package/src/shared/lib/theme/index.ts +94 -52
  191. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  192. package/src/shared/lib/ui-document-title/index.ts +50 -21
  193. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  194. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  195. package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
  196. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  197. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  198. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  199. package/dist/assets/index-7o1WdUbU.js +0 -103
  200. package/dist/assets/index-DQWmEWzk.css +0 -1
  201. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  202. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  203. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  204. package/dist/assets/plus-Bnt56oqG.js +0 -1
  205. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  206. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  207. package/dist/assets/remote-B0LMs8u7.js +0 -1
  208. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  209. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  210. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  211. package/dist/assets/x-CW2KO-Hz.js +0 -1
  212. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  213. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  214. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  215. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  216. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  217. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  218. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  219. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  220. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  221. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  222. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  223. package/src/features/chat/stores/chat-input.store.ts +0 -104
  224. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -0,0 +1,117 @@
1
+ import { useCallback, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ resolveChatInputSurfaceState,
4
+ type ChatInputSurfaceTrigger,
5
+ } from '@nextclaw/agent-chat-ui';
6
+ import { usePanelApps } from '@/features/panel-apps';
7
+ import { t, type I18nLanguage } from '@/shared/lib/i18n';
8
+ import { createPanelAppReferenceInputSurfacePlugin, PANEL_APP_REFERENCE_TRIGGER_SPEC } from '@/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils';
9
+ import {
10
+ createSlashCommandInputSurfacePlugin,
11
+ type ChatSlashCommandDescriptor,
12
+ } from '@/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils';
13
+ import type { ChatInputBarAdapterTexts, ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
14
+
15
+ export function useChatInputSurfaceState(params: {
16
+ commands: readonly ChatSlashCommandDescriptor[];
17
+ isSkillsLoading: boolean;
18
+ itemTexts: {
19
+ slashTexts: Pick<
20
+ ChatInputBarAdapterTexts,
21
+ 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'
22
+ >;
23
+ };
24
+ language: I18nLanguage;
25
+ onSelectSkill: (skillRef: string) => void;
26
+ recentSkillValues: readonly string[];
27
+ skillRecords: readonly ChatSkillRecord[];
28
+ }) {
29
+ const {
30
+ isSkillsLoading,
31
+ itemTexts,
32
+ language,
33
+ onSelectSkill,
34
+ recentSkillValues,
35
+ skillRecords,
36
+ commands,
37
+ } = params;
38
+ const [inputSurfaceTrigger, setInputSurfaceTriggerState] = useState<ChatInputSurfaceTrigger | null>(null);
39
+ const inputSurfaceTriggerSignatureRef = useRef('null');
40
+ const setInputSurfaceTrigger = useCallback((nextTrigger: ChatInputSurfaceTrigger | null): void => {
41
+ const nextSignature = JSON.stringify(nextTrigger ? [nextTrigger.key, nextTrigger.marker, nextTrigger.query, nextTrigger.start, nextTrigger.end] : null);
42
+ if (inputSurfaceTriggerSignatureRef.current === nextSignature) return;
43
+ inputSurfaceTriggerSignatureRef.current = nextSignature;
44
+ setInputSurfaceTriggerState(nextTrigger);
45
+ }, []);
46
+ const panelApps = usePanelApps({ enabled: inputSurfaceTrigger?.key === PANEL_APP_REFERENCE_TRIGGER_SPEC.key });
47
+
48
+ const inputSurfacePlugins = useMemo(
49
+ () => [
50
+ createSlashCommandInputSurfacePlugin({
51
+ commands,
52
+ itemTexts: itemTexts.slashTexts,
53
+ menuTexts: {
54
+ loadingLabel: t('chatSlashLoading', language),
55
+ sectionLabel: t('chatSlashSection', language),
56
+ emptyLabel: t('chatSlashNoResult', language),
57
+ hintLabel: t('chatSlashHint', language),
58
+ itemHintLabel: t('chatSlashSkillHint', language)
59
+ },
60
+ labels: {
61
+ commandHintLabel: t('chatSlashCommandHint', language),
62
+ commandSectionLabel: t('chatSlashSectionCommands', language),
63
+ commandSubtitle: t('chatSlashTypeCommand', language),
64
+ skillHintLabel: t('chatSlashSkillHint', language),
65
+ skillSectionLabel: t('chatSlashSectionSkills', language)
66
+ },
67
+ onSelectSkill
68
+ }),
69
+ createPanelAppReferenceInputSurfacePlugin({
70
+ itemTexts: {
71
+ appIdLabel: t('chatPanelAppReferenceAppId', language),
72
+ fileLabel: t('chatPanelAppReferenceFile', language),
73
+ noDescriptionLabel: t('chatPanelAppReferenceNoDescription', language),
74
+ subtitle: t('chatPanelAppReferenceType', language)
75
+ },
76
+ menuTexts: {
77
+ loadingLabel: t('chatPanelAppReferenceLoading', language),
78
+ sectionLabel: t('chatPanelAppReferenceSection', language),
79
+ emptyLabel: t('chatPanelAppReferenceNoResult', language),
80
+ hintLabel: t('chatPanelAppReferenceHint', language),
81
+ itemHintLabel: t('chatPanelAppReferenceItemHint', language)
82
+ }
83
+ })
84
+ ],
85
+ [
86
+ commands,
87
+ itemTexts.slashTexts,
88
+ language,
89
+ onSelectSkill
90
+ ]
91
+ );
92
+ const inputSurfaceState = useMemo(
93
+ () => resolveChatInputSurfaceState({
94
+ data: {
95
+ isPanelAppsLoading: panelApps.isLoading || panelApps.isFetching,
96
+ isSkillsLoading,
97
+ panelApps: panelApps.data?.entries ?? [],
98
+ recentSkillValues,
99
+ skillRecords
100
+ },
101
+ plugins: inputSurfacePlugins,
102
+ trigger: inputSurfaceTrigger
103
+ }),
104
+ [
105
+ inputSurfacePlugins,
106
+ inputSurfaceTrigger,
107
+ panelApps.data?.entries,
108
+ panelApps.isFetching,
109
+ panelApps.isLoading,
110
+ isSkillsLoading,
111
+ recentSkillValues,
112
+ skillRecords
113
+ ]
114
+ );
115
+
116
+ return { inputSurfaceState, setInputSurfaceTrigger };
117
+ }
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveChatInputSurfaceState } from '@nextclaw/agent-chat-ui';
3
+ import { createPanelAppReferenceInputSurfacePlugin, PANEL_APP_REFERENCE_TRIGGER_SPEC } from '@/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils';
4
+ import type { PanelAppEntryView } from '@/shared/lib/api';
5
+
6
+ function createPanelApp(overrides: Partial<PanelAppEntryView> & Pick<PanelAppEntryView, 'appId' | 'title'>): PanelAppEntryView {
7
+ return {
8
+ id: overrides.id ?? overrides.appId,
9
+ appId: overrides.appId,
10
+ fileName: overrides.fileName ?? `${overrides.appId}.panel.html`,
11
+ kind: overrides.kind ?? 'single-file',
12
+ title: overrides.title,
13
+ description: overrides.description,
14
+ contentPath: overrides.contentPath ?? `/panels/${overrides.appId}.panel.html`,
15
+ createdAt: overrides.createdAt ?? '2026-06-18T00:00:00.000Z',
16
+ updatedAt: overrides.updatedAt ?? '2026-06-18T00:00:00.000Z',
17
+ sizeBytes: overrides.sizeBytes ?? 100,
18
+ favorite: overrides.favorite ?? false,
19
+ clientDeclared: overrides.clientDeclared ?? false,
20
+ clientGranted: overrides.clientGranted ?? false,
21
+ lastOpenedAt: overrides.lastOpenedAt,
22
+ openCount: overrides.openCount ?? 0,
23
+ };
24
+ }
25
+
26
+ describe('panel app reference input surface plugin', () => {
27
+ it('returns panel app reference items for @ trigger queries', () => {
28
+ const plugin = createPanelAppReferenceInputSurfacePlugin({
29
+ itemTexts: {
30
+ appIdLabel: 'App ID',
31
+ fileLabel: 'File',
32
+ noDescriptionLabel: 'No description',
33
+ subtitle: 'Panel App',
34
+ },
35
+ menuTexts: {
36
+ loadingLabel: 'Loading',
37
+ sectionLabel: 'Panel Apps',
38
+ emptyLabel: 'Empty',
39
+ hintLabel: 'Type @',
40
+ itemHintLabel: 'Enter to reference',
41
+ },
42
+ });
43
+
44
+ const state = resolveChatInputSurfaceState({
45
+ plugins: [plugin],
46
+ trigger: {
47
+ ...PANEL_APP_REFERENCE_TRIGGER_SPEC,
48
+ query: 'task',
49
+ start: 0,
50
+ end: 5,
51
+ },
52
+ data: {
53
+ isPanelAppsLoading: false,
54
+ isSkillsLoading: false,
55
+ panelApps: [
56
+ createPanelApp({
57
+ appId: 'task-board',
58
+ title: 'Task Board',
59
+ description: 'Track tasks',
60
+ }),
61
+ ],
62
+ recentSkillValues: [],
63
+ skillRecords: [],
64
+ },
65
+ });
66
+
67
+ expect(state.panel?.items).toEqual([
68
+ expect.objectContaining({
69
+ key: 'panel-app:task-board',
70
+ title: 'Task Board',
71
+ tokenKind: 'panel_app',
72
+ tokenKey: 'task-board',
73
+ value: 'task-board',
74
+ }),
75
+ ]);
76
+ });
77
+ });
@@ -0,0 +1,108 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { resolveChatInputSurfaceState } from '@nextclaw/agent-chat-ui';
3
+ import { createSlashCommandInputSurfacePlugin } from '@/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils';
4
+
5
+ function createPlugin(onSelectCommand = vi.fn()) {
6
+ return createSlashCommandInputSurfacePlugin({
7
+ commands: [
8
+ {
9
+ key: 'side-chat',
10
+ title: 'Side chat',
11
+ description: 'Open side chat',
12
+ detailLines: ['Creates a child session on first send'],
13
+ keywords: ['side', 'chat'],
14
+ onSelect: onSelectCommand,
15
+ },
16
+ ],
17
+ itemTexts: {
18
+ noSkillDescription: 'No description',
19
+ slashSkillScopeLabel: 'Scope',
20
+ slashSkillSpecLabel: 'Spec',
21
+ slashSkillSubtitle: 'Skill',
22
+ },
23
+ labels: {
24
+ commandHintLabel: 'Run command',
25
+ commandSectionLabel: 'Commands',
26
+ commandSubtitle: 'Command',
27
+ skillHintLabel: 'Add skill',
28
+ skillSectionLabel: 'Skills',
29
+ },
30
+ menuTexts: {
31
+ emptyLabel: 'No result',
32
+ hintLabel: 'Type /',
33
+ itemHintLabel: 'Select item',
34
+ loadingLabel: 'Loading',
35
+ sectionLabel: 'Slash',
36
+ },
37
+ onSelectSkill: vi.fn(),
38
+ });
39
+ }
40
+
41
+ describe('createSlashCommandInputSurfacePlugin', () => {
42
+ it('places command items before skill items in the slash panel', () => {
43
+ const state = resolveChatInputSurfaceState({
44
+ data: {
45
+ isPanelAppsLoading: false,
46
+ isSkillsLoading: false,
47
+ panelApps: [],
48
+ recentSkillValues: [],
49
+ skillRecords: [
50
+ {
51
+ key: 'web-search',
52
+ label: 'Web Search',
53
+ description: 'Search web',
54
+ },
55
+ ],
56
+ },
57
+ plugins: [createPlugin()],
58
+ trigger: {
59
+ key: 'slash',
60
+ marker: '/',
61
+ query: '',
62
+ start: 0,
63
+ end: 1,
64
+ },
65
+ });
66
+
67
+ expect(state.panel?.items.map((item) => item.key)).toEqual([
68
+ 'command:side-chat',
69
+ 'skill:web-search',
70
+ ]);
71
+ expect(state.panel?.items[0]).toMatchObject({
72
+ sectionKey: 'commands',
73
+ sectionLabel: 'Commands',
74
+ hintLabel: 'Run command',
75
+ });
76
+ expect(state.panel?.items[1]).toMatchObject({
77
+ sectionKey: 'skills',
78
+ sectionLabel: 'Skills',
79
+ hintLabel: 'Add skill',
80
+ });
81
+ });
82
+
83
+ it('runs a command item through the panel selection handler', () => {
84
+ const onSelectCommand = vi.fn();
85
+ const state = resolveChatInputSurfaceState({
86
+ data: {
87
+ isPanelAppsLoading: false,
88
+ isSkillsLoading: false,
89
+ panelApps: [],
90
+ recentSkillValues: [],
91
+ skillRecords: [],
92
+ },
93
+ plugins: [createPlugin(onSelectCommand)],
94
+ trigger: {
95
+ key: 'slash',
96
+ marker: '/',
97
+ query: 'side',
98
+ start: 0,
99
+ end: 5,
100
+ },
101
+ });
102
+
103
+ const item = state.panel?.items[0];
104
+ expect(item?.key).toBe('command:side-chat');
105
+ state.panel?.onSelectItem?.(item!);
106
+ expect(onSelectCommand).toHaveBeenCalledTimes(1);
107
+ });
108
+ });
@@ -0,0 +1,10 @@
1
+ import type { ChatSkillRecord } from '@/features/chat/types/chat-input-bar.types';
2
+ import type { PanelAppEntryView } from '@/shared/lib/api';
3
+
4
+ export type ChatInputProductPluginData = {
5
+ isPanelAppsLoading: boolean;
6
+ isSkillsLoading: boolean;
7
+ panelApps: readonly PanelAppEntryView[];
8
+ recentSkillValues: readonly string[];
9
+ skillRecords: readonly ChatSkillRecord[];
10
+ };
@@ -0,0 +1,108 @@
1
+ const INPUT_SURFACE_MATCH_SCORE = {
2
+ exactId: 1200,
3
+ exactLabel: 1150,
4
+ prefixId: 1000,
5
+ prefixLabel: 950,
6
+ prefixToken: 900,
7
+ containsId: 800,
8
+ containsLabel: 760,
9
+ containsDescription: 500,
10
+ subsequence: 300,
11
+ fallback: 1,
12
+ } as const;
13
+
14
+ export type InputSurfaceSearchCandidate = {
15
+ id: string;
16
+ label: string;
17
+ description?: string;
18
+ aliases?: readonly string[];
19
+ };
20
+
21
+ export function normalizeInputSurfaceSearchText(value: string | null | undefined): string {
22
+ return (value ?? '').trim().toLowerCase();
23
+ }
24
+
25
+ export function buildInputSurfaceRecentOrderIndex(values: readonly string[]): Map<string, number> {
26
+ return new Map(values.map((value, index) => [value, index] as const));
27
+ }
28
+
29
+ function isSubsequenceMatch(query: string, target: string): boolean {
30
+ if (!query || !target) {
31
+ return false;
32
+ }
33
+ let pointer = 0;
34
+ for (const char of target) {
35
+ if (char === query[pointer]) {
36
+ pointer += 1;
37
+ if (pointer >= query.length) {
38
+ return true;
39
+ }
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+
45
+ export function scoreInputSurfaceSearchCandidate(
46
+ candidate: InputSurfaceSearchCandidate,
47
+ query: string,
48
+ ): number {
49
+ const normalizedQuery = normalizeInputSurfaceSearchText(query);
50
+ if (!normalizedQuery) {
51
+ return INPUT_SURFACE_MATCH_SCORE.fallback;
52
+ }
53
+
54
+ const id = normalizeInputSurfaceSearchText(candidate.id);
55
+ const label = normalizeInputSurfaceSearchText(candidate.label || candidate.id);
56
+ const description = normalizeInputSurfaceSearchText(candidate.description);
57
+ const aliases = (candidate.aliases ?? []).map(normalizeInputSurfaceSearchText).filter(Boolean);
58
+ const labelTokens = label.split(/[\s/_-]+/).filter(Boolean);
59
+ const idCandidates = [id, ...aliases];
60
+
61
+ if (idCandidates.some((value) => value === normalizedQuery)) {
62
+ return INPUT_SURFACE_MATCH_SCORE.exactId;
63
+ }
64
+ if (label === normalizedQuery) {
65
+ return INPUT_SURFACE_MATCH_SCORE.exactLabel;
66
+ }
67
+ if (idCandidates.some((value) => value.startsWith(normalizedQuery))) {
68
+ return INPUT_SURFACE_MATCH_SCORE.prefixId;
69
+ }
70
+ if (label.startsWith(normalizedQuery)) {
71
+ return INPUT_SURFACE_MATCH_SCORE.prefixLabel;
72
+ }
73
+ if (labelTokens.some((token) => token.startsWith(normalizedQuery))) {
74
+ return INPUT_SURFACE_MATCH_SCORE.prefixToken;
75
+ }
76
+ if (idCandidates.some((value) => value.includes(normalizedQuery))) {
77
+ return INPUT_SURFACE_MATCH_SCORE.containsId;
78
+ }
79
+ if (label.includes(normalizedQuery)) {
80
+ return INPUT_SURFACE_MATCH_SCORE.containsLabel;
81
+ }
82
+ if (description.includes(normalizedQuery)) {
83
+ return INPUT_SURFACE_MATCH_SCORE.containsDescription;
84
+ }
85
+ if (
86
+ isSubsequenceMatch(normalizedQuery, label) ||
87
+ idCandidates.some((value) => isSubsequenceMatch(normalizedQuery, value))
88
+ ) {
89
+ return INPUT_SURFACE_MATCH_SCORE.subsequence;
90
+ }
91
+ return 0;
92
+ }
93
+
94
+ export function resolveInputSurfaceMatchTier(score: number): number {
95
+ if (score >= INPUT_SURFACE_MATCH_SCORE.exactLabel) {
96
+ return 4;
97
+ }
98
+ if (score >= INPUT_SURFACE_MATCH_SCORE.prefixToken) {
99
+ return 3;
100
+ }
101
+ if (score >= INPUT_SURFACE_MATCH_SCORE.containsLabel) {
102
+ return 2;
103
+ }
104
+ if (score > 0) {
105
+ return 1;
106
+ }
107
+ return 0;
108
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ createInputSurfaceReferenceTokenPlugin,
3
+ type ChatInputSurfaceItem,
4
+ type ChatInputSurfaceMenuTexts,
5
+ type ChatInputSurfacePlugin,
6
+ type ChatInputSurfaceTriggerSpec,
7
+ } from '@nextclaw/agent-chat-ui';
8
+ import type { ChatInputProductPluginData } from './chat-input-product-plugin-adapters.types';
9
+ import type { PanelAppEntryView } from '@/shared/lib/api';
10
+ import {
11
+ scoreInputSurfaceSearchCandidate,
12
+ resolveInputSurfaceMatchTier,
13
+ } from './input-surface-search.utils';
14
+
15
+ export const PANEL_APP_REFERENCE_TRIGGER_SPEC: ChatInputSurfaceTriggerSpec = {
16
+ key: 'panel-app-reference',
17
+ marker: '@',
18
+ };
19
+
20
+ function getPanelAppActivityTime(entry: PanelAppEntryView): number {
21
+ return Date.parse(entry.lastOpenedAt ?? entry.updatedAt) || 0;
22
+ }
23
+
24
+ function buildPanelAppReferenceItems(params: {
25
+ entries: readonly PanelAppEntryView[];
26
+ query: string;
27
+ texts: {
28
+ appIdLabel: string;
29
+ fileLabel: string;
30
+ noDescriptionLabel: string;
31
+ subtitle: string;
32
+ };
33
+ }): ChatInputSurfaceItem[] {
34
+ const collator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
35
+ return params.entries
36
+ .map((entry, order) => ({
37
+ entry,
38
+ order,
39
+ score: scoreInputSurfaceSearchCandidate(
40
+ {
41
+ id: entry.appId,
42
+ label: entry.title || entry.appId,
43
+ description: entry.description,
44
+ aliases: [entry.id, entry.fileName],
45
+ },
46
+ params.query,
47
+ ),
48
+ }))
49
+ .filter((entry) => entry.score > 0)
50
+ .sort((left, right) => {
51
+ const leftTier = resolveInputSurfaceMatchTier(left.score);
52
+ const rightTier = resolveInputSurfaceMatchTier(right.score);
53
+ if (rightTier !== leftTier) {
54
+ return rightTier - leftTier;
55
+ }
56
+ if (left.entry.favorite !== right.entry.favorite) {
57
+ return left.entry.favorite ? -1 : 1;
58
+ }
59
+ const rightActivity = getPanelAppActivityTime(right.entry);
60
+ const leftActivity = getPanelAppActivityTime(left.entry);
61
+ if (rightActivity !== leftActivity) {
62
+ return rightActivity - leftActivity;
63
+ }
64
+ if (right.score !== left.score) {
65
+ return right.score - left.score;
66
+ }
67
+ const labelCompare = collator.compare(left.entry.title || left.entry.appId, right.entry.title || right.entry.appId);
68
+ return labelCompare || left.order - right.order;
69
+ })
70
+ .map(({ entry }) => ({
71
+ key: `panel-app:${entry.appId}`,
72
+ title: entry.title || entry.appId,
73
+ subtitle: params.texts.subtitle,
74
+ description: (entry.description ?? '').trim() || params.texts.noDescriptionLabel,
75
+ detailLines: [
76
+ `${params.texts.appIdLabel}: ${entry.appId}`,
77
+ `${params.texts.fileLabel}: ${entry.fileName}`,
78
+ ],
79
+ tokenKind: 'panel_app',
80
+ tokenKey: entry.appId,
81
+ value: entry.appId,
82
+ }));
83
+ }
84
+
85
+ export function createPanelAppReferenceInputSurfacePlugin(params: {
86
+ itemTexts: {
87
+ appIdLabel: string;
88
+ fileLabel: string;
89
+ noDescriptionLabel: string;
90
+ subtitle: string;
91
+ };
92
+ menuTexts: ChatInputSurfaceMenuTexts;
93
+ }): ChatInputSurfacePlugin<ChatInputProductPluginData> {
94
+ return createInputSurfaceReferenceTokenPlugin({
95
+ key: 'panel-app-reference',
96
+ trigger: PANEL_APP_REFERENCE_TRIGGER_SPEC,
97
+ tokenKind: 'panel_app',
98
+ texts: params.menuTexts,
99
+ getIsLoading: ({ data }) => data.isPanelAppsLoading,
100
+ getRecords: ({ data }) => data.panelApps,
101
+ getItems: ({ records, query }) =>
102
+ buildPanelAppReferenceItems({
103
+ entries: records,
104
+ query,
105
+ texts: params.itemTexts,
106
+ }),
107
+ });
108
+ }
@@ -0,0 +1,161 @@
1
+ import {
2
+ CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC,
3
+ createInputSurfaceTriggeredPanelPlugin,
4
+ type ChatInputSurfaceItem,
5
+ type ChatInputSurfaceMenuTexts,
6
+ type ChatInputSurfacePlugin,
7
+ } from '@nextclaw/agent-chat-ui';
8
+ import { buildChatSlashItems } from '@/features/chat/features/input/utils/chat-input-bar.utils';
9
+ import type { ChatInputProductPluginData } from './chat-input-product-plugin-adapters.types';
10
+ import type { ChatInputBarAdapterTexts } from '@/features/chat/types/chat-input-bar.types';
11
+ import {
12
+ resolveInputSurfaceMatchTier,
13
+ scoreInputSurfaceSearchCandidate,
14
+ } from './input-surface-search.utils';
15
+
16
+ const SLASH_COMMAND_SECTION_KEY = 'commands';
17
+ const SLASH_SKILL_SECTION_KEY = 'skills';
18
+
19
+ export type ChatSlashCommandDescriptor = {
20
+ key: string;
21
+ title: string;
22
+ description: string;
23
+ detailLines?: readonly string[];
24
+ keywords?: readonly string[];
25
+ onSelect: () => void;
26
+ };
27
+
28
+ function buildSlashCommandItems(params: {
29
+ commands: readonly ChatSlashCommandDescriptor[];
30
+ query: string;
31
+ texts: {
32
+ commandHintLabel: string;
33
+ commandSubtitle: string;
34
+ commandSectionLabel: string;
35
+ };
36
+ }): ChatInputSurfaceItem[] {
37
+ const collator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
38
+ return params.commands
39
+ .map((command, order) => ({
40
+ command,
41
+ order,
42
+ score: scoreInputSurfaceSearchCandidate(
43
+ {
44
+ id: command.key,
45
+ label: command.title,
46
+ description: command.description,
47
+ aliases: command.keywords,
48
+ },
49
+ params.query,
50
+ ),
51
+ }))
52
+ .filter((entry) => entry.score > 0)
53
+ .sort((left, right) => {
54
+ const leftTier = resolveInputSurfaceMatchTier(left.score);
55
+ const rightTier = resolveInputSurfaceMatchTier(right.score);
56
+ if (rightTier !== leftTier) {
57
+ return rightTier - leftTier;
58
+ }
59
+ if (right.score !== left.score) {
60
+ return right.score - left.score;
61
+ }
62
+ return collator.compare(left.command.title, right.command.title) || left.order - right.order;
63
+ })
64
+ .map(({ command }) => ({
65
+ key: `command:${command.key}`,
66
+ title: command.title,
67
+ subtitle: params.texts.commandSubtitle,
68
+ description: command.description,
69
+ detailLines: [...(command.detailLines ?? [])],
70
+ hintLabel: params.texts.commandHintLabel,
71
+ sectionKey: SLASH_COMMAND_SECTION_KEY,
72
+ sectionLabel: params.texts.commandSectionLabel,
73
+ }));
74
+ }
75
+
76
+ function buildSlashSkillItems(params: {
77
+ data: ChatInputProductPluginData;
78
+ itemTexts: Pick<
79
+ ChatInputBarAdapterTexts,
80
+ 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'
81
+ >;
82
+ skillHintLabel: string;
83
+ skillSectionLabel: string;
84
+ query: string;
85
+ }): ChatInputSurfaceItem[] {
86
+ const {
87
+ data,
88
+ itemTexts,
89
+ query,
90
+ skillHintLabel,
91
+ skillSectionLabel,
92
+ } = params;
93
+ return buildChatSlashItems(
94
+ [...data.skillRecords],
95
+ query,
96
+ itemTexts,
97
+ [...data.recentSkillValues],
98
+ ).map((item) => ({
99
+ ...item,
100
+ hintLabel: skillHintLabel,
101
+ sectionKey: SLASH_SKILL_SECTION_KEY,
102
+ sectionLabel: skillSectionLabel,
103
+ }));
104
+ }
105
+
106
+ export function createSlashCommandInputSurfacePlugin(params: {
107
+ commands: readonly ChatSlashCommandDescriptor[];
108
+ menuTexts: ChatInputSurfaceMenuTexts;
109
+ itemTexts: Pick<
110
+ ChatInputBarAdapterTexts,
111
+ 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'
112
+ >;
113
+ labels: {
114
+ commandHintLabel: string;
115
+ commandSectionLabel: string;
116
+ commandSubtitle: string;
117
+ skillHintLabel: string;
118
+ skillSectionLabel: string;
119
+ };
120
+ onSelectSkill?: (skillRef: string) => void;
121
+ }): ChatInputSurfacePlugin<ChatInputProductPluginData> {
122
+ return createInputSurfaceTriggeredPanelPlugin({
123
+ key: 'slash-command',
124
+ trigger: CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC,
125
+ resolvePanel: (context) => {
126
+ const { data, trigger } = context;
127
+ return {
128
+ isLoading: data.isSkillsLoading,
129
+ items: [
130
+ ...buildSlashCommandItems({
131
+ commands: params.commands,
132
+ query: trigger.query,
133
+ texts: {
134
+ commandHintLabel: params.labels.commandHintLabel,
135
+ commandSectionLabel: params.labels.commandSectionLabel,
136
+ commandSubtitle: params.labels.commandSubtitle,
137
+ },
138
+ }),
139
+ ...buildSlashSkillItems({
140
+ data,
141
+ itemTexts: params.itemTexts,
142
+ query: trigger.query,
143
+ skillHintLabel: params.labels.skillHintLabel,
144
+ skillSectionLabel: params.labels.skillSectionLabel,
145
+ }),
146
+ ],
147
+ onSelectItem: (item) => {
148
+ if (item.key.startsWith('command:')) {
149
+ params.commands.find((command) => item.key === `command:${command.key}`)?.onSelect();
150
+ return;
151
+ }
152
+ const skillRef = item.tokenKey ?? item.value;
153
+ if (skillRef) {
154
+ params.onSelectSkill?.(skillRef);
155
+ }
156
+ },
157
+ texts: params.menuTexts,
158
+ };
159
+ },
160
+ });
161
+ }