ommlds 0.0.0.dev514__py3-none-any.whl → 0.0.0.dev515__py3-none-any.whl

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.
@@ -335,7 +335,7 @@ class ChatApp(
335
335
 
336
336
  async def on_unmount(self) -> None:
337
337
  if (cat := self._chat_action_queue_task) is not None:
338
- await self._chat_event_queue.put(None)
338
+ await self._chat_action_queue.put(None)
339
339
  await cat
340
340
 
341
341
  await self._chat_driver.stop()
@@ -359,7 +359,7 @@ class ChatApp(
359
359
  ),
360
360
  )
361
361
 
362
- self._input_history_manager.add(event.text)
362
+ await self._input_history_manager.add(event.text)
363
363
 
364
364
  await self._chat_action_queue.put(ChatApp.UserInput(event.text))
365
365
 
@@ -371,12 +371,14 @@ class ChatApp(
371
371
 
372
372
  @tx.on(InputTextArea.HistoryPrevious)
373
373
  async def on_input_text_area_history_previous(self, event: InputTextArea.HistoryPrevious) -> None:
374
+ await self._input_history_manager.load_if_necessary()
374
375
  if (entry := self._input_history_manager.get_previous(event.text)) is not None:
375
376
  self._get_input_text_area().text = entry
376
377
  self._move_input_cursor_to_end()
377
378
 
378
379
  @tx.on(InputTextArea.HistoryNext)
379
380
  async def on_input_text_area_history_next(self, event: InputTextArea.HistoryNext) -> None:
381
+ await self._input_history_manager.load_if_necessary()
380
382
  if (entry := self._input_history_manager.get_next(event.text)) is not None:
381
383
  ita = self._get_input_text_area()
382
384
  ita.text = entry
@@ -1,9 +1,9 @@
1
1
  import abc
2
- import json
3
2
  import os
4
3
  import typing as ta
5
4
 
6
5
  from omlish import lang
6
+ from omlish.formats import json
7
7
 
8
8
 
9
9
  ##
@@ -11,11 +11,11 @@ from omlish import lang
11
11
 
12
12
  class InputHistoryStorage(lang.Abstract):
13
13
  @abc.abstractmethod
14
- def load(self) -> list[str]:
14
+ def load(self) -> ta.Awaitable[list[str]]:
15
15
  raise NotImplementedError
16
16
 
17
17
  @abc.abstractmethod
18
- def save(self, entries: ta.Sequence[str]) -> None:
18
+ def save(self, entries: ta.Sequence[str]) -> ta.Awaitable[None]:
19
19
  raise NotImplementedError
20
20
 
21
21
 
@@ -25,10 +25,10 @@ class InMemoryInputHistoryStorage(InputHistoryStorage):
25
25
 
26
26
  self._entries: list[str] = []
27
27
 
28
- def load(self) -> list[str]:
28
+ async def load(self) -> list[str]:
29
29
  return list(self._entries)
30
30
 
31
- def save(self, entries: ta.Sequence[str]) -> None:
31
+ async def save(self, entries: ta.Sequence[str]) -> None:
32
32
  self._entries = list(entries)
33
33
 
34
34
 
@@ -38,26 +38,31 @@ class FileInputHistoryStorage(InputHistoryStorage):
38
38
 
39
39
  self._path = path
40
40
 
41
- def load(self) -> list[str]:
41
+ async def load(self) -> list[str]:
42
42
  if not os.path.exists(self._path):
43
43
  return []
44
44
 
45
45
  try:
46
- with open(self._path) as f:
47
- data = json.load(f)
48
- if isinstance(data, list) and all(isinstance(e, str) for e in data):
49
- return data
50
- return []
51
- except (json.JSONDecodeError, OSError):
46
+ with open(self._path) as f: # noqa
47
+ content = f.read()
48
+ except OSError:
52
49
  return []
53
50
 
54
- def save(self, entries: ta.Sequence[str]) -> None:
51
+ data = json.loads(content)
52
+
53
+ if isinstance(data, list) and all(isinstance(e, str) for e in data):
54
+ return data
55
+ return []
56
+
57
+ async def save(self, entries: ta.Sequence[str]) -> None:
58
+ content = json.dumps_pretty(list(entries))
59
+ dir_path = os.path.dirname(self._path)
60
+
55
61
  try:
56
- dir_path = os.path.dirname(self._path)
57
62
  if dir_path:
58
63
  os.makedirs(dir_path, exist_ok=True)
59
- with open(self._path, 'w') as f:
60
- json.dump(list(entries), f, indent=2)
64
+ with open(self._path, 'w') as f: # noqa
65
+ f.write(content)
61
66
  except OSError:
62
67
  pass
63
68
 
@@ -88,16 +93,34 @@ class InputHistoryManager:
88
93
  self._storage = storage
89
94
  self._max_entries = max_entries
90
95
 
91
- self._entries: list[str] = self._storage.load()
92
- self._position: int = len(self._entries)
93
- self._current_draft: str = ''
96
+ #
97
+
98
+ _entries: list[str]
99
+ _position: int = 0
100
+
101
+ async def load_if_necessary(self) -> None:
102
+ try:
103
+ self._entries # noqa
104
+ except AttributeError:
105
+ pass
106
+ else:
107
+ return
108
+
109
+ self._entries = await self._storage.load()
110
+ self._position = len(self._entries)
94
111
 
95
- def add(self, text: str) -> None:
112
+ #
113
+
114
+ _current_draft: str = ''
115
+
116
+ async def add(self, text: str) -> None:
96
117
  """Add a new history entry and reset position."""
97
118
 
98
119
  if not text.strip():
99
120
  return
100
121
 
122
+ await self.load_if_necessary()
123
+
101
124
  # Don't add duplicate consecutive entries
102
125
  if self._entries and self._entries[-1] == text:
103
126
  self.reset_position()
@@ -109,7 +132,7 @@ class InputHistoryManager:
109
132
  if len(self._entries) > self._max_entries:
110
133
  self._entries = self._entries[-self._max_entries:]
111
134
 
112
- self._storage.save(self._entries)
135
+ await self._storage.save(self._entries)
113
136
  self.reset_position()
114
137
 
115
138
  def get_previous(self, text: str | None = None) -> str | None:
@@ -123,20 +146,24 @@ class InputHistoryManager:
123
146
  The previous history entry, or None if at the beginning
124
147
  """
125
148
 
126
- if not self._entries:
149
+ try:
150
+ entries = self._entries
151
+ except AttributeError:
152
+ return None
153
+ if entries:
127
154
  return None
128
155
 
129
156
  # Save current draft if we're at the end
130
- if self._position == len(self._entries) and text is not None:
157
+ if self._position == len(entries) and text is not None:
131
158
  self._current_draft = text
132
159
 
133
160
  # Move to previous entry
134
161
  if self._position > 0:
135
162
  self._position -= 1
136
- return self._entries[self._position]
163
+ return entries[self._position]
137
164
 
138
165
  # Already at oldest entry
139
- return self._entries[0] if self._entries else None
166
+ return entries[0] if entries else None
140
167
 
141
168
  def get_next(self, text: str | None = None) -> str | None:
142
169
  """
@@ -149,20 +176,24 @@ class InputHistoryManager:
149
176
  The next history entry, the saved draft if moving past the end, or None
150
177
  """
151
178
 
152
- if not self._entries:
179
+ try:
180
+ entries = self._entries
181
+ except AttributeError:
182
+ return None
183
+ if entries:
153
184
  return None
154
185
 
155
186
  # Move to next entry
156
- if self._position < len(self._entries):
187
+ if self._position < len(entries):
157
188
  self._position += 1
158
189
 
159
190
  # If we moved past the end, return the draft
160
- if self._position == len(self._entries):
191
+ if self._position == len(entries):
161
192
  draft = self._current_draft
162
193
  self._current_draft = ''
163
194
  return draft
164
195
 
165
- return self._entries[self._position]
196
+ return entries[self._position]
166
197
 
167
198
  # Already at newest position
168
199
  return None
@@ -170,5 +201,11 @@ class InputHistoryManager:
170
201
  def reset_position(self) -> None:
171
202
  """Reset history position to the end (no history item selected)."""
172
203
 
173
- self._position = len(self._entries)
204
+ try:
205
+ entries = self._entries
206
+ except AttributeError:
207
+ self._position = 0
208
+ else:
209
+ self._position = len(entries)
210
+
174
211
  self._current_draft = ''
@@ -10,7 +10,7 @@ import typing as ta
10
10
  from omlish import check
11
11
  from omlish import lang
12
12
  from omlish import typedvalues as tv
13
- from omlish.asyncs.asyncio.sync import AsyncioBufferRelay
13
+ from omlish.asyncs.asyncio.sync import AsyncioSyncBufferRelay
14
14
 
15
15
  from ....chat.choices.services import ChatChoicesRequest
16
16
  from ....chat.choices.services import ChatChoicesResponse
@@ -242,7 +242,7 @@ class TransformersChatChoicesStreamService(BaseTransformersChatChoicesService):
242
242
  for m in request.v
243
243
  ]
244
244
 
245
- relay: AsyncioBufferRelay = AsyncioBufferRelay()
245
+ relay: AsyncioSyncBufferRelay = AsyncioSyncBufferRelay()
246
246
 
247
247
  def streamer_callback(text: str, *, stream_end: bool) -> None:
248
248
  if text or stream_end:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommlds
3
- Version: 0.0.0.dev514
3
+ Version: 0.0.0.dev515
4
4
  Summary: ommlds
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -14,9 +14,9 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Python: >=3.13
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: omlish==0.0.0.dev514
17
+ Requires-Dist: omlish==0.0.0.dev515
18
18
  Provides-Extra: all
19
- Requires-Dist: omdev==0.0.0.dev514; extra == "all"
19
+ Requires-Dist: omdev==0.0.0.dev515; extra == "all"
20
20
  Requires-Dist: llama-cpp-python~=0.3; extra == "all"
21
21
  Requires-Dist: mlx~=0.30; sys_platform == "darwin" and extra == "all"
22
22
  Requires-Dist: mlx-lm~=0.29; sys_platform == "darwin" and extra == "all"
@@ -39,7 +39,7 @@ Requires-Dist: mwparserfromhell~=0.7; extra == "all"
39
39
  Requires-Dist: wikitextparser~=0.56; extra == "all"
40
40
  Requires-Dist: lxml>=5.3; python_version < "3.13" and extra == "all"
41
41
  Provides-Extra: omdev
42
- Requires-Dist: omdev==0.0.0.dev514; extra == "omdev"
42
+ Requires-Dist: omdev==0.0.0.dev515; extra == "omdev"
43
43
  Provides-Extra: backends
44
44
  Requires-Dist: llama-cpp-python~=0.3; extra == "backends"
45
45
  Requires-Dist: mlx~=0.30; sys_platform == "darwin" and extra == "backends"
@@ -214,11 +214,11 @@ ommlds/cli/sessions/chat/interfaces/bare/interactive.py,sha256=ZnYoePvXtUbhkDQ0j
214
214
  ommlds/cli/sessions/chat/interfaces/bare/oneshot.py,sha256=b758OIa0gf9I_0UdxYJ6re-g8-8xndgr3R0OotUOsmc,387
215
215
  ommlds/cli/sessions/chat/interfaces/bare/tools.py,sha256=_UsuoXLIvfpFP_We5DBBlhm6rwB3_cFA3lmFvpG9b-A,824
216
216
  ommlds/cli/sessions/chat/interfaces/textual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
217
- ommlds/cli/sessions/chat/interfaces/textual/app.py,sha256=hgEQ9QEAxYVR-d7z4FJsXycbkHKcLzzAWQ6zR0jy_Zs,13301
217
+ ommlds/cli/sessions/chat/interfaces/textual/app.py,sha256=QVLD4ZT4-SJ4UQhKSfm6MAjOtqeLtYhW3tMMXxg7h88,13432
218
218
  ommlds/cli/sessions/chat/interfaces/textual/configs.py,sha256=-pvG2_Uai70ohDfK4Tt8yaHnvdCs10_gaoQkr-CsOqA,213
219
219
  ommlds/cli/sessions/chat/interfaces/textual/facades.py,sha256=zXVG7DKVl-Xtdc893O_yktHCMvM0do6hLesMd8hbqeo,411
220
220
  ommlds/cli/sessions/chat/interfaces/textual/inject.py,sha256=eBhFVZ2VmQdoTPSZvi2OSkZ-fX8Mw2TKo28bHZeACJY,3056
221
- ommlds/cli/sessions/chat/interfaces/textual/inputhistory.py,sha256=Jdmwd6RTBLK8rGyz4w560cwuK6LXi_OTubVfw_-ORTM,4687
221
+ ommlds/cli/sessions/chat/interfaces/textual/inputhistory.py,sha256=hUGrIpSmI0wlg0jwqdT42hLkrzeQF4wnN6wdom9HDJU,5332
222
222
  ommlds/cli/sessions/chat/interfaces/textual/interface.py,sha256=lHeuiMtA7DW9knuapZEOZSl9-9SmOfUxiPnd4-plLHE,445
223
223
  ommlds/cli/sessions/chat/interfaces/textual/tools.py,sha256=KVlUmIyzqUuOHMzB9ZXGUaGsb-Tp5LAmMpB1agAHYjo,985
224
224
  ommlds/cli/sessions/chat/interfaces/textual/styles/__init__.py,sha256=7_U5oUjwegOymeWgt6nLpFfWfjGTrlWL8m4Au8MsaFE,542
@@ -320,7 +320,7 @@ ommlds/minichain/backends/impls/tokenizers/tokens.py,sha256=wLz9UTWhHrrJm56ZSLZD
320
320
  ommlds/minichain/backends/impls/transformers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
321
321
  ommlds/minichain/backends/impls/transformers/sentence.py,sha256=0T01aY0rVauw--AdchroNkcwaK3ku0ZdP8ikcsYeHyA,1462
322
322
  ommlds/minichain/backends/impls/transformers/tokens.py,sha256=ozlTX0c3sixgcgz87OwEBoVxTF69MTz46LbHzuS8r2Y,2166
323
- ommlds/minichain/backends/impls/transformers/transformers.py,sha256=hXWNe2knROuTJoI737t5FIqUdhmldpHW89tBkfpyofM,9052
323
+ ommlds/minichain/backends/impls/transformers/transformers.py,sha256=6eQa95I4PEcUap3bhpRujEDRsVG3MX768bZk4944fPg,9064
324
324
  ommlds/minichain/backends/strings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
325
325
  ommlds/minichain/backends/strings/manifests.py,sha256=kmlanVUAZqIh0P95Mm8H20e8ib3gEgYHHUlkCXDQGFk,413
326
326
  ommlds/minichain/backends/strings/parsing.py,sha256=FbijHuHiIkOwC3jecUD-IXs7y12PS0ByCQeCfjJKuE8,1781
@@ -526,9 +526,9 @@ ommlds/wiki/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
526
526
  ommlds/wiki/utils/io.py,sha256=UKgDJGtmpnWvIqVd2mJc2QNPOqlToEY1GEveNp6_pMo,7088
527
527
  ommlds/wiki/utils/progress.py,sha256=EhvKcMFYtsarCQhIahlO6f0SboyAKP3UwUyrnVnP-Vk,3222
528
528
  ommlds/wiki/utils/xml.py,sha256=sNJNkZ9rT8B-kJMO6bRz8J1USy4fyPx0m2PwTX7vxYY,3846
529
- ommlds-0.0.0.dev514.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
530
- ommlds-0.0.0.dev514.dist-info/METADATA,sha256=q4ShoZYDFsg8DlxflkSxvX0wF1O_W0ENF79Ne0-WEJQ,3602
531
- ommlds-0.0.0.dev514.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
532
- ommlds-0.0.0.dev514.dist-info/entry_points.txt,sha256=Z5YWtX7ClfiCKdW-dd_CSVvM0h4yQpJPi-2G3q6gNFo,35
533
- ommlds-0.0.0.dev514.dist-info/top_level.txt,sha256=Rbnk5d5wi58vnAXx13WFZqdQ4VX8hBCS2hEL3WeXOhY,7
534
- ommlds-0.0.0.dev514.dist-info/RECORD,,
529
+ ommlds-0.0.0.dev515.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
530
+ ommlds-0.0.0.dev515.dist-info/METADATA,sha256=Ymblue2sqaQ1fupD_pdxYWr0xwhiL2JytUMrqYk_XFQ,3602
531
+ ommlds-0.0.0.dev515.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
532
+ ommlds-0.0.0.dev515.dist-info/entry_points.txt,sha256=Z5YWtX7ClfiCKdW-dd_CSVvM0h4yQpJPi-2G3q6gNFo,35
533
+ ommlds-0.0.0.dev515.dist-info/top_level.txt,sha256=Rbnk5d5wi58vnAXx13WFZqdQ4VX8hBCS2hEL3WeXOhY,7
534
+ ommlds-0.0.0.dev515.dist-info/RECORD,,