chore: add toggle loading sever MCP (#5225)

* chore: add toggle loading sever mcp

* chore: remove duplicate classname

* chore: remove log

* chore: remove log

* fix: save server config

---------

Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
Faisal Amir 2025-06-09 23:56:49 +07:00 committed by GitHub
parent 891c149f1b
commit 8ba4b0be36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 28 deletions

View File

@ -2,20 +2,27 @@ import * as React from 'react'
import * as SwitchPrimitive from '@radix-ui/react-switch' import * as SwitchPrimitive from '@radix-ui/react-switch'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { IconLoader2 } from '@tabler/icons-react'
function Switch({ type SwitchProps = React.ComponentProps<typeof SwitchPrimitive.Root> & {
className, loading?: boolean
...props }
}: React.ComponentProps<typeof SwitchPrimitive.Root>) { function Switch({ loading, className, ...props }: SwitchProps) {
return ( return (
<SwitchPrimitive.Root <SwitchPrimitive.Root
data-slot="switch" data-slot="switch"
className={cn( className={cn(
'peer cursor-pointer data-[state=checked]:bg-accent data-[state=unchecked]:bg-main-view-fg/20 focus-visible:border-none inline-flex h-[18px] w-8.5 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50', 'relative peer cursor-pointer data-[state=checked]:bg-accent data-[state=unchecked]:bg-main-view-fg/20 focus-visible:border-none inline-flex h-[18px] w-8.5 shrink-0 items-center rounded-full border border-transparent shadow-xs outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 transition-all',
loading && 'w-4.5 pointer-events-none',
className className
)} )}
{...props} {...props}
> >
{loading && (
<div className="absolute inset-0 flex items-center justify-center z-10 size-3.5 top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2">
<IconLoader2 className="animate-spin text-main-view-fg/50" />
</div>
)}
<SwitchPrimitive.Thumb <SwitchPrimitive.Thumb
data-slot="switch-thumb" data-slot="switch-thumb"
className={cn( className={cn(

View File

@ -216,7 +216,6 @@ const DropdownModelProvider = ({
// Add the filtered items to their respective groups // Add the filtered items to their respective groups
filteredItems.forEach((item) => { filteredItems.forEach((item) => {
const providerKey = item.provider.provider const providerKey = item.provider.provider
console.log(providerKey, 'providerKey')
if (!groups[providerKey]) { if (!groups[providerKey]) {
groups[providerKey] = [] groups[providerKey] = []
} }

View File

@ -19,6 +19,7 @@ type MCPServerStoreState = {
mcpServers: MCPServers mcpServers: MCPServers
loading: boolean loading: boolean
deletedServerKeys: string[] deletedServerKeys: string[]
getServerConfig: (key: string) => MCPServerConfig | undefined
setLeftPanel: (value: boolean) => void setLeftPanel: (value: boolean) => void
addServer: (key: string, config: MCPServerConfig) => void addServer: (key: string, config: MCPServerConfig) => void
editServer: (key: string, config: MCPServerConfig) => void editServer: (key: string, config: MCPServerConfig) => void
@ -34,7 +35,11 @@ export const useMCPServers = create<MCPServerStoreState>()((set, get) => ({
loading: false, loading: false,
deletedServerKeys: [], deletedServerKeys: [],
setLeftPanel: (value) => set({ open: value }), setLeftPanel: (value) => set({ open: value }),
getServerConfig: (key) => {
const mcpServers = get().mcpServers
// Return the server configuration if it exists, otherwise return undefined
return mcpServers[key] ? mcpServers[key] : undefined
},
// Add a new MCP server or update if the key already exists // Add a new MCP server or update if the key already exists
addServer: (key, config) => addServer: (key, config) =>
set((state) => { set((state) => {

View File

@ -35,6 +35,7 @@ function MCPServers() {
deleteServer, deleteServer,
syncServers, syncServers,
syncServersAndRestart, syncServersAndRestart,
getServerConfig,
} = useMCPServers() } = useMCPServers()
const { allowAllMCPPermissions, setAllowAllMCPPermissions } = const { allowAllMCPPermissions, setAllowAllMCPPermissions } =
useToolApproval() useToolApproval()
@ -56,6 +57,9 @@ function MCPServers() {
MCPServerConfig | Record<string, MCPServerConfig> | undefined MCPServerConfig | Record<string, MCPServerConfig> | undefined
>(undefined) >(undefined)
const [connectedServers, setConnectedServers] = useState<string[]>([]) const [connectedServers, setConnectedServers] = useState<string[]>([])
const [loadingServers, setLoadingServers] = useState<{
[key: string]: boolean
}>({})
const handleOpenDialog = (serverKey?: string) => { const handleOpenDialog = (serverKey?: string) => {
if (serverKey) { if (serverKey) {
@ -70,10 +74,13 @@ function MCPServers() {
setOpen(true) setOpen(true)
} }
const handleSaveServer = (name: string, config: MCPServerConfig) => { const handleSaveServer = async (name: string, config: MCPServerConfig) => {
try {
await toggleServer(name, false)
} catch (error) {
console.error('Error deactivating server:', error)
}
if (editingKey) { if (editingKey) {
// Edit existing server
// If server name changed, delete old one and add new one // If server name changed, delete old one and add new one
if (editingKey !== name) { if (editingKey !== name) {
deleteServer(editingKey) deleteServer(editingKey)
@ -85,7 +92,9 @@ function MCPServers() {
// Add new server // Add new server
addServer(name, config) addServer(name, config)
} }
syncServersAndRestart()
syncServers()
await toggleServer(name, true)
} }
const handleEdit = (serverKey: string) => { const handleEdit = (serverKey: string) => {
@ -105,7 +114,7 @@ function MCPServers() {
} }
} }
const handleOpenJsonEditor = (serverKey?: string) => { const handleOpenJsonEditor = async (serverKey?: string) => {
if (serverKey) { if (serverKey) {
// Edit single server JSON // Edit single server JSON
setJsonServerName(serverKey) setJsonServerName(serverKey)
@ -118,12 +127,19 @@ function MCPServers() {
setJsonEditorOpen(true) setJsonEditorOpen(true)
} }
const handleSaveJson = ( const handleSaveJson = async (
data: MCPServerConfig | Record<string, MCPServerConfig> data: MCPServerConfig | Record<string, MCPServerConfig>
) => { ) => {
if (jsonServerName) { if (jsonServerName) {
try {
await toggleServer(jsonServerName, false)
} catch (error) {
console.error('Error deactivating server:', error)
}
// Save single server // Save single server
editServer(jsonServerName, data as MCPServerConfig) editServer(jsonServerName, data as MCPServerConfig)
syncServers()
toggleServer(jsonServerName, true)
} else { } else {
// Save all servers // Save all servers
// Clear existing servers first // Clear existing servers first
@ -138,23 +154,24 @@ function MCPServers() {
} }
) )
} }
syncServersAndRestart()
} }
const toggleServer = (serverKey: string, active: boolean) => { const toggleServer = (serverKey: string, active: boolean) => {
if (serverKey) if (serverKey) {
if (active) setLoadingServers((prev) => ({ ...prev, [serverKey]: true }))
const config = getServerConfig(serverKey)
if (active && config) {
invoke('activate_mcp_server', { invoke('activate_mcp_server', {
name: serverKey, name: serverKey,
config: { config: {
...(mcpServers[serverKey] as MCPServerConfig), ...(config ?? (mcpServers[serverKey] as MCPServerConfig)),
active, active,
}, },
}) })
.then(() => { .then(() => {
// Save single server // Save single server
editServer(serverKey, { editServer(serverKey, {
...(mcpServers[serverKey] as MCPServerConfig), ...(config ?? (mcpServers[serverKey] as MCPServerConfig)),
active, active,
}) })
syncServers() syncServers()
@ -164,25 +181,30 @@ function MCPServers() {
getConnectedServers().then(setConnectedServers) getConnectedServers().then(setConnectedServers)
}) })
.catch((error) => { .catch((error) => {
editServer(serverKey, {
...(config ?? (mcpServers[serverKey] as MCPServerConfig)),
active: false,
})
toast.error(error, { toast.error(error, {
description: description:
'Please check the parameters according to the tutorial.', 'Please check the parameters according to the tutorial.',
}) })
}) })
else { .finally(() => {
setLoadingServers((prev) => ({ ...prev, [serverKey]: false }))
})
} else {
editServer(serverKey, { editServer(serverKey, {
...(mcpServers[serverKey] as MCPServerConfig), ...(config ?? (mcpServers[serverKey] as MCPServerConfig)),
active, active,
}) })
syncServers() syncServers()
invoke('deactivate_mcp_server', { name: serverKey }) invoke('deactivate_mcp_server', { name: serverKey }).finally(() => {
.catch((error) => { getConnectedServers().then(setConnectedServers)
toast.error(`Failed to deactivate server ${serverKey}: ${error}`) setLoadingServers((prev) => ({ ...prev, [serverKey]: false }))
}) })
.finally(() => {
getConnectedServers().then(setConnectedServers)
})
} }
}
} }
useEffect(() => { useEffect(() => {
@ -334,7 +356,8 @@ function MCPServers() {
</div> </div>
<div className="ml-2"> <div className="ml-2">
<Switch <Switch
checked={config.active === false ? false : true} checked={config.active}
loading={!!loadingServers[key]}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
toggleServer(key, checked) toggleServer(key, checked)
} }