mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 12:34:50 +02:00
[eric] marketplace: the published .swarm catalog is the store tab, installed through the existing import door
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012G8kyALnPjsA7aJFmMBq3R
This commit is contained in:
co-authored by
Claude Opus 5
parent
f57e9509f8
commit
8afec84986
@@ -0,0 +1,62 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { API_BASE } from '@/shared/config';
|
||||
import type { Listing } from '@/app/pages/Directory/packages/catalog';
|
||||
|
||||
const MARKETPLACE_API = `${API_BASE}/marketplace`;
|
||||
|
||||
// 'cache' means the sheet was unreachable and these are the last listings we saw, which the tab says out loud.
|
||||
export type CatalogSource = 'sheet' | 'cache' | 'empty';
|
||||
|
||||
interface CatalogState {
|
||||
listings: Listing[];
|
||||
source: CatalogSource;
|
||||
loading: boolean;
|
||||
loaded: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const initialState: CatalogState = { listings: [], source: 'empty', loading: false, loaded: false, error: '' };
|
||||
|
||||
interface CatalogPayload {
|
||||
source: CatalogSource;
|
||||
count: number;
|
||||
listings: Listing[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export const fetchMarketplaceListings = createAsyncThunk(
|
||||
'marketplaceCatalog/fetch',
|
||||
async (refresh: boolean = false) => {
|
||||
const res = await fetch(`${MARKETPLACE_API}/listings${refresh ? '?refresh=true' : ''}`);
|
||||
if (!res.ok) throw new Error(`Marketplace listings failed: ${res.status}`);
|
||||
return (await res.json()) as CatalogPayload;
|
||||
},
|
||||
{ condition: (_, { getState }) => !(getState() as { marketplaceCatalog: CatalogState }).marketplaceCatalog.loading },
|
||||
);
|
||||
|
||||
const marketplaceCatalogSlice = createSlice({
|
||||
name: 'marketplaceCatalog',
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchMarketplaceListings.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(fetchMarketplaceListings.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.loaded = true;
|
||||
state.listings = action.payload.listings;
|
||||
state.source = action.payload.source;
|
||||
state.error = action.payload.error;
|
||||
})
|
||||
.addCase(fetchMarketplaceListings.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.loaded = true;
|
||||
// Keep whatever listings we already had: a failed refresh must not empty a populated store.
|
||||
state.error = action.error.message || 'Could not reach the marketplace.';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default marketplaceCatalogSlice.reducer;
|
||||
@@ -8,6 +8,7 @@ import modesReducer from './modesSlice';
|
||||
import settingsReducer from './settingsSlice';
|
||||
import mcpRegistryReducer from './mcpRegistrySlice';
|
||||
import skillRegistryReducer from './skillRegistrySlice';
|
||||
import marketplaceCatalogReducer from './marketplaceCatalogSlice';
|
||||
import outputsReducer from './outputsSlice';
|
||||
import dashboardLayoutReducer from './dashboardLayoutSlice';
|
||||
import dashboardsReducer from './dashboardsSlice';
|
||||
@@ -31,6 +32,7 @@ export const store = configureStore({
|
||||
settings: settingsReducer,
|
||||
mcpRegistry: mcpRegistryReducer,
|
||||
skillRegistry: skillRegistryReducer,
|
||||
marketplaceCatalog: marketplaceCatalogReducer,
|
||||
outputs: outputsReducer,
|
||||
dashboardLayout: dashboardLayoutReducer,
|
||||
dashboards: dashboardsReducer,
|
||||
|
||||
Reference in New Issue
Block a user