diff --git a/app/components/AIEnhancedRichTextEditor.tsx b/app/components/AIEnhancedRichTextEditor.tsx index 46aded7..7832aa5 100644 --- a/app/components/AIEnhancedRichTextEditor.tsx +++ b/app/components/AIEnhancedRichTextEditor.tsx @@ -9,7 +9,6 @@ import isHotkey from 'is-hotkey'; import { initializeAGiXT } from '../utils/agixt'; import { FormatButton } from './FormatButton'; import { toggleMark } from './editorUtils'; -import AGiXT from 'agixt'; interface AIEnhancedRichTextEditorProps { placeholder?: string; diff --git a/app/components/ModernTaskPanel.tsx b/app/components/ModernTaskPanel.tsx index 2a2181e..dae64b9 100644 --- a/app/components/ModernTaskPanel.tsx +++ b/app/components/ModernTaskPanel.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useState } from 'react'; +import Modal from './ui/Modal'; import { DragDropContext, DropResult } from '@hello-pangea/dnd'; import { Task } from '../types/task'; import TaskPriorityMatrix from './TaskPriorityMatrix'; @@ -17,6 +18,7 @@ import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts'; import LayoutSettingsPanel from './LayoutSettingsPanel'; import GroupingSettingsPanel from './GroupingSettingsPanel'; import GeminiConfig from './GeminiConfig'; +import useLocalStorage from '../hooks/useLocalStorage'; interface ModernTaskPanelProps { tasks: Task[]; @@ -54,12 +56,13 @@ const ModernTaskPanel: React.FC = ({ lists, }) => { // State management - const [selectedTask, setSelectedTask] = useState(null); + const [selectedTask, setSelectedTask] = useState(undefined); const [isEditorOpen, setIsEditorOpen] = useState(false); const [isAGiXTConfigOpen, setIsAGiXTConfigOpen] = useState(false); - const [agixtConfig, setAgixtConfig] = useState({ backendUrl: '', authToken: '' }); + const [agixtConfig, setAgixtConfig] = useLocalStorage<{ backendUrl: string; authToken: string }>('agixtConfig', { backendUrl: '', authToken: '' }); const [geminiConfig, setGeminiConfig] = useState({ apiKey: '', model: 'gemini-pro' }); const [isGeminiConfigOpen, setIsGeminiConfigOpen] = useState(false); + const [isNotesOpen, setIsNotesOpen] = useState(false); const [currentView, setCurrentView] = useState<'board' | 'matrix'>('board'); const [layoutSettings, setLayoutSettings] = useState({ selectedLayout: 'board', @@ -112,18 +115,7 @@ const ModernTaskPanel: React.FC = ({ if (source.droppableId === destination.droppableId) { // Reorder within same status - const statusTasks = tasks.filter(t => t.status === source.droppableId); - const [movedTask] = statusTasks.splice(source.index, 1); - statusTasks.splice(destination.index, 0, movedTask); - - const updatedTasks = tasks.map(t => { - if (t.status === source.droppableId) { - const reorderedTask = statusTasks.find(rt => rt.id === t.id); - return reorderedTask || t; - } - return t; - }); - + const updatedTasks = reorderTasksWithinStatus(tasks, source, destination); onReorderTasks(updatedTasks); } else { // Move to different status @@ -134,6 +126,20 @@ const ModernTaskPanel: React.FC = ({ } }; + const reorderTasksWithinStatus = (tasks: Task[], source: any, destination: any): Task[] => { + const statusTasks = [...tasks.filter(t => t.status === source.droppableId)]; + const [movedTask] = statusTasks.splice(source.index, 1); + statusTasks.splice(destination.index, 0, movedTask); + + return tasks.map(t => { + if (t.status === source.droppableId) { + const reorderedTask = statusTasks.find(rt => rt.id === t.id); + return reorderedTask || t; + } + return t; + }); + }; + // Helper function to get grouped tasks const getGroupedTasks = (status: 'todo' | 'in-progress' | 'done') => { const filteredTasks = filteredAndSortedTasks().filter(task => task.status === status); @@ -268,6 +274,7 @@ const ModernTaskPanel: React.FC = ({ onDeleteTask={onDeleteTask} onReorderTasks={onReorderTasks} listId="default" + agixtConfig={agixtConfig} /> @@ -294,6 +301,7 @@ const ModernTaskPanel: React.FC = ({ onDeleteTask={onDeleteTask} onReorderTasks={onReorderTasks} listId="default" + agixtConfig={agixtConfig} /> @@ -320,6 +328,7 @@ const ModernTaskPanel: React.FC = ({ onDeleteTask={onDeleteTask} onReorderTasks={onReorderTasks} listId="default" + agixtConfig={agixtConfig} /> @@ -341,7 +350,7 @@ const ModernTaskPanel: React.FC = ({ onTaskUpdate={onUpdateTask} onNewTask={onAddTask} geminiApiKey={geminiConfig.apiKey} - selectedTask={selectedTask} + selectedTask={selectedTask ?? undefined} agixtConfig={agixtConfig} /> )} @@ -351,7 +360,7 @@ const ModernTaskPanel: React.FC = ({ authToken={agixtConfig.authToken} onTaskSuggestion={(suggestion) => { const newTask: Task = { - id: Date.now().toString(), + id: crypto.randomUUID(), title: suggestion.title || '', description: suggestion.description || '', priority: suggestion.priority || 'medium', @@ -365,7 +374,14 @@ const ModernTaskPanel: React.FC = ({ comments: [], version: 1, listId: lists[0]?.id || 'default', + dueDate: suggestion.dueDate, + tags: suggestion.tags, }; + + if (lists && lists.length > 0) { + newTask.listId = lists[0].id; + } + onAddTask(newTask); }} onTaskOptimization={(taskIds) => { @@ -375,7 +391,7 @@ const ModernTaskPanel: React.FC = ({ onReorderTasks(optimizedTasks); }} tasks={tasks} - selectedTask={selectedTask} + selectedTask={selectedTask ?? null} /> )} @@ -384,7 +400,7 @@ const ModernTaskPanel: React.FC = ({ {/* Modals */} {isLayoutSettingsOpen && ( -
+ setIsLayoutSettingsOpen(false)}> { @@ -393,11 +409,11 @@ const ModernTaskPanel: React.FC = ({ }} onClose={() => setIsLayoutSettingsOpen(false)} /> -
+ )} {isGroupingSettingsOpen && ( -
+ setIsGroupingSettingsOpen(false)}> { @@ -406,23 +422,23 @@ const ModernTaskPanel: React.FC = ({ }} onClose={() => setIsGroupingSettingsOpen(false)} /> -
+ )} {isAGiXTConfigOpen && ( -
+ setIsAGiXTConfigOpen(false)}> setIsAGiXTConfigOpen(false)} - onSave={(config) => { + onSave={(config: { backendUrl: string; authToken: string }) => { setAgixtConfig(config); setIsAGiXTConfigOpen(false); }} /> -
+ )} {isGeminiConfigOpen && ( -
+ setIsGeminiConfigOpen(false)}> setIsGeminiConfigOpen(false)} onSave={(config) => { @@ -430,11 +446,11 @@ const ModernTaskPanel: React.FC = ({ setIsGeminiConfigOpen(false); }} /> -
+ )} {isEditorOpen && ( -
+ setIsEditorOpen(false)}> { onAddTask(task); @@ -443,7 +459,7 @@ const ModernTaskPanel: React.FC = ({ onCancel={() => setIsEditorOpen(false)} lists={lists} /> -
+ )} {selectedTask && ( @@ -453,6 +469,9 @@ const ModernTaskPanel: React.FC = ({ onUpdateTask={onUpdateTask} allTasks={tasks} className="fixed inset-y-0 right-0 w-[32rem] shadow-xl" + geminiConfig={geminiConfig} + agixtConfig={agixtConfig} + onAddTask={onAddTask} /> )} diff --git a/app/components/RichTextEditor.tsx b/app/components/RichTextEditor.tsx index 89ae303..72eb3b8 100644 --- a/app/components/RichTextEditor.tsx +++ b/app/components/RichTextEditor.tsx @@ -3,7 +3,7 @@ import React, { useMemo, useCallback } from 'react'; import { createEditor, Descendant, Editor, Element as SlateElement, Text, Node as SlateNode } from 'slate'; import { Slate, Editable, withReact, RenderElementProps, RenderLeafProps } from 'slate-react'; -import { CustomEditor, initialValue as defaultInitialValue, HOTKEYS } from './EditorConfig'; +import { CustomEditor, HOTKEYS } from './EditorConfig'; import isHotkey from 'is-hotkey'; interface RichTextEditorProps { diff --git a/app/components/TaskCard.tsx b/app/components/TaskCard.tsx index fa79b9f..55851b6 100644 --- a/app/components/TaskCard.tsx +++ b/app/components/TaskCard.tsx @@ -13,9 +13,10 @@ export interface TaskCardProps { onClick?: () => void; onDelete?: () => void; onUpdateTask?: (task: Task) => void; + agixtConfig?: { backendUrl: string; authToken: string }; } -export const TaskCard: React.FC = ({ task, index = 0, onClick, onDelete, onUpdateTask }) => { +export const TaskCard: React.FC = ({ task, index = 0, onClick, onDelete, onUpdateTask, agixtConfig }) => { const [agixtError, setAgixtError] = useState(null); if (!task) { @@ -64,15 +65,17 @@ export const TaskCard: React.FC = ({ task, index = 0, onClick, on const handleGenerateSubtasksClick = async (e: React.MouseEvent) => { e.stopPropagation(); - const agixtApiUri = localStorage.getItem('agixtapi') || ''; - const agixtApiKey = localStorage.getItem('agixtkey') || ''; - const selectedAgent = localStorage.getItem('selectedAgent') || 'OpenAI'; - if (agixtApiUri && agixtApiKey && selectedAgent && task) { - const updatedTask = await handleGenerateSubtasks(task, selectedAgent, agixtApiUri, agixtApiKey); - if (updatedTask) { - onUpdateTask?.(updatedTask); + if (task && onUpdateTask && agixtConfig && agixtConfig.backendUrl && agixtConfig.authToken) { + const selectedAgent = localStorage.getItem('selectedAgent') || 'OpenAI'; + if (selectedAgent) { + const updatedTask = await handleGenerateSubtasks(task, selectedAgent, agixtConfig.backendUrl, agixtConfig.authToken); + if (updatedTask) { + onUpdateTask(updatedTask); + } + } else { + setAgixtError("AGiXT API URI, API Key, or agent not set."); } - } else { + } else if (task && onUpdateTask) { setAgixtError("AGiXT API URI, API Key, or agent not set."); } }; diff --git a/app/components/TaskDetailsPanel.tsx b/app/components/TaskDetailsPanel.tsx index 484c4cf..e5e2bde 100644 --- a/app/components/TaskDetailsPanel.tsx +++ b/app/components/TaskDetailsPanel.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { Task } from '../types/task'; import NotesEditor from './NotesEditor'; -import TaskProgress from './TaskProgress'; import TaskComments from './TaskComments'; import TaskAttachments from './TaskAttachments'; import TaskTimer from './TaskTimer'; @@ -19,9 +18,11 @@ interface TaskDetailsPanelProps { task: Task | null; onClose: () => void; onUpdateTask: (task: Task) => void; - onAddTask?: (task: Task) => void; + onAddTask: (task: Task) => void; allTasks: Task[]; className?: string; + geminiConfig: { apiKey: string; model: string }; + agixtConfig: { backendUrl: string; authToken: string }; } const TaskDetailsPanel: React.FC = ({ @@ -30,6 +31,9 @@ const TaskDetailsPanel: React.FC = ({ onUpdateTask, allTasks, className = '', + geminiConfig, + agixtConfig, + onAddTask, }) => { if (!task) return null; @@ -386,57 +390,12 @@ const TaskDetailsPanel: React.FC = ({ -
-

- - - - Notes -

-
- { - const updatedNotes = task.notes?.map(note => - note.id === noteId - ? { ...note, linkedTaskIds: [...(note.linkedTaskIds || []), taskId] } - : note - ); - onUpdateTask({ - ...task, - notes: updatedNotes, - activityLog: [ - ...task.activityLog, - { - id: Date.now().toString(), - taskId: task.id, - userId: 'current-user', - action: 'linked_note', - timestamp: new Date(), - }, - ], - }); - }} - /> - onUpdateTask({ - ...task, - description: content, - updatedAt: new Date(), - })} - onCreateTask={(title, description) => { - // Handle task creation from AI suggestions - // This would typically be handled by the parent component - console.log('AI suggested new task:', { title, description }); - }} - /> -
-
); diff --git a/app/components/TaskList.tsx b/app/components/TaskList.tsx index 2c6cf31..8e30a15 100644 --- a/app/components/TaskList.tsx +++ b/app/components/TaskList.tsx @@ -14,6 +14,7 @@ interface TaskListProps { onDeleteTask: (task: Task) => void; onReorderTasks: (tasks: Task[]) => void; listId: string; + agixtConfig?: { backendUrl: string; authToken: string }; } export const TaskList: React.FC = ({ @@ -23,7 +24,8 @@ export const TaskList: React.FC = ({ onTaskClick, onDeleteTask, onReorderTasks, - listId + listId, + agixtConfig }) => { return (
@@ -42,6 +44,7 @@ export const TaskList: React.FC = ({ onUpdateTask={onUpdateTask} onClick={() => onTaskClick(task)} onDelete={() => onDeleteTask(task)} + agixtConfig={agixtConfig ? agixtConfig : undefined} /> ))} {provided.placeholder} diff --git a/app/components/TaskTimer.tsx b/app/components/TaskTimer.tsx index 5711405..95ce9c9 100644 --- a/app/components/TaskTimer.tsx +++ b/app/components/TaskTimer.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useState, useEffect, useRef } from 'react'; +import Modal from './ui/Modal'; import { Task, TimeEntry } from '../types/task'; import { ActivityLog } from '../types/collaboration'; import { useTaskTracking } from '../hooks/useTaskTracking'; @@ -23,7 +24,7 @@ const TaskTimer: React.FC = ({ const startTimeRef = useRef(null); const [showPermissionDialog, setShowPermissionDialog] = useState(false); - const { isTracking, hasPermission, error, startTracking, stopTracking, checkPermissions } = useTaskTracking({ + const { isTracking, hasPermission, error, startTracking, stopTracking, checkPermissions, setError } = useTaskTracking({ task, onUpdateTask, }); @@ -153,28 +154,26 @@ const TaskTimer: React.FC = ({ return (
{showPermissionDialog && ( -
-
-

Microphone Permission Required

-

- To enable task tracking features, we need access to your microphone. Please allow access when prompted. -

-
- - -
+ setShowPermissionDialog(false)}> +

Microphone Permission Required

+

+ To enable task tracking features, we need access to your microphone. Please allow access when prompted. +

+
+ +
-
+ )} {error && ( diff --git a/app/components/VoiceTaskAssistant.tsx b/app/components/VoiceTaskAssistant.tsx index 4c8218a..833d28d 100644 --- a/app/components/VoiceTaskAssistant.tsx +++ b/app/components/VoiceTaskAssistant.tsx @@ -3,13 +3,13 @@ import { Task } from '../types/task'; import { TranscriptionResult, VoiceCommand, ScreenCaptureData } from '../types/recognition'; import AudioPulse from './audio-pulse/AudioPulse'; import { MultimodalLiveClient } from '../lib/multimodal-live-client'; -import AGiXT from 'agixt'; +import { AGiXT as AGiXTSDK } from '../utils/agixt'; interface VoiceTaskAssistantProps { onTaskUpdate: (task: Task) => void; onNewTask: (task: Task) => void; geminiApiKey: string; - selectedTask?: Task; + selectedTask: Task | undefined; agixtConfig: { backendUrl: string; authToken: string; @@ -23,8 +23,8 @@ const VoiceTaskAssistant: React.FC = ({ selectedTask, agixtConfig }) => { - const recognitionRef = useRef(null); - const agixtClientRef = useRef(null); + const recognitionRef = useRef(null); + const agixtClientRef = useRef(null); const [isListening, setIsListening] = useState(false); const [volume, setVolume] = useState(0); const [transcription, setTranscription] = useState(''); @@ -40,19 +40,19 @@ const VoiceTaskAssistant: React.FC = ({ apiKey: geminiApiKey }); - agixtClientRef.current = new AGiXT({ - baseUrl: agixtConfig.backendUrl, - authToken: agixtConfig.authToken + agixtClientRef.current = new AGiXTSDK({ + baseUri: agixtConfig.backendUrl, + apiKey: agixtConfig.authToken }); // Initialize Web Speech API recognition if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) { - const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.continuous = true; recognitionRef.current.interimResults = true; - recognitionRef.current.onresult = (event) => { + recognitionRef.current.onresult = (event: any) => { const result = event.results[event.results.length - 1]; const transcriptionResult: TranscriptionResult = { text: result[0].transcript, @@ -122,7 +122,7 @@ const VoiceTaskAssistant: React.FC = ({ isScreenSharing, confidence: transcription.confidence } - }); + } as any); // Handle the AGiXT response if (response.action === 'CREATE_TASK') { @@ -134,7 +134,13 @@ const VoiceTaskAssistant: React.FC = ({ status: 'todo', createdAt: new Date(), updatedAt: new Date(), - listId: 'default' + listId: 'default', + progress: 0, + owner: 'current-user', + collaborators: [], + activityLog: [], + comments: [], + version: 1, }); } else if (response.action === 'UPDATE_TASK' && selectedTask) { onTaskUpdate({ @@ -163,7 +169,7 @@ const VoiceTaskAssistant: React.FC = ({ // Process screen capture const videoTrack = stream.getVideoTracks()[0]; - const imageCapture = new ImageCapture(videoTrack); + const imageCapture = new (window as any).ImageCapture(videoTrack); // Periodically capture and process screens const captureInterval = setInterval(async () => { @@ -191,7 +197,7 @@ const VoiceTaskAssistant: React.FC = ({ const contextResponse = await agixtClientRef.current.analyzeScreen({ image: imageData, taskContext: selectedTask - }); + } as any); if (contextResponse.needsUpdate) { onTaskUpdate({ diff --git a/app/components/ui/Modal.tsx b/app/components/ui/Modal.tsx new file mode 100644 index 0000000..eaaa9c5 --- /dev/null +++ b/app/components/ui/Modal.tsx @@ -0,0 +1,37 @@ +import React, { ReactNode } from 'react'; + +interface ModalProps { + children: ReactNode; + onClose: () => void; +} + +const Modal: React.FC = ({ children, onClose }) => { + return ( +
+
+ + {children} +
+
+ ); +}; + +export default Modal; \ No newline at end of file diff --git a/app/hooks/useLocalStorage.ts b/app/hooks/useLocalStorage.ts index 9229951..b2ead51 100644 --- a/app/hooks/useLocalStorage.ts +++ b/app/hooks/useLocalStorage.ts @@ -1,29 +1,29 @@ import { useState, useEffect } from 'react'; -function useLocalStorage(key: string, initialValue: string | null): [string | null, (value: string | null) => void] { +function useLocalStorage(key: string, initialValue: T): [T, (value: T) => void] { // Always initialize with initialValue to avoid hydration mismatch - const [storedValue, setStoredValue] = useState(initialValue); + const [storedValue, setStoredValue] = useState(initialValue); // After hydration, sync with localStorage useEffect(() => { if (typeof window !== 'undefined') { try { const item = window.localStorage.getItem(key); - if (item !== storedValue) { - setStoredValue(item ? item : initialValue); + if (item) { + setStoredValue(JSON.parse(item)); } } catch (error) { console.error(error); } } - }, []); + }, [key]); - const setValue = (value: string | null) => { + const setValue = (value: T) => { try { setStoredValue(value); if (typeof window !== 'undefined') { - window.localStorage.setItem(key, value || ''); - window.dispatchEvent(new StorageEvent('storage', { key: key, newValue: value || '' })); + window.localStorage.setItem(key, JSON.stringify(value)); + window.dispatchEvent(new StorageEvent('storage', { key: key, newValue: JSON.stringify(value) })); } } catch (error) { console.error(error); @@ -35,7 +35,9 @@ function useLocalStorage(key: string, initialValue: string | null): [string | nu if (typeof window !== 'undefined') { try { const item = window.localStorage.getItem(key); - setStoredValue(item ? item : initialValue); + if (item) { + setStoredValue(JSON.parse(item)); + } } catch (error) { console.error(error); } @@ -48,7 +50,7 @@ function useLocalStorage(key: string, initialValue: string | null): [string | nu window.removeEventListener('storage', handleStorageChange); }; } - }, [key, initialValue]); + }, [key]); return [storedValue, setValue]; } diff --git a/app/hooks/useTaskTracking.ts b/app/hooks/useTaskTracking.ts index ad52563..c6b65b2 100644 --- a/app/hooks/useTaskTracking.ts +++ b/app/hooks/useTaskTracking.ts @@ -161,6 +161,7 @@ export const useTaskTracking = ({ task, onUpdateTask }: UseTaskTrackingProps) => error, startTracking, stopTracking, - checkPermissions + checkPermissions, + setError }; }; \ No newline at end of file diff --git a/app/hooks/useTasks.ts b/app/hooks/useTasks.ts index 57186ed..742c71b 100644 --- a/app/hooks/useTasks.ts +++ b/app/hooks/useTasks.ts @@ -3,7 +3,7 @@ import { useState, useEffect, useMemo } from 'react'; import { Task, TaskList } from '../types/task'; import { ActivityLog } from '../types/collaboration'; -import { findChanges, mergeTaskChanges } from '../utils/collaboration'; +import { findChanges } from '../utils/collaboration'; import { createStorageManager } from '../utils/storage'; import { StorageConfig } from '../types/storage'; diff --git a/app/types/task.ts b/app/types/task.ts index d002de1..943f2a9 100644 --- a/app/types/task.ts +++ b/app/types/task.ts @@ -81,4 +81,5 @@ export interface Task { export interface TaskList { id: string; name: string; + tasks: Task[]; } diff --git a/app/utils/agixt.ts b/app/utils/agixt.ts index dd3ef32..7b74345 100644 --- a/app/utils/agixt.ts +++ b/app/utils/agixt.ts @@ -34,7 +34,7 @@ const createCheckpoint = async ( } // Assuming AGiXT has a method to store custom data - await agixt.createTaskCheckpoint(taskId, checkpointData); + await agixt.create_checkpoint(taskId, checkpointData); const checkpoint = { id: crypto.randomUUID(), @@ -71,7 +71,7 @@ const loadCheckpoint = async ( } // Assuming AGiXT has a method to retrieve custom data - const checkpointData = await agixt.getTaskCheckpoint( + const checkpointData = await agixt.get_checkpoint( taskId, checkpointId ); @@ -260,7 +260,7 @@ Example format: .replace('{priority}', task.priority || '') .replace('{additionalContext}', ''); - const result = await agixt.prompt( + const result = await agixt.chat( selectedAgent, userInput, conversationName @@ -309,4 +309,4 @@ const updateConversationLog = ( return updatedTask; }; -export { initializeAGiXT, getAgents, handleGenerateSubtasks, handleRunChain, updateConversationLog, parseSubtasks, createCheckpoint, loadCheckpoint }; +export { initializeAGiXT, getAgents, handleGenerateSubtasks, handleRunChain, updateConversationLog, parseSubtasks, createCheckpoint, loadCheckpoint, AGiXT };