viral-transcript-presenter.service.ts 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { Injectable } from '@angular/core';
  2. import { DouyinTranscriptJob, DouyinTranscriptStatus } from '../models/douyin-insight.model';
  3. @Injectable({ providedIn: 'root' })
  4. export class ViralTranscriptPresenterService {
  5. private readonly processingStatuses = new Set<DouyinTranscriptStatus>([
  6. 'queued',
  7. 'resolving_detail',
  8. 'selecting_media',
  9. 'downloading_media',
  10. 'extracting_audio',
  11. 'submitting_provider',
  12. 'polling_provider',
  13. 'pending',
  14. ]);
  15. private readonly labels: Record<DouyinTranscriptStatus, string> = {
  16. queued: '排队中',
  17. resolving_detail: '获取视频详情',
  18. selecting_media: '选择媒体',
  19. downloading_media: '下载媒体',
  20. extracting_audio: '提取音频',
  21. submitting_provider: '提交转写',
  22. polling_provider: '等待结果',
  23. pending: '处理中',
  24. completed: '已完成',
  25. failed: '失败',
  26. needs_media: '缺少可转写媒体',
  27. needs_media_processing: '需要媒体处理环境',
  28. needs_provider_config: '服务未配置',
  29. };
  30. canStart(analysis: { transcript?: string; transcriptJob?: DouyinTranscriptJob } | null | undefined, busy: boolean): boolean {
  31. if (!analysis || busy || !!analysis.transcript) return false;
  32. const status = analysis.transcriptJob?.status;
  33. return !status
  34. || status === 'failed'
  35. || status === 'needs_media'
  36. || status === 'needs_media_processing'
  37. || status === 'needs_provider_config';
  38. }
  39. statusLabel(job: DouyinTranscriptJob | undefined): string {
  40. if (!job) return '未提交';
  41. return this.labels[job.status] || job.stageLabel || '未知状态';
  42. }
  43. primaryMessage(job: DouyinTranscriptJob | undefined): string {
  44. if (!job) return '';
  45. return this.message(job);
  46. }
  47. isProcessing(job: DouyinTranscriptJob | undefined): boolean {
  48. return !!job && this.processingStatuses.has(job.status);
  49. }
  50. shouldWarn(job: DouyinTranscriptJob | undefined): boolean {
  51. return !!job && job.status !== 'completed' && !this.isProcessing(job);
  52. }
  53. toastType(job: DouyinTranscriptJob): 'success' | 'warn' {
  54. return this.isProcessing(job) || job.status === 'completed' ? 'success' : 'warn';
  55. }
  56. message(job: DouyinTranscriptJob): string {
  57. if (job.stageLabel && this.isProcessing(job)) return job.stageLabel;
  58. if (job.status === 'pending' || job.status === 'queued') return '逐字稿任务已提交,稍后可刷新结果';
  59. if (job.status === 'completed') return '逐字稿已回填';
  60. if (job.status === 'needs_media') return '未找到可转写媒体,请确认视频详情能返回音频或视频地址';
  61. if (job.status === 'needs_media_processing') return job.errorMessage || '需要可用的媒体处理环境,请确认本地 ffmpeg 可用';
  62. if (job.status === 'needs_provider_config') {
  63. return job.warnings?.[0] || '未读取到转写服务配置,请检查 VOC_TOKEN、TRANSCRIPTION_VOC_TOKEN 或 VOICE_TOKEN';
  64. }
  65. return job.errorMessage || job.warnings?.[0] || '逐字稿任务失败,可修复配置后重新提交';
  66. }
  67. }