diff --git a/Dockerfile b/Dockerfile index 346d9c5ba7..039078bd87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM node:16 AS builder +FROM node:16 AS builder WORKDIR /web COPY ./VERSION . @@ -24,7 +24,8 @@ RUN apk add --no-cache \ ENV GO111MODULE=on \ CGO_ENABLED=1 \ - GOOS=linux + GOOS=linux \ + GOARCH=arm64 WORKDIR /build @@ -44,4 +45,4 @@ COPY --from=builder2 /build/one-api / EXPOSE 3000 WORKDIR /data -ENTRYPOINT ["/one-api"] \ No newline at end of file +ENTRYPOINT ["/one-api"] diff --git a/docs/I18N_AUDIT.md b/docs/I18N_AUDIT.md new file mode 100644 index 0000000000..ebdf12b2ff --- /dev/null +++ b/docs/I18N_AUDIT.md @@ -0,0 +1,19 @@ +# i18n Audit Report + +- Languages: zh, en, de, sr +- Base locale: en (581 keys) + +## Key Coverage +- zh: keys=581, missing=0, extra=0 +- en: keys=581, missing=0, extra=0 +- de: keys=581, missing=0, extra=0 +- sr: keys=581, missing=0, extra=0 + +## Placeholder Consistency (vs en) +- zh: mismatches=0 +- de: mismatches=0 +- sr: mismatches=0 + +## Translation Progress Heuristic (identical to en) +- de: identical_to_en=449/581 (77.3%) +- sr: identical_to_en=442/581 (76.1%) diff --git a/docs/I18N_STATUS.md b/docs/I18N_STATUS.md new file mode 100644 index 0000000000..4c05eb00fa --- /dev/null +++ b/docs/I18N_STATUS.md @@ -0,0 +1,46 @@ +# OneAPI Frontend i18n Status (DereineRing) + +## Ist bereits mehrsprachig? + +Ja, **teilweise**: + +- `web/default` hat i18n mit `react-i18next` +- bereits vorhanden: `zh`, `en` +- jetzt erweitert: `de`, `sr` (initial als EN-basierte Fallback-Strings) + +## Aktueller Stand in dieser Branch + +Erweiterte Dateien: + +- `web/default/src/i18n.js` +- `web/default/src/components/Header.js` +- `web/default/src/locales/de/translation.json` +- `web/default/src/locales/sr/translation.json` +- `web/default/src/locales/zh/translation.json` +- `scripts/i18n_audit.py` +- `docs/I18N_AUDIT.md` + +Sprachen im Header-Dropdown: + +- 中文 (`zh`) +- English (`en`) +- Deutsch (`de`) +- Srpski (`sr`) + +## Was noch offen ist + +- vollständige manuelle Übersetzung `de` und `sr` (ein großer Teil ist noch EN-Fallback) +- sprachliche Feinkorrektur der neu übersetzten Info-/Hinweistexte im Review +- optional: `web/berry` und `web/air` ebenfalls auf i18n umstellen + +## Qualitätsprüfung + +- Ein Audit-Skript prüft: + - Key-Vollständigkeit je Sprache + - Placeholder-Konsistenz (`{{name}}`, etc.) + - Fortschrittsheuristik (`de/sr` identisch zu `en`) +- Aktueller Audit-Report: `docs/I18N_AUDIT.md` + +## Warum dieser Ansatz? + +So ist die Public-Branch sofort nutzbar und stabil (kein Build-Bruch), ohne private Daten preiszugeben. diff --git a/docs/PUBLIC_WORKSPACE_POLICY.md b/docs/PUBLIC_WORKSPACE_POLICY.md new file mode 100644 index 0000000000..4affdb615b --- /dev/null +++ b/docs/PUBLIC_WORKSPACE_POLICY.md @@ -0,0 +1,28 @@ +# Public Workspace Policy (DereineRing) + +Dieses Repo ist für späteren Public Upload vorbereitet. + +## Erlaubt (public) + +- Quellcode für OneAPI i18n +- allgemeine Build- und Deploy-Dokumentation +- Platzhalter-Konfigurationen (`.env.example`, Templates) + +## Verboten (nicht public) + +- echte API Keys / Tokens +- Matrix Access Tokens +- lokale Benutzer-/Serverdaten +- private Infrastrukturpfade und persönliche Zugangsdaten + +## Release-Checkliste vor Push/Fork + +1. `git status` prüfen +2. `git diff` auf Secrets prüfen +3. Keine `.env` / `data/` / Logs commiten +4. Nur Template-Dateien veröffentlichen + +## Trennung Public vs. Private + +- **Public Entwicklung:** `/Volumes/M4Data/Coding/DereineRing/one-api` +- **Private Runtime/Secrets:** `/Volumes/M4Data/Coding/CommandStack-Private` diff --git a/docs/i18n-roadmap.de-sr.md b/docs/i18n-roadmap.de-sr.md new file mode 100644 index 0000000000..d3cd479c61 --- /dev/null +++ b/docs/i18n-roadmap.de-sr.md @@ -0,0 +1,41 @@ +# i18n Roadmap (DE/EN/SR/ZH) + +## Ist-Stand +- OneAPI Frontend hat in `web/default/src/locales` aktuell nur: + - `en/translation.json` + - `zh/translation.json` +- Sprachumschalter ist in `web/default/src/components/Header.js` auf `zh` und `en` begrenzt. +- Andere Themes (`web/air`, `web/berry`) nutzen derzeit kein vollständiges i18n-Setup. + +## Ziel +- Saubere Mehrsprachigkeit für: + - Deutsch (`de`) + - Englisch (`en`) + - Serbisch (`sr`) + - Chinesisch (`zh`) +- Konsistente Schlüsselstruktur, keine gemischten Hardcoded-Texte. + +## Umsetzungsschritte +1. [x] Neue Locale-Dateien hinzufügen (`de`, `sr`) auf Basis des finalen Key-Sets. +2. [x] `web/default/src/i18n.js` um `de` und `sr` erweitern, Fallback auf `en` setzen. +3. [x] Sprachauswahl in `Header.js` um `Deutsch` und `Srpski` erweitern. +4. Diff-Check aller Keys zwischen `zh`, `en`, `de`, `sr` automatisieren. +5. UI-Screening pro Hauptseite (Login, Dashboard, Channel, Settings, Token, Logs). +6. Terminologie-Review (Agent, Channel, Token, Fallback, Quota, Billing). + +## Status-Update (2026-04-20) +- Technische Mehrsprachigkeit ist aktiviert (ZH/EN/DE/SR auswählbar). +- `de` und `sr` sind aktuell noch auf EN-Basis und müssen fachlich sauber nachgezogen werden. + +## Qualitätssicherung +- Key-Completeness-Check pro Sprache (100% Abdeckung). +- Visuelle Checks für lange Texte (DE/SR) auf Mobile/Desktop. +- Einheitliche Terminologie-Liste als Referenz. + +## Aufwand (realistisch) +- Technische Integration: ~0.5 Tag +- Vollständige fachlich saubere Übersetzung + QA: ~1.5 bis 3 Tage +- Gesamt: ~2 bis 3.5 Tage für ein releasefähiges Ergebnis. + +## Lizenz +- Repository ist MIT-lizenziert (Fork/Branch/public Beitrag ist zulässig). diff --git a/docs/public/README.de.md b/docs/public/README.de.md new file mode 100644 index 0000000000..bba50b58d3 --- /dev/null +++ b/docs/public/README.de.md @@ -0,0 +1,71 @@ +# DerEineRing (DE) + +## Was dieses Projekt ist +DerEineRing ist eine öffentliche OneAPI-Anpassung mit mehrsprachigem, überarbeitetem Frontend. + +Der Fokus liegt auf: +- UI-Lokalisierung: Chinesisch, Englisch, Deutsch, Serbisch +- klarer öffentlicher Doku und Übersetzungs-Workflow +- visueller und funktionaler Frontend-Politur + +## Für wen es gedacht ist +- Teams, die OneAPI lokal als Gateway betreiben +- Nutzer, die eine mehrsprachige Admin-Oberfläche wollen +- Contributor für i18n- und Frontend-Qualität + +## Enthaltene Sprachen +- `zh` +- `en` +- `de` +- `sr` + +Locale-Dateien: +- `web/default/src/locales/zh/translation.json` +- `web/default/src/locales/en/translation.json` +- `web/default/src/locales/de/translation.json` +- `web/default/src/locales/sr/translation.json` + +## Schnellstart (Docker) +```bash +git clone https://github.com/olivilo/one-api.git +cd one-api +docker compose up -d --build +``` + +Aufruf: +- `http://localhost:3000` + +## Erster Login +Bei einer frischen Datenbank legt OneAPI in der Regel an: +- Benutzername: `root` +- Passwort: `123456` + +Passwort direkt nach dem ersten Login ändern. + +## Nutzung +1. Im Web-UI anmelden. +2. Provider/Kanäle unter **Channels** anlegen. +3. Zugriffstoken unter **Tokens** erstellen. +4. OneAPI als OpenAI-kompatiblen Endpoint nutzen: + - Base URL: `http://localhost:3000/v1` + - API-Key: dein Token aus dem UI + +Beispiel: +```bash +curl http://localhost:3000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_ONEAPI_TOKEN" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [{"role":"user","content":"Hallo von DerEineRing"}] + }' +``` + +## Sprache und Theme im UI +- Über den Sprachumschalter im Header zwischen `zh/en/de/sr` wechseln. +- Über den Theme-Umschalter im Header zwischen den verfügbaren Designs wechseln. + +## Mitwirken +- Textänderungen in den jeweiligen Locale-JSON-Dateien pflegen. +- Terminologie in allen vier Sprachen konsistent halten. +- Übersetzungs- und UX-Änderungen per Pull Request einreichen. diff --git a/docs/public/README.en.md b/docs/public/README.en.md new file mode 100644 index 0000000000..2c1145f5f5 --- /dev/null +++ b/docs/public/README.en.md @@ -0,0 +1,71 @@ +# DerEineRing (EN) + +## What this project is +DerEineRing is a public customization of OneAPI with a polished multilingual frontend. + +It focuses on: +- UI localization: Chinese, English, German, Serbian +- cleaner public docs and translation workflow +- theme and UX polish in the web interface + +## Who this is for +- teams running OneAPI as a local gateway +- users who want a multilingual admin UI +- contributors working on i18n and frontend quality + +## Included languages +- `zh` +- `en` +- `de` +- `sr` + +Locale files: +- `web/default/src/locales/zh/translation.json` +- `web/default/src/locales/en/translation.json` +- `web/default/src/locales/de/translation.json` +- `web/default/src/locales/sr/translation.json` + +## Quick install (Docker) +```bash +git clone https://github.com/olivilo/one-api.git +cd one-api +docker compose up -d --build +``` + +Open: +- `http://localhost:3000` + +## First login +On a fresh database, OneAPI usually creates: +- username: `root` +- password: `123456` + +Change the password immediately after first login. + +## How to use +1. Login to the web UI. +2. Add providers/channels in **Channels**. +3. Create an access token in **Tokens**. +4. Use OneAPI as an OpenAI-compatible endpoint: + - Base URL: `http://localhost:3000/v1` + - API key: your token from the UI + +Example: +```bash +curl http://localhost:3000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_ONEAPI_TOKEN" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [{"role":"user","content":"Hello from DerEineRing"}] + }' +``` + +## UI language and theme +- Use the language selector in the header to switch `zh/en/de/sr`. +- Use the theme selector in the header to switch available UI themes. + +## Contributing +- Keep text changes in the locale JSON files. +- Keep terminology consistent across all four languages. +- Submit translation and UX changes via pull request. diff --git a/docs/public/README.md b/docs/public/README.md new file mode 100644 index 0000000000..7949cdcb75 --- /dev/null +++ b/docs/public/README.md @@ -0,0 +1,6 @@ +# Public Readmes + +- English: `docs/public/README.en.md` +- Deutsch: `docs/public/README.de.md` +- Srpski: `docs/public/README.sr.md` +- 中文: `docs/public/README.zh.md` diff --git a/docs/public/README.sr.md b/docs/public/README.sr.md new file mode 100644 index 0000000000..623f9fe6a3 --- /dev/null +++ b/docs/public/README.sr.md @@ -0,0 +1,71 @@ +# DerEineRing (SR) + +## Šta je ovaj projekat +DerEineRing je javna prilagođena verzija OneAPI-ja sa doteranim višejezičnim frontendom. + +Fokus: +- lokalizovan UI: kineski, engleski, nemački, srpski +- jasna javna dokumentacija i prevodilački workflow +- unapređen izgled i UX interfejsa + +## Kome je namenjen +- timovima koji lokalno koriste OneAPI kao gateway +- korisnicima kojima treba višejezični admin UI +- contributorima za i18n i frontend kvalitet + +## Podržani jezici +- `zh` +- `en` +- `de` +- `sr` + +Locale fajlovi: +- `web/default/src/locales/zh/translation.json` +- `web/default/src/locales/en/translation.json` +- `web/default/src/locales/de/translation.json` +- `web/default/src/locales/sr/translation.json` + +## Brza instalacija (Docker) +```bash +git clone https://github.com/olivilo/one-api.git +cd one-api +docker compose up -d --build +``` + +Otvori: +- `http://localhost:3000` + +## Prvo prijavljivanje +Na čistoj bazi OneAPI obično kreira: +- korisnik: `root` +- lozinka: `123456` + +Promeni lozinku odmah nakon prvog logovanja. + +## Kako se koristi +1. Uloguj se u web UI. +2. Dodaj provajdere/kanale u **Channels**. +3. Kreiraj pristupni token u **Tokens**. +4. Koristi OneAPI kao OpenAI-kompatibilni endpoint: + - Base URL: `http://localhost:3000/v1` + - API ključ: token iz UI-ja + +Primer: +```bash +curl http://localhost:3000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_ONEAPI_TOKEN" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [{"role":"user","content":"Pozdrav iz DerEineRing-a"}] + }' +``` + +## Jezik i tema u UI-ju +- U headeru promeni jezik na `zh/en/de/sr`. +- U headeru promeni temu preko theme selektora. + +## Doprinos projektu +- Tekstualne izmene radi kroz locale JSON fajlove. +- Održavaj doslednu terminologiju u sva četiri jezika. +- Šalji prevodilačke i UX izmene kroz pull request. diff --git a/docs/public/README.zh.md b/docs/public/README.zh.md new file mode 100644 index 0000000000..4f7c22fcc6 --- /dev/null +++ b/docs/public/README.zh.md @@ -0,0 +1,71 @@ +# DerEineRing(ZH) + +## 项目简介 +DerEineRing 是基于 OneAPI 的公开定制版本,重点是高质量多语言前端体验。 + +核心方向: +- 多语言 UI:中文、英文、德文、塞尔维亚文 +- 清晰的公开文档与翻译工作流 +- 前端界面与交互体验优化 + +## 适用人群 +- 本地部署 OneAPI 网关的团队 +- 需要多语言管理界面的用户 +- 参与 i18n 与前端优化的贡献者 + +## 已支持语言 +- `zh` +- `en` +- `de` +- `sr` + +语言文件: +- `web/default/src/locales/zh/translation.json` +- `web/default/src/locales/en/translation.json` +- `web/default/src/locales/de/translation.json` +- `web/default/src/locales/sr/translation.json` + +## 快速安装(Docker) +```bash +git clone https://github.com/olivilo/one-api.git +cd one-api +docker compose up -d --build +``` + +访问地址: +- `http://localhost:3000` + +## 首次登录 +在全新数据库下,OneAPI 通常会创建: +- 用户名:`root` +- 密码:`123456` + +首次登录后请立即修改密码。 + +## 使用方式 +1. 登录 Web 界面。 +2. 在 **Channels** 中添加提供商/渠道。 +3. 在 **Tokens** 中创建访问令牌。 +4. 将 OneAPI 作为 OpenAI 兼容接口使用: + - Base URL:`http://localhost:3000/v1` + - API Key:Web 界面中创建的 token + +调用示例: +```bash +curl http://localhost:3000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_ONEAPI_TOKEN" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [{"role":"user","content":"你好,来自 DerEineRing"}] + }' +``` + +## 界面语言与主题 +- 在页面顶部语言菜单切换 `zh/en/de/sr`。 +- 在页面顶部主题菜单切换可用主题。 + +## 贡献说明 +- 文本修改请在对应 locale JSON 文件中进行。 +- 四种语言尽量保持术语一致。 +- 通过 Pull Request 提交翻译与 UI/UX 改进。 diff --git a/scripts/check_locale_keys.py b/scripts/check_locale_keys.py new file mode 100755 index 0000000000..048714a542 --- /dev/null +++ b/scripts/check_locale_keys.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import json +from pathlib import Path + +base = json.loads(Path('web/default/src/locales/en/translation.json').read_text()) +langs = ['zh','de','sr'] + +def keys(obj,prefix=''): + out=set() + if isinstance(obj,dict): + for k,v in obj.items(): + path=f'{prefix}.{k}' if prefix else k + out.add(path) + out |= keys(v,path) + elif isinstance(obj,list): + for i,v in enumerate(obj): + path=f'{prefix}[{i}]' + out.add(path) + out |= keys(v,path) + return out + +base_keys=keys(base) +ok=True +for lang in langs: + d=json.loads(Path(f'web/default/src/locales/{lang}/translation.json').read_text()) + k=keys(d) + miss=sorted(base_keys-k) + extra=sorted(k-base_keys) + if miss or extra: + ok=False + print(f'[{lang}] missing={len(miss)} extra={len(extra)}') + for x in miss[:10]: print(' MISSING',x) + for x in extra[:10]: print(' EXTRA ',x) + else: + print(f'[{lang}] keys OK ({len(k)})') + +raise SystemExit(0 if ok else 1) diff --git a/scripts/i18n_audit.py b/scripts/i18n_audit.py new file mode 100755 index 0000000000..16c317ecba --- /dev/null +++ b/scripts/i18n_audit.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +import json +import re +from pathlib import Path + +BASE = Path(__file__).resolve().parents[1] / 'web' / 'default' / 'src' / 'locales' +LANGS = ['zh', 'en', 'de', 'sr'] +PH_RE = re.compile(r'\{\{\s*([a-zA-Z0-9_]+)\s*\}\}') + + +def flatten(obj, prefix=''): + out = {} + if isinstance(obj, dict): + for k, v in obj.items(): + p = f"{prefix}.{k}" if prefix else k + if isinstance(v, dict): + out.update(flatten(v, p)) + else: + out[p] = v + return out + + +def placeholders(text): + if not isinstance(text, str): + return set() + return set(PH_RE.findall(text)) + + +def load_lang(lang): + p = BASE / lang / 'translation.json' + return flatten(json.loads(p.read_text(encoding='utf-8'))) + + +def main(): + data = {l: load_lang(l) for l in LANGS} + base_keys = set(data['en']) + + print('# i18n Audit Report') + print('') + print(f'- Languages: {", ".join(LANGS)}') + print(f'- Base locale: en ({len(base_keys)} keys)') + print('') + + print('## Key Coverage') + for lang in LANGS: + keys = set(data[lang]) + missing = sorted(base_keys - keys) + extra = sorted(keys - base_keys) + print(f'- {lang}: keys={len(keys)}, missing={len(missing)}, extra={len(extra)}') + + print('') + print('## Placeholder Consistency (vs en)') + for lang in LANGS: + if lang == 'en': + continue + mismatch = [] + for k in sorted(base_keys): + en_ph = placeholders(data['en'].get(k, '')) + lg_ph = placeholders(data[lang].get(k, '')) + if en_ph != lg_ph: + mismatch.append((k, en_ph, lg_ph)) + print(f'- {lang}: mismatches={len(mismatch)}') + for k, en_ph, lg_ph in mismatch[:10]: + print(f' - {k}: en={sorted(en_ph)} {lang}={sorted(lg_ph)}') + + print('') + print('## Translation Progress Heuristic (identical to en)') + for lang in ['de', 'sr']: + identical = 0 + for k in base_keys: + if isinstance(data['en'].get(k), str) and data[lang].get(k) == data['en'].get(k): + identical += 1 + ratio = identical / len(base_keys) * 100 + print(f'- {lang}: identical_to_en={identical}/{len(base_keys)} ({ratio:.1f}%)') + + +if __name__ == '__main__': + main() diff --git a/scripts/translate_locales.py b/scripts/translate_locales.py new file mode 100755 index 0000000000..7de7aab23c --- /dev/null +++ b/scripts/translate_locales.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +import json +import re +import sys +import time +from pathlib import Path +from typing import Any + +from deep_translator import GoogleTranslator + +ROOT = Path(__file__).resolve().parents[1] +EN_PATH = ROOT / "web/default/src/locales/en/translation.json" + +PLACEHOLDER_RE = re.compile(r"\{\{[^{}]+\}\}|<[^>]+>|`[^`]+`|https?://\S+") +TOKEN_FMT = "§§{:03d}§§" + + +def mask(text: str): + holders = [] + + def repl(m): + idx = len(holders) + holders.append(m.group(0)) + return TOKEN_FMT.format(idx) + + masked = PLACEHOLDER_RE.sub(repl, text) + return masked, holders + + +def unmask(text: str, holders): + out = text + for i, h in enumerate(holders): + out = out.replace(TOKEN_FMT.format(i), h) + return out + + +def walk_strings(node: Any, path=()): + if isinstance(node, dict): + for k, v in node.items(): + yield from walk_strings(v, path + (k,)) + elif isinstance(node, list): + for i, v in enumerate(node): + yield from walk_strings(v, path + (str(i),)) + elif isinstance(node, str): + yield path, node + + +def set_at_path(root: Any, path, value: str): + cur = root + for p in path[:-1]: + cur = cur[int(p)] if isinstance(cur, list) else cur[p] + last = path[-1] + if isinstance(cur, list): + cur[int(last)] = value + else: + cur[last] = value + + +def translate_text(translator: GoogleTranslator, text: str, cache: dict): + if text.strip() == "": + return text + if text in cache: + return cache[text] + + masked, holders = mask(text) + # Keep technical literals unchanged (model IDs, URLs, version tags, code-like tokens). + if ( + (" " not in masked and re.search(r"[/:_@]|\\d", masked)) + or masked.startswith("gpt") + or masked.startswith("gemini") + or masked.startswith("llama") + or masked.startswith("mistral") + ): + cache[text] = text + return text + + for attempt in range(4): + try: + t = translator.translate(masked) + if not isinstance(t, str) or t.strip() == "": + t = text + t = unmask(t, holders) + cache[text] = t + return t + except Exception: + if attempt == 3: + cache[text] = text + return text + time.sleep(0.8 * (attempt + 1)) + + +def run(lang_code: str, out_path: Path): + en = json.loads(EN_PATH.read_text(encoding="utf-8")) + out = json.loads(json.dumps(en, ensure_ascii=False)) + translator = GoogleTranslator(source="en", target=lang_code) + cache = {} + + items = list(walk_strings(en)) + total = len(items) + for idx, (path, text) in enumerate(items, 1): + translated = translate_text(translator, text, cache) + set_at_path(out, path, translated) + if idx % 80 == 0 or idx == total: + print(f"[{lang_code}] {idx}/{total}") + + out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(f"written: {out_path}") + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("usage: translate_locales.py ") + sys.exit(1) + run(sys.argv[1], Path(sys.argv[2])) diff --git a/web/air/public/index.html b/web/air/public/index.html index e0de002dd8..5348ed496a 100644 --- a/web/air/public/index.html +++ b/web/air/public/index.html @@ -1,5 +1,5 @@ - + @@ -9,7 +9,7 @@ name="description" content="OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用" /> - One API + DereineRing diff --git a/web/air/src/helpers/utils.js b/web/air/src/helpers/utils.js index 580c77ced2..c8b39d0a70 100644 --- a/web/air/src/helpers/utils.js +++ b/web/air/src/helpers/utils.js @@ -23,7 +23,7 @@ export function isRoot() { export function getSystemName() { let system_name = localStorage.getItem('system_name'); - if (!system_name) return 'One API'; + if (!system_name) return 'DereineRing'; return system_name; } @@ -230,4 +230,4 @@ export function shouldShowPrompt(id) { export function setPromptShown(id) { localStorage.setItem(`prompt-${id}`, 'true'); -} \ No newline at end of file +} diff --git a/web/berry/public/index.html b/web/berry/public/index.html index 9a2457e2d4..0e2512b8d3 100644 --- a/web/berry/public/index.html +++ b/web/berry/public/index.html @@ -1,7 +1,7 @@ - + - One API + DereineRing diff --git a/web/berry/src/config.js b/web/berry/src/config.js index 8c1faf9bb6..91d94b7c24 100644 --- a/web/berry/src/config.js +++ b/web/berry/src/config.js @@ -16,7 +16,7 @@ const config = { quota_per_unit: 500000, server_address: '', start_time: 0, - system_name: 'One API', + system_name: 'DereineRing', top_up_link: '', turnstile_check: false, turnstile_site_key: '', diff --git a/web/berry/src/utils/common.js b/web/berry/src/utils/common.js index be2961222c..097642abbf 100644 --- a/web/berry/src/utils/common.js +++ b/web/berry/src/utils/common.js @@ -4,7 +4,7 @@ import {API} from './api'; export function getSystemName() { let system_name = localStorage.getItem('system_name'); - if (!system_name) return 'One API'; + if (!system_name) return 'DereineRing'; return system_name; } diff --git a/web/default/public/index.html b/web/default/public/index.html index f22b88437b..f08c45e6df 100644 --- a/web/default/public/index.html +++ b/web/default/public/index.html @@ -1,5 +1,5 @@ - + @@ -7,9 +7,9 @@ - One API + DereineRing diff --git a/web/default/src/components/Header.js b/web/default/src/components/Header.js index fc69298faa..94aae7da55 100644 --- a/web/default/src/components/Header.js +++ b/web/default/src/components/Header.js @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { UserContext } from '../context/User'; import { useTranslation } from 'react-i18next'; @@ -142,12 +142,35 @@ const Header = () => { const languageOptions = [ { key: 'zh', text: '中文', value: 'zh' }, { key: 'en', text: 'English', value: 'en' }, + { key: 'de', text: 'Deutsch', value: 'de' }, + { key: 'sr', text: 'Srpski', value: 'sr' }, ]; const changeLanguage = (language) => { i18n.changeLanguage(language); }; + const themeOptions = [ + { key: 'dereinering', text: 'DereineRing', value: 'dereinering' }, + { key: 'hermes', text: 'Hermes Overlay', value: 'hermes' }, + { key: 'matrix', text: 'Matrix Overlay', value: 'matrix' }, + { key: 'openclaw', text: 'OpenClaw Overlay', value: 'openclaw' }, + { key: 'default', text: 'Default OneAPI', value: 'default' }, + ]; + + const applyTheme = (themeName) => { + document.documentElement.setAttribute('data-ui-theme', themeName); + localStorage.setItem('ui_theme', themeName); + }; + + const [uiTheme, setUiTheme] = useState( + localStorage.getItem('ui_theme') || 'dereinering' + ); + + useEffect(() => { + applyTheme(uiTheme); + }, [uiTheme]); + if (isMobile()) { return ( <> @@ -203,6 +226,17 @@ const Header = () => { onChange={(_, { value }) => changeLanguage(value)} /> + + + } + options={themeOptions} + value={uiTheme} + onChange={(_, { value }) => setUiTheme(value)} + /> + {userState.user ? (