feat(ui): show GitHub stars
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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<FolderSelectionViewProps> = (props) => {
|
||||
>
|
||||
<GitHubMarkIcon class="w-4 h-4" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/NeuralNomadsAI/CodeNomad"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="selector-button selector-button-secondary w-auto px-3 py-1.5 inline-flex items-center justify-center gap-1.5"
|
||||
aria-label="CodeNomad GitHub Stars"
|
||||
title="CodeNomad GitHub Stars"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
openExternalLink("https://github.com/NeuralNomadsAI/CodeNomad")
|
||||
}}
|
||||
>
|
||||
<Star class="w-4 h-4" />
|
||||
<Show when={githubStars() !== null}>
|
||||
<span class="text-xs font-medium">{formatCompactCount(githubStars()!)}</span>
|
||||
</Show>
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.com/channels/1391832426048651334/1458412028325793887/1464701235683917945"
|
||||
target="_blank"
|
||||
|
||||
@@ -10,3 +10,20 @@ export function formatTokenTotal(value: number): string {
|
||||
}
|
||||
return value.toLocaleString()
|
||||
}
|
||||
|
||||
export function formatCompactCount(value: number): string {
|
||||
if (value >= 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()
|
||||
}
|
||||
|
||||
54
packages/ui/src/stores/github-stars.ts
Normal file
54
packages/ui/src/stores/github-stars.ts
Normal file
@@ -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<number | null>(readStoredStars())
|
||||
|
||||
let initialized = false
|
||||
|
||||
export async function initGithubStars(): Promise<void> {
|
||||
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 }
|
||||
Reference in New Issue
Block a user