ommlds 0.0.0.dev491__py3-none-any.whl → 0.0.0.dev493__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.
Files changed (55) hide show
  1. ommlds/.omlish-manifests.json +9 -7
  2. ommlds/__about__.py +1 -1
  3. ommlds/cli/_dataclasses.py +617 -724
  4. ommlds/cli/backends/catalog.py +0 -5
  5. ommlds/cli/backends/inject.py +2 -0
  6. ommlds/cli/inject.py +11 -3
  7. ommlds/cli/main.py +35 -25
  8. ommlds/cli/sessions/base.py +1 -10
  9. ommlds/cli/sessions/chat/configs.py +6 -8
  10. ommlds/cli/sessions/chat/drivers/driver.py +8 -0
  11. ommlds/cli/sessions/chat/drivers/inject.py +2 -3
  12. ommlds/cli/sessions/chat/drivers/state/configs.py +2 -0
  13. ommlds/cli/sessions/chat/drivers/state/ids.py +25 -0
  14. ommlds/cli/sessions/chat/drivers/state/inject.py +54 -6
  15. ommlds/cli/sessions/chat/drivers/state/inmemory.py +0 -4
  16. ommlds/cli/sessions/chat/drivers/state/storage.py +17 -10
  17. ommlds/cli/sessions/chat/drivers/state/types.py +9 -4
  18. ommlds/cli/sessions/chat/drivers/user/inject.py +4 -4
  19. ommlds/cli/sessions/chat/facades/__init__.py +0 -0
  20. ommlds/cli/sessions/chat/facades/configs.py +9 -0
  21. ommlds/cli/sessions/chat/facades/facade.py +19 -0
  22. ommlds/cli/sessions/chat/facades/inject.py +23 -0
  23. ommlds/cli/sessions/chat/inject.py +5 -3
  24. ommlds/cli/sessions/chat/interfaces/bare/configs.py +15 -0
  25. ommlds/cli/sessions/chat/interfaces/bare/inject.py +2 -2
  26. ommlds/cli/sessions/chat/interfaces/bare/interactive.py +10 -2
  27. ommlds/cli/sessions/chat/interfaces/configs.py +2 -14
  28. ommlds/cli/sessions/chat/interfaces/inject.py +9 -4
  29. ommlds/cli/sessions/chat/interfaces/textual/app.py +113 -45
  30. ommlds/cli/sessions/chat/interfaces/textual/configs.py +11 -0
  31. ommlds/cli/sessions/chat/interfaces/textual/inject.py +39 -15
  32. ommlds/cli/sessions/chat/interfaces/textual/interface.py +5 -0
  33. ommlds/cli/sessions/chat/interfaces/textual/styles/input.tcss +3 -1
  34. ommlds/cli/sessions/chat/interfaces/textual/styles/messages.tcss +76 -22
  35. ommlds/cli/sessions/chat/interfaces/textual/tools.py +38 -0
  36. ommlds/cli/sessions/chat/interfaces/textual/widgets/messages.py +60 -2
  37. ommlds/cli/sessions/chat/session.py +2 -15
  38. ommlds/cli/sessions/completion/configs.py +3 -4
  39. ommlds/cli/sessions/completion/inject.py +1 -2
  40. ommlds/cli/sessions/completion/session.py +4 -8
  41. ommlds/cli/sessions/configs.py +10 -0
  42. ommlds/cli/sessions/embedding/configs.py +3 -4
  43. ommlds/cli/sessions/embedding/inject.py +1 -2
  44. ommlds/cli/sessions/embedding/session.py +4 -8
  45. ommlds/cli/sessions/inject.py +15 -15
  46. ommlds/cli/state/storage.py +7 -1
  47. ommlds/minichain/backends/impls/cerebras/protocol.py +4 -4
  48. ommlds/minichain/backends/impls/ollama/chat.py +26 -0
  49. ommlds/minichain/backends/impls/openai/names.py +3 -1
  50. {ommlds-0.0.0.dev491.dist-info → ommlds-0.0.0.dev493.dist-info}/METADATA +6 -6
  51. {ommlds-0.0.0.dev491.dist-info → ommlds-0.0.0.dev493.dist-info}/RECORD +55 -46
  52. {ommlds-0.0.0.dev491.dist-info → ommlds-0.0.0.dev493.dist-info}/WHEEL +0 -0
  53. {ommlds-0.0.0.dev491.dist-info → ommlds-0.0.0.dev493.dist-info}/entry_points.txt +0 -0
  54. {ommlds-0.0.0.dev491.dist-info → ommlds-0.0.0.dev493.dist-info}/licenses/LICENSE +0 -0
  55. {ommlds-0.0.0.dev491.dist-info → ommlds-0.0.0.dev493.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,15 @@
1
+ import typing as ta
2
+
3
+ from omlish import dataclasses as dc
4
+
5
+ from ..configs import InterfaceConfig
6
+
7
+
8
+ ##
9
+
10
+
11
+ @dc.dataclass(frozen=True, kw_only=True)
12
+ class BareInterfaceConfig(InterfaceConfig):
13
+ interactive: bool = False
14
+
15
+ use_readline: bool | ta.Literal['auto'] = 'auto'
@@ -2,7 +2,7 @@ from omlish import inject as inj
2
2
  from omlish import lang
3
3
 
4
4
  from ..base import ChatInterface
5
- from ..configs import InterfaceConfig
5
+ from .configs import BareInterfaceConfig
6
6
 
7
7
 
8
8
  with lang.auto_proxy_import(globals()):
@@ -17,7 +17,7 @@ with lang.auto_proxy_import(globals()):
17
17
  ##
18
18
 
19
19
 
20
- def bind_bare(cfg: InterfaceConfig = InterfaceConfig()) -> inj.Elements:
20
+ def bind_bare(cfg: BareInterfaceConfig = BareInterfaceConfig()) -> inj.Elements:
21
21
  els: list[inj.Elemental] = []
22
22
 
23
23
  #
@@ -1,10 +1,10 @@
1
1
  import typing as ta
2
2
 
3
- from ...... import minichain as mc
4
3
  from .....inputs.asyncs import AsyncStringInput
5
4
  from .....inputs.asyncs import SyncAsyncStringInput
6
5
  from .....inputs.sync import InputSyncStringInput
7
6
  from ...drivers.driver import ChatDriver
7
+ from ...facades.facade import ChatFacade
8
8
  from ..base import ChatInterface
9
9
 
10
10
 
@@ -18,11 +18,13 @@ class InteractiveBareChatInterface(ChatInterface):
18
18
  self,
19
19
  *,
20
20
  driver: ChatDriver,
21
+ facade: ChatFacade,
21
22
  string_input: AsyncStringInput | None = None,
22
23
  ) -> None:
23
24
  super().__init__()
24
25
 
25
26
  self._driver = driver
27
+ self._facade = facade
26
28
  if string_input is None:
27
29
  string_input = self.DEFAULT_STRING_INPUT
28
30
  self._string_input = string_input
@@ -36,6 +38,12 @@ class InteractiveBareChatInterface(ChatInterface):
36
38
  except EOFError:
37
39
  break
38
40
 
39
- await self._driver.send_user_messages([mc.UserMessage(s)])
41
+ print()
42
+ print('<')
43
+ print()
44
+
45
+ await self._facade.handle_user_input(s)
46
+
47
+ print()
40
48
 
41
49
  await self._driver.stop()
@@ -1,23 +1,11 @@
1
- """
2
- TODO:
3
- - obviously, subclasses of InterfaceConfig
4
- - this is really just another instance of the whole `argparse -> config -> inject` flow
5
- """
6
- import typing as ta
7
-
8
1
  from omlish import dataclasses as dc
2
+ from omlish import lang
9
3
 
10
4
 
11
5
  ##
12
6
 
13
7
 
14
8
  @dc.dataclass(frozen=True, kw_only=True)
15
- class InterfaceConfig:
16
- interactive: bool = False
17
-
18
- use_textual: bool = False
19
-
20
- use_readline: bool | ta.Literal['auto'] = 'auto'
21
-
9
+ class InterfaceConfig(lang.Abstract):
22
10
  enable_tools: bool = False
23
11
  dangerous_no_tool_confirmation: bool = False
@@ -1,7 +1,9 @@
1
1
  from omlish import inject as inj
2
2
  from omlish import lang
3
3
 
4
+ from .bare.configs import BareInterfaceConfig
4
5
  from .configs import InterfaceConfig
6
+ from .textual.configs import TextualInterfaceConfig
5
7
 
6
8
 
7
9
  with lang.auto_proxy_import(globals()):
@@ -12,13 +14,16 @@ with lang.auto_proxy_import(globals()):
12
14
  ##
13
15
 
14
16
 
15
- def bind_interface(cfg: InterfaceConfig = InterfaceConfig()) -> inj.Elements:
17
+ def bind_interface(cfg: InterfaceConfig = BareInterfaceConfig()) -> inj.Elements:
16
18
  els: list[inj.Elemental] = []
17
19
 
18
- if cfg.use_textual:
19
- els.append(_textual.bind_textual())
20
+ if isinstance(cfg, TextualInterfaceConfig):
21
+ els.append(_textual.bind_textual(cfg))
20
22
 
21
- else:
23
+ elif isinstance(cfg, BareInterfaceConfig):
22
24
  els.append(_bare.bind_bare(cfg))
23
25
 
26
+ else:
27
+ raise TypeError(cfg)
28
+
24
29
  return inj.as_elements(*els)
@@ -1,45 +1,69 @@
1
1
  import asyncio
2
+ import os
2
3
  import typing as ta
3
4
 
4
5
  from omdev.tui import textual as tx
5
6
  from omlish import check
7
+ from omlish import dataclasses as dc
8
+ from omlish import lang
9
+ from omlish.logs import all as logs
6
10
 
7
11
  from ...... import minichain as mc
12
+ from .....backends.types import BackendName
8
13
  from ...drivers.driver import ChatDriver
9
14
  from ...drivers.events.types import AiDeltaChatEvent
10
15
  from ...drivers.events.types import AiMessagesChatEvent
16
+ from ...facades.facade import ChatFacade
11
17
  from .styles import read_app_css
12
18
  from .widgets.input import InputOuter
13
19
  from .widgets.input import InputTextArea
14
20
  from .widgets.messages import AiMessage
15
21
  from .widgets.messages import StaticAiMessage
16
22
  from .widgets.messages import StreamAiMessage
23
+ from .widgets.messages import ToolConfirmationMessage
17
24
  from .widgets.messages import UserMessage
18
25
  from .widgets.messages import WelcomeMessage
19
26
 
20
27
 
28
+ log, alog = logs.get_module_loggers(globals())
29
+
30
+
21
31
  ##
22
32
 
23
33
 
24
- ChatDriverEventQueue = ta.NewType('ChatDriverEventQueue', asyncio.Queue)
34
+ ChatEventQueue = ta.NewType('ChatEventQueue', asyncio.Queue)
25
35
 
26
36
 
27
37
  ##
28
38
 
29
39
 
40
+ class ChatAppGetter(lang.CachedFunc0[ta.Awaitable['ChatApp']]):
41
+ pass
42
+
43
+
30
44
  class ChatApp(tx.App):
31
45
  ENABLE_COMMAND_PALETTE: ta.ClassVar[bool] = False
32
46
 
33
47
  def __init__(
34
48
  self,
35
49
  *,
36
- driver: ChatDriver,
37
- event_queue: ChatDriverEventQueue,
50
+ chat_facade: ChatFacade,
51
+ chat_driver: ChatDriver,
52
+ chat_event_queue: ChatEventQueue,
53
+ backend_name: BackendName | None = None,
54
+ devtools_setup: tx.DevtoolsSetup | None = None,
38
55
  ) -> None:
39
56
  super().__init__()
40
57
 
41
- self._chat_driver = driver
42
- self._event_queue = event_queue
58
+ if devtools_setup is not None:
59
+ devtools_setup(self)
60
+
61
+ self._chat_facade = chat_facade
62
+ self._chat_driver = chat_driver
63
+ self._chat_event_queue = chat_event_queue
64
+ self._backend_name = backend_name
65
+
66
+ self._chat_action_queue: asyncio.Queue[ta.Any] = asyncio.Queue()
43
67
 
44
68
  def get_driver_class(self) -> type[tx.Driver]:
45
69
  return tx.get_pending_writes_driver_class(super().get_driver_class())
@@ -49,8 +73,7 @@ class ChatApp(tx.App):
49
73
  #
50
74
 
51
75
  def compose(self) -> tx.ComposeResult:
52
- with tx.VerticalScroll(id='messages-scroll'):
53
- yield tx.Static(id='messages-container')
76
+ yield tx.VerticalScroll(id='messages-container')
54
77
 
55
78
  yield InputOuter(id='input-outer')
56
79
 
@@ -59,22 +82,19 @@ class ChatApp(tx.App):
59
82
  def _get_input_text_area(self) -> InputTextArea:
60
83
  return self.query_one('#input', InputTextArea)
61
84
 
62
- def _get_messages_scroll(self) -> tx.VerticalScroll:
63
- return self.query_one('#messages-scroll', tx.VerticalScroll)
64
-
65
- def _get_messages_container(self) -> tx.Static:
66
- return self.query_one('#messages-container', tx.Static)
85
+ def _get_messages_container(self) -> tx.VerticalScroll:
86
+ return self.query_one('#messages-container', tx.VerticalScroll)
67
87
 
68
88
  #
69
89
 
70
90
  def _is_messages_at_bottom(self, threshold: int = 3) -> bool:
71
- return (ms := self._get_messages_scroll()).scroll_y >= (ms.max_scroll_y - threshold)
91
+ return (ms := self._get_messages_container()).scroll_y >= (ms.max_scroll_y - threshold)
72
92
 
73
93
  def _scroll_messages_to_bottom(self) -> None:
74
- self._get_messages_scroll().scroll_end(animate=False)
94
+ self._get_messages_container().scroll_end(animate=False)
75
95
 
76
96
  def _anchor_messages(self) -> None:
77
- if (ms := self._get_messages_scroll()).max_scroll_y:
97
+ if (ms := self._get_messages_container()).max_scroll_y:
78
98
  ms.anchor()
79
99
 
80
100
  #
@@ -102,7 +122,7 @@ class ChatApp(tx.App):
102
122
 
103
123
  await sam.append_content(content)
104
124
 
105
- self.call_after_refresh(lambda: self._get_messages_container().scroll_end(animate=False))
125
+ self.call_after_refresh(self._scroll_messages_to_bottom)
106
126
 
107
127
  if was_at_bottom:
108
128
  self.call_after_refresh(self._anchor_messages)
@@ -116,7 +136,7 @@ class ChatApp(tx.App):
116
136
  msg_ctr = self._get_messages_container()
117
137
 
118
138
  for msg in [*(self._pending_mount_messages or []), *messages]:
119
- if isinstance(msg, AiMessage):
139
+ if isinstance(msg, (AiMessage, ToolConfirmationMessage)):
120
140
  await self._finalize_stream_ai_message()
121
141
 
122
142
  await msg_ctr.mount(msg)
@@ -127,21 +147,24 @@ class ChatApp(tx.App):
127
147
 
128
148
  self._pending_mount_messages = None
129
149
 
130
- self.call_after_refresh(lambda: msg_ctr.scroll_end(animate=False))
150
+ self.call_after_refresh(self._scroll_messages_to_bottom)
131
151
 
132
152
  if was_at_bottom:
133
153
  self.call_after_refresh(self._anchor_messages)
134
154
 
135
155
  #
136
156
 
137
- _event_queue_task: asyncio.Task[None] | None = None
157
+ _chat_event_task: asyncio.Task[None] | None = None
138
158
 
139
- async def _event_queue_task_main(self) -> None:
159
+ @logs.async_exception_logging(alog)
160
+ async def _chat_event_task_main(self) -> None:
140
161
  while True:
141
- ev = await self._event_queue.get()
162
+ ev = await self._chat_event_queue.get()
142
163
  if ev is None:
143
164
  break
144
165
 
166
+ await alog.debug(lambda: f'Got chat event: {ev!r}')
167
+
145
168
  if isinstance(ev, AiMessagesChatEvent):
146
169
  wx: list[tx.Widget] = []
147
170
 
@@ -159,50 +182,71 @@ class ChatApp(tx.App):
159
182
  self.call_later(self._mount_messages)
160
183
 
161
184
  elif isinstance(ev, AiDeltaChatEvent):
162
- cd = check.isinstance(ev.delta, mc.ContentAiDelta)
163
- cc = check.isinstance(cd.c, str)
164
- self.call_later(self. _append_stream_ai_message_content, cc)
185
+ if isinstance(ev.delta, mc.ContentAiDelta):
186
+ cc = check.isinstance(ev.delta.c, str)
187
+ self.call_later(self._append_stream_ai_message_content, cc)
188
+
189
+ elif isinstance(ev.delta, mc.ToolUseAiDelta):
190
+ pass
165
191
 
166
192
  #
167
193
 
168
- # def _schedule_after_refresh(self) -> None:
169
- # self.call_after_refresh(self._after_refresh)
194
+ @dc.dataclass(frozen=True)
195
+ class UserInput:
196
+ text: str
170
197
 
171
- # def _after_refresh(self) -> None:
172
- # self.after_repaint()
173
- #
174
- # self._schedule_after_refresh()
198
+ _chat_action_task: asyncio.Task[None] | None = None
175
199
 
176
- # def after_repaint(self) -> None:
177
- # # from omdev.tui.textual.debug.dominfo import inspect_dom_node # noqa
178
- #
179
- # pass
200
+ @logs.async_exception_logging(alog)
201
+ async def _chat_action_task_main(self) -> None:
202
+ while True:
203
+ ac = await self._chat_action_queue.get()
204
+ if ac is None:
205
+ break
206
+
207
+ await alog.debug(lambda: f'Got chat action: {ac!r}')
208
+
209
+ if isinstance(ac, ChatApp.UserInput):
210
+ try:
211
+ await self._chat_facade.handle_user_input(ac.text)
212
+ except Exception as e: # noqa
213
+ raise
214
+
215
+ else:
216
+ raise TypeError(ac) # noqa
180
217
 
181
218
  #
182
219
 
183
220
  async def on_mount(self) -> None:
184
- # self._schedule_after_refresh()
185
-
186
- check.state(self._event_queue_task is None)
187
- self._event_queue_task = asyncio.create_task(self._event_queue_task_main())
221
+ check.state(self._chat_event_task is None)
222
+ self._chat_event_task = asyncio.create_task(self._chat_event_task_main())
188
223
 
189
224
  await self._chat_driver.start()
190
225
 
226
+ check.state(self._chat_action_task is None)
227
+ self._chat_action_task = asyncio.create_task(self._chat_action_task_main())
228
+
191
229
  self._get_input_text_area().focus()
192
230
 
193
231
  await self._mount_messages(
194
- WelcomeMessage(
195
- 'Hello!',
196
- ),
232
+ WelcomeMessage('\n'.join([
233
+ f'Backend: {self._backend_name or "?"}',
234
+ f'Dir: {os.getcwd()}',
235
+ ])),
197
236
  )
198
237
 
199
238
  async def on_unmount(self) -> None:
239
+ if (cdt := self._chat_event_task) is not None:
240
+ await self._chat_event_queue.put(None)
241
+ await cdt
242
+
200
243
  await self._chat_driver.stop()
201
244
 
202
- if (eqt := self._event_queue_task) is not None:
203
- await self._event_queue.put(None)
204
- await eqt
245
+ if (cet := self._chat_event_task) is not None:
246
+ await self._chat_event_queue.put(None)
247
+ await cet
205
248
 
249
+ @tx.on(InputTextArea.Submitted)
206
250
  async def on_input_text_area_submitted(self, event: InputTextArea.Submitted) -> None:
207
251
  self._get_input_text_area().clear()
208
252
 
@@ -214,4 +258,28 @@ class ChatApp(tx.App):
214
258
  ),
215
259
  )
216
260
 
217
- await self._chat_driver.send_user_messages([mc.UserMessage(event.text)])
261
+ await self._chat_action_queue.put(ChatApp.UserInput(event.text))
262
+
263
+ #
264
+
265
+ async def confirm_tool_use(
266
+ self,
267
+ outer_message: str,
268
+ inner_message: str,
269
+ ) -> bool:
270
+ fut: asyncio.Future[bool] = asyncio.get_running_loop().create_future()
271
+
272
+ tcm = ToolConfirmationMessage(
273
+ outer_message,
274
+ inner_message,
275
+ fut,
276
+ )
277
+
278
+ async def inner() -> None:
279
+ await self._mount_messages(tcm)
280
+
281
+ self.call_later(inner)
282
+
283
+ ret = await fut
284
+
285
+ return ret
@@ -0,0 +1,11 @@
1
+ from omlish import dataclasses as dc
2
+
3
+ from ..configs import InterfaceConfig
4
+
5
+
6
+ ##
7
+
8
+
9
+ @dc.dataclass(frozen=True, kw_only=True)
10
+ class TextualInterfaceConfig(InterfaceConfig):
11
+ pass
@@ -3,44 +3,48 @@ FIXME:
3
3
  - too lazy to lazy import guts like every other proper inject module lol >_<
4
4
  """
5
5
  import asyncio
6
+ import contextlib
6
7
 
7
8
  from omlish import inject as inj
8
9
  from omlish import lang
9
10
 
10
11
  from ...drivers.events.injection import event_callbacks
11
12
  from ..base import ChatInterface
12
- from ..configs import InterfaceConfig
13
- from .app import ChatApp
14
- from .app import ChatDriverEventQueue
15
- from .interface import TextualChatInterface
13
+ from .configs import TextualInterfaceConfig
16
14
 
17
15
 
18
16
  with lang.auto_proxy_import(globals()):
17
+ from omdev.tui import textual as tx
18
+
19
19
  from ...drivers.tools import confirmation as _tools_confirmation
20
+ from . import app as _app
21
+ from . import interface as _interface
22
+ from . import tools as _tools
20
23
 
21
24
 
22
25
  ##
23
26
 
24
27
 
25
- def bind_textual(cfg: InterfaceConfig = InterfaceConfig()) -> inj.Elements:
28
+ def bind_textual(cfg: TextualInterfaceConfig = TextualInterfaceConfig()) -> inj.Elements:
26
29
  els: list[inj.Elemental] = [
27
- inj.bind(ChatInterface, to_ctor=TextualChatInterface, singleton=True),
30
+ inj.bind(ChatInterface, to_ctor=_interface.TextualChatInterface, singleton=True),
28
31
  ]
29
32
 
30
33
  #
31
34
 
32
35
  els.extend([
33
- inj.bind(ChatApp, singleton=True),
36
+ inj.bind(_app.ChatApp, singleton=True),
37
+ inj.bind_async_late(_app.ChatApp, _app.ChatAppGetter),
34
38
  ])
35
39
 
36
40
  #
37
41
 
38
42
  els.extend([
39
- inj.bind(ChatDriverEventQueue, to_const=asyncio.Queue()),
43
+ inj.bind(_app.ChatEventQueue, to_const=asyncio.Queue()),
40
44
 
41
45
  event_callbacks().bind_item(to_fn=inj.KwargsTarget.of(
42
46
  lambda eq: lambda ev: eq.put(ev),
43
- eq=ChatDriverEventQueue,
47
+ eq=_app.ChatEventQueue,
44
48
  )),
45
49
  ])
46
50
 
@@ -55,12 +59,32 @@ def bind_textual(cfg: InterfaceConfig = InterfaceConfig()) -> inj.Elements:
55
59
  ))
56
60
 
57
61
  else:
58
- # els.append(inj.bind(
59
- # _tools_confirmation.ToolExecutionConfirmation,
60
- # to_ctor=_tools.InteractiveToolExecutionConfirmation,
61
- # singleton=True,
62
- # ))
63
- raise NotImplementedError
62
+ els.append(inj.bind(
63
+ _tools_confirmation.ToolExecutionConfirmation,
64
+ to_ctor=_tools.ChatAppToolExecutionConfirmation,
65
+ singleton=True,
66
+ ))
67
+
68
+ #
69
+
70
+ els.extend([
71
+ inj.bind(tx.DevtoolsConfig(port=41932)), # FIXME: lol
72
+
73
+ inj.bind(
74
+ tx.DevtoolsManager,
75
+ singleton=True,
76
+ to_async_fn=inj.make_async_managed_provider(
77
+ tx.DevtoolsManager,
78
+ contextlib.aclosing,
79
+ ),
80
+ ),
81
+
82
+ inj.bind(
83
+ tx.DevtoolsSetup,
84
+ to_async_fn=inj.KwargsTarget.of(lambda mgr: mgr.get_setup(), mgr=tx.DevtoolsManager),
85
+ singleton=True,
86
+ ),
87
+ ])
64
88
 
65
89
  #
66
90
 
@@ -1,3 +1,5 @@
1
+ from omdev.tui import textual as tx
2
+
1
3
  from ..base import ChatInterface
2
4
  from .app import ChatApp
3
5
 
@@ -16,4 +18,7 @@ class TextualChatInterface(ChatInterface):
16
18
  self._app = app
17
19
 
18
20
  async def run(self) -> None:
21
+ # FIXME: move lol
22
+ tx.set_root_logger_to_devtools(self._app.devtools)
23
+
19
24
  await self._app.run_async()
@@ -1,13 +1,15 @@
1
1
  #input-outer {
2
2
  width: 100%;
3
3
  height: auto;
4
+
5
+ background: $background-darken-3;
4
6
  }
5
7
 
6
8
  #input-vertical {
7
9
  width: 100%;
8
10
  height: auto;
9
11
 
10
- margin: 0 2 1 2;
12
+ margin: 0 1 1 1;
11
13
 
12
14
  padding: 0;
13
15
  }