Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

headers-input.tsx 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use client'
  2. import React, { useCallback } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { RiAddLine, RiDeleteBinLine } from '@remixicon/react'
  5. import Input from '@/app/components/base/input'
  6. import Button from '@/app/components/base/button'
  7. import ActionButton from '@/app/components/base/action-button'
  8. import cn from '@/utils/classnames'
  9. export type HeaderItem = {
  10. key: string
  11. value: string
  12. }
  13. type Props = {
  14. headers: Record<string, string>
  15. onChange: (headers: Record<string, string>) => void
  16. readonly?: boolean
  17. isMasked?: boolean
  18. }
  19. const HeadersInput = ({
  20. headers,
  21. onChange,
  22. readonly = false,
  23. isMasked = false,
  24. }: Props) => {
  25. const { t } = useTranslation()
  26. const headerItems = Object.entries(headers).map(([key, value]) => ({ key, value }))
  27. const handleItemChange = useCallback((index: number, field: 'key' | 'value', value: string) => {
  28. const newItems = [...headerItems]
  29. newItems[index] = { ...newItems[index], [field]: value }
  30. const newHeaders = newItems.reduce((acc, item) => {
  31. if (item.key.trim())
  32. acc[item.key.trim()] = item.value
  33. return acc
  34. }, {} as Record<string, string>)
  35. onChange(newHeaders)
  36. }, [headerItems, onChange])
  37. const handleRemoveItem = useCallback((index: number) => {
  38. const newItems = headerItems.filter((_, i) => i !== index)
  39. const newHeaders = newItems.reduce((acc, item) => {
  40. if (item.key.trim())
  41. acc[item.key.trim()] = item.value
  42. return acc
  43. }, {} as Record<string, string>)
  44. onChange(newHeaders)
  45. }, [headerItems, onChange])
  46. const handleAddItem = useCallback(() => {
  47. const newHeaders = { ...headers, '': '' }
  48. onChange(newHeaders)
  49. }, [headers, onChange])
  50. if (headerItems.length === 0) {
  51. return (
  52. <div className='space-y-2'>
  53. <div className='body-xs-regular text-text-tertiary'>
  54. {t('tools.mcp.modal.noHeaders')}
  55. </div>
  56. {!readonly && (
  57. <Button
  58. variant='secondary'
  59. size='small'
  60. onClick={handleAddItem}
  61. className='w-full'
  62. >
  63. <RiAddLine className='mr-1 h-4 w-4' />
  64. {t('tools.mcp.modal.addHeader')}
  65. </Button>
  66. )}
  67. </div>
  68. )
  69. }
  70. return (
  71. <div className='space-y-2'>
  72. {isMasked && (
  73. <div className='body-xs-regular text-text-tertiary'>
  74. {t('tools.mcp.modal.maskedHeadersTip')}
  75. </div>
  76. )}
  77. <div className='overflow-hidden rounded-lg border border-divider-regular'>
  78. <div className='system-xs-medium-uppercase bg-background-secondary flex h-7 items-center leading-7 text-text-tertiary'>
  79. <div className='h-full w-1/2 border-r border-divider-regular pl-3'>{t('tools.mcp.modal.headerKey')}</div>
  80. <div className='h-full w-1/2 pl-3 pr-1'>{t('tools.mcp.modal.headerValue')}</div>
  81. </div>
  82. {headerItems.map((item, index) => (
  83. <div key={index} className={cn(
  84. 'flex items-center border-divider-regular',
  85. index < headerItems.length - 1 && 'border-b',
  86. )}>
  87. <div className='w-1/2 border-r border-divider-regular'>
  88. <Input
  89. value={item.key}
  90. onChange={e => handleItemChange(index, 'key', e.target.value)}
  91. placeholder={t('tools.mcp.modal.headerKeyPlaceholder')}
  92. className='rounded-none border-0'
  93. readOnly={readonly}
  94. />
  95. </div>
  96. <div className='flex w-1/2 items-center'>
  97. <Input
  98. value={item.value}
  99. onChange={e => handleItemChange(index, 'value', e.target.value)}
  100. placeholder={t('tools.mcp.modal.headerValuePlaceholder')}
  101. className='flex-1 rounded-none border-0'
  102. readOnly={readonly}
  103. />
  104. {!readonly && headerItems.length > 1 && (
  105. <ActionButton
  106. onClick={() => handleRemoveItem(index)}
  107. className='mr-2'
  108. >
  109. <RiDeleteBinLine className='h-4 w-4 text-text-destructive' />
  110. </ActionButton>
  111. )}
  112. </div>
  113. </div>
  114. ))}
  115. </div>
  116. {!readonly && (
  117. <Button
  118. variant='secondary'
  119. size='small'
  120. onClick={handleAddItem}
  121. className='w-full'
  122. >
  123. <RiAddLine className='mr-1 h-4 w-4' />
  124. {t('tools.mcp.modal.addHeader')}
  125. </Button>
  126. )}
  127. </div>
  128. )
  129. }
  130. export default React.memo(HeadersInput)