Merge branch 'develop' into feature/ui_upgrade_zy

This commit is contained in:
yingzhao
2026-04-01 11:29:03 +08:00
committed by GitHub
180 changed files with 7455 additions and 3660 deletions

View File

@@ -37,7 +37,6 @@ const ChatContent: FC<ChatContentProps> = ({
const prevDataLengthRef = useRef(data.length);
const isScrolledToBottomRef = useRef(true);
const audioRef = useRef<HTMLAudioElement | null>(null)
const [playingIndex, setPlayingIndex] = useState<number | null>(null)
const [expandedReasoning, setExpandedReasoning] = useState<Set<number>>(new Set())
const [manualToggledReasoning, setManualToggledReasoning] = useState<Set<number>>(new Set())
@@ -54,10 +53,11 @@ const ChatContent: FC<ChatContentProps> = ({
if (manualToggledReasoning.has(index)) return expandedReasoning.has(index)
return !data[index]?.content
}
const [playingIndex, setPlayingIndex] = useState<string | null>(null)
const handlePlay = (index: number, audio_url: string, audio_status?: string) => {
if (audio_status !== 'completed' && !audio_status) return
if (playingIndex === index) {
const handlePlay = (audio_url: string, audio_status?: string) => {
if (audio_status !== 'completed' && typeof audio_status === 'string') return
if (playingIndex === audio_url) {
audioRef.current?.pause()
setPlayingIndex(null)
return
@@ -68,7 +68,7 @@ const ChatContent: FC<ChatContentProps> = ({
const audio = new Audio(audio_url)
audioRef.current = audio
audio.play()
setPlayingIndex(index)
setPlayingIndex(audio_url)
audio.onended = () => setPlayingIndex(null)
}
@@ -95,12 +95,16 @@ const ChatContent: FC<ChatContentProps> = ({
}
};
}, []);
// Auto-scroll to bottom when data changes to show latest messages
// When data array length remains unchanged, if data is updated and user manually scrolled up, don't auto-scroll to bottom
// When data array length changes, auto-scroll to bottom
// If already scrolled to bottom, will auto-scroll to bottom
useEffect(() => {
if (playingIndex && !data.some(item => item.meta_data?.audio_url === playingIndex)) {
audioRef.current?.pause()
setPlayingIndex(null)
}
setTimeout(() => {
if (scrollContainerRef.current) {
// Auto-scroll if data length changed OR user is currently at bottom
@@ -236,16 +240,16 @@ const ChatContent: FC<ChatContentProps> = ({
{item.meta_data?.audio_url && <>
<Divider className="rb:my-3!" />
<Space size={12} className="rb:pb-2 rb:pl-1">
{playingIndex !== index && item.meta_data?.audio_status === 'pending'
{playingIndex !== item.meta_data?.audio_url && item.meta_data?.audio_status === 'pending'
? <Spin />
: playingIndex !== index
: playingIndex !== item.meta_data?.audio_url
? <SoundOutlined className={clsx("rb:cursor-pointer rb:size-5.5", {
'rb:text-[#FF5D34]': item.meta_data?.audio_status === 'error',
'rb:hover:text-[#155EEF]!': !item.meta_data?.audio_status || !['pending', 'error'].includes(item.meta_data?.audio_status)
})} onClick={() => handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)} />
})} onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)} />
: <div
className="rb:size-5.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/audio_ing.gif')]"
onClick={() => handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)}
onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)}
/>
}
</Space>

View File

@@ -24,16 +24,17 @@ interface BodyWrapperProps {
/** Whether to show loading state */
loading?: boolean
/** Whether the content is empty */
empty: boolean
empty: boolean;
className?: string;
}
const BodyWrapper: FC<BodyWrapperProps> = ({ children, loading = false, empty }) => {
const BodyWrapper: FC<BodyWrapperProps> = ({ children, loading = false, empty, className = 'rb:max-h-[calc(100%-48px)]!' }) => {
// Show loading spinner while data is being fetched
if (loading) {
return <PageLoading />
return <PageLoading className={className} />
}
// Show empty state when no data is available
if (!loading && empty) {
return <PageEmpty />
return <PageEmpty className={className} />
}
// Render actual content when data is loaded and available
return children

View File

@@ -128,6 +128,7 @@ const Menu: FC<{
/** Filter menus based on user role and source */
useEffect(() => {
if (!user) return
let menuList: MenuItem[] = []
if (user.role === 'member' && source === 'space') {
@@ -136,7 +137,7 @@ const Menu: FC<{
menuList = allMenus[source] || []
}
const noAuthList = ['user', 'pricing'].filter(vo => !user.permissions?.includes(vo) && !user.permissions?.includes('all'))
const noAuthList = ['user', 'pricing'].filter(vo => (Array.isArray(user.permissions) && !user.permissions?.includes(vo) && !user.permissions?.includes('all')) || !Array.isArray(user.permissions))
if (noAuthList && !noAuthList?.includes('all')) {
const filterMenus = (list: MenuItem[]): MenuItem[] =>{