@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.
- package/CHANGELOG.md +16 -2
- package/dist/assets/ChannelsList-CIMYaIji.js +1 -0
- package/dist/assets/{ChatPage-DM1ewbWf.js → ChatPage-B5UpeEIp.js} +2 -2
- package/dist/assets/{DocBrowser-BLv77lJ0.js → DocBrowser-BJ610SPa.js} +1 -1
- package/dist/assets/{LogoBadge-D7j1al-w.js → LogoBadge-BKq1GKWP.js} +1 -1
- package/dist/assets/MarketplacePage-Bs3sLsgx.js +49 -0
- package/dist/assets/{McpMarketplacePage-DpMjaD3m.js → McpMarketplacePage-BWTguHCs.js} +2 -2
- package/dist/assets/ModelConfig-B-oTP-Bc.js +1 -0
- package/dist/assets/ProvidersList-r7bD0-R0.js +1 -0
- package/dist/assets/RemoteAccessPage-D7On6waK.js +1 -0
- package/dist/assets/{RuntimeConfig-BbX4yFKy.js → RuntimeConfig-C11xVxH9.js} +1 -1
- package/dist/assets/{SearchConfig-BmmmeyJd.js → SearchConfig-BVZdCxiM.js} +1 -1
- package/dist/assets/{SecretsConfig-CWG8J01H.js → SecretsConfig-DuEDdC3X.js} +2 -2
- package/dist/assets/SessionsConfig-Y-Blf_-K.js +2 -0
- package/dist/assets/{chat-message-CGXiVhyN.js → chat-message-B6VCCEXF.js} +1 -1
- package/dist/assets/index-DfEAJJsA.css +1 -0
- package/dist/assets/index-DvA7S11O.js +8 -0
- package/dist/assets/{label-CCSffS1D.js → label-DzwitL78.js} +1 -1
- package/dist/assets/{page-layout-ud8wZ8gX.js → page-layout-DEq5N_8L.js} +1 -1
- package/dist/assets/popover-CY54V8F6.js +1 -0
- package/dist/assets/provider-models-BOeNnjk9.js +1 -0
- package/dist/assets/{security-config-DJJUCMov.js → security-config-CgbYP57d.js} +1 -1
- package/dist/assets/skeleton-zjQZMWu9.js +1 -0
- package/dist/assets/{status-dot-Fz9-eKsl.js → status-dot-CU_P0tvO.js} +1 -1
- package/dist/assets/{switch-B-_SrMSL.js → switch-PvjTvlcs.js} +1 -1
- package/dist/assets/{tabs-custom-6Tm1ZHfS.js → tabs-custom-Bke5J9ny.js} +1 -1
- package/dist/assets/useConfirmDialog-8tzzp_oW.js +1 -0
- package/dist/assets/vendor-CmQZsDAE.js +436 -0
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/App.tsx +36 -39
- package/src/account/components/account-panel.tsx +93 -0
- package/src/account/managers/account.manager.ts +179 -0
- package/src/account/stores/account.store.ts +68 -0
- package/src/api/types.ts +2 -0
- package/src/app-query-client.ts +10 -0
- package/src/components/config/ProviderForm.tsx +91 -641
- package/src/components/config/ProvidersList.tsx +10 -5
- package/src/components/config/provider-advanced-settings-section.tsx +92 -0
- package/src/components/config/provider-auth-section.tsx +113 -0
- package/src/components/config/provider-enabled-field.tsx +20 -0
- package/src/components/config/provider-form-support.ts +344 -0
- package/src/components/config/provider-models-section.tsx +198 -0
- package/src/components/config/provider-pill-selector.tsx +39 -0
- package/src/components/config/provider-status-badge.tsx +21 -0
- package/src/components/layout/Sidebar.tsx +26 -0
- package/src/components/remote/RemoteAccessPage.tsx +162 -442
- package/src/hooks/useRemoteAccess.ts +7 -6
- package/src/lib/i18n.remote.ts +108 -4
- package/src/lib/provider-models.ts +2 -2
- package/src/presenter/app-presenter-context.tsx +20 -0
- package/src/presenter/app.presenter.ts +12 -0
- package/src/remote/managers/remote-access.manager.ts +196 -0
- package/src/remote/remote-access.query.ts +78 -0
- package/src/remote/stores/remote-access.store.ts +44 -0
- package/dist/assets/ChannelsList-Byfj2R01.js +0 -1
- package/dist/assets/MarketplacePage-DuskLKYh.js +0 -49
- package/dist/assets/ModelConfig-ubaecweS.js +0 -1
- package/dist/assets/ProvidersList-w8MJH2LI.js +0 -1
- package/dist/assets/RemoteAccessPage-D79_5Kbn.js +0 -1
- package/dist/assets/SessionsConfig-D-vg_Lgv.js +0 -2
- package/dist/assets/index-COrhpAdh.css +0 -1
- package/dist/assets/index-CeRbsQ90.js +0 -8
- package/dist/assets/index-Ct7FQpxN.js +0 -1
- package/dist/assets/popover-Bfoe6YBX.js +0 -1
- package/dist/assets/provider-models-D3B_xWXx.js +0 -1
- package/dist/assets/skeleton-IOOTmHzP.js +0 -1
- package/dist/assets/useConfirmDialog-BeOW2bOI.js +0 -5
- 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
|