From c03baed4f79838d87519d4c2ac0fd4d40ddfb461 Mon Sep 17 00:00:00 2001 From: zyycn Date: Fri, 3 Jul 2026 21:19:30 +0800 Subject: [PATCH] refactor: split frontend maintenance code Co-authored-by: Codex --- .../base/BaseTable/BaseTablePagination.vue | 133 ++++++++ .../src/components/base/BaseTable/index.vue | 205 +----------- .../BaseTable/useHorizontalStickyShadow.ts | 82 +++++ frontend/src/composables/useAsyncAction.ts | 73 +++++ frontend/src/composables/useIdSet.ts | 42 +++ .../composables/useAccountMutations.ts | 306 +++++++++-------- .../composables/useApiKeyMutations.ts | 141 ++++---- .../settings/components/AdminApiKeyCard.vue | 105 ++++++ .../settings/components/ModelAliasesCard.vue | 94 ++++++ .../components/RotationStrategyCard.vue | 58 ++++ .../components/RuntimeSettingsCard.vue | 57 ++++ frontend/src/views/settings/index.vue | 307 +++--------------- 12 files changed, 928 insertions(+), 675 deletions(-) create mode 100644 frontend/src/components/base/BaseTable/BaseTablePagination.vue create mode 100644 frontend/src/components/base/BaseTable/useHorizontalStickyShadow.ts create mode 100644 frontend/src/composables/useAsyncAction.ts create mode 100644 frontend/src/composables/useIdSet.ts create mode 100644 frontend/src/views/settings/components/AdminApiKeyCard.vue create mode 100644 frontend/src/views/settings/components/ModelAliasesCard.vue create mode 100644 frontend/src/views/settings/components/RotationStrategyCard.vue create mode 100644 frontend/src/views/settings/components/RuntimeSettingsCard.vue diff --git a/frontend/src/components/base/BaseTable/BaseTablePagination.vue b/frontend/src/components/base/BaseTable/BaseTablePagination.vue new file mode 100644 index 000000000..7592186b6 --- /dev/null +++ b/frontend/src/components/base/BaseTable/BaseTablePagination.vue @@ -0,0 +1,133 @@ + + + diff --git a/frontend/src/components/base/BaseTable/index.vue b/frontend/src/components/base/BaseTable/index.vue index 1fed6ee83..a580b47f3 100644 --- a/frontend/src/components/base/BaseTable/index.vue +++ b/frontend/src/components/base/BaseTable/index.vue @@ -1,11 +1,9 @@ diff --git a/frontend/src/components/base/BaseTable/useHorizontalStickyShadow.ts b/frontend/src/components/base/BaseTable/useHorizontalStickyShadow.ts new file mode 100644 index 000000000..ba52e1544 --- /dev/null +++ b/frontend/src/components/base/BaseTable/useHorizontalStickyShadow.ts @@ -0,0 +1,82 @@ +import { useResizeObserver } from '@vueuse/core' +import { nextTick, onMounted, shallowRef, useTemplateRef, watch, type Ref } from 'vue' + +import type BaseScrollbar from '../BaseScrollbar.vue' + +interface UseHorizontalStickyShadowOptions { + hasRows: Ref + watchSources: () => unknown[] +} + +export function useHorizontalStickyShadow(options: UseHorizontalStickyShadowOptions) { + const headerWrapRef = useTemplateRef('headerWrap') + const bodyScrollbarRef = useTemplateRef>('bodyScrollbar') + const tableViewRef = useTemplateRef('tableView') + const horizontalScrolled = shallowRef(false) + const horizontalCanScrollRight = shallowRef(false) + + function maxScrollLeft(wrap: HTMLElement) { + return Math.max(wrap.scrollWidth - wrap.clientWidth, 0) + } + + function scrollWrap() { + return bodyScrollbarRef.value?.wrapRef ?? null + } + + function resetHorizontalScrollbar() { + horizontalScrolled.value = false + horizontalCanScrollRight.value = false + } + + function updateHorizontalScrollbar() { + const wrap = scrollWrap() + if (!wrap || !options.hasRows.value) { + resetHorizontalScrollbar() + return + } + + const scrollRange = maxScrollLeft(wrap) + if (scrollRange <= 0) { + resetHorizontalScrollbar() + return + } + + horizontalScrolled.value = wrap.scrollLeft > 0 + horizontalCanScrollRight.value = wrap.scrollLeft < scrollRange - 1 + } + + function handleTableScroll() { + const wrap = scrollWrap() + if (wrap && headerWrapRef.value) { + headerWrapRef.value.scrollLeft = wrap.scrollLeft + } + updateHorizontalScrollbar() + } + + onMounted(async () => { + await nextTick() + updateHorizontalScrollbar() + }) + + useResizeObserver( + () => + [scrollWrap(), tableViewRef.value].filter( + (element): element is HTMLDivElement | HTMLTableElement => Boolean(element), + ), + updateHorizontalScrollbar, + ) + + watch(options.watchSources, async () => { + await nextTick() + updateHorizontalScrollbar() + }) + + return { + headerWrapRef, + bodyScrollbarRef, + tableViewRef, + horizontalScrolled, + horizontalCanScrollRight, + handleTableScroll, + } +} diff --git a/frontend/src/composables/useAsyncAction.ts b/frontend/src/composables/useAsyncAction.ts new file mode 100644 index 000000000..28a214319 --- /dev/null +++ b/frontend/src/composables/useAsyncAction.ts @@ -0,0 +1,73 @@ +import { shallowRef } from 'vue' + +import { toast } from '@/components/base/BaseToast' +import { withMinimumDuration } from '@/utils/async' + +type MaybePromise = T | Promise +type AsyncActionErrorText = string | false | ((error: unknown) => string | false) + +interface AsyncActionRunOptions { + errorText?: AsyncActionErrorText + minimumMs?: number + onError?: (error: unknown) => void + rethrow?: boolean +} + +function errorMessage(error: unknown) { + if (error instanceof Error) { + return error.message + } + + if (error && typeof error === 'object' && 'message' in error) { + const message = (error as { message?: unknown }).message + return typeof message === 'string' ? message : '' + } + + return '' +} + +function resolveErrorText(error: unknown, errorText: AsyncActionErrorText | undefined) { + const fallback = typeof errorText === 'function' ? errorText(error) : errorText + if (fallback === false) { + return '' + } + + return errorMessage(error) || fallback || '' +} + +export function useAsyncAction() { + const loading = shallowRef(false) + + async function run(task: () => MaybePromise, options: AsyncActionRunOptions = {}) { + if (loading.value) { + return undefined + } + + loading.value = true + try { + const execute = async () => task() + return options.minimumMs === undefined + ? await execute() + : await withMinimumDuration(execute, options.minimumMs) + } catch (error) { + options.onError?.(error) + + const message = resolveErrorText(error, options.errorText) + if (message) { + toast.error(message) + } + + if (options.rethrow) { + throw error + } + return undefined + } finally { + loading.value = false + } + } + + return { + loading, + run, + } +} diff --git a/frontend/src/composables/useIdSet.ts b/frontend/src/composables/useIdSet.ts new file mode 100644 index 000000000..74580ca7f --- /dev/null +++ b/frontend/src/composables/useIdSet.ts @@ -0,0 +1,42 @@ +import { shallowRef } from 'vue' + +type MaybePromise = T | Promise + +export function useIdSet() { + const ids = shallowRef>(new Set()) + + function has(id: T) { + return ids.value.has(id) + } + + function add(id: T) { + ids.value = new Set(ids.value).add(id) + } + + function remove(id: T) { + const next = new Set(ids.value) + next.delete(id) + ids.value = next + } + + async function run(id: T, task: () => MaybePromise) { + if (has(id)) { + return undefined + } + + add(id) + try { + return await task() + } finally { + remove(id) + } + } + + return { + ids, + has, + add, + remove, + run, + } +} diff --git a/frontend/src/views/accounts/composables/useAccountMutations.ts b/frontend/src/views/accounts/composables/useAccountMutations.ts index 7f662d77b..b90b564c2 100644 --- a/frontend/src/views/accounts/composables/useAccountMutations.ts +++ b/frontend/src/views/accounts/composables/useAccountMutations.ts @@ -14,7 +14,9 @@ import { updateAccount, } from '@/api' import { toast } from '@/components/base/BaseToast' +import { useAsyncAction } from '@/composables/useAsyncAction' import { useDownload } from '@/composables/useDownload' +import { useIdSet } from '@/composables/useIdSet' import { withMinimumDuration } from '@/utils/async' export function useAccountMutations(options: { @@ -40,15 +42,24 @@ export function useAccountMutations(options: { const editingAccount = ref(null) const editingScheduleStatus = ref('active') const pendingDeleteAccount = ref(null) - const refreshingAccountIds = ref>(new Set()) - const refreshingQuotaAccountIds = ref>(new Set()) - const updatingStatusAccountIds = ref>(new Set()) - const deletingAccount = ref(false) - const creatingAccount = ref(false) - const authorizingOAuth = ref(false) - const savingAccount = ref(false) - const batchDeleting = ref(false) - const exportingAccounts = ref(false) + const refreshingAccounts = useIdSet() + const refreshingQuotaAccounts = useIdSet() + const updatingStatusAccounts = useIdSet() + const deletingAccountAction = useAsyncAction() + const creatingAccountAction = useAsyncAction() + const authorizingOAuthAction = useAsyncAction() + const savingAccountAction = useAsyncAction() + const batchDeletingAction = useAsyncAction() + const exportingAccountsAction = useAsyncAction() + const refreshingAccountIds = refreshingAccounts.ids + const refreshingQuotaAccountIds = refreshingQuotaAccounts.ids + const updatingStatusAccountIds = updatingStatusAccounts.ids + const deletingAccount = deletingAccountAction.loading + const creatingAccount = creatingAccountAction.loading + const authorizingOAuth = authorizingOAuthAction.loading + const savingAccount = savingAccountAction.loading + const batchDeleting = batchDeletingAction.loading + const exportingAccounts = exportingAccountsAction.loading const createForm = ref({ mode: 'oauth', @@ -130,21 +141,19 @@ export function useAccountMutations(options: { } if (creatingAccount.value) return - try { - const payload = accountImportPayload() - if (!payload) return + await creatingAccountAction.run( + async () => { + const payload = accountImportPayload() + if (!payload) return - creatingAccount.value = true - const result = await importAccounts(payload) - showCreateModal.value = false - resetCreateForm() - await loadAccounts() - toast.success(importSuccessText(result)) - } catch (error) { - toast.error(error instanceof Error ? error.message : '导入失败') - } finally { - creatingAccount.value = false - } + const result = await importAccounts(payload) + showCreateModal.value = false + resetCreateForm() + await loadAccounts() + toast.success(importSuccessText(result)) + }, + { errorText: '导入失败' }, + ) } function accountImportPayload() { @@ -189,45 +198,39 @@ export function useAccountMutations(options: { async function handleAuthorizeOAuth() { if (authorizingOAuth.value) return - try { - authorizingOAuth.value = true - const result = await authorizeAccountOAuth() - createForm.value = { - ...createForm.value, - mode: 'oauth', - oauthSessionId: result.sessionId, - oauthAuthUrl: result.authUrl, - oauthCallback: '', - } - toast.success('授权链接已生成') - } catch (error: any) { - toast.error(error.message || '授权链接生成失败') - } finally { - authorizingOAuth.value = false - } + await authorizingOAuthAction.run( + async () => { + const result = await authorizeAccountOAuth() + createForm.value = { + ...createForm.value, + mode: 'oauth', + oauthSessionId: result.sessionId, + oauthAuthUrl: result.authUrl, + oauthCallback: '', + } + toast.success('授权链接已生成') + }, + { errorText: '授权链接生成失败' }, + ) } async function handleExchangeOAuth() { if (creatingAccount.value) return - try { - const payload = accountImportPayload() - if (!payload) return + await creatingAccountAction.run( + async () => { + const payload = accountImportPayload() + if (!payload) return - creatingAccount.value = true - const result = await exchangeAccountOAuth(payload) - const successText = oauthSuccessText(result) - showCreateModal.value = false - resetCreateForm() - await loadAccounts() - toast.success(successText) - } catch (error: any) { - toast.error( - error.message || (reauthorizingAccount.value ? '重新授权失败' : 'OAuth 授权导入失败'), - ) - } finally { - creatingAccount.value = false - } + const result = await exchangeAccountOAuth(payload) + const successText = oauthSuccessText(result) + showCreateModal.value = false + resetCreateForm() + await loadAccounts() + toast.success(successText) + }, + { errorText: reauthorizingAccount.value ? '重新授权失败' : 'OAuth 授权导入失败' }, + ) } function resetCreateForm() { @@ -286,24 +289,22 @@ export function useAccountMutations(options: { const account = editingAccount.value if (!account) return - try { - savingAccount.value = true - const payload: any = { - id: account.id, - label: nullableTrimmedValue(editForm.value.label), - } - if (editForm.value.status !== editingScheduleStatus.value) { - payload.status = editForm.value.status - } - await updateAccount(payload) - editingAccount.value = null - await loadAccounts() - toast.success('账号已更新') - } catch (error: any) { - toast.error(error.message || '保存失败') - } finally { - savingAccount.value = false - } + await savingAccountAction.run( + async () => { + const payload: any = { + id: account.id, + label: nullableTrimmedValue(editForm.value.label), + } + if (editForm.value.status !== editingScheduleStatus.value) { + payload.status = editForm.value.status + } + await updateAccount(payload) + editingAccount.value = null + await loadAccounts() + toast.success('账号已更新') + }, + { errorText: '保存失败' }, + ) } function nullableTrimmedValue(value: string) { @@ -322,35 +323,36 @@ export function useAccountMutations(options: { const accountId = pendingDeleteAccount.value?.id if (!accountId) return - try { - deletingAccount.value = true - await deleteAccounts({ ids: [accountId] }) - showSingleDeleteModal.value = false - pendingDeleteAccount.value = null - await loadAccounts() - toast.success('账号已删除') - } catch (error: any) { - toast.error(error.message || '删除失败') - } finally { - deletingAccount.value = false - } + await deletingAccountAction.run( + async () => { + await deleteAccounts({ ids: [accountId] }) + showSingleDeleteModal.value = false + pendingDeleteAccount.value = null + await loadAccounts() + toast.success('账号已删除') + }, + { errorText: '删除失败' }, + ) } async function handleBatchDelete() { if (batchDeleting.value) return if (options.selectedIds.value.size === 0) return - try { - batchDeleting.value = true - await deleteAccounts({ ids: [...options.selectedIds.value] }) - options.selectedIds.value = new Set() - showDeleteModal.value = false - await loadAccounts() - } catch (error) { - console.error('Failed to batch delete accounts:', error) - } finally { - batchDeleting.value = false - } + await batchDeletingAction.run( + async () => { + await deleteAccounts({ ids: [...options.selectedIds.value] }) + options.selectedIds.value = new Set() + showDeleteModal.value = false + await loadAccounts() + }, + { + errorText: false, + onError: (error) => { + console.error('Failed to batch delete accounts:', error) + }, + }, + ) } async function handleExportAccounts() { @@ -361,78 +363,64 @@ export function useAccountMutations(options: { return } - try { - exportingAccounts.value = true - const payload = await exportAccounts({ ids: selected.join(',') }) - await downloadJson(payload, exportFileName(selected.length)) - toast.success(`已导出 ${selected.length} 个账号`) - } catch (error: any) { - toast.error(error.message || '导出失败') - } finally { - exportingAccounts.value = false - } + await exportingAccountsAction.run( + async () => { + const payload = await exportAccounts({ ids: selected.join(',') }) + await downloadJson(payload, exportFileName(selected.length)) + toast.success(`已导出 ${selected.length} 个账号`) + }, + { errorText: '导出失败' }, + ) } async function handleRefresh(accountId: string) { - if (refreshingAccountIds.value.has(accountId)) return - refreshingAccountIds.value = new Set(refreshingAccountIds.value).add(accountId) - try { - await withMinimumDuration(async () => { - await refreshAccount({ id: accountId }) - await loadAccounts() - }) - toast.success('Token 已刷新') - } catch (error: any) { - toast.error(error.message || '刷新失败') - } finally { - const next = new Set(refreshingAccountIds.value) - next.delete(accountId) - refreshingAccountIds.value = next - } + await refreshingAccounts.run(accountId, async () => { + try { + await withMinimumDuration(async () => { + await refreshAccount({ id: accountId }) + await loadAccounts() + }) + toast.success('Token 已刷新') + } catch (error: any) { + toast.error(error.message || '刷新失败') + } + }) } async function handleRefreshQuota(accountId: string) { - if (refreshingQuotaAccountIds.value.has(accountId)) return - refreshingQuotaAccountIds.value = new Set(refreshingQuotaAccountIds.value).add(accountId) - try { - await withMinimumDuration(async () => { - const result = await getAccountQuota({ id: accountId }) - if (result?.quotaData) { - accounts.value = accounts.value.map((account) => - account.id === accountId - ? { - ...account, - quota: result.quotaData, - planType: result.planType ?? account.planType, - } - : account, - ) - } - }) - } catch (error) { - console.error('Failed to refresh account quota:', error) - } finally { - const next = new Set(refreshingQuotaAccountIds.value) - next.delete(accountId) - refreshingQuotaAccountIds.value = next - } + await refreshingQuotaAccounts.run(accountId, async () => { + try { + await withMinimumDuration(async () => { + const result = await getAccountQuota({ id: accountId }) + if (result?.quotaData) { + accounts.value = accounts.value.map((account) => + account.id === accountId + ? { + ...account, + quota: result.quotaData, + planType: result.planType ?? account.planType, + } + : account, + ) + } + }) + } catch (error) { + console.error('Failed to refresh account quota:', error) + } + }) } async function handleToggleSchedule(account: any) { - if (updatingStatusAccountIds.value.has(account.id)) return const nextStatus = account.status === 'disabled' ? 'active' : 'disabled' - updatingStatusAccountIds.value = new Set(updatingStatusAccountIds.value).add(account.id) - try { - await updateAccount({ id: account.id, status: nextStatus }) - await loadAccounts() - toast.success(nextStatus === 'disabled' ? '已禁用调度' : '已启用调度') - } catch (error: any) { - toast.error(error.message || '状态更新失败') - } finally { - const next = new Set(updatingStatusAccountIds.value) - next.delete(account.id) - updatingStatusAccountIds.value = next - } + await updatingStatusAccounts.run(account.id, async () => { + try { + await updateAccount({ id: account.id, status: nextStatus }) + await loadAccounts() + toast.success(nextStatus === 'disabled' ? '已禁用调度' : '已启用调度') + } catch (error: any) { + toast.error(error.message || '状态更新失败') + } + }) } function scheduleActionLabel(account: any) { diff --git a/frontend/src/views/api-keys/composables/useApiKeyMutations.ts b/frontend/src/views/api-keys/composables/useApiKeyMutations.ts index 4d449f962..c213dfaf8 100644 --- a/frontend/src/views/api-keys/composables/useApiKeyMutations.ts +++ b/frontend/src/views/api-keys/composables/useApiKeyMutations.ts @@ -4,6 +4,8 @@ import { onMounted, ref, type Ref } from 'vue' import { createApiKey, deleteApiKeys, getApiKeys, updateApiKey } from '@/api' import { toast } from '@/components/base/BaseToast' +import { useAsyncAction } from '@/composables/useAsyncAction' +import { useIdSet } from '@/composables/useIdSet' export function useApiKeyMutations(selectedIds: Ref>) { const { copy } = useClipboard() @@ -16,11 +18,16 @@ export function useApiKeyMutations(selectedIds: Ref>) { const createdKey = ref('') const editingLabel = ref<{ id: string; value: string } | null>(null) const pendingDeleteKey = ref(null) - const creatingKey = ref(false) - const deletingKey = ref(false) - const batchDeleting = ref(false) - const updatingStatusKeyIds = ref>(new Set()) - const savingLabelKeyIds = ref>(new Set()) + const creatingKeyAction = useAsyncAction() + const deletingKeyAction = useAsyncAction() + const batchDeletingAction = useAsyncAction() + const updatingStatusKeys = useIdSet() + const savingLabelKeys = useIdSet() + const creatingKey = creatingKeyAction.loading + const deletingKey = deletingKeyAction.loading + const batchDeleting = batchDeletingAction.loading + const updatingStatusKeyIds = updatingStatusKeys.ids + const savingLabelKeyIds = savingLabelKeys.ids const createForm = ref({ name: '', @@ -46,25 +53,23 @@ export function useApiKeyMutations(selectedIds: Ref>) { return } - try { - creatingKey.value = true - const result = await createApiKey({ - name: createForm.value.name, - label: createForm.value.label || undefined, - }) + await creatingKeyAction.run( + async () => { + const result = await createApiKey({ + name: createForm.value.name, + label: createForm.value.label || undefined, + }) - createdKey.value = result.key - showCreateModal.value = false - showKeyModal.value = true - createForm.value = { name: '', label: '' } + createdKey.value = result.key + showCreateModal.value = false + showKeyModal.value = true + createForm.value = { name: '', label: '' } - await loadApiKeys() - toast.success('API Key 创建成功') - } catch (error: any) { - toast.error(error.message || '创建失败') - } finally { - creatingKey.value = false - } + await loadApiKeys() + toast.success('API Key 创建成功') + }, + { errorText: '创建失败' }, + ) } function requestDeleteKey(key: any) { @@ -78,70 +83,58 @@ export function useApiKeyMutations(selectedIds: Ref>) { const keyId = pendingDeleteKey.value?.id if (!keyId) return - try { - deletingKey.value = true - await deleteApiKeys({ ids: [keyId] }) - showSingleDeleteModal.value = false - pendingDeleteKey.value = null - await loadApiKeys() - toast.success('删除成功') - } catch (error: any) { - toast.error(error.message || '删除失败') - } finally { - deletingKey.value = false - } + await deletingKeyAction.run( + async () => { + await deleteApiKeys({ ids: [keyId] }) + showSingleDeleteModal.value = false + pendingDeleteKey.value = null + await loadApiKeys() + toast.success('删除成功') + }, + { errorText: '删除失败' }, + ) } async function handleBatchDelete() { if (batchDeleting.value) return if (selectedIds.value.size === 0) return - try { - batchDeleting.value = true - const deleteCount = selectedIds.value.size - await deleteApiKeys({ ids: [...selectedIds.value] }) - selectedIds.value = new Set() - showDeleteModal.value = false - await loadApiKeys() - toast.success(`已删除 ${deleteCount} 个 API Key`) - } catch (error: any) { - toast.error(error.message || '批量删除失败') - } finally { - batchDeleting.value = false - } + await batchDeletingAction.run( + async () => { + const deleteCount = selectedIds.value.size + await deleteApiKeys({ ids: [...selectedIds.value] }) + selectedIds.value = new Set() + showDeleteModal.value = false + await loadApiKeys() + toast.success(`已删除 ${deleteCount} 个 API Key`) + }, + { errorText: '批量删除失败' }, + ) } async function handleToggleStatus(key: any) { - if (updatingStatusKeyIds.value.has(key.id)) return - updatingStatusKeyIds.value = new Set(updatingStatusKeyIds.value).add(key.id) - try { - await updateApiKey({ id: key.id, status: key.enabled ? 'disabled' : 'active' }) - await loadApiKeys() - toast.success(key.enabled ? '已禁用' : '已启用') - } catch (error: any) { - toast.error(error.message || '状态更新失败') - } finally { - const next = new Set(updatingStatusKeyIds.value) - next.delete(key.id) - updatingStatusKeyIds.value = next - } + await updatingStatusKeys.run(key.id, async () => { + try { + await updateApiKey({ id: key.id, status: key.enabled ? 'disabled' : 'active' }) + await loadApiKeys() + toast.success(key.enabled ? '已禁用' : '已启用') + } catch (error: any) { + toast.error(error.message || '状态更新失败') + } + }) } async function handleUpdateLabel(keyId: string, label: string) { - if (savingLabelKeyIds.value.has(keyId)) return - savingLabelKeyIds.value = new Set(savingLabelKeyIds.value).add(keyId) - try { - await updateApiKey({ id: keyId, label: label || null }) - editingLabel.value = null - await loadApiKeys() - toast.success('标签已更新') - } catch (error: any) { - toast.error(error.message || '标签更新失败') - } finally { - const next = new Set(savingLabelKeyIds.value) - next.delete(keyId) - savingLabelKeyIds.value = next - } + await savingLabelKeys.run(keyId, async () => { + try { + await updateApiKey({ id: keyId, label: label || null }) + editingLabel.value = null + await loadApiKeys() + toast.success('标签已更新') + } catch (error: any) { + toast.error(error.message || '标签更新失败') + } + }) } function startEditLabel(key: any) { diff --git a/frontend/src/views/settings/components/AdminApiKeyCard.vue b/frontend/src/views/settings/components/AdminApiKeyCard.vue new file mode 100644 index 000000000..3d3d3b49c --- /dev/null +++ b/frontend/src/views/settings/components/AdminApiKeyCard.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/views/settings/components/ModelAliasesCard.vue b/frontend/src/views/settings/components/ModelAliasesCard.vue new file mode 100644 index 000000000..1363cb5d6 --- /dev/null +++ b/frontend/src/views/settings/components/ModelAliasesCard.vue @@ -0,0 +1,94 @@ + + + diff --git a/frontend/src/views/settings/components/RotationStrategyCard.vue b/frontend/src/views/settings/components/RotationStrategyCard.vue new file mode 100644 index 000000000..f2af38d88 --- /dev/null +++ b/frontend/src/views/settings/components/RotationStrategyCard.vue @@ -0,0 +1,58 @@ + + + diff --git a/frontend/src/views/settings/components/RuntimeSettingsCard.vue b/frontend/src/views/settings/components/RuntimeSettingsCard.vue new file mode 100644 index 000000000..45a2c95bf --- /dev/null +++ b/frontend/src/views/settings/components/RuntimeSettingsCard.vue @@ -0,0 +1,57 @@ + + + diff --git a/frontend/src/views/settings/index.vue b/frontend/src/views/settings/index.vue index 107384875..b64fb6452 100644 --- a/frontend/src/views/settings/index.vue +++ b/frontend/src/views/settings/index.vue @@ -1,8 +1,8 @@