enhancement: show app size on download dropdown (#5360)

* enhancement: show app size on download dropdown

* Update docs/src/utils/format.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chor: update copy

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
Faisal Amir 2025-06-19 12:04:16 +07:00 committed by GitHub
parent 8c507e5569
commit 85d32a4c70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 8 deletions

View File

@ -3,6 +3,7 @@ import { IconType } from 'react-icons/lib'
import { FaWindows, FaApple, FaLinux } from 'react-icons/fa' import { FaWindows, FaApple, FaLinux } from 'react-icons/fa'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { DownloadIcon } from 'lucide-react' import { DownloadIcon } from 'lucide-react'
import { formatFileSize } from '@/utils/format'
type Props = { type Props = {
lastRelease: any lastRelease: any
@ -14,6 +15,7 @@ type SystemType = {
logo: IconType logo: IconType
fileFormat: string fileFormat: string
href?: string href?: string
size?: string
} }
const systemsTemplate: SystemType[] = [ const systemsTemplate: SystemType[] = [
@ -84,9 +86,16 @@ export default function CardDownload({ lastRelease }: Props) {
const downloadUrl = system.fileFormat const downloadUrl = system.fileFormat
.replace('{appname}', appname) .replace('{appname}', appname)
.replace('{tag}', tag) .replace('{tag}', tag)
// Find the corresponding asset to get the file size
const asset = lastRelease.assets.find(
(asset: any) => asset.name === downloadUrl
)
return { return {
...system, ...system,
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`, href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
size: asset ? formatFileSize(asset.size) : undefined,
} }
}) })
@ -118,6 +127,11 @@ export default function CardDownload({ lastRelease }: Props) {
> >
<span>{system.label}</span> <span>{system.label}</span>
<DownloadIcon size={16} /> <DownloadIcon size={16} />
{system.size && (
<div className="text-sm text-black/60 dark:text-white/60">
{system.size}
</div>
)}
</a> </a>
</div> </div>
))} ))}

View File

@ -4,6 +4,7 @@ import { IconType } from 'react-icons/lib'
import { IoChevronDownOutline } from 'react-icons/io5' import { IoChevronDownOutline } from 'react-icons/io5'
import { useClickOutside } from '@/hooks/useClickOutside' import { useClickOutside } from '@/hooks/useClickOutside'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { formatFileSize } from '@/utils/format'
type Props = { type Props = {
lastRelease: any lastRelease: any
@ -14,6 +15,7 @@ type SystemType = {
logo: IconType logo: IconType
fileFormat: string fileFormat: string
href?: string href?: string
size?: string
} }
type GpuInfo = { type GpuInfo = {
@ -130,6 +132,7 @@ const DropdownDownload = ({ lastRelease }: Props) => {
const updateDownloadLinks = async () => { const updateDownloadLinks = async () => {
try { try {
const firstAssetName = await lastRelease.assets[0]?.name const firstAssetName = await lastRelease.assets[0]?.name
const appname = extractAppName(firstAssetName) const appname = extractAppName(firstAssetName)
if (!appname) { if (!appname) {
console.error( console.error(
@ -147,9 +150,16 @@ const DropdownDownload = ({ lastRelease }: Props) => {
const downloadUrl = system.fileFormat const downloadUrl = system.fileFormat
.replace('{appname}', appname) .replace('{appname}', appname)
.replace('{tag}', tag) .replace('{tag}', tag)
// Find the corresponding asset to get the file size
const asset = lastRelease.assets.find(
(asset: any) => asset.name === downloadUrl
)
return { return {
...system, ...system,
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`, href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
size: asset ? formatFileSize(asset.size) : undefined,
} }
}) })
setSystems(updatedSystems) setSystems(updatedSystems)
@ -176,10 +186,15 @@ const DropdownDownload = ({ lastRelease }: Props) => {
<div className="inline-flex flex-shrink-0 justify-center relative"> <div className="inline-flex flex-shrink-0 justify-center relative">
<a <a
href={defaultSystem.href} href={defaultSystem.href}
className="dark:border-r-0 dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 pl-4 pr-6 py-4 rounded-l-xl inline-flex items-center !rounded-r-none" className="min-w-[300px] dark:border-r-0 dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white dark:border dark:border-neutral-800 flex-shrink-0 pl-4 pr-6 py-4 rounded-l-xl inline-flex items-center !rounded-r-none"
> >
<defaultSystem.logo className="h-4 mr-2" /> <defaultSystem.logo className="h-4 mr-2" />
{defaultSystem.name} <span>{defaultSystem.name}</span>
{defaultSystem.size && (
<span className="text-white/60 text-sm ml-2">
({defaultSystem.size})
</span>
)}
</a> </a>
<button <button
className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border border-l border-gray-500 dark:border-neutral-800 flex-shrink-0 p-4 px-3 rounded-r-xl" className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border border-l border-gray-500 dark:border-neutral-800 flex-shrink-0 p-4 px-3 rounded-r-xl"
@ -192,18 +207,27 @@ const DropdownDownload = ({ lastRelease }: Props) => {
</button> </button>
{open && ( {open && (
<div <div
className="absolute left-0 top-[64px] w-full dark:nx-bg-neutral-900 bg-black z-30 rounded-xl lg:w-[300px]" className="absolute left-0 top-[64px] w-full dark:nx-bg-neutral-900 bg-black z-30 rounded-xl lg:w-[380px]"
ref={setRefDropdownContent} ref={setRefDropdownContent}
> >
{systems.map((system) => ( {systems.map((system) => (
<div key={system.name} className="py-1"> <div key={system.name} className="py-1">
<a <a
href={system.href || ''} href={system.href || ''}
className="flex px-4 py-3 items-center text-white hover:text-white hover:bg-white/10 dark:hover:bg-white/5" className="flex px-4 py-3 items-center text-white hover:text-white hover:bg-white/10 dark:hover:bg-white/5 justify-between"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
<system.logo className="w-3 mr-3 -mt-1 flex-shrink-0" /> <div className="flex items-center">
<span className="text-white font-medium">{system.name}</span> <system.logo className="w-3 mr-3 -mt-1 flex-shrink-0" />
<span className="text-white font-medium flex-1">
{system.name}
</span>
</div>
{system.size && (
<span className="text-white/60 text-sm ml-2">
{system.size}
</span>
)}
</a> </a>
</div> </div>
))} ))}

View File

@ -44,7 +44,7 @@ const features = [
{ {
title: 'Chat with your files', title: 'Chat with your files',
experimantal: true, experimantal: true,
description: `Set up and run your own OpenAI-compatible API server using local models with just one click.`, description: `Talk to PDFs, notes, and other documents directly to get summaries, answers, or insights.`,
image: { image: {
light: '/assets/images/homepage/features05.png', light: '/assets/images/homepage/features05.png',
dark: '/assets/images/homepage/features05dark.png', dark: '/assets/images/homepage/features05dark.png',

View File

@ -1,8 +1,22 @@
export function formatCompactNumber(count: number) { export function formatCompactNumber(count: number) {
const formatter = Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }) const formatter = Intl.NumberFormat('en', {
notation: 'compact',
maximumFractionDigits: 1,
})
return formatter.format(count) return formatter.format(count)
} }
export function formatFileSize(bytes: number): string {
if (!bytes) return '0 B'
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(1024))
if (i === 0) return `${bytes} ${sizes[i]}`
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`
}
export const totalDownload = (release: []) => { export const totalDownload = (release: []) => {
if (release instanceof Array) { if (release instanceof Array) {
const count = release const count = release