Merge pull request #780 from SuanmoSuanyangTechnology/feature/ui_upgrade_zy

Feature/UI upgrade zy
This commit is contained in:
yingzhao
2026-04-02 16:11:45 +08:00
committed by GitHub
6 changed files with 93 additions and 47 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2025-12-10 16:46:17
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 15:01:53
* @Last Modified time: 2026-04-02 16:05:01
*/
import { type FC, useRef, useEffect, useState } from 'react'
import clsx from 'clsx'
@@ -124,6 +124,9 @@ const ChatContent: FC<ChatContentProps> = ({
const handleDownload = (file: any) => {
window.open(getFileUrl(file), '_blank')
}
const onFormSubmit = (values: Record<string, any>) => {
onSend?.(JSON.stringify(values))
}
return (
<div ref={scrollContainerRef} className={clsx("rb:relative rb:overflow-y-auto", classNames)}>
{data.length === 0
@@ -253,7 +256,7 @@ const ChatContent: FC<ChatContentProps> = ({
{item.status && <div className="rb:size-5 rb:bg-cover rb:bg-[url('@/assets/images/conversation/exclamation_circle.svg')] rb:absolute rb:-left-7"></div>}
{item.subContent && renderRuntime && renderRuntime(item, index)}
{/* Render message content using Markdown component */}
<Markdown content={renderRuntime ? item.content ?? '' : item.content ?? errorDesc ?? ''} />
<Markdown content={renderRuntime ? item.content ?? '' : item.content ?? errorDesc ?? ''} onFormSubmit={onFormSubmit} />
{item.meta_data?.suggested_questions && item.meta_data?.suggested_questions?.length > 0 && <Flex wrap className="rb:my-1!">
{item.meta_data?.suggested_questions?.map((question, idx) => (

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2025-12-10 16:45:54
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 15:01:46
* @Last Modified time: 2026-04-02 16:05:08
*/
import { type ReactNode } from 'react'

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-02 15:16:10
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-25 14:02:17
* @Last Modified time: 2026-04-02 15:41:16
*/
/**
* RbButton Component
@@ -19,11 +19,11 @@ import { Button, type ButtonProps } from 'antd'
/** Button component for rendering buttons in markdown */
const RbButton: FC<ButtonProps> = (props) => {
const { children } = props;
const RbButton: FC<ButtonProps> = ({ children, onClick, ...props }) => {
const size = (props['data-size'] || 'default') as ButtonProps['size']
const type = (props['data-variant'] || 'default') as ButtonProps['type']
return (
<Button>
<Button {...props} size={size} type={type} className="rb:mb-3" onClick={onClick}>
{children}
</Button>
)

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:17:31
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:17:31
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 16:06:03
*/
/**
* RbMarkdown Component
@@ -22,7 +22,7 @@
* @component
*/
import { useState, useRef, useEffect, type FC } from 'react'
import { useState, useRef, useEffect, type FC, createContext, useContext, useCallback } from 'react'
import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider } from 'antd'
import ReactMarkdown from 'react-markdown'
import RemarkGfm from 'remark-gfm'
@@ -37,6 +37,13 @@ import AudioBlock from './AudioBlock'
import Link from './Link'
import RbButton from './RbButton'
/** Context for sharing form field values between form/input/button components */
const FormContext = createContext<{
values: Record<string, any>;
setValue: (name: string, value: any) => void;
onSubmit?: (values: Record<string, any>) => void;
} | null>(null)
/** Props interface for RbMarkdown component */
interface RbMarkdownProps {
/** Markdown content to render */
@@ -49,10 +56,12 @@ interface RbMarkdownProps {
onContentChange?: (content: string) => void;
/** Additional CSS classes */
className?: string;
/** Callback when a form button is clicked, receives form field values */
onFormSubmit?: (values: Record<string, any>) => void;
}
/** Custom component mappings for markdown elements */
const components = {
/** Build components with onFormSubmit callback */
const buildComponents = (onFormSubmit?: (values: Record<string, any>) => void) => ({
h1: ({ children, ...props }: any) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2" {...props}>{children}</h1>,
h2: ({ children, ...props }: any) => <h2 className="rb:text-xl rb:font-bold rb:mb-2" {...props}>{children}</h2>,
h3: ({ children, ...props }: any) => <h3 className="rb:text-lg rb:font-bold rb:mb-2" {...props}>{children}</h3>,
@@ -80,58 +89,93 @@ const components = {
video: ({ src, ...props }: any) => <VideoBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
audio: ({ src, ...props }: any) => <AudioBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
a: ({ href, children, ...props }: any) => <Link href={href || '#'} {...props}>{children}</Link>,
button: ({ children }: any) => <RbButton node={{ children }}>{[children]}</RbButton>,
table: ({ children, ...props }: any) => <div className="rb:overflow-x-auto rb:max-w-full"><table className="rb:border rb:border-[#EBEBEB] rb:mb-2" {...props}>{children}</table></div>,
tr: ({ children, ...props }: any) => <tr className="rb:border rb:border-[#EBEBEB]" {...props}>{children}</tr>,
th: ({ children, ...props }: any) => <th className="rb:border rb:border-[#EBEBEB] rb:px-2 rb:py-1 rb:text-left rb:font-bold" {...props}>{children}</th>,
td: ({ children, ...props }: any) => <td className="rb:border rb:border-[#EBEBEB] rb:px-2 rb:py-1 rb:text-left" {...props}>{children}</td>,
button: ({ children, ...props }: any) => {
const ctx = useContext(FormContext)
return <RbButton {...props} onClick={() => ctx?.onSubmit?.(ctx.values)}>{[children]}</RbButton>
},
table: ({ children, ...props }: any) => <div className="rb:overflow-x-auto rb:max-w-full"><table className="rb:border rb:border-[#D9D9D9] rb:mb-2" {...props}>{children}</table></div>,
tr: ({ children, ...props }: any) => <tr className="rb:border rb:border-[#D9D9D9]" {...props}>{children}</tr>,
th: ({ children, ...props }: any) => <th className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left rb:font-bold" {...props}>{children}</th>,
td: ({ children, ...props }: any) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left" {...props}>{children}</td>,
input: ({ children, ...props }: any) => {
const ctx = useContext(FormContext)
const handleChange = useCallback((val: any) => {
if (props.name) ctx?.setValue(props.name, val)
}, [ctx, props.name])
switch (props.type) {
case 'color':
return <ColorPicker {...props} />
return <ColorPicker className="rb:mb-4!" {...props} onChange={handleChange} />
case 'time':
return <TimePicker {...props} />
return <TimePicker className="rb:mb-4!" {...props} onChange={handleChange} />
case 'date':
return <DatePicker {...props} />
return <DatePicker className="rb:mb-4!" {...props} onChange={handleChange} />
case 'datetime':
case 'datetime-local':
return <DatePicker showTime={true} {...props} />
return <DatePicker className="rb:mb-4!" showTime={true} {...props} onChange={handleChange} />
case 'week':
return <DatePicker picker="week" {...props} />
return <DatePicker className="rb:mb-4!" picker="week" {...props} onChange={handleChange} />
case 'month':
return <DatePicker picker="month" {...props} />
return <DatePicker className="rb:mb-4!" picker="month" {...props} onChange={handleChange} />
case 'number':
return <InputNumber {...props} />
return <InputNumber className="rb:mb-4!" {...props} onChange={handleChange} />
case 'search':
return <Input.Search {...props} />
return <Input.Search className="rb:mb-4!" {...props} onChange={(e) => handleChange(e.target.value)} />
case 'range':
return <Slider {...props} />
return <Slider className="rb:mb-4!" {...props} onChange={handleChange} />
case 'submit':
case 'button':
return <RbButton node={{ children: props.value || children }}>{[props.value || children]}</RbButton>
return <RbButton className="rb:mb-4!" {...props} onClick={() => ctx?.onSubmit?.(ctx.values)}>{[props.value || children]}</RbButton>
case 'checkbox':
return <Checkbox {...props}>{children}</Checkbox>
return <Checkbox className="rb:mb-4!" {...props} onChange={(e) => handleChange(e.target.checked)}>{children}</Checkbox>
case 'password':
return <Input.Password {...props} />
return <Input.Password className="rb:mb-4!" {...props} onChange={(e) => handleChange(e.target.value)} />
case 'radio':
return <Radio {...props}>{children}</Radio>
return <Radio className="rb:mb-4!" {...props} onChange={(e) => handleChange(e.target.value)}>{children}</Radio>
case 'select': {
const raw = props['data-options']
const options = (typeof raw === 'string' ? JSON.parse(raw) : raw || []).map((v: string) => ({ label: v, value: v }))
return <Select className="rb:mb-4! rb:w-full!" options={options} onChange={(val) => { if (props.name) ctx?.setValue(props.name, val) }} />
}
default:
return <Input value={children} {...props} />
return <Input className="rb:mb-4!" value={children} {...props} onChange={(e) => handleChange(e.target.value)} />
}
},
select: ({ children, ...props }: any) => <Select style={{width: '100%'}} {...props}>{children}</Select>,
textarea: ({ children, ...props }: any) => <Input.TextArea {...props}>{children}</Input.TextArea>,
form: ({ children, ...props }: any) => <Form {...props}>{children}</Form>,
hr: (props: any) => <hr className="rb:border-t rb:border-[#EBEBEB] rb:my-3" {...props} />
}
select: ({ children, ...props }: any) => {
const ctx = useContext(FormContext)
return <Select className="rb:mb-4! rb:w-full!" {...props} onChange={(val) => { if (props.name) ctx?.setValue(props.name, val) }}>{children}</Select>
},
textarea: ({ children, ...props }: any) => {
const ctx = useContext(FormContext)
return <Input.TextArea className="rb:mb-4!" {...props} onChange={(e) => { if (props.name) ctx?.setValue(props.name, e.target.value) }}>{children}</Input.TextArea>
},
form: ({ children, ...props }: any) => {
const [values, setValues] = useState<Record<string, any>>({})
const setValue = useCallback((name: string, value: any) => setValues(prev => ({ ...prev, [name]: value })), [])
return (
<FormContext.Provider value={{ values, setValue, onSubmit: onFormSubmit }}>
<Form {...props}>{children}</Form>
</FormContext.Provider>
)
},
label: ({ children, ...props }: any) => {
return <label className="rb:block rb:font-medium rb:text-[#212332] rb:mb-2" {...props}>{children}</label>
},
hr: (props: any) => <hr className="rb:border-t rb:border-[#EBEBEB] rb:my-3" {...props} />,
})
const RbMarkdown: FC<RbMarkdownProps> = ({
content,
showHtmlComments = false,
editable = false,
onContentChange,
className
className,
onFormSubmit,
}) => {
const components = buildComponents(onFormSubmit)
const [editContent, setEditContent] = useState(content)
const textareaRef = useRef<any>(null)
@@ -207,8 +251,6 @@ const RbMarkdown: FC<RbMarkdownProps> = ({
`}</style>
<ReactMarkdown
// allowElement={[]}
// allowedElements={[]}
components={components as any}
disallowedElements={['script', 'iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
rehypePlugins={[

View File

@@ -3,8 +3,8 @@
* @Version: 0.0.1
* @Author: yujiangping
* @Date: 2026-01-12 16:34:59
* @LastEditors: yujiangping
* @LastEditTime: 2026-01-23 19:07:36
* @LastEditors: ZhaoYing
* @LastEditTime: 2026-04-02 10:36:37
*/
import React, { useEffect, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -13,6 +13,7 @@ import { Flex } from 'antd';
import { getVersion, type versionResponse } from '@/api/common'
import Empty from '@/components/Empty';
import { useI18n } from '@/store/locale';
import { formatDateTime } from '@/utils/format'
const VersionCard: React.FC = () => {
const { t } = useTranslation();
@@ -51,18 +52,18 @@ const VersionCard: React.FC = () => {
{introduction
? (<>
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5 rb:mt-1 rb:mb-2">
{t('version.releaseDate')}: {introduction.releaseDate} | {t('version.name')}: {introduction.codeName}
{t('version.releaseDate')}: {formatDateTime(introduction.releaseDate, 'YYYY-MM-DD')} | {t('version.name')}: {introduction.codeName}
</div>
<div className="rb:max-h-76 rb:overflow-y-auto">
<p
className='rb:text-[12px] rb:leading-4.5'
dangerouslySetInnerHTML={{ __html: introduction.upgradePosition }}
dangerouslySetInnerHTML={{ __html: introduction.upgradePosition?.replace(/\n/g, '<br>') }}
/>
{introduction.coreUpgrades?.map((item: string, index: number) => (
<p
key={index}
className='rb:text-[12px] rb:leading-4.5 rb:mt-2'
dangerouslySetInnerHTML={{ __html: item }}
dangerouslySetInnerHTML={{ __html: item?.replace(/\n/g, '<br>') }}
/>
))}
</div>

View File

@@ -99,8 +99,8 @@ const Index = () => {
return (
<Row gutter={12}>
<Col flex="1">
<Flex gap={12} wrap="nowrap" className="rb:w-full! rb:h-full! rb:overflow-y-auto">
<div className="rb:flex-1 rb:min-w-0">
<Flex vertical>
<div className='rb:w-full rb:h-26 rb:p-4 rb:bg-cover rb:bg-[url("@/assets/images/index/index_bg@2x.png")] rb:rounded-xl rb:overflow-hidden'>
<div className="rb:font-[MiSans-Bold] rb:font-bold rb:text-white rb:text-[18px] rb:leading-7">
@@ -123,14 +123,14 @@ const Index = () => {
/>
</div>
</Flex>
</Col>
<Col flex="328px">
</div>
<div className="rb:w-82!">
{/* 引导 */}
<GuideCard />
<VersionCard />
<QuickActions onNavigate={navigate} />
</Col>
</Row>
</div>
</Flex>
);
}