feat(web): rag content add page

This commit is contained in:
zhaoying
2026-03-12 18:36:49 +08:00
parent 2961ea4e44
commit 75b87955dd
4 changed files with 75 additions and 100 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 14:00:06 * @Date: 2026-02-03 14:00:06
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-04 10:58:41 * @Last Modified time: 2026-03-12 18:25:06
*/ */
import { request } from '@/utils/request' import { request } from '@/utils/request'
import type { import type {
@@ -118,8 +118,9 @@ export const getChunkInsight = (end_user_id: string) => {
return request.get(`/dashboard/chunk_insight`, { end_user_id }) return request.get(`/dashboard/chunk_insight`, { end_user_id })
} }
// RAG User Memory - Storage content // RAG User Memory - Storage content
export const getRagContent = (end_user_id: string) => { export const getRagContentUrl = '/dashboard/rag_content'
return request.get(`/dashboard/rag_content`, { end_user_id, limit: 20 }) export const getRagContent = (end_user_id: string, page = 1, pagesize = 20) => {
return request.get(getRagContentUrl, { end_user_id, page, pagesize })
} }
// Emotion distribution analysis // Emotion distribution analysis
export const getWordCloud = (end_user_id: string) => { export const getWordCloud = (end_user_id: string) => {

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-02 15:18:19 * @Date: 2026-02-02 15:18:19
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 15:44:42 * @Last Modified time: 2026-03-12 18:36:19
*/ */
/** /**
* PageScrollList Component * PageScrollList Component
@@ -60,8 +60,8 @@ interface PageScrollListProps<T, Q = Record<string, unknown>> {
/** Infinite scroll list component with pagination support */ /** Infinite scroll list component with pagination support */
const PageScrollList = forwardRef(<T, Q = Record<string, unknown>>({ const PageScrollList = forwardRef(<T, Q = Record<string, unknown>>({
renderItem, renderItem,
query, query,
url, url,
column = 4, column = 4,
className = '', className = '',
@@ -69,68 +69,70 @@ const PageScrollList = forwardRef(<T, Q = Record<string, unknown>>({
}: PageScrollListProps<T, Q>, ref: React.Ref<PageScrollListRef>) => { }: PageScrollListProps<T, Q>, ref: React.Ref<PageScrollListRef>) => {
/** Expose refresh method to parent component */ /** Expose refresh method to parent component */
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
refresh, refresh: () => {
pageRef.current = 1;
loadingRef.current = false;
setHasMore(true);
setData([]);
loadMoreData(true);
},
})); }));
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState<T[]>([]); const [data, setData] = useState<T[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const pageRef = useRef(1);
const loadingRef = useRef(false);
const hasMoreRef = useRef(true);
/** Load more data from API with pagination */ /** Load more data from API with pagination */
const loadMoreData = (flag?: boolean) => { const loadMoreData = (reset?: boolean) => {
if (!flag && (loading || !hasMore)) { if (loadingRef.current || (!reset && !hasMoreRef.current)) return;
return; loadingRef.current = true;
}
setLoading(true); setLoading(true);
const currentPage = reset ? 1 : pageRef.current;
request.get(url, { request.get(url, {
page: page, page: currentPage,
pagesize: PAGE_SIZE, pagesize: PAGE_SIZE,
...(query||{}), ...(query || {}),
}) })
.then((res) => { .then((res) => {
const response = res as ApiResponse<T>; const response = res as ApiResponse<T>;
const results = Array.isArray(response.items) ? response.items : Array.isArray(response) ? response as T[] : []; const results = Array.isArray(response.items) ? response.items : Array.isArray(response) ? response as T[] : [];
// Replace data if flag is true, otherwise append pageRef.current = response.page.page + 1;
if (flag) { setData(prev => reset ? results : [...prev, ...results]);
setData(results); hasMoreRef.current = response.page?.hasnext;
} else {
setData(data.concat(results));
}
setPage(response.page.page + 1);
setHasMore(response.page?.hasnext); setHasMore(response.page?.hasnext);
setLoading(false);
console.log(`${results.length} more items loaded!`);
}) })
.catch(() => { .catch(() => {
setLoading(false); hasMoreRef.current = false;
setHasMore(false); setHasMore(false);
console.error('Failed to load data');
}) })
.finally(() => { .finally(() => {
loadingRef.current = false;
setLoading(false); setLoading(false);
// 内容不足以填满容器时,主动继续加载
setTimeout(() => {
const el = scrollRef.current;
console.log(el, el?.scrollHeight, el?.clientHeight, hasMoreRef.current)
if (el && hasMoreRef.current && el.scrollHeight <= el.clientHeight) {
loadMoreData();
}
}, 0);
}); });
}; };
/** Reset list to initial state and reload data */ /** Reset and reload when query parameters change */
const refresh = () => { const queryKey = JSON.stringify(query);
setPage(1); useEffect(() => {
pageRef.current = 1;
loadingRef.current = false;
hasMoreRef.current = true;
setHasMore(true); setHasMore(true);
setData([]); setData([]);
} loadMoreData(true);
}, [queryKey]);
/** Refresh when query parameters change */
useEffect(() => {
refresh()
}, [query]);
/** Load initial data when list is reset */
useEffect(() => {
if (page === 1 && hasMore && data.length === 0) {
loadMoreData(true);
}
}, [page, hasMore, data])
return ( return (
<> <>
<div <div
@@ -140,7 +142,7 @@ const PageScrollList = forwardRef(<T, Q = Record<string, unknown>>({
> >
<InfiniteScroll <InfiniteScroll
dataLength={data.length} dataLength={data.length}
next={loadMoreData} next={() => loadMoreData()}
hasMore={hasMore} hasMore={hasMore}
loader={loading && needLoading ? <PageLoading /> : false} loader={loading && needLoading ? <PageLoading /> : false}
// endMessage={<Divider plain>It is all, nothing more 🤐</Divider>} // endMessage={<Divider plain>It is all, nothing more 🤐</Divider>}

View File

@@ -1,8 +1,8 @@
/* /*
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 17:57:11 * @Date: 2026-02-03 17:57:11
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:57:11 * @Last Modified time: 2026-03-12 18:00:11
*/ */
/** /**
* RAG User Memory Detail View * RAG User Memory Detail View
@@ -150,9 +150,12 @@ const Rag: FC = () => {
}) })
} }
return ( return (
<Row gutter={[16, 16]} className="rb:pb-6"> <Row gutter={[16, 16]} className="rb:h-full!">
<Col span={8}> <Col span={8}>
<RbCard> <RbCard
className="rb:h-[calc(100vh-104px)]!"
bodyClassName="rb:overflow-y-auto! rb:h-full!"
>
<div className="rb:flex rb:items-center"> <div className="rb:flex rb:items-center">
<div className="rb:flex-[0_0_auto] rb:w-20 rb:h-20 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-20 rb:rounded-lg rb:text-[#FBFDFF] rb:bg-[#155EEF]">{name?.[0]}</div> <div className="rb:flex-[0_0_auto] rb:w-20 rb:h-20 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-20 rb:rounded-lg rb:text-[#FBFDFF] rb:bg-[#155EEF]">{name?.[0]}</div>
<Flex> <Flex>

View File

@@ -1,74 +1,43 @@
/* /*
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 18:34:04 * @Date: 2026-02-03 18:34:04
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 18:34:04 * @Last Modified time: 2026-03-12 18:34:52
*/ */
/** import { type FC } from 'react'
* Conversation Memory Component
* Displays RAG conversation memory content list
*/
import { type FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { Skeleton, List } from 'antd';
import RbCard from '@/components/RbCard/Card' import RbCard from '@/components/RbCard/Card'
import Empty from '@/components/Empty'; import PageScrollList from '@/components/PageScrollList'
import Markdown from '@/components/Markdown' import Markdown from '@/components/Markdown'
import { import { getRagContentUrl } from '@/api/memory'
getRagContent
} from '@/api/memory'
const ConversationMemory:FC = () => { const ConversationMemory: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams() const { id } = useParams()
const [loading, setLoading] = useState<boolean>(true)
const [list, setList] = useState<string[]>([])
useEffect(() => {
if (!id) return
getList()
}, [id])
/** Fetch conversation memory list */
const getList = () => {
if (!id) return
setLoading(true)
getRagContent(id).then((res) => {
setList((res as { contents?: [] }).contents || [])
})
.finally(() => {
setLoading(false)
})
}
return ( return (
<RbCard <RbCard
title={t('userMemory.conversationMemory')} title={t('userMemory.conversationMemory')}
headerClassName="rb:text-[18px]! rb:leading-[24px]" headerClassName="rb:text-[18px]! rb:leading-[24px]"
bodyClassName="rb:h-[100%]! rb:overflow-hidden rb:py-0!" bodyClassName="rb:h-[calc(100%-56px)]! rb:overflow-hidden"
className="rb:h-[calc(100vh-104px)]!"
> >
{loading <PageScrollList<string>
? <Skeleton /> url={getRagContentUrl}
: list.length > 0 query={{ end_user_id: id }}
? <List column={1}
dataSource={list} renderItem={(item) => (
grid={{ gutter: 12, column: 1 }} <div className="rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:px-4 rb:py-3 rb:bg-[#F0F3F8] rb:text-gray-800 rb:text-sm">
renderItem={(item, index) => ( <Markdown content={item} />
<List.Item> </div>
<div )}
key={index} className="rb:h-full!"
className="rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:px-4 rb:py-3 rb:bg-[#F0F3F8] rb:mt-2 rb:text-gray-800 rb:text-sm" // className="rb:h-[calc(100%-24px)]!"
> />
<Markdown content={item} />
</div>
</List.Item>
)}
/>
: <Empty className="rb:h-full" />
}
</RbCard> </RbCard>
) )
} }
export default ConversationMemory
export default ConversationMemory