@nextclaw/ui 0.9.6 → 0.9.8

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 (69) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/dist/assets/ChannelsList-CIMYaIji.js +1 -0
  3. package/dist/assets/{ChatPage-DM1ewbWf.js → ChatPage-B5UpeEIp.js} +2 -2
  4. package/dist/assets/{DocBrowser-BLv77lJ0.js → DocBrowser-BJ610SPa.js} +1 -1
  5. package/dist/assets/{LogoBadge-D7j1al-w.js → LogoBadge-BKq1GKWP.js} +1 -1
  6. package/dist/assets/MarketplacePage-Bs3sLsgx.js +49 -0
  7. package/dist/assets/{McpMarketplacePage-DpMjaD3m.js → McpMarketplacePage-BWTguHCs.js} +2 -2
  8. package/dist/assets/ModelConfig-B-oTP-Bc.js +1 -0
  9. package/dist/assets/ProvidersList-r7bD0-R0.js +1 -0
  10. package/dist/assets/RemoteAccessPage-D7On6waK.js +1 -0
  11. package/dist/assets/{RuntimeConfig-BbX4yFKy.js → RuntimeConfig-C11xVxH9.js} +1 -1
  12. package/dist/assets/{SearchConfig-BmmmeyJd.js → SearchConfig-BVZdCxiM.js} +1 -1
  13. package/dist/assets/{SecretsConfig-CWG8J01H.js → SecretsConfig-DuEDdC3X.js} +2 -2
  14. package/dist/assets/SessionsConfig-Y-Blf_-K.js +2 -0
  15. package/dist/assets/{chat-message-CGXiVhyN.js → chat-message-B6VCCEXF.js} +1 -1
  16. package/dist/assets/index-DfEAJJsA.css +1 -0
  17. package/dist/assets/index-DvA7S11O.js +8 -0
  18. package/dist/assets/{label-CCSffS1D.js → label-DzwitL78.js} +1 -1
  19. package/dist/assets/{page-layout-ud8wZ8gX.js → page-layout-DEq5N_8L.js} +1 -1
  20. package/dist/assets/popover-CY54V8F6.js +1 -0
  21. package/dist/assets/provider-models-BOeNnjk9.js +1 -0
  22. package/dist/assets/{security-config-DJJUCMov.js → security-config-CgbYP57d.js} +1 -1
  23. package/dist/assets/skeleton-zjQZMWu9.js +1 -0
  24. package/dist/assets/{status-dot-Fz9-eKsl.js → status-dot-CU_P0tvO.js} +1 -1
  25. package/dist/assets/{switch-B-_SrMSL.js → switch-PvjTvlcs.js} +1 -1
  26. package/dist/assets/{tabs-custom-6Tm1ZHfS.js → tabs-custom-Bke5J9ny.js} +1 -1
  27. package/dist/assets/useConfirmDialog-8tzzp_oW.js +1 -0
  28. package/dist/assets/vendor-CmQZsDAE.js +436 -0
  29. package/dist/index.html +3 -3
  30. package/package.json +4 -4
  31. package/src/App.tsx +36 -39
  32. package/src/account/components/account-panel.tsx +93 -0
  33. package/src/account/managers/account.manager.ts +179 -0
  34. package/src/account/stores/account.store.ts +68 -0
  35. package/src/api/types.ts +2 -0
  36. package/src/app-query-client.ts +10 -0
  37. package/src/components/config/ProviderForm.tsx +91 -641
  38. package/src/components/config/ProvidersList.tsx +10 -5
  39. package/src/components/config/provider-advanced-settings-section.tsx +92 -0
  40. package/src/components/config/provider-auth-section.tsx +113 -0
  41. package/src/components/config/provider-enabled-field.tsx +20 -0
  42. package/src/components/config/provider-form-support.ts +344 -0
  43. package/src/components/config/provider-models-section.tsx +198 -0
  44. package/src/components/config/provider-pill-selector.tsx +39 -0
  45. package/src/components/config/provider-status-badge.tsx +21 -0
  46. package/src/components/layout/Sidebar.tsx +26 -0
  47. package/src/components/remote/RemoteAccessPage.tsx +162 -442
  48. package/src/hooks/useRemoteAccess.ts +7 -6
  49. package/src/lib/i18n.remote.ts +108 -4
  50. package/src/lib/provider-models.ts +2 -2
  51. package/src/presenter/app-presenter-context.tsx +20 -0
  52. package/src/presenter/app.presenter.ts +12 -0
  53. package/src/remote/managers/remote-access.manager.ts +196 -0
  54. package/src/remote/remote-access.query.ts +78 -0
  55. package/src/remote/stores/remote-access.store.ts +44 -0
  56. package/dist/assets/ChannelsList-Byfj2R01.js +0 -1
  57. package/dist/assets/MarketplacePage-DuskLKYh.js +0 -49
  58. package/dist/assets/ModelConfig-ubaecweS.js +0 -1
  59. package/dist/assets/ProvidersList-w8MJH2LI.js +0 -1
  60. package/dist/assets/RemoteAccessPage-D79_5Kbn.js +0 -1
  61. package/dist/assets/SessionsConfig-D-vg_Lgv.js +0 -2
  62. package/dist/assets/index-COrhpAdh.css +0 -1
  63. package/dist/assets/index-CeRbsQ90.js +0 -8
  64. package/dist/assets/index-Ct7FQpxN.js +0 -1
  65. package/dist/assets/popover-Bfoe6YBX.js +0 -1
  66. package/dist/assets/provider-models-D3B_xWXx.js +0 -1
  67. package/dist/assets/skeleton-IOOTmHzP.js +0 -1
  68. package/dist/assets/useConfirmDialog-BeOW2bOI.js +0 -5
  69. package/dist/assets/vendor-CwsIoNvJ.js +0 -442
@@ -0,0 +1,198 @@
1
+ import { Button } from '@/components/ui/button';
2
+ import { Input } from '@/components/ui/input';
3
+ import { Label } from '@/components/ui/label';
4
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
6
+ import { t } from '@/lib/i18n';
7
+ import type { ThinkingLevel } from '@/api/types';
8
+ import { Plus, Settings2, X } from 'lucide-react';
9
+
10
+ type ModelThinkingConfig = Record<string, { supported: ThinkingLevel[]; default?: ThinkingLevel | null }>;
11
+
12
+ type ProviderModelsSectionProps = {
13
+ models: string[];
14
+ modelThinking: ModelThinkingConfig;
15
+ modelDraft: string;
16
+ showModelInput: boolean;
17
+ onModelDraftChange: (value: string) => void;
18
+ onShowModelInputChange: (value: boolean) => void;
19
+ onAddModel: () => void;
20
+ onRemoveModel: (modelName: string) => void;
21
+ onToggleModelThinkingLevel: (modelName: string, level: ThinkingLevel) => void;
22
+ onSetModelThinkingDefault: (modelName: string, level: ThinkingLevel | null) => void;
23
+ thinkingLevels: ThinkingLevel[];
24
+ formatThinkingLevelLabel: (level: ThinkingLevel) => string;
25
+ };
26
+
27
+ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
28
+ const {
29
+ models,
30
+ modelThinking,
31
+ modelDraft,
32
+ showModelInput,
33
+ onModelDraftChange,
34
+ onShowModelInputChange,
35
+ onAddModel,
36
+ onRemoveModel,
37
+ onToggleModelThinkingLevel,
38
+ onSetModelThinkingDefault,
39
+ thinkingLevels,
40
+ formatThinkingLevelLabel
41
+ } = props;
42
+
43
+ return (
44
+ <div className="space-y-2">
45
+ <div className="flex items-center justify-between">
46
+ <Label className="text-sm font-medium text-gray-900">{t('providerModelsTitle')}</Label>
47
+ {!showModelInput ? (
48
+ <button
49
+ type="button"
50
+ onClick={() => onShowModelInputChange(true)}
51
+ className="flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80"
52
+ >
53
+ <Plus className="h-3 w-3" />
54
+ {t('providerAddModel')}
55
+ </button>
56
+ ) : null}
57
+ </div>
58
+
59
+ {showModelInput ? (
60
+ <div className="flex items-center gap-2">
61
+ <Input
62
+ value={modelDraft}
63
+ onChange={(event) => onModelDraftChange(event.target.value)}
64
+ onKeyDown={(event) => {
65
+ if (event.key === 'Enter') {
66
+ event.preventDefault();
67
+ onAddModel();
68
+ }
69
+ if (event.key === 'Escape') {
70
+ onShowModelInputChange(false);
71
+ onModelDraftChange('');
72
+ }
73
+ }}
74
+ placeholder={t('providerModelInputPlaceholder')}
75
+ className="flex-1 rounded-xl"
76
+ autoFocus
77
+ />
78
+ <Button type="button" size="sm" onClick={onAddModel} disabled={modelDraft.trim().length === 0}>
79
+ {t('add')}
80
+ </Button>
81
+ <Button
82
+ type="button"
83
+ size="sm"
84
+ variant="ghost"
85
+ onClick={() => {
86
+ onShowModelInputChange(false);
87
+ onModelDraftChange('');
88
+ }}
89
+ >
90
+ <X className="h-4 w-4" />
91
+ </Button>
92
+ </div>
93
+ ) : null}
94
+
95
+ {models.length === 0 ? (
96
+ <div className="rounded-xl border border-dashed border-gray-200 bg-gray-50 px-4 py-6 text-center">
97
+ <p className="text-sm text-gray-500">{t('providerModelsEmptyShort')}</p>
98
+ {!showModelInput ? (
99
+ <button
100
+ type="button"
101
+ onClick={() => onShowModelInputChange(true)}
102
+ className="mt-2 text-sm font-medium text-primary hover:text-primary/80"
103
+ >
104
+ {t('providerAddFirstModel')}
105
+ </button>
106
+ ) : null}
107
+ </div>
108
+ ) : (
109
+ <div className="flex flex-wrap gap-2">
110
+ {models.map((modelName) => {
111
+ const thinkingEntry = modelThinking[modelName];
112
+ const supportedLevels = thinkingEntry?.supported ?? [];
113
+ const defaultThinkingLevel = thinkingEntry?.default ?? null;
114
+
115
+ return (
116
+ <div
117
+ key={modelName}
118
+ className="group inline-flex max-w-full items-center gap-1 rounded-full border border-gray-200 bg-white px-3 py-1.5"
119
+ >
120
+ <span className="max-w-[140px] truncate text-sm text-gray-800 sm:max-w-[220px]">{modelName}</span>
121
+ <Popover>
122
+ <PopoverTrigger asChild>
123
+ <button
124
+ type="button"
125
+ className="inline-flex h-5 w-5 items-center justify-center rounded-full text-gray-400 opacity-100 transition-opacity hover:bg-gray-100 hover:text-gray-600 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
126
+ aria-label={t('providerModelThinkingTitle')}
127
+ title={t('providerModelThinkingTitle')}
128
+ >
129
+ <Settings2 className="h-3 w-3" />
130
+ </button>
131
+ </PopoverTrigger>
132
+ <PopoverContent className="w-80 space-y-3">
133
+ <div className="space-y-1">
134
+ <p className="text-xs font-semibold text-gray-800">{t('providerModelThinkingTitle')}</p>
135
+ <p className="text-xs text-gray-500">{t('providerModelThinkingHint')}</p>
136
+ </div>
137
+ <div className="flex flex-wrap gap-1.5">
138
+ {thinkingLevels.map((level) => {
139
+ const selected = supportedLevels.includes(level);
140
+ return (
141
+ <button
142
+ key={level}
143
+ type="button"
144
+ onClick={() => onToggleModelThinkingLevel(modelName, level)}
145
+ className={`rounded-full border px-2.5 py-1 text-xs font-medium transition-colors ${
146
+ selected
147
+ ? 'border-primary bg-primary text-white'
148
+ : 'border-gray-200 bg-white text-gray-600 hover:border-primary/40 hover:text-primary'
149
+ }`}
150
+ >
151
+ {formatThinkingLevelLabel(level)}
152
+ </button>
153
+ );
154
+ })}
155
+ </div>
156
+ <div className="space-y-1.5">
157
+ <Label className="text-xs font-medium text-gray-700">{t('providerModelThinkingDefault')}</Label>
158
+ <Select
159
+ value={defaultThinkingLevel ?? '__none__'}
160
+ onValueChange={(value) =>
161
+ onSetModelThinkingDefault(modelName, value === '__none__' ? null : (value as ThinkingLevel))
162
+ }
163
+ disabled={supportedLevels.length === 0}
164
+ >
165
+ <SelectTrigger className="h-8 rounded-lg bg-white text-xs">
166
+ <SelectValue />
167
+ </SelectTrigger>
168
+ <SelectContent>
169
+ <SelectItem value="__none__">{t('providerModelThinkingDefaultNone')}</SelectItem>
170
+ {supportedLevels.map((level) => (
171
+ <SelectItem key={level} value={level}>
172
+ {formatThinkingLevelLabel(level)}
173
+ </SelectItem>
174
+ ))}
175
+ </SelectContent>
176
+ </Select>
177
+ {supportedLevels.length === 0 ? (
178
+ <p className="text-xs text-gray-500">{t('providerModelThinkingNoSupported')}</p>
179
+ ) : null}
180
+ </div>
181
+ </PopoverContent>
182
+ </Popover>
183
+ <button
184
+ type="button"
185
+ onClick={() => onRemoveModel(modelName)}
186
+ className="inline-flex h-5 w-5 items-center justify-center rounded-full text-gray-400 opacity-100 transition-opacity hover:bg-gray-100 hover:text-gray-600 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
187
+ aria-label={t('remove')}
188
+ >
189
+ <X className="h-3 w-3" />
190
+ </button>
191
+ </div>
192
+ );
193
+ })}
194
+ </div>
195
+ )}
196
+ </div>
197
+ );
198
+ }
@@ -0,0 +1,39 @@
1
+ type PillSelectOption = {
2
+ value: string;
3
+ label: string;
4
+ };
5
+
6
+ type ProviderPillSelectorProps = {
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ options: PillSelectOption[];
10
+ };
11
+
12
+ export type { PillSelectOption };
13
+
14
+ export function ProviderPillSelector(props: ProviderPillSelectorProps) {
15
+ const { value, onChange, options } = props;
16
+
17
+ return (
18
+ <div className="flex flex-wrap gap-2">
19
+ {options.map((option) => {
20
+ const selected = option.value === value;
21
+ return (
22
+ <button
23
+ key={option.value}
24
+ type="button"
25
+ onClick={() => onChange(option.value)}
26
+ aria-pressed={selected}
27
+ className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
28
+ selected
29
+ ? 'border-primary bg-primary text-white shadow-sm'
30
+ : 'border-gray-200 bg-white text-gray-700 hover:border-primary/40 hover:text-primary'
31
+ }`}
32
+ >
33
+ {option.label}
34
+ </button>
35
+ );
36
+ })}
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,21 @@
1
+ import { StatusDot } from '@/components/ui/status-dot';
2
+ import { t } from '@/lib/i18n';
3
+
4
+ type ProviderStatusBadgeProps = {
5
+ enabled: boolean;
6
+ apiKeySet: boolean;
7
+ className?: string;
8
+ };
9
+
10
+ export function ProviderStatusBadge(props: ProviderStatusBadgeProps) {
11
+ if (!props.enabled) {
12
+ return <StatusDot status="inactive" label={t('disabled')} className={props.className} />;
13
+ }
14
+ return (
15
+ <StatusDot
16
+ status={props.apiKeySet ? 'ready' : 'setup'}
17
+ label={props.apiKeySet ? t('statusReady') : t('statusSetup')}
18
+ className={props.className}
19
+ />
20
+ );
21
+ }
@@ -8,6 +8,8 @@ import { BrandHeader } from '@/components/common/BrandHeader';
8
8
  import { useI18n } from '@/components/providers/I18nProvider';
9
9
  import { useTheme } from '@/components/providers/ThemeProvider';
10
10
  import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select';
11
+ import { useRemoteStatus } from '@/hooks/useRemoteAccess';
12
+ import { useAppPresenter } from '@/presenter/app-presenter-context';
11
13
 
12
14
  type SidebarMode = 'main' | 'settings';
13
15
 
@@ -16,11 +18,15 @@ type SidebarProps = {
16
18
  };
17
19
 
18
20
  export function Sidebar({ mode }: SidebarProps) {
21
+ const presenter = useAppPresenter();
19
22
  const docBrowser = useDocBrowser();
23
+ const remoteStatus = useRemoteStatus();
20
24
  const { language, setLanguage } = useI18n();
21
25
  const { theme, setTheme } = useTheme();
22
26
  const currentLanguageLabel = LANGUAGE_OPTIONS.find((option) => option.value === language)?.label ?? language;
23
27
  const currentThemeLabel = t(THEME_OPTIONS.find((option) => option.value === theme)?.labelKey ?? 'themeWarm');
28
+ const accountEmail = remoteStatus.data?.account.email?.trim();
29
+ const accountConnected = Boolean(remoteStatus.data?.account.loggedIn);
24
30
 
25
31
  const handleLanguageSwitch = (nextLanguage: I18nLanguage) => {
26
32
  if (language === nextLanguage) {
@@ -174,6 +180,26 @@ export function Sidebar({ mode }: SidebarProps) {
174
180
 
175
181
  {/* Help Button */}
176
182
  <div className="pt-3 border-t border-[#dde0ea] mt-3">
183
+ {mode === 'settings' ? (
184
+ <button
185
+ onClick={() => presenter.accountManager.openAccountPanel()}
186
+ className="mb-2 w-full rounded-xl px-3 py-2.5 text-left transition-all duration-base text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-900"
187
+ >
188
+ <div className="flex items-start gap-3">
189
+ <KeyRound className={cn('mt-0.5 h-[17px] w-[17px]', accountConnected ? 'text-emerald-600' : 'text-gray-400')} />
190
+ <div className="min-w-0 flex-1">
191
+ <div className="flex items-center justify-between gap-3">
192
+ <p className="truncate text-[14px] font-medium text-gray-900">
193
+ {accountEmail || t('remoteAccountEntryManage')}
194
+ </p>
195
+ </div>
196
+ <p className="mt-1 truncate text-xs text-gray-500">
197
+ {accountConnected ? t('remoteAccountEntryConnected') : t('remoteAccountEntryDisconnected')}
198
+ </p>
199
+ </div>
200
+ </div>
201
+ </button>
202
+ ) : null}
177
203
  {mode === 'main' && (
178
204
  <div className="mb-2">
179
205
  <NavLink