Files
claudia/src/components/ClaudeVersionSelector.tsx
Vivek R f5cefb97ba feat: implement sidecar binary support and enhance Claude execution system
- **Enhanced Claude Binary Management**: Added support for sidecar binary execution alongside system binaries
- **Improved Command Creation**: Refactored command creation logic with separate functions for sidecar and system binaries
- **Enhanced Process Management**: Better process lifecycle management with improved error handling
- **Updated Tauri Configuration**: Added shell plugin configuration and expanded security policies
- **Agent Commands**: Enhanced agent management with improved error handling and validation

- **Redesigned Claude Version Selector**: Complete UI overhaul with modern select component and better UX
- **Enhanced Settings Integration**: Improved settings page integration with new selector component
- **API Layer Updates**: Updated API calls to support new binary execution modes
- **UI Component Improvements**: Better visual feedback and loading states

- **Updated Capabilities**: Enhanced Tauri capabilities for better security and functionality
- **Documentation Updates**: Updated scripts README with new build instructions
- **Security Enhancements**: Improved CSP policies and asset protocol configuration

- Added  function to determine execution mode
- Implemented  for sidecar binary execution
- Implemented  for system binary execution
- Enhanced process management with better error handling

- Replaced radio group with modern select component
- Added visual indicators for different installation types
- Improved loading states and error feedback
- Better responsive design and accessibility

- Enhanced CSP policies for better security
- Improved asset protocol configuration
- Better error handling and validation throughout
- Optimized process management and resource usage

- 10 files modified with 647 additions and 208 deletions
- Major changes in Claude execution system and UI components
- Configuration updates for enhanced security and functionality

- All existing functionality preserved
- New sidecar binary support tested
- UI components thoroughly tested for accessibility and responsiveness
2025-07-04 03:27:57 +05:30

304 lines
11 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { api, type ClaudeInstallation } from "@/lib/api";
import { cn } from "@/lib/utils";
import { CheckCircle, Package, HardDrive, Settings } from "lucide-react";
interface ClaudeVersionSelectorProps {
/**
* Currently selected installation path
*/
selectedPath?: string | null;
/**
* Callback when an installation is selected
*/
onSelect: (installation: ClaudeInstallation) => void;
/**
* Optional className for styling
*/
className?: string;
/**
* Whether to show the save button
*/
showSaveButton?: boolean;
/**
* Callback when save is clicked
*/
onSave?: () => void;
/**
* Whether save is in progress
*/
isSaving?: boolean;
}
/**
* ClaudeVersionSelector component for selecting Claude Code installations
* Supports bundled sidecar, system installations, and user preferences
*
* @example
* <ClaudeVersionSelector
* selectedPath={currentPath}
* onSelect={(installation) => setSelectedInstallation(installation)}
* />
*/
export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
selectedPath,
onSelect,
className,
showSaveButton = false,
onSave,
isSaving = false,
}) => {
const [installations, setInstallations] = useState<ClaudeInstallation[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedInstallation, setSelectedInstallation] = useState<ClaudeInstallation | null>(null);
useEffect(() => {
loadInstallations();
}, []);
useEffect(() => {
// Update selected installation when selectedPath changes
if (selectedPath && installations.length > 0) {
const found = installations.find(i => i.path === selectedPath);
if (found) {
setSelectedInstallation(found);
}
}
}, [selectedPath, installations]);
const loadInstallations = async () => {
try {
setLoading(true);
setError(null);
const foundInstallations = await api.listClaudeInstallations();
setInstallations(foundInstallations);
// If we have a selected path, find and select it
if (selectedPath) {
const found = foundInstallations.find(i => i.path === selectedPath);
if (found) {
setSelectedInstallation(found);
}
} else if (foundInstallations.length > 0) {
// Auto-select the first (best) installation
setSelectedInstallation(foundInstallations[0]);
onSelect(foundInstallations[0]);
}
} catch (err) {
console.error("Failed to load Claude installations:", err);
setError(err instanceof Error ? err.message : "Failed to load Claude installations");
} finally {
setLoading(false);
}
};
const handleInstallationChange = (installationPath: string) => {
const installation = installations.find(i => i.path === installationPath);
if (installation) {
setSelectedInstallation(installation);
onSelect(installation);
}
};
const getInstallationIcon = (installation: ClaudeInstallation) => {
switch (installation.installation_type) {
case "Bundled":
return <Package className="h-4 w-4" />;
case "System":
return <HardDrive className="h-4 w-4" />;
case "Custom":
return <Settings className="h-4 w-4" />;
default:
return <HardDrive className="h-4 w-4" />;
}
};
const getInstallationTypeColor = (installation: ClaudeInstallation) => {
switch (installation.installation_type) {
case "Bundled":
return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300";
case "System":
return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300";
case "Custom":
return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300";
default:
return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300";
}
};
if (loading) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>Claude Code Installation</CardTitle>
<CardDescription>Loading available installations...</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
</div>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>Claude Code Installation</CardTitle>
<CardDescription>Error loading installations</CardDescription>
</CardHeader>
<CardContent>
<div className="text-sm text-destructive mb-4">{error}</div>
<Button onClick={loadInstallations} variant="outline" size="sm">
Retry
</Button>
</CardContent>
</Card>
);
}
const bundledInstallations = installations.filter(i => i.installation_type === "Bundled");
const systemInstallations = installations.filter(i => i.installation_type === "System");
const customInstallations = installations.filter(i => i.installation_type === "Custom");
return (
<Card className={className}>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCircle className="h-5 w-5" />
Claude Code Installation
</CardTitle>
<CardDescription>
Choose your preferred Claude Code installation. Bundled version is recommended for best compatibility.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Available Installations */}
<div className="space-y-3">
<Label className="text-sm font-medium">Available Installations</Label>
<Select value={selectedInstallation?.path || ""} onValueChange={handleInstallationChange}>
<SelectTrigger>
<SelectValue placeholder="Select Claude installation">
{selectedInstallation && (
<div className="flex items-center gap-2">
{getInstallationIcon(selectedInstallation)}
<span className="truncate">{selectedInstallation.path}</span>
<Badge variant="secondary" className={cn("text-xs", getInstallationTypeColor(selectedInstallation))}>
{selectedInstallation.installation_type}
</Badge>
</div>
)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{bundledInstallations.length > 0 && (
<>
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">Bundled</div>
{bundledInstallations.map((installation) => (
<SelectItem key={installation.path} value={installation.path}>
<div className="flex items-center gap-2 w-full">
{getInstallationIcon(installation)}
<div className="flex-1 min-w-0">
<div className="font-medium">Claude Code (Bundled)</div>
<div className="text-xs text-muted-foreground">
{installation.version || "Version unknown"} {installation.source}
</div>
</div>
<Badge variant="secondary" className={cn("text-xs", getInstallationTypeColor(installation))}>
Recommended
</Badge>
</div>
</SelectItem>
))}
</>
)}
{systemInstallations.length > 0 && (
<>
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">System Installations</div>
{systemInstallations.map((installation) => (
<SelectItem key={installation.path} value={installation.path}>
<div className="flex items-center gap-2 w-full">
{getInstallationIcon(installation)}
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{installation.path}</div>
<div className="text-xs text-muted-foreground">
{installation.version || "Version unknown"} {installation.source}
</div>
</div>
<Badge variant="outline" className="text-xs">
System
</Badge>
</div>
</SelectItem>
))}
</>
)}
{customInstallations.length > 0 && (
<>
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">Custom Installations</div>
{customInstallations.map((installation) => (
<SelectItem key={installation.path} value={installation.path}>
<div className="flex items-center gap-2 w-full">
{getInstallationIcon(installation)}
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{installation.path}</div>
<div className="text-xs text-muted-foreground">
{installation.version || "Version unknown"} {installation.source}
</div>
</div>
<Badge variant="outline" className="text-xs">
Custom
</Badge>
</div>
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
</div>
{/* Installation Details */}
{selectedInstallation && (
<div className="p-3 bg-muted rounded-lg space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Selected Installation</span>
<Badge className={cn("text-xs", getInstallationTypeColor(selectedInstallation))}>
{selectedInstallation.installation_type}
</Badge>
</div>
<div className="text-sm text-muted-foreground">
<div><strong>Path:</strong> {selectedInstallation.path}</div>
<div><strong>Source:</strong> {selectedInstallation.source}</div>
{selectedInstallation.version && (
<div><strong>Version:</strong> {selectedInstallation.version}</div>
)}
</div>
</div>
)}
{/* Save Button */}
{showSaveButton && (
<Button
onClick={onSave}
disabled={isSaving || !selectedInstallation}
className="w-full"
>
{isSaving ? "Saving..." : "Save Selection"}
</Button>
)}
</CardContent>
</Card>
);
};