mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-20 00:28:38 +00:00
✨ feat(models-sync): official upstream sync with conflict resolution UI, opt‑out flag, and backend resiliency
Backend - Add endpoints: - GET /api/models/sync_upstream/preview — diff preview (filters out models with sync_official = 0) - POST /api/models/sync_upstream — apply sync (create missing; optionally overwrite selected fields) - Respect opt‑out: skip models with sync_official = 0 in both preview and apply - Return detailed stats: created_models, created_vendors, updated_models, skipped_models, plus created_list / updated_list - Add model.Model.SyncOfficial (default 1); auto‑migrated by GORM - Make HTTP fetching robust: - Shared http.Client (connection reuse) with 3x exponential backoff retry - 10MB response cap; keep existing IPv4‑first for *.github.io - Vendor handling: - New ensureVendorID helper (cache lookup → DB lookup → create), reduces round‑trips - Transactional overwrite to avoid partial updates - Small cleanups and clearer helpers (containsField, coalesce, chooseStatus) Frontend - ModelsActions: add “Sync official” button with Popover (p‑2) explaining community contribution; loading = syncing || previewing; preview → conflict modal → apply flow - New UpstreamConflictModal: - Per‑field columns (description/icon/tags/vendor/name_rule/status) with column‑level checkbox to select all - Cell with Checkbox + Tag (“Click to view differences”) and Popover (p‑2) showing Local vs Official values - Auto‑hide columns with no conflicts; responsive width; use native Semi Modal footer - Full i18n coverage - useModelsData: add syncing/previewing states; new methods previewUpstreamDiff, applyUpstreamOverwrite, syncUpstream; refresh vendors/models after apply - EditModelModal: add “Participate in official sync” switch; persisted as sync_official - ModelsColumnDefs: add “Participate in official sync” column i18n - Add missing English keys for the new UI and messages; fix quoting issues Refs - Upstream metadata: https://github.com/basellm/llm-metadata
This commit is contained in:
@@ -95,6 +95,8 @@ export const useModelsData = () => {
|
||||
const [showAddVendor, setShowAddVendor] = useState(false);
|
||||
const [showEditVendor, setShowEditVendor] = useState(false);
|
||||
const [editingVendor, setEditingVendor] = useState({ id: undefined });
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [previewing, setPreviewing] = useState(false);
|
||||
|
||||
const vendorMap = useMemo(() => {
|
||||
const map = {};
|
||||
@@ -163,6 +165,81 @@ export const useModelsData = () => {
|
||||
await loadModels(page, pageSize);
|
||||
};
|
||||
|
||||
// Sync upstream models/vendors for missing models only
|
||||
const syncUpstream = async () => {
|
||||
setSyncing(true);
|
||||
try {
|
||||
const res = await API.post('/api/models/sync_upstream');
|
||||
const { success, message, data } = res.data || {};
|
||||
if (success) {
|
||||
const createdModels = data?.created_models || 0;
|
||||
const createdVendors = data?.created_vendors || 0;
|
||||
const skipped = (data?.skipped_models || []).length || 0;
|
||||
showSuccess(
|
||||
t(
|
||||
`已同步:新增 ${createdModels} 模型,新增 ${createdVendors} 供应商,跳过 ${skipped} 项`,
|
||||
),
|
||||
);
|
||||
await loadVendors();
|
||||
await refresh();
|
||||
} else {
|
||||
showError(message || t('同步失败'));
|
||||
}
|
||||
} catch (e) {
|
||||
showError(t('同步失败'));
|
||||
}
|
||||
setSyncing(false);
|
||||
};
|
||||
|
||||
// Preview upstream differences
|
||||
const previewUpstreamDiff = async () => {
|
||||
setPreviewing(true);
|
||||
try {
|
||||
const res = await API.get('/api/models/sync_upstream/preview');
|
||||
const { success, message, data } = res.data || {};
|
||||
if (success) {
|
||||
return data || { missing: [], conflicts: [] };
|
||||
}
|
||||
showError(message || t('预览失败'));
|
||||
return { missing: [], conflicts: [] };
|
||||
} catch (e) {
|
||||
showError(t('预览失败'));
|
||||
return { missing: [], conflicts: [] };
|
||||
} finally {
|
||||
setPreviewing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Apply selected overwrite
|
||||
const applyUpstreamOverwrite = async (overwrite = []) => {
|
||||
setSyncing(true);
|
||||
try {
|
||||
const res = await API.post('/api/models/sync_upstream', { overwrite });
|
||||
const { success, message, data } = res.data || {};
|
||||
if (success) {
|
||||
const createdModels = data?.created_models || 0;
|
||||
const updatedModels = data?.updated_models || 0;
|
||||
const createdVendors = data?.created_vendors || 0;
|
||||
const skipped = (data?.skipped_models || []).length || 0;
|
||||
showSuccess(
|
||||
t(
|
||||
`完成:新增 ${createdModels} 模型,更新 ${updatedModels} 模型,新增 ${createdVendors} 供应商,跳过 ${skipped} 项`,
|
||||
),
|
||||
);
|
||||
await loadVendors();
|
||||
await refresh();
|
||||
return true;
|
||||
}
|
||||
showError(message || t('同步失败'));
|
||||
return false;
|
||||
} catch (e) {
|
||||
showError(t('同步失败'));
|
||||
return false;
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Search models with keyword and vendor
|
||||
const searchModels = async () => {
|
||||
const { searchKeyword = '', searchVendor = '' } = getFormValues();
|
||||
@@ -398,5 +475,12 @@ export const useModelsData = () => {
|
||||
|
||||
// Translation
|
||||
t,
|
||||
|
||||
// Upstream sync
|
||||
syncing,
|
||||
previewing,
|
||||
syncUpstream,
|
||||
previewUpstreamDiff,
|
||||
applyUpstreamOverwrite,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user