From c72a2e699bbf493175d409eb24fa6a8c1344a95b Mon Sep 17 00:00:00 2001 From: "alan.cl" <1165243776@qq.com> Date: Mon, 9 Mar 2026 16:10:16 +0800 Subject: [PATCH] feat(web): move view-all icon to sidebar header + add search to conversations page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace bottom '查看全部' text link in sidebar with MoreOutlined icon + Tooltip in the '所有任务' section header (flex justify-between layout) - Add debounced search input (SearchOutlined, 300ms) to /conversations page - Implement client-side title filtering via filteredList useMemo - Show distinct empty states: '暂无历史记录' vs '没有匹配的对话' --- web/components/layout/side-bar.tsx | 12 +- web/pages/conversations/index.tsx | 191 +++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 web/pages/conversations/index.tsx diff --git a/web/components/layout/side-bar.tsx b/web/components/layout/side-bar.tsx index f0f42c1fe..67a34fb03 100644 --- a/web/components/layout/side-bar.tsx +++ b/web/components/layout/side-bar.tsx @@ -16,6 +16,7 @@ import Icon, { MenuFoldOutlined, MenuUnfoldOutlined, MessageOutlined, + MoreOutlined, PlusOutlined, SettingOutlined, ThunderboltOutlined, @@ -341,8 +342,15 @@ function SideBar() { {/* All Tasks Section */}
-
- {t('all_tasks')} +
+ + {t('all_tasks')} + + + + + +
diff --git a/web/pages/conversations/index.tsx b/web/pages/conversations/index.tsx new file mode 100644 index 000000000..efca21166 --- /dev/null +++ b/web/pages/conversations/index.tsx @@ -0,0 +1,191 @@ +import { apiInterceptors, delDialogue, getDialogueListPaged } from '@/client/api'; +import { IChatDialogueSchema } from '@/types/chat'; +import { DeleteOutlined, MessageOutlined, SearchOutlined } from '@ant-design/icons'; +import { useRequest } from 'ahooks'; +import { Empty, Input, Pagination, Popconfirm, Spin, Tooltip, message } from 'antd'; +import debounce from 'lodash/debounce'; +import moment from 'moment'; +import 'moment/locale/zh-cn'; +import { useRouter } from 'next/router'; +import { useCallback, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +const PAGE_SIZE = 20; + +function ConversationsPage() { + const router = useRouter(); + const { t } = useTranslation(); + const [list, setList] = useState([]); + const [searchKeyword, setSearchKeyword] = useState(''); + + const handleSearch = useCallback( + debounce((value: string) => { + setSearchKeyword(value); + }, 300), + [], + ); + + const filteredList = useMemo(() => { + if (!searchKeyword.trim()) return list; + const keyword = searchKeyword.toLowerCase(); + return list.filter(conv => { + const title = typeof conv.user_input === 'string' ? conv.user_input.toLowerCase() : ''; + return title.includes(keyword); + }); + }, [list, searchKeyword]); + + const totalRef = useRef<{ + current_page: number; + total_count: number; + total_pages: number; + }>(); + + const { loading, run: fetchList } = useRequest( + async (page = 1) => + await apiInterceptors( + getDialogueListPaged({ chat_mode: 'chat_react_agent' }, page, PAGE_SIZE), + ), + { + defaultParams: [1], + onSuccess: data => { + const [, res] = data; + setList(res?.items || []); + totalRef.current = { + current_page: res?.page || 1, + total_count: res?.total_count || 0, + total_pages: res?.total_pages || 0, + }; + }, + }, + ); + + const handleDelete = useCallback( + async (e: React.MouseEvent, convUid: string) => { + e.stopPropagation(); + e.preventDefault(); + const [err] = await apiInterceptors(delDialogue(convUid)); + if (!err) { + message.success('已删除'); + const current = totalRef.current; + if (current) { + const remaining = current.total_count - 1; + const maxPage = Math.max(1, Math.ceil(remaining / PAGE_SIZE)); + fetchList(Math.min(current.current_page, maxPage)); + } + } + }, + [fetchList], + ); + + const formatTime = (dateStr?: string) => { + if (!dateStr) return ''; + return moment(dateStr).fromNow(); + }; + + const getTitle = (conv: IChatDialogueSchema) => { + if (typeof conv.user_input === 'string' && conv.user_input.trim()) { + return conv.user_input; + } + return t('new_task') || '新对话'; + }; + + return ( +
+
+

+ {t('all_tasks') || '所有任务'} +

+
+ } + placeholder='搜索对话...' + onChange={e => handleSearch(e.target.value)} + onClear={() => setSearchKeyword('')} + allowClear + className='w-[230px] h-[36px] border-1 border-white backdrop-filter backdrop-blur-lg bg-white bg-opacity-30 dark:border-[#6f7f95] dark:bg-[#6f7f95] dark:bg-opacity-60' + /> + + {totalRef.current ? `共 ${totalRef.current.total_count} 条` : ''} + +
+
+ +
+ + {!loading && list.length === 0 ? ( +
+ +
+ ) : !loading && filteredList.length === 0 ? ( +
+ +
+ ) : ( +
+ {filteredList.map(conv => ( +
router.push(`/?id=${conv.conv_uid}`)} + className='group flex items-center gap-3 px-4 py-3 rounded-xl cursor-pointer transition-colors hover:bg-white dark:hover:bg-gray-800 hover:shadow-sm' + > +
+ +
+ +
+
+ {getTitle(conv)} +
+ {conv.gmt_created && ( +
{formatTime(conv.gmt_created)}
+ )} +
+ + handleDelete(e as React.MouseEvent, conv.conv_uid)} + onCancel={e => { + e?.stopPropagation(); + e?.preventDefault(); + }} + okText='删除' + cancelText='取消' + okButtonProps={{ danger: true }} + > + +
{ + e.stopPropagation(); + e.preventDefault(); + }} + className='flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity' + > + +
+
+
+
+ ))} +
+ )} +
+
+ + {(totalRef.current?.total_count ?? 0) > PAGE_SIZE && ( +
+ `共 ${total} 条`} + onChange={page => fetchList(page)} + /> +
+ )} +
+ ); +} + +export default ConversationsPage;