| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { useState, useEffect, useMemo } from 'react'; |
| import { useTranslation } from 'react-i18next'; |
| import { API, showError, showSuccess } from '../../helpers'; |
| import { ITEMS_PER_PAGE } from '../../constants'; |
| import { useTableCompactMode } from '../common/useTableCompactMode'; |
|
|
| export const useModelsData = () => { |
| const { t } = useTranslation(); |
| const [compactMode, setCompactMode] = useTableCompactMode('models'); |
|
|
| |
| const [models, setModels] = useState([]); |
| const [loading, setLoading] = useState(true); |
| const [activePage, setActivePage] = useState(1); |
| const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); |
| const [searching, setSearching] = useState(false); |
| const [modelCount, setModelCount] = useState(0); |
|
|
| |
| const [showEdit, setShowEdit] = useState(false); |
| const [editingModel, setEditingModel] = useState({ |
| id: undefined, |
| }); |
|
|
| |
| const [selectedKeys, setSelectedKeys] = useState([]); |
| const rowSelection = { |
| getCheckboxProps: (record) => ({ |
| name: record.model_name, |
| }), |
| selectedRowKeys: selectedKeys.map((model) => model.id), |
| onChange: (selectedRowKeys, selectedRows) => { |
| setSelectedKeys(selectedRows); |
| }, |
| }; |
|
|
| |
| const formInitValues = { |
| searchKeyword: '', |
| searchVendor: '', |
| }; |
|
|
| |
| |
| const extractItems = (payload) => { |
| const items = payload?.items || payload || []; |
| return Array.isArray(items) ? items : []; |
| }; |
|
|
| |
| const [formApi, setFormApi] = useState(null); |
|
|
| |
| const getFormValues = () => formApi?.getValues() || formInitValues; |
|
|
| |
| const closeEdit = () => { |
| setShowEdit(false); |
| setTimeout(() => { |
| setEditingModel({ id: undefined }); |
| }, 500); |
| }; |
|
|
| |
| const setModelFormat = (models) => { |
| for (let i = 0; i < models.length; i++) { |
| models[i].key = models[i].id; |
| } |
| setModels(models); |
| }; |
|
|
| |
| const [vendors, setVendors] = useState([]); |
| const [vendorCounts, setVendorCounts] = useState({}); |
| const [activeVendorKey, setActiveVendorKey] = useState('all'); |
| 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 = {}; |
| vendors.forEach((v) => { |
| map[v.id] = v; |
| }); |
| return map; |
| }, [vendors]); |
|
|
| |
| const loadVendors = async () => { |
| try { |
| const res = await API.get('/api/vendors/?page_size=1000'); |
| if (res.data.success) { |
| const items = res.data.data.items || res.data.data || []; |
| setVendors(Array.isArray(items) ? items : []); |
| } |
| } catch (_) { |
| |
| } |
| }; |
|
|
| |
| const loadModels = async ( |
| page = 1, |
| size = pageSize, |
| vendorKey = activeVendorKey, |
| ) => { |
| setLoading(true); |
| try { |
| let url = `/api/models/?p=${page}&page_size=${size}`; |
| if (vendorKey && vendorKey !== 'all') { |
| |
| url = `/api/models/search?vendor=${vendorKey}&p=${page}&page_size=${size}`; |
| } |
|
|
| const res = await API.get(url); |
| const { success, message, data } = res.data; |
| if (success) { |
| const newPageData = extractItems(data); |
| setActivePage(data.page || page); |
| setModelCount(data.total || newPageData.length); |
| setModelFormat(newPageData); |
|
|
| if (data.vendor_counts) { |
| const sumAll = Object.values(data.vendor_counts).reduce( |
| (acc, v) => acc + v, |
| 0, |
| ); |
| setVendorCounts({ ...data.vendor_counts, all: sumAll }); |
| } |
| } else { |
| showError(message); |
| setModels([]); |
| } |
| } catch (error) { |
| console.error(error); |
| showError(t('获取模型列表失败')); |
| setModels([]); |
| } |
| setLoading(false); |
| }; |
|
|
| |
| const refresh = async (page = activePage) => { |
| await loadModels(page, pageSize); |
| }; |
|
|
| |
| const syncUpstream = async (opts = {}) => { |
| const locale = opts?.locale; |
| setSyncing(true); |
| try { |
| 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; |
| 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); |
| }; |
|
|
| |
| const previewUpstreamDiff = async (opts = {}) => { |
| const locale = opts?.locale; |
| setPreviewing(true); |
| try { |
| 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: [] }; |
| } |
| showError(message || t('预览失败')); |
| return { missing: [], conflicts: [] }; |
| } catch (e) { |
| showError(t('预览失败')); |
| return { missing: [], conflicts: [] }; |
| } finally { |
| setPreviewing(false); |
| } |
| }; |
|
|
| |
| 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 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; |
| 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); |
| } |
| }; |
|
|
| |
| const searchModels = async () => { |
| const { searchKeyword = '', searchVendor = '' } = getFormValues(); |
|
|
| if (searchKeyword === '' && searchVendor === '') { |
| |
| await loadModels(1, pageSize); |
| return; |
| } |
|
|
| setSearching(true); |
| try { |
| const res = await API.get( |
| `/api/models/search?keyword=${searchKeyword}&vendor=${searchVendor}&p=1&page_size=${pageSize}`, |
| ); |
| const { success, message, data } = res.data; |
| if (success) { |
| const newPageData = extractItems(data); |
| setActivePage(data.page || 1); |
| setModelCount(data.total || newPageData.length); |
| setModelFormat(newPageData); |
| if (data.vendor_counts) { |
| const sumAll = Object.values(data.vendor_counts).reduce( |
| (acc, v) => acc + v, |
| 0, |
| ); |
| setVendorCounts({ ...data.vendor_counts, all: sumAll }); |
| } |
| } else { |
| showError(message); |
| setModels([]); |
| } |
| } catch (error) { |
| console.error(error); |
| showError(t('搜索模型失败')); |
| setModels([]); |
| } |
| setSearching(false); |
| }; |
|
|
| |
| const manageModel = async (id, action, record) => { |
| let res; |
| switch (action) { |
| case 'delete': |
| res = await API.delete(`/api/models/${id}`); |
| break; |
| case 'enable': |
| res = await API.put('/api/models/?status_only=true', { id, status: 1 }); |
| break; |
| case 'disable': |
| res = await API.put('/api/models/?status_only=true', { id, status: 0 }); |
| break; |
| default: |
| return; |
| } |
|
|
| const { success, message } = res.data; |
| if (success) { |
| showSuccess(t('操作成功完成!')); |
| if (action === 'delete') { |
| await refresh(); |
| } else { |
| |
| setModels((prevModels) => |
| prevModels.map((model) => |
| model.id === id |
| ? { ...model, status: action === 'enable' ? 1 : 0 } |
| : model, |
| ), |
| ); |
| } |
| } else { |
| showError(message); |
| } |
| }; |
|
|
| |
| const handlePageChange = (page) => { |
| setActivePage(page); |
| loadModels(page, pageSize, activeVendorKey); |
| }; |
|
|
| |
| useEffect(() => { |
| loadModels(1, pageSize, activeVendorKey); |
| }, [activeVendorKey]); |
|
|
| |
| const handlePageSizeChange = async (size) => { |
| setPageSize(size); |
| setActivePage(1); |
| await loadModels(1, size, activeVendorKey); |
| }; |
|
|
| |
| const handleRow = (record, index) => { |
| const rowStyle = |
| record.status !== 1 |
| ? { |
| style: { |
| background: 'var(--semi-color-disabled-border)', |
| }, |
| } |
| : {}; |
|
|
| return { |
| ...rowStyle, |
| onClick: (event) => { |
| |
| if (event.target.closest('button, .semi-button')) { |
| return; |
| } |
| const newSelectedKeys = selectedKeys.some( |
| (item) => item.id === record.id, |
| ) |
| ? selectedKeys.filter((item) => item.id !== record.id) |
| : [...selectedKeys, record]; |
| setSelectedKeys(newSelectedKeys); |
| }, |
| }; |
| }; |
|
|
| |
| const batchDeleteModels = async () => { |
| if (selectedKeys.length === 0) { |
| showError(t('请至少选择一个模型')); |
| return; |
| } |
|
|
| try { |
| const deletePromises = selectedKeys.map((model) => |
| API.delete(`/api/models/${model.id}`), |
| ); |
|
|
| const results = await Promise.all(deletePromises); |
| let successCount = 0; |
|
|
| results.forEach((res, index) => { |
| if (res.data.success) { |
| successCount++; |
| } else { |
| showError( |
| `删除模型 ${selectedKeys[index].model_name} 失败: ${res.data.message}`, |
| ); |
| } |
| }); |
|
|
| if (successCount > 0) { |
| showSuccess(t(`成功删除 ${successCount} 个模型`)); |
| setSelectedKeys([]); |
| await refresh(); |
| } |
| } catch (error) { |
| showError(t('批量删除失败')); |
| } |
| }; |
|
|
| |
| const copyText = async (text) => { |
| try { |
| await navigator.clipboard.writeText(text); |
| showSuccess(t('复制成功')); |
| } catch (error) { |
| console.error('Copy failed:', error); |
| showError(t('复制失败')); |
| } |
| }; |
|
|
| |
| useEffect(() => { |
| (async () => { |
| await loadVendors(); |
| })(); |
| |
| }, []); |
|
|
| return { |
| |
| models, |
| loading, |
| searching, |
| activePage, |
| pageSize, |
| modelCount, |
|
|
| |
| selectedKeys, |
| rowSelection, |
| handleRow, |
| setSelectedKeys, |
|
|
| |
| showEdit, |
| editingModel, |
| setEditingModel, |
| setShowEdit, |
| closeEdit, |
|
|
| |
| formInitValues, |
| setFormApi, |
|
|
| |
| loadModels, |
| searchModels, |
| refresh, |
| manageModel, |
| batchDeleteModels, |
| copyText, |
|
|
| |
| setActivePage, |
| handlePageChange, |
| handlePageSizeChange, |
|
|
| |
| compactMode, |
| setCompactMode, |
|
|
| |
| vendors, |
| vendorMap, |
| vendorCounts, |
| activeVendorKey, |
| setActiveVendorKey, |
| showAddVendor, |
| setShowAddVendor, |
| showEditVendor, |
| setShowEditVendor, |
| editingVendor, |
| setEditingVendor, |
| loadVendors, |
|
|
| |
| t, |
|
|
| |
| syncing, |
| previewing, |
| syncUpstream, |
| previewUpstreamDiff, |
| applyUpstreamOverwrite, |
| }; |
| }; |
|
|