feat(web): ui

This commit is contained in:
zhaoying
2026-04-22 14:16:44 +08:00
parent 749083bdbe
commit cda20ac3f1
7 changed files with 68 additions and 51 deletions

View File

@@ -89,12 +89,15 @@ const Knowledge: FC<KnowledgeProps> = ({ value = { knowledge_bases: [] }, onChan
onChange?.({ ...editConfig, knowledge_bases: [...list] }) onChange?.({ ...editConfig, knowledge_bases: [...list] })
} else if (type === 'rerankerConfig') { } else if (type === 'rerankerConfig') {
const rerankerValues = values as RerankerConfig const rerankerValues = values as RerankerConfig
setEditConfig(prev => ({ ...prev, ...rerankerValues })) setEditConfig(prev => {
onChange?.({ const next = {
...editConfig, ...prev,
...rerankerValues, ...rerankerValues,
reranker_id: rerankerValues.rerank_model ? rerankerValues.reranker_id : undefined, reranker_id: rerankerValues.rerank_model ? rerankerValues.reranker_id : undefined,
reranker_top_k: rerankerValues.rerank_model ? rerankerValues.reranker_top_k : undefined, reranker_top_k: rerankerValues.rerank_model ? rerankerValues.reranker_top_k : undefined,
}
onChange?.(next)
return next
}) })
} }
} }

View File

@@ -1,11 +1,11 @@
import { type FC } from 'react'; import { type FC, type MouseEvent } from 'react';
import { Dropdown } from 'antd'; import { Dropdown } from 'antd';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
interface MoreDropdownProps { interface MoreDropdownProps {
items: NonNullable<MenuProps['items']>; items: NonNullable<MenuProps['items']>;
placement?: 'bottomRight' | 'bottomLeft' | 'topRight' | 'topLeft'; placement?: 'bottomRight' | 'bottomLeft' | 'topRight' | 'topLeft';
onClick?: (e: React.MouseEvent) => void; onClick?: (e: MouseEvent) => void;
} }
/** /**

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-02 15:21:14 * @Date: 2026-02-02 15:21:14
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-20 20:24:43 * @Last Modified time: 2026-04-22 12:03:08
*/ */
/** /**
* RbCard Component * RbCard Component
@@ -67,7 +67,7 @@ const RbCard: FC<RbCardProps> = ({
{title} {title}
</div> </div>
</Tooltip> </Tooltip>
: <div className="rb:flex-1 rb:leading-5.5 rb:min-w-0 rb:whitespace-break-spaces rb:wrap-break-word rb:line-clamp-2"> : <div className={`rb:flex-1 rb:leading-5.5 rb:min-w-0 rb:whitespace-break-spaces rb:wrap-break-word rb:line-clamp-2 ${titleClassName}`}>
{title} {title}
</div> </div>
} }

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-02 15:29:57 * @Date: 2026-02-02 15:29:57
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-22 11:39:15 * @Last Modified time: 2026-04-22 13:48:09
*/ */
/** /**
* Tag Component * Tag Component
@@ -40,7 +40,7 @@ const colors = {
/** Custom tag component with color themes */ /** Custom tag component with color themes */
const Tag: FC<TagProps> = ({ color = 'processing', children, className, variant = 'outline' }) => { const Tag: FC<TagProps> = ({ color = 'processing', children, className, variant = 'outline' }) => {
return ( return (
<span className={`rb:inline-block rb:px-1 rb:py-0.5 rb:rounded-sm rb:text-[12px] rb:font-regular! rb:leading-4 rb:border ${colors[color]} ${className || ''}, ${variant === 'borderless' ? 'rb:border-none!' : ''}`}> <span className={`rb:inline-block rb:px-1 rb:py-0.5 rb:rounded-sm rb:text-[12px] rb:font-regular! rb:leading-4 rb:border ${colors[color]} ${className || ''} ${variant === 'borderless' ? 'rb:border-none!' : ''}`}>
{children} {children}
</span> </span>
) )

View File

@@ -11,6 +11,15 @@ import App from '@/App.tsx'
// Synchronously import i18n config to ensure initialization before component rendering // Synchronously import i18n config to ensure initialization before component rendering
import './i18n' import './i18n'
// Fix autofill background color on focus
document.addEventListener('animationstart', (e) => {
if (e.animationName === 'onAutoFillStart') {
const input = e.target as HTMLInputElement
input.style.backgroundColor = 'transparent'
input.addEventListener('focus', () => { input.style.backgroundColor = 'transparent' }, { once: false })
}
})
// After a new release, old dynamic chunk files are deleted; force a page reload on preload error // After a new release, old dynamic chunk files are deleted; force a page reload on preload error
window.addEventListener('vite:preloadError', () => { window.addEventListener('vite:preloadError', () => {
console.warn('New version detected, reloading page to load latest assets...') console.warn('New version detected, reloading page to load latest assets...')

View File

@@ -457,4 +457,14 @@ body {
.pageTabs.ant-segmented .ant-segmented-item-selected { .pageTabs.ant-segmented .ant-segmented-item-selected {
box-shadow: 0px 2px 4px 0px rgba(33, 35, 50, 0.16); box-shadow: 0px 2px 4px 0px rgba(33, 35, 50, 0.16);
} }
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 1000px transparent inset !important;
transition: background-color 5000s ease-in-out 0s !important;
animation-name: onAutoFillStart;
animation-duration: 1ms;
}
@keyframes onAutoFillStart { from {} to {} }

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:52:50 * @Date: 2026-02-03 15:52:50
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-22 11:34:06 * @Last Modified time: 2026-04-22 12:07:40
*/ */
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -95,44 +95,39 @@ const ApiKeyManagement: React.FC = () => {
renderItem={(apiKeyItem) => { renderItem={(apiKeyItem) => {
return ( return (
<RbCard <RbCard
title={ title={apiKeyItem.name}
<Flex justify="space-between" className="rb:w-full!"> extra={<MoreDropdown
<Flex gap={4} vertical className="rb:flex-1!"> items={[
{apiKeyItem.name} {
<Flex gap={6}> key: 'edit',
{apiKeyItem.scopes?.includes('memory') && <Tag>{t('apiKey.memoryEngine')}</Tag>} icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/edit_bold.svg')]" />,
{apiKeyItem.scopes?.includes('rag') && <Tag color="success">{t('apiKey.knowledgeBase')}</Tag>} label: t('common.edit'),
{!apiKeyItem.scopes?.includes('memory') && !apiKeyItem.scopes?.includes('rag') && <div className="rb:font-regular!">{t('apiKey.noScopes')}</div>} onClick: () => handleEdit(apiKeyItem),
</Flex> },
</Flex> {
<MoreDropdown key: 'view',
items={[ icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/eye.svg')]" />,
{ label: t('common.view'),
key: 'edit', onClick: () => handleView(apiKeyItem),
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/edit_bold.svg')]" />, },
label: t('common.edit'), {
onClick: () => handleEdit(apiKeyItem), key: 'delete',
}, danger: true,
{ icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/delete_red_big.svg')]" />,
key: 'view', label: t('common.delete'),
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/eye.svg')]" />, onClick: () => handleDelete(apiKeyItem),
label: t('common.view'), },
onClick: () => handleView(apiKeyItem), ]}
}, />}
{ variant="borderless"
key: 'delete', headerClassName="rb:min-h-[42px]!"
danger: true, titleClassName="rb:line-clamp-1!"
icon: <div className="rb:size-4 rb:bg-cover rb:cursor-pointer rb:bg-[url('@/assets/images/common/delete_red_big.svg')]" />,
label: t('common.delete'),
onClick: () => handleDelete(apiKeyItem),
},
]}
/>
</Flex>
}
isNeedTooltip={false}
headerClassName="rb:min-h-[78px]!"
> >
<Flex gap={6} className="rb:-mt-2! rb:mb-4!">
{apiKeyItem.scopes?.includes('memory') && <Tag>{t('apiKey.memoryEngine')}</Tag>}
{apiKeyItem.scopes?.includes('rag') && <Tag color="success">{t('apiKey.knowledgeBase')}</Tag>}
{!apiKeyItem.scopes?.includes('memory') && !apiKeyItem.scopes?.includes('rag') && <div className="rb:font-regular!">{t('apiKey.noScopes')}</div>}
</Flex>
<RbDescriptions <RbDescriptions
items={['id', 'is_expired', 'created_at'].map(key => ({ items={['id', 'is_expired', 'created_at'].map(key => ({
key, key,