pylogue 0.1.0__py3-none-any.whl → 0.2.1__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.
- pylogue/__init__.py +2 -0
- pylogue/_modidx.py +4 -2
- pylogue/chatapp.py +31 -29
- pylogue/renderer.py +6 -29
- pylogue/service.py +51 -1
- {pylogue-0.1.0.dist-info → pylogue-0.2.1.dist-info}/METADATA +2 -2
- pylogue-0.2.1.dist-info/RECORD +17 -0
- pylogue-0.1.0.dist-info/RECORD +0 -17
- {pylogue-0.1.0.dist-info → pylogue-0.2.1.dist-info}/WHEEL +0 -0
- {pylogue-0.1.0.dist-info → pylogue-0.2.1.dist-info}/entry_points.txt +0 -0
- {pylogue-0.1.0.dist-info → pylogue-0.2.1.dist-info}/licenses/AUTHORS.md +0 -0
- {pylogue-0.1.0.dist-info → pylogue-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {pylogue-0.1.0.dist-info → pylogue-0.2.1.dist-info}/top_level.txt +0 -0
pylogue/__init__.py
CHANGED
pylogue/_modidx.py
CHANGED
|
@@ -45,8 +45,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
45
45
|
'pylogue.renderer': { 'pylogue.renderer.ChatRenderer': ('3-renderer.html#chatrenderer', 'pylogue/renderer.py'),
|
|
46
46
|
'pylogue.renderer.ChatRenderer.__init__': ( '3-renderer.html#chatrenderer.__init__',
|
|
47
47
|
'pylogue/renderer.py'),
|
|
48
|
-
'pylogue.renderer.ChatRenderer.render_chat_interface': ( '3-renderer.html#chatrenderer.render_chat_interface',
|
|
49
|
-
'pylogue/renderer.py'),
|
|
50
48
|
'pylogue.renderer.ChatRenderer.render_form': ( '3-renderer.html#chatrenderer.render_form',
|
|
51
49
|
'pylogue/renderer.py'),
|
|
52
50
|
'pylogue.renderer.ChatRenderer.render_input': ( '3-renderer.html#chatrenderer.render_input',
|
|
@@ -59,8 +57,12 @@ d = { 'settings': { 'branch': 'main',
|
|
|
59
57
|
'pylogue/renderer.py')},
|
|
60
58
|
'pylogue.service': { 'pylogue.service.ChatService': ('2-service.html#chatservice', 'pylogue/service.py'),
|
|
61
59
|
'pylogue.service.ChatService.__init__': ('2-service.html#chatservice.__init__', 'pylogue/service.py'),
|
|
60
|
+
'pylogue.service.ChatService._is_async_generator': ( '2-service.html#chatservice._is_async_generator',
|
|
61
|
+
'pylogue/service.py'),
|
|
62
62
|
'pylogue.service.ChatService.process_message': ( '2-service.html#chatservice.process_message',
|
|
63
63
|
'pylogue/service.py'),
|
|
64
|
+
'pylogue.service.ChatService.process_message_stream': ( '2-service.html#chatservice.process_message_stream',
|
|
65
|
+
'pylogue/service.py'),
|
|
64
66
|
'pylogue.service.ChatService.process_session_message': ( '2-service.html#chatservice.process_session_message',
|
|
65
67
|
'pylogue/service.py'),
|
|
66
68
|
'pylogue.service.ContextAwareResponder': ('2-service.html#contextawareresponder', 'pylogue/service.py'),
|
pylogue/chatapp.py
CHANGED
|
@@ -34,6 +34,7 @@ class ChatAppConfig:
|
|
|
34
34
|
|
|
35
35
|
# WebSocket settings
|
|
36
36
|
ws_endpoint: str = "/ws"
|
|
37
|
+
chat_endpoint: str = "/chat"
|
|
37
38
|
|
|
38
39
|
# FastHTML extensions and headers
|
|
39
40
|
markdown_enabled: bool = True
|
|
@@ -78,15 +79,7 @@ class ChatAppConfig:
|
|
|
78
79
|
|
|
79
80
|
# %% ../../nbs/4-ChatApp.ipynb 4
|
|
80
81
|
class ChatApp:
|
|
81
|
-
"""
|
|
82
|
-
Complete chat application with full dependency injection.
|
|
83
|
-
|
|
84
|
-
This is the main orchestration layer that ties together:
|
|
85
|
-
- Session management (state)
|
|
86
|
-
- Chat service (business logic)
|
|
87
|
-
- Renderer (presentation)
|
|
88
|
-
- FastHTML + WebSocket (infrastructure)
|
|
89
|
-
"""
|
|
82
|
+
"""Main chat application composing all components."""
|
|
90
83
|
|
|
91
84
|
def __init__(
|
|
92
85
|
self,
|
|
@@ -96,7 +89,7 @@ class ChatApp:
|
|
|
96
89
|
config: Optional[ChatAppConfig] = None,
|
|
97
90
|
):
|
|
98
91
|
"""
|
|
99
|
-
Initialize ChatApp with
|
|
92
|
+
Initialize ChatApp with dependency injection.
|
|
100
93
|
|
|
101
94
|
Args:
|
|
102
95
|
session_manager: Manages chat sessions
|
|
@@ -139,7 +132,7 @@ class ChatApp:
|
|
|
139
132
|
def _register_routes(self):
|
|
140
133
|
"""Register HTTP and WebSocket routes."""
|
|
141
134
|
|
|
142
|
-
@self.app.route(
|
|
135
|
+
@self.app.route(self.config.chat_endpoint)
|
|
143
136
|
def home():
|
|
144
137
|
"""Main chat interface."""
|
|
145
138
|
initial_messages = self._get_initial_messages()
|
|
@@ -155,12 +148,11 @@ class ChatApp:
|
|
|
155
148
|
H1(self.config.app_title, style=self.config.header_style),
|
|
156
149
|
self.renderer.render_messages(initial_messages),
|
|
157
150
|
self.renderer.render_form(),
|
|
158
|
-
hx_ext="ws",
|
|
159
|
-
ws_connect=self.config.ws_endpoint,
|
|
160
151
|
style=container_style,
|
|
161
152
|
),
|
|
162
153
|
)
|
|
163
154
|
|
|
155
|
+
# Register WebSocket route with message handler
|
|
164
156
|
@self.app.ws(
|
|
165
157
|
self.config.ws_endpoint, conn=self._on_connect, disconn=self._on_disconnect
|
|
166
158
|
)
|
|
@@ -183,12 +175,11 @@ class ChatApp:
|
|
|
183
175
|
|
|
184
176
|
async def _handle_websocket_message(self, msg: str, send, ws):
|
|
185
177
|
"""
|
|
186
|
-
Handle incoming WebSocket message with
|
|
178
|
+
Handle incoming WebSocket message with streaming support:
|
|
187
179
|
1. Add user message
|
|
188
|
-
2.
|
|
189
|
-
3.
|
|
190
|
-
4.
|
|
191
|
-
5. Clear input
|
|
180
|
+
2. Add empty assistant message for streaming
|
|
181
|
+
3. Stream response tokens and update message progressively
|
|
182
|
+
4. Clear input
|
|
192
183
|
"""
|
|
193
184
|
session_id = str(id(ws))
|
|
194
185
|
session = self.session_manager.get_session(session_id)
|
|
@@ -204,21 +195,32 @@ class ChatApp:
|
|
|
204
195
|
session.add_message("User", msg)
|
|
205
196
|
await send(self.renderer.render_messages(session.get_messages()))
|
|
206
197
|
|
|
207
|
-
# Step 2: Add
|
|
208
|
-
|
|
209
|
-
await send(self.renderer.render_messages(session.get_messages()))
|
|
198
|
+
# Step 2: Add empty assistant message for streaming
|
|
199
|
+
assistant_msg = session.add_message("Assistant", "", pending=False)
|
|
210
200
|
|
|
211
|
-
# Step 3:
|
|
201
|
+
# Step 3: Stream response and update progressively
|
|
212
202
|
try:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
203
|
+
response_chunks = []
|
|
204
|
+
chunk_count = 0
|
|
205
|
+
async for chunk in self.chat_service.process_message_stream(msg, session):
|
|
206
|
+
response_chunks.append(chunk)
|
|
207
|
+
chunk_count += 1
|
|
208
|
+
# Update the assistant message with accumulated response
|
|
209
|
+
full_response = "".join(response_chunks)
|
|
210
|
+
session.update_message(
|
|
211
|
+
assistant_msg.id, content=full_response, pending=False
|
|
212
|
+
)
|
|
213
|
+
# Send updated message list to UI
|
|
214
|
+
print(f"📤 Sending chunk #{chunk_count}: {repr(chunk)}") # Debug
|
|
215
|
+
await send(self.renderer.render_messages(session.get_messages()))
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
except Exception as e:
|
|
218
|
+
# Handle errors
|
|
219
|
+
error_msg = f"Error: {str(e)}"
|
|
220
|
+
session.update_message(assistant_msg.id, content=error_msg, pending=False)
|
|
221
|
+
await send(self.renderer.render_messages(session.get_messages()))
|
|
220
222
|
|
|
221
|
-
# Step
|
|
223
|
+
# Step 4: Clear input field
|
|
222
224
|
await send(self.renderer.render_input())
|
|
223
225
|
|
|
224
226
|
def run(
|
pylogue/renderer.py
CHANGED
|
@@ -21,6 +21,7 @@ class ChatRenderer:
|
|
|
21
21
|
input_placeholder: str = "Type a message...",
|
|
22
22
|
input_style: Optional[str] = None,
|
|
23
23
|
chat_container_style: Optional[str] = None,
|
|
24
|
+
ws_endpoint: str = "/ws",
|
|
24
25
|
):
|
|
25
26
|
"""
|
|
26
27
|
Initialize ChatRenderer.
|
|
@@ -30,9 +31,11 @@ class ChatRenderer:
|
|
|
30
31
|
input_placeholder: Placeholder text for input field
|
|
31
32
|
input_style: Custom CSS style for input field
|
|
32
33
|
chat_container_style: Custom CSS style for chat container
|
|
34
|
+
ws_endpoint: WebSocket endpoint path
|
|
33
35
|
"""
|
|
34
36
|
self.card = card or ChatCard()
|
|
35
37
|
self.input_placeholder = input_placeholder
|
|
38
|
+
self.ws_endpoint = ws_endpoint
|
|
36
39
|
self.input_style = input_style or (
|
|
37
40
|
"width: 60%; max-width: 600px; padding: 0.75em; "
|
|
38
41
|
"font-size: 1em; border-radius: 0.5em"
|
|
@@ -100,7 +103,7 @@ class ChatRenderer:
|
|
|
100
103
|
ws_send: bool = True,
|
|
101
104
|
) -> Any:
|
|
102
105
|
"""
|
|
103
|
-
Render the input form with
|
|
106
|
+
Render the input form with WebSocket connection.
|
|
104
107
|
|
|
105
108
|
Args:
|
|
106
109
|
form_id: HTML ID for the form
|
|
@@ -118,34 +121,8 @@ class ChatRenderer:
|
|
|
118
121
|
return Form(
|
|
119
122
|
self.render_input(),
|
|
120
123
|
id=form_id,
|
|
124
|
+
hx_ext="ws",
|
|
125
|
+
ws_connect=self.ws_endpoint,
|
|
121
126
|
ws_send=ws_send,
|
|
122
127
|
style=form_style,
|
|
123
128
|
)
|
|
124
|
-
|
|
125
|
-
def render_chat_interface(
|
|
126
|
-
self,
|
|
127
|
-
messages: List[Message],
|
|
128
|
-
title: str = "Chat",
|
|
129
|
-
header_style: Optional[str] = None,
|
|
130
|
-
container_style: Optional[str] = None,
|
|
131
|
-
) -> Any:
|
|
132
|
-
"""
|
|
133
|
-
Render complete chat interface with messages and input.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
messages: List of messages to display
|
|
137
|
-
title: Chat title/header
|
|
138
|
-
header_style: Custom style for header
|
|
139
|
-
container_style: Custom style for main container
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
FastHTML Div with complete chat interface
|
|
143
|
-
"""
|
|
144
|
-
header_style = header_style or "text-align: center; padding: 1em;"
|
|
145
|
-
|
|
146
|
-
return Div(
|
|
147
|
-
H1(title, style=header_style),
|
|
148
|
-
self.render_messages(messages),
|
|
149
|
-
self.render_form(),
|
|
150
|
-
style=container_style,
|
|
151
|
-
)
|
pylogue/service.py
CHANGED
|
@@ -60,6 +60,12 @@ class ChatService:
|
|
|
60
60
|
self.error_handler = error_handler or DefaultErrorHandler()
|
|
61
61
|
self.context_provider = context_provider
|
|
62
62
|
|
|
63
|
+
def _is_async_generator(self, obj):
|
|
64
|
+
"""Check if object is an async generator."""
|
|
65
|
+
return inspect.isasyncgenfunction(obj) or (
|
|
66
|
+
hasattr(obj, "__call__") and inspect.isasyncgenfunction(obj.__call__)
|
|
67
|
+
)
|
|
68
|
+
|
|
63
69
|
async def process_message(
|
|
64
70
|
self, user_message: str, session: Optional[ChatSession] = None
|
|
65
71
|
) -> str:
|
|
@@ -79,7 +85,15 @@ class ChatService:
|
|
|
79
85
|
if self.context_provider and session:
|
|
80
86
|
context = self.context_provider(session)
|
|
81
87
|
|
|
82
|
-
#
|
|
88
|
+
# Check if responder is an async generator (streaming)
|
|
89
|
+
if self._is_async_generator(self.responder):
|
|
90
|
+
# For streaming responders, collect all chunks
|
|
91
|
+
chunks = []
|
|
92
|
+
async for chunk in self.responder(user_message, context):
|
|
93
|
+
chunks.append(str(chunk))
|
|
94
|
+
return "".join(chunks)
|
|
95
|
+
|
|
96
|
+
# Non-streaming responder
|
|
83
97
|
if inspect.iscoroutinefunction(self.responder):
|
|
84
98
|
result = await self.responder(user_message, context)
|
|
85
99
|
else:
|
|
@@ -92,6 +106,42 @@ class ChatService:
|
|
|
92
106
|
except Exception as e:
|
|
93
107
|
return self.error_handler(e, user_message)
|
|
94
108
|
|
|
109
|
+
async def process_message_stream(
|
|
110
|
+
self, user_message: str, session: Optional[ChatSession] = None
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
Process a user message and stream the response token by token.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
user_message: The user's input message
|
|
117
|
+
session: Optional chat session for context
|
|
118
|
+
|
|
119
|
+
Yields:
|
|
120
|
+
Response chunks as they are generated
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
# Extract context if provider exists
|
|
124
|
+
context = None
|
|
125
|
+
if self.context_provider and session:
|
|
126
|
+
context = self.context_provider(session)
|
|
127
|
+
|
|
128
|
+
# Check if responder supports streaming
|
|
129
|
+
if self._is_async_generator(self.responder):
|
|
130
|
+
async for chunk in self.responder(user_message, context):
|
|
131
|
+
yield str(chunk)
|
|
132
|
+
else:
|
|
133
|
+
# Non-streaming responder - yield full response
|
|
134
|
+
if inspect.iscoroutinefunction(self.responder):
|
|
135
|
+
result = await self.responder(user_message, context)
|
|
136
|
+
else:
|
|
137
|
+
result = self.responder(user_message, context)
|
|
138
|
+
if inspect.isawaitable(result):
|
|
139
|
+
result = await result
|
|
140
|
+
yield str(result)
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
yield self.error_handler(e, user_message)
|
|
144
|
+
|
|
95
145
|
async def process_session_message(
|
|
96
146
|
self, session: ChatSession, user_message: str, add_to_session: bool = True
|
|
97
147
|
) -> Message:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pylogue
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: A Chatbot UI
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: A Chatbot UI built for AI agents
|
|
5
5
|
Author-email: Yeshwanth Reddy <yyeshr@gmail.com>
|
|
6
6
|
Maintainer-email: Yeshwanth Reddy <yyeshr@gmail.com>
|
|
7
7
|
License: MIT license
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pylogue/__init__.py,sha256=gDXS44jmg6hPtcg8IxTFRdyV-TGsjQTkT-5NjiapYZ4,319
|
|
2
|
+
pylogue/__pre_init__.py,sha256=NiQUAz7feHFR47QTpMwF324j61IVrOreZsk9wY11cds,153
|
|
3
|
+
pylogue/_modidx.py,sha256=apvAlYvaQgShSh_ZrX7fXSpxDdwPBYIKwEQWdEkVJg8,14209
|
|
4
|
+
pylogue/cards.py,sha256=wsOVNbMczFbyoATkujC7xJAeskYgTqsJmxmkHqzHA-g,5385
|
|
5
|
+
pylogue/chat.py,sha256=RyRADCZWxTjCM3W7FgIj0jQ8WrqhRBzbeFEvS9mTZq4,3982
|
|
6
|
+
pylogue/chatapp.py,sha256=Rn2GMmo7GTatsnAdIuYFTalLMb9qTxS1BmikQUVlE3Y,9973
|
|
7
|
+
pylogue/health.py,sha256=8LWhpVzdj2g6qUcErrg455_6WfOsv3lp82OLLwJPtug,443
|
|
8
|
+
pylogue/renderer.py,sha256=KGo0l-Jo8i2pe8NqwlZsM9Qo3ZBA2EV-osrHnBUWzBA,3862
|
|
9
|
+
pylogue/service.py,sha256=e3GfbQrOPlRIj7j0CZZx_qN2ZI_polOuWqBMm7ySXqs,6917
|
|
10
|
+
pylogue/session.py,sha256=fUyGOFIG8b1clhyyw5c_dapdwrJyzd9K2bOc_qKBQcs,5382
|
|
11
|
+
pylogue-0.2.1.dist-info/licenses/AUTHORS.md,sha256=5Viska6TOr9uggc2olSr8VVnu6BFw_F6_64gP42CwQ4,157
|
|
12
|
+
pylogue-0.2.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
pylogue-0.2.1.dist-info/METADATA,sha256=efMS8zmtNQAeAlcthiuk8oxhkTh5OeYn6Z7kF-ft_vg,1059
|
|
14
|
+
pylogue-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
pylogue-0.2.1.dist-info/entry_points.txt,sha256=ha0gwcGgtciKEhmFcMKJO1efTY9Spu1wcTHUopB1bec,40
|
|
16
|
+
pylogue-0.2.1.dist-info/top_level.txt,sha256=oEueWVdlRAUPQt8VfQdqFEqOWxvUObx3UIa3UYa3s6o,8
|
|
17
|
+
pylogue-0.2.1.dist-info/RECORD,,
|
pylogue-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pylogue/__init__.py,sha256=5_YsGHUfQ5L1Eh4uZjSnsgszN9JSrWnO1VwEzBGN6Z4,273
|
|
2
|
-
pylogue/__pre_init__.py,sha256=NiQUAz7feHFR47QTpMwF324j61IVrOreZsk9wY11cds,153
|
|
3
|
-
pylogue/_modidx.py,sha256=S-NkgQExGm8mDtWiZztOY9DhMtvieuDNXg1PG8RFTMc,13970
|
|
4
|
-
pylogue/cards.py,sha256=wsOVNbMczFbyoATkujC7xJAeskYgTqsJmxmkHqzHA-g,5385
|
|
5
|
-
pylogue/chat.py,sha256=RyRADCZWxTjCM3W7FgIj0jQ8WrqhRBzbeFEvS9mTZq4,3982
|
|
6
|
-
pylogue/chatapp.py,sha256=V0edaNeQXy2wKwDr5MZ5IcKC3a_AakXPcfN4sqGpDhU,9643
|
|
7
|
-
pylogue/health.py,sha256=8LWhpVzdj2g6qUcErrg455_6WfOsv3lp82OLLwJPtug,443
|
|
8
|
-
pylogue/renderer.py,sha256=7ZiGNJ83xwCVgvw4pnEEJPB3E9lO60vK6qobAttryNk,4507
|
|
9
|
-
pylogue/service.py,sha256=0hdL8Eg7FDpX_NXSuTzC7bJ_wvEnnRsomDR-kNrs2i4,4952
|
|
10
|
-
pylogue/session.py,sha256=fUyGOFIG8b1clhyyw5c_dapdwrJyzd9K2bOc_qKBQcs,5382
|
|
11
|
-
pylogue-0.1.0.dist-info/licenses/AUTHORS.md,sha256=5Viska6TOr9uggc2olSr8VVnu6BFw_F6_64gP42CwQ4,157
|
|
12
|
-
pylogue-0.1.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
pylogue-0.1.0.dist-info/METADATA,sha256=0qS4tbqcJYe-ZmLOc9LX2NAG1LTmnWHmghaKjKHUCbM,1068
|
|
14
|
-
pylogue-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
-
pylogue-0.1.0.dist-info/entry_points.txt,sha256=ha0gwcGgtciKEhmFcMKJO1efTY9Spu1wcTHUopB1bec,40
|
|
16
|
-
pylogue-0.1.0.dist-info/top_level.txt,sha256=oEueWVdlRAUPQt8VfQdqFEqOWxvUObx3UIa3UYa3s6o,8
|
|
17
|
-
pylogue-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|