feat(stage-pages,stage-ui,i18n): disable auto trigger for chatcompletion check and use explict button (#1336)
Co-authored-by-agent: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Neko <neko@ayaka.moe>
This commit is contained in:
@@ -20,8 +20,12 @@ dialogs:
|
|||||||
baseUrlHelp: API endpoint URL (use default if unsure)
|
baseUrlHelp: API endpoint URL (use default if unsure)
|
||||||
accountId: Account ID
|
accountId: Account ID
|
||||||
validationSuccess: Configuration validation success
|
validationSuccess: Configuration validation success
|
||||||
|
validationPartial: Configuration partially validated
|
||||||
validationFailed: Configuration validation failed
|
validationFailed: Configuration validation failed
|
||||||
validationError: 'Validation error: {error}'
|
validationError: 'Validation error: {error}'
|
||||||
|
testGeneration: Test Generation
|
||||||
|
testGenerationRunning: Testing...
|
||||||
|
testGenerationFailed: Generation test failed
|
||||||
skipForNow: Skip for now
|
skipForNow: Skip for now
|
||||||
saveAndContinue: Save and Continue
|
saveAndContinue: Save and Continue
|
||||||
next: Next
|
next: Next
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: URL del endpoint de la API (usa el predeterminado si no estás seguro)
|
baseUrlHelp: URL del endpoint de la API (usa el predeterminado si no estás seguro)
|
||||||
accountId: ID de Cuenta
|
accountId: ID de Cuenta
|
||||||
validationSuccess: La validación de la configuración fue exitosa
|
validationSuccess: La validación de la configuración fue exitosa
|
||||||
|
validationPartial: Configuración parcialmente validada
|
||||||
validationFailed: La validación de la configuración falló
|
validationFailed: La validación de la configuración falló
|
||||||
validationError: 'Error de validación: {error}'
|
validationError: 'Error de validación: {error}'
|
||||||
|
testGeneration: Prueba de Generación
|
||||||
|
testGenerationRunning: Probando...
|
||||||
|
testGenerationFailed: Prueba de generación fallida
|
||||||
skipForNow: Omitir por ahora
|
skipForNow: Omitir por ahora
|
||||||
saveAndContinue: Guardar y Continuar
|
saveAndContinue: Guardar y Continuar
|
||||||
next: Siguiente
|
next: Siguiente
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: URL de l'endpoint API (utilisez la valeur par défaut si vous n’êtes pas sûr)
|
baseUrlHelp: URL de l'endpoint API (utilisez la valeur par défaut si vous n’êtes pas sûr)
|
||||||
accountId: ID du compte
|
accountId: ID du compte
|
||||||
validationSuccess: Validation de la configuration réussie
|
validationSuccess: Validation de la configuration réussie
|
||||||
|
validationPartial: Configuration partiellement validée
|
||||||
validationFailed: La validation de la configuration a échouée.
|
validationFailed: La validation de la configuration a échouée.
|
||||||
validationError: 'Erreur de validation: {error}'
|
validationError: 'Erreur de validation: {error}'
|
||||||
|
testGeneration: Test de génération
|
||||||
|
testGenerationRunning: Test en cours...
|
||||||
|
testGenerationFailed: Test de génération échoué
|
||||||
skipForNow: Passer pour l'instant
|
skipForNow: Passer pour l'instant
|
||||||
saveAndContinue: Sauvegarder et Continuer
|
saveAndContinue: Sauvegarder et Continuer
|
||||||
next: Suivant
|
next: Suivant
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: APIエンドポイントURL(不明な場合はデフォルトを使用)
|
baseUrlHelp: APIエンドポイントURL(不明な場合はデフォルトを使用)
|
||||||
accountId: アカウントID
|
accountId: アカウントID
|
||||||
validationSuccess: 設定検証に成功
|
validationSuccess: 設定検証に成功
|
||||||
|
validationPartial: 設定が部分的に検証されました
|
||||||
validationFailed: 設定検証に失敗
|
validationFailed: 設定検証に失敗
|
||||||
validationError: '検証エラー: {error}'
|
validationError: '検証エラー: {error}'
|
||||||
|
testGeneration: 生成テスト
|
||||||
|
testGenerationRunning: テスト中...
|
||||||
|
testGenerationFailed: 生成テストに失敗
|
||||||
skipForNow: 今はスキップ
|
skipForNow: 今はスキップ
|
||||||
saveAndContinue: 保存して続行
|
saveAndContinue: 保存して続行
|
||||||
next: 次へ
|
next: 次へ
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: API 엔드포인트 URL(확실하지 않은 경우 기본값 사용)
|
baseUrlHelp: API 엔드포인트 URL(확실하지 않은 경우 기본값 사용)
|
||||||
accountId: 계정 ID
|
accountId: 계정 ID
|
||||||
validationSuccess: 구성 검증 성공
|
validationSuccess: 구성 검증 성공
|
||||||
|
validationPartial: 구성이 부분적으로 검증됨
|
||||||
validationFailed: 구성 검증 실패
|
validationFailed: 구성 검증 실패
|
||||||
validationError: '검증 오류: {error}'
|
validationError: '검증 오류: {error}'
|
||||||
|
testGeneration: 생성 테스트
|
||||||
|
testGenerationRunning: 테스트 중...
|
||||||
|
testGenerationFailed: 생성 테스트 실패
|
||||||
skipForNow: 지금은 건너뛰기
|
skipForNow: 지금은 건너뛰기
|
||||||
saveAndContinue: 저장 및 계속
|
saveAndContinue: 저장 및 계속
|
||||||
next: 다음
|
next: 다음
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: URL конечной точки API (оставьте по умолчанию, если не уверены)
|
baseUrlHelp: URL конечной точки API (оставьте по умолчанию, если не уверены)
|
||||||
accountId: ID аккаунта
|
accountId: ID аккаунта
|
||||||
validationSuccess: Configuration validation success
|
validationSuccess: Configuration validation success
|
||||||
|
validationPartial: Конфигурация частично проверена
|
||||||
validationFailed: Проверка конфигурации не удалась
|
validationFailed: Проверка конфигурации не удалась
|
||||||
validationError: 'Ошибка проверки: {error}'
|
validationError: 'Ошибка проверки: {error}'
|
||||||
|
testGeneration: Тест генерации
|
||||||
|
testGenerationRunning: Тестирование...
|
||||||
|
testGenerationFailed: Тест генерации не удался
|
||||||
skipForNow: Пропустить на сейчас
|
skipForNow: Пропустить на сейчас
|
||||||
saveAndContinue: Сохранить и продолжить
|
saveAndContinue: Сохранить и продолжить
|
||||||
next: Далее
|
next: Далее
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: Địa chỉ API đầu cuối (dùng mặc định nếu không chắc)
|
baseUrlHelp: Địa chỉ API đầu cuối (dùng mặc định nếu không chắc)
|
||||||
accountId: ID tài khoản
|
accountId: ID tài khoản
|
||||||
validationSuccess: Xác thực cấu hình thành công
|
validationSuccess: Xác thực cấu hình thành công
|
||||||
|
validationPartial: Cấu hình được xác thực một phần
|
||||||
validationFailed: Xác thực cấu hình thất bại
|
validationFailed: Xác thực cấu hình thất bại
|
||||||
validationError: 'Lỗi xác thực: {error}'
|
validationError: 'Lỗi xác thực: {error}'
|
||||||
|
testGeneration: Kiểm tra tạo
|
||||||
|
testGenerationRunning: Đang kiểm tra...
|
||||||
|
testGenerationFailed: Kiểm tra tạo thất bại
|
||||||
skipForNow: Bỏ qua
|
skipForNow: Bỏ qua
|
||||||
saveAndContinue: Lưu và tiếp tục
|
saveAndContinue: Lưu và tiếp tục
|
||||||
next: Tiếp
|
next: Tiếp
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: API 端点 URL(如果不确定请使用默认值)
|
baseUrlHelp: API 端点 URL(如果不确定请使用默认值)
|
||||||
accountId: 账户 ID
|
accountId: 账户 ID
|
||||||
validationSuccess: 配置验证成功
|
validationSuccess: 配置验证成功
|
||||||
|
validationPartial: 配置部分验证通过
|
||||||
validationFailed: 配置验证失败
|
validationFailed: 配置验证失败
|
||||||
validationError: '验证错误:{error}'
|
validationError: '验证错误:{error}'
|
||||||
|
testGeneration: 测试生成
|
||||||
|
testGenerationRunning: 测试中...
|
||||||
|
testGenerationFailed: 生成测试失败
|
||||||
skipForNow: 暂时跳过
|
skipForNow: 暂时跳过
|
||||||
saveAndContinue: 保存并继续
|
saveAndContinue: 保存并继续
|
||||||
next: 下一步
|
next: 下一步
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ dialogs:
|
|||||||
baseUrlHelp: API 端點 URL(如果不確定請使用預設值)
|
baseUrlHelp: API 端點 URL(如果不確定請使用預設值)
|
||||||
accountId: 帳號 ID
|
accountId: 帳號 ID
|
||||||
validationSuccess: 設定驗證成功
|
validationSuccess: 設定驗證成功
|
||||||
|
validationPartial: 設定部分驗證通過
|
||||||
validationFailed: 設定驗證失敗
|
validationFailed: 設定驗證失敗
|
||||||
validationError: '驗證錯誤:{error}'
|
validationError: '驗證錯誤:{error}'
|
||||||
|
testGeneration: 測試生成
|
||||||
|
testGenerationRunning: 測試中...
|
||||||
|
testGenerationFailed: 生成測試失敗
|
||||||
skipForNow: 先略過
|
skipForNow: 先略過
|
||||||
saveAndContinue: 儲存並繼續
|
saveAndContinue: 儲存並繼續
|
||||||
next: 下一步
|
next: 下一步
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
import type { RemovableRef } from '@vueuse/core'
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
ProviderAdvancedSettings,
|
ProviderAdvancedSettings,
|
||||||
ProviderApiKeyInput,
|
ProviderApiKeyInput,
|
||||||
ProviderBaseUrlInput,
|
ProviderBaseUrlInput,
|
||||||
ProviderBasicSettings,
|
ProviderBasicSettings,
|
||||||
ProviderSettingsContainer,
|
ProviderSettingsContainer,
|
||||||
ProviderSettingsLayout,
|
ProviderSettingsLayout,
|
||||||
|
ProviderValidationAlerts,
|
||||||
} from '@proj-airi/stage-ui/components'
|
} from '@proj-airi/stage-ui/components'
|
||||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||||
import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness'
|
import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness'
|
||||||
@@ -53,6 +53,11 @@ const {
|
|||||||
validationMessage,
|
validationMessage,
|
||||||
handleResetSettings,
|
handleResetSettings,
|
||||||
forceValid,
|
forceValid,
|
||||||
|
hasManualValidators,
|
||||||
|
isManualTesting,
|
||||||
|
manualTestPassed,
|
||||||
|
manualTestMessage,
|
||||||
|
runManualTest,
|
||||||
} = useProviderValidation(providerId)
|
} = useProviderValidation(providerId)
|
||||||
|
|
||||||
function goToModelSelection() {
|
function goToModelSelection() {
|
||||||
@@ -88,39 +93,18 @@ function goToModelSelection() {
|
|||||||
</ProviderAdvancedSettings>
|
</ProviderAdvancedSettings>
|
||||||
|
|
||||||
<!-- Validation Status -->
|
<!-- Validation Status -->
|
||||||
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
<ProviderValidationAlerts
|
||||||
<template #title>
|
:is-valid="isValid"
|
||||||
<div class="w-full flex items-center justify-between">
|
:is-validating="isValidating"
|
||||||
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
:validation-message="validationMessage"
|
||||||
<button
|
:has-manual-validators="hasManualValidators"
|
||||||
type="button"
|
:is-manual-testing="isManualTesting"
|
||||||
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
:manual-test-passed="manualTestPassed"
|
||||||
@click="forceValid"
|
:manual-test-message="manualTestMessage"
|
||||||
>
|
:on-run-test="runManualTest"
|
||||||
{{ t('settings.pages.providers.common.continueAnyway') }}
|
:on-force-valid="forceValid"
|
||||||
</button>
|
:on-go-to-model-selection="goToModelSelection"
|
||||||
</div>
|
/>
|
||||||
</template>
|
|
||||||
<template v-if="validationMessage" #content>
|
|
||||||
<div class="whitespace-pre-wrap break-all">
|
|
||||||
{{ validationMessage }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
<Alert v-if="isValid && isValidating === 0" type="success">
|
|
||||||
<template #title>
|
|
||||||
<div class="w-full flex items-center justify-between">
|
|
||||||
<span>{{ t('settings.dialogs.onboarding.validationSuccess') }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:class="['ml-2 rounded px-2 py-0.5 text-xs font-medium transition-colors', 'bg-green-100 text-green-600 hover:bg-green-200', 'dark:bg-green-800/30 dark:text-green-300 dark:hover:bg-green-700/40']"
|
|
||||||
@click="goToModelSelection"
|
|
||||||
>
|
|
||||||
{{ t('settings.pages.providers.common.goToModelSelection') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
</ProviderSettingsContainer>
|
</ProviderSettingsContainer>
|
||||||
</ProviderSettingsLayout>
|
</ProviderSettingsLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
import type { RemovableRef } from '@vueuse/core'
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
ProviderAccountIdInput,
|
ProviderAccountIdInput,
|
||||||
ProviderAdvancedSettings,
|
ProviderAdvancedSettings,
|
||||||
ProviderApiKeyInput,
|
ProviderApiKeyInput,
|
||||||
ProviderBasicSettings,
|
ProviderBasicSettings,
|
||||||
ProviderSettingsContainer,
|
ProviderSettingsContainer,
|
||||||
ProviderSettingsLayout,
|
ProviderSettingsLayout,
|
||||||
|
ProviderValidationAlerts,
|
||||||
} from '@proj-airi/stage-ui/components'
|
} from '@proj-airi/stage-ui/components'
|
||||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||||
@@ -66,6 +66,11 @@ const {
|
|||||||
validationMessage,
|
validationMessage,
|
||||||
handleResetSettings,
|
handleResetSettings,
|
||||||
forceValid,
|
forceValid,
|
||||||
|
hasManualValidators,
|
||||||
|
isManualTesting,
|
||||||
|
manualTestPassed,
|
||||||
|
manualTestMessage,
|
||||||
|
runManualTest,
|
||||||
} = useProviderValidation(providerId)
|
} = useProviderValidation(providerId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -113,30 +118,18 @@ const {
|
|||||||
</ProviderAdvancedSettings>
|
</ProviderAdvancedSettings>
|
||||||
|
|
||||||
<!-- Validation Status -->
|
<!-- Validation Status -->
|
||||||
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
<ProviderValidationAlerts
|
||||||
<template #title>
|
:is-valid="isValid"
|
||||||
<div class="w-full flex items-center justify-between">
|
:is-validating="isValidating"
|
||||||
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
:validation-message="validationMessage"
|
||||||
<button
|
:has-manual-validators="hasManualValidators"
|
||||||
type="button"
|
:is-manual-testing="isManualTesting"
|
||||||
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
:manual-test-passed="manualTestPassed"
|
||||||
@click="forceValid"
|
:manual-test-message="manualTestMessage"
|
||||||
>
|
:on-run-test="runManualTest"
|
||||||
{{ t('settings.pages.providers.common.continueAnyway') }}
|
:on-force-valid="forceValid"
|
||||||
</button>
|
:on-go-to-model-selection="() => router.push('/settings/modules/consciousness')"
|
||||||
</div>
|
/>
|
||||||
</template>
|
|
||||||
<template v-if="validationMessage" #content>
|
|
||||||
<div class="whitespace-pre-wrap break-all">
|
|
||||||
{{ validationMessage }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
<Alert v-if="isValid && isValidating === 0" type="success">
|
|
||||||
<template #title>
|
|
||||||
{{ t('settings.dialogs.onboarding.validationSuccess') }}
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
</ProviderSettingsContainer>
|
</ProviderSettingsContainer>
|
||||||
</ProviderSettingsLayout>
|
</ProviderSettingsLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
import type { RemovableRef } from '@vueuse/core'
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
ProviderAccountIdInput,
|
ProviderAccountIdInput,
|
||||||
ProviderApiKeyInput,
|
ProviderApiKeyInput,
|
||||||
ProviderBasicSettings,
|
ProviderBasicSettings,
|
||||||
ProviderSettingsContainer,
|
ProviderSettingsContainer,
|
||||||
ProviderSettingsLayout,
|
ProviderSettingsLayout,
|
||||||
|
ProviderValidationAlerts,
|
||||||
} from '@proj-airi/stage-ui/components'
|
} from '@proj-airi/stage-ui/components'
|
||||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||||
@@ -47,6 +47,11 @@ const {
|
|||||||
validationMessage,
|
validationMessage,
|
||||||
handleResetSettings,
|
handleResetSettings,
|
||||||
forceValid,
|
forceValid,
|
||||||
|
hasManualValidators,
|
||||||
|
isManualTesting,
|
||||||
|
manualTestPassed,
|
||||||
|
manualTestMessage,
|
||||||
|
runManualTest,
|
||||||
} = useProviderValidation(providerId)
|
} = useProviderValidation(providerId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -78,30 +83,18 @@ const {
|
|||||||
</ProviderBasicSettings>
|
</ProviderBasicSettings>
|
||||||
|
|
||||||
<!-- Validation Status -->
|
<!-- Validation Status -->
|
||||||
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
<ProviderValidationAlerts
|
||||||
<template #title>
|
:is-valid="isValid"
|
||||||
<div class="w-full flex items-center justify-between">
|
:is-validating="isValidating"
|
||||||
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
:validation-message="validationMessage"
|
||||||
<button
|
:has-manual-validators="hasManualValidators"
|
||||||
type="button"
|
:is-manual-testing="isManualTesting"
|
||||||
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
:manual-test-passed="manualTestPassed"
|
||||||
@click="forceValid"
|
:manual-test-message="manualTestMessage"
|
||||||
>
|
:on-run-test="runManualTest"
|
||||||
{{ t('settings.pages.providers.common.continueAnyway') }}
|
:on-force-valid="forceValid"
|
||||||
</button>
|
:on-go-to-model-selection="() => router.push('/settings/modules/consciousness')"
|
||||||
</div>
|
/>
|
||||||
</template>
|
|
||||||
<template v-if="validationMessage" #content>
|
|
||||||
<div class="whitespace-pre-wrap break-all">
|
|
||||||
{{ validationMessage }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
<Alert v-if="isValid && isValidating === 0" type="success">
|
|
||||||
<template #title>
|
|
||||||
{{ t('settings.dialogs.onboarding.validationSuccess') }}
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
</ProviderSettingsContainer>
|
</ProviderSettingsContainer>
|
||||||
</ProviderSettingsLayout>
|
</ProviderSettingsLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import type { RemovableRef } from '@vueuse/core'
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
ProviderBaseUrlInput,
|
ProviderBaseUrlInput,
|
||||||
ProviderBasicSettings,
|
ProviderBasicSettings,
|
||||||
ProviderSettingsContainer,
|
ProviderSettingsContainer,
|
||||||
ProviderSettingsLayout,
|
ProviderSettingsLayout,
|
||||||
|
ProviderValidationAlerts,
|
||||||
} from '@proj-airi/stage-ui/components'
|
} from '@proj-airi/stage-ui/components'
|
||||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||||
@@ -38,6 +38,11 @@ const {
|
|||||||
validationMessage,
|
validationMessage,
|
||||||
handleResetSettings,
|
handleResetSettings,
|
||||||
forceValid,
|
forceValid,
|
||||||
|
hasManualValidators,
|
||||||
|
isManualTesting,
|
||||||
|
manualTestPassed,
|
||||||
|
manualTestMessage,
|
||||||
|
runManualTest,
|
||||||
} = useProviderValidation(providerId)
|
} = useProviderValidation(providerId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -60,30 +65,18 @@ const {
|
|||||||
</ProviderBasicSettings>
|
</ProviderBasicSettings>
|
||||||
|
|
||||||
<!-- Validation Status -->
|
<!-- Validation Status -->
|
||||||
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
<ProviderValidationAlerts
|
||||||
<template #title>
|
:is-valid="isValid"
|
||||||
<div class="w-full flex items-center justify-between">
|
:is-validating="isValidating"
|
||||||
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
:validation-message="validationMessage"
|
||||||
<button
|
:has-manual-validators="hasManualValidators"
|
||||||
type="button"
|
:is-manual-testing="isManualTesting"
|
||||||
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
:manual-test-passed="manualTestPassed"
|
||||||
@click="forceValid"
|
:manual-test-message="manualTestMessage"
|
||||||
>
|
:on-run-test="runManualTest"
|
||||||
{{ t('settings.pages.providers.common.continueAnyway') }}
|
:on-force-valid="forceValid"
|
||||||
</button>
|
:on-go-to-model-selection="() => router.push('/settings/modules/consciousness')"
|
||||||
</div>
|
/>
|
||||||
</template>
|
|
||||||
<template v-if="validationMessage" #content>
|
|
||||||
<div class="whitespace-pre-wrap break-all">
|
|
||||||
{{ validationMessage }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
<Alert v-if="isValid && isValidating === 0" type="success">
|
|
||||||
<template #title>
|
|
||||||
{{ t('settings.dialogs.onboarding.validationSuccess') }}
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
</ProviderSettingsContainer>
|
</ProviderSettingsContainer>
|
||||||
</ProviderSettingsLayout>
|
</ProviderSettingsLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
import type { RemovableRef } from '@vueuse/core'
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
ProviderAdvancedSettings,
|
ProviderAdvancedSettings,
|
||||||
ProviderBaseUrlInput,
|
ProviderBaseUrlInput,
|
||||||
ProviderBasicSettings,
|
ProviderBasicSettings,
|
||||||
ProviderSettingsContainer,
|
ProviderSettingsContainer,
|
||||||
ProviderSettingsLayout,
|
ProviderSettingsLayout,
|
||||||
|
ProviderValidationAlerts,
|
||||||
} from '@proj-airi/stage-ui/components'
|
} from '@proj-airi/stage-ui/components'
|
||||||
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
|
||||||
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers'
|
||||||
@@ -39,6 +39,11 @@ const {
|
|||||||
validationMessage,
|
validationMessage,
|
||||||
handleResetSettings,
|
handleResetSettings,
|
||||||
forceValid,
|
forceValid,
|
||||||
|
hasManualValidators,
|
||||||
|
isManualTesting,
|
||||||
|
manualTestPassed,
|
||||||
|
manualTestMessage,
|
||||||
|
runManualTest,
|
||||||
} = useProviderValidation(providerId)
|
} = useProviderValidation(providerId)
|
||||||
|
|
||||||
const headers = ref<{ key: string, value: string }[]>(Object.entries(providers.value[providerId]?.headers || {}).map(([key, value]) => ({ key, value } as { key: string, value: string })) || [{ key: '', value: '' }])
|
const headers = ref<{ key: string, value: string }[]>(Object.entries(providers.value[providerId]?.headers || {}).map(([key, value]) => ({ key, value } as { key: string, value: string })) || [{ key: '', value: '' }])
|
||||||
@@ -176,30 +181,18 @@ onMounted(() => {
|
|||||||
</ProviderAdvancedSettings>
|
</ProviderAdvancedSettings>
|
||||||
|
|
||||||
<!-- Validation Status -->
|
<!-- Validation Status -->
|
||||||
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
<ProviderValidationAlerts
|
||||||
<template #title>
|
:is-valid="isValid"
|
||||||
<div class="w-full flex items-center justify-between">
|
:is-validating="isValidating"
|
||||||
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
:validation-message="validationMessage"
|
||||||
<button
|
:has-manual-validators="hasManualValidators"
|
||||||
type="button"
|
:is-manual-testing="isManualTesting"
|
||||||
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
:manual-test-passed="manualTestPassed"
|
||||||
@click="forceValid"
|
:manual-test-message="manualTestMessage"
|
||||||
>
|
:on-run-test="runManualTest"
|
||||||
{{ t('settings.pages.providers.common.continueAnyway') }}
|
:on-force-valid="forceValid"
|
||||||
</button>
|
:on-go-to-model-selection="() => router.push('/settings/modules/consciousness')"
|
||||||
</div>
|
/>
|
||||||
</template>
|
|
||||||
<template v-if="validationMessage" #content>
|
|
||||||
<div class="whitespace-pre-wrap break-all">
|
|
||||||
{{ validationMessage }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
<Alert v-if="isValid && isValidating === 0" type="success">
|
|
||||||
<template #title>
|
|
||||||
{{ t('settings.dialogs.onboarding.validationSuccess') }}
|
|
||||||
</template>
|
|
||||||
</Alert>
|
|
||||||
</ProviderSettingsContainer>
|
</ProviderSettingsContainer>
|
||||||
</ProviderSettingsLayout>
|
</ProviderSettingsLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export { default as ProviderBaseUrlInput } from './provider-base-url-input.vue'
|
|||||||
export { default as ProviderBasicSettings } from './provider-basic-settings.vue'
|
export { default as ProviderBasicSettings } from './provider-basic-settings.vue'
|
||||||
export { default as ProviderSettingsContainer } from './provider-settings-container.vue'
|
export { default as ProviderSettingsContainer } from './provider-settings-container.vue'
|
||||||
export { default as ProviderSettingsLayout } from './provider-settings-layout.vue'
|
export { default as ProviderSettingsLayout } from './provider-settings-layout.vue'
|
||||||
|
export { default as ProviderValidationAlerts } from './provider-validation-alerts.vue'
|
||||||
|
|
||||||
export { default as SpeechPlaygroundOpenAICompatible } from './speech-playground-openai-compatible.vue'
|
export { default as SpeechPlaygroundOpenAICompatible } from './speech-playground-openai-compatible.vue'
|
||||||
export { default as SpeechPlayground } from './speech-playground.vue'
|
export { default as SpeechPlayground } from './speech-playground.vue'
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import Alert from '../../misc/alert.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
isValid: boolean
|
||||||
|
isValidating: number
|
||||||
|
validationMessage: string
|
||||||
|
hasManualValidators: boolean
|
||||||
|
isManualTesting: boolean
|
||||||
|
manualTestPassed: boolean
|
||||||
|
manualTestMessage: string
|
||||||
|
onRunTest: () => void
|
||||||
|
onForceValid: () => void
|
||||||
|
onGoToModelSelection: () => void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Validation Error -->
|
||||||
|
<Alert v-if="!isValid && isValidating === 0 && validationMessage" type="error">
|
||||||
|
<template #title>
|
||||||
|
<div class="w-full flex items-center justify-between">
|
||||||
|
<span>{{ t('settings.dialogs.onboarding.validationFailed') }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
||||||
|
@click="props.onForceValid"
|
||||||
|
>
|
||||||
|
{{ t('settings.pages.providers.common.continueAnyway') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="validationMessage" #content>
|
||||||
|
<div class="whitespace-pre-wrap break-all">
|
||||||
|
{{ validationMessage }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Alert>
|
||||||
|
<!-- Partial Validation: manual validators exist, no test attempted yet -->
|
||||||
|
<Alert v-else-if="isValid && isValidating === 0 && hasManualValidators && !manualTestPassed && !manualTestMessage" type="info">
|
||||||
|
<template #title>
|
||||||
|
<div class="w-full flex items-center justify-between">
|
||||||
|
<span>{{ t('settings.dialogs.onboarding.validationPartial') }}</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:disabled="isManualTesting"
|
||||||
|
:class="['rounded px-2 py-0.5 text-xs font-medium transition-colors', isManualTesting ? 'opacity-50 cursor-not-allowed' : '', 'bg-blue-100 text-blue-600 hover:bg-blue-200', 'dark:bg-blue-800/30 dark:text-blue-300 dark:hover:bg-blue-700/40']"
|
||||||
|
@click="props.onRunTest"
|
||||||
|
>
|
||||||
|
{{ isManualTesting ? t('settings.dialogs.onboarding.testGenerationRunning') : t('settings.dialogs.onboarding.testGeneration') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-600 font-medium transition-colors dark:bg-blue-800/30 hover:bg-blue-200 dark:text-blue-300 dark:hover:bg-blue-700/40"
|
||||||
|
@click="props.onGoToModelSelection"
|
||||||
|
>
|
||||||
|
{{ t('settings.pages.providers.common.goToModelSelection') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Alert>
|
||||||
|
<!-- Full Validation Success -->
|
||||||
|
<Alert v-else-if="isValid && isValidating === 0 && (!hasManualValidators || manualTestPassed)" type="success">
|
||||||
|
<template #title>
|
||||||
|
<div class="w-full flex items-center justify-between">
|
||||||
|
<span>{{ t('settings.dialogs.onboarding.validationSuccess') }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-2 rounded bg-green-100 px-2 py-0.5 text-xs text-green-600 font-medium transition-colors dark:bg-green-800/30 hover:bg-green-200 dark:text-green-300 dark:hover:bg-green-700/40"
|
||||||
|
@click="props.onGoToModelSelection"
|
||||||
|
>
|
||||||
|
{{ t('settings.pages.providers.common.goToModelSelection') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Alert>
|
||||||
|
<!-- Manual Test Failed -->
|
||||||
|
<Alert v-else-if="hasManualValidators && !manualTestPassed && manualTestMessage && !isManualTesting" type="error">
|
||||||
|
<template #title>
|
||||||
|
<div class="w-full flex items-center justify-between">
|
||||||
|
<span>{{ t('settings.dialogs.onboarding.testGenerationFailed') }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-2 rounded bg-red-100 px-2 py-0.5 text-xs text-red-600 font-medium transition-colors dark:bg-red-800/30 hover:bg-red-200 dark:text-red-300 dark:hover:bg-red-700/40"
|
||||||
|
@click="props.onForceValid"
|
||||||
|
>
|
||||||
|
{{ t('settings.pages.providers.common.continueAnyway') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="whitespace-pre-wrap break-all">
|
||||||
|
{{ manualTestMessage }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Alert>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { RemovableRef } from '@vueuse/core'
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
|
import { errorMessageFrom } from '@moeru/std'
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
@@ -52,6 +53,11 @@ export function useProviderValidation(providerId: string) {
|
|||||||
const isValid = ref(false)
|
const isValid = ref(false)
|
||||||
const validationMessage = ref('')
|
const validationMessage = ref('')
|
||||||
|
|
||||||
|
const hasManualValidators = computed(() => !!providerMetadata.value?.validators.runManualValidation)
|
||||||
|
const isManualTesting = ref(false)
|
||||||
|
const manualTestPassed = ref(false)
|
||||||
|
const manualTestMessage = ref('')
|
||||||
|
|
||||||
async function validateConfiguration() {
|
async function validateConfiguration() {
|
||||||
if (!providerMetadata.value)
|
if (!providerMetadata.value)
|
||||||
return
|
return
|
||||||
@@ -85,7 +91,7 @@ export function useProviderValidation(providerId: string) {
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
isValid.value = false
|
isValid.value = false
|
||||||
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
|
finalValidationMessage = t('settings.dialogs.onboarding.validationError', {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: errorMessageFrom(error),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -120,19 +126,54 @@ export function useProviderValidation(providerId: string) {
|
|||||||
|
|
||||||
watch(credentials, () => {
|
watch(credentials, () => {
|
||||||
debouncedValidateConfiguration()
|
debouncedValidateConfiguration()
|
||||||
|
// Reset manual test state when credentials change
|
||||||
|
manualTestPassed.value = false
|
||||||
|
manualTestMessage.value = ''
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
async function runManualTest() {
|
||||||
|
if (!providerMetadata.value?.validators.runManualValidation)
|
||||||
|
return
|
||||||
|
|
||||||
|
isManualTesting.value = true
|
||||||
|
manualTestMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const config = { ...credentials.value }
|
||||||
|
if (config?.apiKey)
|
||||||
|
config.apiKey = config.apiKey.trim()
|
||||||
|
if (config?.baseUrl)
|
||||||
|
config.baseUrl = config.baseUrl.trim()
|
||||||
|
|
||||||
|
const result = await providerMetadata.value.validators.runManualValidation(config)
|
||||||
|
manualTestPassed.value = result.valid
|
||||||
|
if (!result.valid)
|
||||||
|
manualTestMessage.value = result.reason
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
manualTestPassed.value = false
|
||||||
|
manualTestMessage.value = errorMessageFrom(error)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isManualTesting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleResetSettings() {
|
function handleResetSettings() {
|
||||||
const defaultOptions = providerMetadata.value?.defaultOptions ? providerMetadata.value.defaultOptions() : {}
|
const defaultOptions = providerMetadata.value?.defaultOptions ? providerMetadata.value.defaultOptions() : {}
|
||||||
providers.value[providerId] = { ...defaultOptions }
|
providers.value[providerId] = { ...defaultOptions }
|
||||||
isValid.value = false
|
isValid.value = false
|
||||||
validationMessage.value = ''
|
validationMessage.value = ''
|
||||||
isValidating.value = 0
|
isValidating.value = 0
|
||||||
|
manualTestPassed.value = false
|
||||||
|
manualTestMessage.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function forceValid() {
|
function forceValid() {
|
||||||
isValid.value = true
|
isValid.value = true
|
||||||
validationMessage.value = ''
|
validationMessage.value = ''
|
||||||
|
manualTestPassed.value = true
|
||||||
|
manualTestMessage.value = ''
|
||||||
providersStore.forceProviderConfigured(providerId)
|
providersStore.forceProviderConfigured(providerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,5 +189,11 @@ export function useProviderValidation(providerId: string) {
|
|||||||
validationMessage,
|
validationMessage,
|
||||||
handleResetSettings,
|
handleResetSettings,
|
||||||
forceValid,
|
forceValid,
|
||||||
|
// Manual test generation
|
||||||
|
hasManualValidators,
|
||||||
|
isManualTesting,
|
||||||
|
manualTestPassed,
|
||||||
|
manualTestMessage,
|
||||||
|
runManualTest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export interface ProviderRuntimeValidator<TConfig> {
|
|||||||
name: string
|
name: string
|
||||||
validator: (config: TConfig, provider: ProviderInstance, providerExtra: ProviderExtraMethods<TConfig>, contextOptions: { t: ComposerTranslation }) => MaybePromise<ProviderValidationResult>
|
validator: (config: TConfig, provider: ProviderInstance, providerExtra: ProviderExtraMethods<TConfig>, contextOptions: { t: ComposerTranslation }) => MaybePromise<ProviderValidationResult>
|
||||||
schedule?: ProviderValidatorSchedule
|
schedule?: ProviderValidatorSchedule
|
||||||
|
/**
|
||||||
|
* When true the validator is excluded from automatic validation runs.
|
||||||
|
* It must be triggered explicitly by the user (e.g. a "Test Generation" button).
|
||||||
|
* This is used for costly checks like chat-completion probes that bill per request.
|
||||||
|
*/
|
||||||
|
manualOnly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelInfo {
|
export interface ModelInfo {
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ export function createOpenAICompatibleValidators<TConfig extends { apiKey?: stri
|
|||||||
id: 'openai-compatible:check-chat-completions',
|
id: 'openai-compatible:check-chat-completions',
|
||||||
name: t('settings.pages.providers.catalog.edit.validators.openai-compatible.check-supports-chat-completion.title'),
|
name: t('settings.pages.providers.catalog.edit.validators.openai-compatible.check-supports-chat-completion.title'),
|
||||||
schedule: options?.schedule,
|
schedule: options?.schedule,
|
||||||
|
manualOnly: true,
|
||||||
validator: async (config, provider, providerExtra, contextOptions) => {
|
validator: async (config, provider, providerExtra, contextOptions) => {
|
||||||
const errors: Array<{ error: unknown }> = []
|
const errors: Array<{ error: unknown }> = []
|
||||||
const result = await getChatCheckResult(
|
const result = await getChatCheckResult(
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export interface ProviderValidationPlan {
|
|||||||
definition: ProviderDefinition
|
definition: ProviderDefinition
|
||||||
configValidators: ProviderConfigValidator<Record<string, unknown>>[]
|
configValidators: ProviderConfigValidator<Record<string, unknown>>[]
|
||||||
providerValidators: ProviderRuntimeValidator<Record<string, unknown>>[]
|
providerValidators: ProviderRuntimeValidator<Record<string, unknown>>[]
|
||||||
|
/** Provider validators that require explicit user action (e.g. "Test Generation" button). */
|
||||||
|
manualProviderValidators: ProviderRuntimeValidator<Record<string, unknown>>[]
|
||||||
providerExtra: ProviderExtraMethods<Record<string, unknown>> | undefined
|
providerExtra: ProviderExtraMethods<Record<string, unknown>> | undefined
|
||||||
shouldValidate: boolean
|
shouldValidate: boolean
|
||||||
}
|
}
|
||||||
@@ -83,7 +85,12 @@ export function getValidatorsOfProvider(options: {
|
|||||||
const { definition } = options
|
const { definition } = options
|
||||||
|
|
||||||
const configValidators = (definition.validators?.validateConfig || []).map(creator => creator(options.contextOptions))
|
const configValidators = (definition.validators?.validateConfig || []).map(creator => creator(options.contextOptions))
|
||||||
const providerValidators = (definition.validators?.validateProvider || []).map(creator => creator(options.contextOptions))
|
const allProviderValidators = (definition.validators?.validateProvider || []).map(creator => creator(options.contextOptions))
|
||||||
|
|
||||||
|
// Separate automatic validators from manual-only ones (e.g. chat completion probes)
|
||||||
|
const providerValidators = allProviderValidators.filter(v => !v.manualOnly)
|
||||||
|
const manualProviderValidators = allProviderValidators.filter(v => v.manualOnly)
|
||||||
|
|
||||||
const steps: ProviderValidationStep[] = [
|
const steps: ProviderValidationStep[] = [
|
||||||
...createConfigValidationSteps(configValidators),
|
...createConfigValidationSteps(configValidators),
|
||||||
...createProviderValidationSteps(providerValidators),
|
...createProviderValidationSteps(providerValidators),
|
||||||
@@ -99,6 +106,7 @@ export function getValidatorsOfProvider(options: {
|
|||||||
definition,
|
definition,
|
||||||
configValidators: configValidators as ProviderValidationPlan['configValidators'],
|
configValidators: configValidators as ProviderValidationPlan['configValidators'],
|
||||||
providerValidators: providerValidators as ProviderValidationPlan['providerValidators'],
|
providerValidators: providerValidators as ProviderValidationPlan['providerValidators'],
|
||||||
|
manualProviderValidators: manualProviderValidators as ProviderValidationPlan['manualProviderValidators'],
|
||||||
providerExtra: definition.extraMethods as ProviderValidationPlan['providerExtra'],
|
providerExtra: definition.extraMethods as ProviderValidationPlan['providerExtra'],
|
||||||
shouldValidate,
|
shouldValidate,
|
||||||
}
|
}
|
||||||
@@ -156,7 +164,7 @@ export async function validateProvider(
|
|||||||
for (let i = 0; i < providerValidators.length; i++) {
|
for (let i = 0; i < providerValidators.length; i++) {
|
||||||
const step = steps[providerStepOffset + i]
|
const step = steps[providerStepOffset + i]
|
||||||
step.status = 'invalid'
|
step.status = 'invalid'
|
||||||
step.reason = error instanceof Error ? error.message : String(error)
|
step.reason = errorMessageFrom(error)
|
||||||
}
|
}
|
||||||
return steps
|
return steps
|
||||||
}
|
}
|
||||||
@@ -174,7 +182,70 @@ export async function validateProvider(
|
|||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
step.status = 'invalid'
|
step.status = 'invalid'
|
||||||
step.reason = error instanceof Error ? error.message : String(error)
|
step.reason = errorMessageFrom(error)
|
||||||
|
onValidatorError?.({ kind: 'provider', index, step, error })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run only the manual-only validators (e.g. chat completion probes).
|
||||||
|
* These are excluded from automatic validation to avoid costly API calls.
|
||||||
|
* Must be triggered explicitly by the user.
|
||||||
|
*/
|
||||||
|
export async function validateProviderManual(
|
||||||
|
plan: ProviderValidationPlan,
|
||||||
|
contextOptions: { t: ComposerTranslation },
|
||||||
|
callbacks: ProviderValidationCallbacks = {},
|
||||||
|
) {
|
||||||
|
const { manualProviderValidators, config, definition, providerExtra } = plan
|
||||||
|
if (manualProviderValidators.length === 0)
|
||||||
|
return []
|
||||||
|
|
||||||
|
const runContext = {
|
||||||
|
...contextOptions,
|
||||||
|
validationCache: new Map<string, unknown>(),
|
||||||
|
}
|
||||||
|
const { onValidatorError, onValidatorStart, onValidatorSuccess } = callbacks
|
||||||
|
|
||||||
|
let providerInstance: ProviderInstance
|
||||||
|
try {
|
||||||
|
providerInstance = await definition.createProvider(config as any)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return manualProviderValidators.map(v => ({
|
||||||
|
id: v.id,
|
||||||
|
label: v.name,
|
||||||
|
status: 'invalid' as ProviderValidationStepStatus,
|
||||||
|
reason: errorMessageFrom(error),
|
||||||
|
kind: 'provider' as ProviderValidationStepKind,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps: ProviderValidationStep[] = manualProviderValidators.map(v => ({
|
||||||
|
id: v.id,
|
||||||
|
label: v.name,
|
||||||
|
status: 'idle' as ProviderValidationStepStatus,
|
||||||
|
reason: '',
|
||||||
|
kind: 'provider' as ProviderValidationStepKind,
|
||||||
|
}))
|
||||||
|
|
||||||
|
await Promise.all(manualProviderValidators.map(async (validatorDefinition, index) => {
|
||||||
|
const step = steps[index]
|
||||||
|
step.status = 'validating'
|
||||||
|
step.reason = ''
|
||||||
|
onValidatorStart?.({ kind: 'provider', index, step })
|
||||||
|
try {
|
||||||
|
const result = await validatorDefinition.validator(config, providerInstance as any, providerExtra as any, runContext)
|
||||||
|
step.status = result.valid ? 'valid' : 'invalid'
|
||||||
|
step.reason = result.valid ? '' : result.reason
|
||||||
|
onValidatorSuccess?.({ kind: 'provider', index, step, result })
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
step.status = 'invalid'
|
||||||
|
step.reason = errorMessageFrom(error)
|
||||||
onValidatorError?.({ kind: 'provider', index, step, error })
|
onValidatorError?.({ kind: 'provider', index, step, error })
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -147,6 +147,15 @@ export interface ProviderMetadata {
|
|||||||
reason: string
|
reason: string
|
||||||
valid: boolean
|
valid: boolean
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Run only the manual-only validators. Returns validation result.
|
||||||
|
* Only available when the provider has manual validators.
|
||||||
|
*/
|
||||||
|
runManualValidation?: (config: Record<string, unknown>) => Promise<{
|
||||||
|
errors: unknown[]
|
||||||
|
reason: string
|
||||||
|
valid: boolean
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
transcriptionFeatures?: {
|
transcriptionFeatures?: {
|
||||||
supportsGenerate: boolean
|
supportsGenerate: boolean
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { ProviderMetadata } from '../providers'
|
|||||||
import { listModels } from '@xsai/model'
|
import { listModels } from '@xsai/model'
|
||||||
|
|
||||||
import { isModelProvider } from '../../libs/providers/types'
|
import { isModelProvider } from '../../libs/providers/types'
|
||||||
import { getValidatorsOfProvider, validateProvider } from '../../libs/providers/validators/run'
|
import { getValidatorsOfProvider, validateProvider, validateProviderManual } from '../../libs/providers/validators/run'
|
||||||
|
|
||||||
function getCategoryFromTasks(tasks: string[]): ProviderMetadata['category'] {
|
function getCategoryFromTasks(tasks: string[]): ProviderMetadata['category'] {
|
||||||
if (tasks.some(task => ['speech-to-text', 'automatic-speech-recognition', 'asr', 'stt'].includes(task.toLowerCase()))) {
|
if (tasks.some(task => ['speech-to-text', 'automatic-speech-recognition', 'asr', 'stt'].includes(task.toLowerCase()))) {
|
||||||
@@ -100,6 +100,8 @@ export function convertProviderDefinitionToMetadata(
|
|||||||
const keyExtractor = (input: string): string => input
|
const keyExtractor = (input: string): string => input
|
||||||
const category = getCategoryFromTasks(definition.tasks)
|
const category = getCategoryFromTasks(definition.tasks)
|
||||||
const schemaDefaults = extractSchemaDefaults(definition, t)
|
const schemaDefaults = extractSchemaDefaults(definition, t)
|
||||||
|
const allValidators = (definition.validators?.validateProvider || []).map(creator => creator({ t }))
|
||||||
|
const hasManualValidators = allValidators.some(v => v.manualOnly)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: definition.id,
|
id: definition.id,
|
||||||
@@ -194,6 +196,8 @@ export function convertProviderDefinitionToMetadata(
|
|||||||
|
|
||||||
// Run full validation pipeline (config + provider validators) only when required.
|
// Run full validation pipeline (config + provider validators) only when required.
|
||||||
// This preserves strict config checks while avoiding unnecessary network checks.
|
// This preserves strict config checks while avoiding unnecessary network checks.
|
||||||
|
// NOTICE: manualOnly validators (e.g. chat completion probes) are already excluded
|
||||||
|
// from the plan's providerValidators by getValidatorsOfProvider.
|
||||||
if (plan.shouldValidate) {
|
if (plan.shouldValidate) {
|
||||||
await validateProvider(plan, { t })
|
await validateProvider(plan, { t })
|
||||||
const invalidSteps = plan.steps.filter(step => step.status === 'invalid')
|
const invalidSteps = plan.steps.filter(step => step.status === 'invalid')
|
||||||
@@ -230,6 +234,26 @@ export function convertProviderDefinitionToMetadata(
|
|||||||
await validateProvider(plan, { t })
|
await validateProvider(plan, { t })
|
||||||
return buildConfigValidationResult(plan)
|
return buildConfigValidationResult(plan)
|
||||||
},
|
},
|
||||||
|
runManualValidation: hasManualValidators
|
||||||
|
? async (config) => {
|
||||||
|
const plan = getValidatorsOfProvider({
|
||||||
|
definition,
|
||||||
|
config,
|
||||||
|
schemaDefaults,
|
||||||
|
contextOptions: { t },
|
||||||
|
})
|
||||||
|
const steps = await validateProviderManual(plan, { t })
|
||||||
|
const invalidSteps = steps.filter(step => step.status === 'invalid')
|
||||||
|
if (invalidSteps.length === 0) {
|
||||||
|
return { errors: [], reason: '', valid: true }
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
errors: invalidSteps.map(step => new Error(step.reason || `${step.id} is invalid`)),
|
||||||
|
reason: invalidSteps.map(step => step.reason).filter(Boolean).join('; '),
|
||||||
|
valid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
transcriptionFeatures: definition.capabilities?.transcription
|
transcriptionFeatures: definition.capabilities?.transcription
|
||||||
? {
|
? {
|
||||||
|
|||||||
Reference in New Issue
Block a user