Parcourir la source

Simplify speaking API env config

jimmylee il y a 2 mois
Parent
commit
5dc33b3f40

+ 42 - 12
scripts/test-speaking-api-config.mjs

@@ -4,31 +4,61 @@ import ts from 'typescript'
 
 const sourceUrl = new URL('../src/views/Editor/EnglishSpeaking/services/speakingApiConfig.ts', import.meta.url)
 const source = await readFile(sourceUrl, 'utf8')
-const compiled = ts.transpileModule(source, {
-  compilerOptions: {
-    module: ts.ModuleKind.ESNext,
-    target: ts.ScriptTarget.ES2020,
-  },
-}).outputText
 
-const mod = await import(`data:text/javascript,${encodeURIComponent(compiled)}`)
+async function loadConfig(env = {}) {
+  const compiled = ts.transpileModule(source.replaceAll('import.meta.env', JSON.stringify(env)), {
+    compilerOptions: {
+      module: ts.ModuleKind.ESNext,
+      target: ts.ScriptTarget.ES2020,
+    },
+  }).outputText
+
+  return import(`data:text/javascript,${encodeURIComponent(compiled)}#${Math.random()}`)
+}
+
+const defaults = await loadConfig()
 
 assert.equal(
-  mod.getSpeakingApiHost({}),
+  defaults.FALLBACK_SPEAKING_API_HOST,
   'https://ppt-english-speaking-api.cocorobo.cn',
 )
 
 assert.equal(
-  mod.getSpeakingApiBaseUrl({}),
+  defaults.SPEAKING_DIALOGUE_API_BASE_URL,
   'https://ppt-english-speaking-api.cocorobo.cn/api/speaking/dialogue',
 )
 
 assert.equal(
-  mod.getSpeakingApiBaseUrl({ VITE_SPEAKING_API_HOST: 'https://example.com/' }),
-  'https://example.com/api/speaking/dialogue',
+  defaults.SPEAKING_CONFIG_API_BASE_URL,
+  'https://ppt-english-speaking-api.cocorobo.cn/api/speaking/config',
 )
 
 assert.equal(
-  mod.buildSpeakingWsUrl('/speak-stream'),
+  defaults.SPEAKING_TRANSPORT,
+  'websocket',
+)
+
+assert.equal(
+  defaults.buildSpeakingWsUrl('/speak-stream'),
   'wss://ppt-english-speaking-api.cocorobo.cn/api/speaking/dialogue/speak-stream',
 )
+
+const custom = await loadConfig({
+  VITE_SPEAKING_API_HOST: 'https://example.com/',
+  VITE_SPEAKING_TRANSPORT: 'http',
+})
+
+assert.equal(
+  custom.SPEAKING_DIALOGUE_API_BASE_URL,
+  'https://example.com/api/speaking/dialogue',
+)
+
+assert.equal(
+  custom.SPEAKING_CONFIG_API_BASE_URL,
+  'https://example.com/api/speaking/config',
+)
+
+assert.equal(
+  custom.SPEAKING_TRANSPORT,
+  'http',
+)

+ 2 - 1
src/services/speaking.ts

@@ -1,6 +1,7 @@
 import type { TopicDiscussionConfig } from '@/types/englishSpeaking'
+import { SPEAKING_CONFIG_API_BASE_URL } from '@/views/Editor/EnglishSpeaking/services/speakingApiConfig'
 
-const API_BASE = 'http://localhost:8000/api/speaking/config'
+const API_BASE = SPEAKING_CONFIG_API_BASE_URL
 
 export interface SpeakingConfigRecord {
   id: string

+ 4 - 4
src/views/Editor/EnglishSpeaking/preview/DialogueChatView.vue

@@ -544,7 +544,7 @@ import { useAudioRecorder } from '../composables/useAudioRecorder'
 import { useAudioPlayer } from '../composables/useAudioPlayer'
 import TaskHintModal from './TaskHintModal.vue'
 import { createDialogueApi } from '../services/llmService'
-import { getSpeakTransport } from '../services/speakingApiConfig'
+import { SPEAKING_TRANSPORT } from '../services/speakingApiConfig'
 
 // ─────────────────────────────────────────────
 // Props / Emits
@@ -584,8 +584,8 @@ const emit = defineEmits<{
 const MAX_RECORDING_SECONDS = 10
 
 // 学生录音上送方式:'websocket'(默认,流式)或 'http'(单次 POST /speak)。
-// 由环境变量 VITE_SPEAKING_TRANSPORT 控制;详见 speakingApiConfig.getSpeakTransport。
-const speakTransport = getSpeakTransport()
+// 由环境变量 VITE_SPEAKING_TRANSPORT 控制;未配置时默认 websocket。
+const speakTransport = SPEAKING_TRANSPORT
 
 const SILENCE_HINTS = [
   'You could say: "I really like pandas because they are so cute!"',
@@ -731,7 +731,7 @@ async function handleStartRecording() {
 
   try {
     await recorder.startRecording(startAbortController.signal)
-    // 学生录音上送方式由 speakingApiConfig.getSpeakTransport() 决定:
+    // 学生录音上送方式由 speakingApiConfig.SPEAKING_TRANSPORT 决定:
     // - websocket(默认):开 WS 流式推 PCM;失败由 useDialogueEngine 暴露 error,用户点"重试"才走 HTTP。
     // - http:跳过 WS,handleFinishRecording 直接走 sendStudentMessage → /speak。
     if (speakTransport === 'websocket') {

+ 2 - 2
src/views/Editor/EnglishSpeaking/services/llmService.ts

@@ -9,9 +9,9 @@ import type {
   DialogueReport,
   SentenceEvaluation,
 } from '@/types/englishSpeaking'
-import { getSpeakingApiBaseUrl } from './speakingApiConfig'
+import { SPEAKING_DIALOGUE_API_BASE_URL } from './speakingApiConfig'
 
-const API_BASE = getSpeakingApiBaseUrl()
+const API_BASE = SPEAKING_DIALOGUE_API_BASE_URL
 
 export class DialogueApiError extends Error {
   status: number

+ 10 - 31
src/views/Editor/EnglishSpeaking/services/speakingApiConfig.ts

@@ -1,39 +1,18 @@
-export const DEFAULT_SPEAKING_API_HOST = 'https://ppt-english-speaking-api.cocorobo.cn'
-export const SPEAKING_API_DIALOGUE_PATH = '/api/speaking/dialogue'
+export const FALLBACK_SPEAKING_API_HOST = 'https://ppt-english-speaking-api.cocorobo.cn'
 
-export type SpeakTransport = 'websocket' | 'http'
+const ENV_SPEAKING_API_HOST = import.meta.env.VITE_SPEAKING_API_HOST?.trim()
+const SPEAKING_API_HOST = (ENV_SPEAKING_API_HOST || FALLBACK_SPEAKING_API_HOST).replace(/\/+$/, '')
 
-/** 学生录音上送方式:默认 websocket(流式 ASR + LLM)。
- *  WS 失败时不再自动降级 HTTP——错误暴露给用户,由用户主动点"重试"按钮走 HTTP 重发。 */
-export const DEFAULT_SPEAK_TRANSPORT: SpeakTransport = 'websocket'
+export const SPEAKING_DIALOGUE_API_BASE_URL = `${SPEAKING_API_HOST}/api/speaking/dialogue`
+export const SPEAKING_CONFIG_API_BASE_URL = `${SPEAKING_API_HOST}/api/speaking/config`
 
-type SpeakingApiEnv = {
-  readonly [key: string]: string | boolean | undefined
-  VITE_SPEAKING_API_HOST?: string
-  VITE_SPEAKING_TRANSPORT?: string
-}
+export type SpeakTransport = 'websocket' | 'http'
 
-function normalizeHost(host: string): string {
-  return host.replace(/\/+$/, '')
-}
+const ENV_SPEAKING_TRANSPORT = import.meta.env.VITE_SPEAKING_TRANSPORT?.trim().toLowerCase()
 
-export function getSpeakingApiHost(env: SpeakingApiEnv | undefined = import.meta.env): string {
-  const configuredHost = env?.VITE_SPEAKING_API_HOST?.trim()
-  return normalizeHost(configuredHost || DEFAULT_SPEAKING_API_HOST)
-}
+export const SPEAKING_TRANSPORT: SpeakTransport = ENV_SPEAKING_TRANSPORT === 'http' ? 'http' : 'websocket'
 
-export function getSpeakingApiBaseUrl(env: SpeakingApiEnv | undefined = import.meta.env): string {
-  return `${getSpeakingApiHost(env)}${SPEAKING_API_DIALOGUE_PATH}`
-}
-
-export function buildSpeakingWsUrl(path: string, env: SpeakingApiEnv | undefined = import.meta.env): string {
+export function buildSpeakingWsUrl(path: string): string {
   const normalizedPath = path.startsWith('/') ? path : `/${path}`
-  return getSpeakingApiBaseUrl(env).replace(/^http/, 'ws') + normalizedPath
-}
-
-export function getSpeakTransport(env: SpeakingApiEnv | undefined = import.meta.env): SpeakTransport {
-  const v = env?.VITE_SPEAKING_TRANSPORT?.trim()?.toLowerCase()
-  if (v === 'http') return 'http'
-  if (v === 'websocket' || v === 'ws') return 'websocket'
-  return DEFAULT_SPEAK_TRANSPORT
+  return `${SPEAKING_DIALOGUE_API_BASE_URL.replace(/^http/, 'ws')}${normalizedPath}`
 }