@nextclaw/ui 0.5.14 → 0.5.15
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/ChannelsList-DHBUkGsA.js +1 -0
- package/dist/assets/CronConfig-9YgJQTmP.js +1 -0
- package/dist/assets/DocBrowser-CBY75a92.js +1 -0
- package/dist/assets/MarketplacePage-Dw_0TLNd.js +1 -0
- package/dist/assets/ModelConfig-CBmc0ERp.js +1 -0
- package/dist/assets/ProvidersList-CZfmOr6_.js +1 -0
- package/dist/assets/RuntimeConfig-DX6nI_b5.js +1 -0
- package/dist/assets/SessionsConfig-wH-647_6.js +2 -0
- package/dist/assets/action-link-DNRpMv1A.js +1 -0
- package/dist/assets/card-KMt_4CgV.js +1 -0
- package/dist/assets/config-hints-CApS3K_7.js +1 -0
- package/dist/assets/dialog-CqY6jnQm.js +5 -0
- package/dist/assets/index-CVOQiX2_.js +2 -0
- package/dist/assets/index-DdpR1fdj.css +1 -0
- package/dist/assets/label-C7WCjHWk.js +1 -0
- package/dist/assets/page-layout-CMuYE4DA.js +1 -0
- package/dist/assets/switch-DQmUTN4L.js +1 -0
- package/dist/assets/tabs-custom-C5xYS25o.js +1 -0
- package/dist/assets/useConfig-Bv2DQ4BE.js +1 -0
- package/dist/assets/useConfirmDialog-BOcIsqiA.js +1 -0
- package/dist/assets/vendor-Dkx07DIh.js +342 -0
- package/dist/index.html +3 -2
- package/package.json +1 -1
- package/src/App.tsx +25 -14
- package/src/api/marketplace.ts +20 -20
- package/src/api/types.ts +2 -2
- package/src/components/layout/AppLayout.tsx +15 -3
- package/src/components/marketplace/MarketplacePage.tsx +58 -38
- package/src/hooks/useMarketplace.ts +9 -9
- package/vite.config.ts +2 -2
- package/dist/assets/index-CZnQJKDU.js +0 -347
- package/dist/assets/index-CfkfeqC5.css +0 -1
package/dist/index.html
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CVOQiX2_.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-Dkx07DIh.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DdpR1fdj.css">
|
|
11
12
|
</head>
|
|
12
13
|
|
|
13
14
|
<body>
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
+
import { lazy, Suspense } from 'react';
|
|
1
2
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
3
|
import { AppLayout } from '@/components/layout/AppLayout';
|
|
3
|
-
import { ModelConfig } from '@/components/config/ModelConfig';
|
|
4
|
-
import { ProvidersList } from '@/components/config/ProvidersList';
|
|
5
|
-
import { ChannelsList } from '@/components/config/ChannelsList';
|
|
6
|
-
import { RuntimeConfig } from '@/components/config/RuntimeConfig';
|
|
7
|
-
import { SessionsConfig } from '@/components/config/SessionsConfig';
|
|
8
|
-
import { CronConfig } from '@/components/config/CronConfig';
|
|
9
|
-
import { MarketplacePage } from '@/components/marketplace/MarketplacePage';
|
|
10
4
|
import { useWebSocket } from '@/hooks/useWebSocket';
|
|
11
5
|
import { Toaster } from 'sonner';
|
|
12
6
|
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
|
|
@@ -20,6 +14,22 @@ const queryClient = new QueryClient({
|
|
|
20
14
|
}
|
|
21
15
|
});
|
|
22
16
|
|
|
17
|
+
const ModelConfigPage = lazy(async () => ({ default: (await import('@/components/config/ModelConfig')).ModelConfig }));
|
|
18
|
+
const ProvidersListPage = lazy(async () => ({ default: (await import('@/components/config/ProvidersList')).ProvidersList }));
|
|
19
|
+
const ChannelsListPage = lazy(async () => ({ default: (await import('@/components/config/ChannelsList')).ChannelsList }));
|
|
20
|
+
const RuntimeConfigPage = lazy(async () => ({ default: (await import('@/components/config/RuntimeConfig')).RuntimeConfig }));
|
|
21
|
+
const SessionsConfigPage = lazy(async () => ({ default: (await import('@/components/config/SessionsConfig')).SessionsConfig }));
|
|
22
|
+
const CronConfigPage = lazy(async () => ({ default: (await import('@/components/config/CronConfig')).CronConfig }));
|
|
23
|
+
const MarketplacePage = lazy(async () => ({ default: (await import('@/components/marketplace/MarketplacePage')).MarketplacePage }));
|
|
24
|
+
|
|
25
|
+
function RouteFallback() {
|
|
26
|
+
return <div className="h-full w-full animate-pulse rounded-2xl border border-border/40 bg-card/40" />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function LazyRoute({ children }: { children: JSX.Element }) {
|
|
30
|
+
return <Suspense fallback={<RouteFallback />}>{children}</Suspense>;
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
function AppContent() {
|
|
24
34
|
useWebSocket(queryClient); // Initialize WebSocket connection
|
|
25
35
|
const location = useLocation();
|
|
@@ -29,13 +39,14 @@ function AppContent() {
|
|
|
29
39
|
<AppLayout>
|
|
30
40
|
<div key={location.pathname} className="animate-fade-in w-full h-full">
|
|
31
41
|
<Routes>
|
|
32
|
-
<Route path="/model" element={<
|
|
33
|
-
<Route path="/providers" element={<
|
|
34
|
-
<Route path="/channels" element={<
|
|
35
|
-
<Route path="/runtime" element={<
|
|
36
|
-
<Route path="/sessions" element={<
|
|
37
|
-
<Route path="/cron" element={<
|
|
38
|
-
<Route path="/marketplace" element={<
|
|
42
|
+
<Route path="/model" element={<LazyRoute><ModelConfigPage /></LazyRoute>} />
|
|
43
|
+
<Route path="/providers" element={<LazyRoute><ProvidersListPage /></LazyRoute>} />
|
|
44
|
+
<Route path="/channels" element={<LazyRoute><ChannelsListPage /></LazyRoute>} />
|
|
45
|
+
<Route path="/runtime" element={<LazyRoute><RuntimeConfigPage /></LazyRoute>} />
|
|
46
|
+
<Route path="/sessions" element={<LazyRoute><SessionsConfigPage /></LazyRoute>} />
|
|
47
|
+
<Route path="/cron" element={<LazyRoute><CronConfigPage /></LazyRoute>} />
|
|
48
|
+
<Route path="/marketplace" element={<Navigate to="/marketplace/plugins" replace />} />
|
|
49
|
+
<Route path="/marketplace/:type" element={<LazyRoute><MarketplacePage /></LazyRoute>} />
|
|
39
50
|
<Route path="/" element={<Navigate to="/model" replace />} />
|
|
40
51
|
<Route path="*" element={<Navigate to="/model" replace />} />
|
|
41
52
|
</Routes>
|
package/src/api/marketplace.ts
CHANGED
|
@@ -13,23 +13,25 @@ import type {
|
|
|
13
13
|
} from './types';
|
|
14
14
|
|
|
15
15
|
export type MarketplaceListParams = {
|
|
16
|
+
type: MarketplaceItemType;
|
|
16
17
|
q?: string;
|
|
17
|
-
type?: MarketplaceItemType;
|
|
18
18
|
tag?: string;
|
|
19
19
|
sort?: MarketplaceSort;
|
|
20
20
|
page?: number;
|
|
21
21
|
pageSize?: number;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
function toMarketplaceTypeSegment(type: MarketplaceItemType): 'plugins' | 'skills' {
|
|
25
|
+
return type === 'plugin' ? 'plugins' : 'skills';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function fetchMarketplaceItems(params: MarketplaceListParams): Promise<MarketplaceListView> {
|
|
25
29
|
const query = new URLSearchParams();
|
|
30
|
+
const segment = toMarketplaceTypeSegment(params.type);
|
|
26
31
|
|
|
27
32
|
if (params.q?.trim()) {
|
|
28
33
|
query.set('q', params.q.trim());
|
|
29
34
|
}
|
|
30
|
-
if (params.type) {
|
|
31
|
-
query.set('type', params.type);
|
|
32
|
-
}
|
|
33
35
|
if (params.tag?.trim()) {
|
|
34
36
|
query.set('tag', params.tag.trim());
|
|
35
37
|
}
|
|
@@ -44,7 +46,9 @@ export async function fetchMarketplaceItems(params: MarketplaceListParams = {}):
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
const suffix = query.toString();
|
|
47
|
-
const response = await api.get<MarketplaceListView>(
|
|
49
|
+
const response = await api.get<MarketplaceListView>(
|
|
50
|
+
suffix ? `/api/marketplace/${segment}/items?${suffix}` : `/api/marketplace/${segment}/items`
|
|
51
|
+
);
|
|
48
52
|
if (!response.ok) {
|
|
49
53
|
throw new Error(response.error.message);
|
|
50
54
|
}
|
|
@@ -52,17 +56,10 @@ export async function fetchMarketplaceItems(params: MarketplaceListParams = {}):
|
|
|
52
56
|
return response.data;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
export async function fetchMarketplaceItem(slug: string, type
|
|
56
|
-
const
|
|
57
|
-
if (type) {
|
|
58
|
-
query.set('type', type);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const suffix = query.toString();
|
|
59
|
+
export async function fetchMarketplaceItem(slug: string, type: MarketplaceItemType): Promise<MarketplaceItemView> {
|
|
60
|
+
const segment = toMarketplaceTypeSegment(type);
|
|
62
61
|
const response = await api.get<MarketplaceItemView>(
|
|
63
|
-
|
|
64
|
-
? `/api/marketplace/items/${encodeURIComponent(slug)}?${suffix}`
|
|
65
|
-
: `/api/marketplace/items/${encodeURIComponent(slug)}`
|
|
62
|
+
`/api/marketplace/${segment}/items/${encodeURIComponent(slug)}`
|
|
66
63
|
);
|
|
67
64
|
if (!response.ok) {
|
|
68
65
|
throw new Error(response.error.message);
|
|
@@ -95,7 +92,8 @@ export async function fetchMarketplaceRecommendations(params: {
|
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
export async function installMarketplaceItem(request: MarketplaceInstallRequest): Promise<MarketplaceInstallResult> {
|
|
98
|
-
const
|
|
95
|
+
const segment = toMarketplaceTypeSegment(request.type);
|
|
96
|
+
const response = await api.post<MarketplaceInstallResult>(`/api/marketplace/${segment}/install`, request);
|
|
99
97
|
if (!response.ok) {
|
|
100
98
|
throw new Error(response.error.message);
|
|
101
99
|
}
|
|
@@ -103,8 +101,9 @@ export async function installMarketplaceItem(request: MarketplaceInstallRequest)
|
|
|
103
101
|
return response.data;
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
export async function fetchMarketplaceInstalled(): Promise<MarketplaceInstalledView> {
|
|
107
|
-
const
|
|
104
|
+
export async function fetchMarketplaceInstalled(type: MarketplaceItemType): Promise<MarketplaceInstalledView> {
|
|
105
|
+
const segment = toMarketplaceTypeSegment(type);
|
|
106
|
+
const response = await api.get<MarketplaceInstalledView>(`/api/marketplace/${segment}/installed`);
|
|
108
107
|
if (!response.ok) {
|
|
109
108
|
throw new Error(response.error.message);
|
|
110
109
|
}
|
|
@@ -112,7 +111,8 @@ export async function fetchMarketplaceInstalled(): Promise<MarketplaceInstalledV
|
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
export async function manageMarketplaceItem(request: MarketplaceManageRequest): Promise<MarketplaceManageResult> {
|
|
115
|
-
const
|
|
114
|
+
const segment = toMarketplaceTypeSegment(request.type);
|
|
115
|
+
const response = await api.post<MarketplaceManageResult>(`/api/marketplace/${segment}/manage`, request);
|
|
116
116
|
if (!response.ok) {
|
|
117
117
|
throw new Error(response.error.message);
|
|
118
118
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -355,9 +355,9 @@ export type MarketplaceInstalledRecord = {
|
|
|
355
355
|
};
|
|
356
356
|
|
|
357
357
|
export type MarketplaceInstalledView = {
|
|
358
|
+
type: MarketplaceItemType;
|
|
358
359
|
total: number;
|
|
359
|
-
|
|
360
|
-
skillSpecs: string[];
|
|
360
|
+
specs: string[];
|
|
361
361
|
records: MarketplaceInstalledRecord[];
|
|
362
362
|
};
|
|
363
363
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { lazy, Suspense } from 'react';
|
|
1
2
|
import { Sidebar } from './Sidebar';
|
|
2
|
-
import { DocBrowserProvider,
|
|
3
|
+
import { DocBrowserProvider, useDocBrowser } from '@/components/doc-browser/DocBrowserContext';
|
|
4
|
+
import { useDocLinkInterceptor } from '@/components/doc-browser/useDocLinkInterceptor';
|
|
5
|
+
|
|
6
|
+
const DocBrowser = lazy(async () => ({ default: (await import('@/components/doc-browser/DocBrowser')).DocBrowser }));
|
|
3
7
|
|
|
4
8
|
interface AppLayoutProps {
|
|
5
9
|
children: React.ReactNode;
|
|
@@ -21,9 +25,17 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|
|
21
25
|
</main>
|
|
22
26
|
</div>
|
|
23
27
|
{/* Doc Browser: docked mode renders inline, floating mode renders as overlay */}
|
|
24
|
-
{isOpen && mode === 'docked' &&
|
|
28
|
+
{isOpen && mode === 'docked' && (
|
|
29
|
+
<Suspense fallback={null}>
|
|
30
|
+
<DocBrowser />
|
|
31
|
+
</Suspense>
|
|
32
|
+
)}
|
|
25
33
|
</div>
|
|
26
|
-
{isOpen && mode === 'floating' &&
|
|
34
|
+
{isOpen && mode === 'floating' && (
|
|
35
|
+
<Suspense fallback={null}>
|
|
36
|
+
<DocBrowser />
|
|
37
|
+
</Suspense>
|
|
38
|
+
)}
|
|
27
39
|
</div>
|
|
28
40
|
);
|
|
29
41
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/* eslint-disable max-lines-per-function */
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
MarketplaceInstalledRecord,
|
|
4
|
+
MarketplaceItemSummary,
|
|
5
|
+
MarketplaceManageAction,
|
|
6
|
+
MarketplaceSort,
|
|
7
|
+
MarketplaceItemType
|
|
8
|
+
} from '@/api/types';
|
|
3
9
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
4
10
|
import { Tabs } from '@/components/ui/tabs-custom';
|
|
5
11
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
@@ -15,10 +21,10 @@ import { PageLayout, PageHeader } from '@/components/layout/page-layout';
|
|
|
15
21
|
import { cn } from '@/lib/utils';
|
|
16
22
|
import { PackageSearch } from 'lucide-react';
|
|
17
23
|
import { useEffect, useMemo, useState } from 'react';
|
|
24
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
18
25
|
|
|
19
26
|
const PAGE_SIZE = 12;
|
|
20
27
|
|
|
21
|
-
type FilterType = 'all' | 'plugin' | 'skill';
|
|
22
28
|
type ScopeType = 'all' | 'installed';
|
|
23
29
|
|
|
24
30
|
type InstallState = {
|
|
@@ -38,6 +44,8 @@ type InstalledRenderEntry = {
|
|
|
38
44
|
item?: MarketplaceItemSummary;
|
|
39
45
|
};
|
|
40
46
|
|
|
47
|
+
type MarketplaceRouteType = 'plugins' | 'skills';
|
|
48
|
+
|
|
41
49
|
function normalizeMarketplaceKey(value: string | undefined): string {
|
|
42
50
|
return (value ?? '').trim().toLowerCase();
|
|
43
51
|
}
|
|
@@ -168,10 +176,8 @@ function ItemIcon({ name, fallback }: { name?: string; fallback: string }) {
|
|
|
168
176
|
function FilterPanel(props: {
|
|
169
177
|
scope: ScopeType;
|
|
170
178
|
searchText: string;
|
|
171
|
-
typeFilter: FilterType;
|
|
172
179
|
sort: MarketplaceSort;
|
|
173
180
|
onSearchTextChange: (value: string) => void;
|
|
174
|
-
onTypeFilterChange: (value: FilterType) => void;
|
|
175
181
|
onSortChange: (value: MarketplaceSort) => void;
|
|
176
182
|
}) {
|
|
177
183
|
return (
|
|
@@ -187,28 +193,6 @@ function FilterPanel(props: {
|
|
|
187
193
|
/>
|
|
188
194
|
</div>
|
|
189
195
|
|
|
190
|
-
<div className="inline-flex h-9 rounded-xl bg-gray-100/80 p-1 shrink-0">
|
|
191
|
-
{([
|
|
192
|
-
{ value: 'all', label: t('marketplaceFilterAll') },
|
|
193
|
-
{ value: 'plugin', label: t('marketplaceFilterPlugins') },
|
|
194
|
-
{ value: 'skill', label: t('marketplaceFilterSkills') },
|
|
195
|
-
] as const).map((opt) => (
|
|
196
|
-
<button
|
|
197
|
-
key={opt.value}
|
|
198
|
-
type="button"
|
|
199
|
-
onClick={() => props.onTypeFilterChange(opt.value)}
|
|
200
|
-
className={cn(
|
|
201
|
-
'px-3 rounded-lg text-sm font-medium transition-all whitespace-nowrap',
|
|
202
|
-
props.typeFilter === opt.value
|
|
203
|
-
? 'bg-white text-gray-900 shadow-sm'
|
|
204
|
-
: 'text-gray-500 hover:text-gray-700'
|
|
205
|
-
)}
|
|
206
|
-
>
|
|
207
|
-
{opt.label}
|
|
208
|
-
</button>
|
|
209
|
-
))}
|
|
210
|
-
</div>
|
|
211
|
-
|
|
212
196
|
{props.scope === 'all' && (
|
|
213
197
|
<Select value={props.sort} onValueChange={(v) => props.onSortChange(v as MarketplaceSort)}>
|
|
214
198
|
<SelectTrigger className="h-9 w-[150px] shrink-0 rounded-lg">
|
|
@@ -367,10 +351,27 @@ function PaginationBar(props: {
|
|
|
367
351
|
}
|
|
368
352
|
|
|
369
353
|
export function MarketplacePage() {
|
|
354
|
+
const navigate = useNavigate();
|
|
355
|
+
const params = useParams<{ type?: string }>();
|
|
356
|
+
|
|
357
|
+
const routeType: MarketplaceRouteType | null = useMemo(() => {
|
|
358
|
+
if (params.type === 'plugins' || params.type === 'skills') {
|
|
359
|
+
return params.type;
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}, [params.type]);
|
|
363
|
+
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (!routeType) {
|
|
366
|
+
navigate('/marketplace/plugins', { replace: true });
|
|
367
|
+
}
|
|
368
|
+
}, [routeType, navigate]);
|
|
369
|
+
|
|
370
|
+
const typeFilter: MarketplaceItemType = routeType === 'skills' ? 'skill' : 'plugin';
|
|
371
|
+
|
|
370
372
|
const [searchText, setSearchText] = useState('');
|
|
371
373
|
const [query, setQuery] = useState('');
|
|
372
374
|
const [scope, setScope] = useState<ScopeType>('all');
|
|
373
|
-
const [typeFilter, setTypeFilter] = useState<FilterType>('all');
|
|
374
375
|
const [sort, setSort] = useState<MarketplaceSort>('relevance');
|
|
375
376
|
const [page, setPage] = useState(1);
|
|
376
377
|
|
|
@@ -382,11 +383,15 @@ export function MarketplacePage() {
|
|
|
382
383
|
return () => clearTimeout(timer);
|
|
383
384
|
}, [searchText]);
|
|
384
385
|
|
|
385
|
-
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
setPage(1);
|
|
388
|
+
}, [typeFilter]);
|
|
389
|
+
|
|
390
|
+
const installedQuery = useMarketplaceInstalled(typeFilter);
|
|
386
391
|
|
|
387
392
|
const itemsQuery = useMarketplaceItems({
|
|
388
393
|
q: query || undefined,
|
|
389
|
-
type: typeFilter
|
|
394
|
+
type: typeFilter,
|
|
390
395
|
sort,
|
|
391
396
|
page,
|
|
392
397
|
pageSize: PAGE_SIZE
|
|
@@ -418,7 +423,7 @@ export function MarketplacePage() {
|
|
|
418
423
|
|
|
419
424
|
const installedEntries = useMemo<InstalledRenderEntry[]>(() => {
|
|
420
425
|
const entries = installedRecords
|
|
421
|
-
.filter((record) =>
|
|
426
|
+
.filter((record) => record.type === typeFilter)
|
|
422
427
|
.map((record) => ({
|
|
423
428
|
key: `${record.type}:${record.spec}:${record.id ?? ''}`,
|
|
424
429
|
record,
|
|
@@ -471,10 +476,14 @@ export function MarketplacePage() {
|
|
|
471
476
|
action: manageMutation.variables?.action
|
|
472
477
|
};
|
|
473
478
|
|
|
474
|
-
const
|
|
479
|
+
const scopeTabs = [
|
|
475
480
|
{ id: 'all', label: t('marketplaceTabMarketplace') },
|
|
476
481
|
{ id: 'installed', label: t('marketplaceTabInstalled'), count: installedQuery.data?.total ?? 0 }
|
|
477
482
|
];
|
|
483
|
+
const typeTabs = [
|
|
484
|
+
{ id: 'plugins', label: t('marketplaceFilterPlugins') },
|
|
485
|
+
{ id: 'skills', label: t('marketplaceFilterSkills') }
|
|
486
|
+
];
|
|
478
487
|
|
|
479
488
|
const handleInstall = (item: MarketplaceItemSummary) => {
|
|
480
489
|
if (installMutation.isPending) {
|
|
@@ -518,7 +527,19 @@ export function MarketplacePage() {
|
|
|
518
527
|
<PageHeader title={t('marketplacePageTitle')} description={t('marketplacePageDescription')} />
|
|
519
528
|
|
|
520
529
|
<Tabs
|
|
521
|
-
tabs={
|
|
530
|
+
tabs={typeTabs}
|
|
531
|
+
activeTab={routeType ?? 'plugins'}
|
|
532
|
+
onChange={(value) => {
|
|
533
|
+
const routeValue = value === 'skills' ? 'skills' : 'plugins';
|
|
534
|
+
if (routeType === routeValue) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
navigate(`/marketplace/${routeValue}`);
|
|
538
|
+
}}
|
|
539
|
+
className="mb-4"
|
|
540
|
+
/>
|
|
541
|
+
<Tabs
|
|
542
|
+
tabs={scopeTabs}
|
|
522
543
|
activeTab={scope}
|
|
523
544
|
onChange={(value) => {
|
|
524
545
|
setScope(value as ScopeType);
|
|
@@ -530,13 +551,8 @@ export function MarketplacePage() {
|
|
|
530
551
|
<FilterPanel
|
|
531
552
|
scope={scope}
|
|
532
553
|
searchText={searchText}
|
|
533
|
-
typeFilter={typeFilter}
|
|
534
554
|
sort={sort}
|
|
535
555
|
onSearchTextChange={setSearchText}
|
|
536
|
-
onTypeFilterChange={(value) => {
|
|
537
|
-
setPage(1);
|
|
538
|
-
setTypeFilter(value);
|
|
539
|
-
}}
|
|
540
556
|
onSortChange={(value) => {
|
|
541
557
|
setPage(1);
|
|
542
558
|
setSort(value);
|
|
@@ -545,7 +561,11 @@ export function MarketplacePage() {
|
|
|
545
561
|
|
|
546
562
|
<section>
|
|
547
563
|
<div className="flex items-center justify-between mb-3">
|
|
548
|
-
<h3 className="text-[14px] font-semibold text-gray-900">
|
|
564
|
+
<h3 className="text-[14px] font-semibold text-gray-900">
|
|
565
|
+
{scope === 'installed' ? t('marketplaceSectionInstalled') : t('marketplaceSectionExtensions')}
|
|
566
|
+
{' · '}
|
|
567
|
+
{typeFilter === 'plugin' ? t('marketplaceFilterPlugins') : t('marketplaceFilterSkills')}
|
|
568
|
+
</h3>
|
|
549
569
|
<span className="text-[12px] text-gray-500">{listSummary}</span>
|
|
550
570
|
</div>
|
|
551
571
|
|
|
@@ -31,16 +31,16 @@ export function useMarketplaceRecommendations(params: { scene?: string; limit?:
|
|
|
31
31
|
export function useMarketplaceItem(slug: string | null, type?: MarketplaceItemType) {
|
|
32
32
|
return useQuery({
|
|
33
33
|
queryKey: ['marketplace-item', slug, type],
|
|
34
|
-
queryFn: () => fetchMarketplaceItem(slug as string, type),
|
|
35
|
-
enabled: Boolean(slug),
|
|
34
|
+
queryFn: () => fetchMarketplaceItem(slug as string, type as MarketplaceItemType),
|
|
35
|
+
enabled: Boolean(slug && type),
|
|
36
36
|
staleTime: 30_000
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function useMarketplaceInstalled() {
|
|
40
|
+
export function useMarketplaceInstalled(type: MarketplaceItemType) {
|
|
41
41
|
return useQuery({
|
|
42
|
-
queryKey: ['marketplace-installed'],
|
|
43
|
-
queryFn: fetchMarketplaceInstalled,
|
|
42
|
+
queryKey: ['marketplace-installed', type],
|
|
43
|
+
queryFn: () => fetchMarketplaceInstalled(type),
|
|
44
44
|
staleTime: 10_000
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -51,8 +51,8 @@ export function useInstallMarketplaceItem() {
|
|
|
51
51
|
return useMutation({
|
|
52
52
|
mutationFn: (request: MarketplaceInstallRequest) => installMarketplaceItem(request),
|
|
53
53
|
onSuccess: (result) => {
|
|
54
|
-
queryClient.invalidateQueries({ queryKey: ['marketplace-installed'] });
|
|
55
|
-
queryClient.refetchQueries({ queryKey: ['marketplace-installed'], type: 'active' });
|
|
54
|
+
queryClient.invalidateQueries({ queryKey: ['marketplace-installed', result.type] });
|
|
55
|
+
queryClient.refetchQueries({ queryKey: ['marketplace-installed', result.type], type: 'active' });
|
|
56
56
|
queryClient.refetchQueries({ queryKey: ['marketplace-items'], type: 'active' });
|
|
57
57
|
toast.success(result.message || `${result.type} ${t('marketplaceInstalledCountSuffix')}`);
|
|
58
58
|
},
|
|
@@ -68,9 +68,9 @@ export function useManageMarketplaceItem() {
|
|
|
68
68
|
return useMutation({
|
|
69
69
|
mutationFn: (request: MarketplaceManageRequest) => manageMarketplaceItem(request),
|
|
70
70
|
onSuccess: (result) => {
|
|
71
|
-
queryClient.invalidateQueries({ queryKey: ['marketplace-installed'] });
|
|
71
|
+
queryClient.invalidateQueries({ queryKey: ['marketplace-installed', result.type] });
|
|
72
72
|
queryClient.invalidateQueries({ queryKey: ['marketplace-items'] });
|
|
73
|
-
queryClient.refetchQueries({ queryKey: ['marketplace-installed'], type: 'active' });
|
|
73
|
+
queryClient.refetchQueries({ queryKey: ['marketplace-installed', result.type], type: 'active' });
|
|
74
74
|
queryClient.refetchQueries({ queryKey: ['marketplace-items'], type: 'active' });
|
|
75
75
|
toast.success(result.message || `${result.action} success`);
|
|
76
76
|
},
|
package/vite.config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineConfig } from 'vite';
|
|
1
|
+
import { defineConfig, splitVendorChunkPlugin } from 'vite';
|
|
2
2
|
import react from '@vitejs/plugin-react';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ const apiBase = process.env.VITE_API_BASE ?? 'http://127.0.0.1:18792';
|
|
|
6
6
|
const wsBase = apiBase.replace(/^http/i, 'ws');
|
|
7
7
|
|
|
8
8
|
export default defineConfig({
|
|
9
|
-
plugins: [react()],
|
|
9
|
+
plugins: [react(), splitVendorChunkPlugin()],
|
|
10
10
|
resolve: {
|
|
11
11
|
alias: {
|
|
12
12
|
'@': path.resolve(__dirname, './src')
|