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:
t0ng7u
2025-09-02 18:49:37 +08:00
parent 42e5794d00
commit 14af08750f
9 changed files with 856 additions and 462 deletions

View File

@@ -166,10 +166,13 @@ export const useModelsData = () => {
};
// Sync upstream models/vendors for missing models only
const syncUpstream = async () => {
const syncUpstream = async (opts = {}) => {
const locale = opts?.locale;
setSyncing(true);
try {
const res = await API.post('/api/models/sync_upstream');
const body = {};
if (locale) body.locale = locale;
const res = await API.post('/api/models/sync_upstream', body);
const { success, message, data } = res.data || {};
if (success) {
const createdModels = data?.created_models || 0;
@@ -192,10 +195,12 @@ export const useModelsData = () => {
};
// Preview upstream differences
const previewUpstreamDiff = async () => {
const previewUpstreamDiff = async (opts = {}) => {
const locale = opts?.locale;
setPreviewing(true);
try {
const res = await API.get('/api/models/sync_upstream/preview');
const url = `/api/models/sync_upstream/preview${locale ? `?locale=${locale}` : ''}`;
const res = await API.get(url);
const { success, message, data } = res.data || {};
if (success) {
return data || { missing: [], conflicts: [] };
@@ -211,10 +216,15 @@ export const useModelsData = () => {
};
// Apply selected overwrite
const applyUpstreamOverwrite = async (overwrite = []) => {
const applyUpstreamOverwrite = async (payloadOrArray = []) => {
const isArray = Array.isArray(payloadOrArray);
const overwrite = isArray ? payloadOrArray : payloadOrArray.overwrite || [];
const locale = isArray ? undefined : payloadOrArray.locale;
setSyncing(true);
try {
const res = await API.post('/api/models/sync_upstream', { overwrite });
const body = { overwrite };
if (locale) body.locale = locale;
const res = await API.post('/api/models/sync_upstream', body);
const { success, message, data } = res.data || {};
if (success) {
const createdModels = data?.created_models || 0;