Skip to content

Commit

Permalink
monkey see monkey do
Browse files Browse the repository at this point in the history
  • Loading branch information
birdup000 committed Dec 28, 2024
1 parent ed56e30 commit 921f35e
Show file tree
Hide file tree
Showing 8 changed files with 516 additions and 27 deletions.
2 changes: 1 addition & 1 deletion app/components/GeminiConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const GeminiConfig: React.FC<GeminiConfigProps> = ({ onClose, onSave }) => {
className="w-full px-3 py-2 bg-[#333333] rounded-md text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
<option value="gemini-pro">Gemini Pro</option>
<option value="gemini-pro-vision">Gemini Pro Vision</option>
<option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Exp</option>
</select>
</div>
<div className="flex justify-end gap-2 pt-4">
Expand Down
1 change: 1 addition & 0 deletions app/components/ModernTaskPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useSearch } from '../hooks/useSearch';
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
import LayoutSettingsPanel from './LayoutSettingsPanel';
import GroupingSettingsPanel from './GroupingSettingsPanel';
import GeminiConfig from './GeminiConfig';

interface ModernTaskPanelProps {
tasks: Task[];
Expand Down
85 changes: 81 additions & 4 deletions app/components/TaskTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { Task, TimeEntry } from '../types/task';
import { ActivityLog } from '../types/collaboration';
import { useTaskTracking } from '../hooks/useTaskTracking';

interface TaskTimerProps {
task: Task;
Expand All @@ -20,6 +21,12 @@ const TaskTimer: React.FC<TaskTimerProps> = ({
const [description, setDescription] = useState<string>('');
const timerRef = useRef<NodeJS.Timeout | null>(null);
const startTimeRef = useRef<Date | null>(null);
const [showPermissionDialog, setShowPermissionDialog] = useState(false);

const { isTracking, hasPermission, error, startTracking, stopTracking, checkPermissions } = useTaskTracking({
task,
onUpdateTask,
});

useEffect(() => {
return () => {
Expand Down Expand Up @@ -51,25 +58,55 @@ const TaskTimer: React.FC<TaskTimerProps> = ({
});
};

const startTimer = () => {
if (!isRunning) {
const startTimer = async () => {
if (isRunning) return;

if (!hasPermission) {
setShowPermissionDialog(true);
try {
await checkPermissions();
// Re-check permission state after permissions check
const permissions = await navigator.permissions.query({ name: 'microphone' as PermissionName });
if (permissions.state !== 'granted') {
console.error('Microphone permission was not granted');
setError && setError('Microphone permission required');
return;
}
} catch (err) {
console.error('Failed to get microphone permission:', err);
setError('Failed to get microphone permission');
return;
} finally {
setShowPermissionDialog(false);
}
}

try {
console.log('Starting timer and tracking...');
const now = new Date();
startTimeRef.current = now;

await startTracking();

setIsRunning(true);
timerRef.current = setInterval(() => {
setCurrentTime(prev => prev + 1);
}, 1000);
console.log('Timer started successfully');
} catch (err) {
console.error('Failed to start timer:', err);
}
};
}

const stopTimer = () => {
const stopTimer = async () => {
const startTime = startTimeRef.current;
if (isRunning && startTime) {
setIsRunning(false);
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
await stopTracking();

const endTime = new Date();
const timeEntry: TimeEntry = {
Expand Down Expand Up @@ -115,6 +152,46 @@ const TaskTimer: React.FC<TaskTimerProps> = ({

return (
<div className={`space-y-4 ${className}`}>
{showPermissionDialog && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-gray-800 p-6 rounded-lg max-w-md mx-4">
<h3 className="text-lg font-medium text-white mb-4">Microphone Permission Required</h3>
<p className="text-gray-300 mb-6">
To enable task tracking features, we need access to your microphone. Please allow access when prompted.
</p>
<div className="flex justify-end gap-3">
<button
onClick={() => setShowPermissionDialog(false)}
className="px-4 py-2 text-gray-300 hover:text-white"
>
Cancel
</button>
<button
onClick={checkPermissions}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white"
>
Allow Access
</button>
</div>
</div>
</div>
)}

{error && (
<div className="bg-red-500/20 border border-red-500/50 text-red-300 px-4 py-3 rounded-lg mb-4">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<p className="text-sm font-medium">{error}</p>
</div>
</div>
</div>
)}

<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-300">Time Tracking</h4>
<span className="text-xs text-gray-400">
Expand Down
12 changes: 12 additions & 0 deletions app/config/audio-processing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const AUDIO_PROCESSING_CONFIG = {
websocket: {
url: process.env.NEXT_PUBLIC_MULTIMODAL_API_ENDPOINT || 'ws://localhost:3001',
reconnectInterval: 5000,
maxRetries: 3,
},
audio: {
sampleRate: 44100,
channels: 1,
bufferSize: 1024,
}
};
84 changes: 84 additions & 0 deletions app/hooks/useAudioContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useState, useEffect, useCallback } from 'react';

interface AudioContextState {
audioContext: AudioContext | null;
stream: MediaStream | null;
source: MediaStreamAudioSourceNode | null;
processor: ScriptProcessorNode | null;
}

export const useAudioContext = () => {
const [state, setState] = useState<AudioContextState>({
audioContext: null,
stream: null,
source: null,
processor: null,
});

const cleanup = useCallback(() => {
if (state.processor) {
state.processor.disconnect();
}
if (state.source) {
state.source.disconnect();
}
if (state.stream) {
state.stream.getTracks().forEach(track => track.stop());
}
if (state.audioContext) {
state.audioContext.close();
}
setState({
audioContext: null,
stream: null,
source: null,
processor: null,
});
}, [state]);

useEffect(() => {
return cleanup;
}, [cleanup]);

const initialize = useCallback(async () => {
try {
console.log('Initializing audio context...');
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 44100
}
});
console.log('Got media stream');

const audioContext = new AudioContext({
sampleRate: 44100,
latencyHint: 'interactive'
});
console.log('Created audio context');

const source = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(2048, 1, 1);

setState({
audioContext,
stream,
source,
processor,
});

return { audioContext, stream, source, processor };
} catch (error) {
console.error('Failed to initialize audio context:', error);
throw error;
}
}, []);

return {
...state,
initialize,
cleanup,
};
};
Loading

0 comments on commit 921f35e

Please sign in to comment.