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

index.tsx 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {
  2. useCallback,
  3. useState,
  4. } from 'react'
  5. import { useTranslation } from 'react-i18next'
  6. import {
  7. RiEditBoxLine,
  8. RiExpandRightLine,
  9. RiLayoutLeft2Line,
  10. } from '@remixicon/react'
  11. import { useChatWithHistoryContext } from '../context'
  12. import AppIcon from '@/app/components/base/app-icon'
  13. import ActionButton from '@/app/components/base/action-button'
  14. import Button from '@/app/components/base/button'
  15. import List from '@/app/components/base/chat/chat-with-history/sidebar/list'
  16. import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
  17. import Confirm from '@/app/components/base/confirm'
  18. import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
  19. import DifyLogo from '@/app/components/base/logo/dify-logo'
  20. import type { ConversationItem } from '@/models/share'
  21. import cn from '@/utils/classnames'
  22. import { AccessMode } from '@/models/access-control'
  23. import { useGlobalPublicStore } from '@/context/global-public-context'
  24. type Props = {
  25. isPanel?: boolean
  26. }
  27. const Sidebar = ({ isPanel }: Props) => {
  28. const { t } = useTranslation()
  29. const {
  30. isInstalledApp,
  31. accessMode,
  32. appData,
  33. handleNewConversation,
  34. pinnedConversationList,
  35. conversationList,
  36. currentConversationId,
  37. handleChangeConversation,
  38. handlePinConversation,
  39. handleUnpinConversation,
  40. conversationRenaming,
  41. handleRenameConversation,
  42. handleDeleteConversation,
  43. sidebarCollapseState,
  44. handleSidebarCollapse,
  45. isMobile,
  46. isResponding,
  47. } = useChatWithHistoryContext()
  48. const isSidebarCollapsed = sidebarCollapseState
  49. const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
  50. const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
  51. const [showRename, setShowRename] = useState<ConversationItem | null>(null)
  52. const handleOperate = useCallback((type: string, item: ConversationItem) => {
  53. if (type === 'pin')
  54. handlePinConversation(item.id)
  55. if (type === 'unpin')
  56. handleUnpinConversation(item.id)
  57. if (type === 'delete')
  58. setShowConfirm(item)
  59. if (type === 'rename')
  60. setShowRename(item)
  61. }, [handlePinConversation, handleUnpinConversation])
  62. const handleCancelConfirm = useCallback(() => {
  63. setShowConfirm(null)
  64. }, [])
  65. const handleDelete = useCallback(() => {
  66. if (showConfirm)
  67. handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
  68. }, [showConfirm, handleDeleteConversation, handleCancelConfirm])
  69. const handleCancelRename = useCallback(() => {
  70. setShowRename(null)
  71. }, [])
  72. const handleRename = useCallback((newName: string) => {
  73. if (showRename)
  74. handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
  75. }, [showRename, handleRenameConversation, handleCancelRename])
  76. return (
  77. <div className={cn(
  78. 'flex w-full grow flex-col',
  79. isPanel && 'rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-bg shadow-lg',
  80. )}>
  81. <div className={cn(
  82. 'flex shrink-0 items-center gap-3 p-3 pr-2',
  83. )}>
  84. <div className='shrink-0'>
  85. <AppIcon
  86. size='large'
  87. iconType={appData?.site.icon_type}
  88. icon={appData?.site.icon}
  89. background={appData?.site.icon_background}
  90. imageUrl={appData?.site.icon_url}
  91. />
  92. </div>
  93. <div className={cn('system-md-semibold grow truncate text-text-secondary')}>{appData?.site.title}</div>
  94. {!isMobile && isSidebarCollapsed && (
  95. <ActionButton size='l' onClick={() => handleSidebarCollapse(false)}>
  96. <RiExpandRightLine className='h-[18px] w-[18px]' />
  97. </ActionButton>
  98. )}
  99. {!isMobile && !isSidebarCollapsed && (
  100. <ActionButton size='l' onClick={() => handleSidebarCollapse(true)}>
  101. <RiLayoutLeft2Line className='h-[18px] w-[18px]' />
  102. </ActionButton>
  103. )}
  104. </div>
  105. <div className='shrink-0 px-3 py-4'>
  106. <Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}>
  107. <RiEditBoxLine className='mr-1 h-4 w-4' />
  108. {t('share.chat.newChat')}
  109. </Button>
  110. </div>
  111. <div className='h-0 grow space-y-2 overflow-y-auto px-3 pt-4'>
  112. {/* pinned list */}
  113. {!!pinnedConversationList.length && (
  114. <div className='mb-4'>
  115. <List
  116. isPin
  117. title={t('share.chat.pinnedTitle') || ''}
  118. list={pinnedConversationList}
  119. onChangeConversation={handleChangeConversation}
  120. onOperate={handleOperate}
  121. currentConversationId={currentConversationId}
  122. />
  123. </div>
  124. )}
  125. {!!conversationList.length && (
  126. <List
  127. title={(pinnedConversationList.length && t('share.chat.unpinnedTitle')) || ''}
  128. list={conversationList}
  129. onChangeConversation={handleChangeConversation}
  130. onOperate={handleOperate}
  131. currentConversationId={currentConversationId}
  132. />
  133. )}
  134. </div>
  135. <div className='flex shrink-0 items-center justify-between p-3'>
  136. <MenuDropdown hideLogout={isInstalledApp || accessMode === AccessMode.PUBLIC} placement='top-start' data={appData?.site} />
  137. {/* powered by */}
  138. <div className='shrink-0'>
  139. {!appData?.custom_config?.remove_webapp_brand && (
  140. <div className={cn(
  141. 'flex shrink-0 items-center gap-1.5 px-1',
  142. )}>
  143. <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
  144. {systemFeatures.branding.enabled ? (
  145. <img src={systemFeatures.branding.login_page_logo} alt='logo' className='block h-5 w-auto' />
  146. ) : (
  147. <DifyLogo size='small' />)
  148. }
  149. </div>
  150. )}
  151. </div>
  152. {!!showConfirm && (
  153. <Confirm
  154. title={t('share.chat.deleteConversation.title')}
  155. content={t('share.chat.deleteConversation.content') || ''}
  156. isShow
  157. onCancel={handleCancelConfirm}
  158. onConfirm={handleDelete}
  159. />
  160. )}
  161. {showRename && (
  162. <RenameModal
  163. isShow
  164. onClose={handleCancelRename}
  165. saveLoading={conversationRenaming}
  166. name={showRename?.name || ''}
  167. onSave={handleRename}
  168. />
  169. )}
  170. </div>
  171. </div>
  172. )
  173. }
  174. export default Sidebar