@nextclaw/ui 0.5.48 → 0.6.1

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 (57) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/assets/ChannelsList-CkCpHSto.js +1 -0
  3. package/dist/assets/ChatPage-DM4XNsrW.js +32 -0
  4. package/dist/assets/DocBrowser-B5Aqiz6W.js +1 -0
  5. package/dist/assets/MarketplacePage-BIi0bBdW.js +49 -0
  6. package/dist/assets/ModelConfig-BTFiEAxQ.js +1 -0
  7. package/dist/assets/{ProvidersList-BXHpjVtO.js → ProvidersList-cdk1d-G_.js} +1 -1
  8. package/dist/assets/RuntimeConfig-CFqFsXmR.js +1 -0
  9. package/dist/assets/{SecretsConfig-KkgMzdt1.js → SecretsConfig-CIKasCek.js} +2 -2
  10. package/dist/assets/SessionsConfig-mnCLFtbo.js +2 -0
  11. package/dist/assets/{card-D7NY0Szf.js → card-C1BUfR85.js} +1 -1
  12. package/dist/assets/index-Dxas8MJ9.js +2 -0
  13. package/dist/assets/index-P4YzN9iS.css +1 -0
  14. package/dist/assets/{label-Ojs7Al6B.js → label-CwWfYbuj.js} +1 -1
  15. package/dist/assets/{logos-B1qBsCSi.js → logos-DDyjHSEU.js} +1 -1
  16. package/dist/assets/{page-layout-CUMMO0nN.js → page-layout-DKTRKcHL.js} +1 -1
  17. package/dist/assets/provider-models-y4mUDcGF.js +1 -0
  18. package/dist/assets/{switch-BdhS_16-.js → switch-Bi3yeYiC.js} +1 -1
  19. package/dist/assets/{tabs-custom-D261E5EA.js → tabs-custom-HZFNZrc0.js} +1 -1
  20. package/dist/assets/useConfig-CgzVQTZl.js +6 -0
  21. package/dist/assets/{useConfirmDialog-BUKGHDL6.js → useConfirmDialog-DwD21HlD.js} +2 -2
  22. package/dist/assets/{vendor-Dh04PGww.js → vendor-Ylg6Wdt_.js} +84 -69
  23. package/dist/index.html +3 -3
  24. package/package.json +2 -1
  25. package/src/App.tsx +10 -6
  26. package/src/api/config.ts +42 -1
  27. package/src/api/types.ts +29 -0
  28. package/src/components/chat/ChatConversationPanel.tsx +109 -85
  29. package/src/components/chat/ChatInputBar.tsx +245 -0
  30. package/src/components/chat/ChatPage.tsx +365 -187
  31. package/src/components/chat/ChatSidebar.tsx +242 -0
  32. package/src/components/chat/ChatThread.tsx +92 -25
  33. package/src/components/chat/ChatWelcome.tsx +61 -0
  34. package/src/components/chat/SkillsPicker.tsx +137 -0
  35. package/src/components/chat/useChatStreamController.ts +287 -56
  36. package/src/components/config/ChannelForm.tsx +1 -1
  37. package/src/components/config/ChannelsList.tsx +3 -3
  38. package/src/components/config/ModelConfig.tsx +11 -89
  39. package/src/components/config/RuntimeConfig.tsx +29 -1
  40. package/src/components/layout/AppLayout.tsx +42 -6
  41. package/src/components/layout/Sidebar.tsx +68 -62
  42. package/src/components/marketplace/MarketplacePage.tsx +13 -3
  43. package/src/components/ui/popover.tsx +31 -0
  44. package/src/hooks/useConfig.ts +18 -0
  45. package/src/lib/i18n.ts +53 -0
  46. package/src/lib/provider-models.ts +129 -0
  47. package/dist/assets/ChannelsList-C8cguFLc.js +0 -1
  48. package/dist/assets/ChatPage-BkHWNUNR.js +0 -32
  49. package/dist/assets/CronConfig-D-ESQlvk.js +0 -1
  50. package/dist/assets/DocBrowser-B9ZD6pAk.js +0 -1
  51. package/dist/assets/MarketplacePage-Ds_l9KTF.js +0 -49
  52. package/dist/assets/ModelConfig-N1tbLv9b.js +0 -1
  53. package/dist/assets/RuntimeConfig-KsKfkjgv.js +0 -1
  54. package/dist/assets/SessionsConfig-CWBp8IPf.js +0 -2
  55. package/dist/assets/index-BRBYYgR_.js +0 -2
  56. package/dist/assets/index-C5cdRzpO.css +0 -1
  57. package/dist/assets/useConfig-txxbxXnT.js +0 -6
@@ -0,0 +1,245 @@
1
+ import { Button } from '@/components/ui/button';
2
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
3
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
4
+ import { SkillsPicker } from '@/components/chat/SkillsPicker';
5
+ import type { MarketplaceInstalledRecord } from '@/api/types';
6
+ import { t } from '@/lib/i18n';
7
+ import { Paperclip, Send, Sparkles, Square, X } from 'lucide-react';
8
+
9
+ export type ChatModelOption = {
10
+ value: string;
11
+ modelLabel: string;
12
+ providerLabel: string;
13
+ };
14
+
15
+ type ChatInputBarProps = {
16
+ draft: string;
17
+ onDraftChange: (value: string) => void;
18
+ onSend: () => Promise<void> | void;
19
+ onStop: () => Promise<void> | void;
20
+ onGoToProviders: () => void;
21
+ canStopGeneration: boolean;
22
+ stopDisabledReason?: string | null;
23
+ sendError?: string | null;
24
+ isSending: boolean;
25
+ queuedCount: number;
26
+ modelOptions: ChatModelOption[];
27
+ selectedModel: string;
28
+ onSelectedModelChange: (value: string) => void;
29
+ skillRecords: MarketplaceInstalledRecord[];
30
+ isSkillsLoading?: boolean;
31
+ selectedSkills: string[];
32
+ onSelectedSkillsChange: (next: string[]) => void;
33
+ };
34
+
35
+ export function ChatInputBar({
36
+ draft,
37
+ onDraftChange,
38
+ onSend,
39
+ onStop,
40
+ onGoToProviders,
41
+ canStopGeneration,
42
+ stopDisabledReason = null,
43
+ sendError = null,
44
+ isSending,
45
+ queuedCount,
46
+ modelOptions,
47
+ selectedModel,
48
+ onSelectedModelChange,
49
+ skillRecords,
50
+ isSkillsLoading = false,
51
+ selectedSkills,
52
+ onSelectedSkillsChange
53
+ }: ChatInputBarProps) {
54
+ const hasModelOptions = modelOptions.length > 0;
55
+ const inputDisabled = !hasModelOptions && !isSending;
56
+ const selectedModelOption = modelOptions.find((option) => option.value === selectedModel);
57
+ const resolvedStopHint =
58
+ stopDisabledReason === '__preparing__'
59
+ ? t('chatStopPreparing')
60
+ : stopDisabledReason?.trim() || t('chatStopUnavailable');
61
+ const selectedSkillRecords = selectedSkills.map((spec) => {
62
+ const matched = skillRecords.find((record) => record.spec === spec);
63
+ return {
64
+ spec,
65
+ label: matched?.label || spec
66
+ };
67
+ });
68
+
69
+ return (
70
+ <div className="border-t border-gray-200/80 bg-white p-4">
71
+ <div className="mx-auto w-full max-w-[min(1120px,100%)]">
72
+ <div className="rounded-2xl border border-gray-200 bg-white shadow-card overflow-hidden">
73
+ {/* Textarea */}
74
+ <textarea
75
+ value={draft}
76
+ onChange={(e) => onDraftChange(e.target.value)}
77
+ disabled={inputDisabled}
78
+ onKeyDown={(e) => {
79
+ if (e.key === 'Escape' && isSending && canStopGeneration) {
80
+ e.preventDefault();
81
+ void onStop();
82
+ return;
83
+ }
84
+ if (e.key === 'Enter' && !e.shiftKey) {
85
+ e.preventDefault();
86
+ void onSend();
87
+ }
88
+ }}
89
+ placeholder={hasModelOptions ? t('chatInputPlaceholder') : t('chatModelNoOptions')}
90
+ className="w-full min-h-[68px] max-h-[220px] resize-y bg-transparent outline-none text-sm px-4 py-3 text-gray-800 placeholder:text-gray-400"
91
+ />
92
+ {!hasModelOptions && (
93
+ <div className="px-4 pb-2">
94
+ <div className="inline-flex items-center gap-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-1.5 text-xs text-amber-800">
95
+ <span>{t('chatModelNoOptions')}</span>
96
+ <button
97
+ type="button"
98
+ onClick={onGoToProviders}
99
+ className="font-semibold text-amber-900 underline-offset-2 hover:underline"
100
+ >
101
+ {t('chatGoConfigureProvider')}
102
+ </button>
103
+ </div>
104
+ </div>
105
+ )}
106
+ {selectedSkillRecords.length > 0 && (
107
+ <div className="px-4 pb-2">
108
+ <div className="flex flex-wrap items-center gap-2">
109
+ {selectedSkillRecords.map((record) => (
110
+ <button
111
+ key={record.spec}
112
+ type="button"
113
+ onClick={() => onSelectedSkillsChange(selectedSkills.filter((skill) => skill !== record.spec))}
114
+ className="inline-flex max-w-[200px] items-center gap-1.5 rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary"
115
+ >
116
+ <span className="truncate">{record.label}</span>
117
+ <X className="h-3 w-3 shrink-0" />
118
+ </button>
119
+ ))}
120
+ </div>
121
+ </div>
122
+ )}
123
+
124
+ {/* Toolbar */}
125
+ <div className="flex items-center justify-between px-3 pb-3">
126
+ {/* Left group */}
127
+ <div className="flex items-center gap-1">
128
+ {/* Skills picker */}
129
+ <SkillsPicker
130
+ records={skillRecords}
131
+ isLoading={isSkillsLoading}
132
+ selectedSkills={selectedSkills}
133
+ onSelectedSkillsChange={onSelectedSkillsChange}
134
+ />
135
+
136
+ {/* Model selector */}
137
+ <Select
138
+ value={hasModelOptions ? selectedModel : undefined}
139
+ onValueChange={onSelectedModelChange}
140
+ disabled={!hasModelOptions}
141
+ >
142
+ <SelectTrigger className="h-8 w-auto min-w-[220px] rounded-lg border-0 bg-transparent shadow-none text-xs font-medium text-gray-600 hover:bg-gray-100 focus:ring-0 px-3">
143
+ {selectedModelOption ? (
144
+ <div className="flex min-w-0 items-center gap-2 text-left">
145
+ <Sparkles className="h-3.5 w-3.5 shrink-0 text-primary" />
146
+ <span className="truncate text-xs font-semibold text-gray-700">
147
+ {selectedModelOption.providerLabel}/{selectedModelOption.modelLabel}
148
+ </span>
149
+ </div>
150
+ ) : (
151
+ <SelectValue placeholder={t('chatSelectModel')} />
152
+ )}
153
+ </SelectTrigger>
154
+ <SelectContent className="w-[320px]">
155
+ {modelOptions.length === 0 && (
156
+ <div className="px-3 py-2 text-xs text-gray-500">{t('chatModelNoOptions')}</div>
157
+ )}
158
+ {modelOptions.map((option) => (
159
+ <SelectItem key={option.value} value={option.value} className="py-2">
160
+ <div className="flex min-w-0 flex-col gap-0.5">
161
+ <span className="truncate text-xs font-semibold text-gray-800">{option.modelLabel}</span>
162
+ <span className="truncate text-[11px] text-gray-500">{option.providerLabel}</span>
163
+ </div>
164
+ </SelectItem>
165
+ ))}
166
+ </SelectContent>
167
+ </Select>
168
+
169
+ {/* Attachment button (placeholder) */}
170
+ <TooltipProvider>
171
+ <Tooltip>
172
+ <TooltipTrigger asChild>
173
+ <button
174
+ type="button"
175
+ disabled
176
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-gray-400 cursor-not-allowed"
177
+ >
178
+ <Paperclip className="h-4 w-4" />
179
+ </button>
180
+ </TooltipTrigger>
181
+ <TooltipContent side="top">
182
+ <p className="text-xs">{t('chatInputAttachComingSoon')}</p>
183
+ </TooltipContent>
184
+ </Tooltip>
185
+ </TooltipProvider>
186
+ </div>
187
+
188
+ {/* Right group */}
189
+ <div className="flex flex-col items-end gap-1">
190
+ {sendError?.trim() && (
191
+ <div className="max-w-[420px] text-right text-[11px] text-red-600">{sendError}</div>
192
+ )}
193
+ <div className="flex items-center gap-2">
194
+ {isSending && queuedCount > 0 && (
195
+ <span className="text-[11px] text-gray-400">
196
+ {t('chatQueuedHintPrefix')} {queuedCount} {t('chatQueuedHintSuffix')}
197
+ </span>
198
+ )}
199
+ {isSending ? (
200
+ canStopGeneration ? (
201
+ <Button
202
+ size="sm"
203
+ variant="destructive"
204
+ className="rounded-lg"
205
+ onClick={() => void onStop()}
206
+ >
207
+ <Square className="h-3.5 w-3.5 mr-1.5" />
208
+ {t('chatStop')}
209
+ </Button>
210
+ ) : (
211
+ <TooltipProvider>
212
+ <Tooltip>
213
+ <TooltipTrigger asChild>
214
+ <span>
215
+ <Button size="sm" className="rounded-lg" disabled>
216
+ <Square className="h-3.5 w-3.5 mr-1.5" />
217
+ {t('chatStop')}
218
+ </Button>
219
+ </span>
220
+ </TooltipTrigger>
221
+ <TooltipContent side="top">
222
+ <p className="text-xs">{resolvedStopHint}</p>
223
+ </TooltipContent>
224
+ </Tooltip>
225
+ </TooltipProvider>
226
+ )
227
+ ) : (
228
+ <Button
229
+ size="sm"
230
+ className="rounded-lg"
231
+ onClick={() => void onSend()}
232
+ disabled={draft.trim().length === 0 || !hasModelOptions}
233
+ >
234
+ <Send className="h-3.5 w-3.5 mr-1.5" />
235
+ {t('chatSend')}
236
+ </Button>
237
+ )}
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ );
245
+ }