@nextclaw/ui 0.5.9 → 0.5.10
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 +6 -0
- package/dist/assets/index-BYV0BsFw.js +342 -0
- package/dist/assets/index-DrC_vHy_.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/common/MaskedInput.tsx +1 -1
- package/src/components/common/StatusBadge.tsx +3 -5
- package/src/components/common/TagInput.tsx +3 -2
- package/src/components/config/ChannelForm.tsx +106 -104
- package/src/components/config/ChannelsList.tsx +19 -19
- package/src/components/config/CronConfig.tsx +3 -14
- package/src/components/config/ModelConfig.tsx +9 -8
- package/src/components/config/ProviderForm.tsx +3 -3
- package/src/components/config/ProvidersList.tsx +10 -9
- package/src/components/config/RuntimeConfig.tsx +38 -37
- package/src/components/config/SessionsConfig.tsx +8 -15
- package/src/components/layout/Sidebar.tsx +70 -40
- package/src/components/marketplace/MarketplacePage.tsx +32 -31
- package/src/components/providers/I18nProvider.tsx +64 -0
- package/src/components/ui/confirm-dialog.tsx +3 -2
- package/src/components/ui/tabs-custom.tsx +2 -1
- package/src/hooks/useConfirmDialog.tsx +5 -4
- package/src/hooks/useMarketplace.ts +4 -3
- package/src/lib/i18n.ts +267 -5
- package/src/main.tsx +6 -3
- package/dist/assets/index-BtwwwWcv.css +0 -1
- package/dist/assets/index-STUSj6p9.js +0 -337
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useMarketplaceInstalled,
|
|
11
11
|
useMarketplaceItems
|
|
12
12
|
} from '@/hooks/useMarketplace';
|
|
13
|
+
import { t } from '@/lib/i18n';
|
|
13
14
|
import { cn } from '@/lib/utils';
|
|
14
15
|
import { PackageSearch } from 'lucide-react';
|
|
15
16
|
import { useEffect, useMemo, useState } from 'react';
|
|
@@ -180,16 +181,16 @@ function FilterPanel(props: {
|
|
|
180
181
|
<input
|
|
181
182
|
value={props.searchText}
|
|
182
183
|
onChange={(event) => props.onSearchTextChange(event.target.value)}
|
|
183
|
-
placeholder=
|
|
184
|
+
placeholder={t('marketplaceSearchPlaceholder')}
|
|
184
185
|
className="w-full h-9 border border-gray-200/80 rounded-xl pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40"
|
|
185
186
|
/>
|
|
186
187
|
</div>
|
|
187
188
|
|
|
188
189
|
<div className="inline-flex h-9 rounded-xl bg-gray-100/80 p-1 shrink-0">
|
|
189
190
|
{([
|
|
190
|
-
{ value: 'all', label: '
|
|
191
|
-
{ value: 'plugin', label: '
|
|
192
|
-
{ value: 'skill', label: '
|
|
191
|
+
{ value: 'all', label: t('marketplaceFilterAll') },
|
|
192
|
+
{ value: 'plugin', label: t('marketplaceFilterPlugins') },
|
|
193
|
+
{ value: 'skill', label: t('marketplaceFilterSkills') },
|
|
193
194
|
] as const).map((opt) => (
|
|
194
195
|
<button
|
|
195
196
|
key={opt.value}
|
|
@@ -213,8 +214,8 @@ function FilterPanel(props: {
|
|
|
213
214
|
<SelectValue />
|
|
214
215
|
</SelectTrigger>
|
|
215
216
|
<SelectContent>
|
|
216
|
-
<SelectItem value="relevance">
|
|
217
|
-
<SelectItem value="updated">
|
|
217
|
+
<SelectItem value="relevance">{t('marketplaceSortRelevance')}</SelectItem>
|
|
218
|
+
<SelectItem value="updated">{t('marketplaceSortUpdated')}</SelectItem>
|
|
218
219
|
</SelectContent>
|
|
219
220
|
</Select>
|
|
220
221
|
)}
|
|
@@ -234,8 +235,8 @@ function MarketplaceListCard(props: {
|
|
|
234
235
|
const record = props.record;
|
|
235
236
|
const pluginRecord = record?.type === 'plugin' ? record : undefined;
|
|
236
237
|
const type = props.item?.type ?? record?.type;
|
|
237
|
-
const title = props.item?.name ?? record?.label ?? record?.id ?? record?.spec ?? '
|
|
238
|
-
const summary = props.item?.summary ?? (record ? '
|
|
238
|
+
const title = props.item?.name ?? record?.label ?? record?.id ?? record?.spec ?? t('marketplaceUnknownItem');
|
|
239
|
+
const summary = props.item?.summary ?? (record ? t('marketplaceInstalledLocalSummary') : '');
|
|
239
240
|
const spec = props.item?.install.spec ?? record?.spec ?? '';
|
|
240
241
|
|
|
241
242
|
const targetId = record?.id || record?.spec;
|
|
@@ -249,12 +250,12 @@ function MarketplaceListCard(props: {
|
|
|
249
250
|
const isDisabled = record ? (record.enabled === false || record.runtimeStatus === 'disabled') : false;
|
|
250
251
|
const isInstalling = props.installState.isPending && props.item && props.installState.installingSpec === props.item.install.spec;
|
|
251
252
|
|
|
252
|
-
const displayType = type === 'plugin' ? '
|
|
253
|
+
const displayType = type === 'plugin' ? t('marketplaceTypePlugin') : type === 'skill' ? t('marketplaceTypeSkill') : t('marketplaceTypeExtension');
|
|
253
254
|
|
|
254
255
|
return (
|
|
255
256
|
<article className="group bg-white border border-gray-200/40 hover:border-gray-200/80 rounded-2xl px-5 py-4 hover:shadow-md shadow-sm transition-all flex items-start gap-3.5 justify-between cursor-default">
|
|
256
257
|
<div className="flex gap-3 min-w-0 flex-1 h-full items-start">
|
|
257
|
-
<ItemIcon name={title} fallback={spec || '
|
|
258
|
+
<ItemIcon name={title} fallback={spec || t('marketplaceTypeExtension')} />
|
|
258
259
|
<div className="min-w-0 flex-1 flex flex-col justify-center h-full">
|
|
259
260
|
<TooltipProvider delayDuration={400}>
|
|
260
261
|
<Tooltip>
|
|
@@ -304,7 +305,7 @@ function MarketplaceListCard(props: {
|
|
|
304
305
|
disabled={props.installState.isPending}
|
|
305
306
|
className="inline-flex items-center gap-1.5 h-8 px-4 rounded-xl text-xs font-medium bg-primary text-white hover:bg-primary-600 disabled:opacity-50 transition-colors"
|
|
306
307
|
>
|
|
307
|
-
{isInstalling ? '
|
|
308
|
+
{isInstalling ? t('marketplaceInstalling') : t('marketplaceInstall')}
|
|
308
309
|
</button>
|
|
309
310
|
)}
|
|
310
311
|
|
|
@@ -315,8 +316,8 @@ function MarketplaceListCard(props: {
|
|
|
315
316
|
className="inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-gray-200/80 text-gray-600 bg-white hover:bg-gray-50 hover:border-gray-300 disabled:opacity-50 transition-colors"
|
|
316
317
|
>
|
|
317
318
|
{busyForRecord && props.manageState.action !== 'uninstall'
|
|
318
|
-
? (props.manageState.action === 'enable' ? '
|
|
319
|
-
: (isDisabled ? '
|
|
319
|
+
? (props.manageState.action === 'enable' ? t('marketplaceEnabling') : t('marketplaceDisabling'))
|
|
320
|
+
: (isDisabled ? t('marketplaceEnable') : t('marketplaceDisable'))}
|
|
320
321
|
</button>
|
|
321
322
|
)}
|
|
322
323
|
|
|
@@ -326,7 +327,7 @@ function MarketplaceListCard(props: {
|
|
|
326
327
|
onClick={() => props.onManage('uninstall', record)}
|
|
327
328
|
className="inline-flex items-center h-8 px-4 rounded-xl text-xs font-medium border border-rose-100 text-rose-500 bg-white hover:bg-rose-50 hover:border-rose-200 disabled:opacity-50 transition-colors"
|
|
328
329
|
>
|
|
329
|
-
{busyForRecord && props.manageState.action === 'uninstall' ? '
|
|
330
|
+
{busyForRecord && props.manageState.action === 'uninstall' ? t('marketplaceRemoving') : t('marketplaceUninstall')}
|
|
330
331
|
</button>
|
|
331
332
|
)}
|
|
332
333
|
</div>
|
|
@@ -348,7 +349,7 @@ function PaginationBar(props: {
|
|
|
348
349
|
onClick={props.onPrev}
|
|
349
350
|
disabled={props.page <= 1 || props.busy}
|
|
350
351
|
>
|
|
351
|
-
|
|
352
|
+
{t('prev')}
|
|
352
353
|
</button>
|
|
353
354
|
<div className="text-sm text-gray-600 min-w-20 text-center">
|
|
354
355
|
{props.totalPages === 0 ? '0 / 0' : `${props.page} / ${props.totalPages}`}
|
|
@@ -358,7 +359,7 @@ function PaginationBar(props: {
|
|
|
358
359
|
onClick={props.onNext}
|
|
359
360
|
disabled={props.totalPages === 0 || props.page >= props.totalPages || props.busy}
|
|
360
361
|
>
|
|
361
|
-
|
|
362
|
+
{t('next')}
|
|
362
363
|
</button>
|
|
363
364
|
</div>
|
|
364
365
|
);
|
|
@@ -446,13 +447,13 @@ export function MarketplacePage() {
|
|
|
446
447
|
const listSummary = useMemo(() => {
|
|
447
448
|
if (scope === 'installed') {
|
|
448
449
|
if (installedQuery.isLoading) {
|
|
449
|
-
return '
|
|
450
|
+
return t('loading');
|
|
450
451
|
}
|
|
451
|
-
return `${installedEntries.length}
|
|
452
|
+
return `${installedEntries.length} ${t('marketplaceInstalledCountSuffix')}`;
|
|
452
453
|
}
|
|
453
454
|
|
|
454
455
|
if (!itemsQuery.data) {
|
|
455
|
-
return '
|
|
456
|
+
return t('loading');
|
|
456
457
|
}
|
|
457
458
|
|
|
458
459
|
return `${allItems.length} / ${total}`;
|
|
@@ -470,8 +471,8 @@ export function MarketplacePage() {
|
|
|
470
471
|
};
|
|
471
472
|
|
|
472
473
|
const tabs = [
|
|
473
|
-
{ id: 'all', label: '
|
|
474
|
-
{ id: 'installed', label: '
|
|
474
|
+
{ id: 'all', label: t('marketplaceTabMarketplace') },
|
|
475
|
+
{ id: 'installed', label: t('marketplaceTabInstalled'), count: installedQuery.data?.total ?? 0 }
|
|
475
476
|
];
|
|
476
477
|
|
|
477
478
|
const handleInstall = (item: MarketplaceItemSummary) => {
|
|
@@ -493,9 +494,9 @@ export function MarketplacePage() {
|
|
|
493
494
|
|
|
494
495
|
if (action === 'uninstall') {
|
|
495
496
|
const confirmed = await confirm({
|
|
496
|
-
title:
|
|
497
|
-
description: '
|
|
498
|
-
confirmLabel: '
|
|
497
|
+
title: `${t('marketplaceUninstallTitle')} ${targetId}?`,
|
|
498
|
+
description: t('marketplaceUninstallDescription'),
|
|
499
|
+
confirmLabel: t('marketplaceUninstall'),
|
|
499
500
|
variant: 'destructive'
|
|
500
501
|
});
|
|
501
502
|
if (!confirmed) {
|
|
@@ -514,8 +515,8 @@ export function MarketplacePage() {
|
|
|
514
515
|
return (
|
|
515
516
|
<div className="animate-fade-in pb-20">
|
|
516
517
|
<div className="mb-4">
|
|
517
|
-
<h2 className="text-xl font-semibold text-gray-900">
|
|
518
|
-
<p className="text-[12px] text-gray-400 mt-0.5">
|
|
518
|
+
<h2 className="text-xl font-semibold text-gray-900">{t('marketplacePageTitle')}</h2>
|
|
519
|
+
<p className="text-[12px] text-gray-400 mt-0.5">{t('marketplacePageDescription')}</p>
|
|
519
520
|
</div>
|
|
520
521
|
|
|
521
522
|
<Tabs
|
|
@@ -546,18 +547,18 @@ export function MarketplacePage() {
|
|
|
546
547
|
|
|
547
548
|
<section>
|
|
548
549
|
<div className="flex items-center justify-between mb-3">
|
|
549
|
-
<h3 className="text-[14px] font-semibold text-gray-900">{scope === 'installed' ? '
|
|
550
|
+
<h3 className="text-[14px] font-semibold text-gray-900">{scope === 'installed' ? t('marketplaceSectionInstalled') : t('marketplaceSectionExtensions')}</h3>
|
|
550
551
|
<span className="text-[12px] text-gray-500">{listSummary}</span>
|
|
551
552
|
</div>
|
|
552
553
|
|
|
553
554
|
{scope === 'all' && itemsQuery.isError && (
|
|
554
555
|
<div className="p-4 rounded-xl bg-rose-50 border border-rose-200 text-rose-700 text-sm">
|
|
555
|
-
|
|
556
|
+
{t('marketplaceErrorLoadingData')}: {itemsQuery.error.message}
|
|
556
557
|
</div>
|
|
557
558
|
)}
|
|
558
559
|
{scope === 'installed' && installedQuery.isError && (
|
|
559
560
|
<div className="p-4 rounded-xl bg-rose-50 border border-rose-200 text-rose-700 text-sm">
|
|
560
|
-
|
|
561
|
+
{t('marketplaceErrorLoadingInstalled')}: {installedQuery.error.message}
|
|
561
562
|
</div>
|
|
562
563
|
)}
|
|
563
564
|
|
|
@@ -588,10 +589,10 @@ export function MarketplacePage() {
|
|
|
588
589
|
</div>
|
|
589
590
|
|
|
590
591
|
{scope === 'all' && !itemsQuery.isLoading && !itemsQuery.isError && allItems.length === 0 && (
|
|
591
|
-
<div className="text-[13px] text-gray-500 py-8 text-center">
|
|
592
|
+
<div className="text-[13px] text-gray-500 py-8 text-center">{t('marketplaceNoItems')}</div>
|
|
592
593
|
)}
|
|
593
594
|
{scope === 'installed' && !installedQuery.isLoading && !installedQuery.isError && installedEntries.length === 0 && (
|
|
594
|
-
<div className="text-[13px] text-gray-500 py-8 text-center">
|
|
595
|
+
<div className="text-[13px] text-gray-500 py-8 text-center">{t('marketplaceNoInstalledItems')}</div>
|
|
595
596
|
)}
|
|
596
597
|
</section>
|
|
597
598
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import {
|
|
11
|
+
getLanguage,
|
|
12
|
+
initializeI18n,
|
|
13
|
+
setLanguage as applyLanguage,
|
|
14
|
+
subscribeLanguageChange,
|
|
15
|
+
t,
|
|
16
|
+
type I18nLanguage,
|
|
17
|
+
} from '@/lib/i18n';
|
|
18
|
+
|
|
19
|
+
type I18nContextValue = {
|
|
20
|
+
language: I18nLanguage;
|
|
21
|
+
setLanguage: (lang: I18nLanguage) => void;
|
|
22
|
+
toggleLanguage: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const I18nContext = createContext<I18nContextValue | null>(null);
|
|
26
|
+
|
|
27
|
+
export function I18nProvider({ children }: { children: ReactNode }) {
|
|
28
|
+
const [language, setLanguageState] = useState<I18nLanguage>(() => initializeI18n());
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const unsubscribe = subscribeLanguageChange((next) => {
|
|
32
|
+
setLanguageState(next);
|
|
33
|
+
});
|
|
34
|
+
return unsubscribe;
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const setLanguage = useCallback((lang: I18nLanguage) => {
|
|
38
|
+
applyLanguage(lang);
|
|
39
|
+
setLanguageState(getLanguage());
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const toggleLanguage = useCallback(() => {
|
|
43
|
+
setLanguage(language === 'en' ? 'zh' : 'en');
|
|
44
|
+
}, [language, setLanguage]);
|
|
45
|
+
|
|
46
|
+
// Ensure descendants re-render when language changes; most text calls global t().
|
|
47
|
+
const value = useMemo(
|
|
48
|
+
() => ({ language, setLanguage, toggleLanguage }),
|
|
49
|
+
[language, setLanguage, toggleLanguage]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useI18n(): I18nContextValue & { t: typeof t } {
|
|
56
|
+
const ctx = useContext(I18nContext);
|
|
57
|
+
if (!ctx) {
|
|
58
|
+
throw new Error('useI18n must be used within I18nProvider');
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...ctx,
|
|
62
|
+
t,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DialogTitle
|
|
9
9
|
} from '@/components/ui/dialog';
|
|
10
10
|
import { Button } from '@/components/ui/button';
|
|
11
|
+
import { t } from '@/lib/i18n';
|
|
11
12
|
|
|
12
13
|
export type ConfirmDialogVariant = 'default' | 'destructive';
|
|
13
14
|
|
|
@@ -28,8 +29,8 @@ export const ConfirmDialog = ({
|
|
|
28
29
|
onOpenChange,
|
|
29
30
|
title,
|
|
30
31
|
description,
|
|
31
|
-
confirmLabel = '
|
|
32
|
-
cancelLabel = '
|
|
32
|
+
confirmLabel = t('confirm'),
|
|
33
|
+
cancelLabel = t('cancel'),
|
|
33
34
|
variant = 'default',
|
|
34
35
|
onConfirm,
|
|
35
36
|
onCancel
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { cn } from '@/lib/utils';
|
|
3
|
+
import { formatNumber } from '@/lib/i18n';
|
|
3
4
|
|
|
4
5
|
interface Tab {
|
|
5
6
|
id: string;
|
|
@@ -35,7 +36,7 @@ export function Tabs({ tabs, activeTab, onChange, className }: TabsProps) {
|
|
|
35
36
|
<span className={cn(
|
|
36
37
|
'text-[11px] font-medium',
|
|
37
38
|
isActive ? 'text-gray-500' : 'text-gray-500'
|
|
38
|
-
)}>{tab.count
|
|
39
|
+
)}>{formatNumber(tab.count)}</span>
|
|
39
40
|
)}
|
|
40
41
|
{isActive && (
|
|
41
42
|
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-primary rounded-full" />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { useCallback, useState } from 'react';
|
|
3
3
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
|
4
|
+
import { t } from '@/lib/i18n';
|
|
4
5
|
|
|
5
6
|
export type ConfirmOptions = {
|
|
6
7
|
title: string;
|
|
@@ -24,8 +25,8 @@ const initial: ConfirmState = {
|
|
|
24
25
|
open: false,
|
|
25
26
|
title: '',
|
|
26
27
|
description: '',
|
|
27
|
-
confirmLabel: '
|
|
28
|
-
cancelLabel: '
|
|
28
|
+
confirmLabel: t('confirm'),
|
|
29
|
+
cancelLabel: t('cancel'),
|
|
29
30
|
variant: 'default',
|
|
30
31
|
resolve: null
|
|
31
32
|
};
|
|
@@ -42,8 +43,8 @@ export function useConfirmDialog(): {
|
|
|
42
43
|
open: true,
|
|
43
44
|
title: options.title,
|
|
44
45
|
description: options.description ?? '',
|
|
45
|
-
confirmLabel: options.confirmLabel ?? '
|
|
46
|
-
cancelLabel: options.cancelLabel ?? '
|
|
46
|
+
confirmLabel: options.confirmLabel ?? t('confirm'),
|
|
47
|
+
cancelLabel: options.cancelLabel ?? t('cancel'),
|
|
47
48
|
variant: options.variant ?? 'default',
|
|
48
49
|
resolve: (value) => {
|
|
49
50
|
resolve(value);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
2
2
|
import { toast } from 'sonner';
|
|
3
|
+
import { t } from '@/lib/i18n';
|
|
3
4
|
import {
|
|
4
5
|
fetchMarketplaceItem,
|
|
5
6
|
fetchMarketplaceInstalled,
|
|
@@ -53,10 +54,10 @@ export function useInstallMarketplaceItem() {
|
|
|
53
54
|
queryClient.invalidateQueries({ queryKey: ['marketplace-installed'] });
|
|
54
55
|
queryClient.refetchQueries({ queryKey: ['marketplace-installed'], type: 'active' });
|
|
55
56
|
queryClient.refetchQueries({ queryKey: ['marketplace-items'], type: 'active' });
|
|
56
|
-
toast.success(result.message || `${result.type}
|
|
57
|
+
toast.success(result.message || `${result.type} ${t('marketplaceInstalledCountSuffix')}`);
|
|
57
58
|
},
|
|
58
59
|
onError: (error: Error) => {
|
|
59
|
-
toast.error(error.message || '
|
|
60
|
+
toast.error(error.message || t('marketplaceInstallFailed'));
|
|
60
61
|
}
|
|
61
62
|
});
|
|
62
63
|
}
|
|
@@ -74,7 +75,7 @@ export function useManageMarketplaceItem() {
|
|
|
74
75
|
toast.success(result.message || `${result.action} success`);
|
|
75
76
|
},
|
|
76
77
|
onError: (error: Error) => {
|
|
77
|
-
toast.error(error.message || '
|
|
78
|
+
toast.error(error.message || t('marketplaceOperationFailed'));
|
|
78
79
|
}
|
|
79
80
|
});
|
|
80
81
|
}
|