Guest User

a

a guest
Aug 23rd, 2025
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <script setup>
  2. import { ref, computed } from 'vue'
  3. import axios from 'axios'
  4.  
  5. const captureState = ref('idle') // idle | recording | analyzing
  6. const showReviewModal = ref(false)
  7. const transcript = ref('')
  8. const showTranscript = ref(false)
  9. const tasks = ref([]) // {id, title, selected}
  10. const recordingId = ref(null)
  11. const errorMessage = ref('')
  12. const isSending = ref(false)
  13.  
  14. const statusText = computed(() => {
  15.     switch (captureState.value) {
  16.         case 'recording': return 'Recording... Tap to stop'
  17.         case 'analyzing': return 'Analyzing your thoughts...'
  18.         default:          return 'Tap to start recording'
  19.     }
  20. })
  21. const selectedCount = computed(() => tasks.value.filter(t => t.selected).length)
  22.  
  23. let mediaStream = null
  24. let mediaRecorder = null
  25. let chunks = []
  26.  
  27. async function handleCapture () {
  28.     if (captureState.value === 'idle') {
  29.         await startRecording()
  30.     } else if (captureState.value === 'recording') {
  31.         await stopRecording()
  32.     }
  33. }
  34.  
  35. async function startRecording () {
  36.     errorMessage.value = ''
  37.     try {
  38.         if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  39.             throw new Error('Audio recording requires HTTPS')
  40.         }
  41.         if (!navigator.mediaDevices?.getUserMedia) {
  42.             throw new Error('Audio recording not supported in this browser')
  43.         }
  44.  
  45.         mediaStream = await navigator.mediaDevices.getUserMedia({
  46.             audio: {
  47.                 echoCancellation: true,
  48.                 noiseSuppression: true,
  49.                 autoGainControl: true
  50.             },
  51.             video: false
  52.         })
  53.  
  54.         const mimeType = getSupportedAudioType()
  55.         mediaRecorder = new MediaRecorder(mediaStream, { mimeType })
  56.         chunks = []
  57.  
  58.         mediaRecorder.ondataavailable = (e) => {
  59.             if (e.data && e.data.size > 0) chunks.push(e.data)
  60.         }
  61.  
  62.         mediaRecorder.onstop = async () => {
  63.             try {
  64.                 const blob = new Blob(chunks, { type: mimeType })
  65.                 if (!blob || blob.size === 0) throw new Error('No audio data recorded')
  66.  
  67.                 captureState.value = 'analyzing'
  68.                 await sendRecording(blob)
  69.             } catch (err) {
  70.                 errorMessage.value = err?.message || String(err)
  71.                 captureState.value = 'idle'
  72.             } finally {
  73.                 cleanupMedia()
  74.             }
  75.         }
  76.  
  77.         mediaRecorder.start()
  78.         captureState.value = 'recording'
  79.     } catch (err) {
  80.         errorMessage.value = err?.message || String(err)
  81.         cleanupMedia()
  82.         captureState.value = 'idle'
  83.     }
  84. }
  85.  
  86. async function stopRecording () {
  87.     try {
  88.         if (mediaRecorder && mediaRecorder.state !== 'inactive') {
  89.             mediaRecorder.stop()
  90.         } else {
  91.             cleanupMedia()
  92.             captureState.value = 'idle'
  93.         }
  94.     } catch (err) {
  95.         errorMessage.value = 'Error stopping recording: ' + (err?.message || String(err))
  96.         cleanupMedia()
  97.         captureState.value = 'idle'
  98.     }
  99. }
  100.  
  101. function cleanupMedia () {
  102.     try {
  103.         mediaRecorder?.stop?.()
  104.     } catch {}
  105.     mediaRecorder = null
  106.     chunks = []
  107.  
  108.     if (mediaStream) {
  109.         try {
  110.             mediaStream.getTracks().forEach(t => {
  111.                 try { t.stop() } catch {}
  112.             })
  113.         } catch {}
  114.     }
  115.     mediaStream = null
  116. }
  117.  
  118. function getSupportedAudioType () {
  119.     const types = [
  120.         'audio/webm;codecs=opus',
  121.         'audio/webm',
  122.         'audio/ogg;codecs=opus',
  123.         'audio/ogg',
  124.         'audio/mp4',
  125.         'audio/mpeg'
  126.     ]
  127.     for (const t of types) {
  128.         if (MediaRecorder.isTypeSupported?.(t)) return t
  129.     }
  130.     return ''
  131. }
  132.  
  133. async function sendRecording (blob) {
  134.     if (!blob) return
  135.     isSending.value = true
  136.     try {
  137.         const fileName = pickFileNameFromType(blob.type)
  138.         const formData = new FormData()
  139.         const file = new File([blob], fileName, { type: blob.type || 'application/octet-stream' })
  140.         formData.append('audio', file)
  141.  
  142.         const res = await axios.post('/recordings', formData)
  143.         recordingId.value = res.data.recording_id
  144.         transcript.value = res.data.transcript
  145.         showTranscript.value = false
  146.         tasks.value = (res.data.tasks || []).map(t => ({ ...t, selected: true }))
  147.         showReviewModal.value = true
  148.     } catch (e) {
  149.         errorMessage.value = e?.response?.data?.message || e?.message || 'Upload failed'
  150.     } finally {
  151.         isSending.value = false
  152.         captureState.value = 'idle'
  153.     }
  154. }
  155.  
  156. function pickFileNameFromType (type) {
  157.     if (type.includes('webm')) return 'recording.webm'
  158.     if (type.includes('ogg')) return 'recording.ogg'
  159.     if (type.includes('mp4') || type.includes('mpeg')) return 'recording.m4a'
  160.     return 'recording.bin'
  161. }
  162.  
  163. async function addTasks () {
  164.     if (!recordingId.value) return
  165.     isSending.value = true
  166.     try {
  167.         await axios.post(`/recordings/${recordingId.value}/tasks`, { tasks: tasks.value })
  168.         window.location.href = '/tasks'
  169.     } catch (e) {
  170.         errorMessage.value = e.response?.data?.message || e.message
  171.     } finally {
  172.         isSending.value = false
  173.     }
  174. }
  175.  
  176. function resetRecording () {
  177.     showReviewModal.value = false
  178.     transcript.value = ''
  179.     tasks.value = []
  180.     recordingId.value = null
  181.     errorMessage.value = ''
  182.     showTranscript.value = false
  183. }
  184.  
  185. document.addEventListener('visibilitychange', () => {
  186.     if (document.hidden && captureState.value === 'recording') {
  187.         stopRecording()
  188.     }
  189. })
  190. window.addEventListener('pagehide', () => {
  191.     if (captureState.value === 'recording') {
  192.         stopRecording()
  193.     } else {
  194.         cleanupMedia()
  195.     }
  196. })
  197. </script>
Advertisement
Add Comment
Please, Sign In to add comment