feat: more clear error message for baseUrl (#479)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export function isUrl(url: string) {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url)
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user