feat(settings): add AcoustID API key setting

This commit is contained in:
jxxghp
2026-08-12 08:55:42 +08:00
parent 5e58d904fe
commit 6768f4e8a7
5 changed files with 158 additions and 0 deletions

View File

@@ -2142,6 +2142,10 @@ export default {
tmdbApiKeyPlaceholder: 'Please enter TMDB API Key',
tmdbApiKeyHint: 'Set TheMovieDb API Key',
tmdbApiKeyRequired: 'Please enter TMDB API Key',
acoustIdApiKey: 'AcoustID API Key',
acoustIdApiKeyPlaceholder: 'Please enter AcoustID API Key',
acoustIdApiKeyHint: 'Used to identify MusicBrainz recording IDs from audio fingerprints',
acoustIdApiKeyRequired: 'Please enter AcoustID API Key',
tmdbImageDomain: 'TMDB Image Service Address',
tmdbImageDomainPlaceholder: 'image.tmdb.org',
tmdbImageDomainHint: 'Customize TheMovieDb image service domain or proxy address',

View File

@@ -2117,6 +2117,10 @@ export default {
tmdbApiKeyPlaceholder: '请输入 TMDB API Key',
tmdbApiKeyHint: '设置 TheMovieDb API Key',
tmdbApiKeyRequired: '请输入TMDB API Key',
acoustIdApiKey: 'AcoustID API Key',
acoustIdApiKeyPlaceholder: '请输入 AcoustID API Key',
acoustIdApiKeyHint: '用于通过音频指纹识别 MusicBrainz 录音 ID',
acoustIdApiKeyRequired: '请输入 AcoustID API Key',
tmdbImageDomain: 'TMDB 图片服务地址',
tmdbImageDomainPlaceholder: 'image.tmdb.org',
tmdbImageDomainHint: '自定义 TheMovieDb 图片服务域名或代理地址',

View File

@@ -2116,6 +2116,10 @@ export default {
tmdbApiKeyPlaceholder: '請輸入 TMDB API Key',
tmdbApiKeyHint: '設定 TheMovieDb API Key',
tmdbApiKeyRequired: '請輸入TMDB API Key',
acoustIdApiKey: 'AcoustID API Key',
acoustIdApiKeyPlaceholder: '請輸入 AcoustID API Key',
acoustIdApiKeyHint: '用於通過音訊指紋識別 MusicBrainz 錄音 ID',
acoustIdApiKeyRequired: '請輸入 AcoustID API Key',
tmdbImageDomain: 'TMDB 圖片服務地址',
tmdbImageDomainPlaceholder: 'image.tmdb.org',
tmdbImageDomainHint: '自定義 TheMovieDb 圖片服務域名或代理地址',

View File

@@ -104,6 +104,7 @@ const SystemSettings = ref<any>({
MEDIA_RECOGNIZE_SHARE: true,
TMDB_API_DOMAIN: null,
TMDB_API_KEY: null,
ACOUSTID_API_KEY: null,
TMDB_IMAGE_DOMAIN: null,
MUSIC_COVER_PROXY: null,
TMDB_LOCALE: null,
@@ -2045,6 +2046,17 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => {
prepend-inner-icon="mdi-key-variant"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Advanced.ACOUSTID_API_KEY"
:label="t('setting.system.acoustIdApiKey')"
:hint="t('setting.system.acoustIdApiKeyHint')"
persistent-hint
:placeholder="t('setting.system.acoustIdApiKeyPlaceholder')"
:rules="[(v: string) => !!v || t('setting.system.acoustIdApiKeyRequired')]"
prepend-inner-icon="mdi-music-box-multiple-outline"
/>
</VCol>
<VCol cols="12" md="6">
<VCombobox
v-model="SystemSettings.Advanced.TMDB_IMAGE_DOMAIN"

View File

@@ -0,0 +1,134 @@
import AccountSettingSystem from '@/views/setting/AccountSettingSystem.vue'
import { fireEvent, screen, waitFor, within } from '@testing-library/vue'
import { renderWithProviders } from '@tests/support/render'
import { ref } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
apiGet: vi.fn(),
apiPost: vi.fn(),
toastError: vi.fn(),
toastInfo: vi.fn(),
toastSuccess: vi.fn(),
useLlmProviderDirectory: vi.fn(),
useSilentSettingRefresh: vi.fn(),
}))
vi.mock('colorthief', () => ({
default: class ColorThief {
/** 返回稳定测试色,避免设置页子组件加载原生图像依赖。 */
getColor() {
return [40, 169, 225]
}
},
}))
vi.mock('@/api', () => ({
default: {
get: mocks.apiGet,
post: mocks.apiPost,
},
}))
vi.mock('vue-toastification', () => ({
useToast: () => ({
error: mocks.toastError,
info: mocks.toastInfo,
success: mocks.toastSuccess,
}),
}))
vi.mock('@/composables/useLlmProviderDirectory', () => ({
useLlmProviderDirectory: mocks.useLlmProviderDirectory,
}))
vi.mock('@/composables/useSilentSettingRefresh', () => ({
useSilentSettingRefresh: mocks.useSilentSettingRefresh,
}))
/** 构造系统设置页所需的最小 LLM 目录状态。 */
function createLlmDirectoryState() {
return {
providerItems: ref([]),
baseUrlPresetItems: ref([]),
models: ref([]),
selectedProvider: ref(null),
selectedModel: ref(null),
loadingProviders: ref(false),
loadingModels: ref(false),
providerConnected: ref(false),
showBaseUrlField: ref(false),
showApiKeyField: ref(false),
showApiProtocolField: ref(false),
supportsBuiltinWebSearch: ref(false),
canRefreshModels: ref(false),
setBaseUrlPreset: vi.fn(),
authDialogVisible: ref(false),
authPolling: ref(false),
authPopupBlocked: ref(false),
authSession: ref(null),
handleProviderSelection: vi.fn(),
applyModelMetadata: vi.fn(),
loadProviders: vi.fn().mockResolvedValue(undefined),
loadModels: vi.fn(),
openAuthPage: vi.fn(),
startAuth: vi.fn(),
pollAuthSession: vi.fn(),
disconnectAuth: vi.fn(),
closeAuthDialog: vi.fn(),
}
}
describe('AccountSettingSystem', () => {
beforeEach(() => {
vi.spyOn(console, 'log').mockImplementation(() => {})
mocks.apiGet.mockReset()
mocks.apiPost.mockReset()
mocks.toastError.mockReset()
mocks.toastInfo.mockReset()
mocks.toastSuccess.mockReset()
mocks.useLlmProviderDirectory.mockReset()
mocks.useSilentSettingRefresh.mockReset()
mocks.useLlmProviderDirectory.mockReturnValue(createLlmDirectoryState())
mocks.apiPost.mockResolvedValue({ success: true })
mocks.apiGet.mockImplementation((endpoint: string) => {
if (endpoint === 'system/env') {
return {
success: true,
data: {
ACOUSTID_API_KEY: 'b1auxfOzAg',
DB_TYPE: 'sqlite',
RUST_ACCEL_AVAILABLE: false,
},
}
}
if (endpoint === 'message/agent/mcp/servers') {
return { success: true, data: { servers: [] } }
}
return { success: true, data: { value: [] } }
})
})
it('loads and saves the AcoustID key from advanced media settings', async () => {
await renderWithProviders(AccountSettingSystem, {
global: { stubs: { VDialogCloseBtn: true } },
})
await waitFor(() => expect(mocks.apiGet).toHaveBeenCalledWith('system/env'))
await fireEvent.click(screen.getByRole('button', { name: /高级设置/ }))
await fireEvent.click(await screen.findByRole('tab', { name: '媒体' }))
const dialog = within(screen.getByRole('dialog'))
const input = await dialog.findByLabelText('AcoustID API Key')
expect(input).toHaveValue('b1auxfOzAg')
await fireEvent.update(input, 'custom-acoustid-key')
await fireEvent.click(dialog.getByRole('button', { name: '保存' }))
await waitFor(() => {
expect(mocks.apiPost).toHaveBeenCalledWith(
'system/env',
expect.objectContaining({ ACOUSTID_API_KEY: 'custom-acoustid-key' }),
)
})
})
})