speaking.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import type { ArticleReadingConfig } from '@/types/articleReading'
  2. import type { TopicDiscussionConfig } from '@/types/englishSpeaking'
  3. import { SPEAKING_CONFIG_API_BASE_URL, SPEAKING_DIALOGUE_API_BASE_URL } from '@/views/Editor/EnglishSpeaking/services/speakingApiConfig'
  4. const API_BASE = SPEAKING_CONFIG_API_BASE_URL
  5. /** 话题讨论与文章朗读共用同一套 config endpoint,靠 config.type 判别。 */
  6. export type SpeakingActivityConfig = TopicDiscussionConfig | ArticleReadingConfig
  7. export interface SpeakingConfigRecord<T extends SpeakingActivityConfig = SpeakingActivityConfig> {
  8. id: string
  9. config: T
  10. ownerUid?: string | null
  11. }
  12. async function parse<T>(res: Response): Promise<T> {
  13. if (!res.ok) {
  14. const detail = await res.text().catch(() => '')
  15. throw new Error(`[${res.status}] ${detail || res.statusText}`)
  16. }
  17. return res.json() as Promise<T>
  18. }
  19. /** 新建口语配置(话题讨论或文章朗读) */
  20. export async function createSpeakingConfig<T extends SpeakingActivityConfig>(
  21. config: T,
  22. ownerUid?: string | null,
  23. ): Promise<SpeakingConfigRecord<T>> {
  24. const res = await fetch(API_BASE, {
  25. method: 'POST',
  26. headers: { 'Content-Type': 'application/json' },
  27. body: JSON.stringify({ config, ownerUid: ownerUid ?? null }),
  28. })
  29. return parse<SpeakingConfigRecord<T>>(res)
  30. }
  31. /** 读取口语配置 */
  32. export async function getSpeakingConfig<T extends SpeakingActivityConfig = SpeakingActivityConfig>(
  33. id: string,
  34. ): Promise<SpeakingConfigRecord<T>> {
  35. const res = await fetch(`${API_BASE}/${encodeURIComponent(id)}`)
  36. return parse<SpeakingConfigRecord<T>>(res)
  37. }
  38. /** 更新口语配置 */
  39. export async function updateSpeakingConfig<T extends SpeakingActivityConfig>(
  40. id: string,
  41. config: T,
  42. ): Promise<SpeakingConfigRecord<T>> {
  43. const res = await fetch(`${API_BASE}/${encodeURIComponent(id)}`, {
  44. method: 'PUT',
  45. headers: { 'Content-Type': 'application/json' },
  46. body: JSON.stringify({ config }),
  47. })
  48. return parse<SpeakingConfigRecord<T>>(res)
  49. }
  50. const DIALOGUE_BASE = SPEAKING_DIALOGUE_API_BASE_URL
  51. export interface ClassSessionSummary {
  52. userId: string
  53. sessionId: string
  54. status: 'active' | 'completed' | 'abandoned'
  55. overallStatus: 'ready' | 'generating' | 'failed' | null
  56. currentRound: number
  57. totalRounds: number
  58. overallScore: number | null
  59. dimensions: Record<string, number>
  60. topHighlights: string[]
  61. topImprovements: string[]
  62. createdAt: string | null
  63. completedAt: string | null
  64. }
  65. export interface ListSessionsByConfigResponse {
  66. summaries: ClassSessionSummary[]
  67. }
  68. export async function listSpeakingSessionsByConfig(
  69. configId: string,
  70. userIds: string[],
  71. ): Promise<ListSessionsByConfigResponse> {
  72. const res = await fetch(`${DIALOGUE_BASE}/sessions/by-config`, {
  73. method: 'POST',
  74. headers: { 'Content-Type': 'application/json' },
  75. credentials: 'include',
  76. body: JSON.stringify({ configId, userIds }),
  77. })
  78. return parse<ListSessionsByConfigResponse>(res)
  79. }
  80. export interface ClassSummaryResponse {
  81. bullets: [string, string, string]
  82. generatedAt: string
  83. fromCache: boolean
  84. llmStatus: 'ok' | 'fallback'
  85. }
  86. export async function generateClassSummary(
  87. configId: string,
  88. userIds: string[],
  89. locale: 'zh' | 'en' | 'hk',
  90. ): Promise<ClassSummaryResponse> {
  91. const res = await fetch(`${DIALOGUE_BASE}/sessions/by-config/summary`, {
  92. method: 'POST',
  93. headers: { 'Content-Type': 'application/json' },
  94. credentials: 'include',
  95. body: JSON.stringify({ configId, userIds, locale }),
  96. })
  97. return parse<ClassSummaryResponse>(res)
  98. }