feat: support rename conversation (#1056)
This commit is contained in:
@@ -65,6 +65,7 @@ function useConversation() {
|
||||
setCurrInputs,
|
||||
currConversationInfo,
|
||||
setNewConversationInfo,
|
||||
existConversationInfo,
|
||||
setExistConversationInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ const Main: FC<IMainProps> = ({
|
||||
resetNewConversationInputs,
|
||||
setCurrInputs,
|
||||
setNewConversationInfo,
|
||||
existConversationInfo,
|
||||
setExistConversationInfo,
|
||||
} = useConversation()
|
||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||
@@ -186,6 +187,23 @@ const Main: FC<IMainProps> = ({
|
||||
|
||||
const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string
|
||||
const conversationIntroduction = currConversationInfo?.introduction || ''
|
||||
const [controlChatUpdateAllConversation, setControlChatUpdateAllConversation] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (controlChatUpdateAllConversation && !isNewConversation) {
|
||||
const { data: allConversations } = await fetchAllConversations() as { data: ConversationItem[]; has_more: boolean }
|
||||
const item = allConversations.find(item => item.id === currConversationId)
|
||||
setAllConversationList(allConversations)
|
||||
if (item) {
|
||||
setExistConversationInfo({
|
||||
...existConversationInfo,
|
||||
name: item?.name || '',
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [controlChatUpdateAllConversation])
|
||||
|
||||
const handleConversationSwitch = () => {
|
||||
if (!inited)
|
||||
@@ -546,8 +564,16 @@ const Main: FC<IMainProps> = ({
|
||||
return (
|
||||
<Sidebar
|
||||
list={conversationList}
|
||||
onListChanged={(list) => {
|
||||
setConversationList(list)
|
||||
setControlChatUpdateAllConversation(Date.now())
|
||||
}}
|
||||
isClearConversationList={isClearConversationList}
|
||||
pinnedList={pinnedConversationList}
|
||||
onPinnedListChanged={(list) => {
|
||||
setPinnedConversationList(list)
|
||||
setControlChatUpdateAllConversation(Date.now())
|
||||
}}
|
||||
isClearPinnedConversationList={isClearPinnedConversationList}
|
||||
onMoreLoaded={onMoreLoaded}
|
||||
onPinnedMoreLoaded={onPinnedMoreLoaded}
|
||||
|
||||
@@ -18,8 +18,10 @@ export type ISidebarProps = {
|
||||
currentId: string
|
||||
onCurrentIdChange: (id: string) => void
|
||||
list: ConversationItem[]
|
||||
onListChanged: (newList: ConversationItem[]) => void
|
||||
isClearConversationList: boolean
|
||||
pinnedList: ConversationItem[]
|
||||
onPinnedListChanged: (newList: ConversationItem[]) => void
|
||||
isClearPinnedConversationList: boolean
|
||||
isInstalledApp: boolean
|
||||
installedAppId?: string
|
||||
@@ -40,8 +42,10 @@ const Sidebar: FC<ISidebarProps> = ({
|
||||
currentId,
|
||||
onCurrentIdChange,
|
||||
list,
|
||||
onListChanged,
|
||||
isClearConversationList,
|
||||
pinnedList,
|
||||
onPinnedListChanged,
|
||||
isClearPinnedConversationList,
|
||||
isInstalledApp,
|
||||
installedAppId,
|
||||
@@ -115,6 +119,7 @@ const Sidebar: FC<ISidebarProps> = ({
|
||||
currentId={currentId}
|
||||
onCurrentIdChange={onCurrentIdChange}
|
||||
list={pinnedList}
|
||||
onListChanged={onPinnedListChanged}
|
||||
isClearConversationList={isClearPinnedConversationList}
|
||||
isInstalledApp={isInstalledApp}
|
||||
installedAppId={installedAppId}
|
||||
@@ -138,6 +143,7 @@ const Sidebar: FC<ISidebarProps> = ({
|
||||
currentId={currentId}
|
||||
onCurrentIdChange={onCurrentIdChange}
|
||||
list={list}
|
||||
onListChanged={onListChanged}
|
||||
isClearConversationList={isClearConversationList}
|
||||
isInstalledApp={isInstalledApp}
|
||||
installedAppId={installedAppId}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useRef } from 'react'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import {
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import { useInfiniteScroll } from 'ahooks'
|
||||
import { useBoolean, useInfiniteScroll } from 'ahooks'
|
||||
import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import RenameModal from '../rename-modal'
|
||||
import s from './style.module.css'
|
||||
import type { ConversationItem } from '@/models/share'
|
||||
import { fetchConversations } from '@/service/share'
|
||||
import { fetchConversations as fetchUniversalConversations } from '@/service/universal-chat'
|
||||
import { fetchConversations, renameConversation } from '@/service/share'
|
||||
import { fetchConversations as fetchUniversalConversations, renameConversation as renameUniversalConversation } from '@/service/universal-chat'
|
||||
import ItemOperation from '@/app/components/explore/item-operation'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
export type IListProps = {
|
||||
className: string
|
||||
currentId: string
|
||||
onCurrentIdChange: (id: string) => void
|
||||
list: ConversationItem[]
|
||||
onListChanged?: (newList: ConversationItem[]) => void
|
||||
isClearConversationList: boolean
|
||||
isInstalledApp: boolean
|
||||
isUniversalChat?: boolean
|
||||
@@ -35,6 +39,7 @@ const List: FC<IListProps> = ({
|
||||
currentId,
|
||||
onCurrentIdChange,
|
||||
list,
|
||||
onListChanged,
|
||||
isClearConversationList,
|
||||
isInstalledApp,
|
||||
isUniversalChat,
|
||||
@@ -46,6 +51,8 @@ const List: FC<IListProps> = ({
|
||||
controlUpdate,
|
||||
onDelete,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useInfiniteScroll(
|
||||
@@ -72,6 +79,50 @@ const List: FC<IListProps> = ({
|
||||
reloadDeps: [isNoMore, controlUpdate],
|
||||
},
|
||||
)
|
||||
const [isShowRename, { setTrue: setShowRename, setFalse: setHideRename }] = useBoolean(false)
|
||||
const [isSaving, { setTrue: setIsSaving, setFalse: setNotSaving }] = useBoolean(false)
|
||||
const [currentConversation, setCurrentConversation] = useState<ConversationItem | null>(null)
|
||||
const showRename = (item: ConversationItem) => {
|
||||
setCurrentConversation(item)
|
||||
setShowRename()
|
||||
}
|
||||
const handleRename = async (newName: string) => {
|
||||
if (!newName.trim() || !currentConversation) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('common.chat.conversationNameCanNotEmpty'),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setIsSaving()
|
||||
const currId = currentConversation.id
|
||||
try {
|
||||
if (isUniversalChat)
|
||||
await renameUniversalConversation(currId, newName)
|
||||
|
||||
else
|
||||
await renameConversation(isInstalledApp, installedAppId, currId, newName)
|
||||
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.actionMsg.modifiedSuccessfully'),
|
||||
})
|
||||
onListChanged?.(list.map((item) => {
|
||||
if (item.id === currId) {
|
||||
return {
|
||||
...item,
|
||||
name: newName,
|
||||
}
|
||||
}
|
||||
return item
|
||||
}))
|
||||
setHideRename()
|
||||
}
|
||||
finally {
|
||||
setNotSaving()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<nav
|
||||
ref={listRef}
|
||||
@@ -110,6 +161,8 @@ const List: FC<IListProps> = ({
|
||||
<ItemOperation
|
||||
isPinned={isPinned}
|
||||
togglePin={() => onPinChanged(item.id)}
|
||||
isShowRenameConversation
|
||||
onRenameConversation={() => showRename(item)}
|
||||
isShowDelete
|
||||
onDelete={() => onDelete(item.id)}
|
||||
/>
|
||||
@@ -118,6 +171,15 @@ const List: FC<IListProps> = ({
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{isShowRename && (
|
||||
<RenameModal
|
||||
isShow={isShowRename}
|
||||
onClose={setHideRename}
|
||||
saveLoading={isSaving}
|
||||
name={currentConversation?.name || ''}
|
||||
onSave={handleRename}
|
||||
/>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
47
web/app/components/share/chat/sidebar/rename-modal/index.tsx
Normal file
47
web/app/components/share/chat/sidebar/rename-modal/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export type IRenameModalProps = {
|
||||
isShow: boolean
|
||||
saveLoading: boolean
|
||||
name: string
|
||||
onClose: () => void
|
||||
onSave: (name: string) => void
|
||||
}
|
||||
|
||||
const RenameModal: FC<IRenameModalProps> = ({
|
||||
isShow,
|
||||
saveLoading,
|
||||
name,
|
||||
onClose,
|
||||
onSave,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [tempName, setTempName] = useState(name)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('common.chat.renameConversation')}
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
wrapperClassName='!z-50'
|
||||
>
|
||||
<div className={'mt-6 font-medium text-sm leading-[21px] text-gray-900'}>{t('common.chat.conversationName')}</div>
|
||||
<input className={'mt-2 w-full rounded-lg h-10 box-border px-3 text-sm leading-10 bg-gray-100'}
|
||||
value={tempName}
|
||||
onChange={e => setTempName(e.target.value)}
|
||||
placeholder={t('common.chat.conversationNamePlaceholder') || ''}
|
||||
/>
|
||||
|
||||
<div className='mt-10 flex justify-end'>
|
||||
<Button className='mr-2 flex-shrink-0' onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button type='primary' className='flex-shrink-0' onClick={() => onSave(tempName)} loading={saveLoading}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
export default React.memo(RenameModal)
|
||||
Reference in New Issue
Block a user