@goodandready/dsh-russian-lang 0.2.7 → 0.2.9
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 +39 -1
- package/docs/media/banner.jpg +0 -0
- package/lib/client.js +630 -17
- package/lib/index.js +16 -8
- package/lib/pure.js +203 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// в собственном namespace, чтобы запись переживала браузеры и машины через
|
|
6
6
|
// штатный механизм настроек DSH.
|
|
7
7
|
import z from '@deepseek-ai/schemastery'
|
|
8
|
+
import { SYSTEM_PROMPT_PRESETS } from './pure.js'
|
|
8
9
|
|
|
9
10
|
export const name = '@goodandready/dsh-russian-lang'
|
|
10
11
|
|
|
@@ -13,9 +14,13 @@ const RussianLangSettingsSchema = z.object({
|
|
|
13
14
|
overrides: z.dict(z.string()).required(false),
|
|
14
15
|
typography: z.object({
|
|
15
16
|
enabled: z.boolean().required(false),
|
|
16
|
-
yo: z.boolean().required(false)
|
|
17
|
+
yo: z.boolean().required(false),
|
|
18
|
+
liveInput: z.boolean().required(false)
|
|
17
19
|
}).required(false),
|
|
18
|
-
agentPrompt: z.boolean().required(false)
|
|
20
|
+
agentPrompt: z.boolean().required(false),
|
|
21
|
+
agentPromptPreset: z.string().required(false),
|
|
22
|
+
slashAliases: z.boolean().required(false),
|
|
23
|
+
quickSwitch: z.boolean().required(false)
|
|
19
24
|
})
|
|
20
25
|
|
|
21
26
|
export function apply(ctx) {
|
|
@@ -24,19 +29,22 @@ export function apply(ctx) {
|
|
|
24
29
|
})
|
|
25
30
|
|
|
26
31
|
// #68: русский системный промпт агента (опционально). Регистрируем секцию
|
|
27
|
-
// промпта, которая
|
|
32
|
+
// промпта, которая задаёт стиль ответов по-русски, когда включён флаг
|
|
28
33
|
// russian-lang.agentPrompt. Секция — официальная точка расширения ядра.
|
|
29
34
|
ctx.inject(['settings', 'systemPrompt'], (settingsCtx, prompt) => {
|
|
30
35
|
const NS = 'russian-lang'
|
|
31
36
|
const RU_SECTION = 'dsh-russian-lang-agent-prompt'
|
|
32
|
-
const
|
|
33
|
-
let
|
|
37
|
+
const DEFAULT_RU_TEXT = 'Отвечай пользователю на русском языке. Если пользователь пишет на другом языке, отвечай на его языке.'
|
|
38
|
+
let currentText = ''
|
|
34
39
|
const sync = () => {
|
|
35
40
|
const value = settingsCtx.settings.get(NS)
|
|
36
41
|
const want = !!(value && value.agentPrompt)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const presetKey = value && value.agentPromptPreset
|
|
43
|
+
const preset = SYSTEM_PROMPT_PRESETS[presetKey]
|
|
44
|
+
const text = want ? (preset ? preset.text : DEFAULT_RU_TEXT) : ''
|
|
45
|
+
if (text === currentText) return
|
|
46
|
+
currentText = text
|
|
47
|
+
prompt.section({ name: RU_SECTION, order: 1000, text })
|
|
40
48
|
}
|
|
41
49
|
ctx.on('settings/updated', (ns) => { if (ns === NS) sync() })
|
|
42
50
|
sync()
|
package/lib/pure.js
CHANGED
|
@@ -341,3 +341,206 @@ export const makeLayout = (freq, localDict = new Set()) => {
|
|
|
341
341
|
}
|
|
342
342
|
return { ruWordFraction, candidate, learnWords, localDict }
|
|
343
343
|
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
// ---------------------------------------------------- живая типографика инпута
|
|
347
|
+
|
|
348
|
+
// Предлоги и союзы, после которых ставится неразрывный пробел (U+00A0)
|
|
349
|
+
const SHORT_WORDS_RE = /(^|[\s(«])([вВнНсСпПоОкКуУиИаА]|из|от|до|за|по|со|во|ко|об|на|под|над|при|про|без|для|не|ни|но)( )/g
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Живая типографика для поля ввода DSH.
|
|
353
|
+
* Заменяет кавычки "" на «», дефисы -- на тире —, многоточие ... на …,
|
|
354
|
+
* и добавляет неразрывные пробелы после коротких предлогов.
|
|
355
|
+
* ИГНОРИРУЕТ код внутри обратных кавычек `...` и блоков ```...```.
|
|
356
|
+
*/
|
|
357
|
+
export const formatInputLive = (text) => {
|
|
358
|
+
if (!text || typeof text !== 'string') return text
|
|
359
|
+
|
|
360
|
+
// Разбиваем по блокам кода (inline `...` или fenced ```...```), чтобы не портить синтаксис
|
|
361
|
+
const parts = text.split(/(```[\s\S]*?```|`[^`\n]*`)/g)
|
|
362
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
363
|
+
let s = parts[i]
|
|
364
|
+
if (!s) continue
|
|
365
|
+
|
|
366
|
+
// 1. Троеточие
|
|
367
|
+
s = s.replace(/\.{3}/g, '…')
|
|
368
|
+
|
|
369
|
+
// 2. Длинное тире (-- -> —)
|
|
370
|
+
s = s.replace(/(^|\s)--(\s|$)/g, '$1—$2')
|
|
371
|
+
|
|
372
|
+
// 3. Кавычки-ёлочки («...») с учётом открывающейся/закрывающейся
|
|
373
|
+
s = s.replace(/(^|[\s([{-])"/g, '$1«')
|
|
374
|
+
s = s.replace(/"/g, '»')
|
|
375
|
+
|
|
376
|
+
// 4. Неразрывный пробел после коротких слов/предлогов
|
|
377
|
+
s = s.replace(SHORT_WORDS_RE, '$1$2\u00A0')
|
|
378
|
+
|
|
379
|
+
parts[i] = s
|
|
380
|
+
}
|
|
381
|
+
return parts.join('')
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ------------------------------------------------------- алиасы слэш-команд
|
|
385
|
+
|
|
386
|
+
export const RUSSIAN_SLASH_ALIASES = {
|
|
387
|
+
'/цель': '/goal',
|
|
388
|
+
'/сжать': '/compact',
|
|
389
|
+
'/план': '/plan',
|
|
390
|
+
'/экспорт': '/export',
|
|
391
|
+
'/отзыв': '/feedback',
|
|
392
|
+
'/разрешение': '/permission',
|
|
393
|
+
'/разрешения': '/permission',
|
|
394
|
+
'/память': '/memory',
|
|
395
|
+
'/помощь': '/help',
|
|
396
|
+
'/очистить': '/clear',
|
|
397
|
+
'/сессия': '/session',
|
|
398
|
+
'/модель': '/model'
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Разворачивает русский алиас слэш-команды в каноническую команду DSH.
|
|
403
|
+
* Например: "/цель сделать тесты" -> "/goal сделать тесты"
|
|
404
|
+
*/
|
|
405
|
+
export const expandSlashAlias = (input) => {
|
|
406
|
+
if (!input || typeof input !== 'string' || !input.startsWith('/')) return input
|
|
407
|
+
const match = input.match(/^(\s*\/[^\s]+)(.*)$/)
|
|
408
|
+
if (!match) return input
|
|
409
|
+
const [, cmd, rest] = match
|
|
410
|
+
const lowerCmd = cmd.trim().toLowerCase()
|
|
411
|
+
if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {
|
|
412
|
+
return RUSSIAN_SLASH_ALIASES[lowerCmd] + rest
|
|
413
|
+
}
|
|
414
|
+
return input
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// --------------------------------------------------- фонетический транслит
|
|
418
|
+
|
|
419
|
+
const PHONETIC_LAT_TO_CYR = [
|
|
420
|
+
['shch', 'щ'], ['yo', 'ё'], ['zh', 'ж'], ['ch', 'ч'], ['sh', 'ш'],
|
|
421
|
+
['yu', 'ю'], ['ya', 'я'], ['ts', 'ц'],
|
|
422
|
+
['a', 'а'], ['b', 'б'], ['v', 'в'], ['g', 'г'], ['d', 'д'], ['e', 'е'],
|
|
423
|
+
['z', 'з'], ['i', 'и'], ['j', 'й'], ['k', 'к'], ['l', 'л'], ['m', 'м'],
|
|
424
|
+
['n', 'н'], ['o', 'о'], ['p', 'п'], ['r', 'р'], ['s', 'с'], ['t', 'т'],
|
|
425
|
+
['u', 'у'], ['f', 'ф'], ['h', 'х'], ['c', 'ц'], ['y', 'ы'], ['x', 'кс']
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
const PHONETIC_CYR_TO_LAT = [
|
|
429
|
+
['щ', 'shch'], ['ё', 'yo'], ['ж', 'zh'], ['ч', 'ch'], ['ш', 'sh'],
|
|
430
|
+
['ю', 'yu'], ['я', 'ya'], ['ц', 'ts'],
|
|
431
|
+
['а', 'a'], ['б', 'b'], ['в', 'v'], ['г', 'g'], ['д', 'd'], ['е', 'e'],
|
|
432
|
+
['з', 'z'], ['и', 'i'], ['й', 'j'], ['к', 'k'], ['л', 'l'], ['м', 'm'],
|
|
433
|
+
['н', 'n'], ['о', 'o'], ['п', 'p'], ['р', 'r'], ['с', 's'], ['т', 't'],
|
|
434
|
+
['у', 'u'], ['ф', 'f'], ['х', 'h'], ['ы', 'y'], ['э', 'e'], ['ъ', ''], ['ь', '']
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
export const phoneticTranslit = (text, direction = 'lat2cyr') => {
|
|
438
|
+
if (!text || typeof text !== 'string') return text
|
|
439
|
+
let res = text
|
|
440
|
+
const pairs = direction === 'lat2cyr' ? PHONETIC_LAT_TO_CYR : PHONETIC_CYR_TO_LAT
|
|
441
|
+
for (const [from, to] of pairs) {
|
|
442
|
+
const fromUpper = from.toUpperCase()
|
|
443
|
+
const fromTitle = from[0].toUpperCase() + from.slice(1)
|
|
444
|
+
const toUpper = to.toUpperCase()
|
|
445
|
+
const toTitle = to[0] ? to[0].toUpperCase() + to.slice(1) : ''
|
|
446
|
+
|
|
447
|
+
if (from.length > 1) {
|
|
448
|
+
res = res.replaceAll(fromUpper, toUpper)
|
|
449
|
+
res = res.replaceAll(fromTitle, toTitle)
|
|
450
|
+
}
|
|
451
|
+
res = res.replaceAll(from, to)
|
|
452
|
+
if (from.length === 1) {
|
|
453
|
+
res = res.replaceAll(fromUpper, toUpper)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return res
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ---------------------------------------------------- детектор раскладки
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Определяет преобладающий язык ввода в тексте: 'RU', 'EN' или null.
|
|
463
|
+
*/
|
|
464
|
+
export const detectInputLayout = (text) => {
|
|
465
|
+
if (!text || typeof text !== 'string') return null
|
|
466
|
+
const clean = text.replace(/`[^`]*`/g, '').replace(/https?:\/\/\S+/g, '')
|
|
467
|
+
let cyr = 0
|
|
468
|
+
let lat = 0
|
|
469
|
+
for (const ch of clean) {
|
|
470
|
+
const code = ch.charCodeAt(0)
|
|
471
|
+
if ((code >= 0x0400 && code <= 0x04FF) || code === 0x0500) cyr++
|
|
472
|
+
else if ((code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A)) lat++
|
|
473
|
+
}
|
|
474
|
+
if (cyr > lat && cyr >= 2) return 'RU'
|
|
475
|
+
if (lat > cyr && lat >= 2) return 'EN'
|
|
476
|
+
return null
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ------------------------------------------------- пресеты промптов агента
|
|
480
|
+
|
|
481
|
+
export const SYSTEM_PROMPT_PRESETS = {
|
|
482
|
+
technical_expert: {
|
|
483
|
+
id: 'technical_expert',
|
|
484
|
+
label: 'Технический эксперт',
|
|
485
|
+
desc: 'Строгая русская инженерная терминология, чистый код и русские комментарии',
|
|
486
|
+
text: 'Отвечай пользователю на русском языке. Используй точную русскую инженерную терминологию, пиши чистый, идиоматичный код с русскими комментариями и следуй лучшим практикам разработки. Если пользователь пишет на другом языке, отвечай на его языке.'
|
|
487
|
+
},
|
|
488
|
+
tech_writer: {
|
|
489
|
+
id: 'tech_writer',
|
|
490
|
+
label: 'Технический писатель',
|
|
491
|
+
desc: 'Структурированные тексты, Markdown, ГОСТ/RFC и выверенная типографика',
|
|
492
|
+
text: 'Отвечай пользователю на русском языке. Оформляй документацию и пояснения в структурированном виде (Markdown, списки, таблицы), строго соблюдай правила русской типографики («кавычки-ёлочки», длинное тире, буква «ё») и требования к техническим текстам.'
|
|
493
|
+
},
|
|
494
|
+
concise: {
|
|
495
|
+
id: 'concise',
|
|
496
|
+
label: 'Лаконичный режим',
|
|
497
|
+
desc: 'Краткие ёмкие ответы без лишней вводной воды и повторов',
|
|
498
|
+
text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без лишних вводных слов, повторов и пространных рассуждений. Сразу переходи к сути, коду или прямому решению задачи.'
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// -------------------------------------------------- локализованный экспорт
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Генерирует локализованный Markdown-отчёт по сессии диалога.
|
|
506
|
+
*/
|
|
507
|
+
export const exportSessionToMarkdown = (session, options = {}) => {
|
|
508
|
+
if (!session) return ''
|
|
509
|
+
const title = session.title || session.name || 'Диалог DSH'
|
|
510
|
+
const date = session.createdAt ? new Date(session.createdAt) : new Date()
|
|
511
|
+
const dateStr = new Intl.DateTimeFormat('ru-RU', {
|
|
512
|
+
dateStyle: 'full',
|
|
513
|
+
timeStyle: 'medium'
|
|
514
|
+
}).format(date)
|
|
515
|
+
|
|
516
|
+
const messages = Array.isArray(session.messages) ? session.messages : []
|
|
517
|
+
let md = `# 💬 ${title}\n\n`
|
|
518
|
+
md += `> **Дата экспорта:** ${dateStr}\n`
|
|
519
|
+
if (session.model) md += `> **Модель:** \`${session.model}\`\n`
|
|
520
|
+
if (session.workspace) md += `> **Рабочая область:** \`${session.workspace}\`\n`
|
|
521
|
+
md += `> **Всего сообщений:** ${messages.length}\n\n---\n\n`
|
|
522
|
+
|
|
523
|
+
for (let idx = 0; idx < messages.length; idx++) {
|
|
524
|
+
const msg = messages[idx]
|
|
525
|
+
const role = msg.role || 'unknown'
|
|
526
|
+
const roleName = role === 'user' ? '👤 Пользователь' :
|
|
527
|
+
role === 'assistant' ? '🤖 Ассистент' :
|
|
528
|
+
role === 'system' ? '⚙️ Система' : `🔧 Инструмент (${role})`
|
|
529
|
+
|
|
530
|
+
md += `### ${roleName}\n\n`
|
|
531
|
+
if (msg.content) {
|
|
532
|
+
md += `${msg.content}\n\n`
|
|
533
|
+
}
|
|
534
|
+
if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) {
|
|
535
|
+
md += `*Вызовы инструментов:*\n`
|
|
536
|
+
for (const tc of msg.toolCalls) {
|
|
537
|
+
md += `- **${tc.name || 'tool'}**: \`${JSON.stringify(tc.arguments || {})}\`\n`
|
|
538
|
+
}
|
|
539
|
+
md += `\n`
|
|
540
|
+
}
|
|
541
|
+
md += `---\n\n`
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
md += `*Сгенерировано с помощью [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) и @goodandready/dsh-russian-lang*\n`
|
|
545
|
+
return md
|
|
546
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-russian-lang",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Russian localization for DeepSeek Harness (DSH v0.1.5-alpha.1+) web UI: adds Русский as the native language option in Settings - General - Language. 100.0% coverage across 5395 keys (1144 core UI keys + 4251 keys across 40+ ecosystem plugins) with zero machine drafts. Features Russian plural forms (one/few/many), automatic typography pass (guillemets, em dashes, non-breaking spaces, optional ё), wrong-layout input hint (Alt+L), browser spellcheck, and runtime snapshot extension without modifying core files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|