articleReadingModel.ts 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. import type {
  2. ArticleReadingConfig,
  3. ArticleReadingReport,
  4. ArticleReadingTask,
  5. } from '@/types/articleReading'
  6. import type { SpeakingRecommendationTask } from '@/types/englishSpeaking'
  7. export type SpeakingTaskFilter = 'all' | 'topic-discussion' | 'article-reading'
  8. export function buildEmptyArticleConfig(grade: string): ArticleReadingConfig {
  9. return {
  10. type: 'article',
  11. grade,
  12. title: '',
  13. content: '',
  14. practice: { mode: 'time', duration: 10 },
  15. evaluation: { showReport: true },
  16. }
  17. }
  18. export function buildArticleConfig(task: ArticleReadingTask, grade: string): ArticleReadingConfig {
  19. return {
  20. type: 'article',
  21. grade,
  22. title: task.titleEn,
  23. content: task.content,
  24. practice: { mode: 'time', duration: task.durationMinutes },
  25. evaluation: { showReport: true },
  26. }
  27. }
  28. export function filterSpeakingTasks(
  29. tasks: SpeakingRecommendationTask[],
  30. filter: SpeakingTaskFilter,
  31. ): SpeakingRecommendationTask[] {
  32. return filter === 'all' ? tasks : tasks.filter(task => task.taskType === filter)
  33. }
  34. export function scoreToStars(score: number | null | undefined): number {
  35. if (score == null) return 0
  36. return Math.round(Math.min(100, Math.max(0, score)) * 5) / 100
  37. }
  38. /** 最终报告一次性显示:无段落还在生成,且整篇总评已定局(spec §4.1)。 */
  39. export function isFinalReportReady(
  40. report: Pick<ArticleReadingReport, 'paragraphs' | 'overallStatus'>,
  41. ): boolean {
  42. return !report.paragraphs.some(paragraph => paragraph.status === 'generating')
  43. && (report.overallStatus === 'completed'
  44. || report.overallStatus === 'failed'
  45. || report.overallStatus === null)
  46. }
  47. export function pollDelayMs(attempt: number): number {
  48. return Math.min(8000, 1000 * 2 ** Math.max(0, attempt))
  49. }