feat(web): rag content add page
This commit is contained in:
@@ -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) => {
|
||||||
|
|||||||
@@ -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>}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user