@nextclaw/ui 0.6.14 → 0.7.0

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 (94) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +2 -0
  3. package/dist/assets/ChannelsList-DF2U-LY1.js +1 -0
  4. package/dist/assets/ChatPage-BX39y0U5.js +36 -0
  5. package/dist/assets/DocBrowser-B9ws5JL7.js +1 -0
  6. package/dist/assets/{LogoBadge-BxZJ9BJT.js → LogoBadge-DvGAzkZ3.js} +1 -1
  7. package/dist/assets/MarketplacePage-DG5mHWJ8.js +49 -0
  8. package/dist/assets/ModelConfig-BL_HsOsm.js +1 -0
  9. package/dist/assets/ProvidersList-CH5z00YT.js +1 -0
  10. package/dist/assets/RuntimeConfig-BplBgkwo.js +1 -0
  11. package/dist/assets/SearchConfig-BhaI0fUf.js +1 -0
  12. package/dist/assets/{SecretsConfig-9OABNssV.js → SecretsConfig-CFoimOh9.js} +2 -2
  13. package/dist/assets/SessionsConfig-BHTAYn9T.js +2 -0
  14. package/dist/assets/index-BLeJkJ0o.css +1 -0
  15. package/dist/assets/index-DK4TS5ev.js +8 -0
  16. package/dist/assets/index-X5J6Mm--.js +1 -0
  17. package/dist/assets/{index-CkqvHQAt.js → index-uMsNsQX6.js} +1 -1
  18. package/dist/assets/{label-BIjHWZUm.js → label-D8ly4a2P.js} +1 -1
  19. package/dist/assets/page-layout-BSYfvwbp.js +1 -0
  20. package/dist/assets/security-config-DlKEYHNN.js +1 -0
  21. package/dist/assets/{session-run-status-BZEH0QZp.js → session-run-status-TkIuGbVw.js} +1 -1
  22. package/dist/assets/skeleton-CWbsNx2h.js +1 -0
  23. package/dist/assets/{switch-CnGQpdTp.js → switch-Ce_g9lpN.js} +1 -1
  24. package/dist/assets/tabs-custom-Cf5azvT5.js +1 -0
  25. package/dist/assets/useConfirmDialog-A8Ek8Wu7.js +5 -0
  26. package/dist/assets/vendor-B7ozqnFC.js +412 -0
  27. package/dist/index.html +3 -3
  28. package/package.json +9 -10
  29. package/src/App.tsx +49 -27
  30. package/src/api/client.ts +1 -0
  31. package/src/api/config.ts +60 -0
  32. package/src/api/types.ts +29 -1
  33. package/src/api/websocket.ts +2 -0
  34. package/src/components/auth/login-page.tsx +69 -0
  35. package/src/components/chat/ChatConversationPanel.tsx +12 -54
  36. package/src/components/chat/ChatSidebar.tsx +7 -1
  37. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +80 -0
  38. package/src/components/chat/adapters/chat-input-bar.adapter.ts +329 -0
  39. package/src/components/chat/adapters/chat-message.adapter.test.ts +137 -0
  40. package/src/components/chat/adapters/chat-message.adapter.ts +200 -0
  41. package/src/components/chat/chat-input/chat-input-bar.controller.test.tsx +128 -0
  42. package/src/components/chat/chat-input/chat-input-bar.controller.ts +105 -0
  43. package/src/components/chat/containers/chat-input-bar.container.tsx +270 -0
  44. package/src/components/chat/containers/chat-message-list.container.tsx +67 -0
  45. package/src/components/chat/index.ts +1 -0
  46. package/src/components/chat/managers/chat-thread.manager.ts +3 -1
  47. package/src/components/chat/nextclaw/index.ts +23 -0
  48. package/src/components/common/BrandHeader.tsx +4 -1
  49. package/src/components/common/StatusBadge.tsx +32 -20
  50. package/src/components/config/runtime-security-card.tsx +276 -0
  51. package/src/components/config/security-config.tsx +12 -0
  52. package/src/components/layout/Sidebar.tsx +6 -1
  53. package/src/components/marketplace/MarketplacePage.test.tsx +170 -0
  54. package/src/components/marketplace/MarketplacePage.tsx +77 -28
  55. package/src/hooks/use-auth.ts +111 -0
  56. package/src/hooks/useMarketplace.ts +9 -0
  57. package/src/hooks/useWebSocket.ts +53 -1
  58. package/src/lib/i18n.ts +72 -0
  59. package/src/test/setup.ts +16 -0
  60. package/tsconfig.json +3 -2
  61. package/vite.config.ts +2 -1
  62. package/vitest.config.ts +16 -0
  63. package/.eslintrc.cjs +0 -48
  64. package/dist/assets/ChannelsList-DiSnpiW0.js +0 -1
  65. package/dist/assets/ChatPage-DsaIrNHN.js +0 -36
  66. package/dist/assets/DocBrowser-CnfcptGM.js +0 -1
  67. package/dist/assets/MarketplacePage-BI_J_DBQ.js +0 -49
  68. package/dist/assets/ModelConfig-DfL8F4tN.js +0 -1
  69. package/dist/assets/ProvidersList-DpT_oFHZ.js +0 -1
  70. package/dist/assets/RuntimeConfig-BNYR_Iag.js +0 -1
  71. package/dist/assets/SearchConfig-TDBl7Fjh.js +0 -1
  72. package/dist/assets/SessionsConfig-BRwntUDz.js +0 -2
  73. package/dist/assets/card-BYnT3Mxo.js +0 -1
  74. package/dist/assets/index-BCfS4UY1.css +0 -1
  75. package/dist/assets/index-BnUxgevr.js +0 -8
  76. package/dist/assets/input-oaepEtqu.js +0 -1
  77. package/dist/assets/page-layout-B6JXiSQB.js +0 -1
  78. package/dist/assets/popover-LJQgv5l1.js +0 -1
  79. package/dist/assets/tabs-custom-CpSv7pDl.js +0 -1
  80. package/dist/assets/useConfirmDialog-pqAlPdQZ.js +0 -5
  81. package/dist/assets/vendor-BKtTvQYU.js +0 -407
  82. package/src/components/chat/ChatThread.tsx +0 -402
  83. package/src/components/chat/SkillsPicker.tsx +0 -137
  84. package/src/components/chat/chat-input/ChatInputBarView.tsx +0 -82
  85. package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +0 -83
  86. package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +0 -39
  87. package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +0 -31
  88. package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +0 -112
  89. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +0 -24
  90. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +0 -58
  91. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +0 -56
  92. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +0 -40
  93. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector.tsx +0 -74
  94. package/src/components/chat/chat-input/useChatInputBarController.ts +0 -322
@@ -1,322 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import type { KeyboardEvent } from 'react';
3
- import type { MarketplaceInstalledRecord } from '@/api/types';
4
- import { t } from '@/lib/i18n';
5
- import type { ChatInputBarSlashItem } from '@/components/chat/chat-input.types';
6
-
7
- const SLASH_PANEL_MAX_WIDTH = 680;
8
- const SLASH_PANEL_DESKTOP_SHRINK_RATIO = 0.82;
9
- const SLASH_PANEL_DESKTOP_MIN_WIDTH = 560;
10
-
11
- type RankedSkill = {
12
- record: MarketplaceInstalledRecord;
13
- score: number;
14
- order: number;
15
- };
16
-
17
- type UseChatInputBarControllerParams = {
18
- draft: string;
19
- onDraftChange: (value: string) => void;
20
- onSend: () => Promise<void> | void;
21
- onStop: () => Promise<void> | void;
22
- canStopGeneration: boolean;
23
- isSending: boolean;
24
- skillRecords: MarketplaceInstalledRecord[];
25
- isSkillsLoading: boolean;
26
- selectedSkills: string[];
27
- onSelectedSkillsChange: (next: string[]) => void;
28
- };
29
-
30
- type SlashPanelControllerParams = {
31
- draft: string;
32
- onDraftChange: (value: string) => void;
33
- onSend: () => Promise<void> | void;
34
- onStop: () => Promise<void> | void;
35
- isSending: boolean;
36
- canStopGeneration: boolean;
37
- isSkillsLoading: boolean;
38
- selectedSkills: string[];
39
- onSelectedSkillsChange: (next: string[]) => void;
40
- skillRecords: MarketplaceInstalledRecord[];
41
- };
42
-
43
- function resolveSlashQuery(draft: string): string | null {
44
- const match = /^\/([^\s]*)$/.exec(draft);
45
- if (!match) {
46
- return null;
47
- }
48
- return (match[1] ?? '').trim().toLowerCase();
49
- }
50
-
51
- function normalizeSearchText(value: string | null | undefined): string {
52
- return (value ?? '').trim().toLowerCase();
53
- }
54
-
55
- function isSubsequenceMatch(query: string, target: string): boolean {
56
- if (!query || !target) {
57
- return false;
58
- }
59
- let pointer = 0;
60
- for (const char of target) {
61
- if (char === query[pointer]) {
62
- pointer += 1;
63
- if (pointer >= query.length) {
64
- return true;
65
- }
66
- }
67
- }
68
- return false;
69
- }
70
-
71
- function scoreSkillRecord(record: MarketplaceInstalledRecord, query: string): number {
72
- const normalizedQuery = normalizeSearchText(query);
73
- if (!normalizedQuery) {
74
- return 1;
75
- }
76
-
77
- const spec = normalizeSearchText(record.spec);
78
- const label = normalizeSearchText(record.label || record.spec);
79
- const description = normalizeSearchText(`${record.descriptionZh ?? ''} ${record.description ?? ''}`);
80
- const labelTokens = label.split(/[\s/_-]+/).filter(Boolean);
81
-
82
- if (spec === normalizedQuery) {
83
- return 1200;
84
- }
85
- if (label === normalizedQuery) {
86
- return 1150;
87
- }
88
- if (spec.startsWith(normalizedQuery)) {
89
- return 1000;
90
- }
91
- if (label.startsWith(normalizedQuery)) {
92
- return 950;
93
- }
94
- if (labelTokens.some((token) => token.startsWith(normalizedQuery))) {
95
- return 900;
96
- }
97
- if (spec.includes(normalizedQuery)) {
98
- return 800;
99
- }
100
- if (label.includes(normalizedQuery)) {
101
- return 760;
102
- }
103
- if (description.includes(normalizedQuery)) {
104
- return 500;
105
- }
106
- if (isSubsequenceMatch(normalizedQuery, label) || isSubsequenceMatch(normalizedQuery, spec)) {
107
- return 300;
108
- }
109
- return 0;
110
- }
111
-
112
- function buildSkillSlashItems(skillRecords: MarketplaceInstalledRecord[], normalizedSlashQuery: string): ChatInputBarSlashItem[] {
113
- const skillSortCollator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
114
- const rankedRecords: RankedSkill[] = skillRecords
115
- .map((record, order) => ({
116
- record,
117
- score: scoreSkillRecord(record, normalizedSlashQuery),
118
- order
119
- }))
120
- .filter((entry) => entry.score > 0)
121
- .sort((left, right) => {
122
- if (right.score !== left.score) {
123
- return right.score - left.score;
124
- }
125
- const leftLabel = (left.record.label || left.record.spec).trim();
126
- const rightLabel = (right.record.label || right.record.spec).trim();
127
- const labelCompare = skillSortCollator.compare(leftLabel, rightLabel);
128
- if (labelCompare !== 0) {
129
- return labelCompare;
130
- }
131
- return left.order - right.order;
132
- });
133
-
134
- return rankedRecords
135
- .map((entry) => entry.record)
136
- .map((record) => ({
137
- kind: 'skill',
138
- key: `skill:${record.spec}`,
139
- title: record.label || record.spec,
140
- subtitle: t('chatSlashTypeSkill'),
141
- description: (record.descriptionZh ?? record.description ?? '').trim() || t('chatSkillsPickerNoDescription'),
142
- detailLines: [`${t('chatSlashSkillSpec')}: ${record.spec}`],
143
- skillSpec: record.spec
144
- }));
145
- }
146
-
147
- function useSlashPanelController(params: SlashPanelControllerParams) {
148
- const [activeSlashIndex, setActiveSlashIndex] = useState(0);
149
- const [dismissedSlashPanel, setDismissedSlashPanel] = useState(false);
150
- const [slashPanelWidth, setSlashPanelWidth] = useState<number | null>(null);
151
-
152
- const slashAnchorRef = useRef<HTMLDivElement | null>(null);
153
- const slashListRef = useRef<HTMLDivElement | null>(null);
154
-
155
- const slashQuery = useMemo(() => resolveSlashQuery(params.draft), [params.draft]);
156
- const startsWithSlash = params.draft.startsWith('/');
157
- const normalizedSlashQuery = slashQuery ?? '';
158
-
159
- const skillSlashItems = useMemo(
160
- () => buildSkillSlashItems(params.skillRecords, normalizedSlashQuery),
161
- [normalizedSlashQuery, params.skillRecords]
162
- );
163
- const slashItems = useMemo(() => [...skillSlashItems], [skillSlashItems]);
164
- const isSlashPanelOpen = slashQuery !== null && !dismissedSlashPanel;
165
- const activeSlashItem = slashItems[activeSlashIndex] ?? null;
166
- const isSlashPanelLoading = params.isSkillsLoading;
167
- const resolvedSlashPanelWidth = slashPanelWidth
168
- ? Math.min(
169
- slashPanelWidth > SLASH_PANEL_DESKTOP_MIN_WIDTH
170
- ? slashPanelWidth * SLASH_PANEL_DESKTOP_SHRINK_RATIO
171
- : slashPanelWidth,
172
- SLASH_PANEL_MAX_WIDTH
173
- )
174
- : undefined;
175
-
176
- useEffect(() => {
177
- const anchor = slashAnchorRef.current;
178
- if (!anchor || typeof ResizeObserver === 'undefined') {
179
- return;
180
- }
181
- const update = () => {
182
- setSlashPanelWidth(anchor.getBoundingClientRect().width);
183
- };
184
- update();
185
- const observer = new ResizeObserver(() => update());
186
- observer.observe(anchor);
187
- return () => observer.disconnect();
188
- }, []);
189
-
190
- useEffect(() => {
191
- if (!isSlashPanelOpen || slashItems.length === 0) {
192
- setActiveSlashIndex(0);
193
- return;
194
- }
195
- setActiveSlashIndex((current) => {
196
- if (current < 0) {
197
- return 0;
198
- }
199
- if (current >= slashItems.length) {
200
- return slashItems.length - 1;
201
- }
202
- return current;
203
- });
204
- }, [isSlashPanelOpen, slashItems.length]);
205
-
206
- useEffect(() => {
207
- if (!startsWithSlash && dismissedSlashPanel) {
208
- setDismissedSlashPanel(false);
209
- }
210
- }, [dismissedSlashPanel, startsWithSlash]);
211
-
212
- useEffect(() => {
213
- if (!isSlashPanelOpen || isSlashPanelLoading || slashItems.length === 0) {
214
- return;
215
- }
216
- const container = slashListRef.current;
217
- if (!container) {
218
- return;
219
- }
220
- const active = container.querySelector<HTMLElement>(`[data-slash-index="${activeSlashIndex}"]`);
221
- active?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
222
- }, [activeSlashIndex, isSlashPanelLoading, isSlashPanelOpen, slashItems.length]);
223
-
224
- const handleSelectSlashItem = useCallback((item: ChatInputBarSlashItem) => {
225
- if (item.kind === 'skill' && item.skillSpec) {
226
- if (!params.selectedSkills.includes(item.skillSpec)) {
227
- params.onSelectedSkillsChange([...params.selectedSkills, item.skillSpec]);
228
- }
229
- params.onDraftChange('');
230
- setDismissedSlashPanel(false);
231
- }
232
- }, [params]);
233
-
234
- const onTextareaKeyDown = useCallback((event: KeyboardEvent<HTMLTextAreaElement>) => {
235
- if (isSlashPanelOpen && !event.nativeEvent.isComposing && (event.key === ' ' || event.code === 'Space')) {
236
- setDismissedSlashPanel(true);
237
- }
238
- if (isSlashPanelOpen && slashItems.length > 0) {
239
- if (event.key === 'ArrowDown') {
240
- event.preventDefault();
241
- setActiveSlashIndex((current) => (current + 1) % slashItems.length);
242
- return;
243
- }
244
- if (event.key === 'ArrowUp') {
245
- event.preventDefault();
246
- setActiveSlashIndex((current) => (current - 1 + slashItems.length) % slashItems.length);
247
- return;
248
- }
249
- if ((event.key === 'Enter' && !event.shiftKey) || event.key === 'Tab') {
250
- event.preventDefault();
251
- const selected = slashItems[activeSlashIndex];
252
- if (selected) {
253
- handleSelectSlashItem(selected);
254
- }
255
- return;
256
- }
257
- }
258
- if (event.key === 'Escape') {
259
- if (isSlashPanelOpen) {
260
- event.preventDefault();
261
- setDismissedSlashPanel(true);
262
- return;
263
- }
264
- if (params.isSending && params.canStopGeneration) {
265
- event.preventDefault();
266
- void params.onStop();
267
- return;
268
- }
269
- }
270
- if (event.key === 'Enter' && !event.shiftKey) {
271
- event.preventDefault();
272
- void params.onSend();
273
- }
274
- }, [activeSlashIndex, handleSelectSlashItem, isSlashPanelOpen, params, slashItems]);
275
-
276
- return {
277
- slashAnchorRef,
278
- slashListRef,
279
- isSlashPanelOpen,
280
- isSlashPanelLoading,
281
- resolvedSlashPanelWidth,
282
- skillSlashItems,
283
- activeSlashIndex,
284
- activeSlashItem,
285
- onSelectSlashItem: handleSelectSlashItem,
286
- onSlashPanelOpenChange: (open: boolean) => {
287
- if (!open) {
288
- setDismissedSlashPanel(true);
289
- }
290
- },
291
- onSetActiveSlashIndex: setActiveSlashIndex,
292
- onTextareaKeyDown
293
- };
294
- }
295
-
296
- export function useChatInputBarController(params: UseChatInputBarControllerParams) {
297
- const selectedSkillRecords = params.selectedSkills.map((spec) => {
298
- const matched = params.skillRecords.find((record) => record.spec === spec);
299
- return {
300
- spec,
301
- label: matched?.label || spec
302
- };
303
- });
304
-
305
- const slashController = useSlashPanelController({
306
- draft: params.draft,
307
- onDraftChange: params.onDraftChange,
308
- onSend: params.onSend,
309
- onStop: params.onStop,
310
- isSending: params.isSending,
311
- canStopGeneration: params.canStopGeneration,
312
- isSkillsLoading: params.isSkillsLoading,
313
- selectedSkills: params.selectedSkills,
314
- onSelectedSkillsChange: params.onSelectedSkillsChange,
315
- skillRecords: params.skillRecords
316
- });
317
-
318
- return {
319
- selectedSkillRecords,
320
- ...slashController
321
- };
322
- }