Skip to content

Commit b169cd9

Browse files
feat: more clear error message for baseUrl (#479)
1 parent c7bf369 commit b169cd9

File tree

3 files changed

+106
-62
lines changed

3 files changed

+106
-62
lines changed

packages/stage-ui/src/stores/providers.ts

Lines changed: 96 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {
4848
import { computed, ref, watch } from 'vue'
4949
import { useI18n } from 'vue-i18n'
5050

51-
import { isAbsoluteUrl } from '../utils/string'
51+
import { isUrl } from '../utils/url'
5252
import { models as elevenLabsModels } from './providers/elevenlabs/list-models'
5353

5454
export interface ProviderMetadata {
@@ -160,11 +160,29 @@ export interface VoiceInfo {
160160
export const useProvidersStore = defineStore('providers', () => {
161161
const providerCredentials = useLocalStorage<Record<string, Record<string, unknown>>>('settings/credentials/providers', {})
162162
const { t } = useI18n()
163-
const notBaseUrlError = computed(() => ({
164-
errors: [new Error('Base URL is not absolute')],
165-
reason: 'Base URL is not absolute. Check your input.',
166-
valid: false,
167-
}))
163+
const baseUrlValidator = computed(() => (baseUrl: unknown) => {
164+
let msg = ''
165+
if (!baseUrl) {
166+
msg = 'Base URL is required.'
167+
}
168+
else if (typeof baseUrl !== 'string') {
169+
msg = 'Base URL must be a string.'
170+
}
171+
else if (!isUrl(baseUrl) || new URL(baseUrl).host.length === 0) {
172+
msg = 'Base URL is not absolute. Try to include a scheme (http:// or https://).'
173+
}
174+
else if (!baseUrl.endsWith('/')) {
175+
msg = 'Base URL must end with a trailing slash (/).'
176+
}
177+
if (msg) {
178+
return {
179+
errors: [new Error(msg)],
180+
reason: msg,
181+
valid: false,
182+
}
183+
}
184+
return null
185+
})
168186

169187
// Helper function to fetch OpenRouter models manually
170188
async function fetchOpenRouterModels(config: Record<string, unknown>): Promise<ModelInfo[]> {
@@ -223,8 +241,9 @@ export const useProvidersStore = defineStore('providers', () => {
223241
!config.baseUrl && new Error('Base URL is required'),
224242
].filter(Boolean)
225243

226-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
227-
return notBaseUrlError.value
244+
const res = baseUrlValidator.value(config.baseUrl)
245+
if (res) {
246+
return res
228247
}
229248

230249
return {
@@ -504,8 +523,9 @@ export const useProvidersStore = defineStore('providers', () => {
504523
}
505524
}
506525

507-
if (!isAbsoluteUrl(config.baseUrl as string)) {
508-
return notBaseUrlError.value
526+
const res = baseUrlValidator.value(config.baseUrl)
527+
if (res) {
528+
return res
509529
}
510530

511531
// Check if the Ollama server is reachable
@@ -570,8 +590,9 @@ export const useProvidersStore = defineStore('providers', () => {
570590
}
571591
}
572592

573-
if (!isAbsoluteUrl(config.baseUrl as string)) {
574-
return notBaseUrlError.value
593+
const res = baseUrlValidator.value(config.baseUrl)
594+
if (res) {
595+
return res
575596
}
576597

577598
// Check if the Ollama server is reachable
@@ -665,8 +686,9 @@ export const useProvidersStore = defineStore('providers', () => {
665686
}
666687
}
667688

668-
if (!isAbsoluteUrl(config.baseUrl as string)) {
669-
return notBaseUrlError.value
689+
const res = baseUrlValidator.value(config.baseUrl)
690+
if (res) {
691+
return res
670692
}
671693

672694
// Check if the vLLM is reachable
@@ -742,6 +764,11 @@ export const useProvidersStore = defineStore('providers', () => {
742764
}
743765
}
744766

767+
const res = baseUrlValidator.value(config.baseUrl)
768+
if (res) {
769+
return res
770+
}
771+
745772
// Check if the LM Studio server is reachable
746773
return fetch(`${(config.baseUrl as string).trim()}models`, { headers: (config.headers as HeadersInit) || undefined })
747774
.then((response) => {
@@ -800,8 +827,9 @@ export const useProvidersStore = defineStore('providers', () => {
800827
!config.baseUrl && new Error('Base URL is required. Default to https://api.openai.com/v1/ for official OpenAI API.'),
801828
].filter(Boolean)
802829

803-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
804-
return notBaseUrlError.value
830+
const res = baseUrlValidator.value(config.baseUrl)
831+
if (res) {
832+
return res
805833
}
806834

807835
return {
@@ -848,8 +876,9 @@ export const useProvidersStore = defineStore('providers', () => {
848876
!config.baseUrl && new Error('Base URL is required'),
849877
].filter(Boolean)
850878

851-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
852-
return notBaseUrlError.value
879+
const res = baseUrlValidator.value(config.baseUrl)
880+
if (res) {
881+
return res
853882
}
854883

855884
return {
@@ -965,8 +994,9 @@ export const useProvidersStore = defineStore('providers', () => {
965994
!config.baseUrl && new Error('Base URL is required. Default to https://api.openai.com/v1/ for official OpenAI API.'),
966995
].filter(Boolean)
967996

968-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
969-
return notBaseUrlError.value
997+
const res = baseUrlValidator.value(config.baseUrl)
998+
if (res) {
999+
return res
9701000
}
9711001

9721002
return {
@@ -1016,8 +1046,9 @@ export const useProvidersStore = defineStore('providers', () => {
10161046
!config.baseUrl && new Error('Base URL is required'),
10171047
].filter(Boolean)
10181048

1019-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1020-
return notBaseUrlError.value
1049+
const res = baseUrlValidator.value(config.baseUrl)
1050+
if (res) {
1051+
return res
10211052
}
10221053

10231054
return {
@@ -1063,8 +1094,9 @@ export const useProvidersStore = defineStore('providers', () => {
10631094
!config.baseUrl && new Error('Base URL is required. Default to https://api.openai.com/v1/ for official OpenAI API.'),
10641095
].filter(Boolean)
10651096

1066-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1067-
return notBaseUrlError.value
1097+
const res = baseUrlValidator.value(config.baseUrl)
1098+
if (res) {
1099+
return res
10681100
}
10691101

10701102
return {
@@ -1111,8 +1143,9 @@ export const useProvidersStore = defineStore('providers', () => {
11111143
!config.baseUrl && new Error('Base URL is required'),
11121144
].filter(Boolean)
11131145

1114-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1115-
return notBaseUrlError.value
1146+
const res = baseUrlValidator.value(config.baseUrl)
1147+
if (res) {
1148+
return res
11161149
}
11171150

11181151
return {
@@ -1246,8 +1279,9 @@ export const useProvidersStore = defineStore('providers', () => {
12461279
!config.baseUrl && new Error('Base URL is required. Default to https://api.anthropic.com/v1/ for official Claude API with OpenAI compatibility.'),
12471280
].filter(Boolean)
12481281

1249-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1250-
return notBaseUrlError.value
1282+
const res = baseUrlValidator.value(config.baseUrl)
1283+
if (res) {
1284+
return res
12511285
}
12521286

12531287
return {
@@ -1294,8 +1328,9 @@ export const useProvidersStore = defineStore('providers', () => {
12941328
!config.baseUrl && new Error('Base URL is required. Default to https://generativelanguage.googleapis.com/v1beta/openai/ for official Google Gemini API with OpenAI compatibility.'),
12951329
].filter(Boolean)
12961330

1297-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1298-
return notBaseUrlError.value
1331+
const res = baseUrlValidator.value(config.baseUrl)
1332+
if (res) {
1333+
return res
12991334
}
13001335

13011336
return {
@@ -1383,8 +1418,9 @@ export const useProvidersStore = defineStore('providers', () => {
13831418
!config.baseUrl && new Error('Base URL is required.'),
13841419
].filter(Boolean)
13851420

1386-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1387-
return notBaseUrlError.value
1421+
const res = baseUrlValidator.value(config.baseUrl)
1422+
if (res) {
1423+
return res
13881424
}
13891425

13901426
return {
@@ -1467,8 +1503,9 @@ export const useProvidersStore = defineStore('providers', () => {
14671503
!config.baseUrl && new Error('Base URL is required.'),
14681504
].filter(Boolean)
14691505

1470-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1471-
return notBaseUrlError.value
1506+
const res = baseUrlValidator.value(config.baseUrl)
1507+
if (res) {
1508+
return res
14721509
}
14731510

14741511
return {
@@ -1531,8 +1568,9 @@ export const useProvidersStore = defineStore('providers', () => {
15311568
!config.baseUrl && new Error('Base URL is required.'),
15321569
].filter(Boolean)
15331570

1534-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1535-
return notBaseUrlError.value
1571+
const res = baseUrlValidator.value(config.baseUrl)
1572+
if (res) {
1573+
return res
15361574
}
15371575

15381576
return {
@@ -1592,8 +1630,9 @@ export const useProvidersStore = defineStore('providers', () => {
15921630
!config.baseUrl && new Error('Base URL is required. Default to http://localhost:11996/tts for Index-TTS.'),
15931631
].filter(Boolean)
15941632

1595-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1596-
return notBaseUrlError.value
1633+
const res = baseUrlValidator.value(config.baseUrl)
1634+
if (res) {
1635+
return res
15971636
}
15981637

15991638
return {
@@ -1664,8 +1703,9 @@ export const useProvidersStore = defineStore('providers', () => {
16641703
!config.baseUrl && new Error('Base URL is required.'),
16651704
].filter(Boolean)
16661705

1667-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1668-
return notBaseUrlError.value
1706+
const res = baseUrlValidator.value(config.baseUrl)
1707+
if (res) {
1708+
return res
16691709
}
16701710

16711711
return {
@@ -1729,8 +1769,9 @@ export const useProvidersStore = defineStore('providers', () => {
17291769
!((config.app as any)?.appId) && new Error('App ID is required.'),
17301770
].filter(Boolean)
17311771

1732-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1733-
return notBaseUrlError.value
1772+
const res = baseUrlValidator.value(config.baseUrl)
1773+
if (res) {
1774+
return res
17341775
}
17351776

17361777
return {
@@ -1900,8 +1941,9 @@ export const useProvidersStore = defineStore('providers', () => {
19001941
!config.baseUrl && new Error('Base URL is required.'),
19011942
].filter(Boolean)
19021943

1903-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
1904-
return notBaseUrlError.value
1944+
const res = baseUrlValidator.value(config.baseUrl)
1945+
if (res) {
1946+
return res
19051947
}
19061948

19071949
return {
@@ -2003,8 +2045,9 @@ export const useProvidersStore = defineStore('providers', () => {
20032045
!config.baseUrl && new Error('Base URL is required.'),
20042046
].filter(Boolean)
20052047

2006-
if (!!config.baseUrl && !isAbsoluteUrl(config.baseUrl as string)) {
2007-
return notBaseUrlError.value
2048+
const res = baseUrlValidator.value(config.baseUrl)
2049+
if (res) {
2050+
return res
20082051
}
20092052

20102053
return {
@@ -2131,12 +2174,13 @@ export const useProvidersStore = defineStore('providers', () => {
21312174
}
21322175
}
21332176

2134-
if (!isAbsoluteUrl(config.baseUrl as string)) {
2135-
return notBaseUrlError.value
2177+
const res = baseUrlValidator.value(config.baseUrl)
2178+
if (res) {
2179+
return res
21362180
}
21372181

21382182
// Check if the local running Player 2 is reachable
2139-
return await fetch(`${(config.baseUrl as string).endsWith('/') ? (config.baseUrl as string).slice(0, -1) : config.baseUrl}/health`, {
2183+
return await fetch(`${config.baseUrl}health`, {
21402184
method: 'GET',
21412185
headers: {
21422186
'player2-game-key': 'airi',
@@ -2241,8 +2285,9 @@ export const useProvidersStore = defineStore('providers', () => {
22412285
}
22422286
}
22432287

2244-
if (!isAbsoluteUrl(config.baseUrl as string)) {
2245-
return notBaseUrlError.value
2288+
const res = baseUrlValidator.value(config.baseUrl)
2289+
if (res) {
2290+
return res
22462291
}
22472292

22482293
return {

packages/stage-ui/src/utils/string.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

packages/stage-ui/src/utils/url.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function isUrl(url: string) {
2+
try {
3+
// eslint-disable-next-line no-new
4+
new URL(url)
5+
return true
6+
}
7+
catch {
8+
return false
9+
}
10+
}

0 commit comments

Comments
 (0)