@goodandready/dsh-russian-lang 0.2.26 → 0.3.1
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/README.md +1 -1
- package/bin/dsh-i18n.mjs +247 -0
- package/lib/client.js +785 -410
- package/lib/index.js +69 -13
- package/lib/locales/plugins/01-token-dashboard.json +1 -1
- package/lib/locales/plugins/10-kanban.json +1 -1
- package/lib/locales/plugins/13-agent-teams.json +1 -1
- package/lib/locales/plugins/16-usage-stats.json +1 -1
- package/lib/locales/plugins/23-moa.json +1 -1
- package/lib/locales/plugins/24-personal-directive.json +1 -1
- package/lib/locales/plugins/33-messenger-gateway.json +1 -1
- package/lib/locales/plugins/35-gitea.json +1 -1
- package/lib/locales/plugins/36-cost-meter.json +1 -1
- package/lib/locales/plugins/37-time-machine.json +1 -1
- package/lib/locales/plugins/38-context-lens.json +1 -1
- package/lib/locales/plugins/39-shadow-auditor.json +1 -1
- package/lib/locales/plugins/40-clinebot.json +1 -1
- package/lib/locales/plugins/41-lanmode.json +1 -1
- package/lib/locales/plugins/42-plugin-console.json +1 -1
- package/lib/locales/plugins/46-memory-brain.json +1 -1
- package/lib/locales/plugins/48-task-tracker.json +1 -1
- package/lib/locales/plugins/49-dsml-guard.json +1 -1
- package/lib/locales/plugins/49-usage-guard.json +1 -1
- package/lib/locales/plugins/50-automode.json +1 -1
- package/lib/locales/plugins/54-workshop-market.json +1 -1
- package/lib/locales/plugins/56-cron.json +1 -1
- package/lib/locales/plugins/57-live-canvas.json +1 -1
- package/lib/locales/plugins/58-grok-xsearch.json +1 -1
- package/lib/locales/plugins/59-goal.json +1 -1
- package/lib/locales/plugins/60-model-sync.json +1 -1
- package/lib/locales/plugins/61-issue-reporter.json +1 -1
- package/lib/locales/plugins/62-model-search.json +1 -1
- package/lib/locales/plugins/63-agent-orchestrator.json +1 -1
- package/lib/locales/plugins/64-key-limits.json +1 -1
- package/lib/locales/plugins/65-subscriptions.json +1 -1
- package/lib/locales/plugins/66-tts.json +1 -1
- package/lib/locales/plugins/67-voice.json +1 -1
- package/lib/locales/plugins/68-image-gen.json +1 -1
- package/lib/locales/plugins/69-plugin-notify.json +1 -1
- package/lib/locales/plugins/70-hypotheses.json +1 -1
- package/lib/locales/plugins/71-memory-meter.json +1 -1
- package/lib/locales/plugins/72-server-monitor.json +1 -70
- package/lib/locales/plugins/73-agentrouter.json +1 -1
- package/lib/locales/plugins/75-test-pilot.json +1 -0
- package/lib/locales/plugins/76-visualize.json +1 -0
- package/lib/locales/plugins/77-github-ops.json +1 -0
- package/lib/locales/plugins/78-web-gateway.json +1 -0
- package/lib/locales/plugins/79-approval-gate.json +1 -0
- package/lib/locales.js +50 -12
- package/lib/pure.js +251 -13
- package/package.json +7 -3
- package/lib/locales/plugins/44-goal.json +0 -1
package/lib/client.js
CHANGED
|
@@ -4,13 +4,48 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} }
|
|
5
5
|
var React = null
|
|
6
6
|
try { React = require('react') } catch (e) { /* карточка настроек необязательна */ }
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
7
|
const numberFormat = new Intl.NumberFormat('ru-RU')
|
|
8
|
+
const compactNumberFormat = new Intl.NumberFormat('ru-RU', { notation: 'compact', compactDisplay: 'short' })
|
|
11
9
|
const relativeTimeFormat = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
|
|
10
|
+
const datePresetFormats = {
|
|
11
|
+
short: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }),
|
|
12
|
+
shortDateTime: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
|
|
13
|
+
medium: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
|
|
14
|
+
long: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }),
|
|
15
|
+
time: new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
|
16
|
+
}
|
|
17
|
+
const formatDate = (val, style = 'short') => {
|
|
18
|
+
if (val === null || val === undefined || val === '') return ''
|
|
19
|
+
const d = val instanceof Date ? val : new Date(typeof val === 'number' && val < 1e12 ? val * 1000 : val)
|
|
20
|
+
if (isNaN(d.getTime())) return String(val)
|
|
21
|
+
if (typeof style === 'string' && datePresetFormats[style]) {
|
|
22
|
+
return datePresetFormats[style].format(d)
|
|
23
|
+
}
|
|
24
|
+
if (typeof style === 'object' && style !== null) {
|
|
25
|
+
try {
|
|
26
|
+
return new Intl.DateTimeFormat('ru-RU', style).format(d)
|
|
27
|
+
} catch (_) {
|
|
28
|
+
return datePresetFormats.short.format(d)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return datePresetFormats.short.format(d)
|
|
32
|
+
}
|
|
33
|
+
const formatCompactNumber = (val) => {
|
|
34
|
+
if (val === null || val === undefined || val === '') return ''
|
|
35
|
+
const n = typeof val === 'number' ? val : Number(val)
|
|
36
|
+
return isNaN(n) ? String(val) : compactNumberFormat.format(n)
|
|
37
|
+
}
|
|
38
|
+
const formatTokens = (count, compact = false) => {
|
|
39
|
+
if (count === null || count === undefined || count === '') return ''
|
|
40
|
+
const n = typeof count === 'number' ? count : Number(count)
|
|
41
|
+
if (isNaN(n)) return String(count)
|
|
42
|
+
const tokenWord = plural(n, ['токен', 'токена', 'токенов'])
|
|
43
|
+
if (compact && n >= 1000) {
|
|
44
|
+
return `${compactNumberFormat.format(n)} токенов`
|
|
45
|
+
}
|
|
46
|
+
return `${numberFormat.format(n)} ${tokenWord}`
|
|
47
|
+
}
|
|
12
48
|
const currencyFormats = new Map()
|
|
13
|
-
|
|
14
49
|
const getCurrencyFormat = (cur) => {
|
|
15
50
|
const c = (cur || 'RUB').toUpperCase()
|
|
16
51
|
if (!currencyFormats.has(c)) {
|
|
@@ -22,20 +57,17 @@ const getCurrencyFormat = (cur) => {
|
|
|
22
57
|
}
|
|
23
58
|
return currencyFormats.get(c)
|
|
24
59
|
}
|
|
25
|
-
|
|
26
60
|
const formatNumber = (val) => {
|
|
27
61
|
if (val === null || val === undefined || val === '') return ''
|
|
28
62
|
const n = typeof val === 'number' ? val : Number(val)
|
|
29
63
|
return isNaN(n) ? String(val) : numberFormat.format(n)
|
|
30
64
|
}
|
|
31
|
-
|
|
32
65
|
const formatCurrency = (val, cur) => {
|
|
33
66
|
if (val === null || val === undefined || val === '') return ''
|
|
34
67
|
const n = typeof val === 'number' ? val : Number(val)
|
|
35
68
|
if (isNaN(n)) return String(val)
|
|
36
69
|
return getCurrencyFormat(cur).format(n)
|
|
37
70
|
}
|
|
38
|
-
|
|
39
71
|
const formatRelativeTime = (val, unit) => {
|
|
40
72
|
if (val === null || val === undefined || val === '') return ''
|
|
41
73
|
if (typeof val === 'number' && typeof unit === 'string') {
|
|
@@ -52,8 +84,6 @@ const formatRelativeTime = (val, unit) => {
|
|
|
52
84
|
if (absSec < 31536000) return relativeTimeFormat.format(Math.round(diffSec / 2592000), 'month')
|
|
53
85
|
return relativeTimeFormat.format(Math.round(diffSec / 31536000), 'year')
|
|
54
86
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
87
|
const INFLECT_CUSTOM = {
|
|
58
88
|
'пользователь': { gen: 'пользователя', dat: 'пользователю', acc: 'пользователя', ins: 'пользователем', pre: 'пользователе' },
|
|
59
89
|
'агент': { gen: 'агента', dat: 'агенту', acc: 'агента', ins: 'агентом', pre: 'агенте' },
|
|
@@ -66,9 +96,7 @@ const INFLECT_CUSTOM = {
|
|
|
66
96
|
'файл': { gen: 'файла', dat: 'файлу', acc: 'файл', ins: 'файлом', pre: 'файле' },
|
|
67
97
|
'папка': { gen: 'папки', dat: 'папке', acc: 'папку', ins: 'папкой', pre: 'папке' }
|
|
68
98
|
}
|
|
69
|
-
|
|
70
99
|
const keepCase = (src, out) => (src[0] === src[0].toUpperCase() ? out[0].toUpperCase() + out.slice(1) : out)
|
|
71
|
-
|
|
72
100
|
const inflectWord = (word, cName) => {
|
|
73
101
|
if (!word || typeof word !== 'string') return word
|
|
74
102
|
const lower = word.toLowerCase()
|
|
@@ -77,7 +105,6 @@ const inflectWord = (word, cName) => {
|
|
|
77
105
|
}
|
|
78
106
|
if (/[a-zA-Z0-9_-]/.test(word) || /^[А-ЯЁ]{2,}$/.test(word)) return word
|
|
79
107
|
if (/[оеиую]$/i.test(word) && !/(ко|ло|но|то|во|ро|до|по|со|мо|го)$/i.test(word)) return word
|
|
80
|
-
|
|
81
108
|
const w = lower
|
|
82
109
|
const endings = [
|
|
83
110
|
['ия', 2, (s) => ({ gen: s + 'ии', dat: s + 'ии', acc: s + 'ию', ins: s + 'ией', pre: s + 'ии' })],
|
|
@@ -98,35 +125,58 @@ const inflectWord = (word, cName) => {
|
|
|
98
125
|
}
|
|
99
126
|
return word
|
|
100
127
|
}
|
|
101
|
-
|
|
102
128
|
const INFLECT_CASES = new Set(['gen', 'dat', 'acc', 'ins', 'pre'])
|
|
103
|
-
|
|
104
129
|
const inflect = (phrase, cName) => {
|
|
105
130
|
if (!phrase || typeof phrase !== 'string') return phrase
|
|
106
131
|
if (!INFLECT_CASES.has(cName)) return phrase
|
|
107
132
|
return phrase.split(' ').map((w) => inflectWord(w, cName)).join(' ')
|
|
108
133
|
}
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
const pluralRules = new Intl.PluralRules('ru-RU')
|
|
135
|
+
const pluralForm = (n) => pluralRules.select(n)
|
|
136
|
+
function plural(count, forms, withCount = false) {
|
|
137
|
+
const n = typeof count === 'number' ? count : (Number(count) || 0)
|
|
138
|
+
let one = '', few = '', many = ''
|
|
139
|
+
if (Array.isArray(forms)) {
|
|
140
|
+
one = forms[0] || ''
|
|
141
|
+
few = forms[1] || one
|
|
142
|
+
many = forms[2] || few || one
|
|
143
|
+
} else if (typeof forms === 'string') {
|
|
144
|
+
one = forms
|
|
145
|
+
few = arguments[2] || one
|
|
146
|
+
many = arguments[3] || few || one
|
|
147
|
+
withCount = arguments[4] || false
|
|
148
|
+
} else {
|
|
149
|
+
return ''
|
|
150
|
+
}
|
|
151
|
+
const form = pluralRules.select(n)
|
|
152
|
+
let word = many
|
|
153
|
+
if (form === 'one') word = one
|
|
154
|
+
else if (form === 'few') word = few
|
|
155
|
+
else if (form === 'many') word = many
|
|
156
|
+
return withCount ? `${formatNumber(n)} ${word}` : word
|
|
157
|
+
}
|
|
111
158
|
const fill = (template, params) => {
|
|
112
159
|
if (!params || typeof params !== 'object') return String(template)
|
|
113
|
-
return String(template).replace(/\{(\w+)(?::(
|
|
160
|
+
return String(template).replace(/\{(\w+)(?::([^}]+))?\}/g, (match, name, spec) => {
|
|
114
161
|
if (!(name in params)) return match
|
|
115
162
|
const val = params[name]
|
|
163
|
+
if (!spec) return String(val)
|
|
116
164
|
if (spec === 'number') return formatNumber(val)
|
|
165
|
+
if (spec === 'compact') return formatCompactNumber(val)
|
|
166
|
+
if (spec === 'tokens') return formatTokens(val)
|
|
167
|
+
if (spec === 'tokens:compact') return formatTokens(val, true)
|
|
117
168
|
if (spec === 'reltime') return formatRelativeTime(val)
|
|
118
169
|
if (spec === 'currency') return formatCurrency(val, params.currency || 'RUB')
|
|
170
|
+
if (spec === 'date') return formatDate(val)
|
|
171
|
+
if (spec.startsWith('date:')) return formatDate(val, spec.slice(5))
|
|
172
|
+
if (spec.startsWith('plural:')) {
|
|
173
|
+
const parts = spec.slice(7).split(',')
|
|
174
|
+
return plural(val, parts)
|
|
175
|
+
}
|
|
119
176
|
if (INFLECT_CASES.has(spec)) return inflect(String(val), spec)
|
|
120
177
|
return String(val)
|
|
121
178
|
})
|
|
122
179
|
}
|
|
123
|
-
|
|
124
|
-
const pluralRules = new Intl.PluralRules('ru-RU')
|
|
125
|
-
|
|
126
|
-
/** Русская форма числительного: one | few | many | other. */
|
|
127
|
-
const pluralForm = (n) => pluralRules.select(n)
|
|
128
|
-
|
|
129
|
-
|
|
130
180
|
const stemRussian = (word) => {
|
|
131
181
|
if (!word || typeof word !== 'string') return ''
|
|
132
182
|
let w = word.toLowerCase().trim()
|
|
@@ -138,15 +188,12 @@ const stemRussian = (word) => {
|
|
|
138
188
|
w = w.replace(/(?:ами|ями|иями|ией|иям|ием|ах|ях|иях|ев|ов|ие|ье|ей|ой|ий|ям|ем|ам|ом|а|е|и|о|у|ы|ь|ю|я)$/, '')
|
|
139
189
|
return w.length >= 2 ? w : word.toLowerCase()
|
|
140
190
|
}
|
|
141
|
-
|
|
142
191
|
const EN_RU_KEYS = {
|
|
143
192
|
'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н', 'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', '[': 'х', ']': 'ъ',
|
|
144
193
|
'a': 'ф', 's': 'ы', 'd': 'в', 'f': 'а', 'g': 'п', 'h': 'р', 'j': 'о', 'k': 'л', 'l': 'д', ';': 'ж', "'": 'э',
|
|
145
194
|
'z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и', 'n': 'т', 'm': 'ь', ',': 'б', '.': 'ю'
|
|
146
195
|
}
|
|
147
|
-
|
|
148
196
|
const translitEnToRu = (str) => str.toLowerCase().split('').map((c) => EN_RU_KEYS[c] || c).join('')
|
|
149
|
-
|
|
150
197
|
const fuzzyMatchRu = (query, target) => {
|
|
151
198
|
if (!query || !target) return 0
|
|
152
199
|
const q = query.toLowerCase().trim()
|
|
@@ -164,8 +211,6 @@ const fuzzyMatchRu = (query, target) => {
|
|
|
164
211
|
if (matched > 0) return 50
|
|
165
212
|
return 0
|
|
166
213
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
214
|
const ERROR_MAP = {
|
|
170
215
|
ENOENT: { title: 'Файл не найден', message: 'Указанный файл или директория не существуют', hint: 'Проверьте правильность указанного пути к файлу.' },
|
|
171
216
|
EACCES: { title: 'Отказано в доступе', message: 'Недостаточно прав для чтения или записи', hint: 'Проверьте права доступа к файлу или директории (chmod/chown).' },
|
|
@@ -184,13 +229,17 @@ const ERROR_MAP = {
|
|
|
184
229
|
500: { title: 'Внутренняя ошибка сервера', message: 'На стороне сервера произошел сбой', hint: 'Попробуйте повторить запрос позже или проверьте серверные логи.' },
|
|
185
230
|
502: { title: 'Ошибочный шлюз (Bad Gateway)', message: 'Промежуточный прокси не получил корректный ответ', hint: 'Проверьте работу нижележащей службы или upstream-сервера.' },
|
|
186
231
|
503: { title: 'Служба временно недоступна', message: 'Сервер перегружен или находится на обслуживании', hint: 'Попробуйте повторить операцию через некоторое время.' },
|
|
187
|
-
504: { title: 'Шлюз не отвечает (Gateway Timeout)', message: 'Превышено время ожидания ответа от upstream-сервера', hint: 'Попробуйте повторить запрос позже.' }
|
|
232
|
+
504: { title: 'Шлюз не отвечает (Gateway Timeout)', message: 'Превышено время ожидания ответа от upstream-сервера', hint: 'Попробуйте повторить запрос позже.' },
|
|
233
|
+
FETCH_FAILED: { title: 'Сетевой запрос не удался', message: 'Не удалось связаться с сервером (Failed to fetch)', hint: 'Проверьте сетевое подключение и доступность адреса.' },
|
|
234
|
+
CORS_ERROR: { title: 'Ошибка CORS', message: 'Запрос заблокирован политикой безопасности браузера', hint: 'Проверьте заголовки разрешённых источников на сервере.' },
|
|
235
|
+
WS_CLOSED: { title: 'Разрыв WebSocket', message: 'Связь с сервером по WebSocket прервана', hint: 'Проверьте работу сервера и сетевой статус.' },
|
|
236
|
+
QUOTA_EXCEEDED: { title: 'Исчерпана квота API', message: 'Превышен лимит запросов или баланс провайдера', hint: 'Проверьте баланс API-ключа в кабинете провайдера.' },
|
|
237
|
+
CONTEXT_OVERFLOW: { title: 'Превышен контекст', message: 'Объём диалога превышает размер контекстного окна', hint: 'Очистите или сожмите историю сообщений сессии.' },
|
|
238
|
+
MODEL_NOT_FOUND: { title: 'Модель не найдена', message: 'Указанная модель отсутствует или недоступна', hint: 'Проверьте идентификатор модели в настройках.' }
|
|
188
239
|
}
|
|
189
|
-
|
|
190
240
|
const fromMap = (key, rawMsg) => ({
|
|
191
241
|
code: String(key), title: ERROR_MAP[key].title, message: ERROR_MAP[key].message, hint: ERROR_MAP[key].hint, raw: rawMsg
|
|
192
242
|
})
|
|
193
|
-
|
|
194
243
|
const humanizeError = (err) => {
|
|
195
244
|
if (!err) return null
|
|
196
245
|
const rawMsg = typeof err === 'string' ? err : (err.message || String(err))
|
|
@@ -200,12 +249,23 @@ const humanizeError = (err) => {
|
|
|
200
249
|
if (lookupKey && ERROR_MAP[lookupKey]) return fromMap(lookupKey, rawMsg)
|
|
201
250
|
if (/rate limit|too many requests/i.test(rawMsg)) return fromMap(429, rawMsg)
|
|
202
251
|
if (/unauthorized|invalid token|invalid api key/i.test(rawMsg)) return fromMap(401, rawMsg)
|
|
252
|
+
if (/failed to fetch|network\s*error|load\s*failed|net::err_/i.test(rawMsg)) return fromMap('FETCH_FAILED', rawMsg)
|
|
253
|
+
if (/cors|cross-origin/i.test(rawMsg)) return fromMap('CORS_ERROR', rawMsg)
|
|
254
|
+
if (/websocket.*(?:closed|failed)/i.test(rawMsg)) return fromMap('WS_CLOSED', rawMsg)
|
|
255
|
+
if (/insufficient_quota|quota\s*exceeded/i.test(rawMsg)) return fromMap('QUOTA_EXCEEDED', rawMsg)
|
|
256
|
+
if (/context.*(?:exceeded|overflow|length)/i.test(rawMsg)) return fromMap('CONTEXT_OVERFLOW', rawMsg)
|
|
257
|
+
if (/model.*not.*found|model.*does not exist/i.test(rawMsg)) return fromMap('MODEL_NOT_FOUND', rawMsg)
|
|
203
258
|
return { code: 'UNKNOWN', title: 'Ошибка операции', message: rawMsg, hint: 'Проверьте параметры операции и логи.', raw: rawMsg }
|
|
204
259
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
260
|
+
const formatErrorToast = (err) => {
|
|
261
|
+
const h = humanizeError(err)
|
|
262
|
+
if (!h) return null
|
|
263
|
+
return {
|
|
264
|
+
type: 'error',
|
|
265
|
+
title: h.title,
|
|
266
|
+
description: h.message + (h.hint ? ' ' + h.hint : '')
|
|
267
|
+
}
|
|
268
|
+
}
|
|
209
269
|
const makeIssueUrl = (opts = {}, version = '') => {
|
|
210
270
|
const repo = 'GooDAnDReaDY/dsh-russian-lang'
|
|
211
271
|
const title = opts.title || (opts.plugin
|
|
@@ -234,45 +294,31 @@ const makeIssueUrl = (opts = {}, version = '') => {
|
|
|
234
294
|
params.set('body', bodyLines.join('\n'))
|
|
235
295
|
return `https://github.com/${repo}/issues/new?` + params.toString()
|
|
236
296
|
}
|
|
237
|
-
|
|
238
|
-
/** Словари приходят параметром — в бандле это RU, в тесте фикстура. */
|
|
239
297
|
const makePluginLocalizationStatus = (dicts) => (ns) => {
|
|
240
298
|
const none = { status: 'none', count: 0, label: 'RU отсутствует' }
|
|
241
299
|
if (!ns) return none
|
|
242
300
|
const count = Object.keys((dicts && dicts[ns]) || {}).length
|
|
243
301
|
return count > 0 ? { status: 'full', count, label: `RU: ${count} строк` } : none
|
|
244
302
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
303
|
const typoQuotes = (text) => text
|
|
248
304
|
.replace(/"([^"\n]{1,200})"/g, '«$1»')
|
|
249
305
|
.replace(/[“„]([^“”\n]{1,200})[”"]/g, '«$1»')
|
|
250
306
|
.replace(/[『「]([^』」\n]{1,200})[』」]/g, '«$1»')
|
|
251
|
-
|
|
252
307
|
const typoDash = (text) => text
|
|
253
308
|
.replace(/(^|[\s(\[«])--(?=\s|$)/g, '$1—')
|
|
254
309
|
.replace(/(^|[\s(\[«])-(?=\s)/g, '$1—')
|
|
255
|
-
|
|
256
310
|
const typoPunct = (text) => text.replace(/\s+([,.:;!?])(?=\s|$)/g, '$1')
|
|
257
|
-
|
|
258
311
|
const TYPO_SHORT = new Set(['в', 'с', 'к', 'о', 'у', 'а', 'и', 'но', 'не', 'ни', 'на', 'по', 'до', 'из', 'за', 'от', 'об'])
|
|
259
|
-
|
|
260
312
|
const typoNbsp = (text) => text.replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (
|
|
261
313
|
TYPO_SHORT.has(word) ? lead + word + ' ' : match
|
|
262
314
|
))
|
|
263
|
-
|
|
264
315
|
const YO_EDGE_L = '(?<![а-яёА-ЯЁ])'
|
|
265
316
|
const YO_EDGE_R = '(?![а-яёА-ЯЁ])'
|
|
266
|
-
|
|
267
|
-
/** Восстанавливает регистр совпадения: «ЕЩЕ» -> «ЕЩЁ», «Еще» -> «Ещё». */
|
|
268
317
|
const yoCase = (src, repl) => {
|
|
269
318
|
if (src === src.toUpperCase() && src !== src.toLowerCase()) return repl.toUpperCase()
|
|
270
319
|
if (src[0] === src[0].toUpperCase()) return repl[0].toUpperCase() + repl.slice(1)
|
|
271
320
|
return repl
|
|
272
321
|
}
|
|
273
|
-
|
|
274
|
-
/** pairs — [[«еще», «ещё»], ...] из build.py (ручные + корпусные, за вычетом
|
|
275
|
-
* омографов). Регистр разбирает yoCase, поэтому пара нужна одна на слово. */
|
|
276
322
|
const makeTypoYo = (pairs) => {
|
|
277
323
|
const compiled = pairs.map((p) => [new RegExp(YO_EDGE_L + p[0] + YO_EDGE_R, 'gi'), p[1]])
|
|
278
324
|
return (text) => {
|
|
@@ -280,25 +326,17 @@ const makeTypoYo = (pairs) => {
|
|
|
280
326
|
return text
|
|
281
327
|
}
|
|
282
328
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
329
|
const LAYOUT_LAT_TO_CYR = { ...EN_RU_KEYS, '/': '.', '`': 'ё' }
|
|
286
|
-
|
|
287
330
|
const LAYOUT_CYR_TO_LAT = (() => {
|
|
288
331
|
const out = {}
|
|
289
332
|
for (const k in LAYOUT_LAT_TO_CYR) out[LAYOUT_LAT_TO_CYR[k]] = k
|
|
290
333
|
return out
|
|
291
334
|
})()
|
|
292
|
-
|
|
293
335
|
const translit = (word, map) => {
|
|
294
336
|
let out = ''
|
|
295
337
|
for (const ch of word.toLowerCase()) out += map[ch] !== undefined ? map[ch] : ch
|
|
296
338
|
return out
|
|
297
339
|
}
|
|
298
|
-
|
|
299
|
-
/** freq — частотный корпус, localDict — выученные за сессию слова (#67).
|
|
300
|
-
* Оба приходят снаружи: в бандле это встроенный список и Set в памяти,
|
|
301
|
-
* в тесте — фикстуры. */
|
|
302
340
|
const makeLayout = (freq, localDict = new Set()) => {
|
|
303
341
|
const ruWordFraction = (text) => {
|
|
304
342
|
const words = text.toLowerCase().split(/[^а-яё]+/).filter(Boolean)
|
|
@@ -322,38 +360,21 @@ const makeLayout = (freq, localDict = new Set()) => {
|
|
|
322
360
|
}
|
|
323
361
|
return { ruWordFraction, candidate, learnWords, localDict }
|
|
324
362
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
363
|
const SHORT_WORDS_RE = /(^|[\s(«])([вВнНсСпПоОкКуУиИаА]|из|от|до|за|по|со|во|ко|об|на|под|над|при|про|без|для|не|ни|но)( )/g
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Живая типографика для поля ввода DSH.
|
|
332
|
-
* Заменяет кавычки "" на «», дефисы -- на тире —, многоточие ... на …,
|
|
333
|
-
* и добавляет неразрывные пробелы после коротких предлогов.
|
|
334
|
-
* ИГНОРИРУЕТ код внутри обратных кавычек `...` и блоков ```...```.
|
|
335
|
-
*/
|
|
336
364
|
const formatInputLive = (text) => {
|
|
337
365
|
if (!text || typeof text !== 'string') return text
|
|
338
|
-
|
|
339
366
|
const parts = text.split(/(```[\s\S]*?```|`[^`\n]*`)/g)
|
|
340
367
|
for (let i = 0; i < parts.length; i += 2) {
|
|
341
368
|
let s = parts[i]
|
|
342
369
|
if (!s) continue
|
|
343
|
-
|
|
344
370
|
s = s.replace(/--/g, '—')
|
|
345
|
-
|
|
346
371
|
s = s.replace(/(^|[\s([{-])"/g, '$1«')
|
|
347
372
|
s = s.replace(/"/g, '»')
|
|
348
|
-
|
|
349
373
|
s = s.replace(SHORT_WORDS_RE, '$1$2\u00A0')
|
|
350
|
-
|
|
351
374
|
parts[i] = s
|
|
352
375
|
}
|
|
353
376
|
return parts.join('')
|
|
354
377
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
378
|
const RUSSIAN_SLASH_ALIASES = {
|
|
358
379
|
'/цель': '/goal',
|
|
359
380
|
'/справка': '/help',
|
|
@@ -371,11 +392,6 @@ const RUSSIAN_SLASH_ALIASES = {
|
|
|
371
392
|
'/сессия': '/session',
|
|
372
393
|
'/модель': '/model'
|
|
373
394
|
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Разворачивает русский алиас слэш-команды в каноническую команду DSH.
|
|
377
|
-
* Например: "/цель сделать тесты" -> "/goal сделать тесты"
|
|
378
|
-
*/
|
|
379
395
|
const expandSlashAlias = (input) => {
|
|
380
396
|
if (!input || typeof input !== 'string' || !input.startsWith('/')) return input
|
|
381
397
|
const match = input.match(/^(\s*\/[^\s]+)(.*)$/)
|
|
@@ -387,8 +403,6 @@ const expandSlashAlias = (input) => {
|
|
|
387
403
|
}
|
|
388
404
|
return input
|
|
389
405
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
406
|
const PHONETIC_LAT_TO_CYR = [
|
|
393
407
|
['shch', 'щ'], ['yo', 'ё'], ['zh', 'ж'], ['ch', 'ч'], ['sh', 'ш'],
|
|
394
408
|
['yu', 'ю'], ['ya', 'я'], ['ts', 'ц'],
|
|
@@ -397,7 +411,6 @@ const PHONETIC_LAT_TO_CYR = [
|
|
|
397
411
|
['n', 'н'], ['o', 'о'], ['p', 'п'], ['r', 'р'], ['s', 'с'], ['t', 'т'],
|
|
398
412
|
['u', 'у'], ['f', 'ф'], ['h', 'х'], ['c', 'ц'], ['y', 'ы'], ['x', 'кс']
|
|
399
413
|
]
|
|
400
|
-
|
|
401
414
|
const PHONETIC_CYR_TO_LAT = [
|
|
402
415
|
['щ', 'shch'], ['ё', 'yo'], ['ж', 'zh'], ['ч', 'ch'], ['ш', 'sh'],
|
|
403
416
|
['ю', 'yu'], ['я', 'ya'], ['ц', 'ts'],
|
|
@@ -406,7 +419,6 @@ const PHONETIC_CYR_TO_LAT = [
|
|
|
406
419
|
['н', 'n'], ['о', 'o'], ['п', 'p'], ['р', 'r'], ['с', 's'], ['т', 't'],
|
|
407
420
|
['у', 'u'], ['ф', 'f'], ['х', 'h'], ['ы', 'y'], ['э', 'e'], ['ъ', ''], ['ь', '']
|
|
408
421
|
]
|
|
409
|
-
|
|
410
422
|
const phoneticTranslit = (text, direction = 'lat2cyr') => {
|
|
411
423
|
if (!text || typeof text !== 'string') return text
|
|
412
424
|
let res = text
|
|
@@ -416,7 +428,6 @@ const phoneticTranslit = (text, direction = 'lat2cyr') => {
|
|
|
416
428
|
const fromTitle = from[0].toUpperCase() + from.slice(1)
|
|
417
429
|
const toUpper = to.toUpperCase()
|
|
418
430
|
const toTitle = to[0] ? to[0].toUpperCase() + to.slice(1) : ''
|
|
419
|
-
|
|
420
431
|
if (from.length > 1) {
|
|
421
432
|
res = res.replaceAll(fromUpper, toUpper)
|
|
422
433
|
res = res.replaceAll(fromTitle, toTitle)
|
|
@@ -428,11 +439,6 @@ const phoneticTranslit = (text, direction = 'lat2cyr') => {
|
|
|
428
439
|
}
|
|
429
440
|
return res
|
|
430
441
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Определяет преобладающий язык ввода в тексте: 'RU', 'EN' или null.
|
|
435
|
-
*/
|
|
436
442
|
const detectInputLayout = (text) => {
|
|
437
443
|
if (!text || typeof text !== 'string') return null
|
|
438
444
|
const clean = text.replace(/`[^`]*`/g, '').replace(/https?:\/\/\S+/g, '')
|
|
@@ -447,33 +453,44 @@ const detectInputLayout = (text) => {
|
|
|
447
453
|
if (lat > cyr && lat >= 2) return 'EN'
|
|
448
454
|
return null
|
|
449
455
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
456
|
const SYSTEM_PROMPT_PRESETS = {
|
|
453
457
|
technical_expert: {
|
|
454
458
|
id: 'technical_expert',
|
|
455
459
|
label: 'Технический эксперт',
|
|
456
|
-
desc: '
|
|
457
|
-
text: 'Отвечай пользователю на русском языке. Используй точную
|
|
460
|
+
desc: 'Точная терминология, чистый код и русские комментарии',
|
|
461
|
+
text: 'Отвечай пользователю на русском языке. Используй точную инженерную терминологию, пиши чистый код и русские комментарии.'
|
|
458
462
|
},
|
|
459
463
|
tech_writer: {
|
|
460
464
|
id: 'tech_writer',
|
|
461
465
|
label: 'Технический писатель',
|
|
462
|
-
desc: 'Структурированные тексты, Markdown
|
|
463
|
-
text: 'Отвечай пользователю на русском языке. Оформляй документацию
|
|
466
|
+
desc: 'Структурированные тексты, Markdown и выверенная типографика',
|
|
467
|
+
text: 'Отвечай пользователю на русском языке. Оформляй документацию в Markdown, строго соблюдай правила русской типографики («ёлочки», тире, «ё»).'
|
|
464
468
|
},
|
|
465
469
|
concise: {
|
|
466
470
|
id: 'concise',
|
|
467
471
|
label: 'Лаконичный режим',
|
|
468
|
-
desc: 'Краткие ёмкие ответы без
|
|
469
|
-
text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без
|
|
472
|
+
desc: 'Краткие ёмкие ответы без вводных слов и воды',
|
|
473
|
+
text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без вводных слов и воды, сразу код или решение.'
|
|
474
|
+
},
|
|
475
|
+
code_reviewer: {
|
|
476
|
+
id: 'code_reviewer',
|
|
477
|
+
label: 'Код-ревьюер',
|
|
478
|
+
desc: 'Поиск багов, безопасность и архитектурный аудит',
|
|
479
|
+
text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
|
|
480
|
+
},
|
|
481
|
+
architect: {
|
|
482
|
+
id: 'architect',
|
|
483
|
+
label: 'Системный архитектор',
|
|
484
|
+
desc: 'Контракты API/CLI, модульность и надёжность',
|
|
485
|
+
text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
|
|
486
|
+
},
|
|
487
|
+
tutor: {
|
|
488
|
+
id: 'tutor',
|
|
489
|
+
label: 'Наставник (ментор)',
|
|
490
|
+
desc: 'Понятные объяснения сложных концепций и пошаговый разбор',
|
|
491
|
+
text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
|
|
470
492
|
}
|
|
471
493
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Генерирует локализованный Markdown-отчёт по сессии диалога.
|
|
476
|
-
*/
|
|
477
494
|
const exportSessionToMarkdown = (session, options = {}) => {
|
|
478
495
|
if (!session) return ''
|
|
479
496
|
const title = session.title || session.name || 'Диалог DSH'
|
|
@@ -482,21 +499,18 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
482
499
|
dateStyle: 'full',
|
|
483
500
|
timeStyle: 'medium'
|
|
484
501
|
}).format(date)
|
|
485
|
-
|
|
486
502
|
const messages = Array.isArray(session.messages) ? session.messages : []
|
|
487
503
|
let md = `# 💬 ${title}\n\n`
|
|
488
504
|
md += `> **Дата экспорта:** ${dateStr}\n`
|
|
489
505
|
if (session.model) md += `> **Модель:** \`${session.model}\`\n`
|
|
490
506
|
if (session.workspace) md += `> **Рабочая область:** \`${session.workspace}\`\n`
|
|
491
507
|
md += `> **Всего сообщений:** ${messages.length}\n\n---\n\n`
|
|
492
|
-
|
|
493
508
|
for (let idx = 0; idx < messages.length; idx++) {
|
|
494
509
|
const msg = messages[idx]
|
|
495
510
|
const role = msg.role || 'unknown'
|
|
496
511
|
const roleName = role === 'user' ? '👤 Пользователь' :
|
|
497
512
|
role === 'assistant' ? '🤖 Ассистент' :
|
|
498
513
|
role === 'system' ? '⚙️ Система' : `🔧 Инструмент (${role})`
|
|
499
|
-
|
|
500
514
|
md += `### ${roleName}\n\n`
|
|
501
515
|
if (msg.content) {
|
|
502
516
|
md += `${msg.content}\n\n`
|
|
@@ -510,63 +524,264 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
510
524
|
}
|
|
511
525
|
md += `---\n\n`
|
|
512
526
|
}
|
|
513
|
-
|
|
514
527
|
md += `*Сгенерировано с помощью [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) и @goodandready/dsh-russian-lang*\n`
|
|
515
528
|
return md
|
|
516
529
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
530
|
+
const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}) => {
|
|
531
|
+
if (!text || typeof text !== 'string') return null
|
|
532
|
+
const trimmed = text.trim()
|
|
533
|
+
if (!trimmed) return null
|
|
534
|
+
if (overrides && typeof overrides === 'object') {
|
|
535
|
+
for (const [k, v] of Object.entries(overrides)) {
|
|
536
|
+
if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {
|
|
537
|
+
return { key: k, value: v, source: 'override' }
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (dicts && typeof dicts === 'object') {
|
|
542
|
+
for (const [ns, map] of Object.entries(dicts)) {
|
|
543
|
+
if (!map || typeof map !== 'object') continue
|
|
544
|
+
for (const [k, v] of Object.entries(map)) {
|
|
545
|
+
if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {
|
|
546
|
+
return { ns, key: k, value: v, source: 'dictionary' }
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (zhRu && typeof zhRu === 'object') {
|
|
552
|
+
for (const [zh, ru] of Object.entries(zhRu)) {
|
|
553
|
+
if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {
|
|
554
|
+
return { zh, key: zh, value: ru, source: 'dom_zh' }
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return null
|
|
559
|
+
}
|
|
560
|
+
const bilingualCommandMatch = (query, command, options = {}) => {
|
|
561
|
+
if (!query || !command) return { matched: false, score: 0 }
|
|
562
|
+
const q = String(query).trim().toLowerCase()
|
|
563
|
+
if (!q) return { matched: false, score: 0 }
|
|
564
|
+
const cmdObj = typeof command === 'string' ? { title: command } : command
|
|
565
|
+
const candidates = [
|
|
566
|
+
cmdObj.title,
|
|
567
|
+
cmdObj.label,
|
|
568
|
+
cmdObj.name,
|
|
569
|
+
cmdObj.originalTitle,
|
|
570
|
+
cmdObj.enTitle,
|
|
571
|
+
cmdObj.id,
|
|
572
|
+
cmdObj.description,
|
|
573
|
+
cmdObj.category,
|
|
574
|
+
...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
|
|
575
|
+
].filter((c) => typeof c === 'string' && c.trim())
|
|
576
|
+
if (!candidates.length) return { matched: false, score: 0 }
|
|
577
|
+
const queryVariants = [{ text: q, kind: 'direct' }]
|
|
578
|
+
const lat2cyr = translit(q, LAYOUT_LAT_TO_CYR)
|
|
579
|
+
if (lat2cyr !== q) queryVariants.push({ text: lat2cyr, kind: 'layout' })
|
|
580
|
+
const cyr2lat = translit(q, LAYOUT_CYR_TO_LAT)
|
|
581
|
+
if (cyr2lat !== q) queryVariants.push({ text: cyr2lat, kind: 'layout' })
|
|
582
|
+
const phonLat2Cyr = phoneticTranslit(q, 'lat2cyr')
|
|
583
|
+
if (phonLat2Cyr && phonLat2Cyr !== q && phonLat2Cyr !== lat2cyr) {
|
|
584
|
+
queryVariants.push({ text: phonLat2Cyr, kind: 'phonetic' })
|
|
585
|
+
}
|
|
586
|
+
const phonCyr2Lat = phoneticTranslit(q, 'cyr2lat')
|
|
587
|
+
if (phonCyr2Lat && phonCyr2Lat !== q && phonCyr2Lat !== cyr2lat) {
|
|
588
|
+
queryVariants.push({ text: phonCyr2Lat, kind: 'phonetic' })
|
|
589
|
+
}
|
|
590
|
+
let bestScore = 0
|
|
591
|
+
let bestVariant = null
|
|
592
|
+
let bestCandidate = null
|
|
593
|
+
for (const variant of queryVariants) {
|
|
594
|
+
for (const cand of candidates) {
|
|
595
|
+
const score = fuzzyMatchRu(variant.text, cand)
|
|
596
|
+
if (score > bestScore) {
|
|
597
|
+
bestScore = score
|
|
598
|
+
bestVariant = variant
|
|
599
|
+
bestCandidate = cand
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const threshold = options.threshold ?? 50
|
|
604
|
+
return {
|
|
605
|
+
matched: bestScore >= threshold,
|
|
606
|
+
score: bestScore,
|
|
607
|
+
matchedText: bestCandidate,
|
|
608
|
+
variant: bestVariant ? bestVariant.text : q,
|
|
609
|
+
kind: bestVariant ? bestVariant.kind : 'none'
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const filterCommands = (query, commands = [], options = {}) => {
|
|
613
|
+
if (!Array.isArray(commands)) return []
|
|
614
|
+
if (!query || !String(query).trim()) return commands
|
|
615
|
+
const results = []
|
|
616
|
+
for (const cmd of commands) {
|
|
617
|
+
const res = bilingualCommandMatch(query, cmd, options)
|
|
618
|
+
if (res.matched) {
|
|
619
|
+
results.push({ item: cmd, ...res })
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return results.sort((a, b) => b.score - a.score).map((r) => r.item)
|
|
623
|
+
}
|
|
624
|
+
const RU = {"common":{"back":"Назад","brand.localBuild":"Локальная сборка DSH","cancel":"Отмена","close":"Закрыть","collapse":"Свернуть","copied":"Скопировано","copy":"Копировать","copy.compactJson":"Копировать компактный JSON","copy.failed":"Не удалось скопировать","copy.json":"Копировать JSON","copy.optionsHint":"{action}; правый клик для вариантов копирования","copy.path":"Копировать путь к свойству","copy.prettyJson":"Копировать форматированный JSON","copy.value":"Копировать значение","delete":"Удалить","edit":"Изменить","expand":"Развернуть","json.collapseNode":"Свернуть узел JSON","json.expandNode":"Развернуть узел JSON","json.label":"JSON","load.failed":"Не удалось загрузить","loading":"Загрузка…","markdown.footnotes":"Сноски","markdown.truncatedCharacters":"… обрезано на {total} символах","more":"Ещё","next":"Дальше","none":"Нет","number.million":"{value}M","number.thousand":"{value}K","ok":"ОК","previous":"Назад","retry":"Повторить","save":"Сохранить","search":"Поиск","skip":"Пропустить","submit":"Отправить","submitting":"Отправка…","truncated":"Обрезано","unknown":"Неизвестно"},"dsh-cron":{"sidebar.label":"Задачи по расписанию"},"dsh-usage-stats":{"footer.todayLabel":"Сегодня"},"pluginMarket":{"trigger":"Магазин плагинов"}}
|
|
625
|
+
const ZH_RU = {}
|
|
626
|
+
RU['russian-lang'] = {"cardTitle":"Русская локализация","cardSub":"Язык интерфейса, типографика, раскладка","badgeRu":"🟢 RU активен","badgeEn":"⚪ EN активен","badgeCoverage":"🟢 100% (7,902 ключа)","badgeSmartUx":"⚡ Smart UX активен","secLanguage":"🌐 Язык интерфейса","secLanguageDesc":"Нативное переключение языка интерфейса DSH на русский без перезагрузки страницы.","enabled":"Русский язык включён","enabledDesc":"Переключает язык интерфейса DeepSeek Harness на русский.","quickSwitchNote":"Быстрый переключатель RU ⇄ EN доступен в шапке сессии рядом с кнопками диалога.","secTypography":"✍️ Умная типографика и ввод (Smart UX)","secTypographyDesc":"Автоматическое улучшение текстов по нормам русской типографики во время диалога и набора.","typography":"Типографика вывода","typographyDesc":"Исправляет типографику ответов модели: кавычки-«ёлочки», тире («—») вместо дефисов, неразрывные пробелы после предлогов.","yo":"Буква «ё»","yoDesc":"Восстанавливать «ё» в частых словах (ещё, чёрный, идёт и др.), написанных через «е». Неоднозначные слова (все/всё) не трогаются.","liveInput":"Живая типографика инпута","liveInputDesc":"Автоматически заменять \"\" на «» и -- на — прямо во время набора промпта (код в бэктиках игнорируется).","slashAliases":"Русские алиасы команд","slashAliasesDesc":"Поддержка русских команд: /цель -> /goal, /сжать -> /compact, /план -> /plan, /справка -> /help, /память -> /memory.","altLHintText":"Мгновенная конвертация раскладки текущего поля (ghbdtn ⇄ привет, /vjltkm ⇄ /model). В углу поля ввода также отображается метка раскладки.","secAgentPrompt":"🤖 Системный промпт агента","secAgentPromptDesc":"Официальная секция расширения DSH systemPrompt для ведения диалога на русском языке.","agentPrompt":"Русский промпт агента","agentPromptDesc":"Добавляет в системный промпт инструкцию отвечать по-русски в выбранном стиле.","agentPromptPreset":"Стиль ответов агента","presetExpert":"Технический эксперт (строгая терминология, чистый код)","presetWriter":"Технический писатель (Markdown, таблицы, ГОСТ)","presetConcise":"Лаконичный режим (кратко, без лишней воды)","presetReviewer":"Код-ревьюер (поиск багов, безопасность, аудит)","presetArchitect":"Системный архитектор (контракты, масштабируемость)","presetTutor":"Наставник (понятные объяснения, пошаговый разбор)","secUpdater":"🔄 Обновление языкового пакета","secUpdaterDesc":"Проверка наличия новых релизов в реестре npm и обновление в один клик.","updaterCurrent":"Текущая версия: v{version}","updaterLatest":"Доступна новая версия: v{version}","updaterUpToDate":"Установлена актуальная версия","updaterChecking":"Проверка…","updaterCheckBtn":"Проверить обновления","updaterBtn":"Обновить до v{version} в 1 клик","updaterUpdating":"Установка обновления…","updaterSuccess":"✅ Плагин успешно обновлён! Перезапустите DSH для применения.","updaterFailed":"❌ Не удалось проверить/обновить плагин. Проверьте сеть или логи сервера.","badgeUpdateAvailable":"Доступно обновление","badgeUpToDate":"Актуальная версия","secTranslator":"🌐 Перевод сообщений ассистента","secTranslatorDesc":"Настройка перевода ответов модели в чате на русский язык (локально в Docker или онлайн).","translateEngine":"Движок перевода","engineOff":"Выключен (по умолчанию, без сетевых вызовов)","engineLocal":"Локальный LibreTranslate (приватно, ~600 МБ RAM)","engineGoogle":"Google Translate (онлайн)","googleWarn":"⚠️ Внимание: при онлайн-переводе текст сообщений ассистента передаётся на публичные серверы Google для перевода на лету. Не используйте для конфиденциальных данных.","localStatusRunning":"🟢 Контейнер LibreTranslate активен","localStatusStopped":"⚪ Контейнер LibreTranslate не запущен","localStartBtn":"Запустить LibreTranslate в Docker (~600 МБ RAM)","localStarting":"Запуск контейнера…","localInfo":"LibreTranslate запускается изолированно в Docker на сервере, потребляет ~500–700 МБ RAM и переводит на 100% локально.","secOverrides":"✍️ Пользовательские переопределения (Custom Overrides)","secOverridesDesc":"Возможность заменить любую формулировку интерфейса DSH на собственную. Переопределения сохраняются в настройках и имеют наивысший приоритет.","overrideKeyPlaceholder":"Ключ (например, common.back или settings.title)","overrideValuePlaceholder":"Ваш русский перевод","overrideAddBtn":"Добавить","overrideEmpty":"Переопределений пока нет.","overrideDelete":"Удалить","overrideTip":"💡 Совет: зажмите Alt и кликните на любой переведённый элемент интерфейса, чтобы открыть всплывающий инспектор перевода и сразу изменить формулировку.","secSupport":"📊 Покрытие экосистемы и поддержка","secSupportDesc":"Словари синхронизированы с DSH v0.1.6-alpha.1. 100.0% UI-покрытие ядра и всех установленных плагинов (7,902 ключа) без черновых машинных переводов.","statNamespaces":"Пространств имён","statCoreKeys":"Ключей ядра","statPluginKeys":"Ключей плагинов","overridesCount":"Своих переопределений","statusLoading":"Настройки загружаются…","statusUnavailable":"Настройки недоступны на этом хосте","translateTurn":"Перевести на русский","reportIssue":"Сообщить о неточности перевода","exportMdHint":"Экспорт диалога в Markdown доступен по кнопке [ 📥 MD ] в шапке сессии.","translateTurnHint":"Перевод ответов ассистента на русский доступен по кнопке [ RU ↗ ] на блоках сообщений."}
|
|
527
627
|
const SETTINGS_NS_NAME = 'russian-lang'
|
|
528
628
|
const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
|
|
529
|
-
|
|
530
629
|
function apply(ctx) {
|
|
531
630
|
const runtime = ctx.locale
|
|
532
631
|
const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NS_NAME })
|
|
533
|
-
|
|
534
632
|
for (const ns of Object.keys(RU)) {
|
|
535
633
|
ctx.effect(() => {
|
|
536
634
|
try { return ctx.locale.register(ns, 'ru', RU[ns]) }
|
|
537
635
|
catch (err) { return () => {} }
|
|
538
636
|
}, 'dsh-russian-lang: ' + ns)
|
|
539
637
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
638
|
+
const registerDictMap = (dictMap) => {
|
|
639
|
+
if (!dictMap || typeof dictMap !== 'object') return
|
|
640
|
+
for (const ns of Object.keys(dictMap)) {
|
|
641
|
+
if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
|
|
642
|
+
const dict = dictMap[ns]
|
|
643
|
+
if (typeof dict !== 'object' || !dict) continue
|
|
644
|
+
if (!RU[ns]) {
|
|
645
|
+
RU[ns] = dict
|
|
646
|
+
ctx.effect(() => {
|
|
647
|
+
try { return ctx.locale.register(ns, 'ru', dict) }
|
|
648
|
+
catch (err) { return () => {} }
|
|
649
|
+
}, 'dsh-russian-lang: ' + ns)
|
|
650
|
+
} else {
|
|
651
|
+
Object.assign(RU[ns], dict)
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const CACHE_NAME = 'dsh-ru-cache-v1'
|
|
656
|
+
const loadLocalDict = (key) => {
|
|
657
|
+
try {
|
|
658
|
+
const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
|
|
659
|
+
return raw ? JSON.parse(raw) : null
|
|
660
|
+
} catch (_) { return null }
|
|
661
|
+
}
|
|
662
|
+
const saveLocalDict = (key, etag, data) => {
|
|
663
|
+
try {
|
|
664
|
+
if (typeof localStorage !== 'undefined') {
|
|
665
|
+
localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
|
|
666
|
+
}
|
|
667
|
+
} catch (_e) { /* noop */ }
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
const cachedCore = loadLocalDict('core')
|
|
671
|
+
if (cachedCore && cachedCore.data && cachedCore.data.core) {
|
|
672
|
+
registerDictMap(cachedCore.data.core)
|
|
673
|
+
if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {
|
|
674
|
+
Object.assign(ZH_RU, cachedCore.data.zhRu)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
} catch (_e) { /* noop */ }
|
|
678
|
+
const fetchCachedResource = async (url, cacheKey, onData) => {
|
|
679
|
+
let etag = null
|
|
680
|
+
let delivered = false
|
|
681
|
+
if (typeof caches !== 'undefined') {
|
|
682
|
+
try {
|
|
683
|
+
const cache = await caches.open(CACHE_NAME)
|
|
684
|
+
const matched = await cache.match(url)
|
|
685
|
+
if (matched) {
|
|
686
|
+
etag = matched.headers.get('etag')
|
|
687
|
+
const parsed = await matched.json()
|
|
688
|
+
if (parsed) {
|
|
689
|
+
delivered = true
|
|
690
|
+
onData(parsed)
|
|
691
|
+
}
|
|
550
692
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
693
|
+
} catch (_e) { /* noop */ }
|
|
694
|
+
}
|
|
695
|
+
if (!delivered && cacheKey) {
|
|
696
|
+
const local = loadLocalDict(cacheKey)
|
|
697
|
+
if (local && local.data) {
|
|
698
|
+
etag = local.etag
|
|
699
|
+
delivered = true
|
|
700
|
+
onData(local.data)
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (typeof fetch !== 'function') return
|
|
704
|
+
try {
|
|
705
|
+
const headers = { 'Accept': 'application/json' }
|
|
706
|
+
if (etag) headers['If-None-Match'] = etag
|
|
707
|
+
const res = await fetch(url, { headers })
|
|
708
|
+
if (res.status === 304) return
|
|
709
|
+
if (res.ok) {
|
|
710
|
+
const newEtag = res.headers.get('etag')
|
|
711
|
+
const cloned = res.clone()
|
|
712
|
+
const data = await res.json()
|
|
713
|
+
if (data) {
|
|
714
|
+
onData(data)
|
|
715
|
+
if (typeof caches !== 'undefined') {
|
|
716
|
+
try {
|
|
717
|
+
const cache = await caches.open(CACHE_NAME)
|
|
718
|
+
await cache.put(url, cloned)
|
|
719
|
+
} catch (_e) { /* noop */ }
|
|
563
720
|
}
|
|
721
|
+
if (cacheKey) saveLocalDict(cacheKey, newEtag, data)
|
|
564
722
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
.catch(() => {})
|
|
723
|
+
}
|
|
724
|
+
} catch (_e) { /* noop */ }
|
|
568
725
|
}
|
|
569
|
-
|
|
726
|
+
const loadedPluginNames = new Set()
|
|
727
|
+
const pendingPluginLoads = new Set()
|
|
728
|
+
let pluginBatchTimer = null
|
|
729
|
+
const loadPluginDictionaries = (names) => {
|
|
730
|
+
if (typeof fetch !== 'function') return
|
|
731
|
+
const toLoad = []
|
|
732
|
+
for (const raw of names) {
|
|
733
|
+
const n = String(raw).trim()
|
|
734
|
+
if (!n || loadedPluginNames.has(n) || pendingPluginLoads.has(n)) continue
|
|
735
|
+
toLoad.push(n)
|
|
736
|
+
pendingPluginLoads.add(n)
|
|
737
|
+
}
|
|
738
|
+
const qs = toLoad.length > 0 ? ('?names=' + encodeURIComponent(toLoad.join(','))) : ''
|
|
739
|
+
const url = '/api/dsh-russian-lang/dict/plugins' + qs
|
|
740
|
+
const cacheKey = toLoad.length > 0 ? ('plugins_' + toLoad.sort().join('_')) : 'plugins_all'
|
|
741
|
+
fetchCachedResource(url, cacheKey, (data) => {
|
|
742
|
+
if (data && data.plugins) {
|
|
743
|
+
registerDictMap(data.plugins)
|
|
744
|
+
for (const n of toLoad) loadedPluginNames.add(n)
|
|
745
|
+
}
|
|
746
|
+
}).catch(() => {}).finally(() => {
|
|
747
|
+
for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
748
|
+
})
|
|
749
|
+
}
|
|
750
|
+
const schedulePluginLoad = (name) => {
|
|
751
|
+
if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
|
|
752
|
+
pendingPluginLoads.add(name)
|
|
753
|
+
if (pluginBatchTimer) clearTimeout(pluginBatchTimer)
|
|
754
|
+
pluginBatchTimer = setTimeout(() => {
|
|
755
|
+
const batch = Array.from(pendingPluginLoads).filter((n) => !loadedPluginNames.has(n))
|
|
756
|
+
if (batch.length > 0) loadPluginDictionaries(batch)
|
|
757
|
+
}, 50)
|
|
758
|
+
}
|
|
759
|
+
fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {
|
|
760
|
+
if (!data) return
|
|
761
|
+
if (data.core) registerDictMap(data.core)
|
|
762
|
+
if (data.zhRu && typeof ZH_RU === 'object') {
|
|
763
|
+
Object.assign(ZH_RU, data.zhRu)
|
|
764
|
+
if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
765
|
+
}
|
|
766
|
+
if (typeof syncZhDom === 'function') syncZhDom()
|
|
767
|
+
setTimeout(() => { loadPluginDictionaries([]) }, 100)
|
|
768
|
+
}).catch(() => {
|
|
769
|
+
if (typeof fetch === 'function') {
|
|
770
|
+
fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
|
|
771
|
+
.then((res) => res.ok ? res.json() : null)
|
|
772
|
+
.then((data) => {
|
|
773
|
+
if (!data) return
|
|
774
|
+
const allDicts = Object.assign({}, data.core || {}, data.plugins || {}, data)
|
|
775
|
+
if (data.zhRu && typeof ZH_RU === 'object') {
|
|
776
|
+
Object.assign(ZH_RU, data.zhRu)
|
|
777
|
+
if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
778
|
+
}
|
|
779
|
+
registerDictMap(allDicts)
|
|
780
|
+
if (typeof syncZhDom === 'function') syncZhDom()
|
|
781
|
+
})
|
|
782
|
+
.catch(() => {})
|
|
783
|
+
}
|
|
784
|
+
})
|
|
570
785
|
const origTranslate = runtime.translate.bind(runtime)
|
|
571
786
|
const getOverrides = () => {
|
|
572
787
|
try {
|
|
@@ -577,6 +792,9 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
577
792
|
const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
|
|
578
793
|
try {
|
|
579
794
|
runtime.formatNumber = formatNumber
|
|
795
|
+
runtime.formatCompactNumber = formatCompactNumber
|
|
796
|
+
runtime.formatDate = formatDate
|
|
797
|
+
runtime.formatTokens = formatTokens
|
|
580
798
|
runtime.formatRelativeTime = formatRelativeTime
|
|
581
799
|
runtime.formatCurrency = formatCurrency
|
|
582
800
|
runtime.inflect = inflect
|
|
@@ -584,6 +802,8 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
584
802
|
runtime.stemRussian = stemRussian
|
|
585
803
|
runtime.fuzzyMatchRu = fuzzyMatchRu
|
|
586
804
|
runtime.humanizeError = humanizeError
|
|
805
|
+
runtime.plural = plural
|
|
806
|
+
runtime.pluralForm = pluralForm
|
|
587
807
|
} catch (err) { /* ignore */ }
|
|
588
808
|
const lookupChain = () => {
|
|
589
809
|
try {
|
|
@@ -592,11 +812,13 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
592
812
|
} catch (err) { /* ignore */ }
|
|
593
813
|
return [runtime.getLocale().active]
|
|
594
814
|
}
|
|
815
|
+
const translationRegistry = new Map()
|
|
595
816
|
const lookup = (ns, key) => {
|
|
596
817
|
const chain = lookupChain()
|
|
597
818
|
return runtime.lookup.length >= 3 ? runtime.lookup(ns, key, chain) : runtime.lookup(ns, key)
|
|
598
819
|
}
|
|
599
820
|
runtime.translate = function (ns, key, params) {
|
|
821
|
+
if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
|
|
600
822
|
const overrides = getOverrides()
|
|
601
823
|
if (overrides[key] !== undefined) {
|
|
602
824
|
return params ? fill(overrides[key], params) : overrides[key]
|
|
@@ -623,9 +845,12 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
623
845
|
}
|
|
624
846
|
}
|
|
625
847
|
}
|
|
626
|
-
|
|
848
|
+
const res = origTranslate(ns, key, params)
|
|
849
|
+
if (typeof res === 'string' && res && res.length < 300) {
|
|
850
|
+
translationRegistry.set(res.trim(), { ns, key, params, value: res })
|
|
851
|
+
}
|
|
852
|
+
return res
|
|
627
853
|
}
|
|
628
|
-
|
|
629
854
|
const syncLang = () => {
|
|
630
855
|
try {
|
|
631
856
|
if (typeof document !== 'undefined' && document.documentElement
|
|
@@ -634,15 +859,11 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
634
859
|
}
|
|
635
860
|
} catch (err) { /* ignore */ }
|
|
636
861
|
}
|
|
637
|
-
|
|
638
862
|
const native = runtime.getLocale().locales.some((l) => l.id === 'ru')
|
|
639
|
-
|
|
640
863
|
if (!native) {
|
|
641
864
|
runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
|
|
642
865
|
syncLang()
|
|
643
|
-
|
|
644
866
|
}
|
|
645
|
-
|
|
646
867
|
const syncFlag = () => {
|
|
647
868
|
try {
|
|
648
869
|
const wantRu = runtime.getLocale().active === 'ru'
|
|
@@ -663,7 +884,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
663
884
|
try { return runtime.subscribe(syncFlag) }
|
|
664
885
|
catch (err) { return undefined }
|
|
665
886
|
}, 'dsh-russian-lang: sync-flag')
|
|
666
|
-
|
|
667
887
|
const activate = () => {
|
|
668
888
|
try {
|
|
669
889
|
if (runtime.getLocale().active === 'ru') return
|
|
@@ -680,11 +900,9 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
680
900
|
}
|
|
681
901
|
ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
|
|
682
902
|
tryBoot()
|
|
683
|
-
|
|
684
903
|
const unsubscribeLang = runtime.subscribe(syncLang)
|
|
685
904
|
ctx.effect(() => unsubscribeLang, 'dsh-russian-lang: html-lang')
|
|
686
905
|
syncLang()
|
|
687
|
-
|
|
688
906
|
const SPELL_ON = 'data-russian-lang-spell-on'
|
|
689
907
|
const SPELL_WAS = 'data-russian-lang-spell-was'
|
|
690
908
|
const LANG_WAS = 'data-russian-lang-lang-was'
|
|
@@ -747,63 +965,60 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
747
965
|
}
|
|
748
966
|
}, 'dsh-russian-lang: spellcheck')
|
|
749
967
|
syncSpell()
|
|
750
|
-
|
|
751
968
|
const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
|
|
752
969
|
const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
753
970
|
const ZH_EXACT = new Map()
|
|
754
971
|
const ZH_PATTERNS = []
|
|
755
|
-
|
|
756
972
|
const CORE_ZH_PRESETS = {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
973
|
+
'请求批准': 'Запрашивать подтверждение',
|
|
974
|
+
'完全放开': 'Полный доступ',
|
|
975
|
+
'只读': 'Только чтение',
|
|
976
|
+
'只能读,任何写入都需要审批。': 'Только чтение, любая запись требует подтверждения.',
|
|
977
|
+
'工作区内可写;工作区外的操作请求人工审批。': 'Запись в рабочей области разрешена; операции вне рабочей области требуют подтверждения.',
|
|
978
|
+
'全放行,不弹审批。': 'Полный доступ, запросы на подтверждение не выводятся.',
|
|
979
|
+
'确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.',
|
|
980
|
+
'手动测试': 'Ручное тестирование',
|
|
981
|
+
'事件': 'Событие',
|
|
982
|
+
'模拟 (看匹配)': 'Симуляция (проверка)',
|
|
983
|
+
'执行 (真实触发)': 'Выполнение (реальный триггер)',
|
|
984
|
+
'通知渠道测试': 'Тест каналов уведомлений',
|
|
985
|
+
'渠道': 'Канал',
|
|
986
|
+
'Slack 风格单行摘要': 'Сводка в стиле Slack',
|
|
987
|
+
'发送测试通知': 'Отправить тестовое уведомление',
|
|
988
|
+
'飞书通知': 'Уведомления Feishu',
|
|
989
|
+
'扫码连接飞书': 'Подключить Feishu по QR-коду',
|
|
990
|
+
'将创建名为 [DSH 通知机器人] 的飞书应用': 'Будет создано приложение Feishu [DSH 通知机器人]',
|
|
991
|
+
'卡片截断长度': 'Длина обрезки карточки',
|
|
992
|
+
'复制 YAML': 'Копировать YAML',
|
|
993
|
+
'去抖': 'Дебаунс',
|
|
994
|
+
'已断开': 'Отключено',
|
|
995
|
+
'已连接': 'Подключено',
|
|
996
|
+
'已保存': 'Сохранено',
|
|
997
|
+
'保存失败': 'Не удалось сохранить',
|
|
998
|
+
'编辑': 'Редактировать',
|
|
999
|
+
'取消编辑': 'Отмена',
|
|
1000
|
+
'启用': 'Включить',
|
|
1001
|
+
'停用': 'Отключить',
|
|
1002
|
+
'重试': 'Повторить',
|
|
1003
|
+
'刷新': 'Обновить',
|
|
1004
|
+
'网络请求失败': 'Сетевой запрос не удался',
|
|
1005
|
+
'如未立即生效请重启': 'Если изменения не применились, перезапустите DSH',
|
|
1006
|
+
'通知机器人': 'Бот уведомлений',
|
|
1007
|
+
'飞书扫码授权二维码': 'QR-код авторизации Feishu',
|
|
1008
|
+
'在浏览器中打开飞书授权链接': 'Открыть ссылку авторизации Feishu в браузере',
|
|
1009
|
+
'重新扫码会覆盖现有应用凭据与本': 'Повторное сканирование перезапишет учётные данные',
|
|
1010
|
+
'扫码者本人接收通知卡片': 'Получатель карточки уведомлений — авторизованный пользователь',
|
|
1011
|
+
'tool (可选)': 'Инструмент (опц.)',
|
|
1012
|
+
'runningSubagents (可选)': 'Подагенты (опц.)',
|
|
1013
|
+
'durationMs (可选)': 'Длительность мс (опц.)',
|
|
1014
|
+
'usage 输入 (可选)': 'Входные токены (опц.)',
|
|
1015
|
+
'usage 输出 (可选)': 'Выходные токены (опц.)',
|
|
1016
|
+
'profile (写入哪个 profile 的 cordis.patch.yml)': 'Профиль (куда записать cordis.patch.yml)',
|
|
1017
|
+
'URL (留空用 DSH_HOOKS_WEBHOOK_URL)': 'URL (по умолчанию DSH_HOOKS_WEBHOOK_URL)',
|
|
1018
|
+
'当前已配置的 Hook 规则列表': 'Список текущих настроенных правил хуков',
|
|
1019
|
+
'测试通道': 'Тест канала'
|
|
804
1020
|
}
|
|
805
1021
|
for (const [k, v] of Object.entries(CORE_ZH_PRESETS)) ZH_EXACT.set(k, v)
|
|
806
|
-
|
|
807
1022
|
const zhSortedExact = []
|
|
808
1023
|
const rebuildZhSorted = () => {
|
|
809
1024
|
zhSortedExact.length = 0
|
|
@@ -811,7 +1026,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
811
1026
|
zhSortedExact.sort((a, b) => b[0].length - a[0].length)
|
|
812
1027
|
}
|
|
813
1028
|
rebuildZhSorted()
|
|
814
|
-
|
|
815
1029
|
const updateZhRu = (entries) => {
|
|
816
1030
|
if (!entries || typeof entries !== 'object') return
|
|
817
1031
|
for (const [zhText, ruText] of Object.entries(entries)) {
|
|
@@ -828,7 +1042,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
828
1042
|
rebuildZhSorted()
|
|
829
1043
|
}
|
|
830
1044
|
updateZhRu(ZH_RU)
|
|
831
|
-
|
|
832
1045
|
const zhTranslateText = (text) => {
|
|
833
1046
|
if (!ZH_CJK.test(text)) return null
|
|
834
1047
|
const exact = ZH_EXACT.get(text)
|
|
@@ -857,107 +1070,175 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
857
1070
|
if (changed) return replaced
|
|
858
1071
|
return null
|
|
859
1072
|
}
|
|
860
|
-
|
|
861
1073
|
const DOM_EN_ATTRS = {
|
|
862
1074
|
'Streaming preview': 'Предпросмотр стриминга',
|
|
863
1075
|
'Visualization streaming preview': 'Предпросмотр визуализации',
|
|
864
1076
|
'Security Auditor Shield': 'Защитный щит аудитора',
|
|
865
1077
|
'Shield: Safe': 'Щит: Безопасно',
|
|
866
|
-
'Shield: Alert': 'Щит: Тревога'
|
|
1078
|
+
'Shield: Alert': 'Щит: Тревога',
|
|
1079
|
+
'e.g. Implement report export and cover with unit tests': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами',
|
|
1080
|
+
'e.g. Implement report export and cover with unit tests...': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами…',
|
|
1081
|
+
'e.g. Implement user profile settings card with theme tokens': 'Например: Реализовать карточку настроек профиля с токенами темы',
|
|
1082
|
+
'Detailed functional specs, acceptance criteria, constraints...': 'Детальная функциональная спецификация, критерии приёмки, ограничения…',
|
|
1083
|
+
'Specific instructions, questions, or requirements for this specialist...': 'Конкретные инструкции, вопросы или требования для данного специалиста…',
|
|
867
1084
|
}
|
|
868
1085
|
const DOM_EN_TEXT = {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1086
|
+
'Scheduled tasks': 'Задачи по расписанию',
|
|
1087
|
+
'Today': 'Сегодня',
|
|
1088
|
+
'Plugin Market': 'Магазин плагинов',
|
|
1089
|
+
'GitHub ops': 'Операции с GitHub',
|
|
1090
|
+
'Auto mode': 'Автоматический режим',
|
|
1091
|
+
'Full access': 'Полный доступ',
|
|
1092
|
+
'Read only': 'Только чтение',
|
|
1093
|
+
'Side card': 'Боковая панель',
|
|
1094
|
+
'panelName': 'Палитра команд',
|
|
1095
|
+
'Hooks': 'Хуки',
|
|
1096
|
+
'Manage what the side card shows and how it behaves': 'Настройка содержимого и поведения боковой панели',
|
|
1097
|
+
'Inject the sidebar-open tool for the model': 'Предоставить модели инструмент sidebar-open',
|
|
1098
|
+
'Position compatibility mode': 'Режим совместимости расположения',
|
|
1099
|
+
'Auto-detect': 'Автоопределение',
|
|
1100
|
+
'Sidebar content': 'Содержимое боковой панели',
|
|
1101
|
+
'Changes': 'Изменения',
|
|
1102
|
+
'Tasks': 'Задачи',
|
|
1103
|
+
'Time Machine': 'Машина времени',
|
|
1104
|
+
'Live Canvas': 'Живой холст',
|
|
1105
|
+
'Side Chat (beta)': 'Боковой чат (бета)',
|
|
1106
|
+
'Terminal': 'Терминал',
|
|
1107
|
+
'Feature settings': 'Настройки функции',
|
|
1108
|
+
'Low': 'Низкий',
|
|
1109
|
+
'Medium': 'Средний',
|
|
1110
|
+
'High': 'Высокий',
|
|
1111
|
+
'Effort': 'Рассуждения',
|
|
1112
|
+
'Search engine (ModSearch)': 'Поисковая система (ModSearch)',
|
|
1113
|
+
'Search engine provider configuration.': 'Настройка провайдера поисковой системы.',
|
|
1114
|
+
'X search only': 'Только поиск в X',
|
|
1115
|
+
'Security Auditor Shield': 'Защитный щит аудитора',
|
|
1116
|
+
'Shield: Safe': 'Щит: Безопасно',
|
|
1117
|
+
'Shield: Alert': 'Щит: Тревога',
|
|
1118
|
+
'Lens': 'Линза',
|
|
1119
|
+
'Gallery': 'Галерея',
|
|
1120
|
+
'SSH: Local': 'SSH: Локально',
|
|
1121
|
+
'SSH: Remote': 'SSH: Удалённо',
|
|
1122
|
+
'7/7 rot': '7/7 рот.',
|
|
1123
|
+
'Smoke chat': 'Тестовый чат',
|
|
1124
|
+
'Quick Launch Goal': 'Быстрый запуск цели',
|
|
1125
|
+
'Define the objective for the agent in autonomous mode:': 'Сформулируйте задачу для автономной работы агента:',
|
|
1126
|
+
'Fix Bug': 'Исправление бага',
|
|
1127
|
+
'Refactor (YAGNI)': 'Рефакторинг (YAGNI)',
|
|
1128
|
+
'Tests & Coverage': 'Тесты и покрытие',
|
|
1129
|
+
'Code Review': 'Ревью кода',
|
|
1130
|
+
'New Feature': 'Новая функция',
|
|
1131
|
+
'Security Audit': 'Аудит безопасности',
|
|
1132
|
+
'Docs & Contract': 'Документация и контракт',
|
|
1133
|
+
'Upgrade Deps': 'Обновление зависимостей',
|
|
1134
|
+
'Dead Code': 'Мёртвый код',
|
|
1135
|
+
'Performance': 'Производительность',
|
|
1136
|
+
'Start Goal': 'Запустить цель',
|
|
1137
|
+
'Launch Multi-Agent Orchestrator': 'Запуск мультиагентного оркестратора',
|
|
1138
|
+
'Full DAG Pipeline': 'Полный DAG-пайплайн',
|
|
1139
|
+
'Direct Specialist Subagent': 'Прямой субагент-специалист',
|
|
1140
|
+
'Objective Title': 'Название задачи',
|
|
1141
|
+
'Scope & Requirements': 'Объём и требования',
|
|
1142
|
+
'Topology Scenario': 'Топологический сценарий',
|
|
1143
|
+
'Auto (Infer based on prompt complexity)': 'Авто (определить по сложности задачи)',
|
|
1144
|
+
'Hotfix (1 Stage: Triage & Minimal Fix)': 'Хотфикс (1 этап: анализ и точечный фикс)',
|
|
1145
|
+
'Simple (2 Stages: Spec + Exec)': 'Простой (2 этапа: ТЗ + реализация)',
|
|
1146
|
+
'Medium (4 Stages: Spec -> Design -> Code -> QA)': 'Средний (4 этапа: ТЗ -> Дизайн -> Код -> Тестирование)',
|
|
1147
|
+
'Complex (6 Stages: Full Engineering Lifecycle)': 'Сложный (6 этапов: полный инженерный цикл)',
|
|
1148
|
+
'Enterprise (7 Stages: R&D Spike -> Fullstack -> Gate)': 'Enterprise (7 этапов: R&D исследование -> Фулстек -> Гейт приёмки)',
|
|
1149
|
+
'Target Specialist Role': 'Роль целевого специалиста',
|
|
1150
|
+
'UI/UX Interface Designer': 'UI/UX дизайнер интерфейсов',
|
|
1151
|
+
'System Architect (DESIGN.md / ADR)': 'Системный архитектор (DESIGN.md / ADR)',
|
|
1152
|
+
'Technical Spec Analyst': 'Аналитик технических спецификаций',
|
|
1153
|
+
'Senior Frontend Developer': 'Ведущий frontend-разработчик',
|
|
1154
|
+
'Senior Backend Developer': 'Ведущий backend-разработчик',
|
|
1155
|
+
'QA Automation Engineer': 'Инженер автоматизации тестирования',
|
|
1156
|
+
'Refactoring & Complexity Specialist': 'Специалист по рефакторингу и сложности',
|
|
1157
|
+
'Hotfix & Diagnostic Engineer': 'Инженер хотфиксов и диагностики',
|
|
1158
|
+
'Documentation Specialist': 'Технический писатель / Документация',
|
|
1159
|
+
'Spike & R&D Researcher': 'Исследователь R&D и прототипирования',
|
|
1160
|
+
'DevOps & Tooling Specialist': 'DevOps и инфраструктурный специалист',
|
|
1161
|
+
'Instructions for Subagent': 'Инструкции для субагента',
|
|
1162
|
+
'Start Pipeline': 'Запустить пайплайн',
|
|
1163
|
+
'Delegate Subagent': 'Делегировать субагенту',
|
|
1164
|
+
'Dispatching...': 'Отправка…',
|
|
1165
|
+
'Discounted rate active': 'Действует сниженный тариф',
|
|
1166
|
+
'off-peak': 'непиковый',
|
|
1167
|
+
'peak': 'пиковый',
|
|
1168
|
+
'Context cache saved:': 'Сэкономлено на кэше:',
|
|
1169
|
+
'1M tokens, $': '1 млн токенов, $',
|
|
1170
|
+
'Input (cache hit)': 'Ввод (попадание в кэш)',
|
|
1171
|
+
'Input (cache miss)': 'Ввод (промах кэша)',
|
|
1172
|
+
'Output': 'Вывод',
|
|
1173
|
+
'SESSION TOKENS': 'ТОКЕНЫ СЕССИИ',
|
|
1174
|
+
'SPEND BY MODEL': 'РАСХОД ПО МОДЕЛЯМ',
|
|
1175
|
+
'Session total:': 'Всего за сессию:',
|
|
1176
|
+
'Copy Summary': 'Скопировать сводку',
|
|
1177
|
+
'Context Lens': 'Линза контекста',
|
|
1178
|
+
'Saved tokens': 'Сэкономлено токенов',
|
|
1179
|
+
'% Saved': '% экономии',
|
|
1180
|
+
'ops': 'операций',
|
|
1181
|
+
'Budget': 'Бюджет',
|
|
1182
|
+
'Active focus': 'Активный фокус',
|
|
1183
|
+
'No focus paths set': 'Пути фокусировки не заданы',
|
|
1184
|
+
'Status': 'Статус',
|
|
1185
|
+
'Graph & CI': 'Граф и CI',
|
|
1186
|
+
'Events & PRs': 'События и PR',
|
|
1187
|
+
'Branch': 'Ветка',
|
|
1188
|
+
'Sync': 'Синхронизация',
|
|
1189
|
+
'Up to date with remote (@{upstream})': 'Синхронизировано с удалённым репозиторием (@{upstream})',
|
|
1190
|
+
'Up to date with remote': 'Синхронизировано с удалённым репозиторием',
|
|
1191
|
+
'Clean': 'Чисто',
|
|
1192
|
+
'No modified or untracked files in the working directory.': 'В рабочем каталоге нет изменённых или неотслеживаемых файлов.',
|
|
1193
|
+
'RECENT COMMITS': 'ПОСЛЕДНИЕ КОММИТЫ',
|
|
1194
|
+
'Active jobs': 'Активные задачи',
|
|
1195
|
+
'No active jobs': 'Нет активных задач',
|
|
1196
|
+
'Register in Models': 'Зарегистрировать в моделях',
|
|
1197
|
+
'Base URL': 'Базовый URL',
|
|
1198
|
+
'API key env / credential name': 'Имя переменной окружения / ключа API',
|
|
1199
|
+
'Default model': 'Модель по умолчанию',
|
|
1200
|
+
'Enabled': 'Включено',
|
|
1201
|
+
'Save': 'Сохранить',
|
|
1202
|
+
'Saved': 'Сохранено',
|
|
1203
|
+
'Refresh': 'Обновить',
|
|
1204
|
+
'Loading settings…': 'Загрузка настроек…',
|
|
1205
|
+
'Preferred engine': 'Предпочитаемый движок',
|
|
1206
|
+
'API key': 'API-ключ',
|
|
1207
|
+
'stored, leave empty to keep it': 'сохранён, оставьте пустым для сохранения',
|
|
1208
|
+
'Built-in official endpoint, leave blank to use it': 'Встроенная официальная конечная точка, оставьте пустым',
|
|
1209
|
+
'Discard': 'Сбросить',
|
|
1210
|
+
'Model synchronization': 'Синхронизация моделей',
|
|
1211
|
+
'All API-key providers': 'Все провайдеры API-ключей',
|
|
1212
|
+
'Preview only (dry-run)': 'Только предпросмотр (dry-run)',
|
|
1213
|
+
'Confirm stale removal': 'Подтверждать удаление устаревших',
|
|
1214
|
+
'Refresh status': 'Обновить статус',
|
|
1215
|
+
'Refresh all': 'Обновить всё',
|
|
1216
|
+
'Discover': 'Обнаружить',
|
|
1217
|
+
'Check availability': 'Проверить доступность',
|
|
1218
|
+
'Check credentials': 'Проверить учётные данные',
|
|
1219
|
+
'Choose models': 'Выбрать модели',
|
|
1220
|
+
'Manual model selection': 'Выбор моделей вручную',
|
|
1221
|
+
'Mode': 'Режим',
|
|
1222
|
+
'Hybrid (auto-rewrite + tools)': 'Гибридный (авто-переписывание + инструменты)',
|
|
1223
|
+
'Describe strategy': 'Стратегия описания',
|
|
1224
|
+
'Auto (use vision LLM)': 'Авто (использовать Vision LLM)',
|
|
1225
|
+
'Escalation': 'Эскалация',
|
|
1226
|
+
'Simple only (one pass)': 'Только простая (один проход)',
|
|
1227
|
+
'Routing': 'Маршрутизация',
|
|
1228
|
+
'Channel order': 'Порядок каналов',
|
|
1229
|
+
'Issue Reporter': 'Репортёр проблем',
|
|
1230
|
+
'GitHub sign-in is not configured for this installation.': 'Вход через GitHub не настроен для этой установки.',
|
|
1231
|
+
'0 plugins': '0 плагинов',
|
|
1232
|
+
'Catalog': 'Каталог',
|
|
1233
|
+
'Report Editor': 'Редактор отчёта',
|
|
1234
|
+
'My Reports': 'Мои отчёты',
|
|
1235
|
+
'Authorization': 'Авторизация',
|
|
1236
|
+
'Search installed plugins…': 'Поиск установленных плагинов…',
|
|
1237
|
+
'Search installed plugins...': 'Поиск установленных плагинов...',
|
|
1238
|
+
'Refresh inventory': 'Обновить список',
|
|
1239
|
+
'Loading inventory & status…': 'Загрузка списка и статуса…',
|
|
1240
|
+
'Loading inventory & status...': 'Загрузка списка и статуса...'
|
|
959
1241
|
}
|
|
960
|
-
|
|
961
1242
|
const ZH_WALKER = (root) => {
|
|
962
1243
|
try {
|
|
963
1244
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
|
@@ -1007,7 +1288,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1007
1288
|
}
|
|
1008
1289
|
} catch (err) { /* ignore */ }
|
|
1009
1290
|
}
|
|
1010
|
-
|
|
1011
1291
|
let zhObserver = null
|
|
1012
1292
|
const syncZhDom = () => {
|
|
1013
1293
|
try {
|
|
@@ -1034,8 +1314,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1034
1314
|
}
|
|
1035
1315
|
}, 'dsh-russian-lang: zh-dom')
|
|
1036
1316
|
syncZhDom()
|
|
1037
|
-
|
|
1038
|
-
const TYPO_YO_PAIRS = [["еще", "ещё"], ["ее", "её"], ["черный", "чёрный"], ["черная", "чёрная"], ["черные", "чёрные"], ["зеленый", "зелёный"], ["желтый", "жёлтый"], ["легкий", "лёгкий"], ["тяжелый", "тяжёлый"], ["надежный", "надёжный"], ["дешевый", "дешёвый"], ["идет", "идёт"], ["дает", "даёт"], ["ведет", "ведёт"], ["несет", "несёт"], ["живет", "живёт"], ["привел", "привёл"], ["шел", "шёл"], ["нее", "неё"], ["мое", "моё"], ["серьезно", "серьёзно"], ["придется", "придётся"], ["насчет", "насчёт"], ["твое", "твоё"], ["свое", "своё"], ["пойдем", "пойдём"], ["пришел", "пришёл"], ["нашел", "нашёл"], ["идем", "идём"], ["вперед", "вперёд"], ["пошел", "пошёл"], ["ребенка", "ребёнка"], ["ребенок", "ребёнок"], ["счет", "счёт"], ["своем", "своём"], ["придет", "придёт"], ["ушел", "ушёл"], ["ждет", "ждёт"], ["мертв", "мёртв"], ["твоем", "твоём"], ["вернется", "вернётся"], ["днем", "днём"], ["найдем", "найдём"], ["пойдет", "пойдёт"], ["начнем", "начнём"], ["вернемся", "вернёмся"], ["остается", "остаётся"], ["принес", "принёс"], ["идешь", "идёшь"], ["трех", "трёх"], ["ребенком", "ребёнком"], ["самолет", "самолёт"], ["определенно", "определённо"], ["пойдешь", "пойдёшь"], ["умрет", "умрёт"], ["прием", "приём"], ["прошел", "прошёл"], ["убьет", "убьёт"], ["тетя", "тётя"], ["провел", "провёл"], ["произойдет", "произойдёт"], ["семьей", "семьёй"], ["пойдемте", "пойдёмте"], ["пройдет", "пройдёт"], ["возьмем", "возьмём"], ["вернешься", "вернёшься"], ["найдешь", "найдёшь"], ["живешь", "живёшь"], ["отчет", "отчёт"], ["займет", "займёт"], ["ждем", "ждём"], ["сошел", "сошёл"], ["вдвоем", "вдвоём"], ["найдет", "найдёт"], ["вел", "вёл"], ["живем", "живём"], ["зашел", "зашёл"], ["начнется", "начнётся"], ["путем", "путём"], ["подойдет", "подойдёт"], ["режиссер", "режиссёр"], ["уйдет", "уйдёт"], ["придешь", "придёшь"], ["идемте", "идёмте"], ["ведешь", "ведёшь"], ["пес", "пёс"], ["умрешь", "умрёшь"], ["ждешь", "ждёшь"], ["найдете", "найдёте"], ["найдется", "найдётся"], ["клево", "клёво"], ["поймешь", "поймёшь"], ["разберемся", "разберёмся"], ["времен", "времён"], ["введен", "введён"], ["включен", "включён"], ["включенных", "включённых"], ["возьмется", "возьмётся"], ["завершен", "завершён"], ["завершенного", "завершённого"], ["задает", "задаёт"], ["задается", "задаётся"], ["заменен", "заменён"], ["звезд", "звёзд"], ["звезды", "звёзды"], ["изменен", "изменён"], ["истек", "истёк"], ["незавершенная", "незавершённая"], ["неподтвержденные", "неподтверждённые"], ["несохраненные", "несохранённые"], ["обновлен", "обновлён"], ["обновленные", "обновлённые"], ["определен", "определён"], ["отклонен", "отклонён"], ["отменен", "отменён"], ["очередность", "очерёдность"], ["поврежден", "повреждён"], ["повторен", "повторён"], ["подключен", "подключён"], ["подтвержденные", "подтверждённые"], ["приемки", "приёмки"], ["раздает", "раздаёт"], ["разрешенная", "разрешённая"], ["разрешенные", "разрешённые"], ["создаем", "создаём"], ["сойдется", "сойдётся"], ["сохранен", "сохранён"], ["сохраненный", "сохранённый"], ["счету", "счёту"], ["тяжелые", "тяжёлые"], ["темная", "тёмная"], ["уберет", "уберёт"], ["удален", "удалён"], ["учетные", "учётные"], ["учетных", "учётных"], ["емкости", "ёмкости"], ["емкость", "ёмкость"]]
|
|
1317
|
+
const TYPO_YO_PAIRS = [["еще","ещё"],["ее","её"],["черный","чёрный"],["черная","чёрная"],["черные","чёрные"],["зеленый","зелёный"],["желтый","жёлтый"],["легкий","лёгкий"],["тяжелый","тяжёлый"],["надежный","надёжный"],["дешевый","дешёвый"],["идет","идёт"],["дает","даёт"],["ведет","ведёт"],["несет","несёт"],["живет","живёт"],["привел","привёл"],["шел","шёл"],["нее","неё"],["мое","моё"],["серьезно","серьёзно"],["придется","придётся"],["насчет","насчёт"],["твое","твоё"],["свое","своё"],["пойдем","пойдём"],["пришел","пришёл"],["нашел","нашёл"],["идем","идём"],["вперед","вперёд"],["пошел","пошёл"],["ребенка","ребёнка"],["ребенок","ребёнок"],["счет","счёт"],["своем","своём"],["придет","придёт"],["ушел","ушёл"],["ждет","ждёт"],["мертв","мёртв"],["твоем","твоём"],["вернется","вернётся"],["днем","днём"],["найдем","найдём"],["пойдет","пойдёт"],["начнем","начнём"],["вернемся","вернёмся"],["остается","остаётся"],["принес","принёс"],["идешь","идёшь"],["трех","трёх"],["ребенком","ребёнком"],["самолет","самолёт"],["определенно","определённо"],["пойдешь","пойдёшь"],["умрет","умрёт"],["прием","приём"],["прошел","прошёл"],["убьет","убьёт"],["тетя","тётя"],["провел","провёл"],["произойдет","произойдёт"],["семьей","семьёй"],["пойдемте","пойдёмте"],["пройдет","пройдёт"],["возьмем","возьмём"],["вернешься","вернёшься"],["найдешь","найдёшь"],["живешь","живёшь"],["отчет","отчёт"],["займет","займёт"],["ждем","ждём"],["сошел","сошёл"],["вдвоем","вдвоём"],["найдет","найдёт"],["вел","вёл"],["живем","живём"],["зашел","зашёл"],["начнется","начнётся"],["путем","путём"],["подойдет","подойдёт"],["режиссер","режиссёр"],["уйдет","уйдёт"],["придешь","придёшь"],["идемте","идёмте"],["ведешь","ведёшь"],["пес","пёс"],["умрешь","умрёшь"],["ждешь","ждёшь"],["найдете","найдёте"],["найдется","найдётся"],["клево","клёво"],["поймешь","поймёшь"],["разберемся","разберёмся"],["времен","времён"],["введен","введён"],["включен","включён"],["включенных","включённых"],["возьмется","возьмётся"],["завершен","завершён"],["завершенного","завершённого"],["задает","задаёт"],["задается","задаётся"],["заменен","заменён"],["звезд","звёзд"],["звезды","звёзды"],["изменен","изменён"],["истек","истёк"],["незавершенная","незавершённая"],["неподтвержденные","неподтверждённые"],["несохраненные","несохранённые"],["обновлен","обновлён"],["обновленные","обновлённые"],["определен","определён"],["отклонен","отклонён"],["отменен","отменён"],["очередность","очерёдность"],["поврежден","повреждён"],["повторен","повторён"],["подключен","подключён"],["подтвержденные","подтверждённые"],["приемки","приёмки"],["раздает","раздаёт"],["разрешенная","разрешённая"],["разрешенные","разрешённые"],["создаем","создаём"],["сойдется","сойдётся"],["сохранен","сохранён"],["сохраненный","сохранённый"],["счету","счёту"],["тяжелые","тяжёлые"],["темная","тёмная"],["уберет","уберёт"],["удален","удалён"],["учетные","учётные"],["учетных","учётных"],["емкости","ёмкости"],["емкость","ёмкость"]]
|
|
1039
1318
|
const typoYo = makeTypoYo(TYPO_YO_PAIRS)
|
|
1040
1319
|
const getTypoConf = () => {
|
|
1041
1320
|
try {
|
|
@@ -1047,15 +1326,12 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1047
1326
|
const typoNode = (node, conf) => {
|
|
1048
1327
|
const before = node.nodeValue
|
|
1049
1328
|
if (!before || !before.match || (before.match(/[\u0400-\u04FF]/g) || []).length < 3) return
|
|
1050
|
-
|
|
1051
1329
|
if (node.parentElement && node.parentElement.closest('code, pre, a, script, style, textarea, input, select, button, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) return
|
|
1052
1330
|
if (node.parentElement && node.parentElement.closest('.katex, [data-latex], math')) return
|
|
1053
1331
|
if (/\$[^$\n]+\$/.test(before)) return
|
|
1054
|
-
|
|
1055
1332
|
if (node.parentElement && node.parentElement.closest('[data-chat-flow-status="running"], [data-turn-running], [data-turn-tail], [data-streaming="true"], .dsw-turn-running, [class*="streaming"], [class*="Streaming"]')) {
|
|
1056
1333
|
return
|
|
1057
1334
|
}
|
|
1058
|
-
|
|
1059
1335
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1060
1336
|
if (sel && !sel.isCollapsed && sel.rangeCount > 0) {
|
|
1061
1337
|
try {
|
|
@@ -1065,7 +1341,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1065
1341
|
}
|
|
1066
1342
|
} catch (e) { /* bestEffort */ void e; }
|
|
1067
1343
|
}
|
|
1068
|
-
|
|
1069
1344
|
let after = typoQuotes(before)
|
|
1070
1345
|
after = typoDash(after)
|
|
1071
1346
|
after = typoPunct(after)
|
|
@@ -1127,16 +1402,13 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1127
1402
|
}
|
|
1128
1403
|
}, 'dsh-russian-lang: typography')
|
|
1129
1404
|
syncTypo()
|
|
1130
|
-
|
|
1131
|
-
const layout = makeLayout(new Set(["не", "что", "ты", "это", "на", "он", "мы", "как", "вы", "да", "мне", "нет", "меня", "так", "но", "его", "все", "она", "тебя", "если", "за", "бы", "тебе", "они", "чтобы", "же", "есть", "просто", "из", "для", "ну", "когда", "хорошо", "здесь", "по", "был", "знаю", "то", "только", "всё", "было", "вас", "может", "нас", "вот", "от", "быть", "кто", "будет", "почему", "вам", "их", "очень", "могу", "уже", "спасибо", "или", "нам", "еще", "там", "нужно", "сейчас", "где", "чем", "хочу", "ничего", "этого", "была", "мой", "ее", "ладно", "знаешь", "до", "этом", "потому", "теперь", "думаю", "больше", "её", "со", "раз", "ему", "надо", "время", "этот", "ли", "ещё", "пока", "даже", "привет", "сказал", "себя", "должен", "тоже", "хочешь", "давай", "никогда", "эй", "того", "тогда", "него", "ни", "тут", "были", "конечно", "правда", "об", "моя", "пожалуйста", "тобой", "сказать", "сегодня", "один", "лучше", "можешь", "сюда", "мной", "значит", "сделать", "всегда", "дело", "можно", "ей", "должны", "порядке", "без", "день", "том", "буду", "делать", "хотел", "чего", "эти", "много", "после", "этим", "всего", "во", "твой", "ним", "лет", "боже", "них", "сэр", "ведь", "мистер", "жизнь", "потом", "ней", "такой", "который", "всех", "через", "им", "возможно", "немного", "такое", "слишком", "себе", "зачем", "должна", "моей", "люди", "знаете", "этой", "думаешь", "свою", "точно", "человек", "твоя", "под", "сказала", "назад", "эту", "можем", "случилось", "мама", "мог", "вместе", "отец", "сделал", "мои", "кажется", "друг", "куда", "никто", "поэтому", "эта", "которые", "два", "тот", "сколько", "понимаю", "снова", "жизни", "нравится", "людей", "помочь", "видел", "люблю", "хочет", "место", "знать", "прости", "отлично", "похоже", "парень", "домой", "всем", "деньги", "иди", "времени", "дома", "именно", "доктор", "думал", "говорил", "делаешь", "будем", "прямо", "стоит", "поговорить", "найти", "разве", "слушай", "своей", "лишь", "ага", "можете", "простите", "хотела", "сам", "тем", "будешь", "прошу", "три", "деле", "хотите", "говорить", "давайте", "совсем", "знал", "знает", "какой", "моего", "скажи", "дом", "дела", "вами", "свои", "говорит", "несколько", "должно", "про", "ваш", "происходит", "жаль", "туда", "действительно", "папа", "завтра", "оно", "черт", "одна", "перед", "наш", "уверен", "отсюда", "нужна", "самом", "тех", "нужен", "свой", "мою", "кого", "верно", "работу", "каждый", "твоей", "будут", "хватит", "понял", "нее", "уж", "имя", "против", "пор", "чём", "раньше", "говорю", "более", "надеюсь", "итак", "при", "ваша", "вообще", "пошли", "мать", "нельзя", "наверное", "нами", "твои", "могли", "дай", "ради", "всю", "ребята", "ко", "хотя", "понимаешь", "идти", "этих", "откуда", "такая", "ясно", "другой", "извините", "вижу", "видеть", "над", "могут", "равно", "мисс", "скоро", "будто", "зовут", "виду", "наши", "думала", "послушай", "между", "своего", "вопрос", "этому", "почти", "года", "человека", "которая", "подожди", "руки", "нормально", "такие", "возьми", "минут", "извини", "вещи", "могла", "смотри", "хоть", "работа", "пару", "сын", "ваше", "дня", "пора", "неё", "жить", "видишь", "достаточно", "господи", "быстро", "твою", "весь", "убить", "ночь", "говоришь", "собой", "скажу", "готов", "слышал", "какая", "посмотри", "первый", "самое", "видела", "пусть", "месте", "нашли", "сказали", "плохо", "смогу", "ваши", "отца", "детей", "знаем", "рад", "прав", "никаких", "имею", "миссис", "иногда", "смерти", "своих", "пойду", "брат", "вроде", "рядом", "мир", "произошло", "которую", "сделала", "говорила", "знала", "мое", "одного", "таким", "помощь", "такого", "кем", "насчет", "вчера", "случае", "увидеть", "нашей", "говорят", "правильно", "убил", "одно", "пойти", "друга", "сама", "долго", "работать", "дверь", "делает", "женщина", "важно", "кроме", "будь", "собираюсь", "вашей", "нужны", "здорово", "номер", "проблема", "проблемы", "денег", "чёрт", "хороший", "твоего", "одну", "дальше", "вернуться", "давно", "последний", "ночи", "узнать", "уверена", "машину", "права", "пять", "моих", "также", "тому", "вечером", "дети", "опять", "серьезно", "работает", "своим", "сначала", "две", "одной", "моим", "те", "жена", "думаете", "помнишь", "глаза", "ними", "чувак", "ночью", "чувствую", "взять", "сразу", "утром", "часть", "придется", "стать", "идея", "дорогая", "прекрасно", "слова", "сделали", "видели", "наша", "сердце", "помню", "парня", "момент", "утро", "здравствуйте", "стал", "скорее", "ох", "большой", "часов", "год", "друзья", "сильно", "говори", "смысле", "которой", "уйти", "которого", "капитан", "работы", "любовь", "дочь", "новый", "боюсь", "имеет", "стой", "дней", "той", "голову", "ах", "столько", "понятно", "дать", "девушка", "довольно", "доме", "вечер", "оба", "пойдем", "увидимся", "шанс", "хорошая", "прежде", "сделаю", "какого", "быстрее", "понять", "рассказать", "вашего", "бог", "интересно", "затем", "умер", "обо", "никого", "парни", "самый", "твое", "вон", "пришел", "чтоб", "поверить", "слово", "будете", "нашел", "милая", "странно", "какие", "ой", "та", "однажды", "сможешь", "получить", "обычно", "ума", "полагаю", "алло", "посмотреть", "иначе", "наших", "тело", "хотели", "свое", "пришли", "моё", "другие", "выглядит", "подумал", "лицо", "приятно", "мам", "говорите", "обратно", "нашего", "телефон", "мире", "честь", "господин", "доброе", "план", "другом", "час", "джон", "пути", "делал", "типа", "посмотрим", "насколько", "пытался", "правду", "решил", "поздно", "нем", "работе", "любит", "хотят", "кому", "сестра", "места", "скажешь", "следующий", "других", "получил", "кстати", "говорили", "пришла", "безопасности", "рада", "город", "внимание", "кофе", "которое", "вся", "семья", "конец", "позже", "дал", "думать", "спать", "вернулся", "остаться", "некоторые", "кровь", "оружие", "пришлось", "хуже", "дорогой", "полиция", "сможем", "понимаете", "четыре", "которых", "ужасно", "ждать", "способ", "делаю", "готовы", "совершенно", "смотреть", "вернусь", "большое", "единственный", "вперед", "часа", "муж", "идет", "таких", "ребенка", "машина", "стороны", "смог", "сына", "играть", "сами", "верю", "честно", "нашла", "звучит", "угодно", "руку", "начала", "перевод", "какое", "взял", "станет", "пытаюсь", "внутри", "знали", "новости", "добрый", "подождите", "скажите", "ребенок", "вашу", "мальчик", "осталось", "делаете", "году", "мере", "поняла", "отношения", "далеко", "вокруг", "хотим", "слышала", "двух", "приятель", "никому", "мужчина", "неделю", "вернуть", "смерть", "сих", "ту", "сможет", "дайте", "двое", "видите", "долларов", "крови", "решение", "путь", "следует", "неужели", "около", "готова", "семьи", "идем", "смотрите", "круто", "история", "вдруг", "спросить", "твоих", "второй", "вина", "джек", "женщины", "нашу", "заткнись", "минуту", "собираешься", "твоим", "шоу", "прощения", "больно", "машины", "пап", "трудно", "постоянно", "поможет", "проблем", "например", "дерьмо", "полиции", "начать", "знают", "лучший", "добро", "спасти", "рождения", "оставить", "чарли", "любишь", "конце", "забыл", "месяцев", "часто", "мило", "комнате", "нему", "матери", "милый", "полностью", "школе", "леди", "серьёзно", "убили", "случай", "джо", "убийство", "послушайте", "посмотрите", "пошел", "никакого", "сделаем", "говоря", "неплохо", "использовать", "прошлой", "иду", "узнал", "прекрати", "чуть", "держи", "невозможно", "господа", "слушайте", "наше", "машине", "сэм", "свет", "образом", "иметь", "другое", "другого", "шесть", "людям", "погоди", "имени", "детка", "недели", "меньше", "легко", "крайней", "девочка", "придётся", "мало", "ненавижу", "последние", "насчёт", "выглядишь", "сообщение", "думает", "друзей", "обещаю", "агент", "вместо", "последнее", "самого", "настолько", "ответ", "детектив", "понятия", "родители", "показать", "поехали", "стало", "мэм", "своими", "всей", "очевидно", "помогите", "маленький", "школу", "пожаловать", "твоё", "либо", "забрать", "прошло", "особенно", "имеешь", "истории", "эм", "вопросы", "большая", "минутку", "пол", "остановить", "чему", "свидания", "обязательно", "городе", "убийца", "сделай", "ваших", "любви", "спокойной", "своё", "мадам", "идите", "выйти", "делают", "немедленно", "делай", "возможность", "человеком", "бывает", "ужин", "моем", "купить", "позвонить", "наконец", "стала", "любой", "клянусь", "смешно", "абсолютно", "всему", "переводчики", "забудь", "секунду", "выбор", "тысяч", "потерял", "майкл", "волнуйся", "ноги", "таком", "настоящий", "означает", "встретиться", "известно", "работаю", "замечательно", "любом", "оставь", "связи", "боль", "выпить", "представить", "убийства", "малыш", "умерла", "воды", "окей", "речь", "хорошие", "поводу", "перестань", "увидел", "повезло", "президент", "мужик", "которым", "прошлом", "дам", "десять", "умереть", "поговорим", "отцом", "искать", "тихо", "оставил", "пистолет", "начал", "сложно", "города", "неделе", "хотелось", "принять", "историю", "босс", "держать", "пойдём", "подумала", "фильм", "слышишь", "рано", "удачи", "еду", "словно", "причина", "пришёл", "компании", "спокойно", "работал", "подумать", "вести", "вещь", "брата", "конца", "получится", "блин", "право", "ввиду", "бога", "подарок", "комнату", "мира", "успокойся", "позволить", "моему", "идёт", "самая", "рассказал", "курсе", "намного", "мистера", "женщин", "тяжело", "ух", "предложение", "одним", "счет", "улице", "другим", "уходи", "майк", "среди", "помоги", "получилось", "месяц", "проверить", "генри", "голос", "позвольте", "стороне", "вероятно", "случится", "уверены", "мужа", "питер", "провести", "людьми", "неважно", "нашёл", "доктора", "месяца", "игра", "идём", "нём", "джордж", "познакомиться", "возьму", "слышали", "пошла", "корабль", "чувствуешь", "позволь", "сторону", "игры", "согласен", "письмо", "вниз", "молодец", "наверняка", "утра", "вашим", "нечего", "встречи", "вид", "никакой", "связь", "список", "такую", "весело", "сидеть", "гораздо", "собирался", "впервые", "находится", "объяснить", "полицию", "решили", "семью", "свидание", "отличная", "остальные", "прийти", "руках", "фбр", "чувство", "котором", "решила", "понадобится", "сынок", "найду", "фрэнк", "школы", "чувства", "дядя", "стали", "ушла", "первым", "поверь", "расскажи", "хочется", "девушки", "сон", "имел", "каким", "парнем", "многие", "части", "ехать", "считаю", "никуда", "голове", "старый", "оттуда", "называется", "вопросов", "фото", "пройти", "заниматься", "другу", "вернулась", "пара", "никак", "девочки", "благодарю", "своем", "недавно", "приехал", "глаз", "могло", "становится", "пыталась", "является", "брось", "богу", "нашим", "помощи", "волосы", "невероятно", "одном", "ушел", "джейн", "тела", "недель", "собирается", "защитить", "всеми", "существует", "жив", "первая", "позвоню", "благодаря", "вперёд", "навсегда", "помните", "земле", "делу", "вполне", "пытается", "будущее", "семь", "своему", "ок", "идиот", "слава", "стоило", "удалось", "делаем", "команда", "главное", "огонь", "дэнни", "замуж", "начали", "мужчины", "силы", "семье", "шеф", "суд", "слышу", "тюрьме", "попробовать", "друзьями", "алекс", "вашем", "новая", "маленькая", "черта", "придет", "страшно", "услышать", "новые", "общем", "звонил", "купил", "понравится", "близко", "самой", "смысл", "хорошее", "любил", "сара", "единственная", "делали", "макс", "мэри", "жду", "получается", "хорошего", "эми", "дамы", "жену", "гарри", "неправильно", "стоять", "плевать", "ключ", "верить", "лично", "слышать", "взяли", "поздравляю", "пить", "заставить", "получили", "рот", "делала", "преступления", "двери", "землю", "разумеется", "очередь", "первой", "разговор", "видимо", "звонок", "джеймс", "менее", "нового", "получила", "хм", "подальше", "сможете", "похож", "состоянии", "необходимо"]), new Set())
|
|
1405
|
+
const layout = makeLayout(new Set(["не","что","ты","это","на","он","мы","как","вы","да","мне","нет","меня","так","но","его","все","она","тебя","если","за","бы","тебе","они","чтобы","же","есть","просто","из","для"]), new Set())
|
|
1132
1406
|
const layoutFixCandidate = layout.candidate
|
|
1133
1407
|
const learnWords = layout.learnWords
|
|
1134
|
-
|
|
1135
1408
|
function setNativeInputValue(el, value, cursorStart, cursorEnd) {
|
|
1136
1409
|
if (!el) return
|
|
1137
1410
|
const oldVal = el.value || ''
|
|
1138
1411
|
if (oldVal === value) return
|
|
1139
|
-
|
|
1140
1412
|
const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype
|
|
1141
1413
|
const desc = Object.getOwnPropertyDescriptor(proto, 'value')
|
|
1142
1414
|
if (desc && desc.set) {
|
|
@@ -1144,31 +1416,26 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1144
1416
|
} else {
|
|
1145
1417
|
el.value = value
|
|
1146
1418
|
}
|
|
1147
|
-
|
|
1148
1419
|
if (el._valueTracker) {
|
|
1149
1420
|
el._valueTracker.setValue(value === '' ? '__force__' : '')
|
|
1150
1421
|
}
|
|
1151
|
-
|
|
1152
1422
|
try {
|
|
1153
1423
|
const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$') || k.startsWith('__reactEventHandlers$'))
|
|
1154
1424
|
if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {
|
|
1155
1425
|
el[propsKey].onChange({ target: el, currentTarget: el })
|
|
1156
1426
|
}
|
|
1157
1427
|
} catch (e) { /* bestEffort */ void e; }
|
|
1158
|
-
|
|
1159
1428
|
try {
|
|
1160
1429
|
el.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
|
|
1161
1430
|
} catch (e) {
|
|
1162
1431
|
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1163
1432
|
}
|
|
1164
1433
|
el.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1165
|
-
|
|
1166
1434
|
if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {
|
|
1167
1435
|
const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
|
|
1168
1436
|
try { el.setSelectionRange(cursorStart, end) } catch (e) { /* bestEffort */ void e; }
|
|
1169
1437
|
}
|
|
1170
1438
|
}
|
|
1171
|
-
|
|
1172
1439
|
let layoutHintEl = null
|
|
1173
1440
|
const layoutCurrentInput = (ev) => {
|
|
1174
1441
|
if (ev && ev.target) {
|
|
@@ -1191,7 +1458,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1191
1458
|
if (focused) return focused
|
|
1192
1459
|
return null
|
|
1193
1460
|
}
|
|
1194
|
-
|
|
1195
1461
|
function getCaretCharacterOffset(root) {
|
|
1196
1462
|
if (!root) return -1
|
|
1197
1463
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
@@ -1207,7 +1473,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1207
1473
|
return -1
|
|
1208
1474
|
}
|
|
1209
1475
|
}
|
|
1210
|
-
|
|
1211
1476
|
function setCaretCharacterOffset(root, offset) {
|
|
1212
1477
|
if (!root || offset < 0) return
|
|
1213
1478
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
@@ -1237,7 +1502,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1237
1502
|
}
|
|
1238
1503
|
} catch (e) { /* bestEffort */ void e; }
|
|
1239
1504
|
}
|
|
1240
|
-
|
|
1241
1505
|
const isCaretInCode = (el, value, caretOffset) => {
|
|
1242
1506
|
if (!el) return false
|
|
1243
1507
|
if (caretOffset == null || caretOffset < 0) caretOffset = (value || '').length
|
|
@@ -1253,7 +1517,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1253
1517
|
if (single && single.length % 2 === 1) return true
|
|
1254
1518
|
return false
|
|
1255
1519
|
}
|
|
1256
|
-
|
|
1257
1520
|
const getComposerText = (el) => {
|
|
1258
1521
|
if (!el) return ''
|
|
1259
1522
|
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
@@ -1263,17 +1526,14 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1263
1526
|
const raw = host.innerText !== undefined ? host.innerText : (host.textContent || '')
|
|
1264
1527
|
return raw.replace(/\r/g, '').replace(/[\u200B\uFEFF]/g, '').replace(/\n+$/, '')
|
|
1265
1528
|
}
|
|
1266
|
-
|
|
1267
1529
|
const setComposerText = (el, value, cursorStart, cursorEnd) => {
|
|
1268
1530
|
if (!el) return
|
|
1269
1531
|
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
1270
1532
|
setNativeInputValue(el, value, cursorStart, cursorEnd)
|
|
1271
1533
|
return
|
|
1272
1534
|
}
|
|
1273
|
-
|
|
1274
1535
|
const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
|
|
1275
1536
|
const initialOffset = typeof cursorStart === 'number' ? cursorStart : getCaretCharacterOffset(host)
|
|
1276
|
-
|
|
1277
1537
|
try {
|
|
1278
1538
|
host.focus()
|
|
1279
1539
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
@@ -1284,12 +1544,10 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1284
1544
|
sel.addRange(range)
|
|
1285
1545
|
}
|
|
1286
1546
|
document.execCommand('insertText', false, value)
|
|
1287
|
-
|
|
1288
1547
|
const targetOffset = typeof cursorStart === 'number' ? cursorStart : (initialOffset >= 0 ? Math.min(initialOffset, value.length) : value.length)
|
|
1289
1548
|
setCaretCharacterOffset(host, targetOffset)
|
|
1290
1549
|
} catch (e) { /* bestEffort */ void e; }
|
|
1291
1550
|
}
|
|
1292
|
-
|
|
1293
1551
|
const layoutDismiss = () => {
|
|
1294
1552
|
if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
|
|
1295
1553
|
}
|
|
@@ -1298,9 +1556,10 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1298
1556
|
layoutHintEl = document.createElement('div')
|
|
1299
1557
|
layoutHintEl.dataset.russianLangLayout = '1'
|
|
1300
1558
|
Object.assign(layoutHintEl.style, {
|
|
1301
|
-
position: 'fixed', zIndex: '99999', background: '
|
|
1302
|
-
|
|
1303
|
-
|
|
1559
|
+
position: 'fixed', zIndex: '99999', background: 'var(--dsw-alias-bg-layer-3)',
|
|
1560
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
1561
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '8px', padding: '6px 10px',
|
|
1562
|
+
fontSize: '13px', boxShadow: 'var(--dsw-alias-shadow-l2)', cursor: 'pointer'
|
|
1304
1563
|
})
|
|
1305
1564
|
const label = direction === 'cyr2lat' ? 'Команда, не та раскладка' : 'Не та раскладка'
|
|
1306
1565
|
layoutHintEl.textContent = label + ': ' + converted
|
|
@@ -1315,7 +1574,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1315
1574
|
layoutHintEl.style.left = (r.left + 8) + 'px'
|
|
1316
1575
|
layoutHintEl.style.bottom = (window.innerHeight - r.top + 6) + 'px'
|
|
1317
1576
|
}
|
|
1318
|
-
|
|
1319
1577
|
let layoutBadgeEl = null
|
|
1320
1578
|
const layoutBadge = (el) => {
|
|
1321
1579
|
const value = getComposerText(el)
|
|
@@ -1329,8 +1587,8 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1329
1587
|
layoutBadgeEl.type = 'button'
|
|
1330
1588
|
layoutBadgeEl.dataset.russianLangLayoutBadge = '1'
|
|
1331
1589
|
Object.assign(layoutBadgeEl.style, {
|
|
1332
|
-
position: 'fixed', zIndex: '99998', background: 'var(--dsw-alias-bg-layer-3
|
|
1333
|
-
color: 'var(--dsw-alias-label-secondary
|
|
1590
|
+
position: 'fixed', zIndex: '99998', background: 'var(--dsw-alias-bg-layer-3)',
|
|
1591
|
+
color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
1334
1592
|
borderRadius: '6px', padding: '1px 6px', fontSize: '11px', cursor: 'pointer',
|
|
1335
1593
|
fontFamily: 'monospace', lineHeight: '1.4', fontWeight: '600'
|
|
1336
1594
|
})
|
|
@@ -1354,7 +1612,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1354
1612
|
const layoutBadgeHide = () => {
|
|
1355
1613
|
if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
|
|
1356
1614
|
}
|
|
1357
|
-
|
|
1358
1615
|
let isFormatting = false
|
|
1359
1616
|
const layoutOnInput = (ev) => {
|
|
1360
1617
|
if (isFormatting) return
|
|
@@ -1363,8 +1620,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1363
1620
|
if (!el) { layoutDismiss(); layoutBadgeHide(); return }
|
|
1364
1621
|
layoutBadge(el) // #66: метка раскладки
|
|
1365
1622
|
const value = getComposerText(el)
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
1623
|
if (value.trim().length < 4) { layoutDismiss(); return }
|
|
1369
1624
|
const latCount = (value.match(/[a-z]/g) || []).length
|
|
1370
1625
|
const cyrCount = (value.match(/[\u0430-\u044f\u0451]/g) || []).length
|
|
@@ -1380,18 +1635,14 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1380
1635
|
} catch (err) { /* ignore */ }
|
|
1381
1636
|
}
|
|
1382
1637
|
const unsubscribeLayout = runtime.subscribe(layoutOnInput)
|
|
1383
|
-
|
|
1384
1638
|
const layoutOnKeydown = (ev) => {
|
|
1385
1639
|
const el = layoutCurrentInput(ev)
|
|
1386
1640
|
if (!el) return
|
|
1387
|
-
|
|
1388
1641
|
const value = getComposerText(el)
|
|
1389
|
-
|
|
1390
1642
|
const isL = ev.code === 'KeyL' || ev.key.toLowerCase() === 'l' || ev.key.toLowerCase() === 'д'
|
|
1391
1643
|
if (ev.altKey && !ev.ctrlKey && !ev.metaKey && isL) {
|
|
1392
1644
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1393
1645
|
const selectedText = (sel && !sel.isCollapsed) ? sel.toString() : ''
|
|
1394
|
-
|
|
1395
1646
|
if (selectedText) {
|
|
1396
1647
|
const c = layoutFixCandidate(selectedText, 'lat2cyr') || layoutFixCandidate(selectedText, 'cyr2lat')
|
|
1397
1648
|
if (c) {
|
|
@@ -1413,7 +1664,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1413
1664
|
return
|
|
1414
1665
|
}
|
|
1415
1666
|
}
|
|
1416
|
-
|
|
1417
1667
|
const c = layoutFixCandidate(value, 'lat2cyr') || layoutFixCandidate(value, 'cyr2lat')
|
|
1418
1668
|
if (c) {
|
|
1419
1669
|
ev.preventDefault()
|
|
@@ -1435,7 +1685,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1435
1685
|
}
|
|
1436
1686
|
return
|
|
1437
1687
|
}
|
|
1438
|
-
|
|
1439
1688
|
if (ev.key === ' ' || ev.key === 'Enter') {
|
|
1440
1689
|
if (value && value.startsWith('/') && typeof expandSlashAlias === 'function') {
|
|
1441
1690
|
const expanded = expandSlashAlias(value)
|
|
@@ -1461,18 +1710,15 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1461
1710
|
}
|
|
1462
1711
|
}
|
|
1463
1712
|
}
|
|
1464
|
-
|
|
1465
1713
|
try {
|
|
1466
1714
|
const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1467
1715
|
const typoLive = snapVal.typography ? snapVal.typography.liveInput !== false : true
|
|
1468
1716
|
if (typoLive && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
|
1469
1717
|
const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
|
|
1470
|
-
|
|
1471
1718
|
if (isTextarea) {
|
|
1472
1719
|
const caretPos = el.selectionStart || 0
|
|
1473
1720
|
if (!isCaretInCode(el, value, caretPos)) {
|
|
1474
1721
|
const textBefore = caretPos >= 0 ? value.slice(0, caretPos) : value
|
|
1475
|
-
|
|
1476
1722
|
if (ev.key === '-' && textBefore.endsWith('-')) {
|
|
1477
1723
|
ev.preventDefault()
|
|
1478
1724
|
isFormatting = true
|
|
@@ -1484,7 +1730,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1484
1730
|
}
|
|
1485
1731
|
return
|
|
1486
1732
|
}
|
|
1487
|
-
|
|
1488
1733
|
if (ev.key === '"') {
|
|
1489
1734
|
ev.preventDefault()
|
|
1490
1735
|
isFormatting = true
|
|
@@ -1504,6 +1749,104 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1504
1749
|
}
|
|
1505
1750
|
} catch (e) { /* bestEffort */ void e; }
|
|
1506
1751
|
}
|
|
1752
|
+
const openInspectorModal = (meta, rawText) => {
|
|
1753
|
+
if (typeof document === 'undefined') return
|
|
1754
|
+
const old = document.getElementById('dsh-ru-inspector-modal')
|
|
1755
|
+
if (old) old.remove()
|
|
1756
|
+
const modal = document.createElement('div')
|
|
1757
|
+
modal.id = 'dsh-ru-inspector-modal'
|
|
1758
|
+
modal.className = 'rl-modal-mask'
|
|
1759
|
+
const k = meta ? (meta.key || (meta.ns ? meta.ns + '.' + meta.key : '')) : ''
|
|
1760
|
+
const src = meta ? (meta.source === 'override' ? 'Оверрайд' : meta.source === 'dom_zh' ? 'DOM ZH' : 'Словарь ' + (meta.ns || '')) : 'Текст'
|
|
1761
|
+
const v = (meta && meta.value) || rawText
|
|
1762
|
+
modal.innerHTML = '<div class="rl-modal-box">' +
|
|
1763
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
|
|
1764
|
+
'<div style="font-weight:600;font-size:14px;">🔍 Инспектор перевода</div>' +
|
|
1765
|
+
'<button type="button" class="rl-btn-close" style="background:none;border:none;color:var(--dsw-alias-label-secondary);font-size:18px;cursor:pointer;">×</button>' +
|
|
1766
|
+
'</div>' +
|
|
1767
|
+
'<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;font-size:12px;">' +
|
|
1768
|
+
'<span style="color:var(--dsw-alias-label-secondary);">Ключ:</span>' +
|
|
1769
|
+
'<code style="background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-state-brand-primary);padding:2px 6px;border-radius:4px;">' + (k || '(не опознан)') + '</code>' +
|
|
1770
|
+
'<span style="padding:1px 6px;border-radius:4px;font-size:10px;background:var(--dsw-alias-bg-layer-3);">' + src + '</span>' +
|
|
1771
|
+
'</div>' +
|
|
1772
|
+
'<div style="font-size:12px;color:var(--dsw-alias-label-secondary);word-break:break-word;">Текущий текст: <b class="rl-raw-text" style="color:var(--dsw-alias-label-primary)"></b></div>' +
|
|
1773
|
+
'<div style="display:flex;flex-direction:column;gap:6px;">' +
|
|
1774
|
+
'<label style="font-size:12px;color:var(--dsw-alias-label-secondary);">Новый перевод (оверрайд):</label>' +
|
|
1775
|
+
'<textarea class="rl-edit-val rl-select" style="min-height:50px;font-family:inherit;width:100%;resize:vertical;"></textarea>' +
|
|
1776
|
+
'</div>' +
|
|
1777
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px;">' +
|
|
1778
|
+
'<button type="button" class="rl-btn rl-cancel-btn">Отмена</button>' +
|
|
1779
|
+
'<button type="button" class="rl-btn rl-btn-primary rl-save-btn">Сохранить оверрайд</button>' +
|
|
1780
|
+
'</div>' +
|
|
1781
|
+
'</div>'
|
|
1782
|
+
modal.querySelector('.rl-raw-text').textContent = rawText
|
|
1783
|
+
const textarea = modal.querySelector('.rl-edit-val')
|
|
1784
|
+
textarea.value = v
|
|
1785
|
+
modal.querySelector('.rl-btn-close').onclick = () => modal.remove()
|
|
1786
|
+
modal.querySelector('.rl-cancel-btn').onclick = () => modal.remove()
|
|
1787
|
+
modal.onclick = (e) => { if (e.target === modal) modal.remove() }
|
|
1788
|
+
const saveBtn = modal.querySelector('.rl-save-btn')
|
|
1789
|
+
saveBtn.onclick = () => {
|
|
1790
|
+
const val = textarea.value.trim()
|
|
1791
|
+
const targetKey = k || rawText
|
|
1792
|
+
if (!targetKey || !val) return
|
|
1793
|
+
const cur = getOverrides()
|
|
1794
|
+
try { scope.set('overrides', Object.assign({}, cur, { [targetKey]: val })) } catch (_e) { /* noop */ }
|
|
1795
|
+
saveBtn.textContent = '✓ Сохранено'
|
|
1796
|
+
saveBtn.disabled = true
|
|
1797
|
+
setTimeout(() => modal.remove(), 600)
|
|
1798
|
+
}
|
|
1799
|
+
document.body.appendChild(modal)
|
|
1800
|
+
setTimeout(() => textarea.focus(), 50)
|
|
1801
|
+
}
|
|
1802
|
+
const inspectorOnClick = (e) => {
|
|
1803
|
+
if (!e.altKey) return
|
|
1804
|
+
if (e.target && e.target.closest && e.target.closest('#dsh-ru-inspector-modal')) return
|
|
1805
|
+
const raw = (e.target && (e.target.innerText || e.target.textContent) || '').trim()
|
|
1806
|
+
if (!raw) return
|
|
1807
|
+
e.preventDefault()
|
|
1808
|
+
e.stopPropagation()
|
|
1809
|
+
const meta = translationRegistry.get(raw) || (typeof findTranslationKey === 'function' ? findTranslationKey(raw, RU, getOverrides(), ZH_RU) : null)
|
|
1810
|
+
openInspectorModal(meta, raw)
|
|
1811
|
+
}
|
|
1812
|
+
ctx.effect(() => {
|
|
1813
|
+
document.addEventListener('click', inspectorOnClick, true)
|
|
1814
|
+
return () => {
|
|
1815
|
+
document.removeEventListener('click', inspectorOnClick, true)
|
|
1816
|
+
const m = document.getElementById('dsh-ru-inspector-modal')
|
|
1817
|
+
if (m) m.remove()
|
|
1818
|
+
}
|
|
1819
|
+
}, 'dsh-russian-lang: inspector')
|
|
1820
|
+
runtime.findTranslationKey = (text) => (typeof findTranslationKey === 'function' ? findTranslationKey(text, RU, getOverrides(), ZH_RU) : null)
|
|
1821
|
+
runtime.openInspector = openInspectorModal
|
|
1822
|
+
runtime.translationRegistry = translationRegistry
|
|
1823
|
+
runtime.bilingualCommandMatch = (query, cmd, opts) => (typeof bilingualCommandMatch === 'function' ? bilingualCommandMatch(query, cmd, opts) : { matched: false, score: 0 })
|
|
1824
|
+
runtime.filterCommands = (query, cmds, opts) => (typeof filterCommands === 'function' ? filterCommands(query, cmds, opts) : cmds)
|
|
1825
|
+
runtime.humanizeError = (err) => (typeof humanizeError === 'function' ? humanizeError(err) : null)
|
|
1826
|
+
runtime.formatErrorToast = (err) => (typeof formatErrorToast === 'function' ? formatErrorToast(err) : null)
|
|
1827
|
+
const toastObserver = typeof MutationObserver !== 'undefined' ? new MutationObserver((mutations) => {
|
|
1828
|
+
for (const m of mutations) {
|
|
1829
|
+
for (const node of m.addedNodes) {
|
|
1830
|
+
if (!node || node.nodeType !== 1) continue
|
|
1831
|
+
if (node.matches && (node.matches('.dsw-toast, [role="alert"], .toast, .ant-message-notice'))) {
|
|
1832
|
+
const text = (node.innerText || node.textContent || '').trim()
|
|
1833
|
+
if (/failed to fetch|network error|econnrefused|etimedout/i.test(text)) {
|
|
1834
|
+
const h = typeof humanizeError === 'function' ? humanizeError(text) : null
|
|
1835
|
+
if (h && h.title) {
|
|
1836
|
+
const targetEl = node.querySelector('.toast-body, .ant-message-custom-content') || node
|
|
1837
|
+
targetEl.textContent = h.title + ': ' + h.message
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}) : null
|
|
1844
|
+
ctx.effect(() => {
|
|
1845
|
+
if (toastObserver && typeof document !== 'undefined' && document.body) {
|
|
1846
|
+
toastObserver.observe(document.body, { childList: true, subtree: true })
|
|
1847
|
+
}
|
|
1848
|
+
return () => { if (toastObserver) toastObserver.disconnect() }
|
|
1849
|
+
}, 'dsh-russian-lang: toast-humanizer')
|
|
1507
1850
|
ctx.effect(() => {
|
|
1508
1851
|
document.addEventListener('input', layoutOnInput, true)
|
|
1509
1852
|
document.addEventListener('keydown', layoutOnKeydown, true)
|
|
@@ -1515,7 +1858,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1515
1858
|
layoutBadgeHide()
|
|
1516
1859
|
}
|
|
1517
1860
|
}, 'dsh-russian-lang: layout')
|
|
1518
|
-
|
|
1519
1861
|
if (!ctx.slots || !React) return
|
|
1520
1862
|
const toggleRu = (wantRu) => {
|
|
1521
1863
|
try {
|
|
@@ -1524,6 +1866,16 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1524
1866
|
} catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }
|
|
1525
1867
|
}
|
|
1526
1868
|
try {
|
|
1869
|
+
ctx.slots.inject('plugins.item', () =>
|
|
1870
|
+
ctx.slots.register({
|
|
1871
|
+
name: 'plugins.item',
|
|
1872
|
+
id: SETTINGS_NS_NAME,
|
|
1873
|
+
order: 60,
|
|
1874
|
+
label: () => 'Russian language',
|
|
1875
|
+
locale: SETTINGS_NS_NAME,
|
|
1876
|
+
inject: () => ({ scope, runtime, toggleRu }),
|
|
1877
|
+
}, SettingsCard),
|
|
1878
|
+
)
|
|
1527
1879
|
ctx.slots.inject('plugins.row.config', () =>
|
|
1528
1880
|
ctx.slots.register({
|
|
1529
1881
|
name: 'plugins.row.config',
|
|
@@ -1541,7 +1893,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1541
1893
|
}, SettingsCard),
|
|
1542
1894
|
)
|
|
1543
1895
|
} catch (err) { console.warn('dsh-russian-lang: settings slot unavailable', err) }
|
|
1544
|
-
|
|
1545
1896
|
try {
|
|
1546
1897
|
ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1547
1898
|
ctx.slots.register({
|
|
@@ -1553,7 +1904,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1553
1904
|
}, QuickLangSwitch),
|
|
1554
1905
|
)
|
|
1555
1906
|
} catch (err) { /* ignore if slot not declared */ }
|
|
1556
|
-
|
|
1557
1907
|
try {
|
|
1558
1908
|
ctx.slots.inject('conversation.chat.assistant-actions', () =>
|
|
1559
1909
|
ctx.slots.register({
|
|
@@ -1565,7 +1915,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1565
1915
|
}, TranslateTurnAction),
|
|
1566
1916
|
)
|
|
1567
1917
|
} catch (err) { /* ignore if slot not declared */ }
|
|
1568
|
-
|
|
1569
1918
|
try {
|
|
1570
1919
|
ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1571
1920
|
ctx.slots.register({
|
|
@@ -1578,7 +1927,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1578
1927
|
)
|
|
1579
1928
|
} catch (err) { /* ignore if slot not declared */ }
|
|
1580
1929
|
}
|
|
1581
|
-
|
|
1582
1930
|
function QuickLangSwitch(props) {
|
|
1583
1931
|
const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
|
|
1584
1932
|
const runtime = props.runtime || inj.runtime
|
|
@@ -1598,7 +1946,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1598
1946
|
onClick: () => { if (toggleRu) toggleRu(!isRu) },
|
|
1599
1947
|
}, React.createElement('span', { className: 'rl-lang-text' }, isRu ? 'RU' : 'EN'))
|
|
1600
1948
|
}
|
|
1601
|
-
|
|
1602
1949
|
function ExportMarkdownButton(props) {
|
|
1603
1950
|
const [done, setDone] = React.useState(false)
|
|
1604
1951
|
const onExport = () => {
|
|
@@ -1606,11 +1953,9 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1606
1953
|
const titleEl = document.querySelector('.dsw-session-title, [data-session-title], header h1, header h2, [class*="title"]')
|
|
1607
1954
|
const rawTitle = (titleEl && titleEl.textContent.trim()) || document.title || 'Диалог DSH'
|
|
1608
1955
|
const title = rawTitle.replace(/\s*—\s*DeepSeek Harness\s*$/, '').trim() || 'Диалог DSH'
|
|
1609
|
-
|
|
1610
1956
|
const flow = document.querySelector('[data-chat-flow]') || document.querySelector('[data-chat-flow-scroll]') || document.body
|
|
1611
1957
|
const flowItems = Array.from(flow.querySelectorAll('[data-chat-flow-kind]'))
|
|
1612
1958
|
const messages = []
|
|
1613
|
-
|
|
1614
1959
|
if (flowItems.length > 0) {
|
|
1615
1960
|
flowItems.forEach((node) => {
|
|
1616
1961
|
const kind = node.getAttribute('data-chat-flow-kind')
|
|
@@ -1628,7 +1973,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1628
1973
|
}
|
|
1629
1974
|
})
|
|
1630
1975
|
}
|
|
1631
|
-
|
|
1632
1976
|
if (messages.length === 0) {
|
|
1633
1977
|
const allElements = Array.from(document.querySelectorAll('[class*="userRow"], [class*="UserRow"], [class*="assistant-step"], [class*="AssistantMarkdown"], .dsw-turn-node, [data-role]'))
|
|
1634
1978
|
const seen = new Set()
|
|
@@ -1643,7 +1987,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1643
1987
|
messages.push({ role, content: text })
|
|
1644
1988
|
})
|
|
1645
1989
|
}
|
|
1646
|
-
|
|
1647
1990
|
const session = {
|
|
1648
1991
|
title,
|
|
1649
1992
|
createdAt: new Date().toISOString(),
|
|
@@ -1668,7 +2011,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1668
2011
|
console.warn('dsh-russian-lang: export md failed', err)
|
|
1669
2012
|
}
|
|
1670
2013
|
}
|
|
1671
|
-
|
|
1672
2014
|
return React.createElement('button', {
|
|
1673
2015
|
type: 'button',
|
|
1674
2016
|
className: 'rl-lang-chip rl-export-md-btn',
|
|
@@ -1676,7 +2018,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1676
2018
|
onClick: onExport,
|
|
1677
2019
|
}, React.createElement('span', { className: 'rl-lang-text' }, done ? '✓ MD' : '📥 MD'))
|
|
1678
2020
|
}
|
|
1679
|
-
|
|
1680
2021
|
async function translateTurnContent(text) {
|
|
1681
2022
|
if (!text || typeof text !== 'string') return { error: 'Текст для перевода пуст.' }
|
|
1682
2023
|
try {
|
|
@@ -1694,19 +2035,17 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1694
2035
|
return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
|
|
1695
2036
|
}
|
|
1696
2037
|
}
|
|
1697
|
-
|
|
1698
2038
|
function TranslateTurnAction(props) {
|
|
1699
2039
|
const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1700
2040
|
const [loading, setLoading] = React.useState(false)
|
|
1701
2041
|
const [open, setOpen] = React.useState(false)
|
|
1702
2042
|
const [upStatus, setUpStatus] = React.useState({
|
|
1703
|
-
currentVersion: '0.
|
|
2043
|
+
currentVersion: '0.3.1',
|
|
1704
2044
|
latestVersion: undefined,
|
|
1705
2045
|
updateAvailable: false
|
|
1706
2046
|
})
|
|
1707
2047
|
const [upLoading, setUpLoading] = React.useState(false)
|
|
1708
2048
|
const [upMsg, setUpMsg] = React.useState(null)
|
|
1709
|
-
|
|
1710
2049
|
const checkUpdate = () => {
|
|
1711
2050
|
setUpLoading(true)
|
|
1712
2051
|
setUpMsg(null)
|
|
@@ -1721,7 +2060,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1721
2060
|
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1722
2061
|
})
|
|
1723
2062
|
}
|
|
1724
|
-
|
|
1725
2063
|
const triggerUpdate = () => {
|
|
1726
2064
|
setUpLoading(true)
|
|
1727
2065
|
setUpMsg(null)
|
|
@@ -1744,18 +2082,15 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1744
2082
|
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1745
2083
|
})
|
|
1746
2084
|
}
|
|
1747
|
-
|
|
1748
2085
|
const [transStatus, setTransStatus] = React.useState(null)
|
|
1749
2086
|
const [transStarting, setTransStarting] = React.useState(false)
|
|
1750
2087
|
const [transMsg, setTransMsg] = React.useState(null)
|
|
1751
|
-
|
|
1752
2088
|
const fetchTransStatus = () => {
|
|
1753
2089
|
fetch('/api/dsh-russian-lang/translator/status')
|
|
1754
2090
|
.then((r) => r.json())
|
|
1755
2091
|
.then((data) => setTransStatus(data))
|
|
1756
2092
|
.catch(() => setTransStatus(null))
|
|
1757
2093
|
}
|
|
1758
|
-
|
|
1759
2094
|
const startLibreTranslate = () => {
|
|
1760
2095
|
setTransStarting(true)
|
|
1761
2096
|
setTransMsg(null)
|
|
@@ -1775,15 +2110,12 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1775
2110
|
setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
|
|
1776
2111
|
})
|
|
1777
2112
|
}
|
|
1778
|
-
|
|
1779
2113
|
React.useEffect(() => {
|
|
1780
2114
|
if (open) {
|
|
1781
2115
|
checkUpdate()
|
|
1782
2116
|
fetchTransStatus()
|
|
1783
2117
|
}
|
|
1784
2118
|
}, [open])
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
2119
|
return React.createElement('button', {
|
|
1788
2120
|
type: 'button',
|
|
1789
2121
|
className: 'rl-action-btn' + (open ? ' rl-action-btn-active' : ''),
|
|
@@ -1794,7 +2126,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1794
2126
|
const btn = ev.currentTarget
|
|
1795
2127
|
const tailFlowItem = btn.closest('[data-chat-flow-kind="turn-tail"]') || btn.closest('[data-chat-flow-kind]') || btn.closest('[data-turn-tail]')?.closest('[data-chat-flow-kind]') || btn.closest('[data-turn-tail]')
|
|
1796
2128
|
if (!tailFlowItem) return
|
|
1797
|
-
|
|
1798
2129
|
const parentContainer = tailFlowItem.parentElement
|
|
1799
2130
|
let box = parentContainer ? parentContainer.querySelector('.rl-turn-translation[data-tail-key="' + (tailFlowItem.getAttribute('data-chat-flow-key') || '') + '"]') : null
|
|
1800
2131
|
if (!box) {
|
|
@@ -1805,7 +2136,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1805
2136
|
setOpen(box.style.display !== 'none')
|
|
1806
2137
|
return
|
|
1807
2138
|
}
|
|
1808
|
-
|
|
1809
2139
|
setLoading(true)
|
|
1810
2140
|
try {
|
|
1811
2141
|
const assistantNodes = []
|
|
@@ -1816,7 +2146,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1816
2146
|
}
|
|
1817
2147
|
prev = prev.previousElementSibling
|
|
1818
2148
|
}
|
|
1819
|
-
|
|
1820
2149
|
let rawText = ''
|
|
1821
2150
|
if (assistantNodes.length > 0) {
|
|
1822
2151
|
rawText = assistantNodes.map((node) => {
|
|
@@ -1836,19 +2165,16 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1836
2165
|
}).filter(Boolean).join('\n\n')
|
|
1837
2166
|
}
|
|
1838
2167
|
}
|
|
1839
|
-
|
|
1840
2168
|
if (!rawText) {
|
|
1841
2169
|
const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
|
|
1842
2170
|
if (prose) rawText = prose.innerText.trim()
|
|
1843
2171
|
}
|
|
1844
|
-
|
|
1845
2172
|
let transResult = { error: 'Не удалось обнаружить текст сообщения ассистента для перевода.' }
|
|
1846
2173
|
if (rawText) {
|
|
1847
2174
|
transResult = await translateTurnContent(rawText)
|
|
1848
2175
|
}
|
|
1849
2176
|
const isErr = !!transResult.error
|
|
1850
2177
|
const displayText = transResult.translatedText || transResult.error || ''
|
|
1851
|
-
|
|
1852
2178
|
box = document.createElement('div')
|
|
1853
2179
|
box.className = 'rl-turn-translation'
|
|
1854
2180
|
const key = tailFlowItem.getAttribute('data-chat-flow-key')
|
|
@@ -1860,9 +2186,8 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1860
2186
|
'<button type="button" class="rl-trans-btn rl-btn-close" title="Закрыть">✕</button>' +
|
|
1861
2187
|
'</div>' +
|
|
1862
2188
|
'</div>' +
|
|
1863
|
-
'<div class="rl-trans-body"' + (isErr ? ' style="color: var(--dsw-alias-
|
|
2189
|
+
'<div class="rl-trans-body"' + (isErr ? ' style="color: var(--dsw-alias-state-warning-primary); font-size: 13px;"' : '') + '></div>'
|
|
1864
2190
|
box.querySelector('.rl-trans-body').textContent = displayText
|
|
1865
|
-
|
|
1866
2191
|
const copyBtn = box.querySelector('.rl-btn-copy')
|
|
1867
2192
|
if (copyBtn) {
|
|
1868
2193
|
copyBtn.addEventListener('click', (e) => {
|
|
@@ -1872,14 +2197,12 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1872
2197
|
setTimeout(() => { copyBtn.textContent = '📋 Копировать' }, 2000)
|
|
1873
2198
|
})
|
|
1874
2199
|
}
|
|
1875
|
-
|
|
1876
2200
|
const closeBtn = box.querySelector('.rl-btn-close')
|
|
1877
2201
|
closeBtn.addEventListener('click', (e) => {
|
|
1878
2202
|
e.stopPropagation()
|
|
1879
2203
|
box.style.display = 'none'
|
|
1880
2204
|
setOpen(false)
|
|
1881
2205
|
})
|
|
1882
|
-
|
|
1883
2206
|
if (parentContainer) {
|
|
1884
2207
|
parentContainer.insertBefore(box, tailFlowItem)
|
|
1885
2208
|
} else {
|
|
@@ -1894,7 +2217,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1894
2217
|
},
|
|
1895
2218
|
}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
1896
2219
|
}
|
|
1897
|
-
|
|
1898
2220
|
function SettingsCard(props) {
|
|
1899
2221
|
const inj = typeof props.inject === 'function'
|
|
1900
2222
|
? (props.inject() || {})
|
|
@@ -1903,7 +2225,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1903
2225
|
const runtime = props.runtime || inj.runtime
|
|
1904
2226
|
const toggleRu = props.toggleRu || inj.toggleRu
|
|
1905
2227
|
const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1906
|
-
|
|
1907
2228
|
const [open, setOpen] = React.useState(false)
|
|
1908
2229
|
const [snap, setSnap] = React.useState(
|
|
1909
2230
|
() => (scope && scope.getSnapshot ? scope.getSnapshot() : { status: 'loading', value: {} }))
|
|
@@ -1911,7 +2232,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1911
2232
|
() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
|
|
1912
2233
|
const [typo, setTypoState] = React.useState(() =>
|
|
1913
2234
|
(snap.value && snap.value.typography) || {})
|
|
1914
|
-
|
|
1915
2235
|
React.useEffect(() => {
|
|
1916
2236
|
if (!scope || !scope.subscribe) return undefined
|
|
1917
2237
|
const un = scope.subscribe(() => {
|
|
@@ -1930,12 +2250,13 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1930
2250
|
return un
|
|
1931
2251
|
} catch (e) { return undefined }
|
|
1932
2252
|
}, [runtime])
|
|
1933
|
-
|
|
1934
2253
|
const status = snap.status || 'loading'
|
|
1935
2254
|
const value = snap.value || {}
|
|
1936
2255
|
const typography = typo
|
|
1937
|
-
const
|
|
1938
|
-
|
|
2256
|
+
const overrides = value.overrides || {}
|
|
2257
|
+
const overridesCount = Object.keys(overrides).length
|
|
2258
|
+
const [newKey, setNewKey] = React.useState('')
|
|
2259
|
+
const [newVal, setNewVal] = React.useState('')
|
|
1939
2260
|
const setTypo = (patch) => {
|
|
1940
2261
|
const next = Object.assign({}, typo, patch)
|
|
1941
2262
|
setTypoState(next)
|
|
@@ -1949,24 +2270,20 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1949
2270
|
}
|
|
1950
2271
|
}
|
|
1951
2272
|
const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
|
|
1952
|
-
|
|
1953
2273
|
const checkbox = (checked, onChange, disabled) =>
|
|
1954
2274
|
React.createElement('input', {
|
|
1955
2275
|
type: 'checkbox', checked: !!checked, disabled: !!disabled,
|
|
1956
2276
|
className: 'rl-check', onChange: (ev) => onChange(ev),
|
|
1957
2277
|
})
|
|
1958
|
-
|
|
1959
2278
|
const disabled = status !== 'ready'
|
|
1960
2279
|
const statusLine = status === 'ready'
|
|
1961
2280
|
? ''
|
|
1962
2281
|
: (status === 'unavailable' ? t('statusUnavailable') : t('statusLoading'))
|
|
1963
|
-
|
|
1964
2282
|
let ChevronIcon = null
|
|
1965
2283
|
try {
|
|
1966
2284
|
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
1967
2285
|
ChevronIcon = primitives && primitives.IconChevronDownOutline14
|
|
1968
2286
|
} catch (_) { ChevronIcon = null }
|
|
1969
|
-
|
|
1970
2287
|
const FallbackChevron = () => React.createElement('svg', {
|
|
1971
2288
|
width: 14, height: 14, viewBox: '0 0 14 14', fill: 'none',
|
|
1972
2289
|
'aria-hidden': 'true',
|
|
@@ -1974,17 +2291,13 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1974
2291
|
d: 'M3.5 5.25 7 8.75l3.5-3.5', stroke: 'currentColor',
|
|
1975
2292
|
strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
1976
2293
|
}))
|
|
1977
|
-
|
|
1978
2294
|
const Chevron = ChevronIcon || FallbackChevron
|
|
1979
|
-
|
|
1980
2295
|
const presetKey = value.agentPromptPreset || 'technical_expert'
|
|
1981
2296
|
const presetInfo = typeof SYSTEM_PROMPT_PRESETS !== 'undefined' ? SYSTEM_PROMPT_PRESETS[presetKey] : null
|
|
1982
|
-
|
|
1983
2297
|
if (props && props.view === 'summary') {
|
|
1984
2298
|
return React.createElement('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
|
|
1985
2299
|
}
|
|
1986
2300
|
const page = !!(props && props.view === 'page')
|
|
1987
|
-
|
|
1988
2301
|
return React.createElement('div', { className: page ? 'rl-page-seat' : 'rl-card' },
|
|
1989
2302
|
React.createElement('button', {
|
|
1990
2303
|
type: 'button',
|
|
@@ -2007,7 +2320,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2007
2320
|
}, React.createElement(Chevron, null))),
|
|
2008
2321
|
(page || open) && React.createElement('div', { className: 'rl-body' },
|
|
2009
2322
|
React.createElement('div', { className: 'rl-page' },
|
|
2010
|
-
|
|
2011
2323
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2012
2324
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2013
2325
|
React.createElement('span', null, t('secLanguage')),
|
|
@@ -2028,7 +2340,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2028
2340
|
overridesCount > 0 ? React.createElement('div', { className: 'rl-badge rl-badge-dim', style: { alignSelf: 'flex-start' } },
|
|
2029
2341
|
t('overridesCount') + ': ' + overridesCount) : null
|
|
2030
2342
|
),
|
|
2031
|
-
|
|
2032
2343
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2033
2344
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2034
2345
|
React.createElement('span', null, t('secTypography')),
|
|
@@ -2084,7 +2395,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2084
2395
|
React.createElement('span', null, t('altLHintText'))
|
|
2085
2396
|
)
|
|
2086
2397
|
),
|
|
2087
|
-
|
|
2088
2398
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2089
2399
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2090
2400
|
React.createElement('span', null, t('secAgentPrompt')),
|
|
@@ -2126,7 +2436,10 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2126
2436
|
},
|
|
2127
2437
|
React.createElement('option', { value: 'technical_expert' }, t('presetExpert')),
|
|
2128
2438
|
React.createElement('option', { value: 'tech_writer' }, t('presetWriter')),
|
|
2129
|
-
React.createElement('option', { value: 'concise' }, t('presetConcise'))
|
|
2439
|
+
React.createElement('option', { value: 'concise' }, t('presetConcise')),
|
|
2440
|
+
React.createElement('option', { value: 'code_reviewer' }, t('presetReviewer')),
|
|
2441
|
+
React.createElement('option', { value: 'architect' }, t('presetArchitect')),
|
|
2442
|
+
React.createElement('option', { value: 'tutor' }, t('presetTutor'))
|
|
2130
2443
|
)
|
|
2131
2444
|
),
|
|
2132
2445
|
presetInfo ? React.createElement('div', { className: 'rl-preview-box' },
|
|
@@ -2135,7 +2448,6 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2135
2448
|
) : null
|
|
2136
2449
|
) : null
|
|
2137
2450
|
),
|
|
2138
|
-
|
|
2139
2451
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2140
2452
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2141
2453
|
React.createElement('span', null, t('secUpdater')),
|
|
@@ -2147,9 +2459,9 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2147
2459
|
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },
|
|
2148
2460
|
React.createElement('div', null,
|
|
2149
2461
|
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },
|
|
2150
|
-
t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.
|
|
2462
|
+
t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.1')),
|
|
2151
2463
|
upStatus.updateAvailable
|
|
2152
|
-
? React.createElement('div', { style: { color: 'var(--dsw-alias-
|
|
2464
|
+
? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },
|
|
2153
2465
|
t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
|
|
2154
2466
|
: React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },
|
|
2155
2467
|
t('updaterUpToDate'))
|
|
@@ -2171,13 +2483,12 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2171
2483
|
upMsg ? React.createElement('div', {
|
|
2172
2484
|
style: {
|
|
2173
2485
|
marginTop: '10px', padding: '8px 12px', borderRadius: '8px',
|
|
2174
|
-
background: upMsg.type === 'ok' ? '
|
|
2175
|
-
color: upMsg.type === 'ok' ? '
|
|
2486
|
+
background: upMsg.type === 'ok' ? 'color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)' : 'color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)',
|
|
2487
|
+
color: upMsg.type === 'ok' ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontSize: '12px', fontWeight: 500
|
|
2176
2488
|
}
|
|
2177
2489
|
}, upMsg.text) : null
|
|
2178
2490
|
)
|
|
2179
2491
|
),
|
|
2180
|
-
|
|
2181
2492
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2182
2493
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2183
2494
|
React.createElement('span', null, t('secTranslator')),
|
|
@@ -2208,8 +2519,8 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2208
2519
|
value.translateEngine === 'google' ? React.createElement('div', {
|
|
2209
2520
|
style: {
|
|
2210
2521
|
marginTop: '10px', padding: '8px 12px', borderRadius: '8px',
|
|
2211
|
-
background: '
|
|
2212
|
-
color: 'var(--dsw-alias-
|
|
2522
|
+
background: 'color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-state-warning-primary) 30%, transparent)',
|
|
2523
|
+
color: 'var(--dsw-alias-state-warning-primary)', fontSize: '12px', fontWeight: 500, lineHeight: 1.4
|
|
2213
2524
|
}
|
|
2214
2525
|
}, t('googleWarn')) : null,
|
|
2215
2526
|
value.translateEngine === 'local' ? React.createElement('div', {
|
|
@@ -2230,14 +2541,78 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2230
2541
|
transMsg ? React.createElement('div', {
|
|
2231
2542
|
style: {
|
|
2232
2543
|
marginTop: '6px', padding: '8px 12px', borderRadius: '8px',
|
|
2233
|
-
background: transMsg.type === 'ok' ? '
|
|
2234
|
-
color: transMsg.type === 'ok' ? '
|
|
2544
|
+
background: transMsg.type === 'ok' ? 'color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)' : 'color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)',
|
|
2545
|
+
color: transMsg.type === 'ok' ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontSize: '12px', fontWeight: 500
|
|
2235
2546
|
}
|
|
2236
2547
|
}, transMsg.text) : null
|
|
2237
2548
|
) : null
|
|
2238
2549
|
)
|
|
2239
2550
|
),
|
|
2240
|
-
|
|
2551
|
+
React.createElement('div', { className: 'rl-section-card' },
|
|
2552
|
+
React.createElement('div', { className: 'rl-section-title' },
|
|
2553
|
+
React.createElement('span', null, t('secOverrides')),
|
|
2554
|
+
React.createElement('span', { className: 'rl-badge rl-badge-dim' },
|
|
2555
|
+
String(overridesCount) + ' ' + (typeof plural === 'function' ? plural(overridesCount, ['запись', 'записи', 'записей']) : 'записей'))
|
|
2556
|
+
),
|
|
2557
|
+
React.createElement('div', { className: 'rl-section-desc' }, t('secOverridesDesc')),
|
|
2558
|
+
React.createElement('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },
|
|
2559
|
+
React.createElement('input', {
|
|
2560
|
+
className: 'rl-select',
|
|
2561
|
+
style: { flex: '1 1 180px', maxWidth: 'none' },
|
|
2562
|
+
placeholder: t('overrideKeyPlaceholder'),
|
|
2563
|
+
value: newKey,
|
|
2564
|
+
onChange: (e) => setNewKey(e.target.value),
|
|
2565
|
+
}),
|
|
2566
|
+
React.createElement('input', {
|
|
2567
|
+
className: 'rl-select',
|
|
2568
|
+
style: { flex: '2 1 240px', maxWidth: 'none' },
|
|
2569
|
+
placeholder: t('overrideValuePlaceholder'),
|
|
2570
|
+
value: newVal,
|
|
2571
|
+
onChange: (e) => setNewVal(e.target.value),
|
|
2572
|
+
}),
|
|
2573
|
+
React.createElement('button', {
|
|
2574
|
+
type: 'button',
|
|
2575
|
+
className: 'rl-btn rl-btn-primary',
|
|
2576
|
+
disabled: !newKey.trim() || !newVal.trim(),
|
|
2577
|
+
onClick: () => {
|
|
2578
|
+
const k = newKey.trim()
|
|
2579
|
+
const v = newVal.trim()
|
|
2580
|
+
if (!k || !v) return
|
|
2581
|
+
const next = Object.assign({}, overrides, { [k]: v })
|
|
2582
|
+
try { scope.set('overrides', next) } catch (_e) { /* noop */ }
|
|
2583
|
+
setNewKey('')
|
|
2584
|
+
setNewVal('')
|
|
2585
|
+
},
|
|
2586
|
+
}, t('overrideAddBtn'))
|
|
2587
|
+
),
|
|
2588
|
+
overridesCount === 0
|
|
2589
|
+
? React.createElement('div', { className: 'rl-hint-text', style: { fontStyle: 'italic', padding: '4px 0' } }, t('overrideEmpty'))
|
|
2590
|
+
: React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },
|
|
2591
|
+
Object.keys(overrides).map((k) =>
|
|
2592
|
+
React.createElement('div', {
|
|
2593
|
+
key: k,
|
|
2594
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 10px', background: 'var(--dsw-alias-bg-layer-3)', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', fontSize: '12px' },
|
|
2595
|
+
},
|
|
2596
|
+
React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', overflow: 'hidden' } },
|
|
2597
|
+
React.createElement('code', { style: { color: 'var(--dsw-alias-state-brand-primary)', fontWeight: 600 } }, k),
|
|
2598
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } }, '→'),
|
|
2599
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-primary)' } }, overrides[k])
|
|
2600
|
+
),
|
|
2601
|
+
React.createElement('button', {
|
|
2602
|
+
type: 'button',
|
|
2603
|
+
className: 'rl-btn',
|
|
2604
|
+
style: { padding: '2px 8px', height: '24px', fontSize: '11px', color: 'var(--dsw-alias-state-error-primary)' },
|
|
2605
|
+
onClick: () => {
|
|
2606
|
+
const next = Object.assign({}, overrides)
|
|
2607
|
+
delete next[k]
|
|
2608
|
+
try { scope.set('overrides', next) } catch (_e) { /* noop */ }
|
|
2609
|
+
},
|
|
2610
|
+
}, t('overrideDelete'))
|
|
2611
|
+
)
|
|
2612
|
+
)
|
|
2613
|
+
),
|
|
2614
|
+
React.createElement('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip'))
|
|
2615
|
+
),
|
|
2241
2616
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2242
2617
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2243
2618
|
React.createElement('span', null, t('secSupport')),
|
|
@@ -2260,7 +2635,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2260
2635
|
),
|
|
2261
2636
|
React.createElement('div', { className: 'rl-actions-row' },
|
|
2262
2637
|
React.createElement('a', {
|
|
2263
|
-
href: makeIssueUrl({}, '0.
|
|
2638
|
+
href: makeIssueUrl({}, '0.3.1'),
|
|
2264
2639
|
target: '_blank',
|
|
2265
2640
|
rel: 'noopener noreferrer',
|
|
2266
2641
|
className: 'rl-btn rl-btn-primary'
|
|
@@ -2274,8 +2649,9 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2274
2649
|
)
|
|
2275
2650
|
)
|
|
2276
2651
|
}
|
|
2277
|
-
|
|
2278
2652
|
const RL_CSS = [
|
|
2653
|
+
'.rl-modal-mask{position:fixed;top:0;left:0;width:100vw;height:100vh;background:var(--dsw-alias-bg-mask);display:flex;align-items:center;justify-content:center;z-index:99999;backdrop-filter:blur(2px)}',
|
|
2654
|
+
'.rl-modal-box{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;width:calc(100vw - 40px);max-width:500px;box-shadow:var(--dsw-alias-shadow-l3);display:flex;flex-direction:column;gap:12px;padding:18px}',
|
|
2279
2655
|
'.rl-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;overflow:hidden;transition:border-color .15s ease}',
|
|
2280
2656
|
'.rl-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 18px}',
|
|
2281
2657
|
'.rl-head:hover{background:var(--dsw-alias-bg-layer-2)}',
|
|
@@ -2292,8 +2668,8 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2292
2668
|
'.rl-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:10px}',
|
|
2293
2669
|
'.rl-grid-3{display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:10px}',
|
|
2294
2670
|
'.rl-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}',
|
|
2295
|
-
'.rl-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:
|
|
2296
|
-
'.rl-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:
|
|
2671
|
+
'.rl-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}',
|
|
2672
|
+
'.rl-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}',
|
|
2297
2673
|
'.rl-badge-dim{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}',
|
|
2298
2674
|
'.rl-item-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}',
|
|
2299
2675
|
'.rl-item-head{display:flex;align-items:center;justify-content:space-between;gap:8px}',
|
|
@@ -2340,8 +2716,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2340
2716
|
tag.textContent = RL_CSS
|
|
2341
2717
|
document.head.appendChild(tag)
|
|
2342
2718
|
}
|
|
2343
|
-
|
|
2344
2719
|
module.exports = { apply, inject: ['locale', 'connection', 'remote', 'settingsScope', 'slots'] }
|
|
2345
2720
|
return module.exports
|
|
2346
2721
|
},
|
|
2347
|
-
})
|
|
2722
|
+
})
|