import { useState, useEffect, useCallback } from 'react' import { User, Brain, RefreshCw, Wrench, Monitor, AlertTriangle } from 'lucide-react' import { useI18n } from '../i18n' const PANELS = [ { id: 'profile', icon: User }, { id: 'providers', icon: Brain }, { id: 'updates', icon: RefreshCw }, { id: 'skills', icon: Wrench }, { id: 'system', icon: Monitor }, ] export default function Config({ api }) { const { t, language, keyboard, setLanguage, setKeyboard } = useI18n() const [activePanel, setActivePanel] = useState('profile') const [config, setConfig] = useState(null) const [providers, setProviders] = useState([]) const [skillList, setSkillList] = useState([]) const [updates, setUpdates] = useState([]) const [tools, setTools] = useState([]) const [checking, setChecking] = useState(false) const [updating, setUpdating] = useState(null) const [editProfile, setEditProfile] = useState(false) const [editProvider, setEditProvider] = useState(null) const [profileForm, setProfileForm] = useState({}) const [providerForm, setProviderForm] = useState({}) // keyed by provider name const [toast, setToast] = useState(null) const loadData = useCallback(() => { api.getConfig().then(d => { setConfig(d) setProfileForm(d.profile ? JSON.parse(JSON.stringify(d.profile)) : {}) }).catch(() => {}) api.getProviders().then(d => setProviders(d.providers || [])).catch(() => {}) api.getSkills().then(d => setSkillList(d.skills || [])).catch(() => {}) api.getUpdates().then(d => setUpdates(d.updates || [])).catch(() => {}) api.getTools().then(d => setTools(d.tools || [])).catch(() => {}) }, [api]) useEffect(() => { loadData() }, [loadData]) const showToast = (msg) => { setToast(msg) setTimeout(() => setToast(null), 2500) } const handleCheckUpdates = async () => { setChecking(true) try { await api.runScan() const d = await api.getUpdates() setUpdates(d.updates || []) const td = await api.getTools() setTools(td.tools || []) showToast(t('config.upToDate')) } catch (err) { showToast(`${t('config.error')}: ${err.message}`) } setChecking(false) } const handleUpdateTool = (tool) => { window.dispatchEvent(new CustomEvent('navigate-to-shell', {})) window.dispatchEvent(new CustomEvent('ask-ai-terminal', { detail: { message: `Met à jour l'outil ${tool} sur mon système. Exécute les commandes nécessaires.` } })) } const handleUpdateAll = () => { const toUpdate = updates.filter(u => u.needsUpdate).map(u => u.tool) window.dispatchEvent(new CustomEvent('navigate-to-shell', {})) window.dispatchEvent(new CustomEvent('ask-ai-terminal', { detail: { message: `Met à jour tous les outils suivants sur mon système : ${toUpdate.join(', ')}. Exécute les commandes nécessaires une par une.` } })) } const handleSaveProfile = async () => { try { await api.saveProfile(profileForm) setEditProfile(false) loadData() showToast(t('config.saved')) } catch (err) { showToast(`${t('config.error')}: ${err.message}`) } } const handleSaveProvider = async (name) => { const form = providerForm[name] if (!form) return try { await api.saveProvider({ name, ...form }) setEditProvider(null) loadData() showToast(t('config.saved')) } catch (err) { showToast(`${t('config.error')}: ${err.message}`) } } const openProviderEdit = (p) => { setProviderForm(prev => ({ ...prev, [p.name]: { name: p.name, api_key: p.apiKey || '', model: p.model || '', base_url: p.baseURL || '', }, })) setEditProvider(p.name) } const needsUpdateCount = updates.filter(u => u.needsUpdate).length const installedCount = tools.filter(tool => tool.installed).length const missingCount = tools.filter(tool => !tool.installed).length return (
{toast &&
{toast}
}
{PANELS.map(p => { const Icon = p.icon return (
setActivePanel(p.id)} > {t(`config.panels.${p.id}`)}
) })}
{activePanel === 'profile' && ( )} {activePanel === 'providers' && ( )} {activePanel === 'updates' && ( )} {activePanel === 'skills' && ( )} {activePanel === 'system' && ( )}
) } function PanelProfile({ config, editProfile, profileForm, setProfileForm, setEditProfile, handleSaveProfile, t }) { const updateField = (path, value) => { setProfileForm(prev => { const next = JSON.parse(JSON.stringify(prev)) const keys = path.split('.') let target = next for (let i = 0; i < keys.length - 1; i++) { if (target[keys[i]] == null) target[keys[i]] = {} target = target[keys[i]] } target[keys[keys.length - 1]] = value return next }) } const profile = editProfile ? profileForm : config?.profile if (!profile) { return (
{t('config.loadingProfile')}
) } const personalKeys = Object.entries(profile).filter(([k, v]) => k !== 'preferences' && typeof v !== 'object') const personalObj = Object.fromEntries(personalKeys) const preferences = profile.preferences || null return (
{t('config.profileInfo') || 'Informations personnelles'}
{t('config.profilePrefs') || 'Préférences'}
{preferences ? ( ) : (
)}
{editProfile ? ( <> ) : ( )}
) } function RenderFields({ obj, path, editing, onChange, t }) { if (!obj || typeof obj !== 'object') return null return Object.entries(obj).filter(([, v]) => v === null || typeof v !== 'object').map(([key, value]) => { const fieldPath = path ? `${path}.${key}` : key const label = getFieldLabel(key, t) if (editing) { if (typeof value === 'boolean') { return (
{label}
) } if (Array.isArray(value)) { return (
onChange(fieldPath, e.target.value.split(',').map(s => s.trim()).filter(Boolean))} />
) } return (
onChange(fieldPath, typeof value === 'number' ? Number(e.target.value) : e.target.value)} />
) } if (typeof value === 'boolean') { return (
{label} {value ? 'On' : 'Off'}
) } if (Array.isArray(value)) { return (
{label} {value.length > 0 ? value.join(', ') : '—'}
) } return (
{label} {value != null && value !== '' ? String(value) : '—'}
) }) } function getFieldLabel(key, t) { const translated = t(`config.${key}`) if (translated !== `config.${key}`) return translated return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) } function PanelProviders({ providers, editProvider, providerForm, setProviderForm, setEditProvider, openProviderEdit, handleSaveProvider, api, loadData, t }) { const [validating, setValidating] = useState(null) const [keyStatus, setKeyStatus] = useState({}) const validateKey = async (p) => { setValidating(p.name) try { await api.validateProvider({ name: p.name, api_key: p.apiKey, model: p.model, base_url: p.baseURL || '' }) setKeyStatus(prev => ({ ...prev, [p.name]: { valid: true, checked: true } })) } catch (err) { setKeyStatus(prev => ({ ...prev, [p.name]: { valid: false, checked: true, error: err.message || 'Clé invalide' } })) } setValidating(null) } useEffect(() => { providers.forEach(p => { if (p.apiKey && !keyStatus[p.name]) { validateKey(p) } else if (!p.apiKey) { setKeyStatus(prev => ({ ...prev, [p.name]: { valid: false, checked: true, error: 'Aucune clé' } })) } }) }, [providers]) const handleValidate = async (name, apiKey, model, baseUrl) => { setValidating(name) try { await api.validateProvider({ name, api_key: apiKey, model, base_url: baseUrl }) setKeyStatus(prev => ({ ...prev, [name]: { valid: true, checked: true } })) } catch (err) { setKeyStatus(prev => ({ ...prev, [name]: { valid: false, checked: true, error: err.message || 'Clé invalide' } })) } setValidating(null) } const displayed = providers.filter(p => p.name === 'minimax' || p.name === 'zai') return (
{displayed.map((p, i) => { const isEditing = editProvider === p.name const currentModel = providerForm[p.name]?.model || p.model const status = keyStatus[p.name] return (
{p.name.toUpperCase()} {p.active && active} {status?.checked && status?.valid && ✓ {t('config.keyValid')}} {status?.checked && !status?.valid && ✗ {status.error || t('config.keyInvalid')}}
{ if (!isEditing) openProviderEdit(p) setProviderForm(prev => ({ ...prev, [p.name]: { ...(prev[p.name] || {}), api_key: e.target.value }, })) }} />
{isEditing && ( )}
{t('config.model')} {p.model || '—'}
) })}
) } function PanelUpdates({ updates, tools, checking, updating, needsUpdateCount, installedCount, missingCount, handleCheckUpdates, handleUpdateTool, handleUpdateAll, t }) { const handleInstallTool = (tool) => { window.dispatchEvent(new CustomEvent('navigate-to-shell', {})) window.dispatchEvent(new CustomEvent('ask-ai-terminal', { detail: { message: `Installe l'outil ${tool} sur mon système. Vérifie d'abord s'il est déjà installé, puis installe-le si nécessaire avec les commandes appropriées.` } })) } const missingTools = tools.filter(tool => !tool.installed) return ( <>
{installedCount} {t('config.installed')} {missingCount > 0 && {missingCount} {t('config.missing')}} {needsUpdateCount > 0 && {needsUpdateCount} {t('config.needsUpdate')}}
{needsUpdateCount > 0 && ( )}
{missingTools.length > 0 && ( <>
{t('config.missing') || 'Modules manquants'}
{missingTools.map((tool, i) => (
{tool.name} {t('config.notInstalled') || 'Non installé'}
))}
)} {updates.length === 0 ? (
{t('config.noUpdates')}
) : (
{updates.map((u, i) => (
{u.tool} {u.needsUpdate ? ( <>{u.current} → {u.latest} ) : ( {u.current} )}
{u.needsUpdate && ( )}
))}
)} ) } function PanelSkills({ skillList, t }) { const [selected, setSelected] = useState(null) if (skillList.length === 0) { return
{t('config.noSkills')}
} return ( <>
{skillList.map((s, i) => (
setSelected(s)}>
{s.name}
{s.description}
{s.target && {s.target}} {s.version && {s.version}} {s.category && {s.category}}
))}
{selected && (
setSelected(null)}>
e.stopPropagation()}>
{selected.name}
Description
{selected.description}
Métadonnées
{selected.target && {selected.target}} {selected.version && {selected.version}} {selected.category && {selected.category}} {selected.author && {selected.author}} {selected.languages && selected.languages.map(l => {l})}
{selected.tags && selected.tags.length > 0 && (
Tags
{selected.tags.map(tag => {tag})}
)} {selected.content && (
Contenu
{selected.content}
)} {selected.dependencies && selected.dependencies.length > 0 && (
Dépendances
{selected.dependencies.map((d, i) => (
{d.type} {d.name} {d.required === false && optionnel}
))}
)}
)} ) } function PanelSystem({ api, t }) { const [showResetModal, setShowResetModal] = useState(false) const [toast, setToast] = useState(null) const showToast = (msg) => { setToast(msg) setTimeout(() => setToast(null), 3000) } const handleReset = async () => { try { await api.resetConfig() setShowResetModal(false) showToast(t('config.resetDone')) setTimeout(() => window.location.reload(), 1500) } catch (err) { showToast(`${t('config.error')}: ${err.message}`) } } const handleApplyStarship = () => { window.dispatchEvent(new CustomEvent('navigate-to-shell', {})) window.dispatchEvent(new CustomEvent('ask-ai-terminal', { detail: { message: `Vérifie si starship est installé sur le système. S'il ne l'est pas, installe-le (avec curl ou le gestionnaire de paquets). Ensuite, applique la configuration du thème "charm" pour starship. Assure-toi que starship est bien initialisé dans le shell de l'utilisateur.` } })) } return ( <> {toast &&
{toast}
}
Configuration Système
{t('config.applyStarship')}
Vérifie l'installation de starship et configure le thème charm via l'IA.
Zone Rouge
{t('config.resetConfig')}
Cette action supprimera toute votre configuration et relancera l'application.
{showResetModal && (
setShowResetModal(false)}>
e.stopPropagation()}>
{t('config.resetConfig')}

{t('config.resetConfirm')}

Cette action est irréversible. Toute votre configuration (profil, clés API, préférences) sera supprimée.

)} ) } function FormInput({ label, value, onChange, type = 'text' }) { return (
onChange(e.target.value)} />
) }