Update package dependencies, add Git API routes, and implement audio transcription functionality. Introduce new components for Git management, enhance chat interface with microphone support, and improve UI elements for better user experience.

This commit is contained in:
Simos
2025-07-04 11:30:14 +02:00
parent 845d5346eb
commit 3b0a612c9c
18 changed files with 3495 additions and 360 deletions

View File

@@ -3,8 +3,9 @@ import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button';
import { Badge } from './ui/badge';
import { Input } from './ui/input';
import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw } from 'lucide-react';
import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw, Sparkles, Edit2 } from 'lucide-react';
import { cn } from '../lib/utils';
import ClaudeLogo from './ClaudeLogo';
// Move formatTimeAgo outside component to avoid recreation on every render
const formatTimeAgo = (dateString, currentTime) => {
@@ -56,6 +57,9 @@ function Sidebar({
const [initialSessionsLoaded, setInitialSessionsLoaded] = useState(new Set());
const [currentTime, setCurrentTime] = useState(new Date());
const [isRefreshing, setIsRefreshing] = useState(false);
const [editingSession, setEditingSession] = useState(null);
const [editingSessionName, setEditingSessionName] = useState('');
const [generatingSummary, setGeneratingSummary] = useState({});
// Touch handler to prevent double-tap issues on iPad
const handleTouchClick = (callback) => {
@@ -601,7 +605,7 @@ function Sidebar({
<>
{getAllSessions(project).length === 0 && (
<button
className="w-8 h-8 rounded-lg bg-red-500/10 dark:bg-red-900/30 flex items-center justify-center active:scale-90 transition-all duration-150 border border-red-200 dark:border-red-800"
className="w-8 h-8 rounded-lg bg-red-500/10 dark:bg-red-900/30 flex items-center justify-center active:scale-90 border border-red-200 dark:border-red-800"
onClick={(e) => {
e.stopPropagation();
deleteProject(project.name);
@@ -612,7 +616,7 @@ function Sidebar({
</button>
)}
<button
className="w-8 h-8 rounded-lg bg-primary/10 dark:bg-primary/20 flex items-center justify-center active:scale-90 transition-all duration-150 border border-primary/20 dark:border-primary/30"
className="w-8 h-8 rounded-lg bg-primary/10 dark:bg-primary/20 flex items-center justify-center active:scale-90 border border-primary/20 dark:border-primary/30"
onClick={(e) => {
e.stopPropagation();
startEditing(project);
@@ -639,7 +643,7 @@ function Sidebar({
<Button
variant="ghost"
className={cn(
"hidden md:flex w-full justify-between p-2 h-auto font-normal hover:bg-accent/50 transition-colors duration-200",
"hidden md:flex w-full justify-between p-2 h-auto font-normal hover:bg-accent/50",
isSelected && "bg-accent text-accent-foreground"
)}
onClick={() => {
@@ -781,14 +785,27 @@ function Sidebar({
<p className="text-xs text-muted-foreground">No sessions yet</p>
</div>
) : (
getAllSessions(project).map((session) => (
getAllSessions(project).map((session) => {
// Calculate if session is active (within last 10 minutes)
const sessionDate = new Date(session.lastActivity);
const diffInMinutes = Math.floor((currentTime - sessionDate) / (1000 * 60));
const isActive = diffInMinutes < 10;
return (
<div key={session.id} className="group relative">
{/* Active session indicator dot */}
{isActive && (
<div className="absolute left-0 top-1/2 transform -translate-y-1/2 -translate-x-1">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
</div>
)}
{/* Mobile Session Item */}
<div className="md:hidden">
<div
className={cn(
"p-2 mx-3 my-0.5 rounded-md bg-card border border-border/30 active:scale-[0.98] transition-all duration-150 relative",
selectedSession?.id === session.id && "bg-primary/5 border-primary/20"
"p-2 mx-3 my-0.5 rounded-md bg-card border active:scale-[0.98] transition-all duration-150 relative",
selectedSession?.id === session.id ? "bg-primary/5 border-primary/20" :
isActive ? "border-green-500/30 bg-green-50/5 dark:bg-green-900/5" : "border-border/30"
)}
onClick={() => {
onProjectSelect(project);
@@ -871,22 +888,99 @@ function Sidebar({
</div>
</div>
</Button>
{/* Desktop delete button */}
<button
className="absolute right-2 top-1/2 transform -translate-y-1/2 w-6 h-6 opacity-0 group-hover:opacity-100 transition-all duration-200 bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/40 rounded flex items-center justify-center touch:opacity-100"
onClick={(e) => {
e.stopPropagation();
deleteSession(project.name, session.id);
}}
title="Delete session (Delete)"
>
<Trash2 className="w-3 h-3 text-red-600 dark:text-red-400" />
</button>
{/* Desktop hover buttons */}
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-all duration-200">
{editingSession === session.id ? (
<>
<input
type="text"
value={editingSessionName}
onChange={(e) => setEditingSessionName(e.target.value)}
onKeyDown={(e) => {
e.stopPropagation();
if (e.key === 'Enter') {
updateSessionSummary(project.name, session.id, editingSessionName);
} else if (e.key === 'Escape') {
setEditingSession(null);
setEditingSessionName('');
}
}}
onClick={(e) => e.stopPropagation()}
className="w-32 px-2 py-1 text-xs border border-border rounded bg-background focus:outline-none focus:ring-1 focus:ring-primary"
autoFocus
/>
<button
className="w-6 h-6 bg-green-50 hover:bg-green-100 dark:bg-green-900/20 dark:hover:bg-green-900/40 rounded flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
updateSessionSummary(project.name, session.id, editingSessionName);
}}
title="Save"
>
<Check className="w-3 h-3 text-green-600 dark:text-green-400" />
</button>
<button
className="w-6 h-6 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900/20 dark:hover:bg-gray-900/40 rounded flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
setEditingSession(null);
setEditingSessionName('');
}}
title="Cancel"
>
<X className="w-3 h-3 text-gray-600 dark:text-gray-400" />
</button>
</>
) : (
<>
{/* Generate summary button */}
{/* <button
className="w-6 h-6 bg-blue-50 hover:bg-blue-100 dark:bg-blue-900/20 dark:hover:bg-blue-900/40 rounded flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
generateSessionSummary(project.name, session.id);
}}
title="Generate AI summary for this session"
disabled={generatingSummary[`${project.name}-${session.id}`]}
>
{generatingSummary[`${project.name}-${session.id}`] ? (
<div className="w-3 h-3 animate-spin rounded-full border border-blue-600 dark:border-blue-400 border-t-transparent" />
) : (
<Sparkles className="w-3 h-3 text-blue-600 dark:text-blue-400" />
)}
</button> */}
{/* Edit button */}
<button
className="w-6 h-6 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900/20 dark:hover:bg-gray-900/40 rounded flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
setEditingSession(session.id);
setEditingSessionName(session.summary || 'New Session');
}}
title="Manually edit session name"
>
<Edit2 className="w-3 h-3 text-gray-600 dark:text-gray-400" />
</button>
{/* Delete button */}
<button
className="w-6 h-6 bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/40 rounded flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
deleteSession(project.name, session.id);
}}
title="Delete this session permanently"
>
<Trash2 className="w-3 h-3 text-red-600 dark:text-red-400" />
</button>
</>
)}
</div>
</div>
</div>
))
);
})
)}
{/* Show More Sessions Button */}
{getAllSessions(project).length > 0 && project.sessionMeta?.hasMore !== false && (
<Button