✨ feat(sync): multi-language sync wizard, backend locale support, and conflict modal UX improvements
Frontend (web)
- ModelsActions.jsx
- Replace “Sync Official” with “Sync” and open a new two-step SyncWizard.
- Pass selected locale through to preview, sync, and overwrite flows.
- Keep conflict resolution flow; inject locale into overwrite submission.
- New: models/modals/SyncWizardModal.jsx
- Two-step wizard: (1) method selection (config-sync disabled for now), (2) language selection (en/zh/ja).
- Horizontal, centered Radio cards; returns { option, locale } via onConfirm.
- UpstreamConflictModal.jsx
- Add search input (model fuzzy search) and native pagination.
- Column header checkbox now only applies to rows in the current filtered result.
- Fix “Cannot access ‘filteredDataSource’ before initialization”.
- Refactor with useMemo/useCallback; extract helpers to remove duplicated logic:
- getPresentRowsForField, getHeaderState, applyHeaderChange
- Minor code cleanups and stability improvements.
- i18n (en.json)
- Add strings for the sync wizard and related actions (Sync, Sync Wizard, Select method/source/language, etc.).
- Adjust minor translations.
Hooks
- useModelsData.jsx
- Extend previewUpstreamDiff, syncUpstream, applyUpstreamOverwrite to accept options with locale.
- Send locale via query/body accordingly.
Backend (Go)
- controller/model_sync.go
- Accept locale from query/body and resolve i18n upstream URLs.
- Add SYNC_UPSTREAM_BASE for upstream base override (default: https://basellm.github.io/llm-metadata).
- Make HTTP timeouts/retries/limits configurable:
- SYNC_HTTP_TIMEOUT_SECONDS, SYNC_HTTP_RETRY, SYNC_HTTP_MAX_MB
- Add ETag-based caching and support both envelope and pure array JSON formats.
- Concurrently fetch vendors and models; improve error responses with locale and source URLs.
- Include source meta (locale, models_url, vendors_url) in success payloads.
Notes
- No breaking changes expected.
- Lint passes for touched files.
This commit is contained in:
@@ -21,11 +21,12 @@ import React, { useState } from 'react';
|
||||
import MissingModelsModal from './modals/MissingModelsModal';
|
||||
import PrefillGroupManagement from './modals/PrefillGroupManagement';
|
||||
import EditPrefillGroupModal from './modals/EditPrefillGroupModal';
|
||||
import { Button, Modal, Popover } from '@douyinfe/semi-ui';
|
||||
import { Button, Modal, Popover, RadioGroup, Radio } from '@douyinfe/semi-ui';
|
||||
import { showSuccess, showError, copy } from '../../../helpers';
|
||||
import CompactModeToggle from '../../common/ui/CompactModeToggle';
|
||||
import SelectionNotification from './components/SelectionNotification';
|
||||
import UpstreamConflictModal from './modals/UpstreamConflictModal';
|
||||
import SyncWizardModal from './modals/SyncWizardModal';
|
||||
|
||||
const ModelsActions = ({
|
||||
selectedKeys,
|
||||
@@ -50,10 +51,12 @@ const ModelsActions = ({
|
||||
const [prefillInit, setPrefillInit] = useState({ id: undefined });
|
||||
const [showConflict, setShowConflict] = useState(false);
|
||||
const [conflicts, setConflicts] = useState([]);
|
||||
const [showSyncModal, setShowSyncModal] = useState(false);
|
||||
const [syncLocale, setSyncLocale] = useState('zh');
|
||||
|
||||
const handleSyncUpstream = async () => {
|
||||
const handleSyncUpstream = async (locale) => {
|
||||
// 先预览
|
||||
const data = await previewUpstreamDiff?.();
|
||||
const data = await previewUpstreamDiff?.({ locale });
|
||||
const conflictItems = data?.conflicts || [];
|
||||
if (conflictItems.length > 0) {
|
||||
setConflicts(conflictItems);
|
||||
@@ -61,7 +64,7 @@ const ModelsActions = ({
|
||||
return;
|
||||
}
|
||||
// 无冲突,直接同步缺失
|
||||
await syncUpstream?.();
|
||||
await syncUpstream?.({ locale });
|
||||
};
|
||||
|
||||
// Handle delete selected models with confirmation
|
||||
@@ -151,9 +154,12 @@ const ModelsActions = ({
|
||||
className='flex-1 md:flex-initial'
|
||||
size='small'
|
||||
loading={syncing || previewing}
|
||||
onClick={handleSyncUpstream}
|
||||
onClick={() => {
|
||||
setSyncLocale('zh');
|
||||
setShowSyncModal(true);
|
||||
}}
|
||||
>
|
||||
{t('同步官方')}
|
||||
{t('同步')}
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
@@ -196,6 +202,20 @@ const ModelsActions = ({
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<SyncWizardModal
|
||||
visible={showSyncModal}
|
||||
onClose={() => setShowSyncModal(false)}
|
||||
loading={syncing || previewing}
|
||||
t={t}
|
||||
onConfirm={async ({ option, locale }) => {
|
||||
setSyncLocale(locale);
|
||||
if (option === 'official') {
|
||||
await handleSyncUpstream(locale);
|
||||
}
|
||||
setShowSyncModal(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
<MissingModelsModal
|
||||
visible={showMissingModal}
|
||||
onClose={() => setShowMissingModal(false)}
|
||||
@@ -224,7 +244,10 @@ const ModelsActions = ({
|
||||
onClose={() => setShowConflict(false)}
|
||||
conflicts={conflicts}
|
||||
onSubmit={async (payload) => {
|
||||
return await applyUpstreamOverwrite?.(payload);
|
||||
return await applyUpstreamOverwrite?.({
|
||||
...payload,
|
||||
locale: syncLocale,
|
||||
});
|
||||
}}
|
||||
t={t}
|
||||
loading={syncing}
|
||||
|
||||
Reference in New Issue
Block a user