1
- import React , { useEffect , useState } from 'react' ;
1
+ import React , { useEffect , useState , Suspense } from 'react' ;
2
2
import { createDockerDesktopClient } from '@docker/extension-api-client' ;
3
- import { Stack , Typography , Button , ButtonGroup , Grid , debounce , Card , CardContent , IconButton , Alert , DialogTitle , Dialog , DialogContent , FormControlLabel , Checkbox , CircularProgress , Paper , DialogActions , Box } from '@mui/material' ;
4
- import { CatalogItemWithName } from './components/PromptCard' ;
5
- import { getRegistry } from './Registry' ;
6
- import { Close , FolderOpenRounded , } from '@mui/icons-material' ;
7
- import { ExecResult } from '@docker/extension-api-client-types/dist/v0' ;
3
+ import { Stack , Typography , Button , IconButton , Alert , DialogTitle , Dialog , DialogContent , CircularProgress , Paper , Box } from '@mui/material' ;
4
+ import { CatalogItemWithName } from './components/tile/Tile' ;
5
+ import { Close } from '@mui/icons-material' ;
8
6
import { CatalogGrid } from './components/CatalogGrid' ;
9
7
import { POLL_INTERVAL } from './Constants' ;
10
8
import MCPCatalogLogo from './MCP Catalog.svg'
11
- import Settings from './components/Settings' ;
12
9
import { getMCPClientStates , MCPClientState } from './MCPClients' ;
10
+ import { CatalogProvider , useCatalogContext } from './context/CatalogContext' ;
11
+ import ConfigurationModal from './components/ConfigurationModal' ;
12
+
13
+ const Settings = React . lazy ( ( ) => import ( './components/Settings' ) ) ;
13
14
14
15
export const client = createDockerDesktopClient ( ) ;
15
16
@@ -19,42 +20,9 @@ const DEFAULT_SETTINGS = {
19
20
}
20
21
21
22
export function App ( ) {
22
- const [ canRegister , setCanRegister ] = useState ( false ) ;
23
- const [ registryItems , setRegistryItems ] = useState < { [ key : string ] : { ref : string } } > ( { } ) ;
24
- const [ imagesLoadingResults , setImagesLoadingResults ] = useState < ExecResult | null > ( null ) ;
25
23
const [ settings , setSettings ] = useState < { showModal : boolean , pollIntervalSeconds : number } > ( localStorage . getItem ( 'settings' ) ? JSON . parse ( localStorage . getItem ( 'settings' ) || '{}' ) : DEFAULT_SETTINGS ) ;
26
24
const [ mcpClientStates , setMcpClientStates ] = useState < { [ name : string ] : MCPClientState } > ( { } ) ;
27
- const loadRegistry = async ( ) => {
28
- setCanRegister ( false ) ;
29
- try {
30
- const result = await getRegistry ( client )
31
- setRegistryItems ( result || { } ) ;
32
- }
33
- catch ( error ) {
34
- if ( error instanceof Error ) {
35
- client . desktopUI . toast . error ( 'Failed to get prompt registry: ' + error . message ) ;
36
- } else {
37
- client . desktopUI . toast . error ( 'Failed to get prompt registry: ' + JSON . stringify ( error ) ) ;
38
- }
39
- }
40
- setCanRegister ( true ) ;
41
- }
42
-
43
- const startImagesLoading = async ( ) => {
44
- setImagesLoadingResults ( null ) ;
45
- try {
46
- const result = await client . docker . cli . exec ( 'pull' , [ 'vonwig/function_write_files:latest' ] )
47
- await client . docker . cli . exec ( 'pull' , [ 'alpine:latest' ] )
48
- await client . docker . cli . exec ( 'pull' , [ 'keinos/sqlite3:latest' ] )
49
- setImagesLoadingResults ( result ) ;
50
- }
51
- catch ( error ) {
52
- console . error ( error )
53
- if ( error ) {
54
- setImagesLoadingResults ( error as ExecResult )
55
- }
56
- }
57
- }
25
+ const [ configuringItem , setConfiguringItem ] = useState < CatalogItemWithName | null > ( null ) ;
58
26
59
27
const updateMCPClientStates = async ( ) => {
60
28
const oldStates = mcpClientStates ;
@@ -70,55 +38,131 @@ export function App() {
70
38
}
71
39
72
40
useEffect ( ( ) => {
73
- startImagesLoading ( ) ;
74
- loadRegistry ( ) ;
41
+ let interval : NodeJS . Timeout ;
75
42
updateMCPClientStates ( ) ;
76
- const interval = setInterval ( ( ) => {
77
- loadRegistry ( ) ;
43
+ interval = setInterval ( ( ) => {
78
44
updateMCPClientStates ( ) ;
79
45
} , POLL_INTERVAL ) ;
80
- return ( ) => {
81
- clearInterval ( interval )
82
- }
46
+ return ( ) => clearInterval ( interval ) ;
83
47
} , [ ] ) ;
84
48
49
+ // Wrap the entire application with our CatalogProvider
50
+ return (
51
+ < CatalogProvider client = { client } >
52
+ < AppContent
53
+ settings = { settings }
54
+ setSettings = { setSettings }
55
+ mcpClientStates = { mcpClientStates }
56
+ configuringItem = { configuringItem }
57
+ setConfiguringItem = { setConfiguringItem }
58
+ updateMCPClientStates = { updateMCPClientStates }
59
+ />
60
+ </ CatalogProvider >
61
+ ) ;
62
+ }
63
+
64
+ interface AppContentProps {
65
+ settings : { showModal : boolean , pollIntervalSeconds : number } ;
66
+ setSettings : React . Dispatch < React . SetStateAction < { showModal : boolean , pollIntervalSeconds : number } > > ;
67
+ mcpClientStates : { [ name : string ] : MCPClientState } ;
68
+ configuringItem : CatalogItemWithName | null ;
69
+ setConfiguringItem : React . Dispatch < React . SetStateAction < CatalogItemWithName | null > > ;
70
+ updateMCPClientStates : ( ) => Promise < void > ;
71
+ }
72
+
73
+ function AppContent ( { settings, setSettings, mcpClientStates, configuringItem, setConfiguringItem, updateMCPClientStates } : AppContentProps ) {
74
+ const { imagesLoadingResults, loadImagesIfNeeded, secrets, catalogItems, registryItems, tryLoadSecrets } = useCatalogContext ( ) ;
75
+
85
76
if ( ! imagesLoadingResults || imagesLoadingResults . stderr ) {
86
77
return < Paper sx = { { padding : 2 , height : '90vh' , display : 'flex' , flexDirection : 'column' , justifyContent : 'center' , alignItems : 'center' } } >
87
78
{ ! imagesLoadingResults && < CircularProgress sx = { { marginBottom : 2 } } /> }
88
79
{ ! imagesLoadingResults && < Typography > Loading images...</ Typography > }
89
- { imagesLoadingResults && < Alert sx = { { fontSize : '1.5em' } } action = { < Button variant = 'outlined' color = 'secondary' onClick = { ( ) => startImagesLoading ( ) } > Retry</ Button > } title = "Error loading images" severity = "error" > { imagesLoadingResults . stderr } </ Alert > }
80
+ { imagesLoadingResults && < Alert sx = { { fontSize : '1.5em' } } action = { < Button variant = 'outlined' color = 'secondary' onClick = { ( ) => loadImagesIfNeeded ( ) } > Retry</ Button > } title = "Error loading images" severity = "error" > { imagesLoadingResults . stderr } </ Alert > }
90
81
< Typography > { imagesLoadingResults ?. stdout } </ Typography >
91
82
</ Paper >
92
83
}
93
84
94
- const hasMCPConfigured = Object . values ( mcpClientStates ) . some ( state => state . exists && state . configured )
85
+ if ( ! secrets ) {
86
+ return < Paper sx = { { padding : 2 , height : '90vh' , display : 'flex' , flexDirection : 'column' , justifyContent : 'center' , alignItems : 'center' } } >
87
+ < CircularProgress />
88
+ < Typography > Loading secrets...</ Typography >
89
+ </ Paper >
90
+ }
91
+
92
+ if ( ! catalogItems ) {
93
+ return < Paper sx = { { padding : 2 , height : '90vh' , display : 'flex' , flexDirection : 'column' , justifyContent : 'center' , alignItems : 'center' } } >
94
+ < CircularProgress />
95
+ < Typography > Loading catalog...</ Typography >
96
+ </ Paper >
97
+ }
98
+
99
+ if ( ! registryItems ) {
100
+ return < Paper sx = { { padding : 2 , height : '90vh' , display : 'flex' , flexDirection : 'column' , justifyContent : 'center' , alignItems : 'center' } } >
101
+ < CircularProgress />
102
+ < Typography > Loading registry...</ Typography >
103
+ </ Paper >
104
+ }
105
+
106
+ const hasMCPConfigured = Object . values ( mcpClientStates ) . some ( state => state . exists && state . configured ) ;
95
107
96
108
return (
97
109
< >
98
- < Dialog open = { settings . showModal } onClose = { ( ) => setSettings ( { ...settings , showModal : false } ) } fullWidth maxWidth = 'md' >
99
- < DialogTitle >
100
- < Typography variant = 'h2' sx = { { fontWeight : 'bold' , m : 2 } } > Catalog Settings</ Typography >
101
- </ DialogTitle >
102
- < DialogContent >
103
- < Settings onUpdate = { updateMCPClientStates } mcpClientStates = { mcpClientStates } settings = { settings } setSettings = { setSettings } />
104
- </ DialogContent >
105
- </ Dialog >
110
+ { settings . showModal && (
111
+ < Dialog open = { settings . showModal } fullWidth maxWidth = "xl" >
112
+ < DialogTitle >
113
+ Settings
114
+ < IconButton
115
+ aria-label = "close"
116
+ onClick = { ( ) => setSettings ( { ...settings , showModal : false } ) }
117
+ sx = { { position : 'absolute' , right : 8 , top : 8 } }
118
+ >
119
+ < Close />
120
+ </ IconButton >
121
+ </ DialogTitle >
122
+ < DialogContent >
123
+ < Suspense fallback = { < Box sx = { { display : 'flex' , justifyContent : 'center' , p : 4 } } > < CircularProgress /> </ Box > } >
124
+ < Settings
125
+ settings = { settings }
126
+ setSettings = { setSettings }
127
+ mcpClientStates = { mcpClientStates }
128
+ onUpdate = { updateMCPClientStates }
129
+ />
130
+ </ Suspense >
131
+ </ DialogContent >
132
+ </ Dialog >
133
+ ) }
134
+
135
+ { /* Replace the old PromptConfig dialog with our new ConfigurationModal */ }
136
+ { configuringItem && (
137
+ < ConfigurationModal
138
+ open = { configuringItem !== null }
139
+ onClose = { ( ) => setConfiguringItem ( null ) }
140
+ catalogItem = { configuringItem }
141
+ client = { client }
142
+ secrets = { secrets }
143
+ onSecretChange = { tryLoadSecrets }
144
+ />
145
+ ) }
146
+
106
147
< Stack direction = "column" spacing = { 1 } justifyContent = 'center' alignItems = 'center' >
107
148
< img src = { MCPCatalogLogo } alt = "MCP Catalog" height = { 100 } />
108
149
{ hasMCPConfigured ? < > </ > : < Alert action = { < Button variant = 'outlined' color = 'secondary' onClick = { ( ) => setSettings ( { ...settings , showModal : true } ) } > Configure</ Button > } severity = "error" sx = { { fontWeight : 'bold' } } > MCP Clients are not configured. Please configure MCP Clients to use the MCP Catalog.</ Alert > }
109
- < CatalogGrid settingsBadgeProps = { hasMCPConfigured ? { } : {
110
- color : hasMCPConfigured ? 'default' : 'error' ,
111
- badgeContent : '0 MCP Clients' ,
112
- sx : {
113
- width : 80 ,
114
- height : '100%' ,
115
- display : 'flex' ,
116
- justifyContent : 'center' ,
117
- alignItems : 'center' ,
118
- } ,
119
- } } showSettings = { ( ) => setSettings ( { ...settings , showModal : true } ) } registryItems = { registryItems } canRegister = { canRegister } client = { client } onRegistryChange = { loadRegistry } />
150
+ < CatalogGrid
151
+ settingsBadgeProps = { hasMCPConfigured ? { } : {
152
+ color : hasMCPConfigured ? 'default' : 'error' ,
153
+ badgeContent : '0 MCP Clients' ,
154
+ sx : {
155
+ width : 80 ,
156
+ height : '100%' ,
157
+ display : 'flex' ,
158
+ justifyContent : 'center' ,
159
+ alignItems : 'center' ,
160
+ } ,
161
+ } }
162
+ setConfiguringItem = { setConfiguringItem }
163
+ showSettings = { ( ) => setSettings ( { ...settings , showModal : true } ) }
164
+ />
120
165
</ Stack >
121
166
</ >
122
- )
123
-
167
+ ) ;
124
168
}
0 commit comments