index.tsx 6.6 KB

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 { useGlobalPublicStore } from '@/context/global-public-context'
  23. type Props = {
  24. isPanel?: boolean
  25. }
  26. const Sidebar = ({ isPanel }: Props) => {
  27. const { t } = useTranslation()
  28. const {
  29. isInstalledApp,
  30. appData,
  31. handleNewConversation,
  32. pinnedConversationList,
  33. conversationList,
  34. currentConversationId,
  35. handleChangeConversation,
  36. handlePinConversation,
  37. handleUnpinConversation,
  38. conversationRenaming,
  39. handleRenameConversation,
  40. handleDeleteConversation,
  41. sidebarCollapseState,
  42. handleSidebarCollapse,
  43. isMobile,
  44. isResponding,
  45. } = useChatWithHistoryContext()
  46. const isSidebarCollapsed = sidebarCollapseState
  47. const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
  48. const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
  49. const [showRename, setShowRename] = useState<ConversationItem | null>(null)
  50. const handleOperate = useCallback((type: string, item: ConversationItem) => {
  51. if (type === 'pin')
  52. handlePinConversation(item.id)
  53. if (type === 'unpin')
  54. handleUnpinConversation(item.id)
  55. if (type === 'delete')
  56. setShowConfirm(item)
  57. if (type === 'rename')
  58. setShowRename(item)
  59. }, [handlePinConversation, handleUnpinConversation])
  60. const handleCancelConfirm = useCallback(() => {
  61. setShowConfirm(null)
  62. }, [])
  63. const handleDelete = useCallback(() => {
  64. if (showConfirm)
  65. handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
  66. }, [showConfirm, handleDeleteConversation, handleCancelConfirm])
  67. const handleCancelRename = useCallback(() => {
  68. setShowRename(null)
  69. }, [])
  70. const handleRename = useCallback((newName: string) => {
  71. if (showRename)
  72. handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
  73. }, [showRename, handleRenameConversation, handleCancelRename])
  74. return (
  75. <div className={cn(
  76. 'flex w-full grow flex-col',
  77. isPanel && 'rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-bg shadow-lg',
  78. )}>
  79. <div className={cn(
  80. 'flex shrink-0 items-center gap-3 p-3 pr-2',
  81. )}>
  82. <div className='shrink-0'>
  83. <AppIcon
  84. size='large'
  85. iconType={appData?.site.icon_type}
  86. icon={appData?.site.icon}
  87. background={appData?.site.icon_background}
  88. imageUrl={appData?.site.icon_url}
  89. />
  90. </div>
  91. <div className={cn('system-md-semibold grow truncate text-text-secondary')}>{appData?.site.title}</div>
  92. {!isMobile && isSidebarCollapsed && (
  93. <ActionButton size='l' onClick={() => handleSidebarCollapse(false)}>
  94. <RiExpandRightLine className='h-[18px] w-[18px]' />
  95. </ActionButton>
  96. )}
  97. {!isMobile && !isSidebarCollapsed && (
  98. <ActionButton size='l' onClick={() => handleSidebarCollapse(true)}>
  99. <RiLayoutLeft2Line className='h-[18px] w-[18px]' />
  100. </ActionButton>
  101. )}
  102. </div>
  103. <div className='shrink-0 px-3 py-4'>
  104. <Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}>
  105. <RiEditBoxLine className='mr-1 h-4 w-4' />
  106. {t('share.chat.newChat')}
  107. </Button>
  108. </div>
  109. <div className='h-0 grow space-y-2 overflow-y-auto px-3 pt-4'>
  110. {/* pinned list */}
  111. {!!pinnedConversationList.length && (
  112. <div className='mb-4'>
  113. <List
  114. isPin
  115. title={t('share.chat.pinnedTitle') || ''}
  116. list={pinnedConversationList}
  117. onChangeConversation={handleChangeConversation}
  118. onOperate={handleOperate}
  119. currentConversationId={currentConversationId}
  120. />
  121. </div>
  122. )}
  123. {!!conversationList.length && (
  124. <List
  125. title={(pinnedConversationList.length && t('share.chat.unpinnedTitle')) || ''}
  126. list={conversationList}
  127. onChangeConversation={handleChangeConversation}
  128. onOperate={handleOperate}
  129. currentConversationId={currentConversationId}
  130. />
  131. )}
  132. </div>
  133. <div className='flex shrink-0 items-center justify-between p-3'>
  134. <MenuDropdown hideLogout={isInstalledApp} placement='top-start' data={appData?.site} />
  135. {/* powered by */}
  136. <div className='shrink-0'>
  137. {!appData?.custom_config?.remove_webapp_brand && (
  138. <div className={cn(
  139. 'flex shrink-0 items-center gap-1.5 px-1',
  140. )}>
  141. <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
  142. {
  143. systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
  144. ? <img src={systemFeatures.branding.workspace_logo} alt='logo' className='block h-5 w-auto' />
  145. : appData?.custom_config?.replace_webapp_logo
  146. ? <img src={`${appData?.custom_config?.replace_webapp_logo}`} alt='logo' className='block h-5 w-auto' />
  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