feat: conversation app support pin and delete conversation (#467)
parent
accc5faae3
commit
ec261aea54
@ -0,0 +1,115 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useRef } from 'react'
|
||||||
|
import {
|
||||||
|
ChatBubbleOvalLeftEllipsisIcon,
|
||||||
|
} from '@heroicons/react/24/outline'
|
||||||
|
import { useInfiniteScroll } from 'ahooks'
|
||||||
|
import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import s from './style.module.css'
|
||||||
|
import type { ConversationItem } from '@/models/share'
|
||||||
|
import { fetchConversations } from '@/service/share'
|
||||||
|
import ItemOperation from '@/app/components/explore/item-operation'
|
||||||
|
|
||||||
|
export type IListProps = {
|
||||||
|
className: string
|
||||||
|
currentId: string
|
||||||
|
onCurrentIdChange: (id: string) => void
|
||||||
|
list: ConversationItem[]
|
||||||
|
isInstalledApp: boolean
|
||||||
|
installedAppId?: string
|
||||||
|
onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
|
||||||
|
isNoMore: boolean
|
||||||
|
isPinned: boolean
|
||||||
|
onPinChanged: (id: string) => void
|
||||||
|
controlUpdate: number
|
||||||
|
onDelete: (id: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const List: FC<IListProps> = ({
|
||||||
|
className,
|
||||||
|
currentId,
|
||||||
|
onCurrentIdChange,
|
||||||
|
list,
|
||||||
|
isInstalledApp,
|
||||||
|
installedAppId,
|
||||||
|
onMoreLoaded,
|
||||||
|
isNoMore,
|
||||||
|
isPinned,
|
||||||
|
onPinChanged,
|
||||||
|
controlUpdate,
|
||||||
|
onDelete,
|
||||||
|
}) => {
|
||||||
|
const listRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useInfiniteScroll(
|
||||||
|
async () => {
|
||||||
|
if (!isNoMore) {
|
||||||
|
const lastId = list[list.length - 1]?.id
|
||||||
|
const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
|
||||||
|
onMoreLoaded({ data: conversations, has_more })
|
||||||
|
}
|
||||||
|
return { list: [] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: listRef,
|
||||||
|
isNoMore: () => {
|
||||||
|
return isNoMore
|
||||||
|
},
|
||||||
|
reloadDeps: [isNoMore, controlUpdate],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
ref={listRef}
|
||||||
|
className={cn(className, 'shrink-0 space-y-1 bg-white pb-[60px] overflow-y-auto')}
|
||||||
|
>
|
||||||
|
{list.map((item) => {
|
||||||
|
const isCurrent = item.id === currentId
|
||||||
|
const ItemIcon
|
||||||
|
= isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => onCurrentIdChange(item.id)}
|
||||||
|
key={item.id}
|
||||||
|
className={cn(s.item,
|
||||||
|
isCurrent
|
||||||
|
? 'bg-primary-50 text-primary-600'
|
||||||
|
: 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
|
||||||
|
'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='flex items-center w-0 grow'>
|
||||||
|
<ItemIcon
|
||||||
|
className={cn(
|
||||||
|
isCurrent
|
||||||
|
? 'text-primary-600'
|
||||||
|
: 'text-gray-400 group-hover:text-gray-500',
|
||||||
|
'mr-3 h-5 w-5 flex-shrink-0',
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
!isCurrent && (
|
||||||
|
<div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
|
||||||
|
<ItemOperation
|
||||||
|
isPinned={isPinned}
|
||||||
|
togglePin={() => onPinChanged(item.id)}
|
||||||
|
isShowDelete
|
||||||
|
onDelete={() => onDelete(item.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(List)
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
.opBtn {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover .opBtn {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
@ -1,45 +1,51 @@
|
|||||||
const translation = {
|
const translation = {
|
||||||
common: {
|
common: {
|
||||||
welcome: "Welcome to use",
|
welcome: 'Welcome to use',
|
||||||
appUnavailable: "App is unavailable",
|
appUnavailable: 'App is unavailable',
|
||||||
appUnkonwError: "App is unavailable"
|
appUnkonwError: 'App is unavailable',
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
newChat: "New chat",
|
newChat: 'New chat',
|
||||||
newChatDefaultName: "New conversation",
|
pinnedTitle: 'Pinned',
|
||||||
powerBy: "Powered by",
|
unpinnedTitle: 'Chats',
|
||||||
prompt: "Prompt",
|
newChatDefaultName: 'New conversation',
|
||||||
privatePromptConfigTitle: "Conversation settings",
|
powerBy: 'Powered by',
|
||||||
publicPromptConfigTitle: "Initial Prompt",
|
prompt: 'Prompt',
|
||||||
configStatusDes: "Before start, you can modify conversation settings",
|
privatePromptConfigTitle: 'Conversation settings',
|
||||||
|
publicPromptConfigTitle: 'Initial Prompt',
|
||||||
|
configStatusDes: 'Before start, you can modify conversation settings',
|
||||||
configDisabled:
|
configDisabled:
|
||||||
"Previous session settings have been used for this session.",
|
'Previous session settings have been used for this session.',
|
||||||
startChat: "Start Chat",
|
startChat: 'Start Chat',
|
||||||
privacyPolicyLeft:
|
privacyPolicyLeft:
|
||||||
"Please read the ",
|
'Please read the ',
|
||||||
privacyPolicyMiddle:
|
privacyPolicyMiddle:
|
||||||
"privacy policy",
|
'privacy policy',
|
||||||
privacyPolicyRight:
|
privacyPolicyRight:
|
||||||
" provided by the app developer.",
|
' provided by the app developer.',
|
||||||
|
deleteConversation: {
|
||||||
|
title: 'Delete conversation',
|
||||||
|
content: 'Are you sure you want to delete this conversation?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
generation: {
|
generation: {
|
||||||
tabs: {
|
tabs: {
|
||||||
create: "Create",
|
create: 'Create',
|
||||||
saved: "Saved",
|
saved: 'Saved',
|
||||||
},
|
},
|
||||||
savedNoData: {
|
savedNoData: {
|
||||||
title: "You haven't saved a result yet!",
|
title: 'You haven\'t saved a result yet!',
|
||||||
description: 'Start generating content, and find your saved results here.',
|
description: 'Start generating content, and find your saved results here.',
|
||||||
startCreateContent: 'Start create content'
|
startCreateContent: 'Start create content',
|
||||||
},
|
},
|
||||||
title: "AI Completion",
|
title: 'AI Completion',
|
||||||
queryTitle: "Query content",
|
queryTitle: 'Query content',
|
||||||
queryPlaceholder: "Write your query content...",
|
queryPlaceholder: 'Write your query content...',
|
||||||
run: "RUN",
|
run: 'RUN',
|
||||||
copy: "Copy",
|
copy: 'Copy',
|
||||||
resultTitle: "AI Completion",
|
resultTitle: 'AI Completion',
|
||||||
noData: "AI will give you what you want here.",
|
noData: 'AI will give you what you want here.',
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default translation;
|
export default translation
|
||||||
|
|||||||
@ -1,41 +1,47 @@
|
|||||||
const translation = {
|
const translation = {
|
||||||
common: {
|
common: {
|
||||||
welcome: "欢迎使用",
|
welcome: '欢迎使用',
|
||||||
appUnavailable: "应用不可用",
|
appUnavailable: '应用不可用',
|
||||||
appUnkonwError: "应用不可用",
|
appUnkonwError: '应用不可用',
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
newChat: "新对话",
|
newChat: '新对话',
|
||||||
newChatDefaultName: "新的对话",
|
pinnedTitle: '已置顶',
|
||||||
powerBy: "Powered by",
|
unpinnedTitle: '对话列表',
|
||||||
prompt: "提示词",
|
newChatDefaultName: '新的对话',
|
||||||
privatePromptConfigTitle: "对话设置",
|
powerBy: 'Powered by',
|
||||||
publicPromptConfigTitle: "对话前提示词",
|
prompt: '提示词',
|
||||||
configStatusDes: "开始前,您可以修改对话设置",
|
privatePromptConfigTitle: '对话设置',
|
||||||
configDisabled: "此次会话已使用上次会话表单",
|
publicPromptConfigTitle: '对话前提示词',
|
||||||
startChat: "开始对话",
|
configStatusDes: '开始前,您可以修改对话设置',
|
||||||
privacyPolicyLeft: "请阅读由该应用开发者提供的",
|
configDisabled: '此次会话已使用上次会话表单',
|
||||||
privacyPolicyMiddle: "隐私政策",
|
startChat: '开始对话',
|
||||||
privacyPolicyRight: "。",
|
privacyPolicyLeft: '请阅读由该应用开发者提供的',
|
||||||
|
privacyPolicyMiddle: '隐私政策',
|
||||||
|
privacyPolicyRight: '。',
|
||||||
|
deleteConversation: {
|
||||||
|
title: '删除对话',
|
||||||
|
content: '您确定要删除此对话吗?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
generation: {
|
generation: {
|
||||||
tabs: {
|
tabs: {
|
||||||
create: "创建",
|
create: '创建',
|
||||||
saved: "已保存",
|
saved: '已保存',
|
||||||
},
|
},
|
||||||
savedNoData: {
|
savedNoData: {
|
||||||
title: "您还没有保存结果!",
|
title: '您还没有保存结果!',
|
||||||
description: '开始生成内容,您可以在这里找到保存的结果。',
|
description: '开始生成内容,您可以在这里找到保存的结果。',
|
||||||
startCreateContent: '开始生成内容'
|
startCreateContent: '开始生成内容',
|
||||||
},
|
},
|
||||||
title: "AI 智能书写",
|
title: 'AI 智能书写',
|
||||||
queryTitle: "查询内容",
|
queryTitle: '查询内容',
|
||||||
queryPlaceholder: "请输入文本内容",
|
queryPlaceholder: '请输入文本内容',
|
||||||
run: "运行",
|
run: '运行',
|
||||||
copy: "拷贝",
|
copy: '拷贝',
|
||||||
resultTitle: "AI 书写",
|
resultTitle: 'AI 书写',
|
||||||
noData: "AI 会在这里给你惊喜。",
|
noData: 'AI 会在这里给你惊喜。',
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default translation;
|
export default translation
|
||||||
|
|||||||
Loading…
Reference in New Issue