您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

provider-card.tsx 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. 'use client'
  2. import { useCallback, useState } from 'react'
  3. import { useBoolean } from 'ahooks'
  4. import { useTranslation } from 'react-i18next'
  5. import { useAppContext } from '@/context/app-context'
  6. import { RiHammerFill } from '@remixicon/react'
  7. import Indicator from '@/app/components/header/indicator'
  8. import Icon from '@/app/components/plugins/card/base/card-icon'
  9. import { useFormatTimeFromNow } from './hooks'
  10. import type { ToolWithProvider } from '../../workflow/types'
  11. import Confirm from '@/app/components/base/confirm'
  12. import MCPModal from './modal'
  13. import OperationDropdown from './detail/operation-dropdown'
  14. import { useDeleteMCP, useUpdateMCP } from '@/service/use-tools'
  15. import cn from '@/utils/classnames'
  16. type Props = {
  17. currentProvider?: ToolWithProvider
  18. data: ToolWithProvider
  19. handleSelect: (providerID: string) => void
  20. onUpdate: (providerID: string) => void
  21. onDeleted: () => void
  22. }
  23. const MCPCard = ({
  24. currentProvider,
  25. data,
  26. onUpdate,
  27. handleSelect,
  28. onDeleted,
  29. }: Props) => {
  30. const { t } = useTranslation()
  31. const { formatTimeFromNow } = useFormatTimeFromNow()
  32. const { isCurrentWorkspaceManager } = useAppContext()
  33. const { mutateAsync: updateMCP } = useUpdateMCP({})
  34. const { mutateAsync: deleteMCP } = useDeleteMCP({})
  35. const [isOperationShow, setIsOperationShow] = useState(false)
  36. const [isShowUpdateModal, {
  37. setTrue: showUpdateModal,
  38. setFalse: hideUpdateModal,
  39. }] = useBoolean(false)
  40. const [isShowDeleteConfirm, {
  41. setTrue: showDeleteConfirm,
  42. setFalse: hideDeleteConfirm,
  43. }] = useBoolean(false)
  44. const [deleting, {
  45. setTrue: showDeleting,
  46. setFalse: hideDeleting,
  47. }] = useBoolean(false)
  48. const handleUpdate = useCallback(async (form: any) => {
  49. const res = await updateMCP({
  50. ...form,
  51. provider_id: data.id,
  52. })
  53. if ((res as any)?.result === 'success') {
  54. hideUpdateModal()
  55. onUpdate(data.id)
  56. }
  57. }, [data, updateMCP, hideUpdateModal, onUpdate])
  58. const handleDelete = useCallback(async () => {
  59. showDeleting()
  60. const res = await deleteMCP(data.id)
  61. hideDeleting()
  62. if ((res as any)?.result === 'success') {
  63. hideDeleteConfirm()
  64. onDeleted()
  65. }
  66. }, [showDeleting, deleteMCP, data.id, hideDeleting, hideDeleteConfirm, onDeleted])
  67. return (
  68. <div
  69. onClick={() => handleSelect(data.id)}
  70. className={cn(
  71. 'group relative flex cursor-pointer flex-col rounded-xl border-[1.5px] border-transparent bg-components-card-bg shadow-xs hover:bg-components-card-bg-alt hover:shadow-md',
  72. currentProvider?.id === data.id && 'border-components-option-card-option-selected-border bg-components-card-bg-alt',
  73. )}
  74. >
  75. <div className='flex grow items-center gap-3 rounded-t-xl p-4'>
  76. <div className='shrink-0 overflow-hidden rounded-xl border border-components-panel-border-subtle'>
  77. <Icon src={data.icon} />
  78. </div>
  79. <div className='grow'>
  80. <div className='system-md-semibold mb-1 truncate text-text-secondary' title={data.name}>{data.name}</div>
  81. <div className='system-xs-regular text-text-tertiary'>{data.server_identifier}</div>
  82. </div>
  83. </div>
  84. <div className='flex items-center gap-1 rounded-b-xl pb-2.5 pl-4 pr-2.5 pt-1.5'>
  85. <div className='flex w-0 grow items-center gap-2'>
  86. <div className='flex items-center gap-1'>
  87. <RiHammerFill className='h-3 w-3 shrink-0 text-text-quaternary' />
  88. {data.tools.length > 0 && (
  89. <div className='system-xs-regular shrink-0 text-text-tertiary'>{t('tools.mcp.toolsCount', { count: data.tools.length })}</div>
  90. )}
  91. {!data.tools.length && (
  92. <div className='system-xs-regular shrink-0 text-text-tertiary'>{t('tools.mcp.noTools')}</div>
  93. )}
  94. </div>
  95. <div className={cn('system-xs-regular text-divider-deep', (!data.is_team_authorization || !data.tools.length) && 'sm:hidden')}>/</div>
  96. <div className={cn('system-xs-regular truncate text-text-tertiary', (!data.is_team_authorization || !data.tools.length) && ' sm:hidden')} title={`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at! * 1000)}`}>{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at! * 1000)}`}</div>
  97. </div>
  98. {data.is_team_authorization && data.tools.length > 0 && <Indicator color='green' className='shrink-0' />}
  99. {(!data.is_team_authorization || !data.tools.length) && (
  100. <div className='system-xs-medium flex shrink-0 items-center gap-1 rounded-md border border-util-colors-red-red-500 bg-components-badge-bg-red-soft px-1.5 py-0.5 text-util-colors-red-red-500'>
  101. {t('tools.mcp.noConfigured')}
  102. <Indicator color='red' />
  103. </div>
  104. )}
  105. </div>
  106. {isCurrentWorkspaceManager && (
  107. <div className={cn('absolute right-2.5 top-2.5 hidden group-hover:block', isOperationShow && 'block')} onClick={e => e.stopPropagation()}>
  108. <OperationDropdown
  109. inCard
  110. onOpenChange={setIsOperationShow}
  111. onEdit={showUpdateModal}
  112. onRemove={showDeleteConfirm}
  113. />
  114. </div>
  115. )}
  116. {isShowUpdateModal && (
  117. <MCPModal
  118. data={data}
  119. show={isShowUpdateModal}
  120. onConfirm={handleUpdate}
  121. onHide={hideUpdateModal}
  122. />
  123. )}
  124. {isShowDeleteConfirm && (
  125. <Confirm
  126. isShow
  127. title={t('tools.mcp.delete')}
  128. content={
  129. <div>
  130. {t('tools.mcp.deleteConfirmTitle', { mcp: data.name })}
  131. </div>
  132. }
  133. onCancel={hideDeleteConfirm}
  134. onConfirm={handleDelete}
  135. isLoading={deleting}
  136. isDisabled={deleting}
  137. />
  138. )}
  139. </div>
  140. )
  141. }
  142. export default MCPCard