mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-01 02:49:51 -04:00
chore(website): derive the counters from data, refresh them weekly
The star, fork, contributor and release counts were typed into the templates by hand, so they only moved when somebody remembered. They had already drifted: stars read 48,042 against 48,067, forks 4,314 against 4,320, and contributors 224 against 225. They move to website/data/stats.yaml, which .github/ci/refresh-site-counters.sh rewrites from the GitHub API, run weekly by a new workflow. The contributors and releases endpoints never report a total, so the script asks for one item per page and reads the count out of the Link header. It refuses to write a zero or a non-number, which is what a rate-limited or failed call looks like, and the workflow commits only when a number actually moved. The Discord count has no API behind it, so the script reads the existing value back and carries it through. The engine count was wrong in a second way. The hero said 18, the section heading said "Eighteen engines", the timeline said "Nineteen engines of our own", and the /engines/ page derived 19 from the data file. All of them now derive from that same file, so they cannot disagree again. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude Code:claude-opus-5[1m]
This commit is contained in:
64
.github/ci/refresh-site-counters.sh
vendored
Executable file
64
.github/ci/refresh-site-counters.sh
vendored
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# Refreshes the counters shown on the landing page from the GitHub API.
|
||||
#
|
||||
# The numbers used to be typed into the templates by hand, which meant they
|
||||
# only moved when somebody remembered, and a stale star count on the front
|
||||
# page is worse than no star count. Everything the API can answer for lives
|
||||
# in website/data/stats.yaml and is rewritten wholesale by this script.
|
||||
#
|
||||
# Anything the API cannot answer for (the Discord member count) is read back
|
||||
# out of the existing file and carried through untouched.
|
||||
set -euo pipefail
|
||||
|
||||
REPO="${REPO:-mudler/LocalAI}"
|
||||
OUT="${OUT:-website/data/stats.yaml}"
|
||||
|
||||
# The contributors and releases endpoints are paginated and never report a
|
||||
# total. Asking for one item per page makes the last page number equal to the
|
||||
# item count, which the Link header hands over.
|
||||
count_via_link_header() {
|
||||
local path="$1" link last
|
||||
link=$(gh api -i "${path}?per_page=1" 2>/dev/null | tr -d '\r' | grep -i '^link:' || true)
|
||||
if [ -z "$link" ]; then
|
||||
# No Link header means a single page, so count that page directly.
|
||||
gh api "${path}?per_page=100" --jq 'length'
|
||||
return
|
||||
fi
|
||||
last=$(sed -n 's/.*[?&]page=\([0-9]*\)>; rel="last".*/\1/p' <<<"$link")
|
||||
[ -n "$last" ] || { gh api "${path}?per_page=100" --jq 'length'; return; }
|
||||
printf '%s\n' "$last"
|
||||
}
|
||||
|
||||
read -r stars forks < <(gh api "repos/${REPO}" --jq '"\(.stargazers_count) \(.forks_count)"')
|
||||
contributors=$(count_via_link_header "repos/${REPO}/contributors")
|
||||
releases=$(count_via_link_header "repos/${REPO}/releases")
|
||||
|
||||
# Not derivable from the GitHub API, so keep whatever is already on disk.
|
||||
discord=$(sed -n 's/^discord: *\([0-9]*\).*/\1/p' "$OUT" 2>/dev/null | head -1)
|
||||
discord="${discord:-0}"
|
||||
|
||||
for n in stars forks contributors releases; do
|
||||
v="${!n}"
|
||||
[[ "$v" =~ ^[0-9]+$ ]] && [ "$v" -gt 0 ] || {
|
||||
echo "refusing to write: ${n} came back as '${v}'" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
cat > "$OUT" <<YAML
|
||||
# Counters shown on the landing page.
|
||||
#
|
||||
# The four GitHub fields are rewritten by .github/ci/refresh-site-counters.sh,
|
||||
# which runs weekly from .github/workflows/refresh-site-counters.yml. Editing
|
||||
# them by hand works but will be overwritten on the next run.
|
||||
stars: ${stars}
|
||||
forks: ${forks}
|
||||
contributors: ${contributors}
|
||||
releases: ${releases}
|
||||
|
||||
# The GitHub API cannot answer for this one, so it is maintained by hand and
|
||||
# the refresh script carries it through untouched.
|
||||
discord: ${discord}
|
||||
YAML
|
||||
|
||||
echo "stars=${stars} forks=${forks} contributors=${contributors} releases=${releases} discord=${discord}"
|
||||
44
.github/workflows/refresh-site-counters.yml
vendored
Normal file
44
.github/workflows/refresh-site-counters.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Refresh site counters
|
||||
|
||||
# The landing page shows a star count, a contributor count and a release
|
||||
# count. They were typed in by hand, so they drifted the moment somebody
|
||||
# forgot. This pulls the real numbers once a week and commits them only when
|
||||
# they have actually moved, which in turn triggers the usual Pages deploy.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Mondays, 06:17 UTC. Off the hour on purpose, since the scheduler queues
|
||||
# everything that asks for :00 and drops what it cannot run.
|
||||
- cron: '17 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: refresh-site-counters
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
refresh:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Read the counts off the GitHub API
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./.github/ci/refresh-site-counters.sh
|
||||
|
||||
- name: Commit only if something moved
|
||||
run: |
|
||||
if git diff --quiet -- website/data/stats.yaml; then
|
||||
echo "counters unchanged, nothing to commit"
|
||||
exit 0
|
||||
fi
|
||||
git diff --unified=0 -- website/data/stats.yaml
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add website/data/stats.yaml
|
||||
git commit -m "chore(website): refresh the counters"
|
||||
git push
|
||||
13
website/data/stats.yaml
Normal file
13
website/data/stats.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Counters shown on the landing page.
|
||||
#
|
||||
# The four GitHub fields are rewritten by .github/ci/refresh-site-counters.sh,
|
||||
# which runs weekly from .github/workflows/refresh-site-counters.yml. Editing
|
||||
# them by hand works but will be overwritten on the next run.
|
||||
stars: 48067
|
||||
forks: 4320
|
||||
contributors: 225
|
||||
releases: 133
|
||||
|
||||
# The GitHub API cannot answer for this one, so it is maintained by hand and
|
||||
# the refresh script carries it through untouched.
|
||||
discord: 3187
|
||||
@@ -16,9 +16,8 @@ enableEmoji = true
|
||||
discord = 'https://discord.gg/uJAeKSAGDy'
|
||||
x = 'https://twitter.com/LocalAI_API'
|
||||
huggingface = 'https://huggingface.co/mudler'
|
||||
# Refreshed by hand or by CI; shown in the nav and the traction band.
|
||||
stars = '48,042'
|
||||
contributors = '224'
|
||||
# Counters live in data/stats.yaml, which .github/ci/refresh-site-counters.sh
|
||||
# rewrites weekly. They used to sit here as hand-typed strings and drifted.
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<a class="btn btn--o" href="{{ .Site.Params.github }}">★ Star on GitHub</a>
|
||||
</div>
|
||||
<div class="figures fd">
|
||||
<div><b class="tnum" data-count="48042">0</b><span>GitHub stars</span></div>
|
||||
<div><b class="tnum" data-count="{{ .Site.Data.stats.stars }}">0</b><span>GitHub stars</span></div>
|
||||
<div><b class="tnum" data-count="73">0</b><span>Backends</span></div>
|
||||
<div><b class="tnum" data-count="18">0</b><span>Engines we wrote</span></div>
|
||||
<div><b class="tnum" data-count="{{ len .Site.Data.engines.engines }}">0</b><span>Engines we wrote</span></div>
|
||||
<div><b class="tnum" data-count="1585">0</b><span>Models, one click</span></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,7 +164,7 @@
|
||||
<div class="shell">
|
||||
<div class="bars rv" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
|
||||
<p class="kicker rv">Engines we build</p>
|
||||
<h2 class="rv mt1" style="max-width:22ch">Eighteen engines, written from scratch.</h2>
|
||||
<h2 class="rv mt1" style="max-width:22ch">{{ len .Site.Data.engines.engines }} engines, written from scratch.</h2>
|
||||
<p class="lede rv mt2">Most backends wrap somebody else's engine. These do not. They exist because the thing we needed was a 9 GB Python install, or closed, or nobody had built it yet. Each one is a binary and a GGUF file, checked against the reference implementation in CI.</p>
|
||||
|
||||
<div class="spot rv">
|
||||
@@ -247,7 +247,7 @@
|
||||
<video src="/media/vllm-race.mp4" muted loop playsinline preload="none" data-lazy aria-label="vllm.cpp ahead of vLLM at every concurrency level"></video>
|
||||
</div>
|
||||
|
||||
<p class="rv mt2"><a class="btn btn--o" href="/engines/">All eighteen engines →</a></p>
|
||||
<p class="rv mt2"><a class="btn btn--o" href="/engines/">All {{ len .Site.Data.engines.engines }} engines →</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -363,14 +363,14 @@
|
||||
<h2 class="rv mt1" style="max-width:22ch">Forty-eight thousand stars, and still shipping every week.</h2>
|
||||
<div class="trend rv">
|
||||
<img src="/img/trendshift.svg" alt="LocalAI on Trendshift">
|
||||
<p>LocalAI has been <b>trending on GitHub</b> repeatedly since it launched, and it is one of the most starred self-hosted AI projects there is. <b>224 people</b> have contributed code, <b>3,187</b> are in the Discord, and the README is kept translated into <b>eight languages</b> because the users are everywhere.</p>
|
||||
<p>LocalAI has been <b>trending on GitHub</b> repeatedly since it launched, and it is one of the most starred self-hosted AI projects there is. <b>{{ lang.FormatNumberCustom 0 .Site.Data.stats.contributors }} people</b> have contributed code, <b>{{ lang.FormatNumberCustom 0 .Site.Data.stats.discord }}</b> are in the Discord, and the README is kept translated into <b>eight languages</b> because the users are everywhere.</p>
|
||||
</div>
|
||||
<div class="big rv">
|
||||
<div><b class="tnum" data-count="48042">0</b><span>Stars</span></div>
|
||||
<div><b class="tnum" data-count="4314">0</b><span>Forks</span></div>
|
||||
<div><b class="tnum" data-count="224">0</b><span>Contributors</span></div>
|
||||
<div><b class="tnum" data-count="133">0</b><span>Releases</span></div>
|
||||
<div><b class="tnum" data-count="3187">0</b><span>In Discord</span></div>
|
||||
<div><b class="tnum" data-count="{{ .Site.Data.stats.stars }}">0</b><span>Stars</span></div>
|
||||
<div><b class="tnum" data-count="{{ .Site.Data.stats.forks }}">0</b><span>Forks</span></div>
|
||||
<div><b class="tnum" data-count="{{ .Site.Data.stats.contributors }}">0</b><span>Contributors</span></div>
|
||||
<div><b class="tnum" data-count="{{ .Site.Data.stats.releases }}">0</b><span>Releases</span></div>
|
||||
<div><b class="tnum" data-count="{{ .Site.Data.stats.discord }}">0</b><span>In Discord</span></div>
|
||||
<div><b class="tnum" data-count="0" data-text="3 yrs">0</b><span>Shipping since</span></div>
|
||||
</div>
|
||||
<div class="tl rv">
|
||||
@@ -421,7 +421,7 @@
|
||||
employers reads as a customer logo wall, which is a claim we are not
|
||||
making; a sentence keeps it about the people, which is the true one. */}}
|
||||
<p class="kicker rv mt3">Who shows up</p>
|
||||
<h3 class="eco__h rv">{{ .Site.Params.contributors }} people have put code in this repository.</h3>
|
||||
<h3 class="eco__h rv">{{ lang.FormatNumberCustom 0 .Site.Data.stats.contributors }} people have put code in this repository.</h3>
|
||||
{{- $co := slice }}
|
||||
{{- range .Site.Data.ecosystem.contributors.companies }}{{ $co = $co | append (printf "<b>%s</b>" .name) }}{{ end }}
|
||||
{{- $ac := slice }}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<a href="{{ .Site.Params.docsURL }}">Docs</a>
|
||||
</nav>
|
||||
<div class="topright">
|
||||
<a class="pill" href="{{ .Site.Params.github }}">★ {{ .Site.Params.stars }}</a>
|
||||
<a class="pill" href="{{ .Site.Params.github }}">★ {{ lang.FormatNumberCustom 0 .Site.Data.stats.stars }}</a>
|
||||
<a class="go-btn" href="{{ $home }}#start">Install</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user