From 458472f3e2e3bb84946ee27aaa92d1fd63b6713a Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Thu, 19 Jun 2025 18:54:46 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=8D=20feat(ratio-sync):=20add=20fuzzy?= =?UTF-8?q?=20model=20search=20&=20enhance=20empty-state=20UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary 1. Add model name search box • Introduce Semi UI `Input` with `IconSearch` prefix next to the “Apply Sync” button. • Support case-insensitive fuzzy matching of model names. • Real-time filtering, pagination and bulk-select logic now work on filtered data. 2. Improve empty state handling • Add `hasSynced` flag to distinguish “not synced yet” from “synced with no differences”. • Display messages: – “Please select sync channels” when no sync has been performed. – “No differences found” when a sync completed with zero discrepancies. – “No matching model found” when search yields no results. 3. UI tweaks • Replace lucide-react `Search` icon with Semi UI `IconSearch` for visual consistency. • Keep responsive width and clearable input for better usability. Why These changes allow admins to quickly locate specific models and provide accurate feedback on the sync status, greatly improving the usability of the Upstream Ratio Sync page. --- web/src/i18n/locales/en.json | 3 +- .../pages/Setting/Ratio/UpstreamRatioSync.js | 53 +++++++++++++++---- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index b8e1afd8..ab793364 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1687,5 +1687,6 @@ "缓存倍率": "Cache ratio", "暂无差异化倍率显示": "No differential ratio display", "请先选择同步渠道": "Please select the synchronization channel first", - "与本地相同": "Same as local" + "与本地相同": "Same as local", + "未找到匹配的模型": "No matching model found" } \ No newline at end of file diff --git a/web/src/pages/Setting/Ratio/UpstreamRatioSync.js b/web/src/pages/Setting/Ratio/UpstreamRatioSync.js index aae6d9f3..f83e0cdc 100644 --- a/web/src/pages/Setting/Ratio/UpstreamRatioSync.js +++ b/web/src/pages/Setting/Ratio/UpstreamRatioSync.js @@ -6,7 +6,9 @@ import { Empty, Checkbox, Form, + Input, } from '@douyinfe/semi-ui'; +import { IconSearch } from '@douyinfe/semi-icons'; import { RefreshCcw, CheckSquare, @@ -37,10 +39,16 @@ export default function UpstreamRatioSync(props) { const [differences, setDifferences] = useState({}); const [resolutions, setResolutions] = useState({}); + // 是否已经执行过同步 + const [hasSynced, setHasSynced] = useState(false); + // 分页相关状态 const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); + // 搜索相关状态 + const [searchKeyword, setSearchKeyword] = useState(''); + const fetchAllChannels = async () => { setLoading(true); try { @@ -114,6 +122,7 @@ export default function UpstreamRatioSync(props) { setDifferences(differences); setResolutions({}); + setHasSynced(true); if (Object.keys(differences).length === 0) { showSuccess(t('已与上游倍率完全一致,无需同步')); @@ -233,6 +242,15 @@ export default function UpstreamRatioSync(props) { ); })()} + + } + placeholder={t('搜索模型名称')} + value={searchKeyword} + onChange={setSearchKeyword} + className="!rounded-full w-full md:w-64 mt-2" + showClear + /> @@ -257,20 +275,37 @@ export default function UpstreamRatioSync(props) { return tmp; }, [differences]); + const filteredDataSource = useMemo(() => { + if (!searchKeyword.trim()) { + return dataSource; + } + + const keyword = searchKeyword.toLowerCase().trim(); + return dataSource.filter(item => + item.model.toLowerCase().includes(keyword) + ); + }, [dataSource, searchKeyword]); + const upstreamNames = useMemo(() => { const set = new Set(); - dataSource.forEach((row) => { + filteredDataSource.forEach((row) => { Object.keys(row.upstreams || {}).forEach((name) => set.add(name)); }); return Array.from(set); - }, [dataSource]); + }, [filteredDataSource]); - if (dataSource.length === 0) { + if (filteredDataSource.length === 0) { return ( } darkModeImage={} - description={Object.keys(differences).length === 0 ? t('暂无差异化倍率显示') : t('请先选择同步渠道')} + description={ + searchKeyword.trim() + ? t('未找到匹配的模型') + : (Object.keys(differences).length === 0 ? + (hasSynced ? t('暂无差异化倍率显示') : t('请先选择同步渠道')) + : t('请先选择同步渠道')) + } style={{ padding: 30 }} /> ); @@ -309,7 +344,7 @@ export default function UpstreamRatioSync(props) { let selectableCount = 0; let selectedCount = 0; - dataSource.forEach((row) => { + filteredDataSource.forEach((row) => { const upstreamVal = row.upstreams?.[upName]; if (upstreamVal !== null && upstreamVal !== undefined && upstreamVal !== 'same') { selectableCount++; @@ -333,7 +368,7 @@ export default function UpstreamRatioSync(props) { setResolutions((prev) => { const newRes = { ...prev }; - dataSource.forEach((row) => { + filteredDataSource.forEach((row) => { const upstreamVal = row.upstreams?.[upName]; if (upstreamVal !== null && upstreamVal !== undefined && upstreamVal !== 'same') { if (checked) { @@ -412,17 +447,17 @@ export default function UpstreamRatioSync(props) { return ( t('第 {{start}} - {{end}} 条,共 {{total}} 条', { start: page.currentStart, end: page.currentEnd, - total: dataSource.length, + total: filteredDataSource.length, }), pageSizeOptions: ['5', '10', '20', '50'], onChange: (page, size) => {