From bb4e3815d10bb8969df142179c50a6530b1b784d Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Sun, 25 Jan 2026 00:21:06 +0000 Subject: [PATCH] feat(ui): show GitHub stars --- packages/ui/src/App.tsx | 2 + .../src/components/folder-selection-view.tsx | 21 +++++++- packages/ui/src/lib/formatters.ts | 17 ++++++ packages/ui/src/stores/github-stars.ts | 54 +++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/stores/github-stars.ts diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 78488de8..382cbe46 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -10,6 +10,7 @@ import InstanceShell from "./components/instance/instance-shell2" import { RemoteAccessOverlay } from "./components/remote-access-overlay" import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context" import { initMarkdown } from "./lib/markdown" +import { initGithubStars } from "./stores/github-stars" import { useTheme } from "./lib/theme" import { useCommands } from "./lib/hooks/use-commands" @@ -94,6 +95,7 @@ const App: Component = () => { }) onMount(() => { + void initGithubStars() updateInstanceTabBarHeight() const handleResize = () => updateInstanceTabBarHeight() window.addEventListener("resize", handleResize) diff --git a/packages/ui/src/components/folder-selection-view.tsx b/packages/ui/src/components/folder-selection-view.tsx index c4b7a408..1266227a 100644 --- a/packages/ui/src/components/folder-selection-view.tsx +++ b/packages/ui/src/components/folder-selection-view.tsx @@ -1,5 +1,5 @@ import { Component, createSignal, Show, For, onMount, onCleanup, createEffect } from "solid-js" -import { Folder, Clock, Trash2, FolderPlus, Settings, ChevronRight, MonitorUp } from "lucide-solid" +import { Folder, Clock, Trash2, FolderPlus, Settings, ChevronRight, MonitorUp, Star } from "lucide-solid" import { useConfig } from "../stores/preferences" import AdvancedSettingsModal from "./advanced-settings-modal" import DirectoryBrowserDialog from "./directory-browser-dialog" @@ -7,6 +7,8 @@ import Kbd from "./kbd" import { openNativeFolderDialog, supportsNativeDialogs } from "../lib/native/native-functions" import VersionPill from "./version-pill" import { DiscordSymbolIcon, GitHubMarkIcon } from "./brand-icons" +import { githubStars } from "../stores/github-stars" +import { formatCompactCount } from "../lib/formatters" const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href @@ -283,6 +285,23 @@ const FolderSelectionView: Component = (props) => { > + { + event.preventDefault() + openExternalLink("https://github.com/NeuralNomadsAI/CodeNomad") + }} + > + + + {formatCompactCount(githubStars()!)} + + = 1_000_000_000) { + return `${(value / 1_000_000_000).toFixed(1)}B` + } + if (value >= 1_000_000) { + return `${(value / 1_000_000).toFixed(1)}M` + } + if (value >= 10_000) { + return `${Math.round(value / 1_000)}K` + } + if (value >= 1_000) { + const label = `${(value / 1_000).toFixed(1)}K` + return label.replace(/\.0K$/, "K") + } + return value.toLocaleString() +} diff --git a/packages/ui/src/stores/github-stars.ts b/packages/ui/src/stores/github-stars.ts new file mode 100644 index 00000000..3c396765 --- /dev/null +++ b/packages/ui/src/stores/github-stars.ts @@ -0,0 +1,54 @@ +import { createSignal } from "solid-js" +import { getLogger } from "../lib/logger" + +const log = getLogger("api") + +const STORAGE_KEY = "codenomad:github:stars" +const REPO_API_URL = "https://api.github.com/repos/NeuralNomadsAI/CodeNomad" + +function readStoredStars(): number | null { + if (typeof window === "undefined") return null + const raw = window.localStorage.getItem(STORAGE_KEY) + if (!raw) return null + const value = Number(raw) + if (!Number.isFinite(value) || value < 0) return null + return Math.floor(value) +} + +function storeStars(value: number): void { + if (typeof window === "undefined") return + window.localStorage.setItem(STORAGE_KEY, String(value)) +} + +const [githubStars, setGithubStars] = createSignal(readStoredStars()) + +let initialized = false + +export async function initGithubStars(): Promise { + if (initialized) return + initialized = true + + try { + const response = await fetch(REPO_API_URL, { + headers: { + Accept: "application/vnd.github+json", + }, + }) + if (!response.ok) { + throw new Error(`GitHub API returned ${response.status}`) + } + + const data = (await response.json()) as { stargazers_count?: unknown } + const next = typeof data.stargazers_count === "number" ? data.stargazers_count : null + if (next === null || !Number.isFinite(next) || next < 0) { + return + } + const normalized = Math.floor(next) + setGithubStars(normalized) + storeStars(normalized) + } catch (error) { + log.warn("Failed to fetch GitHub stars", error) + } +} + +export { githubStars }