index.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useMemo, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import {
  6. PortalToFollowElem,
  7. PortalToFollowElemContent,
  8. PortalToFollowElemTrigger,
  9. } from '@/app/components/base/portal-to-follow-elem'
  10. import AppTrigger from '@/app/components/plugins/plugin-detail-panel/app-selector/app-trigger'
  11. import AppPicker from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker'
  12. import AppInputsPanel from '@/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel'
  13. import { useAppFullList } from '@/service/use-apps'
  14. import type { App } from '@/types/app'
  15. import type {
  16. OffsetOptions,
  17. Placement,
  18. } from '@floating-ui/react'
  19. type Props = {
  20. value?: {
  21. app_id: string
  22. inputs: Record<string, any>
  23. files?: any[]
  24. }
  25. scope?: string
  26. disabled?: boolean
  27. placement?: Placement
  28. offset?: OffsetOptions
  29. onSelect: (app: {
  30. app_id: string
  31. inputs: Record<string, any>
  32. files?: any[]
  33. }) => void
  34. supportAddCustomTool?: boolean
  35. }
  36. const AppSelector: FC<Props> = ({
  37. value,
  38. scope,
  39. disabled,
  40. placement = 'bottom',
  41. offset = 4,
  42. onSelect,
  43. }) => {
  44. const { t } = useTranslation()
  45. const [isShow, onShowChange] = useState(false)
  46. const handleTriggerClick = () => {
  47. if (disabled) return
  48. onShowChange(true)
  49. }
  50. const { data: appList } = useAppFullList()
  51. const currentAppInfo = useMemo(() => {
  52. if (!appList?.data || !value)
  53. return undefined
  54. return appList.data.find(app => app.id === value.app_id)
  55. }, [appList?.data, value])
  56. const [isShowChooseApp, setIsShowChooseApp] = useState(false)
  57. const handleSelectApp = (app: App) => {
  58. const clearValue = app.id !== value?.app_id
  59. const appValue = {
  60. app_id: app.id,
  61. inputs: clearValue ? {} : value?.inputs || {},
  62. files: clearValue ? [] : value?.files || [],
  63. }
  64. onSelect(appValue)
  65. setIsShowChooseApp(false)
  66. }
  67. const handleFormChange = (inputs: Record<string, any>) => {
  68. const newFiles = inputs['#image#']
  69. delete inputs['#image#']
  70. const newValue = {
  71. app_id: value?.app_id || '',
  72. inputs,
  73. files: newFiles ? [newFiles] : value?.files || [],
  74. }
  75. onSelect(newValue)
  76. }
  77. const formattedValue = useMemo(() => {
  78. return {
  79. app_id: value?.app_id || '',
  80. inputs: {
  81. ...value?.inputs,
  82. ...(value?.files?.length ? { '#image#': value.files[0] } : {}),
  83. },
  84. }
  85. }, [value])
  86. return (
  87. <>
  88. <PortalToFollowElem
  89. placement={placement}
  90. offset={offset}
  91. open={isShow}
  92. onOpenChange={onShowChange}
  93. >
  94. <PortalToFollowElemTrigger
  95. className='w-full'
  96. onClick={handleTriggerClick}
  97. >
  98. <AppTrigger
  99. open={isShow}
  100. appDetail={currentAppInfo}
  101. />
  102. </PortalToFollowElemTrigger>
  103. <PortalToFollowElemContent className='z-[1000]'>
  104. <div className="relative w-[389px] min-h-20 rounded-xl backdrop-blur-sm bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg">
  105. <div className='px-4 py-3 flex flex-col gap-1'>
  106. <div className='h-6 flex items-center system-sm-semibold text-text-secondary'>{t('app.appSelector.label')}</div>
  107. <AppPicker
  108. placement='bottom'
  109. offset={offset}
  110. trigger={
  111. <AppTrigger
  112. open={isShowChooseApp}
  113. appDetail={currentAppInfo}
  114. />
  115. }
  116. isShow={isShowChooseApp}
  117. onShowChange={setIsShowChooseApp}
  118. disabled={false}
  119. appList={appList?.data || []}
  120. onSelect={handleSelectApp}
  121. scope={scope || 'all'}
  122. />
  123. </div>
  124. {/* app inputs config panel */}
  125. {currentAppInfo && (
  126. <AppInputsPanel
  127. value={formattedValue}
  128. appDetail={currentAppInfo}
  129. onFormChange={handleFormChange}
  130. />
  131. )}
  132. </div>
  133. </PortalToFollowElemContent>
  134. </PortalToFollowElem>
  135. </>
  136. )
  137. }
  138. export default React.memo(AppSelector)