tensorgrid-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +95 -0
- package/README.md +486 -0
- package/lib/client.js +2108 -0
- package/lib/index.js +151 -0
- package/lib/update-check.js +216 -0
- package/package.json +36 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,2108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-половина пакета Obsidian / Ion.
|
|
3
|
+
*
|
|
4
|
+
* Формат файла — не результат сборки, а тот же контракт, который используют
|
|
5
|
+
* поставочные пакеты `@deepseek-ai/dsh-client-ui-*`: регистрация модуля в
|
|
6
|
+
* `window.__ModuleLoader__`, зависимости через колбэк `require`, экспорт
|
|
7
|
+
* `apply` и `inject`. Поэтому пакет пишется и правится руками, без tsdown.
|
|
8
|
+
*
|
|
9
|
+
* Пакет занимает только аддитивные слоты (`replaceRisk: none`) и публичные
|
|
10
|
+
* методы сервисов. Ни один поставочный компонент не замещается, поэтому
|
|
11
|
+
* обновления dsh продолжают доезжать до интерфейса.
|
|
12
|
+
*/
|
|
13
|
+
window.__ModuleLoader__.load({
|
|
14
|
+
id: 'tensorgrid-ui',
|
|
15
|
+
factory: (require) => {
|
|
16
|
+
var module = { exports: {} }
|
|
17
|
+
var exports = module.exports
|
|
18
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
19
|
+
|
|
20
|
+
const React = require('react')
|
|
21
|
+
const { jsx, jsxs } = require('react/jsx-runtime')
|
|
22
|
+
|
|
23
|
+
// ── Палитра ──────────────────────────────────────────────────────────
|
|
24
|
+
//
|
|
25
|
+
// Доставляется слоем переопределения, а не зарегистрированной темой.
|
|
26
|
+
// Причина не в удобстве: `ThemeDefinition` пиннит одну `colorScheme`,
|
|
27
|
+
// то есть выбор такой темы ломает режим «следовать системе». Слой же
|
|
28
|
+
// несёт пару значений на каждый токен и переживает переключение схемы
|
|
29
|
+
// сам. Плюс поставочная строка «Оформление» умеет ровно три значения
|
|
30
|
+
// (light / dark / system) и отрисовать стороннюю тему не может.
|
|
31
|
+
const SOURCE = 'dsx-obsidian-ion'
|
|
32
|
+
|
|
33
|
+
/** Токены, не зависящие от выбранного акцента. */
|
|
34
|
+
const BASE_TOKENS = {
|
|
35
|
+
'--dsw-alias-bg-base': { dark: '#06080f', light: '#f6f8fc' },
|
|
36
|
+
'--dsw-alias-bg-layer-1': { dark: '#0c0f19', light: '#ffffff' },
|
|
37
|
+
'--dsw-alias-bg-layer-2': { dark: '#141927', light: '#eef2f9' },
|
|
38
|
+
'--dsw-alias-bg-overlay': { dark: '#171d2e', light: '#ffffff' },
|
|
39
|
+
'--dsw-alias-border-l1': { dark: '#222a3d', light: '#e3e9f3' },
|
|
40
|
+
'--dsw-alias-border-l2': { dark: '#334059', light: '#cad4e4' },
|
|
41
|
+
'--dsw-alias-label-primary': { dark: '#e9eff9', light: '#0a1120' },
|
|
42
|
+
'--dsw-alias-label-secondary': { dark: '#8b97ad', light: '#5a6780' },
|
|
43
|
+
'--dsw-alias-state-error-primary': { dark: '#ff5c72', light: '#d92d43' },
|
|
44
|
+
'--dsw-alias-state-success-primary': { dark: '#3ddc9a', light: '#0f9d63' },
|
|
45
|
+
'--dsw-alias-state-warn-primary': { dark: '#ffb454', light: '#b8720a' },
|
|
46
|
+
'--dsw-specific-sidebar-fill': { dark: '#090c14', light: '#f0f4fa' },
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Акцент ───────────────────────────────────────────────────────────
|
|
50
|
+
//
|
|
51
|
+
// Каждый пресет несёт пару: ведущий тон и контрапункт. Свечение строится
|
|
52
|
+
// на обоих, поэтому цветовое поле остаётся глубоким при любом выборе.
|
|
53
|
+
// Светлый вариант тона отдельный: на белом фоне тот же цвет не читается.
|
|
54
|
+
const ACCENTS = [
|
|
55
|
+
{ id: 'ion', label: 'Ion', dark: '#5ad9f5', light: '#0b8fad', rgb: '90,217,245', rgb2: '138,108,255' },
|
|
56
|
+
{ id: 'violet', label: 'Violet', dark: '#a78bfa', light: '#6d38d6', rgb: '167,139,250', rgb2: '90,217,245' },
|
|
57
|
+
{ id: 'emerald', label: 'Emerald', dark: '#4ade9a', light: '#0c8f5e', rgb: '74,222,154', rgb2: '56,189,248' },
|
|
58
|
+
{ id: 'amber', label: 'Amber', dark: '#ffc46b', light: '#b3720a', rgb: '255,196,107', rgb2: '255,110,180' },
|
|
59
|
+
]
|
|
60
|
+
const DEFAULT_ACCENT = 'ion'
|
|
61
|
+
|
|
62
|
+
function accentOf(id) {
|
|
63
|
+
for (let i = 0; i < ACCENTS.length; i += 1) if (ACCENTS[i].id === id) return ACCENTS[i]
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Полный слой токенов для выбранного акцента. */
|
|
68
|
+
function tokensFor(accent) {
|
|
69
|
+
const tokens = { '--dsw-alias-brand-primary': { dark: accent.dark, light: accent.light } }
|
|
70
|
+
for (const name of Object.keys(BASE_TOKENS)) tokens[name] = BASE_TOKENS[name]
|
|
71
|
+
return tokens
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Пользовательские настройки ───────────────────────────────────────
|
|
75
|
+
// Ключи хранения намеренно сохраняют прежнее имя пакета. Это не
|
|
76
|
+
// идентичность, а данные: переименование молча сбросило бы у каждого
|
|
77
|
+
// пользователя выбранную интенсивность и акцент.
|
|
78
|
+
const STORAGE_KEY = 'dsh-ui-obsidian-ion:intensity'
|
|
79
|
+
const ACCENT_STORAGE_KEY = 'dsh-ui-obsidian-ion:accent'
|
|
80
|
+
const INTENSITY_VAR = '--dsx-intensity'
|
|
81
|
+
const ACTIVITY_VAR = '--dsx-activity'
|
|
82
|
+
const ACCENT_VAR = '--dsx-accent-rgb'
|
|
83
|
+
const ACCENT2_VAR = '--dsx-accent2-rgb'
|
|
84
|
+
|
|
85
|
+
const LEVELS = [
|
|
86
|
+
{ id: 'off', key: 'intensity.off', value: 0 },
|
|
87
|
+
{ id: 'quiet', key: 'intensity.quiet', value: 0.5 },
|
|
88
|
+
{ id: 'normal', key: 'intensity.normal', value: 1 },
|
|
89
|
+
{ id: 'vivid', key: 'intensity.vivid', value: 1.7 },
|
|
90
|
+
]
|
|
91
|
+
const DEFAULT_LEVEL = 'normal'
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Сервис темы, пойманный в apply(). Слой переопределения переписывается
|
|
95
|
+
* при смене акцента — документация службы прямо разрешает это: повторный
|
|
96
|
+
* вызов с тем же источником заменяет весь слой целиком.
|
|
97
|
+
*/
|
|
98
|
+
let themeService = null
|
|
99
|
+
|
|
100
|
+
function levelOf(id) {
|
|
101
|
+
for (let i = 0; i < LEVELS.length; i += 1) if (LEVELS[i].id === id) return LEVELS[i]
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readLevelId() {
|
|
106
|
+
try {
|
|
107
|
+
const raw = window.localStorage.getItem(STORAGE_KEY)
|
|
108
|
+
if (levelOf(raw) !== null) return raw
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// Приватный режим и отключённое хранилище — не повод падать.
|
|
111
|
+
}
|
|
112
|
+
return DEFAULT_LEVEL
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function writeLevelId(id) {
|
|
116
|
+
try {
|
|
117
|
+
window.localStorage.setItem(STORAGE_KEY, id)
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// см. выше
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function applyLevelId(id) {
|
|
124
|
+
const level = levelOf(id) ?? levelOf(DEFAULT_LEVEL)
|
|
125
|
+
if (typeof document === 'undefined') return
|
|
126
|
+
document.documentElement.style.setProperty(INTENSITY_VAR, String(level.value))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readAccentId() {
|
|
130
|
+
try {
|
|
131
|
+
const raw = window.localStorage.getItem(ACCENT_STORAGE_KEY)
|
|
132
|
+
if (accentOf(raw) !== null) return raw
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// Приватный режим и отключённое хранилище — не повод падать.
|
|
135
|
+
}
|
|
136
|
+
return DEFAULT_ACCENT
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function writeAccentId(id) {
|
|
140
|
+
try {
|
|
141
|
+
window.localStorage.setItem(ACCENT_STORAGE_KEY, id)
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// см. выше
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Применяет акцент к обеим сторонам: свечение ведут CSS-переменные, а
|
|
149
|
+
* бренд-токен интерфейса переписывается через слой переопределения.
|
|
150
|
+
*/
|
|
151
|
+
function applyAccentId(id) {
|
|
152
|
+
const accent = accentOf(id) ?? accentOf(DEFAULT_ACCENT)
|
|
153
|
+
if (typeof document !== 'undefined') {
|
|
154
|
+
const style = document.documentElement.style
|
|
155
|
+
style.setProperty(ACCENT_VAR, accent.rgb)
|
|
156
|
+
style.setProperty(ACCENT2_VAR, accent.rgb2)
|
|
157
|
+
}
|
|
158
|
+
if (themeService !== null) themeService.overrideTokens(SOURCE, tokensFor(accent))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Выбранное название. Имя собственное — не переводится. */
|
|
162
|
+
const BRAND_NAME = 'TENSOR GRID'
|
|
163
|
+
|
|
164
|
+
// ── Локализация ──────────────────────────────────────────────────────
|
|
165
|
+
//
|
|
166
|
+
// Ни одна видимая строка не зашита в разметку: всё идёт через `t()`.
|
|
167
|
+
// Иначе русский текст вылезал бы в английском и китайском интерфейсе.
|
|
168
|
+
const LOCALE_NS = 'obsidian-ion'
|
|
169
|
+
const RU = 'ru'
|
|
170
|
+
|
|
171
|
+
const EN_DICT = {
|
|
172
|
+
'intensity.title': 'Obsidian / Ion atmosphere',
|
|
173
|
+
'intensity.hint': 'Strength of the living glow over the interface',
|
|
174
|
+
'intensity.off': 'Off',
|
|
175
|
+
'intensity.quiet': 'Quiet',
|
|
176
|
+
'intensity.normal': 'Normal',
|
|
177
|
+
'intensity.vivid': 'Vivid',
|
|
178
|
+
'accent.title': 'Obsidian / Ion accent',
|
|
179
|
+
'accent.hint': 'Leading tone of the interface and the glow',
|
|
180
|
+
'update.title': 'Updates',
|
|
181
|
+
'update.check': 'Check',
|
|
182
|
+
'update.apply': 'Update',
|
|
183
|
+
'update.working': 'Working…',
|
|
184
|
+
'update.checking': 'Checking…',
|
|
185
|
+
'update.upToDate': 'Latest version installed',
|
|
186
|
+
'update.available': 'A new version is available ({count} commits)',
|
|
187
|
+
'update.installed': 'Installed {commit}, verified on dsh {dsh}',
|
|
188
|
+
'update.doneReload': 'Updated. Reload the page.',
|
|
189
|
+
'update.doneRestart': 'Updated. Restart the application.',
|
|
190
|
+
'update.failed': 'Update failed',
|
|
191
|
+
'update.dshMismatch': 'Warning: dsh {actual} is installed, but this version was verified on {expected}',
|
|
192
|
+
'update.ahead': 'Installed version is {count} commits ahead of the released one',
|
|
193
|
+
'update.unknownHistory': 'Installed version is missing from history — reinstall',
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const ZH_DICT = {
|
|
197
|
+
'intensity.title': 'Obsidian / Ion 氛围',
|
|
198
|
+
'intensity.hint': '界面上方光晕的强度',
|
|
199
|
+
'intensity.off': '关闭',
|
|
200
|
+
'intensity.quiet': '轻微',
|
|
201
|
+
'intensity.normal': '标准',
|
|
202
|
+
'intensity.vivid': '强烈',
|
|
203
|
+
'accent.title': 'Obsidian / Ion 主色',
|
|
204
|
+
'accent.hint': '界面与光晕的主色调',
|
|
205
|
+
'update.title': '更新',
|
|
206
|
+
'update.check': '检查',
|
|
207
|
+
'update.apply': '更新',
|
|
208
|
+
'update.working': '处理中…',
|
|
209
|
+
'update.checking': '检查中…',
|
|
210
|
+
'update.upToDate': '已是最新版本',
|
|
211
|
+
'update.available': '有新版本({count} 次提交)',
|
|
212
|
+
'update.installed': '已安装 {commit},在 dsh {dsh} 上验证',
|
|
213
|
+
'update.doneReload': '已更新。请刷新页面。',
|
|
214
|
+
'update.doneRestart': '已更新。请重启应用。',
|
|
215
|
+
'update.failed': '更新失败',
|
|
216
|
+
'update.dshMismatch': '注意:已安装 dsh {actual},但此版本在 {expected} 上验证',
|
|
217
|
+
'update.ahead': '已安装版本比已发布版本新 {count} 次提交',
|
|
218
|
+
'update.unknownHistory': '已安装版本不在历史记录中 — 请重新安装',
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── RU-PACK:BEGIN — генерируется tools/build-ru.mjs, руками не править ──
|
|
222
|
+
//
|
|
223
|
+
// Русские словари ПОСТАВОЧНЫХ пространств имён. В браузере файлы не
|
|
224
|
+
// прочитать, поэтому они вшиваются в бандл. Генератор отказывается
|
|
225
|
+
// писать сюда, если набор ключей или плейсхолдеры расходятся с
|
|
226
|
+
// эталоном locale/source-en.json.
|
|
227
|
+
// Пространств: 39, ключей: 1208.
|
|
228
|
+
const RU_PACK = {
|
|
229
|
+
"approval": {
|
|
230
|
+
"waiting": "Ожидание подтверждения",
|
|
231
|
+
"detail.aria": "Подробности подтверждения",
|
|
232
|
+
"escalation": "Инструмент {toolName} запрашивает расширенные права",
|
|
233
|
+
"reject": "Отклонить",
|
|
234
|
+
"allowOnce": "Разрешить один раз"
|
|
235
|
+
},
|
|
236
|
+
"chat": {
|
|
237
|
+
"view.chat": "Чат",
|
|
238
|
+
"number.groupSeparator": " ",
|
|
239
|
+
"duration.compactSeconds": "{seconds}с",
|
|
240
|
+
"duration.compactMinutes": "{minutes}м{seconds}с",
|
|
241
|
+
"duration.milliseconds": "{milliseconds}мс",
|
|
242
|
+
"stats.counts": "{turns} ходов {steps} шагов",
|
|
243
|
+
"stats.cacheHit": "Попадание в кэш {percent}%",
|
|
244
|
+
"stats.dialog.title": "Статистика сессии",
|
|
245
|
+
"stats.dialog.usageTitle": "Расход токенов",
|
|
246
|
+
"stats.dialog.llmTime": "Время LLM",
|
|
247
|
+
"stats.dialog.toolTime": "Время инструментов",
|
|
248
|
+
"stats.dialog.ttft": "Среднее время до первого токена (TTFT)",
|
|
249
|
+
"stats.dialog.speed": "Токенов в секунду (TPS)",
|
|
250
|
+
"chat.loadingHistory": "Загрузка истории…",
|
|
251
|
+
"chat.loadError": "Не удалось загрузить историю: {message} ({code})",
|
|
252
|
+
"chat.loadOlder": "Загрузить предыдущие",
|
|
253
|
+
"chat.toBottom": "Вниз",
|
|
254
|
+
"chat.deepDiving": "Погружение...",
|
|
255
|
+
"chat.turnNavigation.label": "Навигация по ходам",
|
|
256
|
+
"chat.turnNavigation.jump": "Перейти к ходу {turn}",
|
|
257
|
+
"chat.turnNavigation.jumpLoad": "Загрузить и перейти к ходу {turn}",
|
|
258
|
+
"chat.turnNavigation.turn": "Ход {turn}",
|
|
259
|
+
"settings.transcript.title": "Отображение диалога",
|
|
260
|
+
"settings.transcript.description": "Управляет показом процесса в завершённых ходах",
|
|
261
|
+
"settings.transcript.normal": "Обычное",
|
|
262
|
+
"settings.transcript.compact": "Компактное",
|
|
263
|
+
"fileOpen.title": "Не удалось открыть файл",
|
|
264
|
+
"fileOpen.unknown": "Не удалось открыть этот файл",
|
|
265
|
+
"message.extraBlock": "Дополнительный блок содержимого",
|
|
266
|
+
"message.systemPrompt": "Системный запрос",
|
|
267
|
+
"message.systemPromptUpdate": "Обновление системного запроса",
|
|
268
|
+
"message.contextInjection": "Внедрение контекста",
|
|
269
|
+
"message.contextRecall": "Восстановление сессии",
|
|
270
|
+
"message.referenceSummary": "Упомянутая сессия · {labels}",
|
|
271
|
+
"message.referenceSeparator": ", ",
|
|
272
|
+
"message.context.instructions.loaded": "загружено",
|
|
273
|
+
"message.context.instructions.added": "добавлено",
|
|
274
|
+
"message.context.instructions.updated": "обновлено",
|
|
275
|
+
"message.context.instructions.removed": "удалено",
|
|
276
|
+
"message.context.catalog.replaced": "Замена каталога",
|
|
277
|
+
"message.context.catalog.more": "… ещё {count}",
|
|
278
|
+
"message.context.snapshot.supersedes": "Заменяет прежние снимки",
|
|
279
|
+
"message.context.relay.from": "Из сессии {session}",
|
|
280
|
+
"message.context.recall.counts": "{retained} сохранено · {omitted} пропущено",
|
|
281
|
+
"message.context.recall.truncated": "усечено",
|
|
282
|
+
"message.compaction": "Контекст сжат",
|
|
283
|
+
"message.compaction.running": "Сжатие контекста…",
|
|
284
|
+
"message.compaction.completed": "Сжато {items} элементов истории (~{tokens} токенов)",
|
|
285
|
+
"message.compaction.expand": "Показать итог сжатия",
|
|
286
|
+
"message.compaction.unavailable": "Итог сжатия недоступен",
|
|
287
|
+
"message.compaction.commandTitle": "compact",
|
|
288
|
+
"message.think": "Размышление",
|
|
289
|
+
"message.unknownSurface": "Неизвестное событие интерфейса: {type}",
|
|
290
|
+
"message.unknownBlock": "Неизвестный блок содержимого",
|
|
291
|
+
"message.turnProcess.toolCalls.one": "{count} вызов инструмента",
|
|
292
|
+
"message.turnProcess.toolCalls.other": "{count} вызовов инструментов",
|
|
293
|
+
"message.turnProcess.messages.one": "{count} сообщение",
|
|
294
|
+
"message.turnProcess.messages.other": "{count} сообщений",
|
|
295
|
+
"message.turnProcess.subagents.one": "{count} подагент",
|
|
296
|
+
"message.turnProcess.subagents.other": "{count} подагентов",
|
|
297
|
+
"message.turnProcess.thoughtForAWhile": "Думал некоторое время",
|
|
298
|
+
"message.turnProcess.separator": " · ",
|
|
299
|
+
"message.stopped": "Остановлено",
|
|
300
|
+
"message.branch": "Ответвить новый диалог",
|
|
301
|
+
"message.branchUnavailable": "Доступно только для последнего сообщения завершённого хода",
|
|
302
|
+
"message.retry.active": "Повтор запроса к модели",
|
|
303
|
+
"message.retry.cancelled": "Повтор запроса к модели отменён",
|
|
304
|
+
"message.retry.started": "Запрос к модели повторён",
|
|
305
|
+
"message.retry.scheduled": "Ожидание повтора запроса к модели",
|
|
306
|
+
"message.retry.status": "{label} ({retry}/{maximum}) · {seconds} с",
|
|
307
|
+
"message.retry.delay": "Задержка повтора: ",
|
|
308
|
+
"message.retry.failure": "Причина сбоя: ",
|
|
309
|
+
"message.failure.auth": "Неверный API-ключ",
|
|
310
|
+
"message.turnError": "Ход завершился ошибкой",
|
|
311
|
+
"message.maxTokens": "Достигнут лимит выходных токенов",
|
|
312
|
+
"message.maxTokens.hint": "Ответ оборван; предыдущий вывод сохранён в диалоге. Отправьте «continue», чтобы модель продолжила.",
|
|
313
|
+
"message.ranFor": "Выполнялось {duration}",
|
|
314
|
+
"message.tokensPerSecond": "{tps} tok/s",
|
|
315
|
+
"message.turnUsage.title": "Расход за ход",
|
|
316
|
+
"message.turnUsage.consumed": "Расход {total}",
|
|
317
|
+
"message.turnUsage.model": "Провайдер / модель",
|
|
318
|
+
"message.turnUsage.cacheHit": "Попадание в кэш",
|
|
319
|
+
"message.turnUsage.input": "Ввод без кэша",
|
|
320
|
+
"message.turnUsage.cacheRead": "Ввод из кэша",
|
|
321
|
+
"message.turnUsage.cacheWrite": "Запись в кэш",
|
|
322
|
+
"message.turnUsage.output": "Вывод",
|
|
323
|
+
"message.turnUsage.reasoning": " ({tokens} на рассуждения)",
|
|
324
|
+
"message.turnUsage.count": "{count} tok",
|
|
325
|
+
"message.turnTime.title": "Время хода и скорость",
|
|
326
|
+
"message.turnTime.duration": "Общее время выполнения",
|
|
327
|
+
"message.turnTime.speed": "Токенов в секунду (TPS)",
|
|
328
|
+
"message.turnTime.ttft": "Время до первого токена (TTFT)",
|
|
329
|
+
"duration.seconds": "{seconds} с",
|
|
330
|
+
"duration.minutes": "{minutes} мин {seconds} с",
|
|
331
|
+
"command.running": "Выполняется…",
|
|
332
|
+
"command.failed": "Ошибка команды",
|
|
333
|
+
"command.done": "Завершено",
|
|
334
|
+
"command.title": "Команда",
|
|
335
|
+
"row.running": "Выполняется",
|
|
336
|
+
"row.failed": "Ошибка",
|
|
337
|
+
"json.truncated": "… усечено, всего {total} символов",
|
|
338
|
+
"clock.md": "{d}.{m}",
|
|
339
|
+
"clock.ymd": "{d}.{m}.{y}"
|
|
340
|
+
},
|
|
341
|
+
"command": {
|
|
342
|
+
"description.compact": "Сжать раннюю историю диалога",
|
|
343
|
+
"description.export": "Скачать журнал этой сессии в ZIP-архиве",
|
|
344
|
+
"description.feedback": "записать отзыв об этой сессии",
|
|
345
|
+
"description.goal": "задать или посмотреть цель длительной задачи",
|
|
346
|
+
"description.permission": "Сменить пресет разрешений (режим песочницы и политика подтверждений)",
|
|
347
|
+
"description.plan": "Войти в режим плана или выйти из него",
|
|
348
|
+
"search.placeholder": "Поиск…",
|
|
349
|
+
"search.aria": "Фильтр вариантов",
|
|
350
|
+
"status.loading": "Загрузка вариантов…",
|
|
351
|
+
"status.applying": "Применение…",
|
|
352
|
+
"status.empty": "Нет вариантов",
|
|
353
|
+
"overlay.aria": "Варианты /{command}",
|
|
354
|
+
"listbox.aria": "Совпадения /{command}",
|
|
355
|
+
"notice.attachmentsUnsupported": "/{command} не принимает вложения — сначала удалить их"
|
|
356
|
+
},
|
|
357
|
+
"conversation": {
|
|
358
|
+
"hint.plan": "опишите задачу для создания плана",
|
|
359
|
+
"hint.goal": "опишите цель длительной задачи",
|
|
360
|
+
"hint.goal.active": "цель активна — изменить / пауза / продолжить / очистить",
|
|
361
|
+
"placeholder.plan": "опишите задачу для создания плана",
|
|
362
|
+
"placeholder.default": "Сообщение или задача, / команды, @ файлы или сессии",
|
|
363
|
+
"placeholder.unavailable": "Сессия недоступна",
|
|
364
|
+
"placeholder.parentOffline": "Родительская сессия офлайн; отправка недоступна, но выполнение можно остановить",
|
|
365
|
+
"placeholder.hero": "Опишите, что нужно сделать, / команды, @ файлы или сессии",
|
|
366
|
+
"placeholder.workspace": "Выберите рабочую область",
|
|
367
|
+
"placeholder.steerQueue": "Cmd/Ctrl+Enter направит все сообщения из очереди",
|
|
368
|
+
"input.commands": "Команды",
|
|
369
|
+
"input.stop": "Остановить",
|
|
370
|
+
"input.send": "Отправить",
|
|
371
|
+
"input.send.queue": "В очередь",
|
|
372
|
+
"input.send.steer": "Направить",
|
|
373
|
+
"input.accessMode": "Режим доступа, текущий: {name}",
|
|
374
|
+
"attachment.pending": "Вложения к отправке",
|
|
375
|
+
"attachment.scrollLeft": "Прокрутить вложения влево",
|
|
376
|
+
"attachment.scrollRight": "Прокрутить вложения вправо",
|
|
377
|
+
"attachment.dropTitle": "Перетащите сюда файлы или изображения",
|
|
378
|
+
"attachment.dropDesc": "Не более {count} изображений, каждое до {size}",
|
|
379
|
+
"attachment.dropBlocked": "Сейчас нельзя добавить файлы и изображения",
|
|
380
|
+
"image.pending": "Изображения к отправке",
|
|
381
|
+
"image.openOriginal": "Открыть оригинал",
|
|
382
|
+
"image.openOriginalLabel": "{label}, оригинал по клику",
|
|
383
|
+
"image.remove": "Удалить изображение {name}",
|
|
384
|
+
"image.original": "Оригинал изображения",
|
|
385
|
+
"image.label": "Изображение",
|
|
386
|
+
"image.loadFailed": "Не удалось загрузить изображение; повтор по клику",
|
|
387
|
+
"image.loading": "Загрузка изображения…",
|
|
388
|
+
"image.preview": "Просмотр оригинала",
|
|
389
|
+
"image.closePreview": "Закрыть просмотр оригинала",
|
|
390
|
+
"image.unsupportedType": "Поддерживаются только PNG, JPG, WebP и GIF",
|
|
391
|
+
"image.tooMany": "В сообщении не больше {count} изображений",
|
|
392
|
+
"image.fileTooLarge": "Каждое изображение должно быть меньше {size}",
|
|
393
|
+
"image.totalTooLarge": "Изображения суммарно превышают {size}; удалите часть и повторите",
|
|
394
|
+
"image.tooManyPixels": "Слишком высокое разрешение; сожмите изображение и повторите",
|
|
395
|
+
"image.dimensionTooLarge": "Стороны изображения — не больше {size}px; уменьшите и повторите",
|
|
396
|
+
"image.modelUnsupported": "Текущая модель не поддерживает изображения; выберите другую",
|
|
397
|
+
"image.sendFailed": "Не удалось отправить изображения ({reason}); добавьте их заново",
|
|
398
|
+
"file.attach": "Добавить вложение",
|
|
399
|
+
"file.pending": "Файлы к отправке",
|
|
400
|
+
"file.remove": "Удалить файл {name}",
|
|
401
|
+
"file.uploading": "Загрузка…",
|
|
402
|
+
"file.uploadFailed": "Не удалось загрузить; повтор по клику",
|
|
403
|
+
"file.retry": "Повторить загрузку {name}",
|
|
404
|
+
"file.stillUploading": "Файлы ещё загружаются; отправка — после завершения",
|
|
405
|
+
"file.sessionUnavailable": "Сессия недоступна; файлы загрузить нельзя",
|
|
406
|
+
"file.notStaged": "Файл не догрузился; добавьте его заново",
|
|
407
|
+
"file.label": "Файл",
|
|
408
|
+
"context.aria": "Использовано {percent} контекста",
|
|
409
|
+
"context.used": "контекста использовано",
|
|
410
|
+
"context.system": "Системный запрос",
|
|
411
|
+
"context.tools": "Определения инструментов",
|
|
412
|
+
"context.messages": "Сообщения",
|
|
413
|
+
"settings.enter.title": "Отправка во время работы агента",
|
|
414
|
+
"settings.enter.description": "Что делают Enter и кнопка отправки, пока агент работает; Cmd/Ctrl+Enter выполняет другое действие",
|
|
415
|
+
"settings.enter.queue": "В очередь",
|
|
416
|
+
"settings.enter.steer": "Направить",
|
|
417
|
+
"access.preset.readOnly": "Только чтение",
|
|
418
|
+
"access.preset.workspaceWrite": "Запись в рабочую область",
|
|
419
|
+
"access.preset.fullAccess": "Полный доступ",
|
|
420
|
+
"access.confirm.title": "Включить полный доступ?",
|
|
421
|
+
"access.confirm.description": "Полный доступ сокращает число подтверждений и позволяет агенту выполнять больше действий напрямую, включая чувствительные операции, изменение файлов и внешние команды. Включать только при доверии к текущей задаче.",
|
|
422
|
+
"access.confirm.acknowledge": "Риски понятны, продолжить",
|
|
423
|
+
"access.confirm.cancel": "Отмена",
|
|
424
|
+
"access.confirm.enable": "Включить полный доступ",
|
|
425
|
+
"hero.headline": "Навстречу неизвестному",
|
|
426
|
+
"hero.preview": "Предпросмотр",
|
|
427
|
+
"hero.chooseWorkspace": "Выбрать рабочую область",
|
|
428
|
+
"session.hierarchy": "Иерархия сессий",
|
|
429
|
+
"todo.title": "Задачи",
|
|
430
|
+
"todo.progress.done": "{done} выполнено",
|
|
431
|
+
"todo.progress.active": "{active} в работе",
|
|
432
|
+
"todo.progress.pending": "{pending} в ожидании",
|
|
433
|
+
"todo.rowTitle": "Обновить список задач",
|
|
434
|
+
"todo.completed": "{done}/{total} выполнено",
|
|
435
|
+
"command.attachmentsUnsupported": "/{command} не принимает вложения — сначала удалить их",
|
|
436
|
+
"ask.rowTitle": "Задать вопрос",
|
|
437
|
+
"ask.waiting": "ожидание",
|
|
438
|
+
"ask.cancelled": "отменено",
|
|
439
|
+
"ask.cancelledDetail": "Набор вопросов отменён до отправки ответов.",
|
|
440
|
+
"ask.interrupted": "прервано",
|
|
441
|
+
"ask.interruptedDetail": "Набор вопросов прерван до отправки ответов.",
|
|
442
|
+
"ask.answered": "{answered}/{total} отвечено",
|
|
443
|
+
"ask.skipped": "Без ответа",
|
|
444
|
+
"bash.running": "Выполняется",
|
|
445
|
+
"bash.failed": "Ошибка",
|
|
446
|
+
"bash.stopped": "Остановлено",
|
|
447
|
+
"row.running": "Выполняется",
|
|
448
|
+
"row.failed": "Ошибка",
|
|
449
|
+
"row.stopped": "Остановлено",
|
|
450
|
+
"row.input": "ВХОД",
|
|
451
|
+
"row.output": "ВЫХОД",
|
|
452
|
+
"row.inspect": "Подробнее",
|
|
453
|
+
"tool.title.search": "Поиск",
|
|
454
|
+
"tool.title.read": "Чтение",
|
|
455
|
+
"tool.title.bash": "Bash",
|
|
456
|
+
"tool.title.write": "Запись",
|
|
457
|
+
"tool.title.edit": "Правка",
|
|
458
|
+
"tool.title.code": "Код",
|
|
459
|
+
"tool.title.generic": "Вызов инструмента",
|
|
460
|
+
"tool.title.inspect": "Инспекция",
|
|
461
|
+
"tool.title.runCordis": "Запуск плагина Cordis",
|
|
462
|
+
"tool.title.stopCordis": "Остановка плагина Cordis",
|
|
463
|
+
"tool.title.removeCordis": "Удаление плагина Cordis",
|
|
464
|
+
"tool.title.pwsh": "Pwsh",
|
|
465
|
+
"tool.title.readImage": "Чтение изображения",
|
|
466
|
+
"tool.title.grep": "Grep",
|
|
467
|
+
"tool.title.glob": "Glob",
|
|
468
|
+
"tool.title.webSearch": "Поиск",
|
|
469
|
+
"tool.title.webFetch": "Загрузка страницы",
|
|
470
|
+
"diff.files.one": "{count} файл",
|
|
471
|
+
"diff.files.other": "{count} файлов",
|
|
472
|
+
"diff.collapseAria": "Свернуть diff",
|
|
473
|
+
"diff.expandAria": "Развернуть ещё {count} строк diff",
|
|
474
|
+
"diff.expandRest": "… ещё {count} строк",
|
|
475
|
+
"read.window": "Показано {shown} из {total} строк",
|
|
476
|
+
"read.collapseAria": "Свернуть содержимое",
|
|
477
|
+
"read.expandAria": "Развернуть ещё {count} строк",
|
|
478
|
+
"read.expandRest": "… ещё {count} строк",
|
|
479
|
+
"search.paths": "{shown} путей",
|
|
480
|
+
"search.paths.truncated": "Показано {shown} из {total} путей",
|
|
481
|
+
"search.matches": "{shown} совпадений · {files} файлов",
|
|
482
|
+
"search.matches.truncated": "Показано {shown} из {total} совпадений · {files} файлов",
|
|
483
|
+
"search.noResults": "Ничего не найдено",
|
|
484
|
+
"search.collapseAria": "Свернуть результаты",
|
|
485
|
+
"search.expandAria": "Развернуть ещё {count} строк результатов",
|
|
486
|
+
"search.expandRest": "… ещё {count} строк",
|
|
487
|
+
"web.noResults": "Ничего не найдено",
|
|
488
|
+
"web.sourcesTruncated": "Список источников усечён",
|
|
489
|
+
"web.http": "HTTP",
|
|
490
|
+
"web.contentTruncated": "Содержимое усечено",
|
|
491
|
+
"details.running": "Выполняется…",
|
|
492
|
+
"queue.count": "{n} сообщений в очереди",
|
|
493
|
+
"queue.sending": "Отправка…",
|
|
494
|
+
"queue.image": "Изображение из очереди",
|
|
495
|
+
"queue.file": "Файл {name} в очереди",
|
|
496
|
+
"queue.edit": "Изменить сообщение в очереди",
|
|
497
|
+
"queue.edit.unsupported": "Есть нетекстовое содержимое; изменение пока не поддерживается",
|
|
498
|
+
"queue.save": "Сохранить сообщение",
|
|
499
|
+
"queue.cancelEdit": "Отменить изменение",
|
|
500
|
+
"queue.remove": "Удалить сообщение из очереди",
|
|
501
|
+
"queue.steer": "Направить сообщение",
|
|
502
|
+
"queue.steer.unavailable": "Направить можно только во время работы агента",
|
|
503
|
+
"queue.editFailed": "Не удалось изменить: сообщение уже могло уйти в отправку.",
|
|
504
|
+
"queue.removeFailed": "Не удалось удалить: сообщение уже могло уйти в отправку.",
|
|
505
|
+
"queue.steerFailed": "Не удалось направить. Повторите.",
|
|
506
|
+
"terminal.signal": "сигнал {signal}",
|
|
507
|
+
"terminal.exitCode": "код выхода {code}",
|
|
508
|
+
"terminal.running": "Выполняется",
|
|
509
|
+
"terminal.failed": "Ошибка",
|
|
510
|
+
"terminal.done": "Готово",
|
|
511
|
+
"terminal.noOutput": "Нет вывода",
|
|
512
|
+
"terminal.collapseAria": "Свернуть вывод",
|
|
513
|
+
"terminal.expandAria": "Развернуть оставшиеся {n} строк вывода",
|
|
514
|
+
"terminal.expandRest": "… ещё {n} строк",
|
|
515
|
+
"terminal.sendInput": "(отправить ввод)",
|
|
516
|
+
"terminal.session": "Терминал {sessionId}"
|
|
517
|
+
},
|
|
518
|
+
"cordis": {
|
|
519
|
+
"row.defineTitle": "Регистрация плагина Cordis",
|
|
520
|
+
"row.runTitle": "Запуск плагина Cordis",
|
|
521
|
+
"row.updateTitle": "Обновление плагина Cordis",
|
|
522
|
+
"row.stopTitle": "Остановка плагина Cordis",
|
|
523
|
+
"row.removeTitle": "Удаление плагина Cordis",
|
|
524
|
+
"purpose.missing": "(назначение не указано)",
|
|
525
|
+
"status.idle": "Готов",
|
|
526
|
+
"status.awaitingApproval": "Ожидание подтверждения",
|
|
527
|
+
"status.failed": "Ошибка запуска",
|
|
528
|
+
"status.clientPending": "Клиент готов к активации",
|
|
529
|
+
"status.running": "Выполняется",
|
|
530
|
+
"status.removed": "Удалён",
|
|
531
|
+
"status.superseded": "Есть более новый запуск",
|
|
532
|
+
"run.removed": "Этого пакета больше не существует",
|
|
533
|
+
"run.superseded": "Ниже есть карточка более нового запуска",
|
|
534
|
+
"panel.hint": "Управление запуском — в панели Cordis над настройками",
|
|
535
|
+
"panel.plugins.aria": "Плагины Cordis",
|
|
536
|
+
"panel.approvals.aria": "Подтверждения Cordis",
|
|
537
|
+
"panel.trigger": "Плагин Cordis",
|
|
538
|
+
"panel.runningCount": "Выполняется: {count}",
|
|
539
|
+
"panel.title": "Плагины Cordis",
|
|
540
|
+
"panel.empty": "Плагинов пока нет",
|
|
541
|
+
"panel.loading": "Чтение…",
|
|
542
|
+
"panel.readFailed": "Не удалось прочитать список плагинов: {message}",
|
|
543
|
+
"panel.group.current": "Эта сессия",
|
|
544
|
+
"panel.group.others": "Другие сессии",
|
|
545
|
+
"panel.version": "Версия",
|
|
546
|
+
"panel.current": "Текущая: {packageId}",
|
|
547
|
+
"panel.next": "Следующая: {packageId}",
|
|
548
|
+
"action.approve": "Разрешить",
|
|
549
|
+
"action.approveOnce": "Разрешить только эту версию",
|
|
550
|
+
"action.approvePlugin": "Разрешить будущие версии этого плагина",
|
|
551
|
+
"action.decline": "Отклонить",
|
|
552
|
+
"action.run": "Запустить",
|
|
553
|
+
"action.stop": "Остановить",
|
|
554
|
+
"action.remove": "Удалить",
|
|
555
|
+
"action.retry": "Повторить",
|
|
556
|
+
"action.rollback": "Откатить",
|
|
557
|
+
"action.inspect": "Подробнее",
|
|
558
|
+
"render.failedAbdicated": "Ошибка отрисовки в {slot}; восстановлен интерфейс по умолчанию:",
|
|
559
|
+
"render.failedHeld": "Ошибка отрисовки в {slot}:",
|
|
560
|
+
"a11y.defining": "Определение плагина",
|
|
561
|
+
"a11y.failed": "Не удалось определить плагин",
|
|
562
|
+
"a11y.stopped": "Определение прервано",
|
|
563
|
+
"body.source": "Исходный код плагина",
|
|
564
|
+
"body.hostCode": "Host",
|
|
565
|
+
"body.clientCode": "Client",
|
|
566
|
+
"body.output": "Результат",
|
|
567
|
+
"body.copy": "Копировать",
|
|
568
|
+
"body.copied": "Скопировано"
|
|
569
|
+
},
|
|
570
|
+
"deliverables": {
|
|
571
|
+
"presented.nativeUnavailable": "У этого файла нет доступного пути на Host. Открыть просмотр в боковой панели.",
|
|
572
|
+
"presented.revealError": "Не удалось показать в файловом менеджере. Повторить.",
|
|
573
|
+
"presented.directoryError": "Не удалось открыть папку с файлом. Повторить.",
|
|
574
|
+
"presented.directoryOpening": "Открытие папки с файлом…",
|
|
575
|
+
"presented.directoryOpened": "Запрошено открытие папки с файлом",
|
|
576
|
+
"presented.revealed": "Запрошен показ в файловом менеджере",
|
|
577
|
+
"presented.revealing": "Показ в файловом менеджере…",
|
|
578
|
+
"presented.unavailable": "На этом Host нет рабочего стола для открытия файлов и папок",
|
|
579
|
+
"presented.retry": "Повторить",
|
|
580
|
+
"presented.hostError": "Не удалось получить сведения о рабочем столе Host",
|
|
581
|
+
"presented.directory": "Открыть папку с файлом",
|
|
582
|
+
"presented.explorer": "Показать в Проводнике",
|
|
583
|
+
"presented.finder": "Показать в Finder",
|
|
584
|
+
"presented.defaultApp": "Открыть в приложении по умолчанию",
|
|
585
|
+
"presented.more": "Другие действия с файлом {name}",
|
|
586
|
+
"presented.action": "Открыть",
|
|
587
|
+
"presented.preview": "Просмотр в боковой панели",
|
|
588
|
+
"presented.previewButton": "Открыть {name} в боковой панели",
|
|
589
|
+
"presented.previewCard": "Просмотр {name} в боковой панели",
|
|
590
|
+
"presented.all": "Все {count} файлов",
|
|
591
|
+
"presented.expandAria": "Показать все выданные файлы: {count}",
|
|
592
|
+
"presented.collapse": "Свернуть",
|
|
593
|
+
"presented.collapseAria": "Свернуть выданные файлы",
|
|
594
|
+
"presented.opening": "Открытие…",
|
|
595
|
+
"presented.opened": "Открыто в приложении по умолчанию",
|
|
596
|
+
"presented.error": "Не удалось открыть. Нажатие — повтор.",
|
|
597
|
+
"presented.file": "Файл",
|
|
598
|
+
"row.title": "Выдача файлов",
|
|
599
|
+
"row.running": "Выдача",
|
|
600
|
+
"row.ok": "Выдано",
|
|
601
|
+
"row.error": "Ошибка выдачи",
|
|
602
|
+
"row.stopped": "Прервано",
|
|
603
|
+
"row.inspect": "Посмотреть вызов",
|
|
604
|
+
"presented.open": "Открыть {name} в приложении по умолчанию",
|
|
605
|
+
"produced.label": "Изменённые файлы",
|
|
606
|
+
"produced.moreOne": "+ 1 файл",
|
|
607
|
+
"produced.more": "+ {count} файлов",
|
|
608
|
+
"produced.open": "Открыть {name}"
|
|
609
|
+
},
|
|
610
|
+
"directory-browser": {
|
|
611
|
+
"browser.title": "Выбор каталога рабочей области",
|
|
612
|
+
"browser.home": "Домашняя папка",
|
|
613
|
+
"browser.newFolder": "Новая папка",
|
|
614
|
+
"browser.folderName": "Имя папки",
|
|
615
|
+
"browser.createIn": "Новая папка в «{name}»",
|
|
616
|
+
"browser.untitledFolder": "Папка без имени",
|
|
617
|
+
"browser.create": "Создать",
|
|
618
|
+
"browser.cancel": "Отмена",
|
|
619
|
+
"browser.open": "Открыть",
|
|
620
|
+
"browser.editPath": "Изменить путь",
|
|
621
|
+
"browser.loading": "Загрузка…",
|
|
622
|
+
"browser.truncated": "Слишком много папок, показано только начало списка.",
|
|
623
|
+
"browser.showHidden": "Показывать скрытые файлы"
|
|
624
|
+
},
|
|
625
|
+
"documentHtml": {
|
|
626
|
+
"title": "HTML",
|
|
627
|
+
"frame": "Предпросмотр HTML-документа",
|
|
628
|
+
"loading": "Подготовка предпросмотра HTML…",
|
|
629
|
+
"failed": "Не удалось показать этот HTML-документ."
|
|
630
|
+
},
|
|
631
|
+
"documentMarkdown": {
|
|
632
|
+
"viewer.label": "Markdown",
|
|
633
|
+
"code.copy": "Копировать",
|
|
634
|
+
"code.copied": "Скопировано",
|
|
635
|
+
"footnotes": "Сноски"
|
|
636
|
+
},
|
|
637
|
+
"feedback": {
|
|
638
|
+
"action.like": "Хороший ответ",
|
|
639
|
+
"action.likeActive": "Убрать оценку",
|
|
640
|
+
"action.dislike": "Плохой ответ",
|
|
641
|
+
"action.dislikeActive": "Убрать оценку",
|
|
642
|
+
"dialog.title": "Отправить отзыв",
|
|
643
|
+
"dialog.categories": "Категория отзыва",
|
|
644
|
+
"dialog.detail": "Подробности отзыва",
|
|
645
|
+
"dialog.hint": "Подробности помогут стать лучше. К отзыву будет приложен журнал текущего диалога.",
|
|
646
|
+
"category.task-result": "Результат задачи",
|
|
647
|
+
"category.instruction-following": "Понимание и соблюдение инструкций",
|
|
648
|
+
"category.product-interaction": "Возможности продукта и взаимодействие",
|
|
649
|
+
"category.service-stability": "Стабильность и скорость",
|
|
650
|
+
"category.resource-cost": "Расход ресурсов и стоимость",
|
|
651
|
+
"category.security-privacy-permission": "Безопасность, приватность и разрешения",
|
|
652
|
+
"category.other": "Другое",
|
|
653
|
+
"toast.recorded": "Спасибо за отзыв",
|
|
654
|
+
"error.conflict": "Отзыв изменён в другом месте — показано актуальное состояние",
|
|
655
|
+
"error.load": "Не удалось загрузить отзыв",
|
|
656
|
+
"error.generic": "Не удалось сохранить отзыв",
|
|
657
|
+
"error.noteTooLarge": "Описание слишком длинное — сократить и отправить снова"
|
|
658
|
+
},
|
|
659
|
+
"goal": {
|
|
660
|
+
"phase.active": "Активная цель",
|
|
661
|
+
"phase.active.disarmed": "Неактивная цель",
|
|
662
|
+
"phase.paused": "Цель на паузе",
|
|
663
|
+
"phase.blocked": "Заблокированная цель",
|
|
664
|
+
"objective.aria": "Формулировка цели",
|
|
665
|
+
"commandInput.aria": "Ввод команды",
|
|
666
|
+
"action.save": "Сохранить цель",
|
|
667
|
+
"action.cancel": "Отменить правку",
|
|
668
|
+
"action.pause": "Приостановить цель",
|
|
669
|
+
"action.resume": "Возобновить цель",
|
|
670
|
+
"action.edit": "Изменить цель",
|
|
671
|
+
"action.clear": "Очистить цель"
|
|
672
|
+
},
|
|
673
|
+
"job": {
|
|
674
|
+
"count.live.one": "{count} фоновая задача выполняется",
|
|
675
|
+
"count.live.other": "Выполняется фоновых задач: {count}",
|
|
676
|
+
"count.idle.one": "{count} фоновая задача",
|
|
677
|
+
"count.idle.other": "Фоновых задач: {count}",
|
|
678
|
+
"list.aria": "Фоновые задачи",
|
|
679
|
+
"status.running": "выполняется",
|
|
680
|
+
"status.stopping": "останавливается",
|
|
681
|
+
"status.completed": "завершена",
|
|
682
|
+
"status.killed": "отменена",
|
|
683
|
+
"status.failed": "ошибка",
|
|
684
|
+
"duration.seconds": "{seconds} с",
|
|
685
|
+
"duration.minutes": "{minutes} мин {seconds} с",
|
|
686
|
+
"duration.hours": "{hours} ч {minutes} мин",
|
|
687
|
+
"duration.title.live": "Выполняется {duration}",
|
|
688
|
+
"duration.title.done": "Заняла {duration}"
|
|
689
|
+
},
|
|
690
|
+
"model": {
|
|
691
|
+
"command.description": "Выбрать модель для этого диалога",
|
|
692
|
+
"option.loadError": "Не удалось загрузить каталог: {message}",
|
|
693
|
+
"option.deepseekV4Flash.description": "Быстрая, эффективная и экономичная; подходит для точечных, рутинных и параллельных задач.",
|
|
694
|
+
"option.deepseekV4Pro.description": "Сильнее в агентном программировании, знаниях и сложных рассуждениях; подходит для комплексных и критичных к качеству задач, но дороже.",
|
|
695
|
+
"trigger.fallback": "Выбрать модель",
|
|
696
|
+
"trigger.loading": "Загрузка моделей…",
|
|
697
|
+
"trigger.selectAria": "Выбрать модель",
|
|
698
|
+
"trigger.aria": "Выбор модели, текущая — {model}",
|
|
699
|
+
"trigger.ariaEffort": "Выбор модели, текущая — {model}, глубина рассуждений {effort}",
|
|
700
|
+
"menu.aria": "Модель и глубина рассуждений",
|
|
701
|
+
"menu.model": "Модель",
|
|
702
|
+
"menu.effort": "Глубина",
|
|
703
|
+
"effort.providerDefault": "По умолчанию",
|
|
704
|
+
"status.loading": "Обновление списка моделей…",
|
|
705
|
+
"error.action": "Операция с моделью не удалась: {message}",
|
|
706
|
+
"action.reload": "Обновить",
|
|
707
|
+
"warning.groupLoad": "{name}: не удалось загрузить — {message}",
|
|
708
|
+
"empty.models": "Нет доступных моделей.",
|
|
709
|
+
"blocked.composer": "Модель недоступна — выбрать другую, чтобы продолжить",
|
|
710
|
+
"empty.efforts": "У этой модели нет уровней глубины рассуждений."
|
|
711
|
+
},
|
|
712
|
+
"open-in-app": {
|
|
713
|
+
"open.title": "Открыть рабочую область в {app}",
|
|
714
|
+
"open.tooltip": "Открыть локально",
|
|
715
|
+
"open.error": "Не удалось открыть",
|
|
716
|
+
"menu.toggle": "Выбрать приложение для открытия",
|
|
717
|
+
"menu.aria": "Открыть в",
|
|
718
|
+
"app.cursor": "Cursor",
|
|
719
|
+
"app.vscode": "VS Code",
|
|
720
|
+
"app.vscodeinsiders": "VS Code Insiders",
|
|
721
|
+
"app.windsurf": "Windsurf",
|
|
722
|
+
"app.zed": "Zed",
|
|
723
|
+
"app.sublimetext": "Sublime Text",
|
|
724
|
+
"app.xcode": "Xcode",
|
|
725
|
+
"app.androidstudio": "Android Studio",
|
|
726
|
+
"app.intellij": "IntelliJ IDEA",
|
|
727
|
+
"app.pycharm": "PyCharm",
|
|
728
|
+
"app.webstorm": "WebStorm",
|
|
729
|
+
"app.phpstorm": "PhpStorm",
|
|
730
|
+
"app.goland": "GoLand",
|
|
731
|
+
"app.rider": "Rider",
|
|
732
|
+
"app.rustrover": "RustRover",
|
|
733
|
+
"app.fork": "Fork",
|
|
734
|
+
"app.sourcetree": "Sourcetree",
|
|
735
|
+
"app.github": "GitHub Desktop",
|
|
736
|
+
"app.tower": "Tower",
|
|
737
|
+
"app.gitkraken": "GitKraken",
|
|
738
|
+
"app.smartgit": "SmartGit",
|
|
739
|
+
"app.sublimemerge": "Sublime Merge",
|
|
740
|
+
"app.ghostty": "Ghostty",
|
|
741
|
+
"app.warp": "Warp",
|
|
742
|
+
"app.iterm": "iTerm2",
|
|
743
|
+
"app.kitty": "kitty",
|
|
744
|
+
"app.windowsterminal": "Windows Terminal",
|
|
745
|
+
"app.gitbash": "Git Bash",
|
|
746
|
+
"app.gnometerminal": "GNOME Terminal",
|
|
747
|
+
"app.konsole": "Konsole",
|
|
748
|
+
"app.finder": "Finder",
|
|
749
|
+
"app.explorer": "Проводник",
|
|
750
|
+
"app.filemanager": "Файлы",
|
|
751
|
+
"app.terminal": "Терминал"
|
|
752
|
+
},
|
|
753
|
+
"permission.access": {
|
|
754
|
+
"preset.readOnly": "Только чтение",
|
|
755
|
+
"preset.workspaceWrite": "Запись в рабочую область",
|
|
756
|
+
"preset.fullAccess": "Полный доступ",
|
|
757
|
+
"confirm.title": "Включить полный доступ?",
|
|
758
|
+
"confirm.description": "Полный доступ сокращает число подтверждений и позволяет агенту выполнять больше действий напрямую, включая чувствительные операции, изменение файлов и внешние команды. Включать только при доверии к текущей задаче.",
|
|
759
|
+
"confirm.acknowledge": "Риски понятны, продолжить",
|
|
760
|
+
"confirm.cancel": "Отмена",
|
|
761
|
+
"confirm.enable": "Включить полный доступ"
|
|
762
|
+
},
|
|
763
|
+
"plan": {
|
|
764
|
+
"chip.label": "План",
|
|
765
|
+
"chip.on.aria": "Режим плана включён, нажать, чтобы выключить",
|
|
766
|
+
"chip.on.title": "Режим плана включён — нажать, чтобы выключить (/plan off)",
|
|
767
|
+
"chip.off.aria": "Режим плана выключен, нажать, чтобы включить",
|
|
768
|
+
"chip.off.title": "Режим плана выключен — нажать, чтобы включить (/plan)",
|
|
769
|
+
"chip.exitFailed": "Не удалось выйти из режима плана"
|
|
770
|
+
},
|
|
771
|
+
"question": {
|
|
772
|
+
"error.incomplete": "Сначала нужно ответить на этот вопрос.",
|
|
773
|
+
"error.unanswered": "Нужно выбрать вариант или ввести свой ответ.",
|
|
774
|
+
"nav.prev": "Предыдущий вопрос",
|
|
775
|
+
"nav.next": "Следующий вопрос",
|
|
776
|
+
"nav.minimize": "Свернуть карточку вопроса",
|
|
777
|
+
"nav.maximize": "Развернуть карточку вопроса",
|
|
778
|
+
"nav.cancel": "Закрыть все вопросы",
|
|
779
|
+
"option.recommended": "Рекомендуется",
|
|
780
|
+
"custom.placeholder": "Свой ответ",
|
|
781
|
+
"action.skip": "Пропустить вопрос",
|
|
782
|
+
"action.next": "Далее",
|
|
783
|
+
"plan.header": "Проверка плана",
|
|
784
|
+
"plan.approve": "Подтвердить",
|
|
785
|
+
"plan.decline": "Отклонить",
|
|
786
|
+
"plan.discuss": "Обсудить"
|
|
787
|
+
},
|
|
788
|
+
"reference": {
|
|
789
|
+
"section.files": "Файлы и папки",
|
|
790
|
+
"section.sessions": "Сессии",
|
|
791
|
+
"candidate.noCwd": "(нет cwd)",
|
|
792
|
+
"crumb.root": "Рабочая область",
|
|
793
|
+
"time.now": "сейчас",
|
|
794
|
+
"time.minutes": "{n} мин",
|
|
795
|
+
"time.hours": "{n} ч",
|
|
796
|
+
"time.days": "{n} дн",
|
|
797
|
+
"time.months": "{n} мес",
|
|
798
|
+
"time.years": "{n} г"
|
|
799
|
+
},
|
|
800
|
+
"schedule.catalog": {
|
|
801
|
+
"trigger.one": "{count} напоминание",
|
|
802
|
+
"trigger.other": "{count} напоминаний",
|
|
803
|
+
"list.aria": "Активные напоминания",
|
|
804
|
+
"status.scheduled": "Запланировано",
|
|
805
|
+
"status.overdue": "Просрочено",
|
|
806
|
+
"frequency.once": "Однократно",
|
|
807
|
+
"frequency.every": "Раз в {value} {unit}",
|
|
808
|
+
"unit.day.one": "день",
|
|
809
|
+
"unit.day.other": "дней",
|
|
810
|
+
"unit.hour.one": "час",
|
|
811
|
+
"unit.hour.other": "часов",
|
|
812
|
+
"unit.minute.one": "минуту",
|
|
813
|
+
"unit.minute.other": "минут",
|
|
814
|
+
"unit.second.one": "секунду",
|
|
815
|
+
"unit.second.other": "секунд",
|
|
816
|
+
"relative.now": "Сейчас",
|
|
817
|
+
"relative.future": "через {value} {unit}",
|
|
818
|
+
"relative.overdue": "просрочено на {value} {unit}"
|
|
819
|
+
},
|
|
820
|
+
"settings.agentPreset": {
|
|
821
|
+
"error": "Не удалось загрузить пресеты агента.",
|
|
822
|
+
"userTrust": "Свой",
|
|
823
|
+
"seatHint": "Пресет агента для сессии, которая сейчас начнётся",
|
|
824
|
+
"headerHint": "Пресет агента этой сессии, закреплён при её старте",
|
|
825
|
+
"nav": "Пресеты агента",
|
|
826
|
+
"sectionIntro": "Пресет — это композиция плагинов, на которой работает агент сессии: его инструменты, запрос и возможности. Можно скопировать готовый и доработать под себя или поручить черновик агенту в режиме создателя.",
|
|
827
|
+
"builtIn": "Встроенный",
|
|
828
|
+
"setDefault": "Назначить по умолчанию",
|
|
829
|
+
"view": "Посмотреть",
|
|
830
|
+
"presetStandardName": "Стандартный режим",
|
|
831
|
+
"presetStandardDescription": "Полноценный агент для программирования: правка файлов, командная оболочка, поиск по файлам и веб-поиск, навыки, планирование, цели, подагенты и workflow.",
|
|
832
|
+
"presetPtcName": "Режим PTC",
|
|
833
|
+
"presetPtcDescription": "Полноценный агент для программирования без инструмента workflow; остальные инструменты доступны через SDK режима PTC, поэтому модель объединяет многошаговые операции в одной программе на TypeScript.",
|
|
834
|
+
"presetMinimalName": "Минимальный режим",
|
|
835
|
+
"presetMinimalDescription": "Агент для программирования с единственным инструментом — постоянной командной оболочкой.",
|
|
836
|
+
"presetCordisName": "Режим создателя",
|
|
837
|
+
"presetCordisDescription": "Создан для разработки своих пресетов агента: все возможности стандартного режима плюс просмотр среды выполнения, эксперименты с плагинами и подсказки по написанию пресетов.",
|
|
838
|
+
"duplicate": "Скопировать",
|
|
839
|
+
"duplicateUnavailable": "В этом развёртывании нет каталога пресетов с правом записи",
|
|
840
|
+
"delete": "Удалить",
|
|
841
|
+
"presetId": "Идентификатор",
|
|
842
|
+
"presetIdPlaceholder": "my-agent",
|
|
843
|
+
"displayName": "Имя",
|
|
844
|
+
"displayNamePlaceholder": "Показывается в списке выбора; по умолчанию — идентификатор",
|
|
845
|
+
"inUse": "Используется",
|
|
846
|
+
"builtInGroup": "Встроенные",
|
|
847
|
+
"customGroup": "Свои",
|
|
848
|
+
"noDescription": "Без описания.",
|
|
849
|
+
"brokenBadge": "Ошибка загрузки",
|
|
850
|
+
"brokenNoCopy": "Пресет с ошибкой загрузки скопировать нельзя",
|
|
851
|
+
"switchRefused": "Не удалось переключиться на {name}: {reason}",
|
|
852
|
+
"copyOf": "Скопирован из",
|
|
853
|
+
"composition": "Композиция (agent.cordis.yml)",
|
|
854
|
+
"cancel": "Отмена",
|
|
855
|
+
"close": "Закрыть",
|
|
856
|
+
"retry": "Повторить",
|
|
857
|
+
"copyTitle": "Копирование пресета",
|
|
858
|
+
"copyIntro": "Пресет копируется целиком на этой машине. Идентификатор станет именем каталога и позже не меняется; всё остальное правится в файлах самого пресета.",
|
|
859
|
+
"create": "Создать",
|
|
860
|
+
"creating": "Создание…",
|
|
861
|
+
"creatorDraft": "Сделать черновик своего пресета в режиме создателя",
|
|
862
|
+
"openLocation": "Открыть папку",
|
|
863
|
+
"showLocation": "Показать расположение",
|
|
864
|
+
"revealedPathLabel": "Файлы пресета:",
|
|
865
|
+
"idRequired": "Нужен идентификатор пресета.",
|
|
866
|
+
"idInvalid": "Только строчные буквы, цифры и дефисы; начинать с буквы или цифры.",
|
|
867
|
+
"idTaken": "Пресет с таким идентификатором уже есть.",
|
|
868
|
+
"deleteTitle": "Удалить этот пресет?",
|
|
869
|
+
"deleteDescription": "Каталог пресета будет удалён. Уже запущенные на нём сессии продолжат работу; новые сессии выбрать его не смогут.",
|
|
870
|
+
"deleteConfirm": "Удалить",
|
|
871
|
+
"deleting": "Удаление…"
|
|
872
|
+
},
|
|
873
|
+
"settings": {
|
|
874
|
+
"trigger": "Настройки",
|
|
875
|
+
"title": "Настройки",
|
|
876
|
+
"close": "Закрыть",
|
|
877
|
+
"openDocument": "Открыть файл конфигурации",
|
|
878
|
+
"openDocument.error": "Не удалось открыть файл конфигурации",
|
|
879
|
+
"general.nav": "Общие",
|
|
880
|
+
"connection.error": "Нет соединения",
|
|
881
|
+
"connection.retry": "Переподключиться",
|
|
882
|
+
"connection.connecting": "Переподключение",
|
|
883
|
+
"connection.connected": "Подключено",
|
|
884
|
+
"connection.reconnect": "Нет соединения, переподключиться",
|
|
885
|
+
"connection.restart": "Идёт автопереподключение, переподключиться сейчас"
|
|
886
|
+
},
|
|
887
|
+
"settings.models": {
|
|
888
|
+
"nav": "Модели",
|
|
889
|
+
"title": "Модели",
|
|
890
|
+
"intro": "Чтобы использовать модели этих провайдеров, нужно указать ключи API.",
|
|
891
|
+
"edit": "Изменить",
|
|
892
|
+
"editProvider": "Изменить {provider}",
|
|
893
|
+
"remove": "Удалить",
|
|
894
|
+
"removeProvider": "Удалить {provider}",
|
|
895
|
+
"deleteTitle": "Удалить {provider}?",
|
|
896
|
+
"deleteDescription": "Удаление {provider} убирает его конфигурацию. Используемые им учётные данные хранятся отдельно и сохранятся.",
|
|
897
|
+
"deleteDescriptionWithCredential": "Удаление {provider} убирает его конфигурацию и сохранённый ключ API.",
|
|
898
|
+
"deleteConfirm": "Удалить {provider}",
|
|
899
|
+
"deleting": "Удаление {provider}…",
|
|
900
|
+
"add": "Добавить провайдера",
|
|
901
|
+
"provider": "Провайдер",
|
|
902
|
+
"close": "Закрыть",
|
|
903
|
+
"cancel": "Отмена",
|
|
904
|
+
"apply": "Применить",
|
|
905
|
+
"applying": "Применение…",
|
|
906
|
+
"savedProvider": "{provider} сохранён.",
|
|
907
|
+
"credentialConfigured": "Ключ API задан",
|
|
908
|
+
"credentialMissing": "Ключ API не задан",
|
|
909
|
+
"readOnly": "В этом развёртывании файл настроек доступен только для чтения.",
|
|
910
|
+
"loadFailed": "Не удалось загрузить каталог провайдеров",
|
|
911
|
+
"conflict": "Настройки изменены в другом месте, пока карточка была открыта. Закрыть и открыть заново, чтобы править актуальные значения.",
|
|
912
|
+
"retry": "Повторить",
|
|
913
|
+
"keyInput": "Ключ API",
|
|
914
|
+
"keyPlaceholder": "Ключ API",
|
|
915
|
+
"keyPlaceholderNative": "Ключ API; пусто — аутентификация через окружение",
|
|
916
|
+
"keyStored": "Задан — новое значение заменит текущий",
|
|
917
|
+
"keyEnvLocked": "Задан окружением запуска (только чтение)",
|
|
918
|
+
"customized": "Изменённые настройки",
|
|
919
|
+
"baseUrl": "Базовый URL",
|
|
920
|
+
"baseUrlDefault": "По умолчанию провайдера",
|
|
921
|
+
"models": "Модели",
|
|
922
|
+
"modelsInherited": "Каталог адаптера по умолчанию",
|
|
923
|
+
"modelsCustomized": "Изменённый каталог моделей",
|
|
924
|
+
"resetModels": "Сбросить по умолчанию",
|
|
925
|
+
"model": "Модель",
|
|
926
|
+
"modelId": "ID модели",
|
|
927
|
+
"modelName": "Отображаемое имя",
|
|
928
|
+
"modelNamePlaceholder": "Если пусто — берётся ID модели",
|
|
929
|
+
"contextWindow": "Окно контекста",
|
|
930
|
+
"contextWindowPlaceholder": "Если пусто — значение провайдера",
|
|
931
|
+
"maxTokens": "Макс. выходных токенов",
|
|
932
|
+
"maxTokensPlaceholder": "Если пусто — значение провайдера",
|
|
933
|
+
"modelAdvanced": "Лимиты",
|
|
934
|
+
"addModel": "Добавить модель",
|
|
935
|
+
"removeModel": "Удалить модель",
|
|
936
|
+
"modelsEmpty": "В списке выбора моделей ничего не появится. ID вне списка всё равно можно отправлять напрямую.",
|
|
937
|
+
"keyBlank": "Указать ключ API или оставить поле пустым, чтобы сохранить текущий.",
|
|
938
|
+
"keyBlankNew": "Указать ключ API или оставить поле пустым, если провайдер аутентифицируется иначе.",
|
|
939
|
+
"keyIllegalCharacters": "Недопустимый формат ключа API. Нужно проверить значение.",
|
|
940
|
+
"modelIdRequired": "ID модели обязателен.",
|
|
941
|
+
"modelIdDuplicate": "ID модели должен быть уникальным.",
|
|
942
|
+
"modelNameInvalid": "Отображаемое имя не может быть пустым.",
|
|
943
|
+
"modelContextInvalid": "Окно контекста — положительное число, например 131072, 256K или 1M.",
|
|
944
|
+
"modelMaxTokensInvalid": "Макс. выходных токенов — положительное число, например 8192, 64K или 1M.",
|
|
945
|
+
"advancedHint": "Остальные поля — в settings.yaml; править этот раздел напрямую.",
|
|
946
|
+
"modelCapacityInvalid": "Лимит — число, при необходимости с суффиксом K или M.",
|
|
947
|
+
"modelDuplicate": "ID модели может встречаться только один раз.",
|
|
948
|
+
"modelContextWindow": "Окно контекста",
|
|
949
|
+
"modelMaxTokens": "Макс. выходных токенов",
|
|
950
|
+
"fetchModels": "Запросить доступные модели",
|
|
951
|
+
"fetching": "Запрос к провайдеру…",
|
|
952
|
+
"fetchNeedsBaseUrl": "Сначала нужно указать базовый URL, затем запрашивать.",
|
|
953
|
+
"fetchEmpty": "Провайдер не вернул ни одной модели. Добавить вручную.",
|
|
954
|
+
"fetchTitle": "Выбор моделей для добавления",
|
|
955
|
+
"fetchDescription": "Модели, доступные у этого провайдера. Отметить те, которые нужно добавить.",
|
|
956
|
+
"fetchSearch": "Поиск моделей",
|
|
957
|
+
"fetchNoMatches": "Подходящих моделей нет.",
|
|
958
|
+
"fetchSelectAll": "Выбрать все",
|
|
959
|
+
"fetchDeselectAll": "Снять выбор",
|
|
960
|
+
"fetchAdopt": "Добавить выбранные",
|
|
961
|
+
"customAdd": "Добавить свой провайдер",
|
|
962
|
+
"customTitle": "Свой провайдер",
|
|
963
|
+
"customTag": "Свой",
|
|
964
|
+
"customRoute": "ID провайдера",
|
|
965
|
+
"customRouteHint": "Идентификатор в нижнем регистре, начинается с буквы; однозначно задаёт провайдера в запросах и служит именем его учётных данных.",
|
|
966
|
+
"customRouteInvalid": "Начинается со строчной буквы, далее строчные буквы, цифры и дефисы.",
|
|
967
|
+
"customRouteTaken": "Этот ID уже занят другим провайдером.",
|
|
968
|
+
"customDisplayName": "Отображаемое имя",
|
|
969
|
+
"customApi": "Протокол API",
|
|
970
|
+
"customApiUnset": "Не выбран",
|
|
971
|
+
"customNeedsBaseUrl": "Своему провайдеру нужен базовый URL.",
|
|
972
|
+
"customBaseUrlInvalid": "Нужен корректный URL на HTTP или HTTPS.",
|
|
973
|
+
"customNeedsModels": "Своему провайдеру нужна хотя бы одна модель.",
|
|
974
|
+
"customBaseUrlPlaceholder": "https://gateway.example/v1",
|
|
975
|
+
"settingsPathUnresolvable": "путь к настройкам не определён",
|
|
976
|
+
"create": "Создать провайдера",
|
|
977
|
+
"creating": "Создание…",
|
|
978
|
+
"welcomeTitle": "Уведомление о внутреннем тестировании",
|
|
979
|
+
"welcomeBody": "DeepSeek Harness 0.1 остаётся в тестировании для разработчиков Harness. Многое ещё предстоит улучшить, и отзывы сообщества разработчиков очень важны. Основные плагины и базовые API DeepSeek Harness продолжат быстро развиваться в ближайшие месяцы.\n\nМы рассчитываем вместе с разработчиками по всему миру исследовать границы возможностей интеллекта, опираясь на открытую, переиспользуемую и компонуемую инфраструктуру с открытым исходным кодом. Приглашаем разработчиков Harness со всего мира присоединиться к экосистеме плагинов DSH.",
|
|
980
|
+
"welcomeContinue": "Продолжить",
|
|
981
|
+
"welcomeError": "Не удалось сохранить подтверждение. Нужно повторить.",
|
|
982
|
+
"onboardingTitle": "Для начала работы нужен ключ API",
|
|
983
|
+
"onboardingDescription": "Настроить официальный провайдер DeepSeek, чтобы начать работу.",
|
|
984
|
+
"onboardingLater": "Настроить позже",
|
|
985
|
+
"onboardingSave": "Сохранить и продолжить",
|
|
986
|
+
"onboardingSaving": "Сохранение…",
|
|
987
|
+
"keyRequired": "Чтобы продолжить, нужен ключ API."
|
|
988
|
+
},
|
|
989
|
+
"settings.permission": {
|
|
990
|
+
"title": "Разрешения",
|
|
991
|
+
"description": "Режим разрешений по умолчанию для новых сессий",
|
|
992
|
+
"loading": "Загрузка",
|
|
993
|
+
"unavailable": "Недоступно",
|
|
994
|
+
"preset.readOnly": "Только чтение",
|
|
995
|
+
"preset.workspaceWrite": "Запись в рабочую область",
|
|
996
|
+
"preset.fullAccess": "Полный доступ",
|
|
997
|
+
"confirm.title": "Включить полный доступ?",
|
|
998
|
+
"confirm.description": "Полный доступ позволяет новым сессиям реже запрашивать подтверждения и выполнять больше действий напрямую, включая чувствительные операции, изменение файлов и внешние команды. Включать только при доверии к последующим задачам.",
|
|
999
|
+
"confirm.acknowledge": "Риски понятны, продолжить",
|
|
1000
|
+
"confirm.cancel": "Отмена",
|
|
1001
|
+
"confirm.enable": "Включить полный доступ"
|
|
1002
|
+
},
|
|
1003
|
+
"settings.pluginInventory": {
|
|
1004
|
+
"tab": "Список плагинов",
|
|
1005
|
+
"loading": "Чтение плагинов…",
|
|
1006
|
+
"error": "Плагины временно недоступны.",
|
|
1007
|
+
"retry": "Повторить",
|
|
1008
|
+
"search": "Поиск плагинов",
|
|
1009
|
+
"empty": "Доступных плагинов нет.",
|
|
1010
|
+
"emptySearch": "Подходящих плагинов нет.",
|
|
1011
|
+
"presetTitle": "Плагины сессии",
|
|
1012
|
+
"presetSubtitle": "Составляются для каждой сессии пресетами агента",
|
|
1013
|
+
"countUnit": "плагинов",
|
|
1014
|
+
"switcherLabel": "Выбрать пресет агента для просмотра",
|
|
1015
|
+
"presetOptionDefault": "{name} (по умолчанию)",
|
|
1016
|
+
"presetOptionBroken": "{name} (ошибка загрузки)",
|
|
1017
|
+
"globalTitle": "Глобальные плагины",
|
|
1018
|
+
"globalSubtitle": "Общие для системы и всех сессий",
|
|
1019
|
+
"presetProvidedDetail": "Глобально отключён; пресеты агента подключают его для сессии",
|
|
1020
|
+
"enabledIn": "Включён в",
|
|
1021
|
+
"viewInPreset": "Посмотреть в группе пресета",
|
|
1022
|
+
"matchesInOtherPresets": "Ещё совпадения в других пресетах ({count}): ",
|
|
1023
|
+
"failedCountLabel": "с ошибкой",
|
|
1024
|
+
"enabledTag": "Включён",
|
|
1025
|
+
"disabledTag": "Отключён",
|
|
1026
|
+
"conditionalTag": "По условию",
|
|
1027
|
+
"presetEnabledTag": "Включён пресетами",
|
|
1028
|
+
"failedTag": "Ошибка",
|
|
1029
|
+
"moduleLabel": "Модуль",
|
|
1030
|
+
"fromPreset": "Из",
|
|
1031
|
+
"condition": "Отключается при",
|
|
1032
|
+
"configuration": "Конфигурация",
|
|
1033
|
+
"runtime": "Статус",
|
|
1034
|
+
"unobserved": "Не запущен",
|
|
1035
|
+
"pending": "Ожидание зависимостей",
|
|
1036
|
+
"loadingPhase": "Загрузка",
|
|
1037
|
+
"active": "Работает",
|
|
1038
|
+
"failed": "Не удалось запустить",
|
|
1039
|
+
"unloading": "Выгрузка"
|
|
1040
|
+
},
|
|
1041
|
+
"settings.plugins": {
|
|
1042
|
+
"nav": "Плагины",
|
|
1043
|
+
"title": "Плагины",
|
|
1044
|
+
"intro": "Настройка и просмотр плагинов, установленных в этом развёртывании.",
|
|
1045
|
+
"tabs": "Разделы плагинов",
|
|
1046
|
+
"configurableTab": "Настройка плагинов",
|
|
1047
|
+
"empty": "В этом развёртывании нет настроек плагинов.",
|
|
1048
|
+
"overridden": "Переопределено",
|
|
1049
|
+
"reset": "Сбросить по умолчанию",
|
|
1050
|
+
"readOnly": "В этом развёртывании настройки доступны только для чтения.",
|
|
1051
|
+
"expand": "Показать настройки",
|
|
1052
|
+
"collapse": "Скрыть настройки",
|
|
1053
|
+
"save": "Сохранить",
|
|
1054
|
+
"saving": "Сохранение…",
|
|
1055
|
+
"discard": "Отбросить",
|
|
1056
|
+
"unsaved": "Не сохранено",
|
|
1057
|
+
"saveFailed": "Развёртывание не приняло эти значения — они оставлены для исправления.",
|
|
1058
|
+
"invalidNumber": "Нужно число; пусто — значение по умолчанию.",
|
|
1059
|
+
"bashTitle": "Командная оболочка",
|
|
1060
|
+
"bashDescription": "Ограничения для всех команд, которые запускает агент.",
|
|
1061
|
+
"bashTimeoutMs": "Таймаут команды (мс)",
|
|
1062
|
+
"bashTimeoutMsHint": "Сколько может выполняться одна команда до принудительного завершения.",
|
|
1063
|
+
"bashMaxOutputBytes": "Предел вывода на поток (байты)",
|
|
1064
|
+
"bashMaxOutputBytesHint": "Вывод сверх предела уходит во временный файл, а не теряется.",
|
|
1065
|
+
"agentLoopTitle": "Цикл агента",
|
|
1066
|
+
"agentLoopDescription": "Как агент распределяет вызовы инструментов.",
|
|
1067
|
+
"agentLoopMaxParallel": "Параллельные вызовы инструментов",
|
|
1068
|
+
"agentLoopMaxParallelHint": "Сколько безопасных для параллели вызовов может идти одновременно в одном шаге.",
|
|
1069
|
+
"webSearchTitle": "Веб-поиск",
|
|
1070
|
+
"webSearchDescription": "Поисковый провайдер DeepSeek.",
|
|
1071
|
+
"webSearchApiKey": "Ключ API",
|
|
1072
|
+
"webSearchApiKeyHint": "Хранится вне файла настроек. Пусто — текущий ключ остаётся.",
|
|
1073
|
+
"webSearchApiKeySet": "Ключ задан.",
|
|
1074
|
+
"webSearchApiKeyUnset": "Ключ не задан — поиск недоступен, пока он не появится.",
|
|
1075
|
+
"webSearchBaseUrl": "Адрес",
|
|
1076
|
+
"webSearchBaseUrlHint": "Пусто — значение провайдера по умолчанию.",
|
|
1077
|
+
"webSearchMaxUses": "Макс. поисков на запрос",
|
|
1078
|
+
"webSearchMaxUsesHint": "Сколько раз один запрос может искать, прежде чем должен ответить.",
|
|
1079
|
+
"subagentModelSelectionTitle": "Подагент",
|
|
1080
|
+
"subagentModelSelectionDescription": "Какие модели агенты могут выбирать для подагентов.",
|
|
1081
|
+
"subagentModelSelectionToggle": "Разрешить агентам выбирать модели для подагентов",
|
|
1082
|
+
"subagentModelSelectionChoose": "Если включено, агент выбирает провайдера, модель и глубину рассуждений для каждого подагента из разрешённых ниже моделей. Действует только для новых сессий.",
|
|
1083
|
+
"subagentModelSelectionAllowed": "Модели, доступные агентам",
|
|
1084
|
+
"subagentModelSelectionLoading": "Загрузка моделей…",
|
|
1085
|
+
"subagentModelSelectionLoadFailed": "Не удалось загрузить модели.",
|
|
1086
|
+
"subagentModelSelectionRetry": "Повторить",
|
|
1087
|
+
"subagentModelSelectionPartial": "Часть провайдеров моделей загрузить не удалось; сохранённый выбор всё ещё можно удалить.",
|
|
1088
|
+
"subagentModelSelectionUnavailable": "Сейчас недоступна",
|
|
1089
|
+
"subagentModelSelectionUnavailableGroup": "Сохранено, но сейчас недоступно",
|
|
1090
|
+
"subagentModelSelectionEmpty": "Ни один провайдер моделей сейчас не предлагает модель.",
|
|
1091
|
+
"subagentModelSelectionRequired": "Перед сохранением нужно выбрать хотя бы одну модель.",
|
|
1092
|
+
"subagentModelSelectionConflict": "Настройки изменены в другом месте. Отбросить черновик и повторить.",
|
|
1093
|
+
"subagentModelSelectionOff": "Подагенты используют настроенные значения по умолчанию или модель родительского агента. Сохранённый выбор моделей остаётся."
|
|
1094
|
+
},
|
|
1095
|
+
"settings.theme": {
|
|
1096
|
+
"appearance.title": "Оформление",
|
|
1097
|
+
"appearance.light": "Светлое",
|
|
1098
|
+
"appearance.dark": "Тёмное",
|
|
1099
|
+
"appearance.system": "Системное",
|
|
1100
|
+
"fontSize.title": "Размер шрифта",
|
|
1101
|
+
"fontSize.description": "Влияет только на содержимое диалога",
|
|
1102
|
+
"fontSize.unit": "px",
|
|
1103
|
+
"fontSize.increase": "Увеличить шрифт",
|
|
1104
|
+
"fontSize.decrease": "Уменьшить шрифт"
|
|
1105
|
+
},
|
|
1106
|
+
"sidebar": {
|
|
1107
|
+
"session.new": "Новая сессия",
|
|
1108
|
+
"session.new.label": "Новая сессия",
|
|
1109
|
+
"toggle.open": "Открыть боковую панель",
|
|
1110
|
+
"toggle.collapse": "Свернуть боковую панель",
|
|
1111
|
+
"panels.label": "Глобальные панели"
|
|
1112
|
+
},
|
|
1113
|
+
"sidebarCodePreview": {
|
|
1114
|
+
"title": "Код",
|
|
1115
|
+
"copy": "Копировать",
|
|
1116
|
+
"copied": "Скопировано"
|
|
1117
|
+
},
|
|
1118
|
+
"sidebarDocumentPreview": {
|
|
1119
|
+
"loading": "Чтение…",
|
|
1120
|
+
"loadMore": "Загрузить ещё",
|
|
1121
|
+
"changed": "Файл изменился, показано прежнее содержимое.",
|
|
1122
|
+
"reloadNow": "Обновить",
|
|
1123
|
+
"reload": "Прочитать файл заново",
|
|
1124
|
+
"wrap.enable": "Включить перенос строк",
|
|
1125
|
+
"wrap.disable": "Выключить перенос строк",
|
|
1126
|
+
"wrap.aria": "Перенос строк",
|
|
1127
|
+
"openWith": "Открыть в",
|
|
1128
|
+
"viewer.text": "Обычный текст",
|
|
1129
|
+
"resourceUnavailable": "Служба файловых ресурсов недоступна.",
|
|
1130
|
+
"rendererUnavailable": "Предпросмотр {name} недоступен.",
|
|
1131
|
+
"error.notFound": "Файл не найден. Возможно, он перемещён или удалён.",
|
|
1132
|
+
"error.tooLarge": "Страница превышает предел {limit} и не может быть прочитана.",
|
|
1133
|
+
"error.notText": "Не текстовый файл, предпросмотр пока недоступен.",
|
|
1134
|
+
"error.notRegularFile": "Не обычный файл, показывать нечего.",
|
|
1135
|
+
"error.unavailable": "Не удалось прочитать: {message}",
|
|
1136
|
+
"retry": "Повторить"
|
|
1137
|
+
},
|
|
1138
|
+
"sidebarFiles": {
|
|
1139
|
+
"type.label": "Файлы",
|
|
1140
|
+
"guide.title": "Файлы рабочей области",
|
|
1141
|
+
"guide.description": "Просмотр файлов в рабочей области этой сессии",
|
|
1142
|
+
"loading": "Чтение…",
|
|
1143
|
+
"empty": "Каталог пуст",
|
|
1144
|
+
"truncated": "Слишком много элементов, показана только часть.",
|
|
1145
|
+
"noWorkspace": "У этой сессии нет каталога рабочей области.",
|
|
1146
|
+
"reload": "Обновить",
|
|
1147
|
+
"entry.other": "Не файл и не каталог — открыть нельзя.",
|
|
1148
|
+
"error.notFound": "Каталог исчез. Возможно, он перемещён или удалён.",
|
|
1149
|
+
"error.outsideWorkspace": "Каталог вне рабочей области — боковая панель его не читает.",
|
|
1150
|
+
"error.notDirectory": "Это не каталог.",
|
|
1151
|
+
"error.unavailable": "Не удалось прочитать: {message}"
|
|
1152
|
+
},
|
|
1153
|
+
"sidebarImage": {
|
|
1154
|
+
"title": "Изображение",
|
|
1155
|
+
"preview": "Предпросмотр изображения: {name}",
|
|
1156
|
+
"loading": "Открытие изображения…",
|
|
1157
|
+
"failed": "Не удалось показать изображение.",
|
|
1158
|
+
"unsupported": "Для предпросмотра изображения нужен файл целиком."
|
|
1159
|
+
},
|
|
1160
|
+
"sidebarPdf": {
|
|
1161
|
+
"title": "PDF",
|
|
1162
|
+
"pageImage": "Страница PDF {page}",
|
|
1163
|
+
"loading": "Открытие PDF…",
|
|
1164
|
+
"rendering": "Отрисовка страницы…",
|
|
1165
|
+
"failed": "Не удалось показать PDF: {message}",
|
|
1166
|
+
"password": "PDF защищён паролем; предпросмотр таких файлов не поддерживается.",
|
|
1167
|
+
"workerFailed": "Отрисовка PDF прервана. Повторить попытку.",
|
|
1168
|
+
"unsupported": "Для предпросмотра PDF нужен файл целиком.",
|
|
1169
|
+
"retry": "Повторить"
|
|
1170
|
+
},
|
|
1171
|
+
"sidebarRight": {
|
|
1172
|
+
"chrome.expand": "Открыть боковую панель",
|
|
1173
|
+
"chrome.expandAria": "Открыть правую боковую панель",
|
|
1174
|
+
"chrome.collapse": "Свернуть боковую панель",
|
|
1175
|
+
"chrome.collapseAria": "Свернуть правую боковую панель",
|
|
1176
|
+
"chrome.toFullscreen": "На весь экран",
|
|
1177
|
+
"chrome.exitFullscreen": "Выйти из полного экрана",
|
|
1178
|
+
"dock.emptyPane": "Пустая область",
|
|
1179
|
+
"dock.splitPane": "Разделить",
|
|
1180
|
+
"dock.splitPaneDisabled": "Максимум две области",
|
|
1181
|
+
"dock.splitPaneNarrow": "Не хватает ширины для разделения — расширить боковую панель",
|
|
1182
|
+
"dock.closeTab": "Закрыть",
|
|
1183
|
+
"dock.addTab": "Новая вкладка",
|
|
1184
|
+
"dock.dockFloat": "Вернуть в боковую панель",
|
|
1185
|
+
"dock.closeFloat": "Закрыть",
|
|
1186
|
+
"dock.drop.center": "Перенести сюда",
|
|
1187
|
+
"dock.drop.left": "Разделить слева",
|
|
1188
|
+
"dock.drop.right": "Разделить справа",
|
|
1189
|
+
"dock.drop.top": "Разделить сверху",
|
|
1190
|
+
"dock.drop.bottom": "Разделить снизу",
|
|
1191
|
+
"tab.guide.title": "Начало",
|
|
1192
|
+
"tab.unavailable": "Такое содержимое пока нечем просмотреть."
|
|
1193
|
+
},
|
|
1194
|
+
"skill": {
|
|
1195
|
+
"row.title": "Навык",
|
|
1196
|
+
"row.running": "Загрузка навыка",
|
|
1197
|
+
"row.failed": "Не удалось загрузить навык",
|
|
1198
|
+
"row.stopped": "Загрузка навыка остановлена",
|
|
1199
|
+
"row.instructions": "Инструкции",
|
|
1200
|
+
"row.inspect": "Подробнее",
|
|
1201
|
+
"menu.userOnly": "только пользователем"
|
|
1202
|
+
},
|
|
1203
|
+
"slash.menu": {
|
|
1204
|
+
"command": "Команды",
|
|
1205
|
+
"skill": "Навыки",
|
|
1206
|
+
"subagent": "Подагенты",
|
|
1207
|
+
"loading": "Загрузка…",
|
|
1208
|
+
"drill.aria": "Открыть папку",
|
|
1209
|
+
"drill.hint": "Открыть папку",
|
|
1210
|
+
"drill.key": "Tab",
|
|
1211
|
+
"crumbs.aria": "Навигация по папкам",
|
|
1212
|
+
"suggestions.aria": "Подсказки ввода"
|
|
1213
|
+
},
|
|
1214
|
+
"subagent": {
|
|
1215
|
+
"diagnostic.corrupt": "повреждённая запись сессии",
|
|
1216
|
+
"diagnostic.unsupported": "неподдерживаемая версия записи подагента",
|
|
1217
|
+
"diagnostic.unavailable": "запись сессии временно недоступна",
|
|
1218
|
+
"duration.seconds": "{seconds} с",
|
|
1219
|
+
"duration.minutes": "{minutes} мин {seconds} с",
|
|
1220
|
+
"duration.hours": "{hours} ч {minutes} мин {seconds} с",
|
|
1221
|
+
"duration.days": "{days} дн",
|
|
1222
|
+
"duration.daysHours": "{days} дн {hours} ч",
|
|
1223
|
+
"duration.months": "~{months} мес",
|
|
1224
|
+
"duration.monthsDays": "~{months} мес {days} дн",
|
|
1225
|
+
"duration.years": "~{years} г",
|
|
1226
|
+
"duration.yearsMonths": "~{years} г {months} мес",
|
|
1227
|
+
"duration.exactDays": "{days} дн {hours} ч {minutes} мин {seconds} с",
|
|
1228
|
+
"duration.exactTitle": "Общее время работы: {duration}",
|
|
1229
|
+
"tokens.thousand": "{value} тыс.",
|
|
1230
|
+
"tokens.million": "{value} млн",
|
|
1231
|
+
"tokens.total": "{value} tok",
|
|
1232
|
+
"loading.label": "Загрузка подагентов…",
|
|
1233
|
+
"loading.aria": "Загрузка подагентов",
|
|
1234
|
+
"load.error": "Не удалось загрузить подагентов",
|
|
1235
|
+
"retry": "Повторить",
|
|
1236
|
+
"mode.oneShot": "разовый",
|
|
1237
|
+
"mode.continuable": "продолжаемый",
|
|
1238
|
+
"activity.running": "работает",
|
|
1239
|
+
"activity.inactive": "не работает",
|
|
1240
|
+
"branch.collapse": "Свернуть потомков {label}",
|
|
1241
|
+
"branch.expand": "Развернуть потомков {label}",
|
|
1242
|
+
"count.total.one": "{count} подагент",
|
|
1243
|
+
"count.total.other": "{count} подагентов",
|
|
1244
|
+
"count.running.one": "{count} подагент работает",
|
|
1245
|
+
"count.running.other": "{count} подагентов работает",
|
|
1246
|
+
"switcher.aria": "Переключить подагента: {title}",
|
|
1247
|
+
"tree.aria": "Сессии подагентов",
|
|
1248
|
+
"readonly.oneShot.title": "Запись разового подагента",
|
|
1249
|
+
"readonly.title": "Сейчас этот подагент доступен только для чтения",
|
|
1250
|
+
"readonly.oneShot.body": "Разовые задачи не принимают уточнений; здесь доступна полная запись выполнения.",
|
|
1251
|
+
"readonly.body": "Родительская сессия не активна — открыть её заново, чтобы продолжить отправку сообщений."
|
|
1252
|
+
},
|
|
1253
|
+
"trajectory": {
|
|
1254
|
+
"view.trajectory": "Траектория",
|
|
1255
|
+
"toolbar.aria": "Панель траектории",
|
|
1256
|
+
"toolbar.duration": "Длительность",
|
|
1257
|
+
"toolbar.useActualDuration": "Реальная длительность",
|
|
1258
|
+
"toolbar.useEqualWidth": "Равная ширина операций",
|
|
1259
|
+
"toolbar.actualTime": "Реальное время",
|
|
1260
|
+
"toolbar.turns": "Ходы",
|
|
1261
|
+
"toolbar.expandTurns": "Развернуть ходы",
|
|
1262
|
+
"toolbar.collapseTurns": "Свернуть ходы",
|
|
1263
|
+
"toolbar.calls": "Вызовы",
|
|
1264
|
+
"toolbar.expandCalls": "Развернуть вызовы",
|
|
1265
|
+
"toolbar.collapseCalls": "Свернуть вызовы",
|
|
1266
|
+
"toolbar.search": "Поиск по траектории",
|
|
1267
|
+
"toolbar.searchPlaceholder": "Поиск",
|
|
1268
|
+
"kind.system": "СИСТЕМА",
|
|
1269
|
+
"kind.user": "ЧЕЛОВЕК",
|
|
1270
|
+
"kind.context": "КОНТЕКСТ",
|
|
1271
|
+
"kind.compacted": "СЖАТО",
|
|
1272
|
+
"kind.message": "Сообщение",
|
|
1273
|
+
"kind.assistant": "АССИСТЕНТ",
|
|
1274
|
+
"kind.tool": "ВЫЗОВ",
|
|
1275
|
+
"kind.subtool": "ПОДВЫЗОВ",
|
|
1276
|
+
"kind.sub": "Под",
|
|
1277
|
+
"column.input": "Вход",
|
|
1278
|
+
"column.output": "Вывод",
|
|
1279
|
+
"column.think": "Мысли",
|
|
1280
|
+
"column.time": "Время",
|
|
1281
|
+
"column.model": "Модель",
|
|
1282
|
+
"column.tools": "Инструменты",
|
|
1283
|
+
"turn.label": "Ход {turn}",
|
|
1284
|
+
"section.betweenTurns": "Между ходами",
|
|
1285
|
+
"group.message": "Сообщение",
|
|
1286
|
+
"group.step": "Шаг {step}",
|
|
1287
|
+
"group.compaction": "Сжатие контекста {seq}",
|
|
1288
|
+
"status.failed": "Ошибка",
|
|
1289
|
+
"status.pending": "Ожидание",
|
|
1290
|
+
"status.completed": "Завершено",
|
|
1291
|
+
"timing.notAvailable": "Недоступно",
|
|
1292
|
+
"timing.notRecorded": "Не записано",
|
|
1293
|
+
"timing.stepStartUnavailable": "Начало шага недоступно",
|
|
1294
|
+
"timing.firstTokenUnavailable": "Первый токен недоступен",
|
|
1295
|
+
"timing.usageUnavailable": "Расход недоступен",
|
|
1296
|
+
"timing.outputTokensUnavailable": "Выходные токены недоступны",
|
|
1297
|
+
"timing.durationTooShort": "Слишком малая длительность",
|
|
1298
|
+
"timing.showLocalTime": "Показать местное время",
|
|
1299
|
+
"timing.showUnixTimestamp": "Показать метку времени Unix",
|
|
1300
|
+
"timing.started": "Начало",
|
|
1301
|
+
"timing.totalDuration": "Общая длительность",
|
|
1302
|
+
"timing.ttft": "TTFT",
|
|
1303
|
+
"timing.generation": "Генерация",
|
|
1304
|
+
"timing.throughput": "Скорость",
|
|
1305
|
+
"timing.duration": "Длительность",
|
|
1306
|
+
"timing.source": "Источник времени",
|
|
1307
|
+
"timing.sessionTimestamps": "Метки времени сессии",
|
|
1308
|
+
"timing.sessionTimestampsRunning": "Метки времени сессии (выполняется)",
|
|
1309
|
+
"timing.request": "Время запроса",
|
|
1310
|
+
"unit.milliseconds": "{value} мс",
|
|
1311
|
+
"unit.seconds": "{value} с",
|
|
1312
|
+
"unit.tokens": "{value} tok",
|
|
1313
|
+
"unit.tokensPerSecond": "{value} tok/s",
|
|
1314
|
+
"usage.tokens": "Токены",
|
|
1315
|
+
"usage.reasoning": "Рассуждения",
|
|
1316
|
+
"usage.content": "Содержимое",
|
|
1317
|
+
"usage.notReported": "Расход не указан",
|
|
1318
|
+
"usage.input": "Вход",
|
|
1319
|
+
"usage.cached": "Из кеша",
|
|
1320
|
+
"usage.cacheCreated": "Создание кеша",
|
|
1321
|
+
"usage.other": "Прочее",
|
|
1322
|
+
"usage.output": "Вывод",
|
|
1323
|
+
"usage.thisRequest": "Текущий запрос",
|
|
1324
|
+
"usage.sessionCumulative": "Всего за сессию",
|
|
1325
|
+
"options.notRecorded": "Параметры не записаны",
|
|
1326
|
+
"options.json": "JSON параметров запроса",
|
|
1327
|
+
"source.unknown": "Неизвестно",
|
|
1328
|
+
"source.user": "Пользователь",
|
|
1329
|
+
"source.plugin": "Плагин",
|
|
1330
|
+
"source.pluginNamed": "Плагин · {plugin}",
|
|
1331
|
+
"source.goal": "Цель",
|
|
1332
|
+
"source.goalRound": "Цель · Раунд {round}",
|
|
1333
|
+
"source.notRecorded": "Источник не записан",
|
|
1334
|
+
"source.messageJson": "JSON источника сообщения",
|
|
1335
|
+
"tab.summary": "Сводка",
|
|
1336
|
+
"tab.rawOutput": "Исходный вывод",
|
|
1337
|
+
"tab.preview": "Предпросмотр",
|
|
1338
|
+
"tab.raw": "Исходник",
|
|
1339
|
+
"tab.source": "Источник",
|
|
1340
|
+
"tab.payload": "Данные",
|
|
1341
|
+
"tab.result": "Результат",
|
|
1342
|
+
"tab.schema": "Схема",
|
|
1343
|
+
"tab.timing": "Время",
|
|
1344
|
+
"tab.diff": "Различия",
|
|
1345
|
+
"tab.systemPrompt": "Системный запрос",
|
|
1346
|
+
"tab.tools": "Инструменты",
|
|
1347
|
+
"tab.options": "Параметры",
|
|
1348
|
+
"tab.usage": "Расход",
|
|
1349
|
+
"record.toolCallOnly": "(только вызов инструмента)",
|
|
1350
|
+
"record.noContent": "Нет содержимого",
|
|
1351
|
+
"record.noPayload": "Данные не записаны",
|
|
1352
|
+
"record.noResult": "Результат не записан",
|
|
1353
|
+
"record.noOutput": "Нет вывода",
|
|
1354
|
+
"record.schemaUnavailable": "Схема недоступна",
|
|
1355
|
+
"record.parameters": "Параметры",
|
|
1356
|
+
"record.resultJson": "JSON результата",
|
|
1357
|
+
"record.json": "JSON",
|
|
1358
|
+
"record.parametersJson": "JSON параметров",
|
|
1359
|
+
"record.namedParametersJson": "JSON параметров {name}",
|
|
1360
|
+
"record.payloadJson": "JSON данных",
|
|
1361
|
+
"record.outputJson": "JSON результата",
|
|
1362
|
+
"record.thinking": "Размышления",
|
|
1363
|
+
"record.systemPromptMissing": "Системный запрос отсутствует",
|
|
1364
|
+
"record.toolsMissing": "Инструменты отсутствуют",
|
|
1365
|
+
"record.systemPrompt": "Системный запрос",
|
|
1366
|
+
"record.tools": "Инструменты",
|
|
1367
|
+
"block.openSummary": "Открыть сводку вызова инструмента блока #{index}",
|
|
1368
|
+
"block.openSummaryTitle": "Открыть сводку вызова инструмента",
|
|
1369
|
+
"block.label": "Блок #{index} {type}",
|
|
1370
|
+
"history.loadingTrajectory": "Загрузка траектории…",
|
|
1371
|
+
"history.loadingEarlier": "Загрузка ранней истории…",
|
|
1372
|
+
"history.loadingEarlierAria": "Загрузка ранней истории…",
|
|
1373
|
+
"history.loadEarlier": "Загрузить раннюю историю",
|
|
1374
|
+
"history.clickToLoadEarlier": "Нажатие — загрузка ранней истории",
|
|
1375
|
+
"request.label": "Запрос #{request}",
|
|
1376
|
+
"request.labelCompaction": "Запрос #{request} · Сжатие контекста",
|
|
1377
|
+
"request.compaction": "Сжатие контекста · {section}",
|
|
1378
|
+
"request.compactionPurpose": "Сжатие контекста",
|
|
1379
|
+
"request.retryProgress": "{retry} из {maximum}",
|
|
1380
|
+
"request.collapsedSummary": "Свёрнутая сводка {kind}, {summary}",
|
|
1381
|
+
"request.collapsedTurn": "хода",
|
|
1382
|
+
"request.collapsedAssistant": "ассистента",
|
|
1383
|
+
"request.rowAria": "{request}{kind}, {content}",
|
|
1384
|
+
"request.rowPrefix": "Запрос {request}, ",
|
|
1385
|
+
"request.rowAriaCompaction": "Запрос {request}, сжатие контекста",
|
|
1386
|
+
"request.noContent": "нет содержимого",
|
|
1387
|
+
"summary.toolCalls.one": "{count} вызов инструмента",
|
|
1388
|
+
"summary.toolCalls.other": "{count} вызовов инструментов",
|
|
1389
|
+
"summary.steps.one": "{count} шаг",
|
|
1390
|
+
"summary.steps.other": "{count} шагов",
|
|
1391
|
+
"details.event": "Детали события",
|
|
1392
|
+
"details.resize": "Изменить размер деталей события",
|
|
1393
|
+
"details.resizeTitle": "Перетаскивание — изменение размера. Двойной клик — сброс.",
|
|
1394
|
+
"details.close": "Закрыть детали",
|
|
1395
|
+
"details.status": "Статус",
|
|
1396
|
+
"details.purpose": "Назначение",
|
|
1397
|
+
"details.provider": "Провайдер",
|
|
1398
|
+
"details.model": "Модель",
|
|
1399
|
+
"details.toolCalls": "Вызовы инструментов",
|
|
1400
|
+
"details.subtoolCalls": "Вызовы подинструментов",
|
|
1401
|
+
"details.error": "Ошибка",
|
|
1402
|
+
"details.failure.auth": "Неверный API-ключ",
|
|
1403
|
+
"details.retry": "Повтор",
|
|
1404
|
+
"details.scheduled": "Запланировано",
|
|
1405
|
+
"details.retryDelay": "Задержка повтора",
|
|
1406
|
+
"details.result": "Результат",
|
|
1407
|
+
"details.compacted": "Сжато",
|
|
1408
|
+
"details.assistantMessage": "Сообщение ассистента",
|
|
1409
|
+
"details.source": "Источник",
|
|
1410
|
+
"details.hierarchy": "Иерархия",
|
|
1411
|
+
"details.toolCall": "Вызов инструмента",
|
|
1412
|
+
"timeline.aria": "Шкала времени траектории",
|
|
1413
|
+
"timeline.overviewAria": "Обзор шкалы времени; перетаскивание по горизонтали фокусирует события",
|
|
1414
|
+
"timeline.noTimingData": "Нет данных о времени",
|
|
1415
|
+
"timeline.total": "Всего {duration}",
|
|
1416
|
+
"timeline.started": "Начало {time}",
|
|
1417
|
+
"timeline.ttftDecoding": "TTFT {ttft} · Декодирование {decoding}",
|
|
1418
|
+
"layout.compacting": "Сжатие контекста…",
|
|
1419
|
+
"layout.compactionFailed": "Сжатие контекста не удалось",
|
|
1420
|
+
"layout.compacted": "Контекст сжат",
|
|
1421
|
+
"layout.toolCallOnly": "Только вызов инструмента",
|
|
1422
|
+
"layout.imageOnly": "Изображения ×{count}",
|
|
1423
|
+
"layout.fileAttachments": "Файлы ×{count}",
|
|
1424
|
+
"layout.initialSystemPrompt": "Начальный системный запрос",
|
|
1425
|
+
"layout.systemPromptUpdated": "Системный запрос обновлён",
|
|
1426
|
+
"layout.toolsUpdated": "Инструменты обновлены",
|
|
1427
|
+
"layout.systemPromptAndToolsUpdated": "Системный запрос и инструменты обновлены",
|
|
1428
|
+
"layout.compactionInterrupted": "Сжатие контекста прервано до завершения."
|
|
1429
|
+
},
|
|
1430
|
+
"workflowRun": {
|
|
1431
|
+
"run.title": "{name}",
|
|
1432
|
+
"run.members.one": "{count} участник",
|
|
1433
|
+
"run.members.other": "{count} участников",
|
|
1434
|
+
"run.empty": "Участники не запускались",
|
|
1435
|
+
"phase.unassigned": "Без фазы",
|
|
1436
|
+
"phase.empty": "Пустое название фазы",
|
|
1437
|
+
"statusCount.running": "Выполняется: {count}",
|
|
1438
|
+
"statusCount.completed": "Завершено: {count}",
|
|
1439
|
+
"statusCount.failed": "Ошибок: {count}",
|
|
1440
|
+
"statusCount.cancelled": "Отменено: {count}",
|
|
1441
|
+
"statusCount.interrupted": "Прервано: {count}",
|
|
1442
|
+
"member.empty": "Пустое имя участника",
|
|
1443
|
+
"member.open": "Открыть {name}",
|
|
1444
|
+
"status.running": "Выполняется",
|
|
1445
|
+
"status.completed": "Завершено",
|
|
1446
|
+
"status.failed": "Ошибка",
|
|
1447
|
+
"status.cancelled": "Отменено",
|
|
1448
|
+
"status.interrupted": "Прервано"
|
|
1449
|
+
},
|
|
1450
|
+
"workspace": {
|
|
1451
|
+
"group.ungrouped": "Без группы",
|
|
1452
|
+
"session.new": "Новая сессия",
|
|
1453
|
+
"section.workspaces": "Рабочие области",
|
|
1454
|
+
"section.sessions": "Сессии",
|
|
1455
|
+
"viewOptions.label": "Параметры вида",
|
|
1456
|
+
"groupBy.label": "Группировка",
|
|
1457
|
+
"groupBy.workspace": "По рабочим областям",
|
|
1458
|
+
"groupBy.flat": "Одним списком",
|
|
1459
|
+
"orderBy.label": "Сортировка",
|
|
1460
|
+
"orderBy.manual": "Вручную",
|
|
1461
|
+
"orderBy.updated": "По обновлению",
|
|
1462
|
+
"sessions.expand": "Ещё {n} сессий",
|
|
1463
|
+
"sessions.collapse": "Свернуть",
|
|
1464
|
+
"empty.none": "Сессий пока нет",
|
|
1465
|
+
"empty.noMatches": "Ничего не найдено",
|
|
1466
|
+
"workspace.add": "Добавить рабочую область",
|
|
1467
|
+
"search.sessions.aria": "Поиск по сессиям",
|
|
1468
|
+
"search.placeholder": "Поиск по сессиям…",
|
|
1469
|
+
"search.clear": "Очистить поиск",
|
|
1470
|
+
"search.results.aria": "Результаты поиска",
|
|
1471
|
+
"search.pending": "Поиск по истории сессий…",
|
|
1472
|
+
"search.unavailable": "Поиск по содержимому временно недоступен. Показаны совпадения по названию.",
|
|
1473
|
+
"search.noMatches": "Подходящих сессий нет",
|
|
1474
|
+
"search.hasMore": "Показаны первые {n} результатов — уточнить запрос.",
|
|
1475
|
+
"menu.addWorkspace": "Добавить рабочую область…",
|
|
1476
|
+
"picker.loading": "Загрузка рабочих областей…",
|
|
1477
|
+
"conflict.named": "Рабочая область «{name}» уже существует.",
|
|
1478
|
+
"folderError.title": "Не удалось открыть папку",
|
|
1479
|
+
"folderError.retry": "Выбрать снова",
|
|
1480
|
+
"rename": "Переименовать",
|
|
1481
|
+
"rename.workspace.title": "Переименовать рабочую область",
|
|
1482
|
+
"rename.session.title": "Переименовать сессию",
|
|
1483
|
+
"field.workspaceName": "Название рабочей области",
|
|
1484
|
+
"field.sessionName": "Название сессии",
|
|
1485
|
+
"delete.workspace": "Удалить рабочую область",
|
|
1486
|
+
"delete.desc": "«{name}» будет убрана из списка рабочих областей. Папка и журналы сессий сохранятся. Её сессии перейдут в раздел «Без группы».",
|
|
1487
|
+
"delete.pending": "Удаление рабочей области…",
|
|
1488
|
+
"menu.fork": "Ответвить сессию",
|
|
1489
|
+
"menu.archiveSession": "Архивировать сессию",
|
|
1490
|
+
"sessions.count.one": "{n} сессия",
|
|
1491
|
+
"sessions.count.other": "{n} сессий",
|
|
1492
|
+
"actions.workspace.aria": "Действия с рабочей областью {name}",
|
|
1493
|
+
"actions.session.aria": "Действия с сессией {name}",
|
|
1494
|
+
"actions.newSession.aria": "Новая сессия в {name}",
|
|
1495
|
+
"status.running": "Выполняется",
|
|
1496
|
+
"status.subagentsRunning.one": "{n} подагент работает",
|
|
1497
|
+
"status.subagentsRunning.other": "{n} подагентов работает",
|
|
1498
|
+
"status.idle": "Простой",
|
|
1499
|
+
"status.waitingApproval": "Ожидание подтверждения",
|
|
1500
|
+
"status.planReview": "План на проверке",
|
|
1501
|
+
"status.waitingAnswer": "Ожидание ответа",
|
|
1502
|
+
"status.completed": "Завершена",
|
|
1503
|
+
"schedule.active": "Есть активная запланированная задача",
|
|
1504
|
+
"hover.created": "Создана {time}",
|
|
1505
|
+
"hover.copied": "Скопировано",
|
|
1506
|
+
"date.ymd": "{d}.{m}.{y}",
|
|
1507
|
+
"time.now": "сейчас",
|
|
1508
|
+
"time.minutes": "{n} мин",
|
|
1509
|
+
"time.hours": "{n} ч",
|
|
1510
|
+
"time.days": "{n} дн",
|
|
1511
|
+
"time.months": "{n} мес",
|
|
1512
|
+
"time.years": "{n} г",
|
|
1513
|
+
"time.ago": "{t} назад"
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
// ── RU-PACK:END ──
|
|
1517
|
+
|
|
1518
|
+
const RU_DICT = {
|
|
1519
|
+
'intensity.title': 'Атмосфера Obsidian / Ion',
|
|
1520
|
+
'intensity.hint': 'Насыщенность живого свечения поверх интерфейса',
|
|
1521
|
+
'intensity.off': 'Выкл',
|
|
1522
|
+
'intensity.quiet': 'Тихо',
|
|
1523
|
+
'intensity.normal': 'Обычно',
|
|
1524
|
+
'intensity.vivid': 'Ярко',
|
|
1525
|
+
'accent.title': 'Акцент Obsidian / Ion',
|
|
1526
|
+
'accent.hint': 'Ведущий тон интерфейса и свечения',
|
|
1527
|
+
'update.title': 'Обновления',
|
|
1528
|
+
'update.check': 'Проверить',
|
|
1529
|
+
'update.apply': 'Обновить',
|
|
1530
|
+
'update.working': 'Работаю…',
|
|
1531
|
+
'update.checking': 'Проверяю…',
|
|
1532
|
+
'update.upToDate': 'Установлена последняя версия',
|
|
1533
|
+
'update.available': 'Доступна новая версия ({count} коммитов)',
|
|
1534
|
+
'update.installed': 'Установлено {commit}, проверено на dsh {dsh}',
|
|
1535
|
+
'update.doneReload': 'Обновлено. Обновите страницу.',
|
|
1536
|
+
'update.doneRestart': 'Обновлено. Перезапустите приложение.',
|
|
1537
|
+
'update.failed': 'Обновить не удалось',
|
|
1538
|
+
'update.dshMismatch': 'Внимание: установлен dsh {actual}, а эта версия проверялась на {expected}',
|
|
1539
|
+
'update.ahead': 'Установленная версия на {count} коммитов новее выпущенной',
|
|
1540
|
+
'update.unknownHistory': 'Установленной версии нет в истории — переустановите',
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// ── Стили ────────────────────────────────────────────────────────────
|
|
1544
|
+
//
|
|
1545
|
+
// Про режимы наложения. Контейнер слота shell.overlay объявлен как
|
|
1546
|
+
// `z-index:20`, то есть создаёт изолированный контекст наложения.
|
|
1547
|
+
// `mix-blend-mode` внутри него смешивается не с фоном приложения, а с
|
|
1548
|
+
// прозрачным фоном самого контейнера — то есть не работает. Поэтому
|
|
1549
|
+
// здесь его нет вовсе, а разница между темами берётся оттуда, откуда её
|
|
1550
|
+
// и следует брать: из атрибута `data-ds-dark-theme` на body. Его ставит
|
|
1551
|
+
// презентер по полю `colorScheme` активной темы — это объявленный
|
|
1552
|
+
// контракт, а не внутреннее имя класса.
|
|
1553
|
+
//
|
|
1554
|
+
// Каждая непрозрачность умножается на `--dsx-intensity`, поэтому одна
|
|
1555
|
+
// переменная управляет всей атмосферой, включая полное выключение.
|
|
1556
|
+
const GRAIN =
|
|
1557
|
+
'data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27220%27 height=%27220%27%3E' +
|
|
1558
|
+
'%3Cfilter id=%27g%27%3E' +
|
|
1559
|
+
'%3CfeTurbulence type=%27fractalNoise%27 baseFrequency=%270.9%27 numOctaves=%273%27 stitchTiles=%27stitch%27/%3E' +
|
|
1560
|
+
'%3CfeColorMatrix type=%27saturate%27 values=%270%27/%3E' +
|
|
1561
|
+
'%3C/filter%3E' +
|
|
1562
|
+
'%3Crect width=%27220%27 height=%27220%27 filter=%27url(%23g)%27/%3E%3C/svg%3E'
|
|
1563
|
+
|
|
1564
|
+
const css = [
|
|
1565
|
+
// --dsx-activity ведёт живой отклик на работу агента: 0 в покое, 1 пока
|
|
1566
|
+
// идёт ход. Значение ставит невидимый драйвер из сессионного слота,
|
|
1567
|
+
// а читают его слои в корневом shell.overlay — переменная на
|
|
1568
|
+
// documentElement единственный способ связать два разных поддерева.
|
|
1569
|
+
':root{' + INTENSITY_VAR + ':1;' + ACTIVITY_VAR + ':0;' + ACCENT_VAR + ':90,217,245;' + ACCENT2_VAR + ':138,108,255;}',
|
|
1570
|
+
|
|
1571
|
+
// `display:contents` критичен: контейнер слота shell.overlay возвращает
|
|
1572
|
+
// прямым детям `pointer-events:auto`. Обёртка без собственного бокса
|
|
1573
|
+
// не перехватывает клики, а каждый слой ниже гасит события явно.
|
|
1574
|
+
'.dsx-root{display:contents;}',
|
|
1575
|
+
|
|
1576
|
+
'.dsx-ambient{position:fixed;inset:0;overflow:hidden;pointer-events:none;contain:layout paint style;}',
|
|
1577
|
+
'.dsx-ambient__orb{position:absolute;border-radius:50%;filter:blur(100px);will-change:transform;}',
|
|
1578
|
+
|
|
1579
|
+
// ── светлая тема: присутствие есть, доминирования нет ──
|
|
1580
|
+
'.dsx-ambient__orb--lead{width:46vw;height:46vw;left:-15vw;top:-14vw;opacity:calc(.13 * var(' + INTENSITY_VAR + '));background:radial-gradient(circle,rgba(var(' + ACCENT_VAR + '),.88) 0%,rgba(var(' + ACCENT_VAR + '),0) 70%);animation:dsx-drift-a 34s ease-in-out infinite;}',
|
|
1581
|
+
'.dsx-ambient__orb--counter{width:54vw;height:54vw;right:-20vw;bottom:-24vw;opacity:calc(.12 * var(' + INTENSITY_VAR + '));background:radial-gradient(circle,rgba(var(' + ACCENT2_VAR + '),.84) 0%,rgba(var(' + ACCENT2_VAR + '),0) 70%);animation:dsx-drift-b 46s ease-in-out infinite;}',
|
|
1582
|
+
'.dsx-ambient__orb--deep{width:36vw;height:36vw;right:8vw;top:-18vw;opacity:calc(.10 * var(' + INTENSITY_VAR + '));background:radial-gradient(circle,rgba(56,120,255,.78) 0%,rgba(56,120,255,0) 70%);animation:dsx-drift-c 54s ease-in-out infinite;}',
|
|
1583
|
+
'.dsx-ambient__orb--rose{width:30vw;height:30vw;left:18vw;bottom:-18vw;opacity:calc(.08 * var(' + INTENSITY_VAR + '));background:radial-gradient(circle,rgba(255,110,180,.70) 0%,rgba(255,110,180,0) 70%);animation:dsx-drift-d 62s ease-in-out infinite;}',
|
|
1584
|
+
|
|
1585
|
+
// ── тёмная тема: полная интенсивность ──
|
|
1586
|
+
'body[data-ds-dark-theme] .dsx-ambient__orb--lead{opacity:calc(.48 * var(' + INTENSITY_VAR + '));}',
|
|
1587
|
+
'body[data-ds-dark-theme] .dsx-ambient__orb--counter{opacity:calc(.44 * var(' + INTENSITY_VAR + '));}',
|
|
1588
|
+
'body[data-ds-dark-theme] .dsx-ambient__orb--deep{opacity:calc(.34 * var(' + INTENSITY_VAR + '));}',
|
|
1589
|
+
'body[data-ds-dark-theme] .dsx-ambient__orb--rose{opacity:calc(.26 * var(' + INTENSITY_VAR + '));}',
|
|
1590
|
+
|
|
1591
|
+
'.dsx-ambient__sweep{position:absolute;top:0;bottom:0;left:-45%;width:42%;filter:blur(34px);opacity:calc(.35 * var(' + INTENSITY_VAR + '));background:linear-gradient(100deg,rgba(255,255,255,0) 0%,rgba(140,200,255,.055) 42%,rgba(200,160,255,.075) 58%,rgba(255,255,255,0) 100%);animation:dsx-sweep 28s cubic-bezier(.45,0,.25,1) infinite;}',
|
|
1592
|
+
'body[data-ds-dark-theme] .dsx-ambient__sweep{opacity:calc(1 * var(' + INTENSITY_VAR + '));}',
|
|
1593
|
+
|
|
1594
|
+
// Верхняя кромка: постоянная тусклая линия плюс бегущий по ней блик.
|
|
1595
|
+
'.dsx-ambient__seam{position:absolute;top:0;left:0;right:0;height:1px;overflow:hidden;opacity:calc(1 * var(' + INTENSITY_VAR + '));background:linear-gradient(90deg,rgba(120,160,220,0) 0%,rgba(120,160,220,.14) 20%,rgba(120,160,220,.14) 80%,rgba(120,160,220,0) 100%);}',
|
|
1596
|
+
'.dsx-ambient__seam::after{content:"";position:absolute;top:0;left:-30%;width:30%;height:100%;opacity:.45;background:linear-gradient(90deg,rgba(var(' + ACCENT_VAR + '),0) 0%,rgba(var(' + ACCENT_VAR + '),.9) 45%,rgba(var(' + ACCENT2_VAR + '),.9) 55%,rgba(var(' + ACCENT2_VAR + '),0) 100%);animation:dsx-seam-travel 14s cubic-bezier(.5,0,.5,1) infinite;}',
|
|
1597
|
+
'body[data-ds-dark-theme] .dsx-ambient__seam::after{opacity:1;}',
|
|
1598
|
+
|
|
1599
|
+
'.dsx-grain{position:fixed;inset:-60px;pointer-events:none;opacity:calc(.030 * var(' + INTENSITY_VAR + '));background-image:url("' + GRAIN + '");background-size:220px 220px;will-change:transform;animation:dsx-grain 1.2s steps(5) infinite;}',
|
|
1600
|
+
'body[data-ds-dark-theme] .dsx-grain{opacity:calc(.045 * var(' + INTENSITY_VAR + '));}',
|
|
1601
|
+
|
|
1602
|
+
'.dsx-vignette{position:fixed;inset:0;pointer-events:none;opacity:calc(1 * var(' + INTENSITY_VAR + '));background:radial-gradient(125% 95% at 50% 42%,rgba(0,0,0,0) 55%,rgba(20,40,80,.05) 100%);}',
|
|
1603
|
+
'body[data-ds-dark-theme] .dsx-vignette{background:radial-gradient(125% 95% at 50% 42%,rgba(0,0,0,0) 52%,rgba(2,4,10,.30) 100%);}',
|
|
1604
|
+
|
|
1605
|
+
// ── отклик на работу агента ──
|
|
1606
|
+
// Слой целиком гаснет в покое, поэтому в простое он не стоит ни кадра
|
|
1607
|
+
// композитинга. Появление и уход — через transition, а не анимацию,
|
|
1608
|
+
// чтобы переход был плавным в обе стороны.
|
|
1609
|
+
'.dsx-pulse{position:fixed;inset:0;overflow:hidden;pointer-events:none;opacity:calc(var(' + ACTIVITY_VAR + ') * var(' + INTENSITY_VAR + '));transition:opacity .55s ease;contain:layout paint style;}',
|
|
1610
|
+
'.dsx-pulse__beam{position:absolute;top:0;left:0;right:0;height:2px;overflow:hidden;background:linear-gradient(90deg,rgba(var(' + ACCENT_VAR + '),0) 0%,rgba(var(' + ACCENT_VAR + '),.45) 35%,rgba(var(' + ACCENT2_VAR + '),.45) 65%,rgba(var(' + ACCENT2_VAR + '),0) 100%);}',
|
|
1611
|
+
'.dsx-pulse__beam::after{content:"";position:absolute;top:0;bottom:0;left:-25%;width:25%;background:linear-gradient(90deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.95) 50%,rgba(255,255,255,0) 100%);animation:dsx-beam 1.6s linear infinite;}',
|
|
1612
|
+
'.dsx-pulse__breath{position:absolute;left:50%;bottom:-24vh;width:72vw;height:46vh;margin-left:-36vw;border-radius:50%;filter:blur(90px);background:radial-gradient(ellipse at center,rgba(var(' + ACCENT_VAR + '),.45) 0%,rgba(var(' + ACCENT_VAR + '),0) 70%);animation:dsx-breath 2.8s ease-in-out infinite;}',
|
|
1613
|
+
|
|
1614
|
+
// Периоды дрейфа взаимно непропорциональны, поэтому световой рисунок
|
|
1615
|
+
// не повторяется: 34 / 46 / 54 / 62 секунды.
|
|
1616
|
+
'@keyframes dsx-drift-a{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(7vw,5vh,0) scale(1.14)}}',
|
|
1617
|
+
'@keyframes dsx-drift-b{0%,100%{transform:translate3d(0,0,0) scale(1.05)}50%{transform:translate3d(-6vw,-4vh,0) scale(.92)}}',
|
|
1618
|
+
'@keyframes dsx-drift-c{0%,100%{transform:translate3d(0,0,0) scale(.95)}50%{transform:translate3d(-5vw,6vh,0) scale(1.10)}}',
|
|
1619
|
+
'@keyframes dsx-drift-d{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(9vw,-7vh,0) scale(1.18)}}',
|
|
1620
|
+
'@keyframes dsx-sweep{0%{transform:translateX(0)}55%,100%{transform:translateX(340%)}}',
|
|
1621
|
+
// -30% + 433% от собственной ширины (30% кадра) = ровно правый край.
|
|
1622
|
+
'@keyframes dsx-seam-travel{0%{transform:translateX(0)}70%,100%{transform:translateX(433%)}}',
|
|
1623
|
+
'@keyframes dsx-grain{0%{transform:translate3d(0,0,0)}20%{transform:translate3d(-14px,7px,0)}40%{transform:translate3d(11px,-12px,0)}60%{transform:translate3d(-7px,14px,0)}80%{transform:translate3d(13px,5px,0)}100%{transform:translate3d(0,0,0)}}',
|
|
1624
|
+
|
|
1625
|
+
'@keyframes dsx-beam{0%{transform:translateX(0)}100%{transform:translateX(500%)}}',
|
|
1626
|
+
'@keyframes dsx-breath{0%,100%{opacity:.35;transform:scale(.94)}50%{opacity:.80;transform:scale(1.06)}}',
|
|
1627
|
+
'@media (prefers-reduced-motion: reduce){.dsx-ambient__orb,.dsx-ambient__seam::after,.dsx-ambient__sweep,.dsx-grain,.dsx-pulse__beam::after,.dsx-pulse__breath{animation:none!important}.dsx-pulse{transition:none}}',
|
|
1628
|
+
|
|
1629
|
+
// ── айдентика ──
|
|
1630
|
+
// Контур один, цвет ведёт `currentColor`. Пара значений взята прямо из
|
|
1631
|
+
// присланных файлов: белый на тёмной теме, #0B0D12 на светлой. Ключ —
|
|
1632
|
+
// атрибут темы приложения, а не системная схема, поэтому знак следует
|
|
1633
|
+
// ручному переключению.
|
|
1634
|
+
'.dsx-brand{display:block;color:#0B0D12;}',
|
|
1635
|
+
'body[data-ds-dark-theme] .dsx-brand{color:#FFFFFF;}',
|
|
1636
|
+
'.dsx-brand--hero{filter:drop-shadow(0 0 24px rgba(var(' + ACCENT_VAR + '),.35));}',
|
|
1637
|
+
|
|
1638
|
+
// В свёрнутой рейке места по горизонтали мало, а знак широкий, поэтому
|
|
1639
|
+
// там он ужимается. Состояние читается из атрибута `data-sidebar-collapsed`
|
|
1640
|
+
// на фрейме. Оговорка: это НАБЛЮДАЕМЫЙ атрибут, а не объявленный
|
|
1641
|
+
// контракт вроде `data-ds-dark-theme`. Если он однажды исчезнет, знак
|
|
1642
|
+
// просто останется полной высоты — деградация мягкая, без поломки.
|
|
1643
|
+
'.dsx-brand--sidebar{height:24px;width:auto;}',
|
|
1644
|
+
'[data-sidebar-collapsed] .dsx-brand--sidebar{height:16px;}',
|
|
1645
|
+
|
|
1646
|
+
'.dsx-brandname{font-size:15px;font-weight:600;letter-spacing:.14em;color:var(--dsw-alias-label-primary);white-space:nowrap;}',
|
|
1647
|
+
|
|
1648
|
+
// ── строка настроек ──
|
|
1649
|
+
// Собственные цвета не выдумываются: всё берётся из токенов темы,
|
|
1650
|
+
// поэтому строка остаётся согласованной при любой палитре.
|
|
1651
|
+
'.dsx-setting{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;}',
|
|
1652
|
+
'.dsx-setting__title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;}',
|
|
1653
|
+
'.dsx-setting__hint--strong{color:var(--dsw-alias-label-primary)}.dsx-setting__hint--warn{color:var(--dsw-alias-state-warn-primary)}.dsx-setting__hint{color:var(--dsw-alias-label-secondary);font-size:12px;margin-top:2px;}',
|
|
1654
|
+
'.dsx-setting__control{display:inline-flex;padding:2px;gap:2px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2);flex:none;}',
|
|
1655
|
+
'.dsx-seg{appearance:none;border:0;cursor:pointer;padding:4px 10px;border-radius:6px;font-size:12px;line-height:18px;background:transparent;color:var(--dsw-alias-label-secondary);transition:background .16s ease,color .16s ease;}',
|
|
1656
|
+
'.dsx-seg:hover{color:var(--dsw-alias-label-primary);}',
|
|
1657
|
+
'.dsx-seg--on{background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l2),0 0 12px -4px var(--dsw-alias-brand-primary);}',
|
|
1658
|
+
'@media (prefers-reduced-motion: reduce){.dsx-seg{transition:none}}',
|
|
1659
|
+
|
|
1660
|
+
// Кружок акцента показывает сам цвет, поэтому подпись ему не нужна —
|
|
1661
|
+
// но имя остаётся в aria-label, иначе кнопка была бы безымянной.
|
|
1662
|
+
'.dsx-swatch{appearance:none;cursor:pointer;width:22px;height:22px;padding:0;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);background:rgb(var(--dsx-swatch));transition:transform .16s ease,box-shadow .16s ease;}',
|
|
1663
|
+
'.dsx-swatch:hover{transform:scale(1.12);}',
|
|
1664
|
+
'.dsx-swatch--on{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-2),0 0 0 4px rgb(var(--dsx-swatch)),0 0 14px -2px rgb(var(--dsx-swatch));}',
|
|
1665
|
+
'.dsx-setting__swatches{display:inline-flex;gap:10px;align-items:center;flex:none;}',
|
|
1666
|
+
'@media (prefers-reduced-motion: reduce){.dsx-swatch{transition:none}}',
|
|
1667
|
+
].join('\n')
|
|
1668
|
+
|
|
1669
|
+
// Тот же приём вставки стилей, что и у поставочных пакетов:
|
|
1670
|
+
// тег помечается data-plugin-css и не дублируется при повторной загрузке.
|
|
1671
|
+
const tagId = 'tensorgrid-ui/atmosphere.css'
|
|
1672
|
+
if (
|
|
1673
|
+
typeof document !== 'undefined' &&
|
|
1674
|
+
document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') === null
|
|
1675
|
+
) {
|
|
1676
|
+
const tag = document.createElement('style')
|
|
1677
|
+
tag.dataset.plugin = 'tensorgrid-ui'
|
|
1678
|
+
tag.dataset.pluginCss = tagId
|
|
1679
|
+
tag.textContent = css
|
|
1680
|
+
document.head.appendChild(tag)
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// Сохранённый выбор применяется сразу, ещё до монтирования React,
|
|
1684
|
+
// иначе на каждой загрузке был бы кадр с чужими значениями.
|
|
1685
|
+
applyLevelId(readLevelId())
|
|
1686
|
+
applyAccentId(readAccentId())
|
|
1687
|
+
|
|
1688
|
+
// ── Компоненты ───────────────────────────────────────────────────────
|
|
1689
|
+
function Atmosphere() {
|
|
1690
|
+
return jsxs('div', {
|
|
1691
|
+
className: 'dsx-root',
|
|
1692
|
+
'aria-hidden': 'true',
|
|
1693
|
+
children: [
|
|
1694
|
+
jsxs('div', {
|
|
1695
|
+
className: 'dsx-ambient',
|
|
1696
|
+
children: [
|
|
1697
|
+
jsx('div', { className: 'dsx-ambient__orb dsx-ambient__orb--lead' }),
|
|
1698
|
+
jsx('div', { className: 'dsx-ambient__orb dsx-ambient__orb--counter' }),
|
|
1699
|
+
jsx('div', { className: 'dsx-ambient__orb dsx-ambient__orb--deep' }),
|
|
1700
|
+
jsx('div', { className: 'dsx-ambient__orb dsx-ambient__orb--rose' }),
|
|
1701
|
+
jsx('div', { className: 'dsx-ambient__sweep' }),
|
|
1702
|
+
jsx('div', { className: 'dsx-ambient__seam' }),
|
|
1703
|
+
],
|
|
1704
|
+
}),
|
|
1705
|
+
jsxs('div', {
|
|
1706
|
+
className: 'dsx-pulse',
|
|
1707
|
+
children: [
|
|
1708
|
+
jsx('div', { className: 'dsx-pulse__beam' }),
|
|
1709
|
+
jsx('div', { className: 'dsx-pulse__breath' }),
|
|
1710
|
+
],
|
|
1711
|
+
}),
|
|
1712
|
+
jsx('div', { className: 'dsx-grain' }),
|
|
1713
|
+
jsx('div', { className: 'dsx-vignette' }),
|
|
1714
|
+
],
|
|
1715
|
+
})
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* Невидимый драйвер отклика: живёт в сессионном слоте, ничего не рисует и
|
|
1720
|
+
* лишь переносит состояние хода в CSS-переменную на documentElement.
|
|
1721
|
+
*
|
|
1722
|
+
* Разделение вынужденное и осознанное: ambient-слой сидит в корневом
|
|
1723
|
+
* shell.overlay, у которого нет сессии, а `useSession` раздаётся только
|
|
1724
|
+
* сессионным слотам. Переменная на корне — единственный мост между двумя
|
|
1725
|
+
* поддеревьями, не требующий трогать чужой DOM.
|
|
1726
|
+
* @param props - стандартные пропсы сессионного слота.
|
|
1727
|
+
*/
|
|
1728
|
+
function ActivityDriver({ useSession }) {
|
|
1729
|
+
const running = useSession((session) => session.running) ?? false
|
|
1730
|
+
|
|
1731
|
+
React.useEffect(() => {
|
|
1732
|
+
if (typeof document === 'undefined') return undefined
|
|
1733
|
+
const style = document.documentElement.style
|
|
1734
|
+
style.setProperty(ACTIVITY_VAR, running ? '1' : '0')
|
|
1735
|
+
return () => {
|
|
1736
|
+
style.setProperty(ACTIVITY_VAR, '0')
|
|
1737
|
+
}
|
|
1738
|
+
}, [running])
|
|
1739
|
+
|
|
1740
|
+
return null
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function AccentRow({ t }) {
|
|
1744
|
+
const [current, setCurrent] = React.useState(readAccentId)
|
|
1745
|
+
|
|
1746
|
+
React.useEffect(() => {
|
|
1747
|
+
applyAccentId(current)
|
|
1748
|
+
writeAccentId(current)
|
|
1749
|
+
}, [current])
|
|
1750
|
+
|
|
1751
|
+
return jsxs('div', {
|
|
1752
|
+
className: 'dsx-setting',
|
|
1753
|
+
children: [
|
|
1754
|
+
jsxs('div', {
|
|
1755
|
+
children: [
|
|
1756
|
+
jsx('div', { className: 'dsx-setting__title', children: t('accent.title') }),
|
|
1757
|
+
jsx('div', { className: 'dsx-setting__hint', children: t('accent.hint') }),
|
|
1758
|
+
],
|
|
1759
|
+
}),
|
|
1760
|
+
jsx('div', {
|
|
1761
|
+
className: 'dsx-setting__swatches',
|
|
1762
|
+
role: 'group',
|
|
1763
|
+
children: ACCENTS.map((accent) =>
|
|
1764
|
+
jsx(
|
|
1765
|
+
'button',
|
|
1766
|
+
{
|
|
1767
|
+
type: 'button',
|
|
1768
|
+
className: 'dsx-swatch' + (accent.id === current ? ' dsx-swatch--on' : ''),
|
|
1769
|
+
style: { '--dsx-swatch': accent.rgb },
|
|
1770
|
+
'aria-label': accent.label,
|
|
1771
|
+
'aria-pressed': accent.id === current,
|
|
1772
|
+
onClick: () => setCurrent(accent.id),
|
|
1773
|
+
},
|
|
1774
|
+
accent.id,
|
|
1775
|
+
),
|
|
1776
|
+
),
|
|
1777
|
+
}),
|
|
1778
|
+
],
|
|
1779
|
+
})
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// ── Айдентика ────────────────────────────────────────────────────────
|
|
1783
|
+
//
|
|
1784
|
+
// Присланные tg-white.svg и tg-black.svg побайтово совпадают всюду, кроме
|
|
1785
|
+
// заливки, поэтому здесь один контур, а цвет ведёт `currentColor`.
|
|
1786
|
+
const BRAND_VIEWBOX = '245 230 1047 566'
|
|
1787
|
+
const BRAND_RATIO = 566 / 1047
|
|
1788
|
+
const BRAND_PATHS = [
|
|
1789
|
+
'M315 278H929L766 404H626Q620 404 620 410V724C620 737 610 748 597 748H516C503 748 493 738 493 725V411Q493 404 486 404H317C303 404 293 394 293 380V302C293 288 303 278 315 278Z',
|
|
1790
|
+
'M968 278H1126C1190 278 1244 333 1244 402V421Q1244 435 1230 435H1128Q1117 435 1117 424Q1117 404 1099 404H889C867 404 849 422 849 444V604C849 625 866 638 887 638H1086C1103 638 1115 627 1115 611V572Q1115 564 1107 564H954C943 564 935 556 935 546V493C935 482 943 474 954 474H1225C1236 474 1244 483 1244 494V619C1244 691 1192 748 1121 748H847C778 748 724 696 724 625V510C724 475 733 455 755 438L968 278Z',
|
|
1791
|
+
]
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Знак TG. Ширина задаётся снаружи, высота считается из пропорции 1047:566,
|
|
1795
|
+
* поэтому знак никогда не переполняет узкую рейку сайдбара.
|
|
1796
|
+
* @param width - желаемая ширина в пикселях.
|
|
1797
|
+
* @param extraClass - дополнительный класс оформления.
|
|
1798
|
+
*/
|
|
1799
|
+
function brandSvg(width, extraClass) {
|
|
1800
|
+
return jsx('svg', {
|
|
1801
|
+
className: extraClass === undefined ? 'dsx-brand' : 'dsx-brand ' + extraClass,
|
|
1802
|
+
viewBox: BRAND_VIEWBOX,
|
|
1803
|
+
width: width,
|
|
1804
|
+
height: Math.round(width * BRAND_RATIO),
|
|
1805
|
+
role: 'img',
|
|
1806
|
+
'aria-label': 'TG',
|
|
1807
|
+
children: jsx('g', {
|
|
1808
|
+
fill: 'currentColor',
|
|
1809
|
+
children: BRAND_PATHS.map((d, index) => jsx('path', { d: d }, String(index))),
|
|
1810
|
+
}),
|
|
1811
|
+
})
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Знак в сайдбаре.
|
|
1816
|
+
*
|
|
1817
|
+
* Слот передаёт `size: 24` — высоту полосы `.brandIdentity`, которую
|
|
1818
|
+
* поставочная квадратная рыба занимает целиком. Знак TG вдвое шире, и
|
|
1819
|
+
* если принять `size` за ширину, высота выходит ~13px: рядом с надписью
|
|
1820
|
+
* в 18px он читается мелким. Поэтому `size` трактуется как ВЫСОТА, а
|
|
1821
|
+
* ширина считается из пропорции.
|
|
1822
|
+
* @param size - высота, которую отводит слот.
|
|
1823
|
+
*/
|
|
1824
|
+
function BrandMark({ size }) {
|
|
1825
|
+
const box = typeof size === 'number' && size > 0 ? size : 24
|
|
1826
|
+
return brandSvg(Math.round(box / BRAND_RATIO), 'dsx-brand--sidebar')
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/** Знак на экране пустой сессии — крупнее и со свечением акцента. */
|
|
1830
|
+
function HeroMark() {
|
|
1831
|
+
return brandSvg(96, 'dsx-brand--hero')
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/** Надпись рядом со знаком. */
|
|
1835
|
+
function BrandName() {
|
|
1836
|
+
return jsx('span', { className: 'dsx-brandname', children: BRAND_NAME })
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
function IntensityRow({ t }) {
|
|
1840
|
+
const [current, setCurrent] = React.useState(readLevelId)
|
|
1841
|
+
|
|
1842
|
+
React.useEffect(() => {
|
|
1843
|
+
applyLevelId(current)
|
|
1844
|
+
writeLevelId(current)
|
|
1845
|
+
}, [current])
|
|
1846
|
+
|
|
1847
|
+
return jsxs('div', {
|
|
1848
|
+
className: 'dsx-setting',
|
|
1849
|
+
children: [
|
|
1850
|
+
jsxs('div', {
|
|
1851
|
+
children: [
|
|
1852
|
+
jsx('div', { className: 'dsx-setting__title', children: t('intensity.title') }),
|
|
1853
|
+
jsx('div', { className: 'dsx-setting__hint', children: t('intensity.hint') }),
|
|
1854
|
+
],
|
|
1855
|
+
}),
|
|
1856
|
+
jsx('div', {
|
|
1857
|
+
className: 'dsx-setting__control',
|
|
1858
|
+
role: 'group',
|
|
1859
|
+
children: LEVELS.map((level) =>
|
|
1860
|
+
jsx(
|
|
1861
|
+
'button',
|
|
1862
|
+
{
|
|
1863
|
+
type: 'button',
|
|
1864
|
+
className: 'dsx-seg' + (level.id === current ? ' dsx-seg--on' : ''),
|
|
1865
|
+
'aria-pressed': level.id === current,
|
|
1866
|
+
onClick: () => setCurrent(level.id),
|
|
1867
|
+
children: t(level.key),
|
|
1868
|
+
},
|
|
1869
|
+
level.id,
|
|
1870
|
+
),
|
|
1871
|
+
),
|
|
1872
|
+
}),
|
|
1873
|
+
],
|
|
1874
|
+
})
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// ── Обновления ───────────────────────────────────────────────────────
|
|
1878
|
+
//
|
|
1879
|
+
// Маршрут поднимает host-половина пакета. Путь относительный, токен не
|
|
1880
|
+
// нужен: страница уже авторизована сессией, как и у поставочных
|
|
1881
|
+
// пакетов, которые ходят на /api/ обычным fetch.
|
|
1882
|
+
const UPDATE_PATH = '/api/tensorgrid.update'
|
|
1883
|
+
|
|
1884
|
+
function UpdateRow({ t }) {
|
|
1885
|
+
const [state, setState] = React.useState(null)
|
|
1886
|
+
const [busy, setBusy] = React.useState(false)
|
|
1887
|
+
const [result, setResult] = React.useState(null)
|
|
1888
|
+
|
|
1889
|
+
const load = React.useCallback(async (force) => {
|
|
1890
|
+
setBusy(true)
|
|
1891
|
+
setResult(null)
|
|
1892
|
+
try {
|
|
1893
|
+
const response = await fetch(UPDATE_PATH + (force ? '?force=1' : ''))
|
|
1894
|
+
setState(response.ok ? await response.json() : { problem: 'сервер ответил ' + response.status })
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
setState({ problem: String(error && error.message ? error.message : error) })
|
|
1897
|
+
} finally {
|
|
1898
|
+
setBusy(false)
|
|
1899
|
+
}
|
|
1900
|
+
}, [])
|
|
1901
|
+
|
|
1902
|
+
// Первый показ берёт готовый ответ: host проверяет при старте, так что
|
|
1903
|
+
// ждать сети обычно не приходится.
|
|
1904
|
+
React.useEffect(() => { load(false) }, [load])
|
|
1905
|
+
|
|
1906
|
+
const apply = React.useCallback(async () => {
|
|
1907
|
+
setBusy(true)
|
|
1908
|
+
setResult(null)
|
|
1909
|
+
try {
|
|
1910
|
+
const response = await fetch(UPDATE_PATH, {
|
|
1911
|
+
method: 'POST',
|
|
1912
|
+
headers: { 'content-type': 'application/json' },
|
|
1913
|
+
body: '{}',
|
|
1914
|
+
})
|
|
1915
|
+
const value = await response.json()
|
|
1916
|
+
setResult(value)
|
|
1917
|
+
if (value.status) setState(value.status)
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
setResult({ ok: false, problem: String(error && error.message ? error.message : error) })
|
|
1920
|
+
} finally {
|
|
1921
|
+
setBusy(false)
|
|
1922
|
+
}
|
|
1923
|
+
}, [])
|
|
1924
|
+
|
|
1925
|
+
let summary = t('update.checking')
|
|
1926
|
+
if (state !== null) {
|
|
1927
|
+
if (state.problem) summary = state.problem
|
|
1928
|
+
else if (state.unknownHistory) summary = t('update.unknownHistory')
|
|
1929
|
+
else if (state.updateAvailable) summary = t('update.available').replace('{count}', String(state.behind))
|
|
1930
|
+
// Установленное опережает выпущенное — так выглядит машина
|
|
1931
|
+
// разработчика, который поставил себе ещё не запушенное.
|
|
1932
|
+
else if (state.ahead > 0) summary = t('update.ahead').replace('{count}', String(state.ahead))
|
|
1933
|
+
else summary = t('update.upToDate')
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const version = state === null || !state.installedCommit
|
|
1937
|
+
? ''
|
|
1938
|
+
: t('update.installed').replace('{commit}', state.installedCommit).replace('{dsh}', state.dshExpected || '?')
|
|
1939
|
+
|
|
1940
|
+
// Расхождение версий dsh — самый тихий способ всё сломать: пакет
|
|
1941
|
+
// опирается на контракты, а проверяли его на другой версии. Пользователь
|
|
1942
|
+
// об этом узнать ниоткуда не может, поэтому говорим прямо.
|
|
1943
|
+
const dshMismatch = state !== null
|
|
1944
|
+
&& state.dshExpected
|
|
1945
|
+
&& state.dshActual
|
|
1946
|
+
&& state.dshExpected !== state.dshActual
|
|
1947
|
+
? t('update.dshMismatch').replace('{expected}', state.dshExpected).replace('{actual}', state.dshActual)
|
|
1948
|
+
: null
|
|
1949
|
+
|
|
1950
|
+
return jsxs('div', {
|
|
1951
|
+
className: 'dsx-setting',
|
|
1952
|
+
children: [
|
|
1953
|
+
jsxs('div', {
|
|
1954
|
+
children: [
|
|
1955
|
+
jsx('div', { className: 'dsx-setting__title', children: t('update.title') }),
|
|
1956
|
+
jsx('div', { className: 'dsx-setting__hint', children: summary }),
|
|
1957
|
+
version === '' ? null : jsx('div', { className: 'dsx-setting__hint', children: version }),
|
|
1958
|
+
dshMismatch === null ? null : jsx('div', {
|
|
1959
|
+
className: 'dsx-setting__hint dsx-setting__hint--warn',
|
|
1960
|
+
children: dshMismatch,
|
|
1961
|
+
}),
|
|
1962
|
+
result === null ? null : jsx('div', {
|
|
1963
|
+
className: 'dsx-setting__hint dsx-setting__hint--strong',
|
|
1964
|
+
children: result.ok
|
|
1965
|
+
? (result.needsRestart ? t('update.doneRestart') : t('update.doneReload'))
|
|
1966
|
+
: (result.problem || t('update.failed')),
|
|
1967
|
+
}),
|
|
1968
|
+
],
|
|
1969
|
+
}),
|
|
1970
|
+
jsxs('div', {
|
|
1971
|
+
className: 'dsx-setting__control',
|
|
1972
|
+
role: 'group',
|
|
1973
|
+
children: [
|
|
1974
|
+
jsx('button', {
|
|
1975
|
+
type: 'button',
|
|
1976
|
+
className: 'dsx-seg',
|
|
1977
|
+
disabled: busy,
|
|
1978
|
+
onClick: () => load(true),
|
|
1979
|
+
children: busy ? t('update.working') : t('update.check'),
|
|
1980
|
+
}),
|
|
1981
|
+
state !== null && state.canUpdate
|
|
1982
|
+
? jsx('button', {
|
|
1983
|
+
type: 'button',
|
|
1984
|
+
className: 'dsx-seg dsx-seg--on',
|
|
1985
|
+
disabled: busy,
|
|
1986
|
+
onClick: apply,
|
|
1987
|
+
children: t('update.apply'),
|
|
1988
|
+
})
|
|
1989
|
+
: null,
|
|
1990
|
+
],
|
|
1991
|
+
}),
|
|
1992
|
+
],
|
|
1993
|
+
})
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// ── Регистрация ──────────────────────────────────────────────────────
|
|
1997
|
+
/** Жёсткие зависимости клиентской половины. */
|
|
1998
|
+
const inject = ['slots', 'theme', 'locale']
|
|
1999
|
+
|
|
2000
|
+
/**
|
|
2001
|
+
* Кладёт слой токенов и занимает пять аддитивных мест: ambient-слой над
|
|
2002
|
+
* фреймом, две строки настроек, невидимый драйвер отклика в сессионном
|
|
2003
|
+
* слоте и знак на экране пустой сессии. Каждая регистрация возвращает
|
|
2004
|
+
* диспозер, поэтому снятие строки композиции убирает пакет без следов.
|
|
2005
|
+
* @param ctx - Корневой клиентский контекст.
|
|
2006
|
+
*/
|
|
2007
|
+
function apply(ctx) {
|
|
2008
|
+
// Ссылка живёт ровно столько же, сколько слой: её берёт строка акцента,
|
|
2009
|
+
// чтобы переписать слой при смене тона.
|
|
2010
|
+
ctx.effect(() => {
|
|
2011
|
+
themeService = ctx.theme
|
|
2012
|
+
const dispose = ctx.theme.overrideTokens(SOURCE, tokensFor(accentOf(readAccentId()) ?? accentOf(DEFAULT_ACCENT)))
|
|
2013
|
+
return () => {
|
|
2014
|
+
themeService = null
|
|
2015
|
+
dispose()
|
|
2016
|
+
}
|
|
2017
|
+
})
|
|
2018
|
+
|
|
2019
|
+
// Русский как язык-пакет. Цепочка запасных вариантов обязана дойти до
|
|
2020
|
+
// английского, и это ровно то, что делает перевод безопасным: любой
|
|
2021
|
+
// ключ без русского значения — включая строки, которые появятся в
|
|
2022
|
+
// будущих версиях dsh, — сам покажется по-английски. Сломаться нечему.
|
|
2023
|
+
ctx.effect(() => {
|
|
2024
|
+
const disposers = []
|
|
2025
|
+
|
|
2026
|
+
const known = ctx.locale.getLocale().locales.some((entry) => entry.id === RU)
|
|
2027
|
+
if (!known) {
|
|
2028
|
+
try {
|
|
2029
|
+
disposers.push(ctx.locale.addLanguage({ id: RU, label: 'Русский', fallback: 'en' }))
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
console.error('addLanguage failed', error)
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
disposers.push(ctx.locale.register(LOCALE_NS, { en: EN_DICT, zh: ZH_DICT }))
|
|
2036
|
+
disposers.push(ctx.locale.register(LOCALE_NS, RU, RU_DICT))
|
|
2037
|
+
|
|
2038
|
+
// Русские словари чужих пространств имён. Трёхаргументная форма
|
|
2039
|
+
// register принимает произвольное имя, поэтому язык-пакет может
|
|
2040
|
+
// дополнять словари пакетов, которые ему не принадлежат, ничего в
|
|
2041
|
+
// них не замещая: английский и китайский остаются нетронутыми.
|
|
2042
|
+
for (const ns of Object.keys(RU_PACK)) {
|
|
2043
|
+
disposers.push(ctx.locale.register(ns, RU, RU_PACK[ns]))
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
return () => {
|
|
2047
|
+
for (const dispose of disposers) dispose()
|
|
2048
|
+
}
|
|
2049
|
+
}, 'obsidian-ion: language pack')
|
|
2050
|
+
|
|
2051
|
+
ctx.slots.inject('shell.overlay', () =>
|
|
2052
|
+
ctx.slots.register(
|
|
2053
|
+
{ name: 'shell.overlay', id: 'obsidian-ion-ambient', order: -1000 },
|
|
2054
|
+
Atmosphere,
|
|
2055
|
+
),
|
|
2056
|
+
)
|
|
2057
|
+
|
|
2058
|
+
ctx.slots.inject('settings.general.item', () =>
|
|
2059
|
+
ctx.slots.register(
|
|
2060
|
+
{ name: 'settings.general.item', id: 'obsidian-ion-intensity', order: 12, locale: LOCALE_NS },
|
|
2061
|
+
IntensityRow,
|
|
2062
|
+
),
|
|
2063
|
+
)
|
|
2064
|
+
|
|
2065
|
+
ctx.slots.inject('settings.general.item', () =>
|
|
2066
|
+
ctx.slots.register(
|
|
2067
|
+
{ name: 'settings.general.item', id: 'obsidian-ion-update', order: 11, locale: LOCALE_NS },
|
|
2068
|
+
UpdateRow,
|
|
2069
|
+
),
|
|
2070
|
+
)
|
|
2071
|
+
|
|
2072
|
+
ctx.slots.inject('settings.general.item', () =>
|
|
2073
|
+
ctx.slots.register(
|
|
2074
|
+
{ name: 'settings.general.item', id: 'obsidian-ion-accent', order: 13, locale: LOCALE_NS },
|
|
2075
|
+
AccentRow,
|
|
2076
|
+
),
|
|
2077
|
+
)
|
|
2078
|
+
|
|
2079
|
+
ctx.slots.inject('conversation.input.dock', () =>
|
|
2080
|
+
ctx.slots.register(
|
|
2081
|
+
{ name: 'conversation.input.dock', id: 'obsidian-ion-activity', order: 1000 },
|
|
2082
|
+
ActivityDriver,
|
|
2083
|
+
),
|
|
2084
|
+
)
|
|
2085
|
+
|
|
2086
|
+
ctx.slots.inject('conversation.hero.brand.mark', () =>
|
|
2087
|
+
ctx.slots.register({ name: 'conversation.hero.brand.mark' }, HeroMark),
|
|
2088
|
+
)
|
|
2089
|
+
|
|
2090
|
+
// Два слота ниже помечены `shadows-shipped-ui`, и это единственное
|
|
2091
|
+
// осознанное исключение из правила пакета. Заменяемые компоненты —
|
|
2092
|
+
// чистая графика (`FishLogo` и `BrandWordmark`): ни дочерних слотов,
|
|
2093
|
+
// ни поведения, ни будущих функций, которые мы бы пропустили. Потерять
|
|
2094
|
+
// поставочную айдентику здесь и есть цель.
|
|
2095
|
+
ctx.slots.inject('sidebar.brand.mark', () =>
|
|
2096
|
+
ctx.slots.register({ name: 'sidebar.brand.mark' }, BrandMark),
|
|
2097
|
+
)
|
|
2098
|
+
|
|
2099
|
+
ctx.slots.inject('sidebar.brand.name', () =>
|
|
2100
|
+
ctx.slots.register({ name: 'sidebar.brand.name' }, BrandName),
|
|
2101
|
+
)
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
exports.apply = apply
|
|
2105
|
+
exports.inject = inject
|
|
2106
|
+
return module.exports
|
|
2107
|
+
},
|
|
2108
|
+
})
|