import { useStore } from '@nanostores/react' import { useEffect, useState } from 'react' import { BrandMark } from '@/components/brand-mark' import { Button } from '@/components/ui/button' import { Codicon } from '@/components/ui/codicon' import { type Translations, useI18n } from '@/i18n' import { useAiturkCopy } from '@/i18n/aiturk' import { AlertTriangle, CheckCircle2, ExternalLink, Loader2, RefreshCw } from '@/lib/icons' import { cn } from '@/lib/utils' import { $desktopVersion, $updateApply, $updateChecking, $updateStatus, checkUpdates, openUpdatesWindow, refreshDesktopVersion, startActiveUpdate } from '@/store/updates' import { ListRow, SectionHeading, SettingsContent } from './primitives' import { UninstallSection } from './uninstall-section' const RELEASE_NOTES_URL = 'https://gitea.turkservis.online/yilsem/aiturk-hermes-ide/releases' const INSTALLER_URL = 'https://turkservis.online/ide' function relativeTime(ms: number | undefined, a: Translations['settings']['about']) { if (!ms) { return a.never } const diff = Date.now() - ms if (diff < 60_000) { return a.justNow } if (diff < 3_600_000) { return a.minAgo(Math.round(diff / 60_000)) } if (diff < 86_400_000) { return a.hoursAgo(Math.round(diff / 3_600_000)) } return a.daysAgo(Math.round(diff / 86_400_000)) } export function AboutSettings() { const { t } = useI18n() const copy = useAiturkCopy() const a = t.settings.about const version = useStore($desktopVersion) const status = useStore($updateStatus) const apply = useStore($updateApply) const checking = useStore($updateChecking) const [justChecked, setJustChecked] = useState(false) // The version atom is loaded once at app boot, which makes About show a // stale number after a self-update (the running binary is current, the // displayed string is not). Re-read on mount so opening About always // reflects the running build. useEffect(() => { void refreshDesktopVersion() }, []) const behind = status?.behind ?? 0 // behind is null when the exact count is unknowable (shallow clone): the // backend flags that case via updateAvailable instead of a number. const updateAvailable = behind > 0 || Boolean(status?.updateAvailable) const supported = status?.supported !== false const applying = apply.applying || apply.stage === 'restart' const handleCheck = async () => { setJustChecked(false) const next = await checkUpdates() setJustChecked(Boolean(next)) } let statusLine: string let statusTone: 'idle' | 'available' | 'error' = 'idle' if (!supported) { statusLine = status?.message ?? a.cantUpdate statusTone = 'error' } else if (status?.error) { statusLine = a.cantReach statusTone = 'error' } else if (applying) { statusLine = a.installing statusTone = 'available' } else if (updateAvailable) { statusLine = behind > 0 ? a.updateReady(behind) : a.updateReadyUnknown statusTone = 'available' } else if (status) { statusLine = a.onLatest } else { statusLine = a.tapCheck } return (

AITURK IDE

{copy.about}

{copy.downloads} {copy.source}

{a.heading}

{version?.appVersion ? a.version(version.appVersion) : a.versionUnavailable}

{(version?.bundleOutOfSync || version?.bundleSwapPending) && (
{version?.bundleSwapPending ? ( // The updated app is already on disk — the updater swapped it // under this running process — so a restart loads it. Saying // "App build out of date" here would repeat the contradiction // this banner is meant to resolve: the Updates card below // already reports the runtime as current. <>

{a.bundleSwapPending}

{a.bundleSwapPendingDesc}

) : ( <>

{a.bundleOutOfSync}

{a.bundleOutOfSyncDesc}

)}
)}
{statusTone === 'available' ? ( ) : statusTone === 'error' ? null : ( )}

{statusLine}

{a.lastChecked(relativeTime(status?.fetchedAt, a))} {justChecked && !checking ? a.justNowSuffix : ''}

{updateAvailable && supported && !applying && ( <> )}
) }