feat: more clear error message for baseUrl (#479)

This commit is contained in:
MisakaKumomi 御坂云见
2025-08-26 23:03:24 +08:00
committed by GitHub
parent c7bf3698c4
commit b169cd999d
3 changed files with 106 additions and 62 deletions
+96 -51
View File
@@ -48,7 +48,7 @@ import {
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { isAbsoluteUrl } from '../utils/string'
import { isUrl } from '../utils/url'
import { models as elevenLabsModels } from './providers/elevenlabs/list-models'
export interface ProviderMetadata {
@@ -160,11 +160,29 @@ export interface VoiceInfo {
export const useProvidersStore = defineStore('providers', () => {
const providerCredentials = useLocalStorage<Record<string, Record<string, unknown>>>('settings/credentials/providers', {})
const { t } = useI18n()
const notBaseUrlError = computed(() => ({
errors: [new Error('Base URL is not absolute')],
reason: 'Base URL is not absolute. Check your input.',
valid: false,
}))
const baseUrlValidator = computed(() => (baseUrl: unknown) => {
let msg = ''
if (!baseUrl) {
msg = 'Base URL is required.'
}
else if (typeof baseUrl !== 'string') {
msg = 'Base URL must be a string.'
}
else if (!isUrl(baseUrl) || new URL(baseUrl).host.length === 0) {
msg = 'Base URL is not absolute. Try to include a scheme (http:// or https://).'
}
else if (!baseUrl.endsWith('/')) {
msg = 'Base URL must end with a trailing slash (/).'
}
if (msg) {
return {
errors: [new Error(msg)],
reason: msg,
valid: false,
}
}
return null
})
// Helper function to fetch OpenRouter models manually
async function fetchOpenRouterModels(config: Record<string, unknown>): Promise<ModelInfo[]> {
@@ -223,8 +241,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -504,8 +523,9 @@ export const useProvidersStore = defineStore('providers', () => {
}
}
if (!isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
// Check if the Ollama server is reachable
@@ -570,8 +590,9 @@ export const useProvidersStore = defineStore('providers', () => {
}
}
if (!isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
// Check if the Ollama server is reachable
@@ -665,8 +686,9 @@ export const useProvidersStore = defineStore('providers', () => {
}
}
if (!isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
// Check if the vLLM is reachable
@@ -742,6 +764,11 @@ export const useProvidersStore = defineStore('providers', () => {
}
}
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
// Check if the LM Studio server is reachable
return fetch(`${(config.baseUrl as string).trim()}models`, { headers: (config.headers as HeadersInit) || undefined })
.then((response) => {
@@ -800,8 +827,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required. Default to https://api.openai.com/v1/ for official OpenAI API.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -848,8 +876,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -965,8 +994,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required. Default to https://api.openai.com/v1/ for official OpenAI API.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1016,8 +1046,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1063,8 +1094,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required. Default to https://api.openai.com/v1/ for official OpenAI API.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1111,8 +1143,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1246,8 +1279,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required. Default to https://api.anthropic.com/v1/ for official Claude API with OpenAI compatibility.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1294,8 +1328,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required. Default to https://generativelanguage.googleapis.com/v1beta/openai/ for official Google Gemini API with OpenAI compatibility.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1383,8 +1418,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1467,8 +1503,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1531,8 +1568,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1592,8 +1630,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required. Default to http://localhost:11996/tts for Index-TTS.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1664,8 +1703,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1729,8 +1769,9 @@ export const useProvidersStore = defineStore('providers', () => {
!((config.app as any)?.appId) && new Error('App ID is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -1900,8 +1941,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -2003,8 +2045,9 @@ export const useProvidersStore = defineStore('providers', () => {
!config.baseUrl && new Error('Base URL is required.'),
].filter(Boolean)
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
@@ -2131,12 +2174,13 @@ export const useProvidersStore = defineStore('providers', () => {
}
}
if (!isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
// Check if the local running Player 2 is reachable
return await fetch(`${(config.baseUrl as string).endsWith('/') ? (config.baseUrl as string).slice(0, -1) : config.baseUrl}/health`, {
return await fetch(`${config.baseUrl}health`, {
method: 'GET',
headers: {
'player2-game-key': 'airi',
@@ -2241,8 +2285,9 @@ export const useProvidersStore = defineStore('providers', () => {
}
}
if (!isAbsoluteUrl(config.baseUrl as string)) {
return notBaseUrlError.value
const res = baseUrlValidator.value(config.baseUrl)
if (res) {
return res
}
return {
-11
View File
@@ -1,11 +0,0 @@
export function isAbsoluteUrl(url: string) {
try {
// This could be the most reliable way to check so using side-effect is acceptable
// eslint-disable-next-line no-new
new URL(url)
return true
}
catch {
return false
}
}
+10
View File
@@ -0,0 +1,10 @@
export function isUrl(url: string) {
try {
// eslint-disable-next-line no-new
new URL(url)
return true
}
catch {
return false
}
}