feat: standalone download page

This commit is contained in:
Faisal Amir 2024-03-17 23:41:23 +07:00
parent 1b6568f665
commit 7d3bf0111b
8 changed files with 329 additions and 253 deletions

View File

@ -334,6 +334,11 @@ const config = {
position: 'left',
label: 'Ecosystem',
},
{
to: 'download',
position: 'left',
label: 'Download',
},
// {
// type: "docSidebar",
// sidebarId: "pricingSidebar",

View File

@ -37,6 +37,7 @@
"postcss": "^8.4.30",
"posthog-docusaurus": "^2.0.0",
"prism-react-renderer": "^1.3.5",
"lucide-react": "^0.291.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.47.0",

View File

@ -15,77 +15,76 @@
const sidebars = {
aboutSidebar: [
{
type: "category",
label: "What is Jan?",
link: { type: "doc", id: "about/about" },
type: 'category',
label: 'What is Jan?',
link: { type: 'doc', id: 'about/about' },
items: [
//"about/roadmap",
"community/community",
'community/community',
],
},
{
type: "category",
label: "Who we are",
link: { type: "doc", id: "team/team" },
items: ["team/join-us", "team/contributor-program"],
type: 'category',
label: 'Who we are',
link: { type: 'doc', id: 'team/team' },
items: ['team/join-us', 'team/contributor-program'],
},
"wall-of-love",
'wall-of-love',
{
type: "category",
label: "How We Work",
link: { type: "doc", id: "how-we-work" },
type: 'category',
label: 'How We Work',
link: { type: 'doc', id: 'how-we-work' },
items: [
"how-we-work/strategy/strategy",
"how-we-work/project-management/project-management",
'how-we-work/strategy/strategy',
'how-we-work/project-management/project-management',
{
type: "category",
label: "Engineering",
link: { type: "doc", id: "how-we-work/engineering/engineering" },
type: 'category',
label: 'Engineering',
link: { type: 'doc', id: 'how-we-work/engineering/engineering' },
items: [
"how-we-work/engineering/ci-cd",
"how-we-work/engineering/qa",
'how-we-work/engineering/ci-cd',
'how-we-work/engineering/qa',
],
},
"how-we-work/product-design/product-design",
"how-we-work/analytics/analytics",
"how-we-work/website-docs/website-docs",
'how-we-work/product-design/product-design',
'how-we-work/analytics/analytics',
'how-we-work/website-docs/website-docs',
],
},
"acknowledgements",
'acknowledgements',
{
type: "category",
label: "FAQ",
link: { type: "doc", id: "about/faq" },
items:
[],
type: 'category',
label: 'FAQ',
link: { type: 'doc', id: 'about/faq' },
items: [],
},
],
productSidebar: [
{
type: "category",
label: "Platforms",
type: 'category',
label: 'Platforms',
collapsible: false,
items: [
"platforms/desktop",
"server-suite/home-server",
'platforms/desktop',
'server-suite/home-server',
// "server-suite/enterprise",
// "platforms/mobile",
// "platforms/hub",
],
},
{
type: "category",
type: 'category',
collapsible: true,
collapsed: false,
label: "Features",
link: { type: "doc", id: "features/features" },
label: 'Features',
link: { type: 'doc', id: 'features/features' },
items: [
"features/local",
"features/remote",
"features/api-server",
"features/extensions-framework",
"features/agents-framework",
"features/data-security",
'features/local',
'features/remote',
'features/api-server',
'features/extensions-framework',
'features/agents-framework',
'features/data-security',
],
},
// NOTE: Jan Server Suite will be torn out into it's own section in the future
@ -103,56 +102,56 @@ const sidebars = {
],
solutionSidebar: [
{
type: "category",
label: "Use Cases",
type: 'category',
label: 'Use Cases',
collapsed: true,
collapsible: true,
items: ["solutions/ai-pc", "solutions/chatgpt-alternative"],
items: ['solutions/ai-pc', 'solutions/chatgpt-alternative'],
},
{
type: "category",
label: "Sectors",
type: 'category',
label: 'Sectors',
collapsed: true,
collapsible: true,
items: [
"solutions/finance",
"solutions/healthcare",
"solutions/legal",
"solutions/government",
'solutions/finance',
'solutions/healthcare',
'solutions/legal',
'solutions/government',
],
},
{
type: "category",
label: "Organization Type",
type: 'category',
label: 'Organization Type',
collapsed: true,
collapsible: true,
items: [
"solutions/developers",
"solutions/consultants",
"solutions/startups",
"solutions/enterprises",
'solutions/developers',
'solutions/consultants',
'solutions/startups',
'solutions/enterprises',
],
},
],
pricingSidebar: ["pricing/pricing"],
pricingSidebar: ['pricing/pricing'],
ecosystemSidebar: [
"ecosystem/ecosystem",
'ecosystem/ecosystem',
{
type: "category",
label: "Partners",
link: { type: "doc", id: "partners/partners" },
type: 'category',
label: 'Partners',
link: { type: 'doc', id: 'partners/partners' },
collapsible: true,
items: ["partners/become-a-partner"],
items: ['partners/become-a-partner'],
},
{
type: "category",
label: "Integrations",
link: { type: "doc", id: "integrations" },
type: 'category',
label: 'Integrations',
link: { type: 'doc', id: 'integrations' },
items: [
{
type: "autogenerated",
dirName: "integrations",
type: 'autogenerated',
dirName: 'integrations',
},
],
},
@ -165,159 +164,154 @@ const sidebars = {
// ],
guidesSidebar: [
{
type: "category",
label: "Get Started",
type: 'category',
label: 'Get Started',
collapsible: false,
className: "head_Menu",
className: 'head_Menu',
items: [
"guides/quickstart",
"guides/install",
"guides/start-server",
"guides/models-list"
]
'guides/quickstart',
'guides/install',
'guides/start-server',
'guides/models-list',
],
},
{
type: "category",
label: "Guides",
type: 'category',
label: 'Guides',
collapsible: false,
className: "head_Menu",
items: [
"guides/best-practices",
"guides/thread",
]
className: 'head_Menu',
items: ['guides/best-practices', 'guides/thread'],
},
{
type: "category",
label: "Advanced Features",
type: 'category',
label: 'Advanced Features',
collapsible: false,
className: "head_Menu",
className: 'head_Menu',
items: [
{
type: "category",
label: "Advanced Settings",
className: "head_SubMenu",
type: 'category',
label: 'Advanced Settings',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/advanced-settings/advanced-settings",
id: 'guides/advanced-settings/advanced-settings',
},
items: [
"guides/advanced-settings/http-proxy",
]
items: ['guides/advanced-settings/http-proxy'],
},
{
type: "category",
label: "Advanced Model Setup",
className: "head_SubMenu",
type: 'category',
label: 'Advanced Model Setup',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/models/README",
id: 'guides/models/README',
},
items: [
"guides/models/customize-engine",
"guides/models/import-models",
"guides/models/integrate-remote",
]
'guides/models/customize-engine',
'guides/models/import-models',
'guides/models/integrate-remote',
],
},
{
type: "category",
label: "Inference Providers",
className: "head_SubMenu",
type: 'category',
label: 'Inference Providers',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/providers/README",
id: 'guides/providers/README',
},
items: [
"guides/providers/llama-cpp",
"guides/providers/tensorrt-llm",
]
'guides/providers/llama-cpp',
'guides/providers/tensorrt-llm',
],
},
{
type: "category",
label: "Extensions",
className: "head_SubMenu",
type: 'category',
label: 'Extensions',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/extensions/README",
id: 'guides/extensions/README',
},
items: [
"guides/extensions/import-ext",
"guides/extensions/setup-ext",
]
'guides/extensions/import-ext',
'guides/extensions/setup-ext',
],
},
{
type: "category",
label: "Integrations",
className: "head_SubMenu",
type: 'category',
label: 'Integrations',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/integration/README",
id: 'guides/integration/README',
},
items: [
"guides/integration/azure",
"guides/integration/discord",
"guides/integration/groq",
"guides/integration/lmstudio",
"guides/integration/mistral",
"guides/integration/ollama",
"guides/integration/openinterpreter",
"guides/integration/openrouter",
"guides/integration/raycast",
"guides/integration/vscode",
]
'guides/integration/azure',
'guides/integration/discord',
'guides/integration/groq',
'guides/integration/lmstudio',
'guides/integration/mistral',
'guides/integration/ollama',
'guides/integration/openinterpreter',
'guides/integration/openrouter',
'guides/integration/raycast',
'guides/integration/vscode',
],
},
]
],
},
{
type: "category",
label: "Troubleshooting",
type: 'category',
label: 'Troubleshooting',
collapsible: false,
className: "head_Menu",
className: 'head_Menu',
items: [
{
type: "category",
label: "Error Codes",
className: "head_SubMenu",
type: 'category',
label: 'Error Codes',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/error-codes/README",
id: 'guides/error-codes/README',
},
items: [
"guides/error-codes/how-to-get-error-logs",
"guides/error-codes/permission-denied",
"guides/error-codes/something-amiss",
"guides/error-codes/undefined-issue",
"guides/error-codes/unexpected-token",
]
'guides/error-codes/how-to-get-error-logs',
'guides/error-codes/permission-denied',
'guides/error-codes/something-amiss',
'guides/error-codes/undefined-issue',
'guides/error-codes/unexpected-token',
],
},
{
type: "category",
label: "Common Error",
className: "head_SubMenu",
type: 'category',
label: 'Common Error',
className: 'head_SubMenu',
link: {
type: 'doc',
id: "guides/common-error/README",
id: 'guides/common-error/README',
},
items: [
"guides/common-error/broken-build",
"guides/common-error/not-using-gpu",
]
'guides/common-error/broken-build',
'guides/common-error/not-using-gpu',
],
},
"guides/faq"
]
'guides/faq',
],
},
],
developerSidebar: [
{
type: "autogenerated",
dirName: "developer",
type: 'autogenerated',
dirName: 'developer',
},
],
releasesSidebar: [
{
type: "autogenerated",
dirName: "releases",
type: 'autogenerated',
dirName: 'releases',
},
]
};
],
}
module.exports = sidebars;
module.exports = sidebars

View File

@ -1,135 +1,169 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import { FaWindows, FaApple, FaLinux } from "react-icons/fa";
import { twMerge } from "tailwind-merge";
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import { FaWindows, FaApple, FaLinux } from 'react-icons/fa'
import { twMerge } from 'tailwind-merge'
import { DownloadIcon } from 'lucide-react'
const systemsTemplate = [
{
name: "Mac M1, M2, M3",
name: 'Mac M1, M2, M3',
label: 'Apple Silicon',
logo: FaApple,
fileFormat: "{appname}-mac-arm64-{tag}.dmg",
comingSoon: false,
fileFormat: '{appname}-mac-arm64-{tag}.dmg',
},
{
name: "Mac (Intel)",
name: 'Mac (Intel)',
label: 'Apple Intel',
logo: FaApple,
fileFormat: "{appname}-mac-x64-{tag}.dmg",
comingSoon: false,
fileFormat: '{appname}-mac-x64-{tag}.dmg',
},
{
name: "Windows",
name: 'Windows',
label: 'Standard (64-bit)',
logo: FaWindows,
fileFormat: "{appname}-win-x64-{tag}.exe",
fileFormat: '{appname}-win-x64-{tag}.exe',
},
{
name: "Linux (AppImage)",
name: 'Linux (AppImage)',
label: 'AppImage',
logo: FaLinux,
fileFormat: "{appname}-linux-x86_64-{tag}.AppImage",
fileFormat: '{appname}-linux-x86_64-{tag}.AppImage',
},
{
name: "Linux (deb)",
name: 'Linux (deb)',
label: 'Deb',
logo: FaLinux,
fileFormat: "{appname}-linux-amd64-{tag}.deb",
fileFormat: '{appname}-linux-amd64-{tag}.deb',
},
];
]
const groupTemnplate = [
{ label: 'MacOS', name: 'mac', logo: FaApple },
{ label: 'Windows', name: 'windows', logo: FaWindows },
{ label: 'Linux', name: 'linux', logo: FaLinux },
]
export default function DownloadApp() {
const [systems, setSystems] = useState(systemsTemplate);
const [systems, setSystems] = useState(systemsTemplate)
const getLatestReleaseInfo = async (repoOwner, repoName) => {
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`;
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`
try {
const response = await axios.get(url);
return response.data;
const response = await axios.get(url)
return response.data
} catch (error) {
console.error(error);
return null;
console.error(error)
return null
}
};
}
const extractAppName = (fileName) => {
// Extract appname using a regex that matches the provided file formats
const regex = /^(.*?)-(?:mac|win|linux)-(?:arm64|x64|amd64|x86_64)-.*$/;
const match = fileName.match(regex);
return match ? match[1] : null;
};
const regex = /^(.*?)-(?:mac|win|linux)-(?:arm64|x64|amd64|x86_64)-.*$/
const match = fileName.match(regex)
return match ? match[1] : null
}
useEffect(() => {
const updateDownloadLinks = async () => {
try {
const releaseInfo = await getLatestReleaseInfo("janhq", "jan");
const releaseInfo = await getLatestReleaseInfo('janhq', 'jan')
// Extract appname from the first asset name
const firstAssetName = releaseInfo.assets[0].name;
const appname = extractAppName(firstAssetName);
const firstAssetName = releaseInfo.assets[0].name
const appname = extractAppName(firstAssetName)
if (!appname) {
console.error(
"Failed to extract appname from file name:",
'Failed to extract appname from file name:',
firstAssetName
);
)
return;
return
}
// Remove 'v' at the start of the tag_name
const tag = releaseInfo.tag_name.startsWith("v")
const tag = releaseInfo.tag_name.startsWith('v')
? releaseInfo.tag_name.substring(1)
: releaseInfo.tag_name;
: releaseInfo.tag_name
const updatedSystems = systems.map((system) => {
const downloadUrl = system.fileFormat
.replace("{appname}", appname)
.replace("{tag}", tag);
.replace('{appname}', appname)
.replace('{tag}', tag)
return {
...system,
href: `https://github.com/janhq/jan/releases/download/${releaseInfo.tag_name}/${downloadUrl}`,
};
});
}
})
setSystems(updatedSystems);
setSystems(updatedSystems)
} catch (error) {
console.error("Failed to update download links:", error);
console.error('Failed to update download links:', error)
}
};
}
updateDownloadLinks();
}, []);
updateDownloadLinks()
}, [])
const renderDownloadLink = (group) => {
return (
<>
{systems
.filter((x) => x.name.toLowerCase().includes(group))
.map((system, i) => (
<div
key={i}
className="border-b border-[#F0F0F0] dark:border-gray-800 last:border-none pb-2 pt-2"
>
<a
href={system.href || ''}
className={twMerge(
'btn-shadow inline-flex text-lg my-2 font-semibold cursor-pointer justify-center items-center space-x-2] text-blue-500 hover:text-blue-500 gap-2',
system.comingSoon && 'pointer-events-none'
)}
>
<span className="text-sm">{system.label}</span>
<DownloadIcon size={16} />
</a>
</div>
))}
</>
)
}
return (
<div>
<div className="flex flex-col lg:flex-row items-center justify-center gap-4 mb-4">
<span className="text-zinc-500 text-lg font-medium inline-block">
Download for PC
</span>
<div className="bg-yellow-50 text-yellow-700 space-x-2 px-4 py-2 border border-yellow-400 rounded-lg text-base">
<span>🚧</span>
<span className="font-semibold">Warning:</span>
<span className="font-medium">
Jan is in the process of being built. Expect bugs!
</span>
<div className="flex flex-col items-center justify-center gap-4 mb-4">
<h6 className="text-2xl font-medium">Detailed platforms</h6>
<p className="leading-relaxed text-black/60 dark:text-white/60">
Jan is in the process of being built. Expect bugs!
</p>
</div>
<div className="w-full lg:w-3/5 mx-auto px-4">
<div className="grid grid-cols-1 lg:grid-cols-3 py-10 gap-8">
{groupTemnplate.map((item, i) => {
return (
<div
className="border border-[#F0F0F0] dark:border-gray-800 rounded-xl text-center"
key={i}
>
<div className="text-center">
<div className="flex gap-2 p-4 border-b border-[#F0F0F0] dark:border-gray-800 items-center justify-center">
<div className="text-2xl">
<item.logo />
</div>
<h6>{item.label}</h6>
</div>
<div className="mx-auto text-center py-2">
{renderDownloadLink(item.name)}
</div>
</div>
</div>
)
})}
</div>
</div>
<div className="mx-auto text-center">
{systems.map((system, i) => (
<a
key={i}
href={system.href || ""}
className={twMerge(
"btn-shadow inline-flex m-2 px-4 rounded-lg text-lg font-semibold cursor-pointer justify-center items-center space-x-2 border border-zinc-200 dark:border-gray-700 text-black dark:text-white bg-zinc-50 min-w-[150px] dark:bg-[#18181B] h-[36px]",
system.comingSoon && "pointer-events-none"
)}
>
<system.logo />
<span className="text-sm">{system.name}</span>
{system.comingSoon && (
<span className="bg-zinc-200 py-0.5 px-2 inline-block ml-2 rounded-md text-xs h-[20px] dark:text-black">
Coming Soon
</span>
)}
</a>
))}
</div>
</div>
);
)
}

View File

@ -36,6 +36,10 @@ const menus = [
{
name: 'Product',
child: [
{
menu: 'Download',
path: '/download',
},
{
menu: 'Documentation',
path: '/developer',

View File

@ -0,0 +1,45 @@
import React, { useState, useEffect } from 'react'
import DownloadApp from '@site/src/containers/DownloadApp'
import ThemedImage from '@theme/ThemedImage'
import Layout from '@theme/Layout'
import Banner from '@site/src/containers/Banner'
import useBaseUrl from '@docusaurus/useBaseUrl'
export default function Download() {
return (
<>
<Banner />
<Layout
title="Download"
description="Jan turns your computer into an AI machine by running LLMs locally on your computer. It's a privacy-focus, local-first, open-source solution."
>
<main>
{/* Hero */}
<div className="text-center px-4 py-20">
<h1 className="text-6xl lg:text-7xl !font-normal leading-tight lg:leading-tight mt-2 font-serif">
Download Jan for your desktop
</h1>
<p className="text-2xl -mt-1 leading-relaxed text-black/60 dark:text-white/60">
Turn your computer into an AI machine
</p>
<div className="my-16">
<ThemedImage
className="w-28 mx-auto h-auto"
alt="App screenshots"
sources={{
light: useBaseUrl('/img/homepage/mac-system-black.svg'),
dark: useBaseUrl('/img/homepage/mac-system-white.svg'),
}}
/>
</div>
<DownloadApp />
</div>
</main>
</Layout>
</>
)
}

View File

@ -87,12 +87,6 @@ export default function Home() {
const { stargazers } = useAppStars()
const { data } = useDiscordWidget()
const handleAnchorLink = () => {
document
.getElementById('download-section')
.scrollIntoView({ behavior: 'smooth' })
}
const userAgent = isBrowser && navigator.userAgent
const isBrowserChrome = isBrowser && userAgent.includes('Chrome')
@ -167,12 +161,12 @@ export default function Home() {
</p>
<div className="mt-10">
{!isBrowserChrome ? (
<div
onClick={() => handleAnchorLink()}
<a
href="/download"
className="inline-flex px-4 py-3 rounded-lg text-lg font-semibold cursor-pointer justify-center items-center space-x-2 dark:bg-white dark:text-black bg-black text-white dark:hover:text-black hover:text-white scroll-smooth"
>
<span>Download Jan for PC</span>
</div>
</a>
) : (
<Dropdown />
)}
@ -811,12 +805,12 @@ export default function Home() {
</div>
<div className="mt-10 w-full lg:w-1/2 mx-auto lg:mr-auto lg:text-right">
{!isBrowserChrome ? (
<div
onClick={() => handleAnchorLink()}
<a
href="/download"
className="inline-flex px-4 py-3 rounded-lg text-lg font-semibold cursor-pointer justify-center items-center space-x-2 dark:bg-white dark:text-black bg-black text-white dark:hover:text-black hover:text-white scroll-smooth"
>
<span>Download Jan for PC</span>
</div>
</a>
) : (
<Dropdown />
)}
@ -826,12 +820,6 @@ export default function Home() {
</div>
</div>
</div>
{!isBrowserChrome && (
<div className="my-10" id="download-section">
<DownloadApp />
</div>
)}
</main>
</Layout>
</>

View File

@ -7097,6 +7097,11 @@ lru-cache@^6.0.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484"
integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==
lucide-react@^0.291.0:
version "0.291.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.291.0.tgz#d6db8f5117a68c8bbc6ed51d800879fa6a471978"
integrity sha512-79jHlT9Je2PXSvXIBGDkItCK7In2O9iKnnSJ/bJxvIBOFaX2Ex0xEcC4fRS/g0F2uQGFejjmn2qWhwdc5wicMQ==
lunr@^2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"