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 { RemoteAccessOverlay } from "./components/remote-access-overlay"
|
||||||
import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context"
|
import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context"
|
||||||
import { initMarkdown } from "./lib/markdown"
|
import { initMarkdown } from "./lib/markdown"
|
||||||
|
import { initGithubStars } from "./stores/github-stars"
|
||||||
|
|
||||||
import { useTheme } from "./lib/theme"
|
import { useTheme } from "./lib/theme"
|
||||||
import { useCommands } from "./lib/hooks/use-commands"
|
import { useCommands } from "./lib/hooks/use-commands"
|
||||||
@@ -94,6 +95,7 @@ const App: Component = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
void initGithubStars()
|
||||||
updateInstanceTabBarHeight()
|
updateInstanceTabBarHeight()
|
||||||
const handleResize = () => updateInstanceTabBarHeight()
|
const handleResize = () => updateInstanceTabBarHeight()
|
||||||
window.addEventListener("resize", handleResize)
|
window.addEventListener("resize", handleResize)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, createSignal, Show, For, onMount, onCleanup, createEffect } from "solid-js"
|
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 { useConfig } from "../stores/preferences"
|
||||||
import AdvancedSettingsModal from "./advanced-settings-modal"
|
import AdvancedSettingsModal from "./advanced-settings-modal"
|
||||||
import DirectoryBrowserDialog from "./directory-browser-dialog"
|
import DirectoryBrowserDialog from "./directory-browser-dialog"
|
||||||
@@ -7,6 +7,8 @@ import Kbd from "./kbd"
|
|||||||
import { openNativeFolderDialog, supportsNativeDialogs } from "../lib/native/native-functions"
|
import { openNativeFolderDialog, supportsNativeDialogs } from "../lib/native/native-functions"
|
||||||
import VersionPill from "./version-pill"
|
import VersionPill from "./version-pill"
|
||||||
import { DiscordSymbolIcon, GitHubMarkIcon } from "./brand-icons"
|
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
|
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" />
|
<GitHubMarkIcon class="w-4 h-4" />
|
||||||
</a>
|
</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
|
<a
|
||||||
href="https://discord.com/channels/1391832426048651334/1458412028325793887/1464701235683917945"
|
href="https://discord.com/channels/1391832426048651334/1458412028325793887/1464701235683917945"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -10,3 +10,20 @@ export function formatTokenTotal(value: number): string {
|
|||||||
}
|
}
|
||||||
return value.toLocaleString()
|
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