Set up the basic structure and core functionality for the application

Initialize project with UI components, core libraries, and basic app structure.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 556aa286-edd2-4cea-8583-f4fc3cfd119b
This commit is contained in:
ghaddaditw
2025-05-30 17:08:53 +00:00
parent b47089ae5d
commit cced901e9a
100 changed files with 20488 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import { QueryClient, QueryFunction } from "@tanstack/react-query";
async function throwIfResNotOk(res: Response) {
if (!res.ok) {
const text = (await res.text()) || res.statusText;
throw new Error(`${res.status}: ${text}`);
}
}
export async function apiRequest(
method: string,
url: string,
data?: unknown | undefined,
): Promise<Response> {
const res = await fetch(url, {
method,
headers: data ? { "Content-Type": "application/json" } : {},
body: data ? JSON.stringify(data) : undefined,
credentials: "include",
});
await throwIfResNotOk(res);
return res;
}
type UnauthorizedBehavior = "returnNull" | "throw";
export const getQueryFn: <T>(options: {
on401: UnauthorizedBehavior;
}) => QueryFunction<T> =
({ on401: unauthorizedBehavior }) =>
async ({ queryKey }) => {
const res = await fetch(queryKey[0] as string, {
credentials: "include",
});
if (unauthorizedBehavior === "returnNull" && res.status === 401) {
return null;
}
await throwIfResNotOk(res);
return await res.json();
};
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: getQueryFn({ on401: "throw" }),
refetchInterval: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
retry: false,
},
mutations: {
retry: false,
},
},
});
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+176
View File
@@ -0,0 +1,176 @@
/**
* Voice processing utilities for local audio handling
* Provides audio format conversion and preprocessing for STT models
*/
export interface AudioConfig {
sampleRate: number;
channels: number;
bitDepth: number;
}
export const DEFAULT_AUDIO_CONFIG: AudioConfig = {
sampleRate: 16000, // Common for STT models
channels: 1, // Mono
bitDepth: 16,
};
export class VoiceProcessor {
private audioContext: AudioContext | null = null;
constructor() {
if (typeof window !== "undefined" && window.AudioContext) {
this.audioContext = new AudioContext();
}
}
/**
* Convert audio blob to PCM format suitable for STT processing
*/
async convertToPCM(audioBlob: Blob, config: AudioConfig = DEFAULT_AUDIO_CONFIG): Promise<Float32Array> {
if (!this.audioContext) {
throw new Error("AudioContext not supported");
}
const arrayBuffer = await audioBlob.arrayBuffer();
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
// Resample if necessary
const resampled = this.resample(audioBuffer, config.sampleRate);
// Convert to mono if necessary
const mono = config.channels === 1 ? this.convertToMono(resampled) : resampled;
return mono.getChannelData(0);
}
/**
* Resample audio to target sample rate
*/
private resample(audioBuffer: AudioBuffer, targetSampleRate: number): AudioBuffer {
if (audioBuffer.sampleRate === targetSampleRate) {
return audioBuffer;
}
const ratio = audioBuffer.sampleRate / targetSampleRate;
const newLength = Math.floor(audioBuffer.length / ratio);
const newBuffer = this.audioContext!.createBuffer(
audioBuffer.numberOfChannels,
newLength,
targetSampleRate
);
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
const inputData = audioBuffer.getChannelData(channel);
const outputData = newBuffer.getChannelData(channel);
for (let i = 0; i < newLength; i++) {
const sourceIndex = Math.floor(i * ratio);
outputData[i] = inputData[sourceIndex];
}
}
return newBuffer;
}
/**
* Convert stereo to mono by averaging channels
*/
private convertToMono(audioBuffer: AudioBuffer): AudioBuffer {
if (audioBuffer.numberOfChannels === 1) {
return audioBuffer;
}
const monoBuffer = this.audioContext!.createBuffer(
1,
audioBuffer.length,
audioBuffer.sampleRate
);
const monoData = monoBuffer.getChannelData(0);
for (let i = 0; i < audioBuffer.length; i++) {
let sum = 0;
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
sum += audioBuffer.getChannelData(channel)[i];
}
monoData[i] = sum / audioBuffer.numberOfChannels;
}
return monoBuffer;
}
/**
* Apply noise reduction and audio enhancement
*/
async enhanceAudio(pcmData: Float32Array): Promise<Float32Array> {
// Simple noise gate implementation
const threshold = 0.01;
const enhanced = new Float32Array(pcmData.length);
for (let i = 0; i < pcmData.length; i++) {
enhanced[i] = Math.abs(pcmData[i]) > threshold ? pcmData[i] : 0;
}
return enhanced;
}
/**
* Detect voice activity in audio data
*/
detectVoiceActivity(pcmData: Float32Array, windowSize: number = 1024): boolean[] {
const activity: boolean[] = [];
const energyThreshold = 0.02;
for (let i = 0; i < pcmData.length; i += windowSize) {
const window = pcmData.slice(i, i + windowSize);
const energy = this.calculateEnergy(window);
activity.push(energy > energyThreshold);
}
return activity;
}
/**
* Calculate RMS energy of audio window
*/
private calculateEnergy(window: Float32Array): number {
let sum = 0;
for (let i = 0; i < window.length; i++) {
sum += window[i] * window[i];
}
return Math.sqrt(sum / window.length);
}
/**
* Convert Float32Array to base64 for API transmission
*/
pcmToBase64(pcmData: Float32Array): string {
// Convert to 16-bit PCM
const int16Array = new Int16Array(pcmData.length);
for (let i = 0; i < pcmData.length; i++) {
int16Array[i] = Math.max(-32768, Math.min(32767, pcmData[i] * 32767));
}
const buffer = int16Array.buffer;
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* Cleanup resources
*/
dispose(): void {
if (this.audioContext && this.audioContext.state !== 'closed') {
this.audioContext.close();
}
}
}
export const voiceProcessor = new VoiceProcessor();