Files
Hermes-ui/packages/client/src/i18n/locales/de.ts
T
ww 610f3eb9d0 feat(copilot): integrate GitHub Copilot provider with dynamic model list / 集成 GitHub Copilot provider 与动态模型列表 (#239)
* feat(copilot): integrate GitHub Copilot provider with dynamic model list

集成 GitHub Copilot provider 与动态模型列表

EN:
- New copilot-models service: fetch live model list from GitHub /models API
  - Filter noise IDs (accounts/, text-embedding, rerank prefixes)
  - Pass through preview/disabled metadata to frontend
  - Cache isolated per OAuth token (FNV-1a hash key) to prevent cross-account leak
  - Multi-source token resolution: env > apps.json > gh CLI
- ModelSelector renders PREVIEW (orange) and UNAVAILABLE (gray, non-selectable)
  badges with tooltips
- ProviderFormModal exposes Copilot OAuth login entry
- New CopilotLoginModal component: guides gh auth login device flow
- ProviderCard hides delete button for OAuth-only builtin providers
  (copilot/codex/nous) since their credentials live outside auth.json

ZH:
- 新增 copilot-models 服务:从 GitHub /models live API 拉取模型列表
  - 噪音 ID 过滤(accounts/、text-embedding、rerank 前缀)
  - preview/disabled 元数据透传至前端
  - 缓存按 OAuth token 隔离(FNV-1a hash key),避免切换 profile 串账号
  - 多源 token 解析优先级:env > apps.json > gh CLI
- ModelSelector 渲染 PREVIEW(橙色)/ UNAVAILABLE(灰色、不可选)badge,附 tooltip
- ProviderFormModal 提供 Copilot OAuth 登录入口
- 新增 CopilotLoginModal 组件:引导 gh auth login 设备流程
- ProviderCard 对 OAuth-only builtin(copilot/codex/nous)隐藏删除按钮
  其凭证不在 auth.json,删除按钮原本无效

Tests / 测试: new copilot-models suite (cache isolation, noise filter,
preview/disabled passthrough) + copilot-login-modal — 24/24 passed.
Pre-existing sessions-db-lineage failure on upstream/main is unrelated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(copilot): switch to explicit opt-in per maintainer feedback

回应 PR #239 review:上一版会自动把系统级 GitHub OAuth 凭证(VS Code Copilot
插件、gh CLI 登录态)当作 hermes provider 拉到列表里,对未在 hermes 中注册过
Copilot 的用户造成困扰。本次改为显式 opt-in:用户必须通过 Add Provider 主动添加,
删除时按 token 来源决定是否清 ~/.hermes/.env,并避免误清理 VS Code / gh CLI 用户的
全局凭证。

Address PR #239 review feedback. Previously Copilot would silently appear in the
provider list whenever the host had any GitHub OAuth token (VS Code plugin, gh CLI
login). This caused confusion for users who never explicitly registered Copilot
in hermes. Now Copilot requires explicit opt-in via Add Provider; on delete we only
clear ~/.hermes/.env when the token actually originated there, leaving VS Code /
gh CLI credentials untouched.

What changed
- 新增 ~/.hermes-web-ui/config.json 的 copilotEnabled flag 控制可见性
- 即便能解析到 token,未启用时也不在列表中显示
- resolveCopilotOAuthTokenWithSource 区分 token 来源(env / gh-cli / apps-json)
- ProviderFormModal 增加 GitHub Copilot 入口;无 token 时进 device flow modal
- CopilotLoginModal 重写为 in-app device flow 状态机(不再要求用户在终端跑 gh)
- 删除 Copilot 时仅 source='env' 才清 ~/.hermes/.env,并自动 fallback 默认模型
- 老用户升级兼容:若 default 仍指向已禁用的 copilot,后端清空 default 让前端兜底

API
- POST /api/hermes/copilot-auth/check-token
- POST /api/hermes/copilot-auth/enable
- POST /api/hermes/copilot-auth/disable
- POST /api/hermes/copilot-auth/start  (device flow)
- POST /api/hermes/copilot-auth/poll   (device flow)

Tests
- tests/server/copilot-auth-controller.test.ts (11 cases)
- tests/server/copilot-device-flow.test.ts (12 cases)
- tests/client/copilot-login-modal.test.ts 重写覆盖状态机

Follow-ups (留作后续 PR)
- device flow session 未绑定 profile,登录中切 profile 会写到错的 .env
- copilot device-code 接口的 expires_in 字段未使用,硬编码 15 分钟超时

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 22:51:35 +08:00

699 lines
28 KiB
TypeScript

export default {
// Login
login: {
title: 'Hermes Web UI',
description: 'Geben Sie Ihren Zugangs-Token ein, um fortzufahren. Finden Sie ihn in den Server-Startprotokollen.',
placeholder: 'Zugangs-Token',
submit: 'Anmelden',
tokenRequired: 'Bitte geben Sie Ihren Zugangs-Token ein',
invalidToken: 'Ungultiger Token',
connectionFailed: 'Verbindung zum Server nicht moglich',
passwordLogin: 'Passwort',
tokenLogin: 'Token',
usernamePlaceholder: 'Benutzername',
passwordPlaceholder: 'Passwort',
credentialsRequired: 'Bitte Benutzername und Passwort eingeben',
invalidCredentials: 'Ungultiger Benutzername oder Passwort',
passwordMismatch: 'Passworter stimmen nicht uberein',
passwordTooShort: 'Passwort muss mindestens 6 Zeichen lang sein',
setupSuccess: 'Passwort-Login erfolgreich konfiguriert',
passwordChanged: 'Passwort erfolgreich geandert',
passwordRemoved: 'Passwort-Login entfernt',
setupPassword: 'Passwort-Login einrichten',
changePassword: 'Passwort andern',
changeUsername: 'Benutzername andern',
removePasswordLogin: 'Entfernen',
username: 'Benutzername',
currentPassword: 'Aktuelles Passwort',
newPassword: 'Neues Passwort',
confirmPassword: 'Passwort bestatigen',
newUsername: 'Neuer Benutzername',
usernameChanged: 'Benutzername erfolgreich geandert',
usernameTooShort: 'Benutzername muss mindestens 2 Zeichen lang sein',
setupDescription: 'Richten Sie Benutzername und Passwort fur bequemes Login ein. Der Zugangs-Token bleibt als Backup verfugbar.',
removeConfirm: 'Mochten Sie das Passwort-Login wirklich entfernen? Sie mussen dann den Zugangs-Token verwenden.',
passwordLoginNotConfigured: 'Passwort-Login ist nicht konfiguriert',
passwordLoginConfigured: 'Passwort-Login aktiviert ({username})',
},
// Common
common: {
loading: 'Laden...',
cancel: 'Abbrechen',
retry: 'Erneutern',
delete: 'Loschen',
edit: 'Bearbeiten',
save: 'Speichern',
saved: 'Gespeichert',
update: 'Aktualisieren',
create: 'Erstellen',
saveFailed: 'Speichern fehlgeschlagen',
deleteFailed: 'Loschen fehlgeschlagen',
ok: 'OK',
copied: 'Kopiert',
copy: 'Kopieren',
noData: 'Keine Daten',
fetch: 'Abrufen',
add: 'Hinzufugen',
enable: 'Aktivieren',
disable: 'Deaktivieren',
configured: 'Konfiguriert',
notConfigured: 'Nicht konfiguriert',
confirm: 'Bestatigen',
expand: 'Aufklappen',
collapse: 'Zuklappen',
},
// Sidebar
sidebar: {
chat: 'Chat',
jobs: 'Geplante Aufgaben',
models: 'Modelle',
profiles: 'Profile',
skills: 'Fahigkeiten',
memory: 'Gedachtnis',
logs: 'Protokolle',
usage: 'Nutzung',
channels: 'Kanale',
terminal: 'Terminal',
files: 'Dateien',
groupChat: 'Gruppenchat',
groupConversation: 'Konversation',
settings: 'Einstellungen',
connected: 'Verbunden',
disconnected: 'Getrennt',
updateTip: 'Fuhren Sie "hermes-web-ui update" im Terminal aus, um zu aktualisieren',
updateVersion: 'Aktualisieren auf v{version}',
updating: 'Aktualisierung...',
updateSuccess: 'Aktualisierung abgeschlossen, bitte Server neu starten',
updateFailed: 'Aktualisierung fehlgeschlagen',
logout: 'Abmelden',
nodeVersionWarning: 'Node.js v{version} erkannt. Bitte aktualisieren Sie auf Version 23 oder neuer.',
changelog: 'Anderungsprotokoll',
noChangelog: 'Kein Anderungsprotokoll verfugbar',
},
// Chat
chat: {
contextRemaining: 'übrig',
emptyState: 'Starten Sie eine Konversation mit Hermes Agent',
inputPlaceholder: 'Nachricht eingeben... (Enter zum Senden, Shift+Enter fur neue Zeile)',
attachFiles: 'Dateien anhangen',
stop: 'Stopp',
send: 'Senden',
contextUsed: 'Kontext verwendet:',
sessions: 'Sitzungen',
noSessions: 'Keine Sitzungen',
newChat: 'Neuer Chat',
deleteSession: 'Diese Sitzung loschen?',
sessionDeleted: 'Sitzung geloscht',
rename: 'Umbenennen',
pin: 'Anheften',
unpin: 'Lösen',
pinned: 'Angeheftet',
chatMode: 'Chat',
liveMode: 'Live',
liveSessions: 'Live-Sitzungen',
recentBadge: 'Kürzlich',
linkedSessions: '{count} verknüpft',
noVisibleMessages: 'Keine für Menschen sichtbaren Nachrichten.',
monitorRoleUser: 'Benutzer',
monitorRoleAssistant: 'Assistent',
copySessionId: 'Sitzungs-ID kopieren',
renamed: 'Umbenannt',
renameFailed: 'Umbenennung fehlgeschlagen',
renameSession: 'Sitzung umbenennen',
enterNewTitle: 'Neuen Titel eingeben',
other: 'Sonstige',
runFailed: 'Ausfuhrung fehlgeschlagen',
error: 'Fehler',
tool: 'Werkzeug',
arguments: 'Argumente',
result: 'Ergebnis',
truncated: '... (abgeschnitten)',
thinkingLabel: 'Denkprozess',
thinkingInProgress: 'Denkt…',
thinkingShow: 'Denkprozess anzeigen',
thinkingHide: 'Denkprozess ausblenden',
thinkingDuration: 'Beobachtet {duration}',
thinkingChars: '{count} Zeichen',
},
// Jobs
jobs: {
title: 'Geplante Aufgaben',
createJob: 'Aufgabe erstellen',
editJob: 'Aufgabe bearbeiten',
noJobs: 'Noch keine geplanten Aufgaben. Erstellen Sie eine, um zu beginnen.',
name: 'Name',
namePlaceholder: 'Aufgabenname',
schedule: 'Zeitplan (Cron-Ausdruck)',
schedulePlaceholder: 'z. B. 0 9 * * *',
quickPresets: 'Schnellvorgaben',
selectPreset: 'Vorgabe auswahlen...',
presetEveryMinute: 'Jede Minute',
presetEvery5Min: 'Alle 5 Minuten',
presetEveryHour: 'Jede Stunde',
presetEveryDay: 'Jeden Tag um 00:00',
presetEveryDay9: 'Jeden Tag um 09:00',
presetEveryMonday: 'Jeden Montag um 09:00',
presetEveryMonth: 'Am 1. jedes Monats um 09:00',
prompt: 'Prompt',
promptPlaceholder: 'Der auszufuhrende Prompt',
deliverTarget: 'Zustellziel',
origin: 'Herkunft',
local: 'Lokal',
repeatCount: 'Wiederholungsanzahl (optional)',
repeatPlaceholder: 'Leer lassen fur unendlich',
jobCreated: 'Aufgabe erstellt',
jobUpdated: 'Aufgabe aktualisiert',
nameRequired: 'Name ist erforderlich',
scheduleRequired: 'Zeitplan ist erforderlich',
loadFailed: 'Laden der Aufgabe fehlgeschlagen',
jobPaused: 'Aufgabe pausiert',
jobResumed: 'Aufgabe fortgesetzt',
jobTriggered: 'Aufgabe ausgelost',
jobDeleted: 'Aufgabe geloscht',
status: {
running: 'Lauft',
paused: 'Pausiert',
disabled: 'Deaktiviert',
scheduled: 'Geplant',
},
info: {
schedule: 'Zeitplan',
lastRun: 'Letzte Ausfuhrung',
nextRun: 'Nachste Ausfuhrung',
deliver: 'Zustellung',
repeat: 'Wiederholung',
},
action: {
pause: 'Pausieren',
pauseJob: 'Aufgabe pausieren',
resume: 'Fortsetzen',
resumeJob: 'Aufgabe fortsetzen',
runNow: 'Jetzt ausfuhren',
triggerImmediately: 'Sofort auslosen',
},
},
// Skills
skills: {
title: 'Fahigkeiten',
searchPlaceholder: 'Fahigkeiten suchen...',
noMatch: 'Keine Fahigkeiten entsprechen Ihrer Suche',
noSkills: 'Keine Fahigkeiten gefunden',
backTo: 'Zuruck zu',
attachedFiles: 'Angehange Dateien',
loadFailed: 'Laden der Fahigkeit fehlgeschlagen',
fileLoadFailed: 'Laden der Datei fehlgeschlagen',
toggleFailed: 'Aktivieren/Deaktivieren der Fahigkeit fehlgeschlagen',
},
// Memory
memory: {
title: 'Gedachtnis',
refresh: 'Aktualisieren',
loadFailed: 'Laden des Gedachtnisses fehlgeschlagen',
myNotes: 'Meine Notizen',
noNotes: 'Noch keine Notizen.',
notesPlaceholder: 'Notizen schreiben...',
userProfile: 'Benutzerprofil',
noProfile: 'Noch kein Profil.',
profilePlaceholder: 'Profil schreiben...',
soul: 'Seele',
noSoul: 'Noch keine Seelenkonfiguration.',
soulPlaceholder: 'Seelenkonfiguration schreiben...',
},
// Models
models: {
title: 'Modelle',
addProvider: 'Anbieter hinzufugen',
providerType: 'Anbietertyp',
preset: 'Vorgabe',
custom: 'Benutzerdefiniert',
selectProvider: 'Anbieter auswahlen',
chooseProvider: 'Anbieter wahlen...',
name: 'Name',
autoGeneratedName: 'Automatisch aus Basis-URL generiert',
baseUrl: 'Basis-URL',
region: 'Region',
regionIntl: 'International',
regionCn: 'Festlandchina',
baseUrlPlaceholder: 'z. B. https://api.example.com/v1',
apiKey: 'API-Schlussel',
apiKeyPlaceholder: 'sk-...',
defaultModel: 'Standardmodell',
selectOrInput: 'Modell auswählen oder eingeben...',
selectModel: 'Modell auswahlen...',
providerAdded: 'Anbieter hinzugefugt',
providerDeleted: 'Anbieter geloscht',
deleteProvider: 'Anbieter loschen',
deleteConfirm: 'Mochten Sie "{name}" wirklich loschen?',
codexLoginTitle: 'OpenAI Codex Anmeldung',
codexWaiting: 'Geben Sie diesen Code auf der Autorisierungsseite ein, um sich anzumelden:',
codexCopyCode: 'Code kopiert',
codexOpenLink: 'Autorisierungsseite öffnen',
codexApproved: 'Anmeldung erfolgreich',
codexExpired: 'Die Autorisierung ist abgelaufen. Bitte versuchen Sie es erneut.',
nousLoginTitle: 'Nous Portal Login',
nousWaiting: 'Geben Sie diesen Code auf der Autorisierungsseite ein:',
nousCopyCode: 'Code kopiert',
nousOpenLink: 'Autorisierungsseite öffnen',
nousApproved: 'Login erfolgreich',
nousDenied: 'Autorisierung wurde abgelehnt',
nousExpired: 'Autorisierung abgelaufen',
copilotLoginTitle: 'GitHub Copilot Anmeldung',
copilotWaiting: 'Öffnen Sie GitHub und geben Sie den unten angezeigten Gerätecode ein. Das Fenster schließt sich automatisch nach Genehmigung.',
copilotCopyCode: 'Code kopiert',
copilotOpenLink: 'GitHub-Autorisierungsseite öffnen',
copilotApproved: 'Anmeldung erfolgreich!',
copilotDenied: 'Autorisierung abgelehnt.',
copilotExpired: 'Der Autorisierungslink ist abgelaufen. Bitte erneut versuchen.',
copilotAddDetectedTitle: 'GitHub Copilot erkannt',
copilotAddDetected: 'Auf diesem Rechner wurde ein GitHub Copilot OAuth-Token erkannt. Klicken Sie auf „Hinzufügen", um Copilot in Hermes zu aktivieren.',
copilotAddSourceEnv: 'Quelle: ~/.hermes/.env (COPILOT_GITHUB_TOKEN)',
copilotAddSourceGhCli: 'Quelle: gh CLI (gh auth token)',
copilotAddSourceAppsJson: 'Quelle: VS Code Copilot-Erweiterung (apps.json)',
copilotDeleteHintEnv: 'Dies löscht COPILOT_GITHUB_TOKEN in ~/.hermes/.env. Andere Tools sind nicht betroffen.',
copilotDeleteHintGhCli: 'Copilot wird aus Hermes ausgeblendet. Ihre gh CLI-Anmeldung bleibt erhalten — `gh auth status` zeigt weiterhin als angemeldet.',
copilotDeleteHintAppsJson: 'Copilot wird aus Hermes ausgeblendet. Ihre VS Code Copilot-Erweiterung bleibt angemeldet.',
customBadge: 'BENUTZERDEF.',
previewBadge: 'VORSCHAU',
disabledBadge: 'NICHT VERFÜGBAR',
disabledTooltip: "Dieses Modell ist für Ihr Konto derzeit nicht verfügbar.",
customModelPlaceholder: 'Benutzerdefinierter Modellname',
customModelHint: 'Enter zum Laden',
noProviders: 'Keine Anbieter gefunden. Fugen Sie einen benutzerdefinierten Anbieter hinzu, um zu beginnen.',
builtIn: 'Integriert',
customType: 'Benutzerdefiniert',
provider: 'Anbieter',
contextLength: 'Kontextlange',
contextLengthPlaceholder: 'z.B. 200000 (optional)',
local: 'Lokal ({host})',
selectProviderRequired: 'Bitte wahlen Sie einen Anbieter',
baseUrlRequired: 'Basis-URL ist erforderlich',
apiKeyRequired: 'API-Schlussel ist erforderlich',
modelRequired: 'Standardmodell ist erforderlich',
enterBaseUrl: 'Bitte geben Sie zuerst die Basis-URL ein',
unexpectedFormat: 'Unerwartetes Antwortformat',
foundModels: '{count} Modelle gefunden',
fetchFailed: 'Abrufen der Modelle fehlgeschlagen',
},
// Profiles
profiles: {
title: 'Profile',
create: 'Profil erstellen',
import: 'Importieren',
export: 'Exportieren',
rename: 'Umbenennen',
delete: 'Loschen',
switchTo: 'Wechseln zu',
switchConfirm: 'Das Wechseln zum Profil "{name}" startet das Gateway neu. Fortfahren?',
switchSuccess: 'Zum Profil "{name}" gewechselt',
switchFailed: 'Profilwechsel fehlgeschlagen. Moglicherweise muss das Gateway manuell neu gestartet werden.',
createSuccess: 'Profil "{name}" erstellt',
createFailed: 'Erstellen des Profils fehlgeschlagen',
renameSuccess: 'Profil umbenannt',
renameFailed: 'Umbenennung des Profils fehlgeschlagen',
deleteConfirm: 'Mochten Sie das Profil "{name}" wirklich loschen?',
deleteSuccess: 'Profil geloscht',
deleteFailed: 'Loschen des Profils fehlgeschlagen',
exportSuccess: 'Profil exportiert',
exportFailed: 'Exportieren des Profils fehlgeschlagen',
importSuccess: 'Profil importiert',
importFailed: 'Importieren des Profils fehlgeschlagen',
importSelectFile: 'Archivdatei auswahlen',
importInvalidFile: 'Bitte wahlen Sie ein gultiges Archiv (.tar.gz, .tgz, .gz, .zip)',
name: 'Profilname',
namePlaceholder: 'Nur Buchstaben, Zahlen und Bindestriche',
newName: 'Neuer Name',
newNamePlaceholder: 'Neuen Namen eingeben',
cloneFromCurrent: 'Aus aktuellem Profil klonen',
archivePath: 'Archivpfad',
archivePathPlaceholder: 'Serverpfad zur Archivdatei',
importName: 'Profilname (optional)',
importNamePlaceholder: 'Leer lassen, um den Archivnamen zu verwenden',
active: 'Aktiv',
model: 'Modell',
gateway: 'Gateway',
alias: 'Alias',
provider: 'Anbieter',
path: 'Pfad',
skills: 'Fahigkeiten',
hasEnv: 'Hat .env',
hasSoulMd: 'Hat soul.md',
noProfiles: 'Keine Profile gefunden. Erstellen Sie eines, um zu beginnen.',
},
// Logs
logs: {
title: 'Protokolle',
all: 'Alle',
searchPlaceholder: 'Suchen...',
refresh: 'Aktualisieren',
noEntries: 'Keine Protokolleintrage',
},
// Settings
settings: {
title: 'Einstellungen',
saved: 'Gespeichert',
saveFailed: 'Speichern fehlgeschlagen',
tabs: {
display: 'Anzeige',
account: 'Konto',
agent: 'Agent',
memory: 'Gedachtnis',
session: 'Sitzung',
privacy: 'Datenschutz',
apiServer: 'API-Server',
},
display: {
streaming: 'Streaming-Antworten',
streamingHint: 'KI-Antworten in Echtzeit anzeigen',
compact: 'Kompaktmodus',
compactHint: 'Nachrichtenabstand reduzieren',
showReasoning: 'Schlussfolgerung anzeigen',
showReasoningHint: 'Denkprozess des Modells anzeigen',
showCost: 'Kosten anzeigen',
showCostHint: 'Token-Nutzung in Antworten anzeigen',
inlineDiffs: 'Inline-Diffs',
inlineDiffsHint: 'Codeanderungen inline anzeigen',
bellOnComplete: 'Abschluss-Signalton',
bellOnCompleteHint: 'Ton abspielen, wenn die KI fertig ist',
busyInputMode: 'Eingabemodus bei Beschaftigung',
busyInputModeHint: 'Eingabe erlauben, wahrend die KI verarbeitet',
theme: 'Design',
themeHint: 'Hell, Dunkel oder Systemeinstellung verwenden',
themeLight: 'Hell',
themeDark: 'Dunkel',
themeSystem: 'System',
},
agent: {
maxTurns: 'Maximale Runden',
maxTurnsHint: 'Maximale Interaktionsrunden pro Konversation',
gatewayTimeout: 'Gateway-Timeout',
gatewayTimeoutHint: 'Anfrage-Timeout in Sekunden',
restartDrainTimeout: 'Neustart-Drain-Timeout',
restartDrainTimeoutHint: 'Drain-Timeout vor Neustart in Sekunden',
toolEnforcement: 'Werkzeuferzwingung',
toolEnforcementHint: 'Ausfuhrungsmodus fur Werkzeugaufrufe steuern',
auto: 'Automatisch',
always: 'Immer',
never: 'Nie',
},
memory: {
enabled: 'Gedachtnis aktivieren',
enabledHint: 'KI erlauben, den Konversationskontext zu merken',
userProfile: 'Benutzerprofil',
userProfileHint: 'KI erlauben, Benutzereinstellungen zu merken',
charLimit: 'Zeichenlimit fur Gedachtnis',
charLimitHint: 'Maximale Zeichen fur MEMORY.md',
userCharLimit: 'Zeichenlimit fur Benutzerprofil',
userCharLimitHint: 'Maximale Zeichen fur USER.md',
},
session: {
mode: 'Zurucksetzungsmodus',
modeHint: 'Ausloser fur Sitzungszurucksetzung',
modeBoth: 'Inaktivitat + Geplant',
modeIdle: 'Nur Inaktivitat',
modeHourly: 'Nur Geplant',
idleMinutes: 'Inaktivitats-Timeout',
idleMinutesHint: 'Wartezeit vor automatischer Zurucksetzung (Minuten)',
atHour: 'Geplante Zurucksetzungszeit',
humanOnly: 'Nur menschliche Sitzungen anzeigen',
humanOnlyHint: 'Unteragenten- und Sitzungsmonitor-Rauschen standardmäßig ausblenden',
liveMonitorHumanOnly: 'Live-Monitor: nur menschliche Sitzungen anzeigen',
liveMonitorHumanOnlyHint: 'Im Live-Monitor Unteragenten- und Sitzungsmonitor-Rauschen standardmäßig ausblenden',
atHourHint: 'Sitzung taglich zu dieser Stunde zurucksetzen',
},
privacy: {
redactPii: 'Personliche Daten maskieren',
redactPiiHint: 'Sensible Informationen automatisch erkennen und ausblenden (Passworter, Schlussel usw.)',
},
apiServer: {
enable: 'Aktivieren',
enableHint: 'API-Server aktivieren',
host: 'Host',
hostHint: 'Listen-Adresse',
port: 'Port',
portHint: 'Listen-Port',
key: 'Schlussel',
keyHint: 'API-Zugangsschlussel',
cors: 'CORS-Ursprunge',
corsHint: 'Erlaubte Cross-Origin-Quellen',
},
},
// Platform channel settings
platform: {
requireMention: "Erwahnung {'@'} erfordern",
requireMentionGroup: "Erwahnung {'@'} in Gruppen erfordern zum Antworten",
requireMentionChannel: "Erwahnung {'@'} in Kanalen erfordern zum Antworten",
requireMentionRoom: "Erwahnung {'@'} in Raumen erfordern zum Antworten",
reactions: 'Reaktionen',
reactionsHint: 'Auf Nachrichten mit Emoji reagieren',
freeResponseChats: 'Frei antwortende Chats',
freeResponseChatsHint: "Chat-IDs, die ohne {'@'}Erwahnung antworten (komma-getrennt)",
freeResponseChannels: 'Frei antwortende Kanale',
freeResponseChannelsHint: "Kanal-IDs, die ohne {'@'}Erwahnung antworten (komma-getrennt)",
freeResponseRooms: 'Frei antwortende Raume',
freeResponseRoomsHint: "Raum-IDs, die ohne {'@'}Erwahnung antworten (komma-getrennt)",
mentionPatterns: 'Benutzerdefinierte Erwahnungsmuster',
mentionPatternsHint: 'Zusatzliche Auslosermuster',
autoThread: 'Auto-Thread',
autoThreadHint: "Automatisch Antwort-Threads nach {'@'}Erwahnung erstellen",
autoThreadHintRoom: 'Automatisch Antwort-Threads in Raumen erstellen',
dmMentionThreads: 'DM-Erwahnungs-Threads',
dmMentionThreadsHint: 'Thread-Antworten fur Erwahnungen in DMs verwenden',
allowBots: 'Bot-Nachrichten erlauben',
allowBotsHint: 'Auf Nachrichten von anderen Bots antworten',
allowedChannels: 'Erlaubte Kanale',
allowedChannelsHint: 'Whitelist der Kanal-IDs (komma-getrennt)',
ignoredChannels: 'Ignorierte Kanale',
ignoredChannelsHint: 'Kanale, in denen der Bot nie antwortet (komma-getrennt)',
noThreadChannels: 'Thread-lose Kanale',
noThreadChannelsHint: 'Kanale, in denen der Bot ohne Threads antwortet (komma-getrennt)',
botToken: 'Bot-Token',
botTokenHint: 'Bot-Token vom Entwicklerportal',
accessToken: 'Zugangs-Token',
accessTokenHint: 'Matrix-Zugangs-Token',
homeserver: 'Homeserver-URL',
homeserverHint: 'Matrix-Homeserver-URL',
appId: 'App-ID',
appIdHint: 'Feishu App-ID',
appSecret: 'App-Geheimnis',
appSecretHint: 'Feishu App-Geheimnis',
clientId: 'Client-ID',
clientIdHint: 'DingTalk Client-ID',
clientSecret: 'Client-Geheimnis',
clientSecretHint: 'DingTalk Client-Geheimnis',
botId: 'Bot-ID',
botIdHint: 'WeCom Bot-ID',
wecomSecretHint: 'WeCom Bot-Geheimnis',
waEnabled: 'WhatsApp aktivieren',
waEnabledHint: 'WhatsApp uber QR-Code-Kopplung aktivieren',
weixinToken: 'Weixin-Token',
weixinTokenHint: 'Von Weixin-CLI QR-Anmeldung (hermes weixin)',
accountId: 'Konto-ID',
accountIdHint: 'Weixin-Konto-ID',
qrLogin: 'QR-Anmeldung',
qrRelogin: 'Erneut anmelden',
qrFetching: 'QR-Code wird abgerufen...',
qrScanHint: 'Mit WeChat scannen zum Anmelden',
qrScanedHint: 'Gescannt, bitte auf dem Gerat bestatigen...',
},
// Language
language: {
label: 'Sprache',
zh: '中文',
en: 'English',
de: 'Deutsch',
},
// Terminal
terminal: {
sessions: 'Sitzungen',
newTab: 'Neues Terminal',
closeSession: 'Diese Sitzung schliessen?',
sessionExited: 'Beendet',
processExited: 'Prozess beendet mit Code {code}',
},
// Usage
usage: {
title: 'Nutzungsstatistiken',
refresh: 'Aktualisieren',
totalTokens: 'Gesamt-Tokens',
inputTokens: 'Eingabe',
outputTokens: 'Ausgabe',
totalSessions: 'Gesamt-Sitzungen',
avgPerDay: '~{n}/Tag Durchschn.',
estimatedCost: 'Gesch. Kosten',
cacheHitRate: 'Cache-Trefferquote',
modelBreakdown: 'Modellaufschluesselung',
dailyTrend: 'Tagliche Nutzung (letzte 30 Tage)',
date: 'Datum',
tokens: 'Tokens',
cache: 'Cache',
sessions: 'Sitzungen',
cost: 'Kosten',
noData: 'Keine Nutzungsdaten',
},
// Anderungsprotokoll
changelog: {
new_0_4_8_1: 'Safe Mermaid diagram rendering with async render and timeout fallback',
new_0_4_8_2: 'Fix nested markdown fence rendering truncation',
new_0_4_8_3: 'Fix compressed session lineage projection and search',
new_0_4_8_4: 'Optimize session list N+1 queries and fix search 500 on non-CJK input',
new_0_4_8_5: 'Fix forced scroll to bottom when switching back from other tabs',
new_0_4_8_6: 'Smooth session switch with loading transition overlay',
new_0_4_8_7: 'Fix login token validation using Hermes session endpoint',
new_0_4_8_8: 'Fix image attachments broken after page refresh (blob URL persistence)',
new_0_4_8_9: 'Click image attachments to preview in fullscreen overlay',
new_0_4_8_10: 'Move upload directory from temp to ~/.hermes-web-ui/upload',
new_0_4_7_1: 'Echtzeit-Streaming-Anzeige von Denk-/Argumentationsblocken',
new_0_4_7_2: 'Prepare-Skript wahrend Docker-Build uberspringen',
new_0_4_7_3: 'Gruppenchat-Mobile-UX-Verbesserungen und UI-Aufpolierung',
new_0_4_7_4: 'Verbleibende Kontext-Token auf 0 statt auf negativ begrenzen',
new_0_4_7_5: 'Alibaba Coding Plan integrierter Provider mit .env base_url-Uberschreibung hinzugefugt',
new_0_4_7_6: 'Remote-Profile beim Start uberspringen, um Hanger zu vermeiden',
new_0_4_7_7: 'Stillschweigend verschluckte Laufzeitfehler erkennen und anzeigen',
new_0_4_7_8: 'Provider-bewusste Kontextlangen-Abfrage',
new_0_4_7_9: 'config.model beim Wechsel zurucksetzen und CLI-Custom-Provider auflosen',
new_0_4_7_10: 'base_url_env aus .env beim Loschen des integrierten Providers entfernen',
new_0_4_7_11: 'Gruppenchat-Raum-Seitenleistenhintergrund an die Sitzungsliste anpassen',
new_0_4_5_1: 'Add group chat with multi-agent rooms, @mention routing, and typing status recovery',
new_0_4_5_2: 'Rewrite model-context config to use YAML with context_length setting',
new_0_4_5_3: 'Add gpt-5.5 to OpenAI Codex model list',
new_0_4_5_4: 'Replace jobs proxy with local controller and optimize model loading',
new_0_4_5_5: 'Add i18n support for custom model feature in ModelSelector',
new_0_4_5_6: 'Fix sidebar i18n missing key warnings',
new_0_4_5_7: 'Clear all localStorage on logout',
new_0_4_5_8: 'Add periodic log rotation to prevent unbounded log growth',
new_0_4_2_1: 'Token-Nutzungsverfolgung und dynamische Kontextlange hinzugefugt',
new_0_4_2_2: 'Sitzungssuche-Modal hinzugefugt',
new_0_4_2_3: 'Gruppenchat-System mit Socket.IO und SQLite wiederhergestellt',
new_0_4_2_4: 'Angeheftete Sitzungen und Live-Monitor hinzugefugt',
new_0_4_2_5: 'Eingebaute Provider-Erkennung und Modellabgleich behoben',
},
// Dateien
files: {
title: 'Dateien',
tree: 'Verzeichnisbaum',
list: 'Dateiliste',
breadcrumbRoot: 'Start',
newFile: 'Neue Datei',
newFolder: 'Neuer Ordner',
upload: 'Hochladen',
refresh: 'Aktualisieren',
open: 'Offnen',
edit: 'Bearbeiten',
preview: 'Vorschau',
download: 'Herunterladen',
copyPath: 'Pfad kopieren',
rename: 'Umbenennen',
delete: 'Loschen',
name: 'Name',
size: 'Grosse',
modified: 'Geandert',
actions: 'Aktionen',
emptyDir: 'Leeres Verzeichnis',
loading: 'Wird geladen...',
confirmDelete: 'Mochten Sie "{name}" wirklich loschen?',
confirmDeleteDir: 'Mochten Sie das Verzeichnis "{name}" und seinen gesamten Inhalt wirklich loschen?',
deleteFailed: 'Loschen fehlgeschlagen',
deleted: 'Geloscht',
renameTo: 'Umbenennen in',
newFileName: 'Dateiname',
newFolderName: 'Ordnername',
created: 'Erstellt',
createFailed: 'Erstellen fehlgeschlagen',
renamed: 'Umbenannt',
renameFailed: 'Umbenennen fehlgeschlagen',
uploadSuccess: '{count} Datei(en) hochgeladen',
uploadFailed: 'Hochladen fehlgeschlagen',
saveFailed: 'Speichern fehlgeschlagen',
saved: 'Gespeichert',
unsavedChanges: 'Sie haben ungespeicherte Anderungen. Verwerfen?',
pathCopied: 'Pfad kopiert',
fileTooLarge: 'Datei zu gross (max. 10 MB)',
permissionDenied: 'Geschutzte Datei kann nicht geandert werden',
notFound: 'Datei oder Verzeichnis nicht gefunden',
backendError: 'Dateioperation fehlgeschlagen',
dragDropHint: 'Dateien hierher ziehen, um sie hochzuladen',
closeEditor: 'Editor schliessen',
closePreview: 'Schliessen',
saveFile: 'Speichern',
},
// Gruppenchat
groupChat: {
title: 'Gruppenchat',
createRoom: 'Raum erstellen',
joinByCode: 'Mit Code beitreten',
roomName: 'Raumname',
roomNamePlaceholder: 'Raumnamen eingeben',
inviteCode: 'Einladungscode',
autoGenerate: 'Automatisch generieren',
noRooms: 'Noch keine Raume',
selectOrCreate: 'Wahlen oder erstellen Sie einen Raum, um zu chatten',
agents: 'Agenten',
addAgent: 'Agent hinzufugen',
selectProfile: 'Wahlen Sie ein Profil',
agentAdded: 'Agent hinzugefugt',
agentAlreadyInRoom: 'Agent ist bereits in diesem Raum',
noAgents: 'Keine Agenten in diesem Raum',
members: 'Mitglieder',
roomCreated: 'Raum erstellt',
roomDeleted: 'Raum gelöscht',
deleteRoomConfirm: 'Diesen Raum löschen?',
you: 'Du',
joined: 'Raum beigetreten',
joinFailed: 'Beitreten fehlgeschlagen',
inputPlaceholder: 'Nachricht eingeben... (Enter zum Senden)',
enterCode: 'Einladungscode eingeben',
yourName: 'Dein Name',
yourNamePlaceholder: 'Gib deinen Anzeigenamen ein',
yourDescription: 'Beschreibung (optional)',
yourDescriptionPlaceholder: 'Erzahl anderen wer du bist...',
agentName: 'Agent-Name',
agentNamePlaceholder: 'Benutzerdefinierter Name (leer = Profilname)',
agentDesc: 'Agent-Beschreibung',
agentDescPlaceholder: 'Beschreiben Sie, was dieser Agent tut...',
agentReplying: 'antwortet...',
agentCompressing: 'komprimiert Kontext...',
compressionSettings: 'Komprimierungseinstellungen',
triggerTokens: 'Trigger-Token',
triggerTokensDesc: 'Token-Schwelle für Kontextkomprimierung',
maxHistoryTokens: 'Max. Verlaufs-Token',
maxHistoryTokensDesc: 'Maximale Token für komprimierten Kontext',
tailMessageCount: 'Nachrichten am Ende',
tailMessageCountDesc: 'Anzahl der letzten Nachrichten, die unverändert bleiben',
compressionConfig: 'Komprimierungskonfig',
compressNow: 'Jetzt komprimieren',
compressingInProgress: 'Komprimierung läuft, bitte warten',
compressionSaved: 'Konfiguration gespeichert',
},
// Download
download: {
downloading: 'Wird heruntergeladen...',
downloadFailed: 'Download fehlgeschlagen',
fileNotFound: 'Datei nicht gefunden oder geloscht',
fileTooLarge: 'Datei zu gross (Limit uberschritten)',
backendError: 'Lesen der Datei fehlgeschlagen, Remote-Umgebung moglicherweise nicht verfugbar',
backendTimeout: 'Zeituberschreitung beim Lesen der Datei',
unsupportedBackend: 'Aktuelles Terminal-Backend unterstutzt keine Datei-Downloads',
invalidPath: 'Ungultiger Dateipfad',
download: 'Herunterladen',
downloadFile: 'Datei herunterladen',
},
}