610f3eb9d0
* 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>
699 lines
28 KiB
TypeScript
699 lines
28 KiB
TypeScript
export default {
|
|
// Login
|
|
login: {
|
|
title: 'Hermes Web UI',
|
|
description: 'Insira seu token de acesso para continuar. Encontre-o nos logs de inicializacao do servidor.',
|
|
placeholder: 'Token de acesso',
|
|
submit: 'Entrar',
|
|
tokenRequired: 'Por favor, insira seu token de acesso',
|
|
invalidToken: 'Token invalido',
|
|
connectionFailed: 'Nao foi possivel conectar ao servidor',
|
|
passwordLogin: 'Senha',
|
|
tokenLogin: 'Token',
|
|
usernamePlaceholder: 'Nome de usuario',
|
|
passwordPlaceholder: 'Senha',
|
|
credentialsRequired: 'Por favor, insira nome de usuario e senha',
|
|
invalidCredentials: 'Nome de usuario ou senha incorretos',
|
|
passwordMismatch: 'As senhas nao conferem',
|
|
passwordTooShort: 'A senha deve ter pelo menos 6 caracteres',
|
|
setupSuccess: 'Login por senha configurado com sucesso',
|
|
passwordChanged: 'Senha alterada com sucesso',
|
|
passwordRemoved: 'Login por senha removido',
|
|
setupPassword: 'Configurar login por senha',
|
|
changePassword: 'Alterar senha',
|
|
changeUsername: 'Alterar nome de usuario',
|
|
removePasswordLogin: 'Remover',
|
|
username: 'Nome de usuario',
|
|
currentPassword: 'Senha atual',
|
|
newPassword: 'Nova senha',
|
|
confirmPassword: 'Confirmar senha',
|
|
newUsername: 'Novo nome de usuario',
|
|
usernameChanged: 'Nome de usuario alterado com sucesso',
|
|
usernameTooShort: 'O nome de usuario deve ter pelo menos 2 caracteres',
|
|
setupDescription: 'Configure um nome de usuario e senha para login conveniente. O token de acesso continuara funcionando como backup.',
|
|
removeConfirm: 'Tem certeza de que deseja remover o login por senha? Voce precisara usar o token de acesso.',
|
|
passwordLoginNotConfigured: 'Login por senha nao configurado',
|
|
passwordLoginConfigured: 'Login por senha habilitado ({username})',
|
|
},
|
|
|
|
// Common
|
|
common: {
|
|
loading: 'Carregando...',
|
|
cancel: 'Cancelar',
|
|
retry: 'Tentar novamente',
|
|
delete: 'Excluir',
|
|
edit: 'Editar',
|
|
save: 'Salvar',
|
|
saved: 'Salvo',
|
|
update: 'Atualizar',
|
|
create: 'Criar',
|
|
saveFailed: 'Falha ao salvar',
|
|
deleteFailed: 'Falha ao excluir',
|
|
ok: 'OK',
|
|
copied: 'Copiado',
|
|
copy: 'Copiar',
|
|
noData: 'Sem dados',
|
|
fetch: 'Buscar',
|
|
add: 'Adicionar',
|
|
enable: 'Ativar',
|
|
disable: 'Desativar',
|
|
configured: 'Configurado',
|
|
notConfigured: 'Nao configurado',
|
|
confirm: 'Confirmar',
|
|
expand: 'Expandir',
|
|
collapse: 'Recolher',
|
|
},
|
|
|
|
// Sidebar
|
|
sidebar: {
|
|
chat: 'Chat',
|
|
jobs: 'Tarefas agendadas',
|
|
models: 'Modelos',
|
|
profiles: 'Perfis',
|
|
skills: 'Habilidades',
|
|
memory: 'Memoria',
|
|
logs: 'Logs',
|
|
usage: 'Uso',
|
|
channels: 'Canais',
|
|
terminal: 'Terminal',
|
|
files: 'Arquivos',
|
|
groupChat: 'Chat em grupo',
|
|
groupConversation: 'Conversa',
|
|
settings: 'Configuracoes',
|
|
connected: 'Conectado',
|
|
disconnected: 'Desconectado',
|
|
updateTip: 'Execute "hermes-web-ui update" no terminal para atualizar',
|
|
updateVersion: 'Atualizar para v{version}',
|
|
updating: 'Atualizando...',
|
|
updateSuccess: 'Atualizacao concluida, por favor reinicie o servidor',
|
|
updateFailed: 'Falha na atualizacao',
|
|
logout: 'Sair',
|
|
nodeVersionWarning: 'Node.js v{version} detectado. Atualize para a versao 23 ou posterior.',
|
|
changelog: 'Registro de alteracoes',
|
|
noChangelog: 'Nenhum registro disponivel',
|
|
},
|
|
|
|
// Chat
|
|
chat: {
|
|
contextRemaining: 'restante',
|
|
emptyState: 'Inicie uma conversa com o Hermes Agent',
|
|
inputPlaceholder: 'Digite uma mensagem... (Enter para enviar, Shift+Enter para nova linha)',
|
|
attachFiles: 'Anexar arquivos',
|
|
stop: 'Parar',
|
|
send: 'Enviar',
|
|
contextUsed: 'Contexto utilizado:',
|
|
sessions: 'Sessoes',
|
|
noSessions: 'Sem sessoes',
|
|
newChat: 'Novo chat',
|
|
deleteSession: 'Excluir esta sessao?',
|
|
sessionDeleted: 'Sessao excluida',
|
|
rename: 'Renomear',
|
|
pin: 'Fixar',
|
|
unpin: 'Desafixar',
|
|
pinned: 'Fixadas',
|
|
chatMode: 'Chat',
|
|
liveMode: 'Ao vivo',
|
|
liveSessions: 'Sessões ao vivo',
|
|
recentBadge: 'Recente',
|
|
linkedSessions: '{count} vinculadas',
|
|
noVisibleMessages: 'Nenhuma mensagem visível para humanos.',
|
|
monitorRoleUser: 'Usuário',
|
|
monitorRoleAssistant: 'Assistente',
|
|
copySessionId: 'Copiar ID da sessão',
|
|
renamed: 'Renomeado',
|
|
renameFailed: 'Falha ao renomear',
|
|
renameSession: 'Renomear sessao',
|
|
enterNewTitle: 'Digite um novo titulo',
|
|
other: 'Outro',
|
|
runFailed: 'Falha na execucao',
|
|
error: 'Erro',
|
|
tool: 'Ferramenta',
|
|
arguments: 'Argumentos',
|
|
result: 'Resultado',
|
|
truncated: '... (truncado)',
|
|
thinkingLabel: 'Raciocínio',
|
|
thinkingInProgress: 'Pensando…',
|
|
thinkingShow: 'Mostrar raciocínio',
|
|
thinkingHide: 'Ocultar raciocínio',
|
|
thinkingDuration: 'Observado {duration}',
|
|
thinkingChars: '{count} caracteres',
|
|
},
|
|
|
|
// Jobs
|
|
jobs: {
|
|
title: 'Tarefas agendadas',
|
|
createJob: 'Criar tarefa',
|
|
editJob: 'Editar tarefa',
|
|
noJobs: 'Nenhuma tarefa agendada ainda. Crie uma para comecar.',
|
|
name: 'Nome',
|
|
namePlaceholder: 'Nome da tarefa',
|
|
schedule: 'Agendamento (expressao Cron)',
|
|
schedulePlaceholder: 'ex. 0 9 * * *',
|
|
quickPresets: 'Presets rapidos',
|
|
selectPreset: 'Selecionar um preset...',
|
|
presetEveryMinute: 'Cada minuto',
|
|
presetEvery5Min: 'A cada 5 minutos',
|
|
presetEveryHour: 'Cada hora',
|
|
presetEveryDay: 'Todos os dias as 00:00',
|
|
presetEveryDay9: 'Todos os dias as 09:00',
|
|
presetEveryMonday: 'Toda segunda as 09:00',
|
|
presetEveryMonth: 'Dia 1 de cada mes as 09:00',
|
|
prompt: 'Prompt',
|
|
promptPlaceholder: 'O prompt a executar',
|
|
deliverTarget: 'Destino de entrega',
|
|
origin: 'Origem',
|
|
local: 'Local',
|
|
repeatCount: 'Contagem de repeticoes (opcional)',
|
|
repeatPlaceholder: 'Deixar vazio para infinito',
|
|
jobCreated: 'Tarefa criada',
|
|
jobUpdated: 'Tarefa atualizada',
|
|
nameRequired: 'O nome e obrigatorio',
|
|
scheduleRequired: 'O agendamento e obrigatorio',
|
|
loadFailed: 'Falha ao carregar a tarefa',
|
|
jobPaused: 'Tarefa pausada',
|
|
jobResumed: 'Tarefa retomada',
|
|
jobTriggered: 'Tarefa acionada',
|
|
jobDeleted: 'Tarefa excluida',
|
|
status: {
|
|
running: 'Em execucao',
|
|
paused: 'Pausada',
|
|
disabled: 'Desativada',
|
|
scheduled: 'Agendada',
|
|
},
|
|
info: {
|
|
schedule: 'Agendamento',
|
|
lastRun: 'Ultima execucao',
|
|
nextRun: 'Proxima execucao',
|
|
deliver: 'Entrega',
|
|
repeat: 'Repeticao',
|
|
},
|
|
action: {
|
|
pause: 'Pausar',
|
|
pauseJob: 'Pausar tarefa',
|
|
resume: 'Retomar',
|
|
resumeJob: 'Retomar tarefa',
|
|
runNow: 'Executar agora',
|
|
triggerImmediately: 'Acionar imediatamente',
|
|
},
|
|
},
|
|
|
|
// Skills
|
|
skills: {
|
|
title: 'Habilidades',
|
|
searchPlaceholder: 'Buscar habilidades...',
|
|
noMatch: 'Nenhuma habilidade corresponde a sua busca',
|
|
noSkills: 'Nenhuma habilidade encontrada',
|
|
backTo: 'Voltar para',
|
|
attachedFiles: 'Arquivos anexados',
|
|
loadFailed: 'Falha ao carregar a habilidade',
|
|
fileLoadFailed: 'Falha ao carregar o arquivo',
|
|
toggleFailed: 'Falha ao ativar/desativar a habilidade',
|
|
},
|
|
|
|
// Memory
|
|
memory: {
|
|
title: 'Memoria',
|
|
refresh: 'Atualizar',
|
|
loadFailed: 'Falha ao carregar a memoria',
|
|
myNotes: 'Minhas notas',
|
|
noNotes: 'Nenhuma nota ainda.',
|
|
notesPlaceholder: 'Escreva suas notas...',
|
|
userProfile: 'Perfil do usuario',
|
|
noProfile: 'Nenhum perfil ainda.',
|
|
profilePlaceholder: 'Escreva seu perfil...',
|
|
soul: 'Alma',
|
|
noSoul: 'Nenhuma configuracao de alma ainda.',
|
|
soulPlaceholder: 'Escreva a configuracao da alma...',
|
|
},
|
|
|
|
// Models
|
|
models: {
|
|
title: 'Modelos',
|
|
addProvider: 'Adicionar provedor',
|
|
providerType: 'Tipo de provedor',
|
|
preset: 'Preset',
|
|
custom: 'Personalizado',
|
|
selectProvider: 'Selecionar provedor',
|
|
chooseProvider: 'Escolha um provedor...',
|
|
name: 'Nome',
|
|
autoGeneratedName: 'Gerado automaticamente pela URL base',
|
|
baseUrl: 'URL base',
|
|
region: 'Região',
|
|
regionIntl: 'Internacional',
|
|
regionCn: 'China Continental',
|
|
baseUrlPlaceholder: 'ex. https://api.example.com/v1',
|
|
apiKey: 'Chave API',
|
|
apiKeyPlaceholder: 'sk-...',
|
|
defaultModel: 'Modelo padrao',
|
|
selectOrInput: 'Selecionar ou digitar um modelo...',
|
|
selectModel: 'Selecionar um modelo...',
|
|
providerAdded: 'Provedor adicionado',
|
|
providerDeleted: 'Provedor excluido',
|
|
deleteProvider: 'Excluir provedor',
|
|
deleteConfirm: 'Tem certeza de que deseja excluir "{name}"?',
|
|
codexLoginTitle: 'Login do OpenAI Codex',
|
|
codexWaiting: 'Digite este código na página de autorização para fazer login:',
|
|
codexCopyCode: 'Código copiado',
|
|
codexOpenLink: 'Abrir página de autorização',
|
|
codexApproved: 'Login bem-sucedido',
|
|
codexExpired: 'A autorização expirou. Por favor, tente novamente.',
|
|
nousLoginTitle: 'Login do Nous Portal',
|
|
nousWaiting: 'Insira este código na página de autorização:',
|
|
nousCopyCode: 'Código copiado',
|
|
nousOpenLink: 'Abrir página de autorização',
|
|
nousApproved: 'Login bem-sucedido',
|
|
nousDenied: 'Autorização negada',
|
|
nousExpired: 'Autorização expirada',
|
|
copilotLoginTitle: 'Login do GitHub Copilot',
|
|
copilotWaiting: 'Abra o GitHub e insira o código do dispositivo abaixo para autorizar. A janela fechará automaticamente após a aprovação.',
|
|
copilotCopyCode: 'Código copiado',
|
|
copilotOpenLink: 'Abrir a página de autorização do GitHub',
|
|
copilotApproved: 'Login bem-sucedido!',
|
|
copilotDenied: 'Autorização negada.',
|
|
copilotExpired: 'O link de autorização expirou. Tente novamente.',
|
|
copilotAddDetectedTitle: 'GitHub Copilot detectado',
|
|
copilotAddDetected: 'Foi detectado um token OAuth do GitHub Copilot nesta máquina. Clique em Adicionar para ativar o Copilot no Hermes.',
|
|
copilotAddSourceEnv: 'Origem: ~/.hermes/.env (COPILOT_GITHUB_TOKEN)',
|
|
copilotAddSourceGhCli: 'Origem: gh CLI (gh auth token)',
|
|
copilotAddSourceAppsJson: 'Origem: extensão Copilot do VS Code (apps.json)',
|
|
copilotDeleteHintEnv: 'Isto irá limpar o COPILOT_GITHUB_TOKEN em ~/.hermes/.env. Outras ferramentas não são afetadas.',
|
|
copilotDeleteHintGhCli: 'O Copilot ficará oculto no Hermes. Sua sessão no gh CLI não é afetada — `gh auth status` continuará indicando que está conectado.',
|
|
copilotDeleteHintAppsJson: 'O Copilot ficará oculto no Hermes. A extensão Copilot do VS Code continuará conectada.',
|
|
customBadge: 'PERSONALIZADO',
|
|
previewBadge: 'PRÉVIA',
|
|
disabledBadge: 'INDISPONÍVEL',
|
|
disabledTooltip: "Este modelo não está disponível para sua conta.",
|
|
customModelPlaceholder: 'Nome do modelo personalizado',
|
|
customModelHint: 'Enter para carregar',
|
|
noProviders: 'Nenhum provedor encontrado. Adicione um provedor personalizado para comecar.',
|
|
builtIn: 'Integrado',
|
|
customType: 'Personalizado',
|
|
provider: 'Provedor',
|
|
contextLength: 'Tamanho do contexto',
|
|
contextLengthPlaceholder: 'ex: 200000 (opcional)',
|
|
local: 'Local ({host})',
|
|
selectProviderRequired: 'Por favor, selecione um provedor',
|
|
baseUrlRequired: 'A URL base e obrigatoria',
|
|
apiKeyRequired: 'A chave API e obrigatoria',
|
|
modelRequired: 'O modelo padrao e obrigatorio',
|
|
enterBaseUrl: 'Por favor, insira a URL base primeiro',
|
|
unexpectedFormat: 'Formato de resposta inesperado',
|
|
foundModels: '{count} modelos encontrados',
|
|
fetchFailed: 'Falha ao buscar os modelos',
|
|
},
|
|
|
|
// Profiles
|
|
profiles: {
|
|
title: 'Perfis',
|
|
create: 'Criar perfil',
|
|
import: 'Importar',
|
|
export: 'Exportar',
|
|
rename: 'Renomear',
|
|
delete: 'Excluir',
|
|
switchTo: 'Mudar para',
|
|
switchConfirm: 'Mudar para o perfil "{name}" reiniciara o gateway. Continuar?',
|
|
switchSuccess: 'Mudou para o perfil "{name}"',
|
|
switchFailed: 'Falha ao mudar de perfil. O gateway pode precisar de reinicio manual.',
|
|
createSuccess: 'Perfil "{name}" criado',
|
|
createFailed: 'Falha ao criar o perfil',
|
|
renameSuccess: 'Perfil renomeado',
|
|
renameFailed: 'Falha ao renomear o perfil',
|
|
deleteConfirm: 'Tem certeza de que deseja excluir o perfil "{name}"?',
|
|
deleteSuccess: 'Perfil excluido',
|
|
deleteFailed: 'Falha ao excluir o perfil',
|
|
exportSuccess: 'Perfil exportado',
|
|
exportFailed: 'Falha ao exportar o perfil',
|
|
importSuccess: 'Perfil importado',
|
|
importFailed: 'Falha ao importar o perfil',
|
|
importSelectFile: 'Selecionar arquivo de arquivo',
|
|
importInvalidFile: 'Por favor, selecione um arquivo valido (.tar.gz, .tgz, .gz, .zip)',
|
|
name: 'Nome do perfil',
|
|
namePlaceholder: 'Apenas letras, numeros e hifens',
|
|
newName: 'Novo nome',
|
|
newNamePlaceholder: 'Digite um novo nome',
|
|
cloneFromCurrent: 'Clonar do perfil atual',
|
|
archivePath: 'Caminho do arquivo',
|
|
archivePathPlaceholder: 'Caminho do servidor para o arquivo',
|
|
importName: 'Nome do perfil (opcional)',
|
|
importNamePlaceholder: 'Deixe vazio para usar o nome do arquivo',
|
|
active: 'Ativo',
|
|
model: 'Modelo',
|
|
gateway: 'Gateway',
|
|
alias: 'Alias',
|
|
provider: 'Provedor',
|
|
path: 'Caminho',
|
|
skills: 'Habilidades',
|
|
hasEnv: 'Tem .env',
|
|
hasSoulMd: 'Tem soul.md',
|
|
noProfiles: 'Nenhum perfil encontrado. Crie um para comecar.',
|
|
},
|
|
|
|
// Logs
|
|
logs: {
|
|
title: 'Logs',
|
|
all: 'Todos',
|
|
searchPlaceholder: 'Buscar...',
|
|
refresh: 'Atualizar',
|
|
noEntries: 'Nenhuma entrada de log',
|
|
},
|
|
|
|
// Settings
|
|
settings: {
|
|
title: 'Configuracoes',
|
|
saved: 'Salvo',
|
|
saveFailed: 'Falha ao salvar',
|
|
tabs: {
|
|
display: 'Exibicao',
|
|
account: 'Conta',
|
|
agent: 'Agente',
|
|
memory: 'Memoria',
|
|
session: 'Sessao',
|
|
privacy: 'Privacidade',
|
|
apiServer: 'Servidor API',
|
|
},
|
|
display: {
|
|
streaming: 'Respostas em streaming',
|
|
streamingHint: 'Mostrar respostas da IA em tempo real',
|
|
compact: 'Modo compacto',
|
|
compactHint: 'Reduzir espacamento entre mensagens',
|
|
showReasoning: 'Mostrar raciocinio',
|
|
showReasoningHint: 'Mostrar processo de pensamento do modelo',
|
|
showCost: 'Mostrar custo',
|
|
showCostHint: 'Mostrar uso de tokens nas respostas',
|
|
inlineDiffs: 'Diffs em linha',
|
|
inlineDiffsHint: 'Mostrar alteracoes de codigo em linha',
|
|
bellOnComplete: 'Som de conclusao',
|
|
bellOnCompleteHint: 'Tocar som quando a IA terminar',
|
|
busyInputMode: 'Modo de entrada ocupada',
|
|
busyInputModeHint: 'Permitir entrada enquanto a IA processa',
|
|
theme: 'Tema',
|
|
themeHint: 'Escolha claro, escuro ou seguir a preferencia do sistema',
|
|
themeLight: 'Claro',
|
|
themeDark: 'Escuro',
|
|
themeSystem: 'Sistema',
|
|
},
|
|
agent: {
|
|
maxTurns: 'Maximo de turnos',
|
|
maxTurnsHint: 'Maximo de rodadas de interacao por conversa',
|
|
gatewayTimeout: 'Timeout do gateway',
|
|
gatewayTimeoutHint: 'Timeout da requisicao em segundos',
|
|
restartDrainTimeout: 'Timeout de drenagem ao reiniciar',
|
|
restartDrainTimeoutHint: 'Timeout de drenagem antes de reiniciar em segundos',
|
|
toolEnforcement: 'Obrigatoriedade de ferramentas',
|
|
toolEnforcementHint: 'Controlar o modo de execucao de chamadas de ferramentas',
|
|
auto: 'Automatico',
|
|
always: 'Sempre',
|
|
never: 'Nunca',
|
|
},
|
|
memory: {
|
|
enabled: 'Ativar memoria',
|
|
enabledHint: 'Permitir que a IA lembre do contexto da conversa',
|
|
userProfile: 'Perfil do usuario',
|
|
userProfileHint: 'Permitir que a IA lembre das preferencias do usuario',
|
|
charLimit: 'Limite de caracteres da memoria',
|
|
charLimitHint: 'Maximo de caracteres para MEMORY.md',
|
|
userCharLimit: 'Limite de caracteres do perfil do usuario',
|
|
userCharLimitHint: 'Maximo de caracteres para USER.md',
|
|
},
|
|
session: {
|
|
mode: 'Modo de reinicializacao',
|
|
modeHint: 'Condicao de acionamento para reinicializacao de sessao',
|
|
modeBoth: 'Inatividade + Agendado',
|
|
modeIdle: 'Somente inatividade',
|
|
modeHourly: 'Somente agendado',
|
|
idleMinutes: 'Timeout de inatividade',
|
|
idleMinutesHint: 'Tempo de espera antes da reinicializacao automatica (minutos)',
|
|
atHour: 'Horario de reinicializacao agendada',
|
|
humanOnly: 'Mostrar apenas sessões humanas',
|
|
humanOnlyHint: 'Oculta por padrão o ruído de subagentes e do monitor de sessões',
|
|
liveMonitorHumanOnly: 'Monitor ao vivo: mostrar apenas sessões humanas',
|
|
liveMonitorHumanOnlyHint: 'Oculta por padrão o ruído de subagentes e do monitor de sessões no monitor ao vivo',
|
|
atHourHint: 'Reiniciar sessao neste horario diariamente',
|
|
},
|
|
privacy: {
|
|
redactPii: 'Ocultar dados pessoais',
|
|
redactPiiHint: 'Detectar e ocultar automaticamente informacoes sensiveis (senhas, chaves, etc.)',
|
|
},
|
|
apiServer: {
|
|
enable: 'Ativar',
|
|
enableHint: 'Ativar servidor API',
|
|
host: 'Host',
|
|
hostHint: 'Endereco de escuta',
|
|
port: 'Porta',
|
|
portHint: 'Porta de escuta',
|
|
key: 'Chave',
|
|
keyHint: 'Chave de acesso API',
|
|
cors: 'Origens CORS',
|
|
corsHint: 'Fontes cross-origin permitidas',
|
|
},
|
|
},
|
|
|
|
// Platform channel settings
|
|
platform: {
|
|
requireMention: "Exigir mencao {'@'}",
|
|
requireMentionGroup: "Exigir mencao {'@'} em grupos para responder",
|
|
requireMentionChannel: "Exigir mencao {'@'} em canais para responder",
|
|
requireMentionRoom: "Exigir mencao {'@'} em salas para responder",
|
|
reactions: 'Reacoes',
|
|
reactionsHint: 'Reagir a mensagens com emoji',
|
|
freeResponseChats: 'Chats de resposta livre',
|
|
freeResponseChatsHint: "IDs de chats que respondem sem mencao {'@'} (separados por virgula)",
|
|
freeResponseChannels: 'Canais de resposta livre',
|
|
freeResponseChannelsHint: "IDs de canais que respondem sem mencao {'@'} (separados por virgula)",
|
|
freeResponseRooms: 'Salas de resposta livre',
|
|
freeResponseRoomsHint: "IDs de salas que respondem sem mencao {'@'} (separados por virgula)",
|
|
mentionPatterns: 'Padroes de mencao personalizados',
|
|
mentionPatternsHint: 'Padroes de acionamento adicionais',
|
|
autoThread: 'Thread automatica',
|
|
autoThreadHint: "Criar automaticamente threads de resposta apos mencao {'@'}",
|
|
autoThreadHintRoom: 'Criar automaticamente threads de resposta em salas',
|
|
dmMentionThreads: 'Threads de mencao em DM',
|
|
dmMentionThreadsHint: 'Usar respostas em thread para mencoes em DMs',
|
|
allowBots: 'Permitir mensagens de bots',
|
|
allowBotsHint: 'Responder a mensagens de outros bots',
|
|
allowedChannels: 'Canais permitidos',
|
|
allowedChannelsHint: 'Lista branca de IDs de canais (separados por virgula)',
|
|
ignoredChannels: 'Canais ignorados',
|
|
ignoredChannelsHint: 'Canais onde o bot nunca responde (separados por virgula)',
|
|
noThreadChannels: 'Canais sem thread',
|
|
noThreadChannelsHint: 'Canais onde o bot responde sem threads (separados por virgula)',
|
|
botToken: 'Token do bot',
|
|
botTokenHint: 'Token do bot do portal do desenvolvedor',
|
|
accessToken: 'Token de acesso',
|
|
accessTokenHint: 'Token de acesso Matrix',
|
|
homeserver: 'URL do homeserver',
|
|
homeserverHint: 'URL do homeserver Matrix',
|
|
appId: 'ID do aplicativo',
|
|
appIdHint: 'ID do aplicativo Feishu',
|
|
appSecret: 'Segredo do aplicativo',
|
|
appSecretHint: 'Segredo do aplicativo Feishu',
|
|
clientId: 'ID do cliente',
|
|
clientIdHint: 'ID do cliente DingTalk',
|
|
clientSecret: 'Segredo do cliente',
|
|
clientSecretHint: 'Segredo do cliente DingTalk',
|
|
botId: 'ID do bot',
|
|
botIdHint: 'ID do bot WeCom',
|
|
wecomSecretHint: 'Segredo do bot WeCom',
|
|
waEnabled: 'Ativar WhatsApp',
|
|
waEnabledHint: 'Ativar WhatsApp via pareamento por codigo QR',
|
|
weixinToken: 'Token Weixin',
|
|
weixinTokenHint: 'Do login QR da CLI weixin (hermes weixin)',
|
|
accountId: 'ID da conta',
|
|
accountIdHint: 'ID da conta Weixin',
|
|
qrLogin: 'Login por QR',
|
|
qrRelogin: 'Reconectar',
|
|
qrFetching: 'Buscando codigo QR...',
|
|
qrScanHint: 'Escaneie com WeChat para fazer login',
|
|
qrScanedHint: 'Escaneado, por favor confirme no celular...',
|
|
},
|
|
|
|
// Language
|
|
language: {
|
|
label: 'Idioma',
|
|
zh: '中文',
|
|
en: 'English',
|
|
pt: 'Portugues',
|
|
},
|
|
|
|
// Terminal
|
|
terminal: {
|
|
sessions: 'Sessoes',
|
|
newTab: 'Novo terminal',
|
|
closeSession: 'Fechar esta sessao?',
|
|
sessionExited: 'Encerrada',
|
|
processExited: 'Processo encerrado com codigo {code}',
|
|
},
|
|
|
|
// Usage
|
|
usage: {
|
|
title: 'Estatisticas de uso',
|
|
refresh: 'Atualizar',
|
|
totalTokens: 'Total de tokens',
|
|
inputTokens: 'Entrada',
|
|
outputTokens: 'Saida',
|
|
totalSessions: 'Total de sessoes',
|
|
avgPerDay: '~{n}/dia em media',
|
|
estimatedCost: 'Custo est.',
|
|
cacheHitRate: 'Taxa de acerto de cache',
|
|
modelBreakdown: 'Detalhamento por modelo',
|
|
dailyTrend: 'Uso diario (ultimos 30 dias)',
|
|
date: 'Data',
|
|
tokens: 'Tokens',
|
|
cache: 'Cache',
|
|
sessions: 'Sessoes',
|
|
cost: 'Custo',
|
|
noData: 'Sem dados de uso',
|
|
},
|
|
|
|
// Registro de alteracoes
|
|
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: 'Exibicao em streaming em tempo real de blocos de pensamento/razoamento',
|
|
new_0_4_7_2: 'Ignorar script de preparacao durante o build Docker',
|
|
new_0_4_7_3: 'Melhorias na UX mobile do chat em grupo e polimento da UI',
|
|
new_0_4_7_4: 'Limitar os tokens restantes do contexto a 0 em vez de negativo',
|
|
new_0_4_7_5: 'Adicionar provedor integrado Alibaba Coding Plan com substituicao de base_url no .env',
|
|
new_0_4_7_6: 'Ignorar perfis remotos na inicializacao para evitar travamento',
|
|
new_0_4_7_7: 'Detectar e exibir erros de execucao silenciosamente engolidos',
|
|
new_0_4_7_8: 'Consulta de comprimento de contexto consciente do provedor',
|
|
new_0_4_7_9: 'Redefinir config.model ao trocar e resolver provedor personalizado CLI',
|
|
new_0_4_7_10: 'Limpar base_url_env do .env ao excluir provedor integrado',
|
|
new_0_4_7_11: 'Alinhar o fundo da barra lateral da sala de chat em grupo com a lista de sessoes',
|
|
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: 'Adicionar rastreamento de uso de tokens e comprimento de contexto dinamico',
|
|
new_0_4_2_2: 'Adicionar modal de busca de sessoes',
|
|
new_0_4_2_3: 'Restaurar sistema de chat em grupo com Socket.IO e SQLite',
|
|
new_0_4_2_4: 'Adicionar sessoes fixas e monitor ao vivo na pagina de chat',
|
|
new_0_4_2_5: 'Corrigir deteccao de provedores integrados e combinacao de modelos',
|
|
},
|
|
|
|
// Arquivos
|
|
files: {
|
|
title: 'Arquivos',
|
|
tree: 'Arvore de diretorios',
|
|
list: 'Lista de arquivos',
|
|
breadcrumbRoot: 'Inicio',
|
|
newFile: 'Novo arquivo',
|
|
newFolder: 'Nova pasta',
|
|
upload: 'Enviar',
|
|
refresh: 'Atualizar',
|
|
open: 'Abrir',
|
|
edit: 'Editar',
|
|
preview: 'Visualizar',
|
|
download: 'Baixar',
|
|
copyPath: 'Copiar caminho',
|
|
rename: 'Renomear',
|
|
delete: 'Excluir',
|
|
name: 'Nome',
|
|
size: 'Tamanho',
|
|
modified: 'Modificado',
|
|
actions: 'Acoes',
|
|
emptyDir: 'Diretorio vazio',
|
|
loading: 'Carregando...',
|
|
confirmDelete: 'Tem certeza de que deseja excluir "{name}"?',
|
|
confirmDeleteDir: 'Tem certeza de que deseja excluir o diretorio "{name}" e todo o seu conteudo?',
|
|
deleteFailed: 'Falha ao excluir',
|
|
deleted: 'Excluido',
|
|
renameTo: 'Renomear para',
|
|
newFileName: 'Nome do arquivo',
|
|
newFolderName: 'Nome da pasta',
|
|
created: 'Criado',
|
|
createFailed: 'Falha ao criar',
|
|
renamed: 'Renomeado',
|
|
renameFailed: 'Falha ao renomear',
|
|
uploadSuccess: '{count} arquivo(s) enviado(s)',
|
|
uploadFailed: 'Falha ao enviar',
|
|
saveFailed: 'Falha ao salvar',
|
|
saved: 'Salvo',
|
|
unsavedChanges: 'Voce tem alteracoes nao salvas. Descartar?',
|
|
pathCopied: 'Caminho copiado',
|
|
fileTooLarge: 'Arquivo muito grande (max 10MB)',
|
|
permissionDenied: 'Nao e possivel modificar arquivo protegido',
|
|
notFound: 'Arquivo ou diretorio nao encontrado',
|
|
backendError: 'Falha na operacao de arquivo',
|
|
dragDropHint: 'Arraste arquivos aqui para enviar',
|
|
closeEditor: 'Fechar editor',
|
|
closePreview: 'Fechar',
|
|
saveFile: 'Salvar',
|
|
},
|
|
|
|
// Chat em grupo
|
|
groupChat: {
|
|
title: 'Chat em grupo',
|
|
createRoom: 'Criar sala',
|
|
joinByCode: 'Entrar com codigo',
|
|
roomName: 'Nome da sala',
|
|
roomNamePlaceholder: 'Digite o nome da sala',
|
|
inviteCode: 'Codigo de convite',
|
|
autoGenerate: 'Gerar automaticamente',
|
|
noRooms: 'Nenhuma sala ainda',
|
|
selectOrCreate: 'Selecione ou crie uma sala para comecar a conversar',
|
|
agents: 'Agentes',
|
|
addAgent: 'Adicionar agente',
|
|
selectProfile: 'Selecione um perfil',
|
|
agentAdded: 'Agente adicionado',
|
|
agentAlreadyInRoom: 'O agente ja esta nesta sala',
|
|
noAgents: 'Nenhum agente nesta sala',
|
|
members: 'Membros',
|
|
roomCreated: 'Sala criada',
|
|
roomDeleted: 'Sala excluída',
|
|
deleteRoomConfirm: 'Excluir esta sala?',
|
|
you: 'Você',
|
|
joined: 'Entrou na sala',
|
|
joinFailed: 'Falha ao entrar na sala',
|
|
inputPlaceholder: 'Digite uma mensagem... (Enter para enviar)',
|
|
enterCode: 'Digite o codigo de convite',
|
|
yourName: 'Seu nome',
|
|
yourNamePlaceholder: 'Digite seu nome de exibicao',
|
|
yourDescription: 'Descricao (opcional)',
|
|
yourDescriptionPlaceholder: 'Conte aos outros quem voce e...',
|
|
agentName: 'Nome do agente',
|
|
agentNamePlaceholder: 'Nome personalizado (vazio = nome do perfil)',
|
|
agentDesc: 'Descrição do agente',
|
|
agentDescPlaceholder: 'Descreva o que este agente faz...',
|
|
agentReplying: 'está respondendo...',
|
|
agentCompressing: 'está compactando contexto...',
|
|
compressionSettings: 'Configuração de compactação',
|
|
triggerTokens: 'Tokens de acionamento',
|
|
triggerTokensDesc: 'Limite de tokens para acionar a compactação',
|
|
maxHistoryTokens: 'Máx. tokens de histórico',
|
|
maxHistoryTokensDesc: 'Máximo de tokens para o contexto compactado',
|
|
tailMessageCount: 'Mensagens recentes',
|
|
tailMessageCountDesc: 'Número de mensagens recentes a manter',
|
|
compressionConfig: 'Config. de compactação',
|
|
compressNow: 'Compactar agora',
|
|
compressingInProgress: 'Compactação em andamento',
|
|
compressionSaved: 'Configuração salva',
|
|
},
|
|
|
|
// Download
|
|
download: {
|
|
downloading: 'Baixando...',
|
|
downloadFailed: 'Falha no download',
|
|
fileNotFound: 'Arquivo nao encontrado ou excluido',
|
|
fileTooLarge: 'Arquivo muito grande (limite excedido)',
|
|
backendError: 'Falha ao ler o arquivo, o ambiente remoto pode estar indisponivel',
|
|
backendTimeout: 'Tempo esgotado para ler o arquivo',
|
|
unsupportedBackend: 'O backend de terminal atual nao suporta download de arquivos',
|
|
invalidPath: 'Caminho de arquivo invalido',
|
|
download: 'Baixar',
|
|
downloadFile: 'Baixar arquivo',
|
|
},
|
|
}
|