-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransforms.py
More file actions
211 lines (192 loc) · 11.2 KB
/
Copy pathtransforms.py
File metadata and controls
211 lines (192 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""Преобразования текста по одному нажатию.
Чем отличается от правки голосом (hotkey_command), которая уже есть. Там надо
зажать клавишу и СКАЗАТЬ, что сделать, - каждый раз заново. Здесь готовый
список: выделил, нажал, выбрал из списка. Разница как между «объяснить словами»
и «ткнуть пальцем», и для того, что делаешь по десять раз на дню, второе
быстрее в разы.
Работа целиком на llm_command() из cleaner.py: он уже умеет «взять текст, взять
указание, вернуть переписанное» и уже обвешан предохранителями - пустой ответ,
абсурдная длина, недоступная Ollama. Здесь только список указаний и разбор
своих. Ничего нового по механике не добавляется намеренно: у правки голосом
есть боевой опыт и тесты, и делить его с трансформами дешевле, чем заводить
второй путь с теми же граблями.
Свои преобразования лежат в конфиге списком:
"transforms": [{"id": "my1", "title": "По-деловому", "instruction": "..."}]
Указание уходит в модель как есть, без обработки: человек пишет его словами, а
не подбирает формулировку под наш формат.
"""
# Готовые преобразования. Порядок - по частоте, а не по алфавиту: первые три
# получают номера 1-3 и нажимаются не глядя.
#
# Тексты указаний на русском и подробные. Модель у нас маленькая (qwen2.5:3b),
# и короткое указание вроде «сократи» она понимает как угодно: то выбрасывает
# половину смысла, то дописывает от себя. Чем точнее сказано, что ТРОГАТЬ
# НЕЛЬЗЯ, тем предсказуемее результат.
PRESETS: list[dict] = [
{
"id": "prompt",
"title": "Задание для ИИ",
"hint": "Сумбурная мысль - во внятную задачу",
"instruction": (
"Перепиши это как чёткое задание для ИИ-помощника. Убери сумбур, "
"повторы и оговорки. Сохрани ВСЕ требования, числа и подробности, "
"которые есть в тексте, - ничего не выбрасывай. Не выполняй само "
"задание, только переформулируй его."
),
},
{
"id": "shorter",
"title": "Короче",
"hint": "То же самое, но вдвое короче",
"instruction": (
"Сократи примерно вдвое. Сохрани все факты, числа, имена и просьбы. "
"Убирай только повторы, вводные слова и воду. Ничего не добавляй."
),
},
{
"id": "fix",
"title": "Только ошибки",
"hint": "Опечатки и запятые, больше ничего",
"instruction": (
"Исправь орфографию, пунктуацию и согласование слов. Больше НЕ "
"МЕНЯЙ НИЧЕГО: ни слов, ни их порядка, ни стиля, ни длины. Если "
"ошибок нет - верни текст точно таким же."
),
},
{
"id": "formal",
"title": "Строже",
"hint": "Для письма начальнику или в банк",
"instruction": (
"Перепиши официально-деловым тоном: вежливо, без разговорных слов, "
"без сокращений и без восклицательных знаков. Смысл и все "
"подробности сохрани."
),
},
{
"id": "simpler",
"title": "Проще",
"hint": "Чтобы понял кто угодно",
"instruction": (
"Перепиши простыми словами и короткими предложениями, как "
"объясняют человеку не из этой области. Термины замени или "
"объясни. Смысл не теряй."
),
},
{
"id": "bullets",
"title": "Списком",
"hint": "Разложить по пунктам",
"instruction": (
"Разложи по пунктам. Каждый пункт с новой строки, начинается с "
"«- ». Ничего не выбрасывай и не добавляй - только перегруппируй "
"то, что уже сказано."
),
},
{
"id": "email",
"title": "Письмом",
"hint": "Приветствие, суть, подпись",
"instruction": (
"Оформи как письмо: приветствие, суть абзацами, вежливое "
"завершение. Тему письма не пиши. Подпись не выдумывай."
),
},
{
"id": "steps",
"title": "По шагам",
"hint": "Разложить задачу на понятные шаги",
"instruction": (
"Разложи задачу на пронумерованные шаги в том порядке, в котором "
"их надо делать. Каждый шаг - одно действие. Ничего не выдумывай "
"сверх сказанного и ничего не выбрасывай."
),
},
{
"id": "spec",
"title": "Техзадание",
"hint": "Что сделать, что не трогать, как проверить",
"instruction": (
"Перепиши как техническое задание тремя разделами: «Что сделать», "
"«Чего не делать», «Как проверить». Бери содержимое только из "
"сказанного; если для раздела нет данных - напиши «не оговорено», "
"но не выдумывай."
),
},
{
"id": "en",
"title": "На английский",
"hint": "Перевести, сохранив тон",
"instruction": (
"Переведи на английский. Сохрани тон и смысл. Ничего не добавляй "
"и не поясняй - верни только перевод."
),
},
]
MAX_CUSTOM = 12 # больше не влезет в список, который выбирают с клавиатуры
def _clean_custom(raw) -> list[dict]:
"""Свои преобразования из конфига. Битые записи молча пропускаем.
Конфиг человек правит руками, и одна кривая строка не должна лишать его
всего списка - тем более что список этот открывается по хоткею посреди
работы, а не в спокойной обстановке.
"""
out: list[dict] = []
if not isinstance(raw, list):
return out
for i, item in enumerate(raw):
if not isinstance(item, dict):
continue
title = str(item.get("title") or "").strip()
instruction = str(item.get("instruction") or "").strip()
if not title or not instruction:
continue
out.append({
"id": str(item.get("id") or f"custom{i}"),
"title": title[:40],
"hint": str(item.get("hint") or "").strip()[:60],
"instruction": instruction,
"custom": True,
})
if len(out) >= MAX_CUSTOM:
break
return out
def custom_of(cfg: dict) -> list[dict]:
"""Только свои преобразования, без готовых. Для окна настроек."""
return _clean_custom(cfg.get("transforms"))
def pack_custom(rows) -> list[dict]:
"""Строки из окна настроек -> записи конфига.
Идентификаторы раздаём сами и заново при каждом сохранении: человек правит
название прямо на месте, и держать за строкой прежний id значило бы решать,
та же это запись или новая. Ответ никому не нужен - на id тут никто не
ссылается, спрятанные лежат отдельным списком и только по готовым.
Подсказку не спрашиваем. У готовых она есть, потому что их писали мы; у
своих её заменяет само название. Просить человека сочинить вторую строку
про то же самое - лишнее поле в форме, а форму заполняют один раз.
"""
out: list[dict] = []
for i, r in enumerate(rows or []):
item = r if isinstance(r, dict) else {}
title = str(item.get("title") or "").strip()
instruction = str(item.get("instruction") or "").strip()
if not title or not instruction:
continue
out.append({"id": f"custom{i + 1}", "title": title[:40],
"hint": "", "instruction": instruction})
if len(out) >= MAX_CUSTOM:
break
return out
def all_for(cfg: dict) -> list[dict]:
"""Что показать в списке: готовые (кроме спрятанных) плюс свои.
Спрятать готовое можно, удалить - нельзя: список задан кодом, и удалять
из него нечего. Спрятанные лежат в конфиге по id, чтобы человек, который
никогда не переводит на английский, не видел этот пункт вечно.
"""
hidden = set(cfg.get("transforms_hidden") or [])
out = [dict(p) for p in PRESETS if p["id"] not in hidden]
out.extend(_clean_custom(cfg.get("transforms")))
return out
def find(cfg: dict, tid: str) -> dict | None:
for t in all_for(cfg):
if t["id"] == tid:
return t
return None