mirror of
https://github.com/EKKOLearnAI/hermes-web-ui.git
synced 2026-07-20 23:40:20 +00:00
[codex] Delete voice provider settings on remove (#1782)
* Delete voice provider settings on remove * fix voice provider delete UI test
This commit is contained in:
@@ -112,6 +112,15 @@ export async function clearSttSecret(
|
||||
return body as SttProviderSettingsResponse
|
||||
}
|
||||
|
||||
export async function deleteSttProvider(
|
||||
provider: StoredSttProvider,
|
||||
): Promise<{ success?: boolean; deleted?: boolean; activeProvider?: SttProvider | null }> {
|
||||
return request<{ success?: boolean; deleted?: boolean; activeProvider?: SttProvider | null }>(
|
||||
`/api/hermes/stt/settings/${provider}`,
|
||||
{ method: 'DELETE' },
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteSttBaseUrlPreset(
|
||||
provider: StoredSttProvider,
|
||||
url: string,
|
||||
|
||||
@@ -107,6 +107,15 @@ export async function clearTtsSecret(
|
||||
return body as TtsProviderSettingsResponse
|
||||
}
|
||||
|
||||
export async function deleteTtsProvider(
|
||||
provider: Exclude<StoredTtsProvider, 'edge'>,
|
||||
): Promise<{ success?: boolean; deleted?: boolean; activeProvider?: StoredTtsProvider | null }> {
|
||||
return request<{ success?: boolean; deleted?: boolean; activeProvider?: StoredTtsProvider | null }>(
|
||||
`/api/hermes/tts/settings/${provider}`,
|
||||
{ method: 'DELETE' },
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteTtsBaseUrlPreset(
|
||||
provider: StoredTtsProvider,
|
||||
url: string,
|
||||
|
||||
@@ -136,7 +136,7 @@ const moreOptions = computed(() => [
|
||||
{
|
||||
label: t('settings.voice.remove'),
|
||||
key: 'remove',
|
||||
disabled: !props.connection.hasSecret,
|
||||
disabled: props.connection.isBuiltin,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -149,7 +149,7 @@ function handleEditAction() {
|
||||
}
|
||||
|
||||
function handleMoreSelect(key: string | number) {
|
||||
if (key === 'remove' && props.connection.hasSecret) {
|
||||
if (key === 'remove' && !props.connection.isBuiltin) {
|
||||
emit('remove', props.connection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
clearTtsSecret,
|
||||
deleteTtsProvider,
|
||||
fetchTtsSettings,
|
||||
saveActiveTtsProvider,
|
||||
saveTtsSettings,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type TtsStoredSettings,
|
||||
} from '@/api/hermes/tts-settings'
|
||||
import {
|
||||
clearSttSecret,
|
||||
deleteSttProvider,
|
||||
fetchSttSettings,
|
||||
saveActiveSttProvider,
|
||||
saveSttSettings,
|
||||
@@ -276,11 +276,11 @@ export function useVoiceApiConnections() {
|
||||
|
||||
async function deleteSecret(kind: VoiceApiKind, provider: VoiceApiProvider) {
|
||||
if (kind === 'tts') {
|
||||
if (!isStoredTtsProvider(provider)) return
|
||||
await clearTtsSecret(provider, 'apiKey')
|
||||
if (!isStoredTtsProvider(provider) || provider === 'edge') return
|
||||
await deleteTtsProvider(provider)
|
||||
} else {
|
||||
if (!isStoredSttProvider(provider)) return
|
||||
await clearSttSecret(provider, 'apiKey')
|
||||
await deleteSttProvider(provider)
|
||||
}
|
||||
await refresh()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
assertActiveSttProvider,
|
||||
assertStoredSttProvider,
|
||||
clearStoredSttSecret,
|
||||
deleteSttProviderSetting,
|
||||
getActiveSttProvider,
|
||||
getSttProviderSetting,
|
||||
isStoredSttProvider,
|
||||
@@ -398,6 +399,27 @@ export async function deleteSecret(ctx: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteProvider(ctx: Context) {
|
||||
const userId = authUserId(ctx)
|
||||
if (!userId) return
|
||||
|
||||
const provider = ctx.params.provider || ''
|
||||
|
||||
try {
|
||||
const profile = requestedProfile(ctx)
|
||||
const storedProvider = assertStoredSttProvider(provider)
|
||||
const deleted = deleteSttProviderSetting(profile, storedProvider)
|
||||
const currentActiveProvider = getActiveSttProvider(profile)
|
||||
const activeProvider = currentActiveProvider === storedProvider
|
||||
? saveActiveSttProvider(profile, 'browser')
|
||||
: currentActiveProvider
|
||||
ctx.body = { success: true, deleted, activeProvider }
|
||||
} catch (error) {
|
||||
if (handleSettingsError(ctx, error)) return
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveActiveProvider(ctx: Context) {
|
||||
const userId = authUserId(ctx)
|
||||
if (!userId) return
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
assertActiveTtsProvider,
|
||||
assertStoredTtsProvider,
|
||||
clearStoredTtsSecret,
|
||||
deleteTtsProviderSetting,
|
||||
getActiveTtsProvider,
|
||||
getTtsProviderSetting,
|
||||
isStoredTtsProvider,
|
||||
@@ -194,6 +195,32 @@ export async function deleteSecret(ctx: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteProvider(ctx: Context) {
|
||||
const userId = authUserId(ctx)
|
||||
if (!userId) return
|
||||
|
||||
const provider = ctx.params.provider || ''
|
||||
|
||||
try {
|
||||
const profile = requestedProfile(ctx)
|
||||
const storedProvider = assertStoredTtsProvider(provider)
|
||||
if (storedProvider === 'edge') {
|
||||
ctx.status = 400
|
||||
ctx.body = { error: 'built-in TTS provider cannot be deleted' }
|
||||
return
|
||||
}
|
||||
const deleted = deleteTtsProviderSetting(profile, storedProvider)
|
||||
const currentActiveProvider = getActiveTtsProvider(profile)
|
||||
const activeProvider = currentActiveProvider === storedProvider
|
||||
? saveActiveTtsProvider(profile, 'edge')
|
||||
: currentActiveProvider
|
||||
ctx.body = { success: true, deleted, activeProvider }
|
||||
} catch (error) {
|
||||
if (handleSettingsError(ctx, error)) return
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
type ProbeKind = 'tts' | 'stt'
|
||||
type ProbeCompatibility = 'openai-compatible' | 'manual'
|
||||
|
||||
|
||||
@@ -403,3 +403,13 @@ export function clearStoredSttSecret(
|
||||
|
||||
return getSttProviderSetting(profileName, storedProvider)
|
||||
}
|
||||
|
||||
export function deleteSttProviderSetting(profile: string, provider: StoredSttProvider): boolean {
|
||||
const profileName = normalizeProfile(profile)
|
||||
const storedProvider = assertStoredSttProvider(provider)
|
||||
const db = requireDb()
|
||||
const result = db.prepare(
|
||||
`DELETE FROM ${STT_PROFILE_PROVIDER_SETTINGS_TABLE} WHERE profile = ? AND provider = ?`
|
||||
).run(profileName, storedProvider)
|
||||
return result.changes > 0
|
||||
}
|
||||
|
||||
@@ -398,3 +398,13 @@ export function clearStoredTtsSecret(
|
||||
|
||||
return getTtsProviderSetting(profileName, storedProvider)
|
||||
}
|
||||
|
||||
export function deleteTtsProviderSetting(profile: string, provider: StoredTtsProvider): boolean {
|
||||
const profileName = normalizeProfile(profile)
|
||||
const storedProvider = assertStoredTtsProvider(provider)
|
||||
const db = requireDb()
|
||||
const result = db.prepare(
|
||||
`DELETE FROM ${TTS_PROFILE_PROVIDER_SETTINGS_TABLE} WHERE profile = ? AND provider = ?`
|
||||
).run(profileName, storedProvider)
|
||||
return result.changes > 0
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ sttProtectedRoutes.get('/api/hermes/stt/profile-status/missing-audio', ctrl.miss
|
||||
sttProtectedRoutes.post('/api/hermes/mcu/voice-turn', ctrl.mcuVoiceTurn)
|
||||
sttProtectedRoutes.put('/api/hermes/stt/settings/active', ctrl.saveActiveProvider)
|
||||
sttProtectedRoutes.put('/api/hermes/stt/settings/:provider', ctrl.saveSettings)
|
||||
sttProtectedRoutes.delete('/api/hermes/stt/settings/:provider', ctrl.deleteProvider)
|
||||
sttProtectedRoutes.delete('/api/hermes/stt/settings/:provider/base-url-preset', ctrl.deleteBaseUrlPreset)
|
||||
sttProtectedRoutes.delete('/api/hermes/stt/settings/:provider/secret/:secretName', ctrl.deleteSecret)
|
||||
sttProtectedRoutes.post('/api/hermes/stt/transcribe', ctrl.transcribe)
|
||||
|
||||
@@ -11,6 +11,7 @@ ttsRoutes.get('/api/hermes/mcu/audio/:file', ctrl.mcuAudio)
|
||||
ttsProtectedRoutes.get('/api/hermes/tts/settings', ctrl.listSettings)
|
||||
ttsProtectedRoutes.put('/api/hermes/tts/settings/active', ctrl.saveActiveProvider)
|
||||
ttsProtectedRoutes.put('/api/hermes/tts/settings/:provider', ctrl.saveSettings)
|
||||
ttsProtectedRoutes.delete('/api/hermes/tts/settings/:provider', ctrl.deleteProvider)
|
||||
ttsProtectedRoutes.delete('/api/hermes/tts/settings/:provider/base-url-preset', ctrl.deleteBaseUrlPreset)
|
||||
ttsProtectedRoutes.delete('/api/hermes/tts/settings/:provider/secret/:secretName', ctrl.deleteSecret)
|
||||
ttsProtectedRoutes.post('/api/voice/providers/probe', ctrl.probeProvider)
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('@/router', () => ({
|
||||
|
||||
import router from '@/router'
|
||||
import { hasApiKey } from '../../packages/client/src/api/client'
|
||||
import { clearSttSecret, deleteSttBaseUrlPreset, fetchSttSettings, saveActiveSttProvider, saveSttSettings } from '../../packages/client/src/api/hermes/stt-settings'
|
||||
import { clearSttSecret, deleteSttBaseUrlPreset, deleteSttProvider, fetchSttSettings, saveActiveSttProvider, saveSttSettings } from '../../packages/client/src/api/hermes/stt-settings'
|
||||
import { transcribeSpeech } from '../../packages/client/src/api/hermes/stt'
|
||||
|
||||
const mockFetch = vi.fn()
|
||||
@@ -199,6 +199,34 @@ describe('stt api wrappers', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('deletes stored provider settings via the provider endpoint', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
success: true,
|
||||
deleted: true,
|
||||
activeProvider: 'browser',
|
||||
}),
|
||||
})
|
||||
|
||||
const result = await deleteSttProvider('openai')
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://hermes.example/api/hermes/stt/settings/openai',
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
headers: expect.objectContaining({
|
||||
Authorization: 'Bearer jwt-token',
|
||||
}),
|
||||
}),
|
||||
)
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
deleted: true,
|
||||
activeProvider: 'browser',
|
||||
})
|
||||
})
|
||||
|
||||
it('deletes saved base URL presets via the provider preset endpoint', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
|
||||
@@ -9,11 +9,13 @@ const mockFetchSttSettings = vi.fn()
|
||||
const mockSaveSttSettings = vi.fn()
|
||||
const mockSaveActiveSttProvider = vi.fn()
|
||||
const mockClearSttSecret = vi.fn()
|
||||
const mockDeleteSttProvider = vi.fn()
|
||||
const mockDeleteSttBaseUrlPreset = vi.fn()
|
||||
const mockTranscribeSpeech = vi.fn()
|
||||
const mockFetchTtsSettings = vi.fn()
|
||||
const mockSaveTtsSettings = vi.fn()
|
||||
const mockClearTtsSecret = vi.fn()
|
||||
const mockDeleteTtsProvider = vi.fn()
|
||||
const mockDeleteTtsBaseUrlPreset = vi.fn()
|
||||
const mockFetchProviderModels = vi.fn()
|
||||
const mockProbeVoiceProvider = vi.fn()
|
||||
@@ -371,6 +373,7 @@ vi.mock('@/api/hermes/stt-settings', () => ({
|
||||
saveSttSettings: mockSaveSttSettings,
|
||||
saveActiveSttProvider: mockSaveActiveSttProvider,
|
||||
clearSttSecret: mockClearSttSecret,
|
||||
deleteSttProvider: mockDeleteSttProvider,
|
||||
deleteSttBaseUrlPreset: mockDeleteSttBaseUrlPreset,
|
||||
}))
|
||||
|
||||
@@ -392,6 +395,7 @@ vi.mock('@/api/hermes/tts-settings', () => ({
|
||||
fetchTtsSettings: mockFetchTtsSettings,
|
||||
saveTtsSettings: mockSaveTtsSettings,
|
||||
clearTtsSecret: mockClearTtsSecret,
|
||||
deleteTtsProvider: mockDeleteTtsProvider,
|
||||
deleteTtsBaseUrlPreset: mockDeleteTtsBaseUrlPreset,
|
||||
}))
|
||||
|
||||
@@ -512,6 +516,7 @@ describe('useSttSettings', () => {
|
||||
mockSaveSttSettings.mockReset()
|
||||
mockSaveActiveSttProvider.mockReset()
|
||||
mockClearSttSecret.mockReset()
|
||||
mockDeleteSttProvider.mockReset()
|
||||
mockDeleteSttBaseUrlPreset.mockReset()
|
||||
mockTranscribeSpeech.mockReset()
|
||||
mockMicStart.mockReset()
|
||||
@@ -520,6 +525,7 @@ describe('useSttSettings', () => {
|
||||
mockFetchTtsSettings.mockReset()
|
||||
mockSaveTtsSettings.mockReset()
|
||||
mockClearTtsSecret.mockReset()
|
||||
mockDeleteTtsProvider.mockReset()
|
||||
mockDeleteTtsBaseUrlPreset.mockReset()
|
||||
mockFetchProviderModels.mockReset()
|
||||
mockProbeVoiceProvider.mockReset()
|
||||
@@ -719,6 +725,7 @@ describe('VoiceSettings STT UI', () => {
|
||||
mockSaveSttSettings.mockReset()
|
||||
mockSaveActiveSttProvider.mockReset()
|
||||
mockClearSttSecret.mockReset()
|
||||
mockDeleteSttProvider.mockReset()
|
||||
mockDeleteSttBaseUrlPreset.mockReset()
|
||||
mockTranscribeSpeech.mockReset()
|
||||
mockMicStart.mockReset()
|
||||
@@ -727,6 +734,7 @@ describe('VoiceSettings STT UI', () => {
|
||||
mockFetchTtsSettings.mockReset()
|
||||
mockSaveTtsSettings.mockReset()
|
||||
mockClearTtsSecret.mockReset()
|
||||
mockDeleteTtsProvider.mockReset()
|
||||
mockDeleteTtsBaseUrlPreset.mockReset()
|
||||
mockFetchProviderModels.mockReset()
|
||||
mockSpeechStop.mockReset()
|
||||
@@ -966,12 +974,7 @@ describe('VoiceSettings STT UI', () => {
|
||||
}],
|
||||
})
|
||||
.mockResolvedValue({ providers: [] })
|
||||
mockClearTtsSecret.mockResolvedValue({
|
||||
provider: 'mimo',
|
||||
settings: {},
|
||||
secrets: {},
|
||||
updatedAt: 6,
|
||||
})
|
||||
mockDeleteTtsProvider.mockResolvedValue({ success: true, deleted: true, activeProvider: null })
|
||||
|
||||
const wrapper = await mountComponent()
|
||||
await flushPromises()
|
||||
@@ -980,7 +983,7 @@ describe('VoiceSettings STT UI', () => {
|
||||
await wrapper.get('[data-testid="dropdown-option-remove"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(mockClearTtsSecret).toHaveBeenCalledWith('mimo', 'apiKey')
|
||||
expect(mockDeleteTtsProvider).toHaveBeenCalledWith('mimo')
|
||||
expect(wrapper.text()).not.toContain('raw-secret')
|
||||
})
|
||||
|
||||
|
||||
@@ -132,6 +132,32 @@ describe('stt settings controller', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('deletes a stored STT provider row and falls back to browser when it was active', async () => {
|
||||
const ctrl = await initController()
|
||||
const user = { id: 7, username: 'bob', role: 'admin' }
|
||||
|
||||
await ctrl.saveSettings(makeCtx(user, {
|
||||
settings: { model: 'gpt-4o-transcribe' },
|
||||
secrets: { apiKey: 'server-secret' },
|
||||
}, { provider: 'openai' }))
|
||||
|
||||
const deleteCtx = makeCtx(user, {}, { provider: 'openai' })
|
||||
await ctrl.deleteProvider(deleteCtx)
|
||||
|
||||
expect(deleteCtx.status).toBe(200)
|
||||
expect(deleteCtx.body).toEqual({
|
||||
success: true,
|
||||
deleted: true,
|
||||
activeProvider: 'browser',
|
||||
})
|
||||
expect(db.prepare(
|
||||
'SELECT COUNT(*) AS count FROM stt_profile_provider_settings WHERE profile = ? AND provider = ?'
|
||||
).get('default', 'openai').count).toBe(0)
|
||||
|
||||
const activeRow = db.prepare('SELECT active_provider FROM stt_profile_settings WHERE profile = ?').get('default') as { active_provider: string }
|
||||
expect(activeRow.active_provider).toBe('browser')
|
||||
})
|
||||
|
||||
it('deletes saved custom base URL presets without deleting the current setting or secret', async () => {
|
||||
const ctrl = await initController()
|
||||
const user = { id: 13, username: 'dana', role: 'admin' }
|
||||
@@ -263,6 +289,7 @@ describe('stt routes', () => {
|
||||
const listSettings = vi.fn(async (ctx: any) => { ctx.body = { route: 'listSettings' } })
|
||||
const saveActiveProvider = vi.fn(async (ctx: any) => { ctx.body = { route: 'saveActiveProvider' } })
|
||||
const saveSettings = vi.fn(async (ctx: any) => { ctx.body = { route: 'saveSettings' } })
|
||||
const deleteProvider = vi.fn(async (ctx: any) => { ctx.body = { route: 'deleteProvider' } })
|
||||
const deleteSecret = vi.fn(async (ctx: any) => { ctx.body = { route: 'deleteSecret' } })
|
||||
const deleteBaseUrlPreset = vi.fn(async (ctx: any) => { ctx.body = { route: 'deleteBaseUrlPreset' } })
|
||||
const profileStatus = vi.fn(async (ctx: any) => { ctx.body = { route: 'profileStatus' } })
|
||||
@@ -274,6 +301,7 @@ describe('stt routes', () => {
|
||||
listSettings,
|
||||
saveActiveProvider,
|
||||
saveSettings,
|
||||
deleteProvider,
|
||||
deleteSecret,
|
||||
deleteBaseUrlPreset,
|
||||
profileStatus,
|
||||
@@ -292,6 +320,7 @@ describe('stt routes', () => {
|
||||
'/api/hermes/mcu/voice-turn',
|
||||
'/api/hermes/stt/settings/active',
|
||||
'/api/hermes/stt/settings/:provider',
|
||||
'/api/hermes/stt/settings/:provider',
|
||||
'/api/hermes/stt/settings/:provider/base-url-preset',
|
||||
'/api/hermes/stt/settings/:provider/secret/:secretName',
|
||||
'/api/hermes/stt/transcribe',
|
||||
|
||||
@@ -158,6 +158,80 @@ describe('tts synthesize controller', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('deletes a stored TTS provider row and falls back to Edge when it was active', async () => {
|
||||
const { DatabaseSync } = await import('node:sqlite')
|
||||
const db = new DatabaseSync(':memory:')
|
||||
vi.doMock('../../packages/server/src/db/index', () => ({
|
||||
getDb: () => db,
|
||||
getStoragePath: () => ':memory:',
|
||||
}))
|
||||
|
||||
try {
|
||||
const schemas = await import('../../packages/server/src/db/hermes/schemas')
|
||||
schemas.initAllHermesTables()
|
||||
const ctrl = await import('../../packages/server/src/controllers/hermes/tts')
|
||||
const { ctx: saveCtx } = createMockCtx({
|
||||
settings: { model: 'tts-1', voice: 'alloy' },
|
||||
secrets: { apiKey: 'server-secret' },
|
||||
})
|
||||
saveCtx.state = { user: { id: 7 } }
|
||||
saveCtx.params = { provider: 'openai' }
|
||||
saveCtx.query = {}
|
||||
saveCtx.get = vi.fn(() => '')
|
||||
await ctrl.saveSettings(saveCtx)
|
||||
|
||||
const { ctx: deleteCtx } = createMockCtx()
|
||||
deleteCtx.state = { user: { id: 7 } }
|
||||
deleteCtx.params = { provider: 'openai' }
|
||||
deleteCtx.query = {}
|
||||
deleteCtx.get = vi.fn(() => '')
|
||||
await ctrl.deleteProvider(deleteCtx)
|
||||
|
||||
expect(deleteCtx.status).toBe(200)
|
||||
expect(deleteCtx.body).toEqual({
|
||||
success: true,
|
||||
deleted: true,
|
||||
activeProvider: 'edge',
|
||||
})
|
||||
expect(db.prepare(
|
||||
'SELECT COUNT(*) AS count FROM tts_profile_provider_settings WHERE profile = ? AND provider = ?'
|
||||
).get('default', 'openai').count).toBe(0)
|
||||
const activeRow = db.prepare('SELECT active_provider FROM tts_profile_settings WHERE profile = ?').get('default') as { active_provider: string }
|
||||
expect(activeRow.active_provider).toBe('edge')
|
||||
} finally {
|
||||
db.close()
|
||||
vi.doUnmock('../../packages/server/src/db/index')
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects deleting the built-in Edge TTS provider', async () => {
|
||||
const { DatabaseSync } = await import('node:sqlite')
|
||||
const db = new DatabaseSync(':memory:')
|
||||
vi.doMock('../../packages/server/src/db/index', () => ({
|
||||
getDb: () => db,
|
||||
getStoragePath: () => ':memory:',
|
||||
}))
|
||||
|
||||
try {
|
||||
const schemas = await import('../../packages/server/src/db/hermes/schemas')
|
||||
schemas.initAllHermesTables()
|
||||
const ctrl = await import('../../packages/server/src/controllers/hermes/tts')
|
||||
const { ctx } = createMockCtx()
|
||||
ctx.state = { user: { id: 7 } }
|
||||
ctx.params = { provider: 'edge' }
|
||||
ctx.query = {}
|
||||
ctx.get = vi.fn(() => '')
|
||||
|
||||
await ctrl.deleteProvider(ctx)
|
||||
|
||||
expect(ctx.status).toBe(400)
|
||||
expect(ctx.body).toEqual({ error: 'built-in TTS provider cannot be deleted' })
|
||||
} finally {
|
||||
db.close()
|
||||
vi.doUnmock('../../packages/server/src/db/index')
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves numeric Edge TTS rate and pitch settings on save', async () => {
|
||||
const { DatabaseSync } = await import('node:sqlite')
|
||||
const db = new DatabaseSync(':memory:')
|
||||
@@ -804,6 +878,7 @@ describe('tts routes', () => {
|
||||
const listSettings = vi.fn(async (ctx: any) => { ctx.body = { route: 'listSettings' } })
|
||||
const saveSettings = vi.fn(async (ctx: any) => { ctx.body = { route: 'saveSettings' } })
|
||||
const saveActiveProvider = vi.fn(async (ctx: any) => { ctx.body = { route: 'saveActiveProvider' } })
|
||||
const deleteProvider = vi.fn(async (ctx: any) => { ctx.body = { route: 'deleteProvider' } })
|
||||
const deleteBaseUrlPreset = vi.fn(async (ctx: any) => { ctx.body = { route: 'deleteBaseUrlPreset' } })
|
||||
const deleteSecret = vi.fn(async (ctx: any) => { ctx.body = { route: 'deleteSecret' } })
|
||||
const probeProvider = vi.fn(async (ctx: any) => { ctx.body = { route: 'probeProvider' } })
|
||||
@@ -817,6 +892,7 @@ describe('tts routes', () => {
|
||||
listSettings,
|
||||
saveSettings,
|
||||
saveActiveProvider,
|
||||
deleteProvider,
|
||||
deleteBaseUrlPreset,
|
||||
deleteSecret,
|
||||
probeProvider,
|
||||
@@ -836,6 +912,7 @@ describe('tts routes', () => {
|
||||
'/api/hermes/tts/settings',
|
||||
'/api/hermes/tts/settings/active',
|
||||
'/api/hermes/tts/settings/:provider',
|
||||
'/api/hermes/tts/settings/:provider',
|
||||
'/api/hermes/tts/settings/:provider/base-url-preset',
|
||||
'/api/hermes/tts/settings/:provider/secret/:secretName',
|
||||
'/api/voice/providers/probe',
|
||||
|
||||
Reference in New Issue
Block a user