Download desktop/src/components/shared/UpdateChecker.tsx from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 4.38 kB
-
https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/shared/UpdateChecker.tsx
- Command line
-
hf download hf://chenbhao/codev@e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/shared/UpdateChecker.tsx
-
curl -L -o UpdateChecker.tsx https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/shared/UpdateChecker.tsx
4.38 kB
| import { useEffect } from 'react' | |
| import { useTranslation } from '../../i18n' | |
| import { MarkdownRenderer } from '../markdown/MarkdownRenderer' | |
| import { isTauriRuntime } from '../../lib/desktopRuntime' | |
| import { useUpdateStore } from '../../stores/updateStore' | |
| import { formatBytes } from '../../lib/formatBytes' | |
| export function UpdateChecker() { | |
| const t = useTranslation() | |
| const status = useUpdateStore((s) => s.status) | |
| const availableVersion = useUpdateStore((s) => s.availableVersion) | |
| const releaseNotes = useUpdateStore((s) => s.releaseNotes) | |
| const progressPercent = useUpdateStore((s) => s.progressPercent) | |
| const downloadedBytes = useUpdateStore((s) => s.downloadedBytes) | |
| const totalBytes = useUpdateStore((s) => s.totalBytes) | |
| const error = useUpdateStore((s) => s.error) | |
| const shouldPrompt = useUpdateStore((s) => s.shouldPrompt) | |
| const initialize = useUpdateStore((s) => s.initialize) | |
| const installUpdate = useUpdateStore((s) => s.installUpdate) | |
| const dismissPrompt = useUpdateStore((s) => s.dismissPrompt) | |
| useEffect(() => { | |
| void initialize() | |
| }, [initialize]) | |
| if (!isTauriRuntime()) return null | |
| const showPopup = | |
| shouldPrompt && !!availableVersion && ['available', 'downloading', 'restarting'].includes(status) | |
| if (!showPopup) return null | |
| const hasKnownProgress = typeof totalBytes === 'number' && totalBytes > 0 | |
| const downloadedText = formatBytes(downloadedBytes) | |
| const statusText = | |
| status === 'restarting' | |
| ? t('update.restarting') | |
| : status === 'downloading' | |
| ? hasKnownProgress | |
| ? t('update.downloading') | |
| : t('update.progressBytes', { downloaded: downloadedText }) | |
| : null | |
| return ( | |
| <div className="fixed top-4 right-4 z-[200] max-w-sm"> | |
| <div className="bg-[var(--color-surface-container-low)] border border-[var(--color-border)] rounded-[var(--radius-lg)] shadow-[var(--shadow-dropdown)] p-4"> | |
| <p className="text-sm font-medium text-[var(--color-text-primary)]"> | |
| {t('update.available', { version: availableVersion })} | |
| </p> | |
| {releaseNotes && ( | |
| <div className="mt-2 max-h-40 overflow-y-auto rounded-lg border border-[var(--color-border)]/60 bg-[var(--color-surface)]/70 px-3 py-2"> | |
| <MarkdownRenderer | |
| content={releaseNotes} | |
| className="text-xs leading-5 text-[var(--color-text-secondary)] [&_h1]:mb-2 [&_h1]:text-sm [&_h1]:font-semibold [&_h2]:mb-1.5 [&_h2]:text-xs [&_h2]:font-semibold [&_p]:my-1.5 [&_p]:text-xs [&_p]:leading-5 [&_ul]:my-1.5 [&_ol]:my-1.5" | |
| /> | |
| </div> | |
| )} | |
| {(status === 'downloading' || status === 'restarting') && ( | |
| <div className="mt-3"> | |
| <div className="h-1.5 bg-[var(--color-surface)] rounded-full overflow-hidden"> | |
| {hasKnownProgress || status === 'restarting' ? ( | |
| <div | |
| className="h-full bg-[var(--color-text-accent)] transition-all duration-300" | |
| style={{ width: `${Math.min(progressPercent, 100)}%` }} | |
| /> | |
| ) : ( | |
| <div className="h-full w-1/3 rounded-full bg-[var(--color-text-accent)]/75 animate-pulse" /> | |
| )} | |
| </div> | |
| {statusText && ( | |
| <p className="text-xs text-[var(--color-text-tertiary)] mt-1"> | |
| {statusText} | |
| {status === 'downloading' && hasKnownProgress ? ` ${progressPercent}%` : ''} | |
| </p> | |
| )} | |
| </div> | |
| )} | |
| {error && ( | |
| <p className="mt-2 text-xs text-[var(--color-error)]"> | |
| {t('update.failed', { error })} | |
| </p> | |
| )} | |
| {status === 'available' && ( | |
| <div className="mt-3 flex gap-2"> | |
| <button | |
| onClick={() => void installUpdate()} | |
| className="px-3 py-1 text-xs font-medium rounded-[var(--radius-md)] bg-[var(--color-text-accent)] text-white hover:opacity-90 transition-opacity" | |
| > | |
| {t('update.now')} | |
| </button> | |
| <button | |
| onClick={dismissPrompt} | |
| className="px-3 py-1 text-xs text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors" | |
| > | |
| {t('update.later')} | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ) | |
| } | |