From 119f85b6123d130edc3f97742682b3e44a15c383 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Sat, 17 Aug 2024 11:46:50 +0800 Subject: [PATCH 01/74] Adding file picker component --- hosting/src/components/FilePicker.tsx | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 hosting/src/components/FilePicker.tsx diff --git a/hosting/src/components/FilePicker.tsx b/hosting/src/components/FilePicker.tsx new file mode 100644 index 00000000..84195985 --- /dev/null +++ b/hosting/src/components/FilePicker.tsx @@ -0,0 +1,62 @@ +import React, {useState} from "react"; + +interface FilePickerProps { + onFileSelect: (file: File) => void; +} + +export default function FilePicker({onFileSelect}: FilePickerProps) { + const [dragActive, setDragActive] = useState(false); + + const handleDrag = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") { + setDragActive(true); + } else if (e.type === "dragleave") { + setDragActive(false); + } + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + onFileSelect(e.dataTransfer.files[0]); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + onFileSelect(e.target.files[0]); + } + }; + + const handleClick = () => { + document.getElementById("fileUploadInput")?.click(); + }; + + return ( +
+ +
+ +

+ e.preventDefault()}> + Click to upload + {" "} + or drag and drop +

+
+
+ ); +} From 3a0dc9a2cc73bca24f96d2b9c10ed774e4307b07 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Sat, 17 Aug 2024 11:47:03 +0800 Subject: [PATCH 02/74] Adding dialog component --- hosting/src/components/Dialog.tsx | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 hosting/src/components/Dialog.tsx diff --git a/hosting/src/components/Dialog.tsx b/hosting/src/components/Dialog.tsx new file mode 100644 index 00000000..bf8e211a --- /dev/null +++ b/hosting/src/components/Dialog.tsx @@ -0,0 +1,50 @@ +import React, {useState, ReactNode} from "react"; + +interface DialogProps { + isOpen: boolean; + onClose: () => void; + title: string; + content: ReactNode; +} + +export default function Dialog({isOpen, onClose, title, content}: DialogProps) { + const [loading, setLoading] = useState(false); + + const handleSubmit = async () => { + setLoading(true); + await dummyHook(); // Replace this with your actual hook + setLoading(false); + onClose(); + }; + + const dummyHook = (): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 2000); // Simulate a delay + }); + }; + + if (!isOpen) return null; + + return ( +
+
+

{title}

+
{content}
+
+ + +
+
+
+ ); +} From 85762afe1df91741fcea84d01c0a12b44d97be6c Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Wed, 21 Aug 2024 15:39:01 +0800 Subject: [PATCH 03/74] Setting status of document based on publish timestamp --- functions/src/models/TanamDocument.ts | 28 +++++++++++++------ .../DocumentType/DocumentTypeGenericList.tsx | 3 +- .../src/components/TogglePublishDocument.tsx | 9 ++++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/functions/src/models/TanamDocument.ts b/functions/src/models/TanamDocument.ts index 642194fa..95f04101 100644 --- a/functions/src/models/TanamDocument.ts +++ b/functions/src/models/TanamDocument.ts @@ -2,12 +2,18 @@ interface DocumentData { [key: string]: unknown; } -export type TanamPublishStatus = "published" | "unpublished" | "scheduled"; +export enum TanamPublishStatus { + Processing = "processing", + Published = "published", + Unpublished = "unpublished", + Scheduled = "scheduled", +} export interface ITanamDocument { data: DocumentData; documentType: string; revision?: number; + status?: TanamPublishStatus; publishedAt?: TimestampType; createdAt?: TimestampType; updatedAt?: TimestampType; @@ -19,6 +25,17 @@ export abstract class TanamDocument { this.data = json.data ?? {}; this.documentType = json.documentType ?? "unknown"; this.publishedAt = json.publishedAt; + + // The status of the document is determined by the publishedAt field + this.status = json.publishedAt + ? json.status === TanamPublishStatus.Scheduled + ? TanamPublishStatus.Scheduled + : TanamPublishStatus.Published + : json.status === TanamPublishStatus.Unpublished + ? TanamPublishStatus.Unpublished + : TanamPublishStatus.Processing; + + this.status = json.status || json.publishedAt ? TanamPublishStatus.Published : TanamPublishStatus.Unpublished; this.revision = json.revision ?? 0; this.createdAt = json.createdAt; this.updatedAt = json.updatedAt; @@ -28,18 +45,11 @@ export abstract class TanamDocument { public data: DocumentData; public documentType: string; public publishedAt?: TimestampType; + public status: TanamPublishStatus; public revision: number; public readonly createdAt?: TimestampType; public readonly updatedAt?: TimestampType; - get status(): TanamPublishStatus { - if (!this.publishedAt) { - return "unpublished"; - } else { - return "published"; - } - } - protected abstract getServerTimestamp(): FieldValueType; toJson(): object { diff --git a/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx b/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx index 770f417f..09d89cea 100644 --- a/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx +++ b/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx @@ -1,6 +1,7 @@ import {Table, TableRowLabel} from "@/components/Table"; import {TanamDocumentClient} from "@/models/TanamDocumentClient"; import {TanamDocumentTypeClient} from "@/models/TanamDocumentTypeClient"; +import {TanamPublishStatus} from "@functions/models/TanamDocument"; import Link from "next/link"; interface TableOverviewGenericProps { @@ -24,7 +25,7 @@ export function DocumentTypeGenericList({documents, documentType}: TableOverview , ])} /> diff --git a/hosting/src/components/TogglePublishDocument.tsx b/hosting/src/components/TogglePublishDocument.tsx index 5a53f14a..6260d845 100644 --- a/hosting/src/components/TogglePublishDocument.tsx +++ b/hosting/src/components/TogglePublishDocument.tsx @@ -1,5 +1,6 @@ import {Button} from "@/components/Button"; import {useTanamDocument} from "@/hooks/useTanamDocuments"; +import {TanamPublishStatus} from "@functions/models/TanamDocument"; import {useParams} from "next/navigation"; export function TogglePublishDocument() { @@ -12,14 +13,18 @@ export function TogglePublishDocument() { } // Toggle the status of the document - return changeStatus(document.status === "published" ? "unpublished" : "published"); + return changeStatus( + document.status === TanamPublishStatus.Unpublished + ? TanamPublishStatus.Unpublished + : TanamPublishStatus.Published, + ); } return ( documentId && ( <> + + {value && ( +
+

Your Recording:

+ + +
+ )} + + ); +} From 794d5830c72f5308a2cf92e556f1a823b772273a Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Mon, 26 Aug 2024 14:33:54 +0700 Subject: [PATCH 39/74] make dynamic title --- hosting/src/app/(protected)/content/article/page.tsx | 9 +++++++-- hosting/src/components/VoiceRecorder.tsx | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 3b14c5e0..ec1d45db 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -3,6 +3,7 @@ import {Button} from "@/components/Button"; import Dialog from "@/components/Dialog"; import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; import FilePicker from "@/components/FilePicker"; +import {VoiceRecorder} from "@/components/VoiceRecorder"; import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; @@ -14,12 +15,13 @@ import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; export default function DocumentTypeDocumentsPage() { + const router = useRouter(); const {data: documentType} = useTanamDocumentType("article"); const {create, error: crudError} = useCrudTanamDocument(); const {data: documents, error: docsError, isLoading} = useTanamDocuments("article"); const [notification, setNotification] = useState(null); - const router = useRouter(); const [isDialogOpen, setIsDialogOpen] = useState(false); + const [audio, setAudio] = useState(""); const {createFromRecording, status} = useGenkitArticle(); useEffect(() => { @@ -65,7 +67,10 @@ export default function DocumentTypeDocumentsPage() { setIsDialogOpen(false)} title={"Tell your story"}> {status === ProcessingState.Ready ? ( - + <> + + + ) : (
diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 258d396c..df2dcc0e 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,6 +1,7 @@ import {useEffect, useRef, useState} from "react"; export interface VoiceRecorderProps { + title?: string; value: string; onChange: (value: string) => void; } @@ -13,7 +14,7 @@ export interface VoiceRecorderProps { * @returns {JSX.Element} The rendered VoiceRecorder component. */ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { - const {value, onChange} = props; + const {title, value, onChange} = props; const [isRecording, setIsRecording] = useState(false); const mediaRecorderRef = useRef(); @@ -78,8 +79,8 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { } return ( -
-

Voice Recorder

+
+ {title &&

{title}

}
+ {isRecording ? ( + + ) : ( + + )}
{value && (
From 2a0482892247c644c7f597bef01211749900717d Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Mon, 26 Aug 2024 15:16:30 +0700 Subject: [PATCH 41/74] add speech recognition --- hosting/src/components/VoiceRecorder.tsx | 66 ++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 10f8b671..e66fd6c3 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -4,21 +4,25 @@ export interface VoiceRecorderProps { title?: string; value: string; onChange: (value: string) => void; + onTranscriptChange?: (transcript: string) => void; } /** - * VoiceRecorder component that allows users to record audio and returns the recorded audio - * as a base64 string. The component is interactive, using Tailwind CSS for styling. + * VoiceRecorder component allows users to record audio, convert it to a base64 string, + * and automatically performs speech recognition to provide a text transcript of the recorded audio. * * @param {VoiceRecorderProps} props - The props for the component. * @returns {JSX.Element} The rendered VoiceRecorder component. */ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { - const {title, value, onChange} = props; + const {title, value, onChange, onTranscriptChange} = props; const [isRecording, setIsRecording] = useState(false); + const [transcript, setTranscript] = useState(""); + const mediaRecorderRef = useRef(); const audioChunksRef = useRef([]); + const recognitionRef = useRef(); /** * Effect to trigger onChange whenever the value prop changes. @@ -29,6 +33,52 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { } }, [value, onChange]); + /** + * Effect hook that sets up the SpeechRecognition instance and its event handlers. + * It handles starting, stopping, and restarting the speech recognition process. + */ + useEffect(() => { + const speechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; + + console.info("speechRecognition :: ", speechRecognition); + + if (speechRecognition) { + recognitionRef.current = new speechRecognition(); + const recognition = recognitionRef.current; + + if (!recognition) return; + + console.info("recognition before :: ", recognition); + + recognition.lang = "en-US"; // Set language for speech recognition + recognition.interimResults = false; // Only return final results + recognition.continuous = true; + + console.info("recognition after :: ", recognition); + + // Handle the event when speech recognition returns results + recognition.onresult = (event: any) => { + const transcript = Array.from(event.results) + .map((result: any) => result[0]) + .map((result) => result.transcript) + .join(""); + + setTranscript(transcript); // Update local state with the transcript + + if (onTranscriptChange) { + onTranscriptChange(transcript); + } + }; + + // Restart speech recognition if it ends while still recording + recognition.onend = () => { + if (isRecording) { + recognition.start(); + } + }; + } + }, [onTranscriptChange, isRecording]); + /** * Starts recording audio using the user's microphone. * It sets up the MediaRecorder, gathers audio data, and pushes it to the audioChunksRef. @@ -58,6 +108,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { }; mediaRecorderRef.current.start(); + recognitionRef.current?.start(); } /** @@ -69,6 +120,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { if (!mediaRecorderRef.current) return; mediaRecorderRef.current.stop(); + recognitionRef.current?.stop(); } /** @@ -76,6 +128,8 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { */ function handleReset() { onChange(""); + setTranscript(""); + recognitionRef.current?.stop(); } return ( @@ -117,6 +171,12 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element {
)} + {transcript && ( +
+

Transcript:

+

{transcript}

+
+ )}
); } From d904d637950a0292125c2241afe9571466f38b0a Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Mon, 26 Aug 2024 16:05:04 +0700 Subject: [PATCH 42/74] fix cant stop transcript --- hosting/global.d.ts | 48 ++++++++++++++++++++++++ hosting/src/components/VoiceRecorder.tsx | 32 ++++++++-------- 2 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 hosting/global.d.ts diff --git a/hosting/global.d.ts b/hosting/global.d.ts new file mode 100644 index 00000000..7dd0a746 --- /dev/null +++ b/hosting/global.d.ts @@ -0,0 +1,48 @@ +interface SpeechRecognition { + start(): void; + stop(): void; + abort(): void; + lang: string; + interimResults: boolean; + maxAlternatives: number; + continuous: boolean; + onaudiostart?: (event: Event) => void; + onsoundstart?: (event: Event) => void; + onspeechstart?: (event: Event) => void; + onspeechend?: (event: Event) => void; + onsoundend?: (event: Event) => void; + onaudioend?: (event: Event) => void; + onresult?: (event: SpeechRecognitionEvent) => void; + onnomatch?: (event: Event) => void; + onerror?: (event: Event) => void; + onstart?: (event: Event) => void; + onend?: (event: Event) => void; +} + +interface SpeechRecognitionEvent { + resultIndex: number; + results: SpeechRecognitionResultList; +} + +interface SpeechRecognitionResultList { + [index: number]: SpeechRecognitionResult; + length: number; + item(index: number): SpeechRecognitionResult; +} + +interface SpeechRecognitionResult { + [index: number]: SpeechRecognitionAlternative; + length: number; + isFinal: boolean; + item(index: number): SpeechRecognitionAlternative; +} + +interface SpeechRecognitionAlternative { + transcript: string; + confidence: number; +} + +interface Window { + SpeechRecognition: typeof SpeechRecognition; + webkitSpeechRecognition: typeof SpeechRecognition; +} diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index e66fd6c3..d5f8d7d9 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -22,7 +22,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const mediaRecorderRef = useRef(); const audioChunksRef = useRef([]); - const recognitionRef = useRef(); + const recognitionRef = useRef(); /** * Effect to trigger onChange whenever the value prop changes. @@ -38,19 +38,19 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * It handles starting, stopping, and restarting the speech recognition process. */ useEffect(() => { - const speechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; - console.info("speechRecognition :: ", speechRecognition); + console.info("SpeechRecognition :: ", SpeechRecognition); - if (speechRecognition) { - recognitionRef.current = new speechRecognition(); + if (SpeechRecognition) { + recognitionRef.current = new SpeechRecognition(); const recognition = recognitionRef.current; if (!recognition) return; console.info("recognition before :: ", recognition); - recognition.lang = "en-US"; // Set language for speech recognition + // recognition.lang = "en-US"; // Set language for speech recognition recognition.interimResults = false; // Only return final results recognition.continuous = true; @@ -69,14 +69,9 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { onTranscriptChange(transcript); } }; - - // Restart speech recognition if it ends while still recording - recognition.onend = () => { - if (isRecording) { - recognition.start(); - } - }; } + + console.info("isRecording :: ", isRecording); }, [onTranscriptChange, isRecording]); /** @@ -85,6 +80,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * When recording stops, it converts the audio chunks to a base64 string and passes it to onChange. */ async function handleStartRecording() { + handleReset(); setIsRecording(true); const stream = await navigator.mediaDevices.getUserMedia({audio: true}); @@ -117,10 +113,12 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { function handleStopRecording() { setIsRecording(false); - if (!mediaRecorderRef.current) return; + if (!mediaRecorderRef.current || !recognitionRef.current) return; + + console.info("handleStopRecording"); mediaRecorderRef.current.stop(); - recognitionRef.current?.stop(); + recognitionRef.current.stop(); } /** @@ -129,7 +127,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { function handleReset() { onChange(""); setTranscript(""); - recognitionRef.current?.stop(); + handleStopRecording(); } return ( @@ -139,7 +137,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { {isRecording ? ( )}
+ {value && (

Your Recording:

From 498de97a3c4dc210e5b8c32af4d6703a983c7115 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 11:16:28 +0700 Subject: [PATCH 46/74] add sound wave animation --- hosting/src/components/VoiceRecorder.tsx | 143 ++++++++++------------- 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 4ccb4be2..4bea4c8b 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,3 +1,4 @@ +import Peaks, {PeaksOptions} from "peaks.js"; import {useEffect, useRef, useState} from "react"; export interface VoiceRecorderProps { @@ -19,15 +20,14 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const [isRecording, setIsRecording] = useState(false); const [transcript, setTranscript] = useState(""); - const [amplitudeArray, setAmplitudeArray] = useState([]); + const [audioUrl, setAudioUrl] = useState(""); const mediaRecorderRef = useRef(); const audioChunksRef = useRef([]); const recognitionRef = useRef(); const streamRef = useRef(null); const audioContextRef = useRef(null); - const analyserRef = useRef(null); - const canvasRef = useRef(null); + const peaksInstanceRef = useRef(null); /** * Effect to trigger onChange whenever the value prop changes. @@ -80,7 +80,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { }, [onTranscriptChange, isRecording]); /** - * Starts recording audio using the user's microphone. + * Starts recording audio using the user"s microphone. * It sets up the MediaRecorder, gathers audio data, and pushes it to the audioChunksRef. * When recording stops, it converts the audio chunks to a base64 string and passes it to onChange. */ @@ -95,13 +95,6 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const audioContext = new AudioContext(); audioContextRef.current = audioContext; - const analyser = audioContext.createAnalyser(); - analyser.fftSize = 2048; - analyserRef.current = analyser; - - const source = audioContext.createMediaStreamSource(stream); - source.connect(analyser); - mediaRecorderRef.current.ondataavailable = (event: BlobEvent) => { audioChunksRef.current.push(event.data); }; @@ -114,21 +107,25 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { console.info("reader result :: ", reader.result); - const base64String = reader.result?.toString().split(",")[1] || ""; + const base64String = reader.result?.toString() || ""; console.info("base64String :: ", base64String); onChange(base64String); + + // Set audio URL for visualization + const url = URL.createObjectURL(audioBlob); + setAudioUrl(url); + + setTimeout(() => { + initSoundWave(); + }, 500); }; + reader.readAsDataURL(audioBlob); audioChunksRef.current = []; - - // Clear amplitude data when recording stops - setAmplitudeArray([]); }; mediaRecorderRef.current.start(); recognitionRef.current?.start(); - - visualizeAmplitude(); } /** @@ -136,7 +133,6 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { */ function handleStopRecording() { setIsRecording(false); - setAmplitudeArray([]); if (!mediaRecorderRef.current || !recognitionRef.current || !streamRef.current) return; @@ -160,75 +156,52 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { function handleReset() { onChange(""); setTranscript(""); + setAudioUrl(""); handleStopRecording(); } - function drawAmplitude(data: Uint8Array) { - const canvas = canvasRef.current; - const analyser = analyserRef.current; - - if (!data || !analyser || !canvas) return; - - analyser.getByteTimeDomainData(data); - - console.info("drawAmplitude canvas :: ", canvas); - console.info("drawAmplitude :: ", data); - - const context = canvas.getContext("2d"); - if (!context) return; - - context.clearRect(0, 0, canvas.width, canvas.height); - - context.lineWidth = 2; - context.strokeStyle = "green"; - - context.beginPath(); - - const sliceWidth = canvas.width / data.length; - let x = 0; - - for (let i = 0; i < data.length; i++) { - const v = data[i] / 128.0; - const y = (v * canvas.height) / 2; - - if (i === 0) { - context.moveTo(x, y); - } else { - context.lineTo(x, y); - } - - x += sliceWidth; - - console.info("drawAmplitude loop :: ", x); - } - - context.lineTo(canvas.width, canvas.height / 2); - context.stroke(); - } - /** - * Visualizes the amplitude of the audio input in real-time. + * Loads the audio buffer and initializes Peak.js with the given options. */ - function visualizeAmplitude() { - const analyser = analyserRef.current; - - if (!analyser) return; - - const bufferLength = analyser.fftSize; - const dataArray = new Uint8Array(bufferLength); - - console.info("visualizeAmplitude bufferLength :: ", bufferLength); - console.info("visualizeAmplitude :: ", dataArray); - - const renderFrame = () => { - drawAmplitude(dataArray); - - if (isRecording) { - requestAnimationFrame(renderFrame); - } - }; + function initSoundWave() { + if (peaksInstanceRef.current) { + peaksInstanceRef.current.destroy(); + } - renderFrame(); + // Peak.js options configuration (https://www.npmjs.com/package/peaks.js/v/0.18.1#configuration) + const options = { + containers: { + overview: document.getElementById("overviewContainer"), + }, + mediaElement: document.getElementById("audio"), + webAudio: { + audioContext: new AudioContext(), + audioBuffer: null, + multiChannel: false, + }, + height: 200, + waveformBuilderOptions: { + scale: 128, + }, + zoomLevels: [128, 256, 512, 1024, 2048], + logger: console.error.bind(console), + emitCueEvents: false, + segmentStartMarkerColor: "#a0a0a0", + segmentEndMarkerColor: "#a0a0a0", + overviewWaveformColor: "rgba(0,0,0,0.2)", + overviewHighlightColor: "grey", + overviewHighlightOffset: 11, + segmentColor: "rgba(255, 161, 39, 1)", + playheadColor: "rgba(0, 0, 0, 1)", + playheadTextColor: "#aaa", + showPlayheadTime: false, + pointMarkerColor: "#FF0000", + axisGridlineColor: "#ccc", + axisLabelColor: "#aaa", + randomizeSegmentColor: true, + } as unknown as PeaksOptions; + + peaksInstanceRef.current = Peaks.init(options); } return ( @@ -258,11 +231,13 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { )}
- - {value && ( + {audioUrl && (

Your Recording:

- +
+
+
+ + <> +
+ +
+ +

Recording...

+ ) : (
-

Recording...

+

Mic's Live!

) : ( - + <> +
+ +
+ +

{!audioUrl ? "Tap & Speak" : "Tap to Try Again"}

+ )}
{audioUrl && ( From df3fffaa412c5990481c27bd556fb13832468ab3 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 13:55:07 +0700 Subject: [PATCH 50/74] integrate voice recorder with ai --- .../app/(protected)/content/article/page.tsx | 21 +++++- hosting/src/components/Dialog.tsx | 66 +++++++++++-------- hosting/src/plugins/fileUpload.ts | 27 ++++++++ 3 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 hosting/src/plugins/fileUpload.ts diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 5e02ec3e..81a713bf 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -7,15 +7,18 @@ import {VoiceRecorder} from "@/components/VoiceRecorder"; import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; +import {useAuthentication} from "@/hooks/useAuthentication"; import {ProcessingState, useGenkitArticle} from "@/hooks/useGenkitArticle"; import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; import {useCrudTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; import {UserNotification} from "@/models/UserNotification"; +import {base64ToFile} from "@/plugins/fileUpload"; import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; export default function DocumentTypeDocumentsPage() { const router = useRouter(); + const {authUser} = useAuthentication(); const {data: documentType} = useTanamDocumentType("article"); const {create, error: crudError} = useCrudTanamDocument(); const {data: documents, error: docsError, isLoading} = useTanamDocuments("article"); @@ -36,6 +39,17 @@ export default function DocumentTypeDocumentsPage() { router.push(`article/${id}`); } + async function submitAudio() { + if (!audio) return; + + console.info("submitAudio audio :: ", audio); + + const file = base64ToFile(audio, `audio-${authUser?.uid}`); + await handleFileSelect(file); + + console.info("submitAudio file :: ", file); + } + async function handleFileSelect(file: File) { console.log(file); const articleId = await createFromRecording(file); @@ -66,7 +80,12 @@ export default function DocumentTypeDocumentsPage() { )} - setIsDialogOpen(false)} title={"Tell your story"}> + setIsDialogOpen(false)} + title={"Tell your story"} + > {status === ProcessingState.Ready ? ( <> diff --git a/hosting/src/components/Dialog.tsx b/hosting/src/components/Dialog.tsx index 7c4c0464..0ed4001b 100644 --- a/hosting/src/components/Dialog.tsx +++ b/hosting/src/components/Dialog.tsx @@ -1,29 +1,36 @@ -import React, {useState, ReactNode} from "react"; +import {ReactNode, useEffect, useState} from "react"; interface DialogProps { isOpen: boolean; - onClose: () => void; + isLoading?: boolean; title: string; children: ReactNode; + onClose?: () => void; + onSubmit?: () => void; + onLoadingChange?: (isLoading: boolean) => void; } -export default function Dialog({isOpen, onClose, title, children}: DialogProps) { +export default function Dialog(props: DialogProps) { + const {isOpen, title, children, isLoading, onClose, onSubmit, onLoadingChange} = props; const [loading, setLoading] = useState(false); - const handleSubmit = async () => { - setLoading(true); - await dummyHook(); // Replace this with your actual hook - setLoading(false); - onClose(); - }; - - const dummyHook = (): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 2000); // Simulate a delay - }); - }; + /** + * Effect to trigger onLoadingChange whenever the loading prop changes. + */ + useEffect(() => { + if (onLoadingChange) { + onLoadingChange(loading); + } + }, [loading, onLoadingChange]); + + /** + * Effect to trigger setLoading. + */ + useEffect(() => { + setLoading(isLoading ?? false); + + return () => setLoading(false); + }, [isLoading]); if (!isOpen) return null; @@ -33,16 +40,21 @@ export default function Dialog({isOpen, onClose, title, children}: DialogProps)

{title}

{children}
- - + {onClose && ( + + )} + + {onSubmit && ( + + )}
diff --git a/hosting/src/plugins/fileUpload.ts b/hosting/src/plugins/fileUpload.ts new file mode 100644 index 00000000..48d4cfcb --- /dev/null +++ b/hosting/src/plugins/fileUpload.ts @@ -0,0 +1,27 @@ +/** + * Convert a base64-encoded string to a File. + * + * @param {string} base64String - The base64-encoded string. + * @param {string} fileName - The name of the resulting File. + * @returns {File} The resulting File object containing the file data. + */ +export function base64ToFile(base64String: string, fileName: string): File { + // Split the base64 string into metadata and data parts + const [metadata, data] = base64String.split(","); + + // Extract MIME type from metadata + const mimeType = metadata.split(":")[1].split(";")[0]; + + // Decode the base64 string into binary data + const binaryString = window.atob(data); + const len = binaryString.length; + const bytes = new Uint8Array(len); + + // Convert binary string to byte array + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // Create and return a File object from the byte array + return new File([bytes], fileName, {type: mimeType}); +} From bd729c509a3dc2429c6455ca4cb7b1fb2efe96b3 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 14:00:36 +0700 Subject: [PATCH 51/74] fix lint error --- hosting/src/plugins/fileUpload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/src/plugins/fileUpload.ts b/hosting/src/plugins/fileUpload.ts index 48d4cfcb..ba5b512c 100644 --- a/hosting/src/plugins/fileUpload.ts +++ b/hosting/src/plugins/fileUpload.ts @@ -3,7 +3,7 @@ * * @param {string} base64String - The base64-encoded string. * @param {string} fileName - The name of the resulting File. - * @returns {File} The resulting File object containing the file data. + * @return {File} The resulting File object containing the file data. */ export function base64ToFile(base64String: string, fileName: string): File { // Split the base64 string into metadata and data parts From 4b81002b82be7ed4bdbdaa8a40eb2d2e60f99d32 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 14:17:47 +0700 Subject: [PATCH 52/74] still getting the error build --- hosting/package-lock.json | 158 ++++++++++++++++++ hosting/package.json | 1 + .../app/(protected)/content/article/page.tsx | 55 +++--- hosting/src/components/VoiceRecorder.tsx | 1 + 4 files changed, 189 insertions(+), 26 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index b9a748ed..16fde9f2 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -44,6 +44,7 @@ "@iconify-json/ic": "^1.1.17", "@iconify-json/ri": "^1.1.20", "@next/eslint-plugin-next": "^14.2.3", + "@opentelemetry/exporter-jaeger": "^1.25.1", "@tailwindcss/typography": "^0.5.13", "@tiptap/extension-link": "^2.5.5", "@types/highlight.js": "^10.1.0", @@ -1735,6 +1736,24 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, + "node_modules/@opentelemetry/exporter-jaeger": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.25.1.tgz", + "integrity": "sha512-6/HwzrwUx0fpkFXrouF0IJp+hpN8xkx8RqEk+BZfeoMAHydpyigyYsKyAtAZRwfJe45WWJbJUqoK8aBjiC9iLQ==", + "dev": true, + "dependencies": { + "@opentelemetry/core": "1.25.1", + "@opentelemetry/sdk-trace-base": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1", + "jaeger-client": "^3.15.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { "version": "0.49.1", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.49.1.tgz", @@ -4601,6 +4620,15 @@ } } }, + "node_modules/ansi-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz", + "integrity": "sha512-bF6xLaZBLpOQzgYUtYEhJx090nPSZk1BQ/q2oyBK9aMMcJHzx9uXGCjI2Y+LebsN4Jwoykr0V9whbPiogdyHoQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5091,6 +5119,21 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/bufrw": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/bufrw/-/bufrw-1.4.0.tgz", + "integrity": "sha512-sWm8iPbqvL9+5SiYxXH73UOkyEbGQg7kyHQmReF89WJHQJw2eV4P/yZ0E+b71cczJ4pPobVhXxgQcmfSTgGHxQ==", + "dev": true, + "dependencies": { + "ansi-color": "^0.2.1", + "error": "^7.0.0", + "hexer": "^1.5.0", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.x" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -5816,6 +5859,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha512-UtVv4l5MhijsYUxPJo4390gzfZvAnTHreNnDjnTZaKIiZ/SemXxAhBkYSKtWa5RtBXbLP8tMgn/n0RUa/H7jXw==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -7812,6 +7865,24 @@ "node": ">= 0.4" } }, + "node_modules/hexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/hexer/-/hexer-1.5.0.tgz", + "integrity": "sha512-dyrPC8KzBzUJ19QTIo1gXNqIISRXQ0NwteW6OeQHRN4ZuZeHkdODfj0zHBdOlHbRY8GqbqK57C9oWSvQZizFsg==", + "dev": true, + "dependencies": { + "ansi-color": "^0.2.1", + "minimist": "^1.1.0", + "process": "^0.10.0", + "xtend": "^4.0.0" + }, + "bin": { + "hexer": "cli.js" + }, + "engines": { + "node": ">= 0.10.x" + } + }, "node_modules/highlight.js": { "version": "11.10.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", @@ -8463,6 +8534,31 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jaeger-client": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/jaeger-client/-/jaeger-client-3.19.0.tgz", + "integrity": "sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0", + "opentracing": "^0.14.4", + "thriftrw": "^3.5.0", + "uuid": "^8.3.2", + "xorshift": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jaeger-client/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -9254,6 +9350,12 @@ "node": ">= 6.13.0" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -9466,6 +9568,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opentracing": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.14.7.tgz", + "integrity": "sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -10053,6 +10164,15 @@ } } }, + "node_modules/process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -11066,6 +11186,12 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", + "dev": true + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -11606,6 +11732,32 @@ "node": ">=0.8" } }, + "node_modules/thriftrw": { + "version": "3.11.4", + "resolved": "https://registry.npmjs.org/thriftrw/-/thriftrw-3.11.4.tgz", + "integrity": "sha512-UcuBd3eanB3T10nXWRRMwfwoaC6VMk7qe3/5YIWP2Jtw+EbHqJ0p1/K3x8ixiR5dozKSSfcg1W+0e33G1Di3XA==", + "dev": true, + "dependencies": { + "bufrw": "^1.2.1", + "error": "7.0.2", + "long": "^2.4.0" + }, + "bin": { + "thrift2json": "thrift2json.js" + }, + "engines": { + "node": ">= 0.10.x" + } + }, + "node_modules/thriftrw/node_modules/long": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", + "integrity": "sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -12280,6 +12432,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xorshift": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/xorshift/-/xorshift-1.2.0.tgz", + "integrity": "sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g==", + "dev": true + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/hosting/package.json b/hosting/package.json index c413f6c4..700419c5 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -54,6 +54,7 @@ "@iconify-json/ic": "^1.1.17", "@iconify-json/ri": "^1.1.20", "@next/eslint-plugin-next": "^14.2.3", + "@opentelemetry/exporter-jaeger": "^1.25.1", "@tailwindcss/typography": "^0.5.13", "@tiptap/extension-link": "^2.5.5", "@types/highlight.js": "^10.1.0", diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 81a713bf..a8316de0 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -80,34 +80,37 @@ export default function DocumentTypeDocumentsPage() { )} - setIsDialogOpen(false)} - title={"Tell your story"} - > - {status === ProcessingState.Ready ? ( - <> - - {!isRecording && !audio && ( - <> -
Or
+ {isDialogOpen && ( + setIsDialogOpen(false)} + title={"Tell your story"} + > + {status === ProcessingState.Ready ? ( + <> + - - - )} - - ) : ( -
- - {status === ProcessingState.Uploading &&

Uploading file...

} - {status === ProcessingState.Processing &&

Preparing...

} - {status === ProcessingState.Generating &&

Generating with AI...

} - {status === ProcessingState.Finalizing &&

Finalizing...

} -
- )} -
+ {!isRecording && !audio && ( + <> +
Or
+ + + + )} + + ) : ( +
+ + {status === ProcessingState.Uploading &&

Uploading file...

} + {status === ProcessingState.Processing &&

Preparing...

} + {status === ProcessingState.Generating &&

Generating with AI...

} + {status === ProcessingState.Finalizing &&

Finalizing...

} +
+ )} +
+ )} {notification && ( diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 1add3e9c..715e654a 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,3 +1,4 @@ +"use client"; import Peaks, {PeaksOptions} from "peaks.js"; import {useEffect, useRef, useState} from "react"; From c959f9172e916d27c70712bd266d2611223c15f7 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 15:02:15 +0700 Subject: [PATCH 53/74] [TEST] can build without dialog --- hosting/package-lock.json | 6 +-- .../app/(protected)/content/article/page.tsx | 38 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index 16fde9f2..df8f6e77 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -9078,9 +9078,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3", diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index a8316de0..68eaef5d 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -1,16 +1,14 @@ +/* eslint-disable */ "use client"; import {Button} from "@/components/Button"; -import Dialog from "@/components/Dialog"; -import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; -import FilePicker from "@/components/FilePicker"; -import {VoiceRecorder} from "@/components/VoiceRecorder"; import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; +import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; import {useAuthentication} from "@/hooks/useAuthentication"; -import {ProcessingState, useGenkitArticle} from "@/hooks/useGenkitArticle"; -import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; +import {useGenkitArticle} from "@/hooks/useGenkitArticle"; import {useCrudTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; +import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; import {UserNotification} from "@/models/UserNotification"; import {base64ToFile} from "@/plugins/fileUpload"; import {useRouter} from "next/navigation"; @@ -81,7 +79,19 @@ export default function DocumentTypeDocumentsPage() { )} - {isDialogOpen && ( + {notification && ( + + )} + + }> + {documentType ? ( + + ) : ( + + )} + + + {/* {isDialogOpen && ( )} - )} - - {notification && ( - - )} - - }> - {documentType ? ( - - ) : ( - - )} - + )} */} ); } From f731f6d336c03db57fc01a0ef502517d14581c75 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 15:38:43 +0700 Subject: [PATCH 54/74] fix unsupported engine --- .github/workflows/firebase-hosting-pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml index d4499e1f..70d2408b 100644 --- a/.github/workflows/firebase-hosting-pull-request.yml +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -17,7 +17,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: "npm" cache-dependency-path: | hosting/package-lock.json @@ -46,7 +46,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: "npm" cache-dependency-path: | hosting/package-lock.json From 9254ae6a238b339917c7253d50b6e35c047c01e0 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 15:50:39 +0700 Subject: [PATCH 55/74] [TEST] Can build without voice recorder --- .github/workflows/firebase-merge.yml | 4 ++-- functions/package.json | 2 +- hosting/package-lock.json | 7 ++++--- hosting/package.json | 1 + hosting/src/app/(protected)/content/article/page.tsx | 10 ++++++---- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/firebase-merge.yml b/.github/workflows/firebase-merge.yml index 63488b7b..f240371c 100644 --- a/.github/workflows/firebase-merge.yml +++ b/.github/workflows/firebase-merge.yml @@ -16,7 +16,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: "npm" cache-dependency-path: | hosting/package-lock.json @@ -44,7 +44,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: "npm" cache-dependency-path: | hosting/package-lock.json diff --git a/functions/package.json b/functions/package.json index 4e9493a2..2b77ebe6 100644 --- a/functions/package.json +++ b/functions/package.json @@ -16,7 +16,7 @@ }, "name": "functions", "engines": { - "node": "18" + "node": "20" }, "dependencies": { "axios": "^1.7.2", diff --git a/hosting/package-lock.json b/hosting/package-lock.json index df8f6e77..641bf8f1 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -60,6 +60,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.1", + "firebase-admin": "^12.4.0", "postcss": "^8", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", @@ -7147,9 +7148,9 @@ } }, "node_modules/firebase-admin": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.3.1.tgz", - "integrity": "sha512-vEr3s3esl8nPIA9r/feDT4nzIXCfov1CyyCSpMQWp6x63Q104qke0MEGZlrHUZVROtl8FLus6niP/M9I1s4VBA==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.4.0.tgz", + "integrity": "sha512-3HOHqJxNmFv0JgK3voyMQgmcibhJN4LQfZfhnZGb6pcONnZxejki4nQ1twsoJlGaIvgQWBtO7rc5mh/cqlOJNA==", "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^1.0.2", diff --git a/hosting/package.json b/hosting/package.json index 700419c5..730bc8ff 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -70,6 +70,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.1", + "firebase-admin": "^12.4.0", "postcss": "^8", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 68eaef5d..b5a46402 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -4,9 +4,11 @@ import {Button} from "@/components/Button"; import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; +import Dialog from "@/components/Dialog"; import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; +import FilePicker from "@/components/FilePicker"; import {useAuthentication} from "@/hooks/useAuthentication"; -import {useGenkitArticle} from "@/hooks/useGenkitArticle"; +import {ProcessingState, useGenkitArticle} from "@/hooks/useGenkitArticle"; import {useCrudTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; import {UserNotification} from "@/models/UserNotification"; @@ -91,7 +93,7 @@ export default function DocumentTypeDocumentsPage() { )} - {/* {isDialogOpen && ( + {isDialogOpen && ( {status === ProcessingState.Ready ? ( <> - + {/* */} {!isRecording && !audio && ( <> @@ -120,7 +122,7 @@ export default function DocumentTypeDocumentsPage() { )} - )} */} + )} ); } From 43deb0bef6124c7ce6c6d3ed8a3a6545afa6dadb Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 27 Aug 2024 16:39:29 +0700 Subject: [PATCH 56/74] can build without voice recorder --- hosting/package-lock.json | 13 +- hosting/package.json | 4 +- .../app/(protected)/content/article/page.tsx | 3 +- hosting/src/components/VoiceRecorder.tsx | 130 +++++++++--------- 4 files changed, 82 insertions(+), 68 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index 641bf8f1..add75961 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -60,14 +60,14 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.1", - "firebase-admin": "^12.4.0", "postcss": "^8", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", "sass": "^1.77.2", "sass-loader": "^14.2.1", "tailwindcss": "^3.4.1", - "typescript": "5.1" + "typescript": "5.1", + "xhr2": "^0.2.1" }, "engines": { "node": "20" @@ -12433,6 +12433,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xhr2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/xorshift": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/xorshift/-/xorshift-1.2.0.tgz", diff --git a/hosting/package.json b/hosting/package.json index 730bc8ff..ad7b6a0a 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -70,13 +70,13 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.1", - "firebase-admin": "^12.4.0", "postcss": "^8", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11", "sass": "^1.77.2", "sass-loader": "^14.2.1", "tailwindcss": "^3.4.1", - "typescript": "5.1" + "typescript": "5.1", + "xhr2": "^0.2.1" } } diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index b5a46402..592f9ff2 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -7,6 +7,7 @@ import PageHeader from "@/components/common/PageHeader"; import Dialog from "@/components/Dialog"; import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; import FilePicker from "@/components/FilePicker"; +import {VoiceRecorder} from "@/components/VoiceRecorder"; import {useAuthentication} from "@/hooks/useAuthentication"; import {ProcessingState, useGenkitArticle} from "@/hooks/useGenkitArticle"; import {useCrudTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; @@ -102,7 +103,7 @@ export default function DocumentTypeDocumentsPage() { > {status === ProcessingState.Ready ? ( <> - {/* */} + {!isRecording && !audio && ( <> diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 715e654a..9128cda3 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,5 +1,6 @@ +/* eslint-disable */ "use client"; -import Peaks, {PeaksOptions} from "peaks.js"; +// import Peaks from "peaks.js"; import {useEffect, useRef, useState} from "react"; export interface VoiceRecorderProps { @@ -176,6 +177,8 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * Loads the audio buffer and initializes Peak.js with the given options. */ function initSoundWave() { + if (!window) return; + if (peaksInstanceRef.current) { peaksInstanceRef.current.destroy(); } @@ -211,71 +214,72 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { axisGridlineColor: "#ccc", axisLabelColor: "#aaa", randomizeSegmentColor: true, - } as unknown as PeaksOptions; + } as any; - peaksInstanceRef.current = Peaks.init(options); + // peaksInstanceRef.current = Peaks.init(options); } return ( -
- {title &&

{title}

} -
- {isRecording ? ( - <> -
- -
- -

Mic's Live!

- - ) : ( - <> -
- -
- -

{!audioUrl ? "Tap & Speak" : "Tap to Try Again"}

- - )} -
- {audioUrl && ( -
-

Your Recording:

-
-
-
- - -
- )} - {transcript && ( -
-

What You Said:

-

{transcript}

-
- )} -
+ //
+ // {title &&

{title}

} + //
+ // {isRecording ? ( + // <> + //
+ // + //
+ + //

Mic's Live!

+ // + // ) : ( + // <> + //
+ // + //
+ + //

{!audioUrl ? "Tap & Speak" : "Tap to Try Again"}

+ // + // )} + //
+ // {audioUrl && ( + //
+ //

Your Recording:

+ //
+ //
+ //
+ // + // + //
+ // )} + // {transcript && ( + //
+ //

What You Said:

+ //

{transcript}

+ //
+ // )} + //
+ <>Hello ); } From 469f88e1f08cb114a8ea73a6f75a13205326a967 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 13:49:49 +0700 Subject: [PATCH 57/74] navigator is not undefined error --- hosting/package-lock.json | 69 ++++----- hosting/package.json | 4 +- .../app/(protected)/content/article/page.tsx | 68 ++++----- hosting/src/components/VoiceRecorder.tsx | 131 +++++++++--------- 4 files changed, 141 insertions(+), 131 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index add75961..c2f31f8c 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -30,12 +30,14 @@ "flatpickr": "^4.6.13", "highlight.js": "^11.10.0", "jsvectormap": "^1.5.3", + "konva": "^9.3.14", "lowlight": "^3.1.0", "next": "^14.2.3", - "peaks.js": "^0.18.1", + "peaks.js": "^3.4.2", "react": "^18.3.1", "react-apexcharts": "^1.4.1", "react-dom": "^18.3.1", + "waveform-data": "^4.5.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.2" }, @@ -5377,14 +5379,6 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, - "node_modules/colors.css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/colors.css/-/colors.css-3.0.0.tgz", - "integrity": "sha512-mnoVd1viYkriMAuMWggN8v3lFvOjkJ2LcCNdh5ZbJs7SkxindeXmU8EV7nBGxdga03zwe00ut7uoFVCCu2mA/w==", - "engines": { - "node": ">=0.10.22" - } - }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -6801,10 +6795,10 @@ "node": ">=6" } }, - "node_modules/eventemitter2": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.0.0.tgz", - "integrity": "sha512-ZuNWHD7S7IoikyEmx35vPU8H1W0L+oi644+4mSTg7nwXvBQpIwQL7DPjYUF0VMB0jPkNMo3MqD07E7MYrkFmjQ==" + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/eventid": { "version": "2.0.1", @@ -8055,11 +8049,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/inline-worker": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/inline-worker/-/inline-worker-1.1.0.tgz", - "integrity": "sha512-2nlxBGg5Uoop6IRco9wMzlAz62690ylokrUDg3IaCi9bJaq0HykbWlRtRXgvSKiBNmCBGXnOCFBkIFjaSGJocA==" - }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -8770,9 +8759,23 @@ "dev": true }, "node_modules/konva": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/konva/-/konva-4.1.6.tgz", - "integrity": "sha512-UpskmAKOspXw96Oab1Ea8VLGl87UQpv8jFr7Bq03U8xg/Pc3r9P2CbiuqWtiyAguoZriWk13BvVPep5DQ1Zv2A==" + "version": "9.3.14", + "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.14.tgz", + "integrity": "sha512-Gmm5lyikGYJyogKQA7Fy6dKkfNh350V6DwfZkid0RVrGYP2cfCsxuMxgF5etKeCv7NjXYpJxKqi1dYkIkX/dcA==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ] }, "node_modules/kuler": { "version": "2.0.0", @@ -9730,17 +9733,18 @@ "dev": true }, "node_modules/peaks.js": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/peaks.js/-/peaks.js-0.18.1.tgz", - "integrity": "sha512-vzCnsEJhvZvl5JZ4MStfaljF34TH8ddStywB/gHtmqHrsqwQWqc0Gp8HjeIk4l0UQP1ORzn7Gm79X4UyYbYeNA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/peaks.js/-/peaks.js-3.4.2.tgz", + "integrity": "sha512-cgKooOBIBwIiLBCIOYbrFH4+8TmeDMV9KbqR31cFRlvWwBVOgEuDdNgX0szmOw4F4cjSM5IjqovfrUYKlDl/JA==", "dependencies": { - "colors.css": "~3.0.0", - "eventemitter2": "~6.0.0", - "konva": "~4.1.3", - "waveform-data": "~3.1.0" + "eventemitter3": "~5.0.1" }, "engines": { "node": ">= 8.11.2" + }, + "peerDependencies": { + "konva": ">= 8.3.14 < 10", + "waveform-data": ">= 4.3.0 < 5" } }, "node_modules/pg-int8": { @@ -12140,12 +12144,9 @@ } }, "node_modules/waveform-data": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/waveform-data/-/waveform-data-3.1.0.tgz", - "integrity": "sha512-Xephsy9bCJmDPVeXNYhoZ7HTSzUMKMxAd/buZ4urewhLjLYpWqqBTWOoEaV2eQ/VjroHweE28C4ngu1o+1ZoXw==", - "dependencies": { - "inline-worker": "~1.1.0" - } + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/waveform-data/-/waveform-data-4.5.0.tgz", + "integrity": "sha512-fLT8yLGUuVhDBMVVOD2AGqHlLLVT/zfGlSIKgfqMCwFJGtkvY9eEhu0EL9W55lFJQt4r53X5Ncj86ymTGJlJTA==" }, "node_modules/web-streams-polyfill": { "version": "3.3.3", diff --git a/hosting/package.json b/hosting/package.json index ad7b6a0a..d2963e91 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -40,12 +40,14 @@ "flatpickr": "^4.6.13", "highlight.js": "^11.10.0", "jsvectormap": "^1.5.3", + "konva": "^9.3.14", "lowlight": "^3.1.0", "next": "^14.2.3", - "peaks.js": "^0.18.1", + "peaks.js": "^3.4.2", "react": "^18.3.1", "react-apexcharts": "^1.4.1", "react-dom": "^18.3.1", + "waveform-data": "^4.5.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.2" }, diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 592f9ff2..156d41c4 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -88,42 +88,46 @@ export default function DocumentTypeDocumentsPage() { }> {documentType ? ( - + <> + + + {isDialogOpen && ( + setIsDialogOpen(false)} + title={"Tell your story"} + > + {status === ProcessingState.Ready ? ( + <> + {window && navigator && ( + + )} + + {!isRecording && !audio && ( + <> +
Or
+ + + + )} + + ) : ( +
+ + {status === ProcessingState.Uploading &&

Uploading file...

} + {status === ProcessingState.Processing &&

Preparing...

} + {status === ProcessingState.Generating &&

Generating with AI...

} + {status === ProcessingState.Finalizing &&

Finalizing...

} +
+ )} +
+ )} + ) : ( )}
- - {isDialogOpen && ( - setIsDialogOpen(false)} - title={"Tell your story"} - > - {status === ProcessingState.Ready ? ( - <> - - - {!isRecording && !audio && ( - <> -
Or
- - - - )} - - ) : ( -
- - {status === ProcessingState.Uploading &&

Uploading file...

} - {status === ProcessingState.Processing &&

Preparing...

} - {status === ProcessingState.Generating &&

Generating with AI...

} - {status === ProcessingState.Finalizing &&

Finalizing...

} -
- )} -
- )} ); } diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 9128cda3..24d32a27 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,6 +1,6 @@ /* eslint-disable */ "use client"; -// import Peaks from "peaks.js"; +import Peaks from "peaks.js"; import {useEffect, useRef, useState} from "react"; export interface VoiceRecorderProps { @@ -55,6 +55,8 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * It handles starting, stopping, and restarting the speech recognition process. */ useEffect(() => { + if (typeof navigator === "undefined" || typeof window === "undefined") return; + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; console.info("SpeechRecognition :: ", SpeechRecognition); @@ -97,6 +99,8 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * When recording stops, it converts the audio chunks to a base64 string and passes it to onChange. */ async function handleStartRecording() { + if (typeof navigator === "undefined" || typeof window === "undefined") return; + handleReset(); setIsRecording(true); @@ -177,7 +181,7 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * Loads the audio buffer and initializes Peak.js with the given options. */ function initSoundWave() { - if (!window) return; + if (typeof navigator === "undefined" || typeof window === "undefined") return; if (peaksInstanceRef.current) { peaksInstanceRef.current.destroy(); @@ -216,70 +220,69 @@ export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { randomizeSegmentColor: true, } as any; - // peaksInstanceRef.current = Peaks.init(options); + peaksInstanceRef.current = Peaks.init(options); } return ( - //
- // {title &&

{title}

} - //
- // {isRecording ? ( - // <> - //
- // - //
- - //

Mic's Live!

- // - // ) : ( - // <> - //
- // - //
- - //

{!audioUrl ? "Tap & Speak" : "Tap to Try Again"}

- // - // )} - //
- // {audioUrl && ( - //
- //

Your Recording:

- //
- //
- //
- // - // - //
- // )} - // {transcript && ( - //
- //

What You Said:

- //

{transcript}

- //
- // )} - //
- <>Hello +
+ {title &&

{title}

} +
+ {isRecording ? ( + <> +
+ +
+ +

Mic's Live!

+ + ) : ( + <> +
+ +
+ +

{!audioUrl ? "Tap & Speak" : "Tap to Try Again"}

+ + )} +
+ {audioUrl && ( +
+

Your Recording:

+
+
+
+ + +
+ )} + {transcript && ( +
+

What You Said:

+

{transcript}

+
+ )} +
); } From 1e14a6b7c878cd44bb621dbdb653bc319517093f Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:00:23 +0700 Subject: [PATCH 58/74] fix voice recorder build error --- hosting/src/app/(protected)/content/article/page.tsx | 10 ++++++---- hosting/src/components/VoiceRecorder.tsx | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 156d41c4..83a6ee0e 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -7,16 +7,20 @@ import PageHeader from "@/components/common/PageHeader"; import Dialog from "@/components/Dialog"; import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; import FilePicker from "@/components/FilePicker"; -import {VoiceRecorder} from "@/components/VoiceRecorder"; import {useAuthentication} from "@/hooks/useAuthentication"; import {ProcessingState, useGenkitArticle} from "@/hooks/useGenkitArticle"; import {useCrudTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; import {UserNotification} from "@/models/UserNotification"; import {base64ToFile} from "@/plugins/fileUpload"; +import dynamic from "next/dynamic"; import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; +const VoiceRecorder = dynamic(() => import("@/components/VoiceRecorder"), { + ssr: false, +}); + export default function DocumentTypeDocumentsPage() { const router = useRouter(); const {authUser} = useAuthentication(); @@ -100,9 +104,7 @@ export default function DocumentTypeDocumentsPage() { > {status === ProcessingState.Ready ? ( <> - {window && navigator && ( - - )} + {!isRecording && !audio && ( <> diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 24d32a27..9c8ce485 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -3,7 +3,7 @@ import Peaks from "peaks.js"; import {useEffect, useRef, useState} from "react"; -export interface VoiceRecorderProps { +interface VoiceRecorderProps { title?: string; value: string; onChange: (value: string) => void; @@ -18,7 +18,7 @@ export interface VoiceRecorderProps { * @param {VoiceRecorderProps} props - The props for the component. * @return {JSX.Element} The rendered VoiceRecorder component. */ -export function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { +export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const {title, value, onChange, onTranscriptChange, onLoadingChange} = props; const [isRecording, setIsRecording] = useState(false); From 9cdf17ff5d7cb24913a0747a123f5c19a7c2249c Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:00:51 +0700 Subject: [PATCH 59/74] remove eslint disabled --- hosting/src/app/(protected)/content/article/page.tsx | 1 - hosting/src/components/VoiceRecorder.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 83a6ee0e..64231931 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -1,4 +1,3 @@ -/* eslint-disable */ "use client"; import {Button} from "@/components/Button"; import Loader from "@/components/common/Loader"; diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 9c8ce485..b4792a09 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,4 +1,3 @@ -/* eslint-disable */ "use client"; import Peaks from "peaks.js"; import {useEffect, useRef, useState} from "react"; From 801e17a659bf1a2fd3d109cd788a8cc75800e217 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:06:29 +0700 Subject: [PATCH 60/74] add comment --- hosting/src/app/(protected)/content/article/page.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 64231931..c6e4a5de 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -16,6 +16,9 @@ import dynamic from "next/dynamic"; import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; +// I don't know why this component always errors +// when built because this component is still detected as a component rendered on the server. +// Even though I've used "use client" inside the component :( const VoiceRecorder = dynamic(() => import("@/components/VoiceRecorder"), { ssr: false, }); From d4aed52bb75b0f2805da6a1ac9ea5d9dcb216a81 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:13:24 +0700 Subject: [PATCH 61/74] fix next ssr problem --- .../app/(protected)/content/article/[documentId]/page.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hosting/src/app/(protected)/content/article/[documentId]/page.tsx b/hosting/src/app/(protected)/content/article/[documentId]/page.tsx index 20554a37..fb450b86 100644 --- a/hosting/src/app/(protected)/content/article/[documentId]/page.tsx +++ b/hosting/src/app/(protected)/content/article/[documentId]/page.tsx @@ -1,13 +1,18 @@ "use client"; -import TiptapEditor from "@/components/Tiptap/TiptapEditor"; import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; import {useCrudTanamDocument, useTanamDocument} from "@/hooks/useTanamDocuments"; import {UserNotification} from "@/models/UserNotification"; +import dynamic from "next/dynamic"; import {useParams, useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; +// TiptapEditor is also detected as ssr, even though it uses "use client" :( +const TiptapEditor = dynamic(() => import("@/components/Tiptap/TiptapEditor"), { + ssr: false, +}); + export default function DocumentDetailsPage() { const router = useRouter(); const {documentId} = useParams<{documentId: string}>() ?? {}; From a9b6e3b2cc4e4ebef9aace78fe46c9c243f3497b Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:23:53 +0700 Subject: [PATCH 62/74] fix tiptapeditor error console --- hosting/src/components/Tiptap/TiptapEditor.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hosting/src/components/Tiptap/TiptapEditor.tsx b/hosting/src/components/Tiptap/TiptapEditor.tsx index 72751e99..10da911b 100644 --- a/hosting/src/components/Tiptap/TiptapEditor.tsx +++ b/hosting/src/components/Tiptap/TiptapEditor.tsx @@ -66,8 +66,15 @@ export default function TiptapEditor(props: TiptapEditorProps) { ); const editor = useEditor({ + immediatelyRender: false, extensions: [ - StarterKit, + StarterKit.configure({ + heading: false, + codeBlock: false, + paragraph: false, + bulletList: false, + orderedList: false, + }), Underline, CodeBlockLowlight.extend({ addNodeView() { From 8e5e24ae75421b5b5d753d1c4ed7803b35850c11 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:29:58 +0700 Subject: [PATCH 63/74] test deploy error --- hosting/package-lock.json | 133 ++------------------------------------ hosting/package.json | 1 + 2 files changed, 8 insertions(+), 126 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index c2f31f8c..d267856f 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -26,6 +26,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "firebase": "^10.12.0", + "firebase-admin": "^12.4.0", "firebaseui": "^6.1.0", "flatpickr": "^4.6.13", "highlight.js": "^11.10.0", @@ -1482,126 +1483,6 @@ "node": ">= 10" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7165,17 +7046,17 @@ } }, "node_modules/firebase-admin/node_modules/@types/node": { - "version": "22.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.1.tgz", - "integrity": "sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==", + "version": "22.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", + "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", "dependencies": { "undici-types": "~6.19.2" } }, "node_modules/firebase-admin/node_modules/undici-types": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.6.tgz", - "integrity": "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/firebase-admin/node_modules/uuid": { "version": "10.0.0", diff --git a/hosting/package.json b/hosting/package.json index d2963e91..4f9bd7b1 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -36,6 +36,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "firebase": "^10.12.0", + "firebase-admin": "^12.4.0", "firebaseui": "^6.1.0", "flatpickr": "^4.6.13", "highlight.js": "^11.10.0", From 1d8259ef833563630624354c9cbd7a883199a012 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 14:33:47 +0700 Subject: [PATCH 64/74] remove unused --- hosting/package-lock.json | 120 ++++++++++++++++++ .../app/(protected)/content/article/page.tsx | 4 - hosting/src/components/VoiceRecorder.tsx | 15 +-- 3 files changed, 121 insertions(+), 18 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index d267856f..6ef62bc0 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -1483,6 +1483,126 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", + "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index c6e4a5de..fd022d6b 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -49,12 +49,8 @@ export default function DocumentTypeDocumentsPage() { async function submitAudio() { if (!audio) return; - console.info("submitAudio audio :: ", audio); - const file = base64ToFile(audio, `audio-${authUser?.uid}`); await handleFileSelect(file); - - console.info("submitAudio file :: ", file); } async function handleFileSelect(file: File) { diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index b4792a09..09ca5a45 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -58,22 +58,16 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; - console.info("SpeechRecognition :: ", SpeechRecognition); - if (SpeechRecognition) { recognitionRef.current = new SpeechRecognition(); const recognition = recognitionRef.current; if (!recognition) return; - console.info("recognition before :: ", recognition); - - // recognition.lang = "en-US"; // Set language for speech recognition + recognition.lang = "en-US"; // Set language for speech recognition recognition.interimResults = false; // Only return final results recognition.continuous = true; - console.info("recognition after :: ", recognition); - // Handle the event when speech recognition returns results recognition.onresult = (event: any) => { const transcript = Array.from(event.results) @@ -88,8 +82,6 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { } }; } - - console.info("isRecording :: ", isRecording); }, [onTranscriptChange, isRecording]); /** @@ -121,10 +113,7 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { reader.onloadend = () => { if (!reader.result) return; - console.info("reader result :: ", reader.result); - const base64String = reader.result?.toString() || ""; - console.info("base64String :: ", base64String); onChange(base64String); // Set audio URL for visualization @@ -152,8 +141,6 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { if (!mediaRecorderRef.current || !recognitionRef.current || !streamRef.current) return; - console.info("handleStopRecording"); - mediaRecorderRef.current.stop(); recognitionRef.current.stop(); // Stop all tracks to turn off the microphone From 9ae57079ef35730ffb96d51d2bf276372657f10f Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 15:09:14 +0700 Subject: [PATCH 65/74] fix sound wave not showing --- hosting/src/components/VoiceRecorder.tsx | 49 +++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 09ca5a45..1a99d3a6 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -49,13 +49,26 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { } }, [isRecording, onLoadingChange]); + /** + * Effect to trigger initSoundWave whenever the audioUrl changes. + */ + useEffect(() => { + if (audioUrl) { + setTimeout(() => { + initSoundWave(); + }, 1000); + } + + return () => { + resetSoundWave(); + }; + }, [audioUrl]); + /** * Effect hook that sets up the SpeechRecognition instance and its event handlers. * It handles starting, stopping, and restarting the speech recognition process. */ useEffect(() => { - if (typeof navigator === "undefined" || typeof window === "undefined") return; - const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (SpeechRecognition) { @@ -119,10 +132,6 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { // Set audio URL for visualization const url = URL.createObjectURL(audioBlob); setAudioUrl(url); - - setTimeout(() => { - initSoundWave(); - }, 500); }; reader.readAsDataURL(audioBlob); @@ -167,16 +176,15 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * Loads the audio buffer and initializes Peak.js with the given options. */ function initSoundWave() { - if (typeof navigator === "undefined" || typeof window === "undefined") return; + resetSoundWave(); - if (peaksInstanceRef.current) { - peaksInstanceRef.current.destroy(); - } + console.info("initSoundWave zoomviewContainer :: ", document.getElementById("zoomviewContainer")); + console.info("initSoundWave overviewContainer :: ", document.getElementById("overviewContainer")); // Peak.js options configuration (https://www.npmjs.com/package/peaks.js/v/0.18.1#configuration) const options = { - containers: { - overview: document.getElementById("overviewContainer"), + overview: { + container: document.getElementById("overviewContainer"), }, mediaElement: document.getElementById("audio"), webAudio: { @@ -206,7 +214,20 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { randomizeSegmentColor: true, } as any; - peaksInstanceRef.current = Peaks.init(options); + peaksInstanceRef.current = Peaks.init(options, (err, peaksInstance) => { + if (err) { + console.error(err.message); + return; + } + + console.log("Peaks instance ready"); + }); + } + + function resetSoundWave() { + if (!peaksInstanceRef.current) return; + + peaksInstanceRef.current.destroy(); } return ( @@ -252,7 +273,7 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element {

Your Recording:

-
+
+ +
+
) : ( diff --git a/hosting/src/components/Button.tsx b/hosting/src/components/Button.tsx index b3849fc9..cfcfcef8 100644 --- a/hosting/src/components/Button.tsx +++ b/hosting/src/components/Button.tsx @@ -24,28 +24,26 @@ export function Button({title, onClick, style = "rounded", color = "primary", ch "inline-flex", "items-center", "justify-center", - "px-10", - "py-4", + "px-4", + "py-2", "text-center", "font-medium", "hover:bg-opacity-90", - "lg:px-8", - "xl:px-10", isLoading ? "opacity-50 cursor-not-allowed" : "", ]; switch (color) { case "primary": - styles.push("bg-primary", !style ? "text-white" : ""); + styles.push("bg-primary", "text-white"); break; case "meta-3": - styles.push("bg-meta-3", !style ? "text-white" : ""); + styles.push("bg-meta-3", "text-white"); break; case "black": - styles.push("bg-black", !style ? "text-white" : ""); + styles.push("bg-black", "text-white"); break; default: - styles.push("bg-primary", !style ? "text-white" : ""); + styles.push("bg-primary", "text-white"); } switch (style) { From 027363ee5c78a1e9dff9105147caddbd55af6279 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 16:30:30 +0700 Subject: [PATCH 68/74] missing shared process in pipeline --- .github/workflows/firebase-hosting-pull-request.yml | 10 ++++++++++ .github/workflows/firebase-merge.yml | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml index 70d2408b..772cf061 100644 --- a/.github/workflows/firebase-hosting-pull-request.yml +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -20,9 +20,14 @@ jobs: node-version: 20 cache: "npm" cache-dependency-path: | + shared/package-lock.json hosting/package-lock.json functions/package-lock.json + - name: Run code checking and formatting (shared) + run: npm ci && npm run codecheck + working-directory: ./shared + - name: Run code checking and formatting (functions) run: npm ci && npm run codecheck working-directory: ./functions @@ -49,6 +54,7 @@ jobs: node-version: 20 cache: "npm" cache-dependency-path: | + shared/package-lock.json hosting/package-lock.json functions/package-lock.json @@ -66,6 +72,10 @@ jobs: - name: Set hosting environment run: echo $HOSTING_ENV > hosting/.env + - name: Build shared code + run: npm ci && npm run build + working-directory: ./shared + - name: Build functions code run: npm ci && npm run build working-directory: ./functions diff --git a/.github/workflows/firebase-merge.yml b/.github/workflows/firebase-merge.yml index f240371c..f31b4fb6 100644 --- a/.github/workflows/firebase-merge.yml +++ b/.github/workflows/firebase-merge.yml @@ -19,9 +19,14 @@ jobs: node-version: 20 cache: "npm" cache-dependency-path: | + shared/package-lock.json hosting/package-lock.json functions/package-lock.json + - name: Run code checking and formatting (shared) + run: npm ci && npm run codecheck + working-directory: ./shared + - name: Run code checking and formatting (functions) run: npm ci && npm run codecheck working-directory: ./functions @@ -47,6 +52,7 @@ jobs: node-version: 20 cache: "npm" cache-dependency-path: | + shared/package-lock.json hosting/package-lock.json functions/package-lock.json @@ -64,6 +70,10 @@ jobs: - name: Set hosting environment run: echo $HOSTING_ENV > hosting/.env + - name: Build shared code + run: npm ci && npm run build + working-directory: ./shared + - name: Build functions code run: npm ci && npm run build working-directory: ./functions From 467a38ea58e6eceb3a1e3f8c6782f8405bef15fd Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 16:35:22 +0700 Subject: [PATCH 69/74] missing eslint and prettier in shared folder --- shared/.eslintrc.js | 35 +++++++++++++++++++++++++ shared/.prettierrc | 9 +++++++ shared/package.json | 3 ++- shared/src/index.ts | 1 - shared/src/models/TanamDocument.ts | 2 +- shared/src/models/TanamDocumentField.ts | 2 +- shared/src/models/TanamDocumentType.ts | 2 +- shared/tsconfig.json | 9 ++----- 8 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 shared/.eslintrc.js create mode 100644 shared/.prettierrc diff --git a/shared/.eslintrc.js b/shared/.eslintrc.js new file mode 100644 index 00000000..da84fa2f --- /dev/null +++ b/shared/.eslintrc.js @@ -0,0 +1,35 @@ +module.exports = { + root: true, + env: { + es6: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "plugin:prettier/recommended", + "google", + "plugin:@typescript-eslint/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + project: ["tsconfig.json", "tsconfig.dev.json"], + sourceType: "module", + }, + ignorePatterns: [ + "/lib/**/*", // Ignore built files. + "/generated/**/*", // Ignore generated files. + ], + plugins: ["@typescript-eslint", "import", "prettier"], + rules: { + quotes: "off", // Use config from prettier + "max-len": "off", + "require-jsdoc": "off", + "import/no-unresolved": 0, + indent: ["error", 2, {SwitchCase: 1}], + "operator-linebreak": ["error", "before"], + "prettier/prettier": "error", + }, +}; diff --git a/shared/.prettierrc b/shared/.prettierrc new file mode 100644 index 00000000..3d5ba65a --- /dev/null +++ b/shared/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": false, + "bracketSpacing": false, + "trailingComma": "all", + "tabWidth": 2, + "useTabs": false, + "printWidth": 120 +} diff --git a/shared/package.json b/shared/package.json index 7b0a14b9..30c739f1 100644 --- a/shared/package.json +++ b/shared/package.json @@ -13,7 +13,8 @@ "codecheck": "npm run prettier:fix && npm run lint:fix", "clean": "rm -rf ./lib", "lint": "eslint --ext .ts .", - "lint:fix": "npm run lint --fix" + "lint:fix": "npm run lint --fix", + "prettier:fix": "prettier --write ." }, "dependencies": { "zod": "^3.23.8" diff --git a/shared/src/index.ts b/shared/src/index.ts index fd5e2aaf..805a6bd4 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -8,4 +8,3 @@ export * from "./models/TanamDocumentData"; export * from "./models/TanamDocumentField"; export * from "./models/TanamDocumentType"; export * from "./models/TanamUser"; - diff --git a/shared/src/models/TanamDocument.ts b/shared/src/models/TanamDocument.ts index a3bc3a59..7e083bae 100644 --- a/shared/src/models/TanamDocument.ts +++ b/shared/src/models/TanamDocument.ts @@ -1,4 +1,4 @@ -import { TanamPublishStatus } from "./../definitions/TanamPublishStatus"; +import {TanamPublishStatus} from "./../definitions/TanamPublishStatus"; interface DocumentData { [key: string]: unknown; diff --git a/shared/src/models/TanamDocumentField.ts b/shared/src/models/TanamDocumentField.ts index ed3069e1..b84b21d5 100644 --- a/shared/src/models/TanamDocumentField.ts +++ b/shared/src/models/TanamDocumentField.ts @@ -1,4 +1,4 @@ -import { LocalizedString } from "./LocalizedString"; +import {LocalizedString} from "./LocalizedString"; export interface ITanamDocumentField { weight: number; diff --git a/shared/src/models/TanamDocumentType.ts b/shared/src/models/TanamDocumentType.ts index 3373c708..9f3050b0 100644 --- a/shared/src/models/TanamDocumentType.ts +++ b/shared/src/models/TanamDocumentType.ts @@ -1,4 +1,4 @@ -import { LocalizedString } from "./LocalizedString"; +import {LocalizedString} from "./LocalizedString"; export interface ITanamDocumentType { titleSingular: LocalizedString; diff --git a/shared/tsconfig.json b/shared/tsconfig.json index bf659c99..439aac2a 100644 --- a/shared/tsconfig.json +++ b/shared/tsconfig.json @@ -8,11 +8,6 @@ "esModuleInterop": true, "skipLibCheck": true }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules", - "lib" - ] + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "lib"] } From 8ab9a4f3288dd6fe38e337f25fcd2ab19ca1477f Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Wed, 28 Aug 2024 16:39:19 +0700 Subject: [PATCH 70/74] fix error --- functions/package-lock.json | 13 ++++++------- functions/tsconfig.json | 12 +++--------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/functions/package-lock.json b/functions/package-lock.json index 090fda9b..ad012d90 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -2920,10 +2920,9 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", - "license": "MIT", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -7070,9 +7069,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3", diff --git a/functions/tsconfig.json b/functions/tsconfig.json index b34e6e37..4894bc7d 100644 --- a/functions/tsconfig.json +++ b/functions/tsconfig.json @@ -1,8 +1,6 @@ { "compileOnSave": true, - "include": [ - "src", - ], + "include": ["src"], "compilerOptions": { "module": "commonjs", "noImplicitReturns": true, @@ -14,12 +12,8 @@ "esModuleInterop": true, "noUnusedLocals": true, "paths": { - "@/*": [ - "./src/*" - ], - "tanam-shared/*": [ - "../shared/src/*" - ] + "@/*": ["./src/*"], + "tanam-shared/*": ["../shared/src/*"] } } } From 0c6df412f738832afb00167c1813e12793ae0165 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 3 Sep 2024 10:38:14 +0700 Subject: [PATCH 71/74] add tanamSpeechRecognition model --- hosting/global.d.ts | 44 -------- hosting/src/components/VoiceRecorder.tsx | 7 +- hosting/src/models/TanamSpeechRecognition.ts | 107 +++++++++++++++++++ 3 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 hosting/src/models/TanamSpeechRecognition.ts diff --git a/hosting/global.d.ts b/hosting/global.d.ts index 7dd0a746..da0da9c3 100644 --- a/hosting/global.d.ts +++ b/hosting/global.d.ts @@ -1,47 +1,3 @@ -interface SpeechRecognition { - start(): void; - stop(): void; - abort(): void; - lang: string; - interimResults: boolean; - maxAlternatives: number; - continuous: boolean; - onaudiostart?: (event: Event) => void; - onsoundstart?: (event: Event) => void; - onspeechstart?: (event: Event) => void; - onspeechend?: (event: Event) => void; - onsoundend?: (event: Event) => void; - onaudioend?: (event: Event) => void; - onresult?: (event: SpeechRecognitionEvent) => void; - onnomatch?: (event: Event) => void; - onerror?: (event: Event) => void; - onstart?: (event: Event) => void; - onend?: (event: Event) => void; -} - -interface SpeechRecognitionEvent { - resultIndex: number; - results: SpeechRecognitionResultList; -} - -interface SpeechRecognitionResultList { - [index: number]: SpeechRecognitionResult; - length: number; - item(index: number): SpeechRecognitionResult; -} - -interface SpeechRecognitionResult { - [index: number]: SpeechRecognitionAlternative; - length: number; - isFinal: boolean; - item(index: number): SpeechRecognitionAlternative; -} - -interface SpeechRecognitionAlternative { - transcript: string; - confidence: number; -} - interface Window { SpeechRecognition: typeof SpeechRecognition; webkitSpeechRecognition: typeof SpeechRecognition; diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 73711d4c..542a93d8 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -1,4 +1,5 @@ "use client"; +import {TanamSpeechRecognition} from "@/models/TanamSpeechRecognition"; import Peaks from "peaks.js"; import {useEffect, useRef, useState} from "react"; @@ -26,7 +27,7 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { const mediaRecorderRef = useRef(); const audioChunksRef = useRef([]); - const recognitionRef = useRef(); + const recognitionRef = useRef(); const streamRef = useRef(null); const audioContextRef = useRef(null); const peaksInstanceRef = useRef(null); @@ -67,10 +68,10 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { * It handles starting, stopping, and restarting the speech recognition process. */ useEffect(() => { - const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const SpeechRecognition = new TanamSpeechRecognition(); if (SpeechRecognition) { - recognitionRef.current = new SpeechRecognition(); + recognitionRef.current = SpeechRecognition; const recognition = recognitionRef.current; if (!recognition) return; diff --git a/hosting/src/models/TanamSpeechRecognition.ts b/hosting/src/models/TanamSpeechRecognition.ts new file mode 100644 index 00000000..a6b4c345 --- /dev/null +++ b/hosting/src/models/TanamSpeechRecognition.ts @@ -0,0 +1,107 @@ +export interface SpeechRecognition { + start(): void; + stop(): void; + abort(): void; + lang: string; + interimResults: boolean; + maxAlternatives: number; + continuous: boolean; + onaudiostart?: (event: Event) => void; + onsoundstart?: (event: Event) => void; + onspeechstart?: (event: Event) => void; + onspeechend?: (event: Event) => void; + onsoundend?: (event: Event) => void; + onaudioend?: (event: Event) => void; + onresult?: (event: SpeechRecognitionEvent) => void; + onnomatch?: (event: Event) => void; + onerror?: (event: Event) => void; + onstart?: (event: Event) => void; + onend?: (event: Event) => void; +} + +export interface SpeechRecognitionEvent { + resultIndex: number; + results: SpeechRecognitionResultList; +} + +export interface SpeechRecognitionResultList { + [index: number]: SpeechRecognitionResult; + length: number; + item(index: number): SpeechRecognitionResult; +} + +export interface SpeechRecognitionResult { + [index: number]: SpeechRecognitionAlternative; + length: number; + isFinal: boolean; + item(index: number): SpeechRecognitionAlternative; +} + +export interface SpeechRecognitionAlternative { + transcript: string; + confidence: number; +} + +export class TanamSpeechRecognition implements SpeechRecognition { + constructor() { + const SpeechRecognitionConstructor = window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognitionConstructor) { + throw new Error("SpeechRecognition is not supported in this browser."); + } + + this.recognition = new SpeechRecognitionConstructor(); + + // Bind event handlers + this.recognition.onstart = this.handleEvent.bind(this, "onstart"); + this.recognition.onend = this.handleEvent.bind(this, "onend"); + this.recognition.onerror = this.handleEvent.bind(this, "onerror"); + this.recognition.onresult = this.handleEvent.bind(this, "onresult"); + this.recognition.onspeechstart = this.handleEvent.bind(this, "onspeechstart"); + this.recognition.onspeechend = this.handleEvent.bind(this, "onspeechend"); + this.recognition.onsoundstart = this.handleEvent.bind(this, "onsoundstart"); + this.recognition.onsoundend = this.handleEvent.bind(this, "onsoundend"); + this.recognition.onaudiostart = this.handleEvent.bind(this, "onaudiostart"); + this.recognition.onaudioend = this.handleEvent.bind(this, "onaudioend"); + this.recognition.onnomatch = this.handleEvent.bind(this, "onnomatch"); + } + + public lang: string = "en-US"; + public interimResults: boolean = false; + public maxAlternatives: number = 1; + public continuous: boolean = false; + + public onaudiostart?: (event: Event) => void; + public onsoundstart?: (event: Event) => void; + public onspeechstart?: (event: Event) => void; + public onspeechend?: (event: Event) => void; + public onsoundend?: (event: Event) => void; + public onaudioend?: (event: Event) => void; + public onresult?: (event: SpeechRecognitionEvent) => void; + public onnomatch?: (event: Event) => void; + public onerror?: (event: Event) => void; + public onstart?: (event: Event) => void; + public onend?: (event: Event) => void; + + private recognition: SpeechRecognition; + + private handleEvent(eventName: keyof SpeechRecognition, event: Event | SpeechRecognitionEvent): void { + const handler = this[eventName]; + + if (typeof handler === "function") { + handler(event as Event & SpeechRecognitionEvent); + } + } + + start(): void { + this.recognition.start(); + } + + stop(): void { + this.recognition.stop(); + } + + abort(): void { + this.recognition.abort(); + } +} From 7a4426d372bb4755909e7546495726497239afcb Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 3 Sep 2024 10:44:38 +0700 Subject: [PATCH 72/74] remove unused --- hosting/src/components/VoiceRecorder.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hosting/src/components/VoiceRecorder.tsx b/hosting/src/components/VoiceRecorder.tsx index 542a93d8..8bf5e967 100644 --- a/hosting/src/components/VoiceRecorder.tsx +++ b/hosting/src/components/VoiceRecorder.tsx @@ -76,10 +76,6 @@ export default function VoiceRecorder(props: VoiceRecorderProps): JSX.Element { if (!recognition) return; - recognition.lang = "en-US"; // Set language for speech recognition - recognition.interimResults = false; // Only return final results - recognition.continuous = true; - // Handle the event when speech recognition returns results recognition.onresult = (event: any) => { const transcript = Array.from(event.results) From 21c2dd23c66e0473d0bda232728900ef3be45cdc Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 3 Sep 2024 13:47:21 +0700 Subject: [PATCH 73/74] remove global.d.ts --- hosting/global.d.ts | 4 ---- hosting/src/models/TanamSpeechRecognition.ts | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 hosting/global.d.ts diff --git a/hosting/global.d.ts b/hosting/global.d.ts deleted file mode 100644 index da0da9c3..00000000 --- a/hosting/global.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -interface Window { - SpeechRecognition: typeof SpeechRecognition; - webkitSpeechRecognition: typeof SpeechRecognition; -} diff --git a/hosting/src/models/TanamSpeechRecognition.ts b/hosting/src/models/TanamSpeechRecognition.ts index a6b4c345..8c9700c5 100644 --- a/hosting/src/models/TanamSpeechRecognition.ts +++ b/hosting/src/models/TanamSpeechRecognition.ts @@ -44,7 +44,7 @@ export interface SpeechRecognitionAlternative { export class TanamSpeechRecognition implements SpeechRecognition { constructor() { - const SpeechRecognitionConstructor = window.SpeechRecognition || window.webkitSpeechRecognition; + const SpeechRecognitionConstructor = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; if (!SpeechRecognitionConstructor) { throw new Error("SpeechRecognition is not supported in this browser."); From 1c82ec420ac1eb454694f71984791a4333e087c5 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 3 Sep 2024 13:54:11 +0700 Subject: [PATCH 74/74] extend window context interface --- hosting/src/models/TanamSpeechRecognition.ts | 43 +++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/hosting/src/models/TanamSpeechRecognition.ts b/hosting/src/models/TanamSpeechRecognition.ts index 8c9700c5..1dd673bf 100644 --- a/hosting/src/models/TanamSpeechRecognition.ts +++ b/hosting/src/models/TanamSpeechRecognition.ts @@ -1,3 +1,44 @@ +/** + * Extend the global `window` interface to include custom speech recognition classes. + * This is useful for environments where `SpeechRecognition` or `webkitSpeechRecognition` + * are available or need to be assigned to a custom implementation. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition} for + * standard SpeechRecognition API documentation. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WebkitSpeechRecognition} for + * WebkitSpeechRecognition API documentation. + */ +declare global { + /** + * The global `window` object, extended with additional properties for speech recognition. + */ + interface Window { + /** + * A reference to the custom `SpeechRecognition` implementation or standard implementation. + * This can be used to access speech recognition features in browsers that support it. + * + * @type {typeof TanamSpeechRecognition} + * @example + * // Example usage + * const recognition = new window.SpeechRecognition(); + * recognition.start(); + */ + SpeechRecognition: typeof TanamSpeechRecognition; + + /** + * A reference to the Webkit-specific `SpeechRecognition` implementation. + * This is particularly relevant for browsers that use the `webkit` prefix. + * + * @type {typeof TanamSpeechRecognition} + * @example + * // Example usage + * const recognition = new window.webkitSpeechRecognition(); + * recognition.start(); + */ + webkitSpeechRecognition: typeof TanamSpeechRecognition; + } +} + export interface SpeechRecognition { start(): void; stop(): void; @@ -44,7 +85,7 @@ export interface SpeechRecognitionAlternative { export class TanamSpeechRecognition implements SpeechRecognition { constructor() { - const SpeechRecognitionConstructor = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; + const SpeechRecognitionConstructor = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognitionConstructor) { throw new Error("SpeechRecognition is not supported in this browser.");