feat: add Radix UI radio group component and update MCP settings UI

- Introduced a new RadioGroup component using Radix UI for better UI consistency.
- Updated MCPProviderSettings to utilize the new RadioGroup and adjusted button styles for improved UX.
- Added Radix UI radio group dependency to package.json and yarn.lock.
This commit is contained in:
kangfenmao 2025-10-18 18:31:00 +08:00
parent 3417acafe2
commit d2b6433609
6 changed files with 87 additions and 14 deletions

View File

@ -49,6 +49,7 @@
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-use-controllable-state": "^1.2.2", "@radix-ui/react-use-controllable-state": "^1.2.2",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",

View File

@ -0,0 +1,28 @@
import { cn } from '@cherrystudio/ui/utils/index'
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
import { CircleIcon } from 'lucide-react'
import * as React from 'react'
function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return <RadioGroupPrimitive.Root data-slot="radio-group" className={cn('grid gap-3', className)} {...props} />
}
function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center">
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
export { RadioGroup, RadioGroupItem }

View File

@ -1,7 +1,7 @@
import { Flex, RowFlex } from '@cherrystudio/ui' import { Button, Flex, RowFlex } from '@cherrystudio/ui'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import type { MCPServer } from '@renderer/types' import type { MCPServer } from '@renderer/types'
import { Button, Divider, Input, Space } from 'antd' import { Divider, Input, Space } from 'antd'
import Link from 'antd/es/typography/Link' import Link from 'antd/es/typography/Link'
import { SquareArrowOutUpRight } from 'lucide-react' import { SquareArrowOutUpRight } from 'lucide-react'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
@ -17,7 +17,7 @@ interface Props {
} }
const McpProviderSettings: React.FC<Props> = ({ provider, existingServers }) => { const McpProviderSettings: React.FC<Props> = ({ provider, existingServers }) => {
const { addMCPServer, updateMCPServer } = useMCPServers() const { addMCPServer } = useMCPServers()
const [isFetching, setIsFetching] = useState(false) const [isFetching, setIsFetching] = useState(false)
const [token, setToken] = useState<string>('') const [token, setToken] = useState<string>('')
const [availableServers, setAvailableServers] = useState<MCPServer[]>([]) const [availableServers, setAvailableServers] = useState<MCPServer[]>([])
@ -64,11 +64,13 @@ const McpProviderSettings: React.FC<Props> = ({ provider, existingServers }) =>
<ProviderName>{provider.name}</ProviderName> <ProviderName>{provider.name}</ProviderName>
{provider.discoverUrl && ( {provider.discoverUrl && (
<Link target="_blank" href={provider.discoverUrl} style={{ display: 'flex' }}> <Link target="_blank" href={provider.discoverUrl} style={{ display: 'flex' }}>
<Button type="text" size="small" icon={<SquareArrowOutUpRight size={14} />} /> <Button variant="flat" size="sm">
<SquareArrowOutUpRight size={14} />
</Button>
</Link> </Link>
)} )}
</Flex> </Flex>
<Button type="primary" onClick={handleFetch} loading={isFetching} disabled={isFetchDisabled}> <Button variant="solid" onClick={handleFetch} isLoading={isFetching} isDisabled={isFetchDisabled}>
{t('settings.mcp.fetch.button', 'Fetch Servers')} {t('settings.mcp.fetch.button', 'Fetch Servers')}
</Button> </Button>
</ProviderHeader> </ProviderHeader>
@ -105,19 +107,18 @@ const McpProviderSettings: React.FC<Props> = ({ provider, existingServers }) =>
<ServerDescription>{server.description}</ServerDescription> <ServerDescription>{server.description}</ServerDescription>
</ServerInfo> </ServerInfo>
{(() => { {(() => {
const isAlreadyAdded = existingServers.some(existing => existing.id === server.id) const isAlreadyAdded = existingServers.some((existing) => existing.id === server.id)
return ( return (
<Button <Button
type={isAlreadyAdded ? 'default' : 'primary'} variant={isAlreadyAdded ? 'bordered' : 'solid'}
size="small" size="sm"
disabled={isAlreadyAdded} disabled={isAlreadyAdded}
onClick={() => { onClick={() => {
if (!isAlreadyAdded) { if (!isAlreadyAdded) {
addMCPServer(server) addMCPServer(server)
window.toast.success(t('settings.mcp.server.added', 'MCP server added')) window.toast.success(t('settings.mcp.server.added', 'MCP server added'))
} }
}} }}>
>
{isAlreadyAdded ? t('settings.mcp.server.added', 'Added') : t('settings.mcp.add.server', 'Add')} {isAlreadyAdded ? t('settings.mcp.server.added', 'Added') : t('settings.mcp.add.server', 'Add')}
</Button> </Button>
) )

View File

@ -1,9 +1,10 @@
import type { MCPServer } from '@renderer/types'
import { getAI302Token, saveAI302Token, syncAi302Servers } from './302ai' import { getAI302Token, saveAI302Token, syncAi302Servers } from './302ai'
import { getBailianToken, saveBailianToken, syncBailianServers } from './bailian' import { getBailianToken, saveBailianToken, syncBailianServers } from './bailian'
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './lanyun' import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './lanyun'
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './modelscope' import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './modelscope'
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './tokenflux' import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './tokenflux'
import type { MCPServer } from '@renderer/types'
export interface ProviderConfig { export interface ProviderConfig {
key: string key: string

View File

@ -17,7 +17,7 @@ export const MCPRoutes = {
tokenflux: '/settings/mcp/tokenflux', tokenflux: '/settings/mcp/tokenflux',
lanyun: '/settings/mcp/lanyun', lanyun: '/settings/mcp/lanyun',
'302ai': '/settings/mcp/302ai', '302ai': '/settings/mcp/302ai',
bailian: '/settings/mcp/bailian', bailian: '/settings/mcp/bailian'
} as const } as const
/** /**

View File

@ -1920,6 +1920,7 @@ __metadata:
"@heroui/react": "npm:^2.8.4" "@heroui/react": "npm:^2.8.4"
"@radix-ui/react-dialog": "npm:^1.1.15" "@radix-ui/react-dialog": "npm:^1.1.15"
"@radix-ui/react-popover": "npm:^1.1.15" "@radix-ui/react-popover": "npm:^1.1.15"
"@radix-ui/react-radio-group": "npm:^1.3.8"
"@radix-ui/react-slot": "npm:^1.2.3" "@radix-ui/react-slot": "npm:^1.2.3"
"@radix-ui/react-use-controllable-state": "npm:^1.2.2" "@radix-ui/react-use-controllable-state": "npm:^1.2.2"
"@storybook/addon-docs": "npm:^9.1.6" "@storybook/addon-docs": "npm:^9.1.6"
@ -7323,6 +7324,34 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@radix-ui/react-radio-group@npm:^1.3.8":
version: 1.3.8
resolution: "@radix-ui/react-radio-group@npm:1.3.8"
dependencies:
"@radix-ui/primitive": "npm:1.1.3"
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-context": "npm:1.1.2"
"@radix-ui/react-direction": "npm:1.1.1"
"@radix-ui/react-presence": "npm:1.1.5"
"@radix-ui/react-primitive": "npm:2.1.3"
"@radix-ui/react-roving-focus": "npm:1.1.11"
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
"@radix-ui/react-use-previous": "npm:1.1.1"
"@radix-ui/react-use-size": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10c0/23af8e8b833da1fc4aa4e67c3607dedee4fc5b39278d2e2b820bec7f7b3c0891b006a8a35c57ba436ddf18735bbd8dad9a598d14632a328753a875fde447975c
languageName: node
linkType: hard
"@radix-ui/react-roving-focus@npm:1.1.11": "@radix-ui/react-roving-focus@npm:1.1.11":
version: 1.1.11 version: 1.1.11
resolution: "@radix-ui/react-roving-focus@npm:1.1.11" resolution: "@radix-ui/react-roving-focus@npm:1.1.11"
@ -7437,6 +7466,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@radix-ui/react-use-previous@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-previous@npm:1.1.1"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/52f1089d941491cd59b7f52a5679a14e9381711419a0557ce0f3bc9a4c117078224efec54dcced41a3653a13a386a7b6ec75435d61a273e8b9f5d00235f2b182
languageName: node
linkType: hard
"@radix-ui/react-use-rect@npm:1.1.1": "@radix-ui/react-use-rect@npm:1.1.1":
version: 1.1.1 version: 1.1.1
resolution: "@radix-ui/react-use-rect@npm:1.1.1" resolution: "@radix-ui/react-use-rect@npm:1.1.1"