endpoint-list.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import React, { useMemo } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { useContext } from 'use-context-selector'
  4. import { useBoolean } from 'ahooks'
  5. import {
  6. RiAddLine,
  7. RiApps2AddLine,
  8. RiBookOpenLine,
  9. } from '@remixicon/react'
  10. import EndpointModal from './endpoint-modal'
  11. import EndpointCard from './endpoint-card'
  12. import { NAME_FIELD } from './utils'
  13. import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
  14. import ActionButton from '@/app/components/base/action-button'
  15. import Tooltip from '@/app/components/base/tooltip'
  16. import Toast from '@/app/components/base/toast'
  17. import {
  18. useCreateEndpoint,
  19. useEndpointList,
  20. useInvalidateEndpointList,
  21. } from '@/service/use-endpoints'
  22. import type { PluginDetail } from '@/app/components/plugins/types'
  23. import { LanguagesSupported } from '@/i18n/language'
  24. import I18n from '@/context/i18n'
  25. import cn from '@/utils/classnames'
  26. type Props = {
  27. detail: PluginDetail
  28. }
  29. const EndpointList = ({ detail }: Props) => {
  30. const { t } = useTranslation()
  31. const { locale } = useContext(I18n)
  32. const pluginUniqueID = detail.plugin_unique_identifier
  33. const declaration = detail.declaration.endpoint
  34. const showTopBorder = detail.declaration.tool
  35. const { data } = useEndpointList(detail.plugin_id)
  36. const invalidateEndpointList = useInvalidateEndpointList()
  37. const [isShowEndpointModal, {
  38. setTrue: showEndpointModal,
  39. setFalse: hideEndpointModal,
  40. }] = useBoolean(false)
  41. const formSchemas = useMemo(() => {
  42. return toolCredentialToFormSchemas([NAME_FIELD, ...declaration.settings])
  43. }, [declaration.settings])
  44. const { mutate: createEndpoint } = useCreateEndpoint({
  45. onSuccess: async () => {
  46. await invalidateEndpointList(detail.plugin_id)
  47. hideEndpointModal()
  48. },
  49. onError: () => {
  50. Toast.notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  51. },
  52. })
  53. const handleCreate = (state: any) => createEndpoint({
  54. pluginUniqueID,
  55. state,
  56. })
  57. if (!data)
  58. return null
  59. return (
  60. <div className={cn('px-4 py-2 border-divider-subtle', showTopBorder && 'border-t')}>
  61. <div className='mb-1 h-6 flex items-center justify-between text-text-secondary system-sm-semibold-uppercase'>
  62. <div className='flex items-center gap-0.5'>
  63. {t('plugin.detailPanel.endpoints')}
  64. <Tooltip
  65. position='right'
  66. needsDelay
  67. popupClassName='w-[240px] p-4 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border'
  68. popupContent={
  69. <div className='flex flex-col gap-2'>
  70. <div className='w-8 h-8 flex items-center justify-center bg-background-default-subtle rounded-lg border-[0.5px] border-components-panel-border-subtle'>
  71. <RiApps2AddLine className='w-4 h-4 text-text-tertiary' />
  72. </div>
  73. <div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsTip')}</div>
  74. <a
  75. href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}guides/api-documentation/endpoint`}
  76. target='_blank'
  77. rel='noopener noreferrer'
  78. >
  79. <div className='inline-flex items-center gap-1 text-text-accent system-xs-regular cursor-pointer'>
  80. <RiBookOpenLine className='w-3 h-3' />
  81. {t('plugin.detailPanel.endpointsDocLink')}
  82. </div>
  83. </a>
  84. </div>
  85. }
  86. />
  87. </div>
  88. <ActionButton onClick={showEndpointModal}>
  89. <RiAddLine className='w-4 h-4' />
  90. </ActionButton>
  91. </div>
  92. {data.endpoints.length === 0 && (
  93. <div className='mb-1 p-3 flex justify-center rounded-[10px] bg-background-section text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsEmpty')}</div>
  94. )}
  95. <div className='flex flex-col gap-2'>
  96. {data.endpoints.map((item, index) => (
  97. <EndpointCard
  98. key={index}
  99. data={item}
  100. handleChange={() => invalidateEndpointList(detail.plugin_id)}
  101. />
  102. ))}
  103. </div>
  104. {isShowEndpointModal && (
  105. <EndpointModal
  106. formSchemas={formSchemas}
  107. onCancel={hideEndpointModal}
  108. onSaved={handleCreate}
  109. />
  110. )}
  111. </div>
  112. )
  113. }
  114. export default EndpointList