Skip to content

Commit 71d5b94

Browse files
committed
switch to textarea in console
1 parent 882a251 commit 71d5b94

File tree

1 file changed

+94
-100
lines changed

1 file changed

+94
-100
lines changed

oneping/interface/textual.py

Lines changed: 94 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
import os
55
import datetime
66

7-
from textual import on, work
8-
from textual.app import App, ComposeResult
7+
from textual import work, Logger
8+
from textual.app import App
99
from textual.widget import Widget
10-
from textual.widgets import Header, Input, Static, Markdown, Label
10+
from textual.widgets import Header, Static, Markdown, Label, TextArea
1111
from textual.containers import Vertical, VerticalScroll
12-
from textual.events import Key
1312
from textual.reactive import reactive
14-
from rich.style import Style
15-
from rich.text import Text
16-
from rich.panel import Panel
13+
from textual.message import Message
1714

1815
from ..utils import cumcat
1916
from ..chat import Chat
@@ -34,38 +31,6 @@
3431
##
3532

3633
class Sidebar(Widget):
37-
DEFAULT_CSS = """
38-
Sidebar {
39-
width: 30;
40-
layer: sidebar;
41-
dock: left;
42-
offset-x: -100%;
43-
44-
border-right: #cccccc;
45-
46-
transition: offset 100ms;
47-
48-
&.-visible {
49-
offset-x: 0;
50-
}
51-
52-
& > Vertical {
53-
margin: 1 2;
54-
}
55-
}
56-
57-
#history_title {
58-
width: 100%;
59-
text-align: center;
60-
border: round #d576f6;
61-
}
62-
63-
Sidebar > Vertical > Label {
64-
margin-bottom: 1;
65-
text-wrap: wrap;
66-
}
67-
"""
68-
6934
def __init__(self, convo, **kwargs):
7035
super().__init__(**kwargs)
7136
self.convo = convo
@@ -82,13 +47,6 @@ def compose(self):
8247
##
8348

8449
class ChatMessage(Markdown):
85-
DEFAULT_CSS = """
86-
ChatMessage {
87-
padding: 0 1;
88-
margin: 0 0;
89-
}
90-
"""
91-
9250
def __init__(self, title, text, **kwargs):
9351
super().__init__(text, **kwargs)
9452
self.border_title = title
@@ -103,17 +61,13 @@ def on_click(self, event):
10361
pass
10462

10563
def update(self, text):
64+
if len(text.strip()) == 0:
65+
text = '...'
10666
self._text = text
10767
return super().update(text)
10868

10969
# chat history widget
11070
class ChatHistory(VerticalScroll):
111-
DEFAULT_CSS = """
112-
ChatHistory {
113-
scrollbar-size-vertical: 0;
114-
}
115-
"""
116-
11771
def __init__(self, system=None, **kwargs):
11872
super().__init__(**kwargs)
11973
self.system = system
@@ -122,32 +76,25 @@ def compose(self):
12276
if self.system is not None:
12377
yield ChatMessage('system', self.system)
12478

125-
class BareQuery(Input):
126-
DEFAULT_CSS = """
127-
BareQuery {
128-
background: transparent;
129-
padding: 0 1;
130-
}
131-
"""
132-
133-
def __init__(self, height, **kwargs):
134-
super().__init__(**kwargs)
135-
self.styles.border = ('none', None)
136-
self.styles.height = height
137-
138-
class ChatInput(Static):
139-
DEFAULT_CSS = """
140-
ChatInput {
141-
border: round white;
142-
}
143-
"""
79+
class ChatInput(TextArea):
80+
class Submitted(Message):
81+
def __init__(self, text: str) -> None:
82+
self.text = text
83+
super().__init__()
14484

14585
def __init__(self, **kwargs):
146-
super().__init__(**kwargs)
147-
self.border_title = 'user'
86+
super().__init__(highlight_cursor_line=False, **kwargs)
14887

149-
def compose(self):
150-
yield BareQuery(height=3, placeholder='Type a message...')
88+
def on_key(self, event):
89+
if event.key == 'ctrl+enter':
90+
self.insert('\n')
91+
event.prevent_default()
92+
elif event.key == 'enter':
93+
if len(query := self.text.strip()) > 0:
94+
message = self.Submitted(query)
95+
self.post_message(message)
96+
self.clear()
97+
event.prevent_default()
15198

15299
# textualize chat app
153100
class ChatWindow(Static):
@@ -162,24 +109,19 @@ def compose(self):
162109

163110
def on_key(self, event):
164111
history = self.query_one('ChatHistory')
165-
if event.key == 'PageUp':
112+
if event.key == 'pageup':
166113
history.scroll_up(animate=False)
167-
elif event.key == 'PageDown':
114+
elif event.key == 'pagedown':
168115
history.scroll_down(animate=False)
169116

170-
@on(Input.Submitted)
171-
async def on_input(self, event):
172-
query = self.query_one('BareQuery')
173-
history = self.query_one('ChatHistory')
174-
175-
# ignore empty messages
176-
if len(message := query.value) == 0:
177-
return
178-
query.clear()
117+
async def on_chat_input_submitted(self, message):
118+
await self.submit_query(message.text)
179119

120+
async def submit_query(self, query):
180121
# mount user query and start response
122+
history = self.query_one('ChatHistory')
181123
response = ChatMessage('assistant', '...')
182-
await history.mount(ChatMessage('user', message))
124+
await history.mount(ChatMessage('user', query))
183125
await history.mount(response)
184126

185127
# make update method
@@ -188,13 +130,15 @@ def update(reply):
188130
history.scroll_end(animate=False)
189131

190132
# send message
191-
generate = self.stream(message)
133+
generate = self.stream(query)
134+
self.log.debug(f'STARTING STREAM: {query}')
192135
self.pipe_stream(generate, update)
193136

194137
@work(thread=True)
195138
async def pipe_stream(self, generate, setter):
196139
async for reply in cumcat(generate):
197140
self.app.call_from_thread(setter, reply)
141+
self.log.debug('STREAM DONE')
198142

199143
class ConvoStore:
200144
def __init__(self, store):
@@ -242,6 +186,63 @@ def load_store(self):
242186
self.convo[file] = self.load_convo(path)
243187

244188
class TextualChat(App):
189+
CSS = """
190+
ChatMessage {
191+
background: transparent;
192+
padding: 0 1;
193+
margin: 0 0;
194+
}
195+
196+
ChatHistory {
197+
scrollbar-size-vertical: 0;
198+
}
199+
200+
ChatInput {
201+
background: transparent;
202+
border: round white;
203+
padding: 0 1;
204+
height: 6;
205+
}
206+
207+
ChatInput > TextArea {
208+
height: 100%;
209+
}
210+
211+
ChatWindow {
212+
background: transparent;
213+
}
214+
215+
Sidebar {
216+
width: 30;
217+
layer: sidebar;
218+
dock: left;
219+
offset-x: -100%;
220+
221+
border-right: #cccccc;
222+
223+
transition: offset 100ms;
224+
225+
&.-visible {
226+
offset-x: 0;
227+
}
228+
229+
& > Vertical {
230+
margin: 1 2;
231+
}
232+
}
233+
234+
#history_title {
235+
width: 100%;
236+
text-align: center;
237+
border: round #d576f6;
238+
}
239+
240+
Sidebar > Vertical > Label {
241+
margin-bottom: 1;
242+
text-wrap: wrap;
243+
}
244+
"""
245+
245246
show_sidebar = reactive(False)
246247

247248
def __init__(self, chat, store=None, **kwargs):
@@ -262,26 +263,19 @@ def compose(self):
262263
yield ChatWindow(self.chat.stream_async, system=self.chat.system)
263264

264265
def on_mount(self):
265-
query = self.query_one('BareQuery')
266+
query = self.query_one('ChatInput')
266267
history = self.query_one('ChatHistory')
267268
self.set_focus(query)
268269
history.scroll_end(animate=False)
269270

270271
def on_key(self, event):
271-
if event.key in ('up', 'down', 'pageup', 'pagedown'):
272-
history = self.query_one('ChatHistory')
273-
if event.key == 'up':
274-
history.scroll_up(animate=False)
275-
elif event.key == 'down':
276-
history.scroll_down(animate=False)
277-
elif event.key == 'pageup':
278-
history.scroll_page_up(animate=False)
279-
elif event.key == 'pagedown':
280-
history.scroll_page_down(animate=False)
281-
elif event.key == 'ctrl+s':
272+
if event.key == 'ctrl+s':
282273
if self.store is not None:
283274
self.show_sidebar = not self.show_sidebar
284275
event.prevent_default()
276+
elif event.key == 'ctrl+c':
277+
self.exit()
278+
event.prevent_default()
285279

286280
def watch_show_sidebar(self, show_sidebar):
287281
if self.store is not None:

0 commit comments

Comments
 (0)