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:
t0ng7u
2025-09-02 02:04:22 +08:00
parent 55c8271311
commit 54f118d9ba
10 changed files with 902 additions and 22 deletions

View File

@@ -121,6 +121,7 @@ const EditModelModal = (props) => {
endpoints: '',
name_rule: props.editingModel?.model_name ? 0 : undefined, // 通过未配置模型过来的固定为精确匹配
status: true,
sync_official: true,
});
const handleCancel = () => {
@@ -145,8 +146,9 @@ const EditModelModal = (props) => {
if (!data.endpoints) {
data.endpoints = '';
}
// 处理status将数字转为布尔值
// 处理status/sync_official,将数字转为布尔值
data.status = data.status === 1;
data.sync_official = (data.sync_official ?? 1) === 1;
if (formApiRef.current) {
formApiRef.current.setValues({ ...getInitValues(), ...data });
}
@@ -193,6 +195,7 @@ const EditModelModal = (props) => {
tags: Array.isArray(values.tags) ? values.tags.join(',') : values.tags,
endpoints: values.endpoints || '',
status: values.status ? 1 : 0,
sync_official: values.sync_official ? 1 : 0,
};
if (isEdit) {
@@ -505,6 +508,16 @@ const EditModelModal = (props) => {
}
/>
</Col>
<Col span={24}>
<Form.Switch
field='sync_official'
label={t('参与官方同步')}
extraText={t(
'关闭后,此模型将不会被“同步官方”自动覆盖或创建',
)}
size='large'
/>
</Col>
<Col span={24}>
<Form.Switch
field='status'