* feat(docs): mirror every docs/ page in all 65 locales Extends the documentation mirrors from the 22-page core set (#13940) to every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors (6,208 new), language bars rewritten for the full locale list, state adopted so the blocking drift gate now covers all 152 pages. run-translation.mjs: an oversized block made only of table rows or list items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is cut at item boundaries and rejoined without a blank line — the single 16-40 KB request outlived the backend socket for verbose scripts. 48 older mirrors whose tables had lost rows were retranslated with --force. * docs(i18n): refresh mirrors for the sources the base changed since the branch cut Section-level retranslation of the 29 docs (and README.md) whose source or mirrors moved on release/v3.8.51 during the run, then state adoption; the drift gate is green again on the merged tree.
52 KiB
Gamification & Leaderboard System (Hrvatski)
🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
Izvor istine:
src/lib/gamification/,src/lib/db/gamification.ts,src/app/api/gamification/Posljednje ažuriranje: 2026-06-28 — v3.8.40
OmniRoute uključuje sloj gamifikacije s lokalnim pristupom koji nagrađuje korisnike za interakciju s platformom — slanje zahtjeva, promjenu pružatelja usluga, stvaranje kombinacija, dijeljenje tokena i doprinos zajednici. Cjelokupno stanje pohranjeno je u bazi SQLite; povezivanje s poslužiteljima zajednice opcionalno je i temelji se na prosljeđivanju podataka.
Sustav je osmišljen tako da ima nultu latenciju na kritičnom putu — događaji gamifikacije odašilju se bez čekanja iz cjevovoda zahtjeva i nikada ne blokiraju odgovor LLM-a.
Pregled
Svrha
Povećati angažman i zadržavanje korisnika pružanjem vidljivog napretka (XP, razine, značke), društvene potvrde (ljestvice poretka) i ekonomskih poticaja (dijeljenje tokena, nagrade za pozivanje).
Opseg
| Značajka | Opis |
|---|---|
| XP i razine | Stjecanje XP-a po radnji; napredovanje po polinomnoj krivulji |
| Značke | Više od 20 postignuća u 5 kategorija s 4 razine rijetkosti |
| Nizovi | Dnevno praćenje aktivne uporabe s trenutačnim/najduljim nizom |
| Ljestvice poretka | Globalni, tjedni, mjesečni, dijeljenje tokena i doprinosi |
| Dijeljenje tokena | Prijenos kredita između korisnika putem dvojnog knjigovodstva |
| Pozivanje i iskorištavanje | Kodovi preporuke s pohranom sažetaka SHA-256 |
| Poslužitelji zajednice | Federiranje s vanjskim instancama OmniRoutea |
| Zaštita od varanja | Bodovanje na strani poslužitelja, ograničavanje učestalosti i otkrivanje anomalija pomoću z-vrijednosti |
Načela dizajna
- Lokalni pristup — cjelokupno stanje nalazi se u bazi SQLite, bez potrebe za vanjskim uslugama.
- Neblokirajuće — događaji se odašilju bez čekanja; put odgovora LLM-a nikada nije usporen logikom gamifikacije.
- Mjerodavnost poslužitelja — XP se izračunava isključivo na strani poslužitelja; klijenti ne mogu umjetno povećavati rezultate.
- Poštovanje privatnosti — sudjelovanje na ljestvici poretka opcionalno je; korisnici mogu sakriti svoj profil.
- Spremno za federiranje — poslužitelji zajednice mogu slati rezultate putem potpisanog API-ja; sinkronizacija prepisuje podatke umjesto da ih pribraja.
Arhitektura
Tijek visoke razine
Zahtjev klijenta
→ /v1/chat/completions
→ handleChatCore() [open-sse/handlers/chatCore.ts]
→ ... (postojeći cjevovod) ...
→ odgovor nadređenog poslužitelja poslan klijentu
→ setImmediate (bez čekanja):
→ emitGamificationEvent() [src/lib/gamification/events.ts]
→ awardXp() [src/lib/gamification/xp.ts]
→ updateStreak() [src/lib/gamification/streaks.ts]
→ evaluateBadges() [src/lib/gamification/badges.ts]
→ updateLeaderboard() [src/lib/gamification/leaderboard.ts]
→ checkAnomalies() [src/lib/gamification/antiCheat.ts]
Odašiljač događaja jedinstvena je integracijska točka. chatCore.ts poziva
emitGamificationEvent() nakon slanja odgovora; modul događaja zatim ga
prosljeđuje podsustavima za XP, nizove, značke, ljestvice poretka i zaštitu od varanja.
Graf ovisnosti modula
src/lib/gamification/
events.ts ← ulazna točka (poziva se iz chatCore.ts)
├── xp.ts ← izračun XP-a i određivanje razine
├── streaks.ts ← praćenje dnevnog niza aktivnosti
├── badges.ts ← provjera kriterija za značke
├── leaderboard.ts ← izračun poretka i SSE emitiranje
├── antiCheat.ts ← ograničavanje učestalosti i otkrivanje anomalija
├── sharing.ts ← evidencija prijenosa tokena
├── invites.ts ← upravljanje kodovima za pozivanje/iskorištavanje
├── servers.ts ← federiranje poslužitelja zajednice
└── notifications.ts ← SSE tok obavijesti
src/lib/db/
gamification.ts ← sve CRUD operacije (8 tablica)
src/app/api/gamification/
leaderboard/ ← GET rang-lista, POST ručno osvježavanje
leaderboard/stream ← SSE ažuriranja u stvarnom vremenu
transfer/ ← GET povijest, POST slanje tokena
invite/ ← GET/POST kodovi, DELETE opoziv
invite/redeem/ ← POST iskorištavanje koda
servers/ ← GET/POST/DELETE poslužitelji zajednice
federation/score/ ← POST slanje rezultata poslužitelju
federation/leaderboard/ ← GET dohvaćanje ljestvice poretka s poslužitelja
notifications/ ← SSE obavijesti o značkama/napredovanju razine
anomalies/ ← GET izvješća o anomalijama (administrator)
rotate/ ← POST rotacija tajni tokena za pozivanje
Podatkovni sloj
Tablice baze podataka
Sve tablice nalaze se u glavnoj SQLite bazi podataka OmniRoute, stvorenoj migracijom
060_create_gamification.sql. WAL vođenje dnevnika nasljeđuje se iz singletona
getDbInstance() u datoteci src/lib/db/core.ts.
┌─────────────────────────┐ ┌──────────────────────────┐
│ leaderboard │ │ user_levels │
├─────────────────────────┤ ├──────────────────────────┤
│ id TEXT PK │ │ api_key_id TEXT PK │
│ api_key_id TEXT │ │ xp INTEGER │
│ scope TEXT │ │ level INTEGER │
│ score INTEGER │ │ title TEXT │
│ period TEXT │ │ updated_at TEXT │
│ updated_at TEXT │ └──────────────────────────┘
└─────────────────────────┘
│
│ 1:N
▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ user_badges │ │ badge_definitions │
├─────────────────────────┤ ├──────────────────────────┤
│ id TEXT PK │ │ id TEXT PK │
│ api_key_id TEXT │ │ name TEXT │
│ badge_id TEXT FK │ │ category TEXT │
│ earned_at TEXT │ │ rarity TEXT │
│ notified INTEGER │ │ criteria_type TEXT │
└─────────────────────────┘ │ criteria TEXT(JSON) │
│ description TEXT │
│ icon TEXT │
│ hidden INTEGER │
└──────────────────────────┘
┌─────────────────────────┐ ┌──────────────────────────┐
│ xp_audit_log │ │ token_ledger │
├─────────────────────────┤ ├──────────────────────────┤
│ id TEXT PK │ │ id TEXT PK │
│ api_key_id TEXT │ │ from_key_id TEXT │
│ action TEXT │ │ to_key_id TEXT │
│ xp_awarded INTEGER │ │ amount INTEGER │
│ metadata TEXT(JSON)│ │ idempotency_key TEXT UQ │
│ created_at TEXT │ │ created_at TEXT │
└─────────────────────────┘ └──────────────────────────┘
┌─────────────────────────┐ ┌──────────────────────────┐
│ invite_tokens │ │ community_servers │
├─────────────────────────┤ ├──────────────────────────┤
│ id TEXT PK │ │ id TEXT PK │
│ api_key_id TEXT │ │ name TEXT │
│ code TEXT UQ │ │ url TEXT │
│ token_hash TEXT │ │ token_hash TEXT │
│ uses INTEGER │ │ status TEXT │
│ max_uses INTEGER │ │ last_sync TEXT │
│ created_at TEXT │ │ created_at TEXT │
│ expires_at TEXT │ └──────────────────────────┘
└─────────────────────────┘
Domenski modul: src/lib/db/gamification.ts
Slijedi standardni obrazac OmniRoute — uvozi getDbInstance() iz
core.ts i izvozi tipizirane CRUD funkcije. U obrađivačima ruta nema sirovog SQL-a.
Ključne funkcije:
| Funkcija | Opis |
|---|---|
upsertLeaderboardEntry() |
Umeće ili ažurira rezultat za (api_key_id, scope, period) |
getLeaderboard() |
Straničene ljestvice za zadani opseg/razdoblje |
getUserLevel() |
Dohvaća ili stvara zapis o razini korisnika |
updateUserLevel() |
Atomski postavlja XP, razinu i titulu |
getBadgeDefinitions() |
Sve definicije znački (uz opcionalno filtriranje) |
getUserBadges() |
Značke koje je korisnik osvojio |
awardBadge() |
Umeće zapis o osvajanju značke (idempotentno prema badge_id) |
logXpAction() |
Dodaje zapis u xp_audit_log |
getXpAuditLog() |
Straničena povijest revizije za korisnika |
insertLedgerEntry() |
Prijenos s dvostrukim knjiženjem (unutar transakcije) |
getBalance() |
Zbroj primljenih sredstava umanjen za poslana sredstva korisnika |
getTransferHistory() |
Straničeni dnevnik prijenosa |
createInviteToken() |
Umeće pozivni kod i sažetak tokena |
redeemInviteToken() |
Traži prema kodu, provjerava valjanost i povećava broj upotreba |
upsertCommunityServer() |
Registrira ili ažurira poslužitelj federacije |
getCommunityServers() |
Navodi poslužitelje korisnika |
deleteCommunityServer() |
Uklanja registraciju poslužitelja |
Sustav XP-a / razina
Datoteka: src/lib/gamification/xp.ts
Krivulja razina
XP potreban za dosezanje razine n slijedi polinomnu krivulju:
xp_for_level(n) = floor(100 * n^1.5)
| Razina | XP do sljedeće razine | Kumulativni XP | Titula |
|---|---|---|---|
| 1 | 100 | 100 | Početnik |
| 5 | 1,118 | 2,415 | Početnik |
| 10 | 3,162 | 10,523 | Istraživač |
| 25 | 12,500 | 86,024 | Istraživač |
| 50 | 35,355 | 345,529 | Stručnjak |
| 75 | 64,952 | 948,683 | Majstor |
| 100 | 100,000 | 2,050,000 | Legenda |
Titule
| Raspon razina | Titula |
|---|---|
| 1 – 9 | Početnik |
| 10 – 24 | Istraživač |
| 25 – 49 | Stručnjak |
| 50 – 74 | Majstor |
| 75 – 100 | Legenda |
XP nagrade
| Radnja | XP | Opis |
|---|---|---|
request |
1 | Za svaki API zahtjev usmjeren kroz OmniRoute |
provider_switch |
5 | Prebacivanje na drugog pružatelja usluge |
model_switch |
3 | Prebacivanje na drugi model |
combo_create |
10 | Stvaranje nove kombinacije |
combo_use |
2 | Upotreba kombinacije za zahtjev |
token_share |
1 | Za svakih 1 000 tokena podijeljenih s drugim korisnikom |
invite_redeem |
50 | Iskorištavanje pozivnog koda |
daily_login |
5 | Dnevna aktivna upotreba (jednom dnevno) |
streak_bonus |
2 | Za svaki uzastopni dan niza (pomnoženo s duljinom niza) |
badge_unlock |
10 | Otključavanje značke |
Tijek dodjele
export async function awardXp(
apiKeyId: string,
action: XpAction,
metadata?: Record<string, unknown>
): Promise<{ xp: number; level: number; title: string; levelUp: boolean }>;
- Potražite
XP_REWARDS[action]kako biste dobili količinu XP-a. - Proslijedite kroz
checkRateLimit()(zaštita od varanja: najviše 1000 XP-a/min po ključu). - Otvorite transakciju:
- Pročitajte trenutačni redak
user_levels. - Dodajte XP; ponovno izračunajte razinu putem
levelFromXp(totalXp). - Ako se razina promijenila, postavite
levelUp = true. - Ažurirajte redak
user_levels. - Umetnite zapis u
xp_audit_log.
- Pročitajte trenutačni redak
- Vratite rezultat. Pozivatelj obrađuje obavijesti.
Pomoćna funkcija: levelFromXp(totalXp)
Iterira kroz razine 1..100, zbrajajući xp_for_level(n) sve dok kumulativni XP
ne premaši totalXp. Vraća najvišu razinu čiji je prag dosegnut.
Složenost je O(100) — prihvatljivo jer su razine ograničene na 100.
Sustav znački
Datoteka: src/lib/gamification/badges.ts
Kategorije
| Kategorija | Opis | Primjeri znački |
|---|---|---|
usage |
Ključne prekretnice temeljene na količini | Prvi zahtjev, 1K zahtjeva, 100K |
sharing |
Dijeljenje tokena i preporuke | Prvo dijeljenje, Velikodušan (10 dijeljenja) |
contribution |
Sudjelovanje u zajednici | Kreator kombinacija, Istraživač pružatelja |
streak |
Dosljednost tijekom vremena | Tjedni ratnik, Mjesečno posvećen |
rare |
Teško dostupna ili skrivena postignuća | Rani korisnik, Prijavitelj pogrešaka |
Rijetkosti
| Rijetkost | Boja | Okvirna vjerojatnost |
|---|---|---|
common |
Siva | Većina korisnika |
uncommon |
Zelena | Aktivni korisnici |
rare |
Plava | Predani korisnici |
legendary |
Zlatna | Najboljih 1 % |
Vrste kriterija
| Vrsta | Polje | Opis |
|---|---|---|
action_count |
count |
Izvršite radnju N puta (npr. 1000 zahtjeva) |
streak |
days |
Održavajte niz tijekom N uzastopnih dana |
unique_count |
field, n |
Upotrijebite N jedinstvenih vrijednosti (npr. 10 različitih modela) |
rank |
scope, n |
Dosegnite N. mjesto u zadanom opsegu ljestvice |
first |
— | Budite prvi koji je izvršio radnju |
hidden |
(razlikuje se) | Kriteriji se ne prikazuju dok se značka ne osvoji |
Definicije znački pohranjuju se u badge_definitions kao JSON criteria:
{
"type": "action_count",
"action": "request",
"count": 1000
}
Tijek evaluacije
emitGamificationEvent(event)
→ evaluateBadges(apiKeyId, event)
→ getBadgeDefinitions() # sve definicije
→ getUserBadges(apiKeyId) # već osvojene (preskoči)
→ za svaku neosvojenu značku:
→ matchesCriteria(badge, event, userState)
→ ako odgovara: awardBadge(apiKeyId, badgeId)
→ vrati podatke obavijesti
Evaluacija se pokreće događajima — izvršava se nakon svakog gamifikacijskog događaja, ali
provjerava samo značke čiji je criteria.type usklađen s radnjom događaja. Time
evaluacija ostaje brza (< 5 ms za većinu događaja).
matchesCriteria(badge, event, userState)
| Vrsta kriterija | Provjera |
|---|---|
action_count |
getActionCount(apiKeyId, action) >= count |
streak |
getCurrentStreak(apiKeyId) >= days |
unique_count |
getUniqueCount(apiKeyId, field) >= n |
rank |
getRank(apiKeyId, scope) <= n |
first |
Nema prethodnog unosa u xp_audit_log za ovu vrstu radnje |
hidden |
Delegira odgovarajućoj potprovjeri |
Ugrađene značke (20+)
Potpuni popis znački
| Značka | Kategorija | Rijetkost | Kriteriji |
|---|---|---|---|
| Prvi koraci | korištenje | uobičajena | 1 zahtjev |
| Zagrijavanje | korištenje | uobičajena | 100 zahtjeva |
| Napredni korisnik | korištenje | neuobičajena | 1.000 zahtjeva |
| Centurion | korištenje | rijetka | 10.000 zahtjeva |
| OmniPower | korištenje | legendarna | 100.000 zahtjeva |
| Skakač među pružateljima | doprinos | uobičajena | Upotrijebite 5 različitih pružatelja |
| Majstor pružatelja | doprinos | neuobičajena | Upotrijebite 20 različitih pružatelja |
| Arhitekt kombinacija | doprinos | neuobičajena | Izradite 5 kombinacija |
| Velemajstor kombinacija | doprinos | rijetka | Izradite 25 kombinacija |
| Prvo dijeljenje | dijeljenje | uobičajena | 1 prijenos tokena |
| Velikodušan | dijeljenje | neuobičajena | 10 prijenosa tokena |
| Filantrop | dijeljenje | rijetka | Prenesite ukupno 10.000 tokena |
| Preporučitelj | dijeljenje | uobičajena | 1 uspješna preporuka |
| Graditelj mreže | dijeljenje | neuobičajena | 10 uspješnih preporuka |
| Tjedni ratnik | niz | neuobičajena | Niz od 7 dana |
| Mjesečno posvećen | niz | rijetka | Niz od 30 dana |
| Nezaustavljiv | niz | legendarna | Niz od 365 dana |
| Rani korisnik | rijetka | legendarna | Pridružite se tijekom beta-razdoblja |
| Pionir kompresije | rijetka | neuobičajena | Upotrijebite kompresiju 100 puta |
| Sakupljač vještina | rijetka | rijetka | Upotrijebite 10 različitih vještina |
| Istraživač modela | doprinos | neuobičajena | Upotrijebite 15 različitih modela |
Praćenje niza
Datoteka: src/lib/gamification/streaks.ts
Model podataka
Nizovi se pohranjuju u tablici key_value (zajednička pomoćna tablica) pod
ključevima s prostorom imena:
| Ključ | Vrijednost | Opis |
|---|---|---|
gamification:streak:{keyId} |
{current},{longest},{lastDate} |
Podaci o aktivnom nizu |
Logika
export async function updateStreak(
apiKeyId: string
): Promise<{ current: number; longest: number; milestone: boolean }>;
- Pročitajte zapis niza iz tablice
key_value. - Raščlanite
{current},{longest},{lastDate}(datumski niz u ISO formatu). - Ako je
lastDate === today— nema promjene (današnji je dan već uračunat). - Ako je
lastDate === yesterday— povećajtecurrent; po potrebi ažurirajtelongest. - Ako je
lastDate < yesterday— vratitecurrent = 1(niz je prekinut). - Zapišite ažurirani zapis.
- Provjerite prekretnice: 7, 14, 30, 60, 90, 180, 365 dana. Ako je neka prijeđena, postavite
milestone = true(pozivatelj dodjeljuje XP i provjerava značke).
Rubni slučajevi
- Vremenska zona: nizovi upotrebljavaju UTC datume (
new Date().toISOString().slice(0, 10)). To je namjerno — jedna kanonska vremenska zona sprječava manipuliranje promjenom vremenskih zona. - Novi korisnici: zapis niza ne postoji; prvi ga zahtjev stvara s vrijednostima
current=1, longest=1, lastDate=today. - Više zahtjeva dnevno: samo prvi zahtjev u UTC danu povećava niz.
Ljestvica poretka
Datoteka: src/lib/gamification/leaderboard.ts
Opsezi
| Opseg | Razdoblje | Opis |
|---|---|---|
global |
all |
Ukupni XP svih vremena |
weekly |
week |
XP zarađen u trenutačnom UTC tjednu (pon–ned) |
monthly |
month |
XP zarađen u trenutačnom UTC mjesecu |
tokens_shared |
all |
Ukupan broj tokena prenesenih drugima |
contributions |
all |
Stvorene kombinacije + korišteni pružatelji + korištene vještine |
Izračun ranga
Rangovi se izračunavaju pri čitanju, a ne pohranjuju. Time se izbjegavaju zastarjeli podaci o rangu i uklanja potreba za periodičnim zadacima ponovnog izračunavanja rangova.
export async function getLeaderboard(
scope: LeaderboardScope,
period: string,
limit: number,
offset: number
): Promise<{ entries: LeaderboardEntry[]; total: number }>;
Obrazac upita:
SELECT api_key_id, score,
RANK() OVER (ORDER BY score DESC) as rank
FROM leaderboard
WHERE scope = ? AND period = ?
ORDER BY score DESC
LIMIT ? OFFSET ?
Rotacija razdoblja
Tjedne i mjesečne ljestvice automatski se rotiraju:
- Arhiviranje: na granici razdoblja kopirajte trenutačne unose u
leaderboard_archives oznakom razdoblja. - Ponovno postavljanje: izbrišite unose za isteklo razdoblje.
- Okidač: provjerava se pri svakom pozivu funkcije
updateLeaderboard(); prvi zahtjev u novom razdoblju pokreće rotaciju.
Time se osigurava da se tjedne ljestvice ponovno postavljaju svakog ponedjeljka u 00:00 UTC, a mjesečne ljestvice prvog dana svakog mjeseca.
SSE ažuriranja u stvarnom vremenu
Krajnja točka: GET /api/gamification/stream
Klijent → GET /api/gamification/stream
→ Uspostavljena je SSE veza
→ Poslužitelj odmah šalje snimku 10 najboljih na ljestvici
→ Svakih 5 sekundi: šalje ažuriranih 10 najboljih ako je došlo do promjene
→ Svakih 15 sekundi: komentar za održavanje veze (": heartbeat\n\n")
→ Klijent prekida vezu → čišćenje (uklanjanje slušatelja)
Format događaja:
event: leaderboard
data: {"scope":"global","entries":[...]}
event: leaderboard
data: {"scope":"weekly","entries":[...]}
: heartbeat
Upravitelj SSE-a prati povezane klijente po opsegu i šalje ažuriranja samo kada su se podaci ljestvice stvarno promijenili od posljednjeg slanja.
Dijeljenje tokena
Datoteka: src/lib/gamification/sharing.ts
Dvojno knjigovodstvo
Svaki prijenos stvara dva retka u tablici token_ledger:
| Redak | from_key_id |
to_key_id |
amount |
|---|---|---|---|
| Dugovna stavka | pošiljatelj | primatelj | +iznos |
| Potražna stavka | primatelj | pošiljatelj | -iznos |
Čekajte — konvencija je:
| Redak | from_key_id |
to_key_id |
amount |
Značenje |
|---|---|---|---|---|
| Slanje | pošiljatelj | primatelj | +iznos | Odljev od pošiljatelja |
| Primanje | primatelj | pošiljatelj | +iznos | Priljev primatelju |
Stanje se izračunava na sljedeći način:
SELECT
COALESCE(SUM(CASE WHEN to_key_id = ? THEN amount ELSE 0 END), 0)
- COALESCE(SUM(CASE WHEN from_key_id = ? THEN amount ELSE 0 END), 0)
AS balance
FROM token_ledger
WHERE from_key_id = ? OR to_key_id = ?
Tijek prijenosa
export async function transferTokens(
fromKeyId: string,
toKeyId: string,
amount: number,
idempotencyKey: string
): Promise<{ success: boolean; balance: number }>;
- Provjera valjanosti:
amount > 0,fromKeyId !== toKeyId. - Idempotentnost: provjerite postoji li
idempotency_keyveć u glavnoj knjizi. Ako postoji, vratite predmemorirani rezultat. - Transakcija (jedna SQLite transakcija):
a. Izračunajte stanje pošiljatelja.
b. Ako je
balance < amount, prekinite transakciju (nedovoljno sredstava). c. Umetnite redak slanja (from=sender,.
Ograničavanje učestalosti
- Najviše 10 prijenosa u minuti po API ključu.
- Najviše 10.000 tokena po pojedinačnom prijenosu.
- Najviše 100.000 prenesenih tokena dnevno po API ključu.
Tokeni za pozivanje i iskorištavanje
Datoteka: src/lib/gamification/invites.ts
Format koda
- Kod: alfanumerički kod od 8 znakova (npr.
A3K9-X7M2), čitljiv ljudima, prikazuje se korisniku. - Token: nasumični token od 32 bajta, pohranjen kao SHA-256 sažetak. Upotrebljava se za programsko iskorištavanje (npr. putem URL poveznica).
Pohrana
| Stupac | Vrijednost |
|---|---|
code |
A3K9X7M2 (jedinstven, indeksiran) |
token_hash |
SHA-256(raw_token) |
Neobrađeni token vraća se korisniku samo jednom, u trenutku stvaranja. OmniRoute ga više nikada ne pohranjuje niti prikazuje — zadržava se samo sažetak.
Sprječavanje samopreporuke
Kada korisnik iskoristi kod, sustav provjerava:
- Pripada li kod drugom
api_key_id. - Je li korisnik koji iskorištava kod prethodno iskoristio neki kod istog
preporučitelja (spajanje tablica
invite_tokensi zapisnika iskorištavanja).
Ako bilo koja provjera ne uspije, iskorištavanje se odbija uz jasnu poruku o pogrešci.
Istek i ograničenja
- Zadani
max_uses: 10 (može se konfigurirati pri stvaranju). - Zadani
expires_at: 30 dana od stvaranja. - Istekli ili u potpunosti iskorišteni kodovi vraćaju HTTP 410 Gone.
Federacija poslužitelja zajednice
Datoteka: src/lib/gamification/servers.ts
Povezivanje
Poslužitelj zajednice registrira se putem pozivnog tokena koji izdaje udaljeni poslužitelj. Lokalna instanca:
- Prima pozivni token (npr. zalijepljen na nadzornu ploču).
- Poziva
POST /api/gamification/federation/leaderboardna udaljenom poslužitelju kako bi provjerila valjanost tokena i dohvatila trenutačnu ljestvicu. - Sprema zapis poslužitelja sa
status: connected.
Model sinkronizacije
Federacija upotrebljava sinkronizaciju prepisivanjem, a ne zbrajanjem:
Lokalna instanca Poslužitelj zajednice
│ │
├── slanje rezultata ─────────►│ POST /federation/score
│ { api_key_id, score } │ (poslužitelj provjerava sažetak tokena)
│ │
├── dohvaćanje ljestvice ─────►│ GET /federation/leaderboard
│◄── prvih N unosa ───────────┤ (prepisuje lokalnu predmemoriju)
│ │
└── provjera dostupnosti ─────►│ GET /federation/health
(svakih 60 s, istek 5 s) │
Autentifikacija
Zahtjevi federacije uključuju:
Authorization: Bearer <raw_token>
X-Federation-Version: 1
Udaljeni poslužitelj izračunava sažetak tokena i traži odgovarajući redak u
community_servers. Time se izbjegava prijenos pohranjenog sažetka.
Praćenje dostupnosti
Svaki zapis poslužitelja prati:
| Polje | Opis |
|---|---|
status |
connected, degraded, unreachable |
last_sync |
ISO vremenska oznaka zadnje uspješne sinkronizacije |
failures |
Uzastopni neuspjesi provjere dostupnosti |
Nakon 5 uzastopnih neuspjeha status se mijenja u unreachable, a sinkronizacija
se pauzira sve dok ručna provjera dostupnosti ne uspije.
Zaštita od varanja
Datoteka: src/lib/gamification/antiCheat.ts
Izračun bodova na strani poslužitelja
Svi izračuni XP-a odvijaju se u src/lib/gamification/xp.ts. Klijenti nikada
ne šalju rezultat — šalju radnje, a poslužitelj izračunava XP. U stupac
leaderboard.score može pisati samo kôd na strani poslužitelja.
Ograničavanje učestalosti
| Ograničenje | Vrijednost | Opseg |
|---|---|---|
| Maks. XP-a po minuti | 1,000 | Po API ključu |
| Maks. prijenosa po minuti | 10 | Po API ključu |
| Maks. iznos prijenosa | 10,000 | Po prijenosu |
| Maks. dnevnih prijenosa | 100,000 | Po API ključu |
Ograničenja učestalosti upotrebljavaju klizni prozor u memoriji (isti obrazac kao
RateLimitManager u open-sse/services/). Ako se proces ponovno pokrene,
upotrebljavaju se brojači pohranjeni u SQLiteu.
Otkrivanje anomalija Z-rezultatom
Za svaki API ključ sustav održava pomični sedmodnevni prozor XP-a zarađenog po satu. Pri svakoj dodjeli XP-a:
- Izračunava trenutačnu korisnikovu satnu stopu XP-a.
- Izračunava aritmetičku sredinu i standardnu devijaciju populacije.
- Izračunava
z = (user_rate - mean) / stddev. - Ako je
z > 3.0(3 standardne devijacije), označava događaj kao anomaliju.
Anomalije se zapisuju u xp_audit_log uz action = 'anomaly_detected'
i prikazuju na administratorskoj nadzornoj ploči.
Revizijski trag
Svaka dodjela XP-a, prijenos, osvajanje značke i otkrivanje anomalije zapisuju se
u xp_audit_log sa sljedećim podacima:
| Polje | Opis |
|---|---|
api_key_id |
Tko |
action |
Što se dogodilo (xp_award, transfer, anomaly, …) |
xp_awarded |
Iznos (0 za događaje koji nisu povezani s XP-om) |
metadata |
JSON s kontekstom (vrsta radnje, cilj, …) |
created_at |
Kada (ISO 8601) |
Administratori mogu dohvatiti cijeli revizijski trag putem
GET /api/gamification/anomalies.
API rute
Sve rute slijede standardni OmniRoute obrazac:
Ruta → CORS preliminarni zahtjev → Provjera tijela zahtjeva (Zod) → Autentikacija (extractApiKey)
→ Rukovatelj
Krajnje točke
| Metoda | Putanja | Opis | Autentikacija |
|---|---|---|---|
| GET | /api/gamification/leaderboard |
Dohvaćanje ljestvice (opseg, razdoblje, paginacija) | Neobavezna |
| POST | /api/gamification/leaderboard |
Prisilno osvježavanje predmemorije ljestvice | Obavezna |
| GET | /api/gamification/stream |
SSE ažuriranja ljestvice u stvarnom vremenu | Neobavezna |
| GET | /api/gamification/transfer |
Dohvaćanje povijesti prijenosa (paginacija) | Obavezna |
| POST | /api/gamification/transfer |
Slanje tokena drugom korisniku | Obavezna |
| GET | /api/gamification/invite |
Popis mojih pozivnih kodova | Obavezna |
| POST | /api/gamification/invite |
Generiranje novog pozivnog koda | Obavezna |
| DELETE | /api/gamification/invite |
Opozivanje pozivnog koda | Obavezna |
| POST | /api/gamification/invite/redeem |
Iskorištavanje pozivnog koda | Obavezna |
| GET | /api/gamification/servers |
Popis poslužitelja zajednice | Obavezna |
| POST | /api/gamification/servers |
Povezivanje s poslužiteljem zajednice | Obavezna |
| DELETE | /api/gamification/servers |
Prekid veze s poslužiteljem zajednice | Obavezna |
| POST | /api/gamification/federation/score |
Slanje rezultata udaljenom poslužitelju | Federacija |
| GET | /api/gamification/federation/leaderboard |
Dohvaćanje ljestvice s udaljenog poslužitelja | Federacija |
| GET | /api/gamification/notifications |
SSE obavijesti o značkama/napredovanju razine | Obavezna |
| GET | /api/gamification/anomalies |
Pregled izvješća o anomalijama (administrator) | Administrator |
| POST | /api/gamification/rotate |
Rotacija tajni pozivnih tokena | Obavezna |
Primjeri zahtjeva/odgovora
POST /api/gamification/transfer
// Zahtjev
{
"to": "recipient-api-key-id",
"amount": 500,
"idempotencyKey": "uuid-v4"
}
// Odgovor 200
{
"success": true,
"transfer": {
"id": "txn-uuid",
"from": "sender-api-key-id",
"to": "recipient-api-key-id",
"amount": 500,
"createdAt": "2026-05-19T12:00:00.000Z"
},
"balance": 2500
}
// Odgovor 400 (nedovoljno sredstava)
{
"error": "Insufficient balance",
"balance": 200,
"requested": 500
}
GET /api/gamification/leaderboard?scope=weekly&limit=10
{
"scope": "weekly",
"period": "2026-W20",
"entries": [
{
"rank": 1,
"apiKeyId": "key-uuid",
"displayName": "User***1234",
"score": 15230,
"level": 42,
"title": "Expert"
}
],
"total": 847,
"updatedAt": "2026-05-19T12:00:00.000Z"
}
MCP alati (8)
Registrirani u open-sse/mcp-server/ zajedno s postojećim alatima. Ograničeni su
na opseg dopuštenja gamification.
| Alat | Opis | Ulazna shema | |
|---|---|---|---|
gamification_leaderboard |
Dohvati ljestvicu za opseg/razdoblje | { scope, period?, limit? } |
|
gamification_rank |
Dohvati rang pozivatelja i susjedne rangove | { scope } |
|
gamification_profile |
Dohvati sažetak XP-a, razine, titule i niza | {} |
|
gamification_badges |
Navedi osvojene značke ili sve definicije | { earned?: boolean } |
|
gamification_transfer |
Pošalji tokene drugom korisniku | { to, amount } |
|
gamification_invite |
Generiraj ili navedi pozivne kodove | `{ action: "create" | "list" }` |
gamification_servers |
Navedi ili poveži poslužitelje zajednice | { action, token? } |
|
gamification_anomalies |
Pregledaj izvješća o anomalijama (administratorski opseg) | { limit?, since? } |
Stranice nadzorne ploče
/dashboard/leaderboard
- Prikaz pobjedničkog postolja (prva 3 mjesta s avatarima i XP-om).
- Odabir opsega: Globalno / Tjedno / Mjesečno / Podijeljeni tokeni / Doprinosi.
- Tablica sa straničenjem (25 po stranici) s rangom, imenom, rezultatom, razinom i titulom.
- Ažuriranja u stvarnom vremenu putem SSE-a — promjene ranga su animirane.
- Trenutačni korisnik istaknut je u tablici ljepljivim retkom "Vaš rang".
/dashboard/profile
- Traka napretka XP-a s trenutačnom razinom i pragom sljedeće razine.
- Značka titule prikazana je na istaknutom mjestu.
- Galerija znački — osvojene značke s datumom osvajanja, neosvojene značke prikazane su sivo (skrivene značke prikazuju "???" dok se ne osvoje).
- Brojač niza s ikonom plamena; kalendar niza (posljednjih 30 dana).
- Grafikon povijesti XP-a (dnevni XP tijekom posljednjih 30 dana).
/dashboard/tokens
- Saldo tokena (istaknut na vrhu stranice).
- Obrazac za prijenos: primatelj, iznos, dijaloški okvir za potvrdu.
- Tablica povijesti prijenosa s filtrima (poslano/primljeno/sve).
- Odjeljak s pozivnicama: aktivni kodovi, generiranje novih, poveznica za dijeljenje.
- Poslužitelji zajednice: popis sa zdravstvenim stanjem, povezivanje/prekid veze.
/dashboard/gamification/admin
- Popis anomalija s ozbiljnošću, korisnikom, vremenskom oznakom i z-rezultatom.
- Preglednik zapisnika revizije s filtrima (vrsta radnje, korisnik, raspon datuma).
- Statistika sustava: ukupno dodijeljeni XP, aktivni korisnici, stope osvajanja znački.
- Pregled zdravstvenog stanja federacijskih poslužitelja.
Integracija s procesnim tokom
Točka integracije
Gamifikacija se uključuje u procesni tok zahtjeva na jednoj točki u
open-sse/handlers/chatCore.ts:
// Nakon što se odgovor pošalje klijentu:
setImmediate(() => {
emitGamificationEvent({
type: "request.completed",
apiKeyId,
metadata: {
provider: selectedProvider,
model: selectedModel,
comboId: resolvedCombo?.id,
compressionUsed: compressionStats?.applied,
skillUsed: skillExecution?.name,
},
}).catch(() => {
// Pokreni i zaboravi: zabilježi, ali nikada ne prosljeđuj klijentu
});
});
Vrste događaja
| Vrsta događaja | Kada se emitira |
|---|---|
request.completed |
Poslan je uspješan odgovor LLM-a |
provider.switch |
Pružatelj je promijenjen (računa se zamjenski odabir kombinacije) |
combo.created |
Spremljena je nova konfiguracija kombinacije |
combo.used |
Cilj kombinacije uspješno je pogođen |
badge.earned |
Evaluacijom značke pronađeno je podudaranje |
streak.milestone |
Dosegnut je prag niza |
transfer.sent |
Prijenos tokena je dovršen |
referral.redeemed |
Pozivni kod uspješno je iskorišten |
compression.used |
Primijenjena je kompresija upita |
skill.executed |
Izvršavanje vještine je dovršeno |
model.first_use |
Model nije korišten u posljednjih 7 dana |
Jamstvo neblokirajućeg izvršavanja
Uzorak setImmediate + .catch(() => {}) osigurava:
- Odgovor je u potpunosti poslan prije pokretanja gamifikacije.
- Pogreške gamifikacije nikada se ne prikazuju klijentu.
- Obrada događaja izvršava se u sljedećem mikrozadatku, a ne izravno.
Sigurnost
Model prijetnji
| Prijetnja | Ublažavanje |
|---|---|
| Umjetno povećavanje bodova | Izračun XP-a samo na poslužitelju; klijenti šalju radnje, a ne bodove |
| Napadi ponavljanjem | Ključevi idempotentnosti za prijenose; deduplikacija zapisnika nadzora |
| Prijevara s prijenosima | Dvojno knjigovodstvo; atomske transakcije; ograničenja učestalosti zahtjeva |
| Samostalna preporuka | Unakrsna provjera api_key_id pri iskorištavanju |
| Manipulacija ljestvicom | Otkrivanje anomalija Z-vrijednosti; administratorska nadzorna ploča anomalija |
| Krađa federacijskog tokena | Pohrana sa SHA-256 sažetkom; neobrađeni token prikazuje se samo jednom |
| Grubo pogađanje kodova poziva | Ograničavanje učestalosti na krajnjoj točki za iskorištavanje; entropija od 8 znakova |
| XSS u imenima za prikaz | Imena za prikaz su sanitizirana; unosi ljestvice pravilno su escapeani |
| Vremenski napadi na sažetke | crypto.timingSafeEqual za usporedbu sažetaka tokena |
Zahtjevi za autentikaciju
- Javno (bez autentikacije):
GET /leaderboard,GET /stream(ljestvice samo za čitanje). - Potreban API ključ: sve operacije pisanja, profil, prijenosi i pozivi.
- Samo za administratore: nadzorna ploča anomalija, preglednik zapisnika nadzora.
- Federacija: zaseban put autentikacije koji upotrebljava neobrađeni token u
zaglavlju
Authorization, provjeren u odnosu na pohranjeni SHA-256 sažetak.
Testiranje
Datoteke testova
Svi testovi upotrebljavaju izvorni Node.js pokretač testova (node --import tsx/esm --test).
| Datoteka testa | Obuhvaća | Testovi |
|---|---|---|
tests/unit/gamification/xp.test.ts |
Izračun XP-a, krivulju razina, titule | 8 |
tests/unit/gamification/badges.test.ts |
Podudaranje kriterija znački, dodjeljivanje | 10 |
tests/unit/gamification/streaks.test.ts |
Logiku nizova, ključne točke, rubne slučajeve | 7 |
tests/unit/gamification/leaderboard.test.ts |
Izračun ranga, straničenje, rotaciju | 8 |
tests/unit/gamification/sharing.test.ts |
Prijenose, stanje, idempotentnost | 9 |
tests/unit/gamification/invites.test.ts |
Stvaranje, iskorištavanje, istek, samostalne preporuke | 7 |
tests/unit/gamification/antiCheat.test.ts |
Ograničenja učestalosti, Z-vrijednost, zapisivanje nadzora | 6 |
tests/unit/gamification/events.test.ts |
Emitiranje događaja, distribuciju, obradu pogrešaka | 5 |
Pokretanje testova
# Svi testovi gamifikacije
node --import tsx/esm --test tests/unit/gamification/*.test.ts
# Jedna datoteka testa
node --import tsx/esm --test tests/unit/gamification/xp.test.ts
Zahtjevi za pokrivenost
Prema CONTRIBUTING.md — svi novi moduli moraju imati:
- Pokrivenost grana >= 80%.
- Svaka javna funkcija mora biti testirana barem jednom.
- Putovi pogrešaka moraju biti testirani (nedovoljno stanje, istekli kodovi, ograničenja učestalosti).
Struktura datoteka
src/
lib/
db/
migrations/
060_create_gamification.sql # Svih 8 tablica + indeksi
gamification.ts # Domenski CRUD modul
gamification/
xp.ts # Izračun XP-a, krivulja razina, titule
badges.ts # Definicije znački, kriteriji, evaluacija
streaks.ts # Praćenje dnevnog niza aktivnosti
leaderboard.ts # Izračun ranga, SSE, rotacija
antiCheat.ts # Ograničavanje učestalosti, z-vrijednost, revizija
sharing.ts # Glavna knjiga prijenosa tokena
invites.ts # Kodovi za poziv i iskorištavanje
servers.ts # Federacija poslužitelja zajednice
events.ts # Emiter događaja (integracijska točka)
notifications.ts # SSE tok obavijesti
app/
api/
gamification/
leaderboard/route.ts # GET/POST ljestvica
leaderboard/stream/route.ts # SSE ažuriranja u stvarnom vremenu
transfer/route.ts # GET/POST prijenosi
invite/route.ts # GET/POST/DELETE pozivni kodovi
invite/redeem/route.ts # POST iskorištavanje koda
servers/route.ts # GET/POST/DELETE poslužitelji
federation/score/route.ts # POST slanje rezultata
federation/leaderboard/route.ts # GET dohvaćanje ljestvice
notifications/route.ts # SSE obavijesti
anomalies/route.ts # GET izvješća o anomalijama
rotate/route.ts # POST rotiranje tajni
(dashboard)/
dashboard/
leaderboard/page.tsx # Stranica s rangiranjem
profile/page.tsx # Stranica XP-a/znački/nizova aktivnosti
tokens/page.tsx # Stranica stanja/prijenosa/pozivnica
gamification/admin/page.tsx # Administratorski nadzor anomalija
shared/
constants/
gamification.ts # XP_REWARDS, TITLES, BADGE_DEFS, LIMITS
tests/
unit/
gamification/
xp.test.ts
badges.test.ts
streaks.test.ts
leaderboard.test.ts
sharing.test.ts
invites.test.ts
antiCheat.test.ts
events.test.ts
docs/
frameworks/
GAMIFICATION.md # Ovaj dokument
Strategija migracije
Faza 1: Jezgra pozadinskog sustava (PR 1)
- Migracija
060_create_gamification.sql(8 tablica). src/lib/db/gamification.ts(domenski modul).src/lib/gamification/xp.ts,streaks.ts,events.ts.- Integracijska točka u
chatCore.ts. - Jedinični testovi za XP, nizove aktivnosti i događaje.
Faza 2: Značke i ljestvica (PR 2)
src/lib/gamification/badges.ts,leaderboard.ts.- Definicije znački u konstantama.
- API rute ljestvice + SSE tok.
- Jedinični testovi za značke i ljestvicu.
Faza 3: Dijeljenje i pozivnice (PR 3)
src/lib/gamification/sharing.ts,invites.ts,antiCheat.ts.- API rute za prijenose i pozivnice.
- Jedinični testovi za dijeljenje, pozivnice i sprječavanje varanja.
Faza 4: Federacija i nadzorna ploča (PR 4)
src/lib/gamification/servers.ts,notifications.ts.- API rute federacije.
- Stranice nadzorne ploče (ljestvica, profil, tokeni, administracija).
- Registracija MCP alata.
Buduća razmatranja
- Sezonski događaji: vremenski ograničeni skupovi znački i sezone ljestvice najboljih.
- Timske ljestvice najboljih: grupiranje korisnika prema organizaciji ili kombinaciji.
- XP množitelji: povećanje XP-a tijekom promotivnih razdoblja.
- Dijeljenje postignuća: generiranje djeljivih kartica znački (OpenGraph slike).
- Mobilne push obavijesti: obavijesti temeljene na webhookovima za događaje povezane sa značkama/razinama.
- API ljestvice najboljih: javni API za integracije trećih strana.