agent-ecosystem/src/renderer/components/settings/sections/AdvancedSection.tsx
matt 540aefc3d3 Implement auto-update functionality and enhance build configuration
- Integrated electron-updater for automatic updates, including IPC handlers for checking, downloading, and installing updates.
- Updated electron-builder configuration to include entitlements and notarization scripts for macOS.
- Enhanced README with installation instructions and updated features.
- Added new components for update notifications and dialogs in the renderer.
- Improved CI workflow permissions for better release management.
2026-02-11 21:23:40 +09:00

177 lines
6 KiB
TypeScript

/**
* AdvancedSection - Advanced settings including config management and about info.
*/
import { useCallback, useEffect, useRef, useState } from 'react';
import appIcon from '@renderer/favicon.png';
import { useStore } from '@renderer/store';
import { CheckCircle, Code2, Download, Loader2, RefreshCw, Upload } from 'lucide-react';
import { SettingsSectionHeader } from '../components';
interface AdvancedSectionProps {
readonly saving: boolean;
readonly onResetToDefaults: () => void;
readonly onExportConfig: () => void;
readonly onImportConfig: () => void;
readonly onOpenInEditor: () => void;
}
export const AdvancedSection = ({
saving,
onResetToDefaults,
onExportConfig,
onImportConfig,
onOpenInEditor,
}: AdvancedSectionProps): React.JSX.Element => {
const [version, setVersion] = useState<string>('');
const updateStatus = useStore((s) => s.updateStatus);
const availableVersion = useStore((s) => s.availableVersion);
const checkForUpdates = useStore((s) => s.checkForUpdates);
// Auto-revert "not-available" / "error" status back to idle after a brief display
const revertTimerRef = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => {
if (updateStatus === 'not-available' || updateStatus === 'error') {
revertTimerRef.current = setTimeout(() => {
useStore.setState({ updateStatus: 'idle' });
}, 3000);
}
return () => {
if (revertTimerRef.current) clearTimeout(revertTimerRef.current);
};
}, [updateStatus]);
useEffect(() => {
window.electronAPI.getAppVersion().then(setVersion).catch(console.error);
}, []);
const handleCheckForUpdates = useCallback(() => {
checkForUpdates();
}, [checkForUpdates]);
const getUpdateButtonContent = (): React.JSX.Element => {
switch (updateStatus) {
case 'checking':
return (
<>
<Loader2 className="size-3.5 animate-spin" />
Checking...
</>
);
case 'not-available':
return (
<>
<CheckCircle className="size-3.5" />
Up to date
</>
);
case 'available':
case 'downloaded':
return (
<>
<Download className="size-3.5" />
{updateStatus === 'downloaded' ? 'Update ready' : `v${availableVersion} available`}
</>
);
default:
return (
<>
<RefreshCw className="size-3.5" />
Check for Updates
</>
);
}
};
return (
<div>
<SettingsSectionHeader title="Configuration" />
<div className="space-y-2 py-2">
<button
onClick={onResetToDefaults}
disabled={saving}
className={`flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2.5 text-sm font-medium transition-all duration-150 ${saving ? 'cursor-not-allowed opacity-50' : ''} `}
style={{
borderColor: 'var(--color-border)',
color: 'var(--color-text-secondary)',
}}
>
<RefreshCw className="size-4" />
Reset to Defaults
</button>
<button
onClick={onExportConfig}
disabled={saving}
className={`flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2.5 text-sm font-medium transition-all duration-150 ${saving ? 'cursor-not-allowed opacity-50' : ''} `}
style={{
borderColor: 'var(--color-border)',
color: 'var(--color-text-secondary)',
}}
>
<Download className="size-4" />
Export Config
</button>
<button
onClick={onImportConfig}
disabled={saving}
className={`flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2.5 text-sm font-medium transition-all duration-150 ${saving ? 'cursor-not-allowed opacity-50' : ''} `}
style={{
borderColor: 'var(--color-border)',
color: 'var(--color-text-secondary)',
}}
>
<Upload className="size-4" />
Import Config
</button>
<button
onClick={onOpenInEditor}
className="flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2.5 text-sm font-medium transition-all duration-150"
style={{
borderColor: 'var(--color-border)',
color: 'var(--color-text-secondary)',
}}
>
<Code2 className="size-4" />
Open in Editor
</button>
</div>
<SettingsSectionHeader title="About" />
<div className="flex items-start gap-4 py-3">
<img src={appIcon} alt="App Icon" className="size-10 rounded-lg" />
<div>
<div className="flex items-center gap-3">
<p className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
Claude Code Context
</p>
<button
onClick={handleCheckForUpdates}
disabled={updateStatus === 'checking'}
className="flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium transition-colors hover:bg-white/5 disabled:opacity-50"
style={{
borderColor: 'var(--color-border)',
color:
updateStatus === 'not-available'
? 'var(--color-text-muted)'
: updateStatus === 'available' || updateStatus === 'downloaded'
? '#60a5fa'
: 'var(--color-text-secondary)',
}}
>
{getUpdateButtonContent()}
</button>
</div>
<p className="mt-0.5 text-xs" style={{ color: 'var(--color-text-muted)' }}>
Version {version || '...'}
</p>
<p className="mt-2 text-xs leading-relaxed" style={{ color: 'var(--color-text-muted)' }}>
Visualize and analyze Claude Code session executions with interactive waterfall charts
and detailed insights.
</p>
</div>
</div>
</div>
);
};