cli-memory-os 0.1.8__tar.gz → 0.1.10__tar.gz
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.
- {cli_memory_os-0.1.8/cli_memory_os.egg-info → cli_memory_os-0.1.10}/PKG-INFO +1 -1
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/tui/app.py +97 -60
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10/cli_memory_os.egg-info}/PKG-INFO +1 -1
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/pyproject.toml +1 -1
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_tui.py +19 -19
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/LICENSE +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/MANIFEST.in +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/README.md +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/ask.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/benchmark.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/config_cmd.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/doctor.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/export.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/import_cmd.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/init.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/logs.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/monitor.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/plugins.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/restart.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/start.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/status.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/stop.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/sync.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/version.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/commands/workspace.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/main.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/parser.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli/tui/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli_memory_os.egg-info/SOURCES.txt +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli_memory_os.egg-info/dependency_links.txt +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli_memory_os.egg-info/entry_points.txt +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli_memory_os.egg-info/requires.txt +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/cli_memory_os.egg-info/top_level.txt +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/connectors/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/connectors/base.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/connectors/github.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/connectors/gmail.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/connectors/notion.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/connectors/registry.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/core/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/core/chunker.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/core/context_builder.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/core/embedder.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/core/llm.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/core/vector_store.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/compose.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/config.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/docker.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/health.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/observability.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/infrastructure/workspace.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/models/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/models/memory.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/setup.cfg +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/storage/__init__.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/storage/db.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/storage/graph.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/storage/schema.sql +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/storage/tech_detector.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_cli_integration.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_commands_phase2.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_compose_manager.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_first_time_user_experience.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_infrastructure_stabilization.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_sync_ask_cli.py +0 -0
- {cli_memory_os-0.1.8 → cli_memory_os-0.1.10}/tests/test_v1_features.py +0 -0
|
@@ -159,31 +159,44 @@ class Sidebar(Static):
|
|
|
159
159
|
f"Gmail: {self.gmail_status}\n"
|
|
160
160
|
f"Notion: {self.notion_status}\n\n"
|
|
161
161
|
f"[bold cyan]⌨️ Shortcuts[/bold cyan]\n"
|
|
162
|
-
f"[cyan]
|
|
163
|
-
f"[cyan]
|
|
164
|
-
f"[cyan]
|
|
165
|
-
f"[cyan]
|
|
166
|
-
f"[cyan]
|
|
167
|
-
f"[cyan]
|
|
168
|
-
f"[cyan]
|
|
169
|
-
f"[cyan]
|
|
162
|
+
f"[cyan]ctrl+1[/cyan]: Chat\n"
|
|
163
|
+
f"[cyan]ctrl+2[/cyan]: Sync\n"
|
|
164
|
+
f"[cyan]ctrl+3[/cyan]: Doctor\n"
|
|
165
|
+
f"[cyan]ctrl+4[/cyan]: Graph\n"
|
|
166
|
+
f"[cyan]ctrl+5[/cyan]: Search\n"
|
|
167
|
+
f"[cyan]ctrl+6[/cyan]: Monitor\n"
|
|
168
|
+
f"[cyan]ctrl+7[/cyan]: Settings\n"
|
|
169
|
+
f"[cyan]ctrl+q[/cyan]: Quit"
|
|
170
170
|
)
|
|
171
|
-
self.update(Panel(content, title="Memory-OS", border_style="
|
|
171
|
+
self.update(Panel(content, title="Memory-OS", border_style="#1F2937"))
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
class ChatPanel(
|
|
174
|
+
class ChatPanel(Container):
|
|
175
175
|
"""Main Chat Interface supporting streaming responses and input field."""
|
|
176
176
|
|
|
177
177
|
def compose(self) -> ComposeResult:
|
|
178
178
|
with Vertical(id="chat_container"):
|
|
179
179
|
with VerticalScroll(id="chat_history_scroll"):
|
|
180
|
+
welcome_md = (
|
|
181
|
+
"```\n"
|
|
182
|
+
" __ __ ____ ____\n"
|
|
183
|
+
"| \\/ | ___ _ __ ___ ___ _ __ _ _ / ___| / ___|\n"
|
|
184
|
+
"| |\\/| |/ _ \\ '_ ` _ \\ / _ \\| '__| | | | \\___ \\| |\n"
|
|
185
|
+
"| | | | __/ | | | | | (_) | | | |_| | ___) | |___\n"
|
|
186
|
+
"|_| |_|\\___|_| |_| |_|\\___/|_| \\__, | |____/ \\____|\n"
|
|
187
|
+
" |___/\n"
|
|
188
|
+
"```\n"
|
|
189
|
+
"**Grounded Personal Knowledge Operating System**\n\n"
|
|
190
|
+
"Ask questions grounded against your connected workspace: repositories, documentation files, and synced emails.\n\n"
|
|
191
|
+
"💡 *Tip: Try asking: 'tell me about my projects' or 'what repositories use Python?'*"
|
|
192
|
+
)
|
|
180
193
|
yield Static(
|
|
181
|
-
RichMarkdown(
|
|
194
|
+
RichMarkdown(welcome_md),
|
|
182
195
|
id="welcome_message"
|
|
183
196
|
)
|
|
184
197
|
with Horizontal(id="input_bar"):
|
|
198
|
+
yield Label("❯", id="prompt_symbol")
|
|
185
199
|
yield Input(placeholder="Ask a question...", id="chat_input")
|
|
186
|
-
yield Button("Send", id="chat_send_btn", variant="primary")
|
|
187
200
|
|
|
188
201
|
def on_mount(self) -> None:
|
|
189
202
|
self.query_one("#chat_input").focus()
|
|
@@ -194,10 +207,6 @@ class ChatPanel(Static):
|
|
|
194
207
|
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
195
208
|
await self.process_message()
|
|
196
209
|
|
|
197
|
-
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
198
|
-
if event.button.id == "chat_send_btn":
|
|
199
|
-
await self.process_message()
|
|
200
|
-
|
|
201
210
|
async def process_message(self) -> None:
|
|
202
211
|
chat_input = self.query_one("#chat_input", Input)
|
|
203
212
|
user_text = chat_input.value.strip()
|
|
@@ -212,20 +221,20 @@ class ChatPanel(Static):
|
|
|
212
221
|
except Exception:
|
|
213
222
|
pass
|
|
214
223
|
|
|
215
|
-
# Append User Message
|
|
224
|
+
# Append User Message
|
|
216
225
|
history_scroll = self.query_one("#chat_history_scroll")
|
|
226
|
+
user_markup = f"[bold green]● You[/bold green]\n\n{user_text}"
|
|
217
227
|
history_scroll.mount(
|
|
218
|
-
Static(
|
|
228
|
+
Static(user_markup, classes="chat_msg_user")
|
|
219
229
|
)
|
|
220
230
|
|
|
221
|
-
# Append Assistant Loading/Streaming Message
|
|
222
|
-
agent_msg = Static(
|
|
231
|
+
# Append Assistant Loading/Streaming Message
|
|
232
|
+
agent_msg = Static("[bold cyan]● Memory-OS[/bold cyan]\n\n[italic dim]Thinking...[/italic dim]", classes="chat_msg_agent")
|
|
223
233
|
history_scroll.mount(agent_msg)
|
|
224
234
|
self.scroll_to_bottom()
|
|
225
235
|
|
|
226
236
|
# Disable input while processing
|
|
227
237
|
chat_input.disabled = True
|
|
228
|
-
self.query_one("#chat_send_btn").disabled = True
|
|
229
238
|
|
|
230
239
|
# Run generator task in a background worker
|
|
231
240
|
self.run_worker(self.stream_response(user_text, agent_msg))
|
|
@@ -240,7 +249,7 @@ class ChatPanel(Static):
|
|
|
240
249
|
try:
|
|
241
250
|
gen = await loop.run_in_executor(executor, get_generator)
|
|
242
251
|
except Exception as e:
|
|
243
|
-
response_widget.update(
|
|
252
|
+
response_widget.update(f"[bold red]● Error[/bold red]\n\n{e}")
|
|
244
253
|
self.enable_input()
|
|
245
254
|
return
|
|
246
255
|
|
|
@@ -255,14 +264,14 @@ class ChatPanel(Static):
|
|
|
255
264
|
except StopIteration:
|
|
256
265
|
break
|
|
257
266
|
except Exception as e:
|
|
258
|
-
response_widget.update(
|
|
267
|
+
response_widget.update(f"[bold red]● Error[/bold red]\n\n{e}")
|
|
259
268
|
break
|
|
260
269
|
|
|
261
270
|
if item["type"] == "diagnostics":
|
|
262
271
|
query_class = item["data"].get("query_class", "Unknown")
|
|
263
272
|
elif item["type"] == "token":
|
|
264
273
|
answer_text += item["content"]
|
|
265
|
-
response_widget.update(
|
|
274
|
+
response_widget.update(f"[bold cyan]● Memory-OS ({query_class})[/bold cyan]\n\n{answer_text}")
|
|
266
275
|
self.scroll_to_bottom()
|
|
267
276
|
elif item["type"] == "done":
|
|
268
277
|
sources = item["sources"]
|
|
@@ -280,7 +289,7 @@ class ChatPanel(Static):
|
|
|
280
289
|
metadata.append(f"**Confidence**: {confidence:.1f}")
|
|
281
290
|
final_md += "\n\n---\n" + "\n".join(metadata)
|
|
282
291
|
|
|
283
|
-
response_widget.update(
|
|
292
|
+
response_widget.update(RichMarkdown(f"### ● Memory-OS ({query_class})\n\n{final_md}"))
|
|
284
293
|
self.scroll_to_bottom()
|
|
285
294
|
|
|
286
295
|
self.enable_input()
|
|
@@ -288,12 +297,11 @@ class ChatPanel(Static):
|
|
|
288
297
|
def enable_input(self) -> None:
|
|
289
298
|
chat_input = self.query_one("#chat_input", Input)
|
|
290
299
|
chat_input.disabled = False
|
|
291
|
-
self.query_one("#chat_send_btn").disabled = False
|
|
292
300
|
chat_input.focus()
|
|
293
301
|
self.scroll_to_bottom()
|
|
294
302
|
|
|
295
303
|
|
|
296
|
-
class DoctorPanel(
|
|
304
|
+
class DoctorPanel(Container):
|
|
297
305
|
"""System diagnostic view execution wizard."""
|
|
298
306
|
|
|
299
307
|
def compose(self) -> ComposeResult:
|
|
@@ -354,11 +362,11 @@ class DoctorPanel(Static):
|
|
|
354
362
|
report.append("=" * 45)
|
|
355
363
|
report.append(f"Summary: [bold]{healthy_count}/{len(results)}[/bold] services are functional.")
|
|
356
364
|
|
|
357
|
-
results_static.update(Panel("\n".join(report), border_style="
|
|
365
|
+
results_static.update(Panel("\n".join(report), border_style="#1F2937"))
|
|
358
366
|
btn.disabled = False
|
|
359
367
|
|
|
360
368
|
|
|
361
|
-
class SyncPanel(
|
|
369
|
+
class SyncPanel(Container):
|
|
362
370
|
"""Workspace synchronization action board."""
|
|
363
371
|
|
|
364
372
|
def compose(self) -> ComposeResult:
|
|
@@ -453,13 +461,13 @@ class SyncPanel(Static):
|
|
|
453
461
|
return mystdout.getvalue()
|
|
454
462
|
|
|
455
463
|
output = await loop.run_in_executor(executor, run_sync_redirected)
|
|
456
|
-
log_widget.update(Panel(output, title="Sync Log Output", border_style="
|
|
464
|
+
log_widget.update(Panel(output, title="Sync Log Output", border_style="#1F2937"))
|
|
457
465
|
|
|
458
466
|
btn_all.disabled = False
|
|
459
467
|
btn_rebuild.disabled = False
|
|
460
468
|
|
|
461
469
|
|
|
462
|
-
class GraphPanel(
|
|
470
|
+
class GraphPanel(Container):
|
|
463
471
|
"""View graph metrics and technological detections."""
|
|
464
472
|
|
|
465
473
|
def compose(self) -> ComposeResult:
|
|
@@ -530,10 +538,10 @@ class GraphPanel(Static):
|
|
|
530
538
|
for r_name, lang in repos:
|
|
531
539
|
stats_text += f" - [white]{r_name}[/white] : {lang or 'Unknown'}\n"
|
|
532
540
|
|
|
533
|
-
self.query_one("#graph_stats", Static).update(Panel(stats_text, border_style="
|
|
541
|
+
self.query_one("#graph_stats", Static).update(Panel(stats_text, border_style="#1F2937"))
|
|
534
542
|
|
|
535
543
|
|
|
536
|
-
class SearchPanel(
|
|
544
|
+
class SearchPanel(Container):
|
|
537
545
|
"""Semantic Finder View."""
|
|
538
546
|
|
|
539
547
|
def compose(self) -> ComposeResult:
|
|
@@ -598,7 +606,7 @@ class SearchPanel(Static):
|
|
|
598
606
|
results_static.update("\n".join(report))
|
|
599
607
|
|
|
600
608
|
|
|
601
|
-
class MonitorPanel(
|
|
609
|
+
class MonitorPanel(Container):
|
|
602
610
|
"""Latency dashboard displaying performance parser metrics."""
|
|
603
611
|
|
|
604
612
|
def compose(self) -> ComposeResult:
|
|
@@ -633,17 +641,17 @@ class MonitorPanel(Static):
|
|
|
633
641
|
f" - [bold cyan]Average LLM generation[/bold cyan]: {avg(l_times):.2f}s ({len(l_times)} calls)\n"
|
|
634
642
|
f" - [bold cyan]Average Embedding Gen[/bold cyan] : {avg([x[0] for x in e_times]):.2f}s ({len(e_times)} runs)\n"
|
|
635
643
|
f" - [bold cyan]Average Qdrant Upload[/bold cyan] : {avg([x[0] for x in v_uploads]):.2f}s ({len(v_uploads)} runs)\n\n"
|
|
636
|
-
f"[bold cyan]
|
|
644
|
+
f"[bold cyan] Recent Log Details[/bold cyan]\n"
|
|
637
645
|
)
|
|
638
646
|
if r_times:
|
|
639
647
|
stats_text += f" - Last RAG execution: {r_times[-1]:.2f}s\n"
|
|
640
648
|
if l_times:
|
|
641
649
|
stats_text += f" - Last Groq response: {l_times[-1]:.2f}s\n"
|
|
642
650
|
|
|
643
|
-
self.query_one("#monitor_stats", Static).update(Panel(stats_text, border_style="
|
|
651
|
+
self.query_one("#monitor_stats", Static).update(Panel(stats_text, border_style="#1F2937"))
|
|
644
652
|
|
|
645
653
|
|
|
646
|
-
class SettingsPanel(
|
|
654
|
+
class SettingsPanel(Container):
|
|
647
655
|
"""View active configs and workspaces."""
|
|
648
656
|
|
|
649
657
|
def compose(self) -> ComposeResult:
|
|
@@ -668,7 +676,7 @@ class SettingsPanel(Static):
|
|
|
668
676
|
f"Groq Model: {cfg.get('groq', {}).get('model', 'llama-3.3-70b-versatile')}\n"
|
|
669
677
|
f"Embedding Model: {cfg.get('embeddings', {}).get('model', 'all-MiniLM-L6-v2')}\n"
|
|
670
678
|
)
|
|
671
|
-
self.query_one("#settings_details", Static).update(Panel(details, border_style="
|
|
679
|
+
self.query_one("#settings_details", Static).update(Panel(details, border_style="#1F2937"))
|
|
672
680
|
|
|
673
681
|
|
|
674
682
|
class MemoryOSTUIApp(App):
|
|
@@ -677,7 +685,7 @@ class MemoryOSTUIApp(App):
|
|
|
677
685
|
TITLE = "Memory-OS Terminal UI"
|
|
678
686
|
CSS = """
|
|
679
687
|
Screen {
|
|
680
|
-
background: #
|
|
688
|
+
background: #0B0F19;
|
|
681
689
|
}
|
|
682
690
|
|
|
683
691
|
#main_layout {
|
|
@@ -687,8 +695,8 @@ class MemoryOSTUIApp(App):
|
|
|
687
695
|
Sidebar {
|
|
688
696
|
width: 35;
|
|
689
697
|
height: 100%;
|
|
690
|
-
background: #
|
|
691
|
-
border-right: solid
|
|
698
|
+
background: #111622;
|
|
699
|
+
border-right: solid #1F2937;
|
|
692
700
|
}
|
|
693
701
|
|
|
694
702
|
ContentSwitcher {
|
|
@@ -703,19 +711,24 @@ class MemoryOSTUIApp(App):
|
|
|
703
711
|
|
|
704
712
|
#chat_history_scroll {
|
|
705
713
|
height: 1fr;
|
|
706
|
-
border: solid
|
|
714
|
+
border: solid #1F2937;
|
|
707
715
|
margin-bottom: 1;
|
|
708
716
|
padding: 1 2;
|
|
717
|
+
background: #0B0F19;
|
|
709
718
|
}
|
|
710
719
|
|
|
711
720
|
.chat_msg_user {
|
|
712
721
|
margin: 1 0;
|
|
713
|
-
|
|
722
|
+
background: #161B22;
|
|
723
|
+
border-left: solid #10B981;
|
|
724
|
+
padding: 1 2;
|
|
714
725
|
}
|
|
715
726
|
|
|
716
727
|
.chat_msg_agent {
|
|
717
728
|
margin: 1 0;
|
|
718
|
-
|
|
729
|
+
background: #111622;
|
|
730
|
+
border-left: solid #3B82F6;
|
|
731
|
+
padding: 1 2;
|
|
719
732
|
}
|
|
720
733
|
|
|
721
734
|
#input_bar {
|
|
@@ -725,14 +738,21 @@ class MemoryOSTUIApp(App):
|
|
|
725
738
|
|
|
726
739
|
#chat_input {
|
|
727
740
|
width: 1fr;
|
|
728
|
-
background: #
|
|
729
|
-
border: solid
|
|
741
|
+
background: #161B22;
|
|
742
|
+
border: solid #1F2937;
|
|
730
743
|
color: white;
|
|
731
744
|
}
|
|
732
745
|
|
|
733
746
|
#chat_send_btn {
|
|
734
747
|
width: 12;
|
|
735
748
|
margin-left: 1;
|
|
749
|
+
background: #1F2937;
|
|
750
|
+
color: white;
|
|
751
|
+
border: none;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
#chat_send_btn:hover {
|
|
755
|
+
background: #3B82F6;
|
|
736
756
|
}
|
|
737
757
|
|
|
738
758
|
#search_bar {
|
|
@@ -742,25 +762,40 @@ class MemoryOSTUIApp(App):
|
|
|
742
762
|
|
|
743
763
|
#search_query_input {
|
|
744
764
|
width: 1fr;
|
|
745
|
-
background: #
|
|
746
|
-
border: solid
|
|
765
|
+
background: #161B22;
|
|
766
|
+
border: solid #1F2937;
|
|
747
767
|
color: white;
|
|
748
768
|
}
|
|
749
769
|
|
|
750
770
|
#search_run_btn {
|
|
751
771
|
width: 12;
|
|
752
772
|
margin-left: 1;
|
|
773
|
+
background: #1F2937;
|
|
774
|
+
color: white;
|
|
775
|
+
border: none;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#search_run_btn:hover {
|
|
779
|
+
background: #3B82F6;
|
|
753
780
|
}
|
|
754
781
|
|
|
755
782
|
#doctor_results_scroll, #sync_results_scroll, #search_results_scroll {
|
|
756
783
|
height: 1fr;
|
|
757
|
-
border: solid
|
|
784
|
+
border: solid #1F2937;
|
|
758
785
|
margin-top: 1;
|
|
759
786
|
padding: 1 2;
|
|
787
|
+
background: #0B0F19;
|
|
760
788
|
}
|
|
761
789
|
|
|
762
790
|
Button {
|
|
763
791
|
min-width: 15;
|
|
792
|
+
background: #1F2937;
|
|
793
|
+
color: white;
|
|
794
|
+
border: none;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
Button:hover {
|
|
798
|
+
background: #3B82F6;
|
|
764
799
|
}
|
|
765
800
|
|
|
766
801
|
Label {
|
|
@@ -769,23 +804,25 @@ class MemoryOSTUIApp(App):
|
|
|
769
804
|
"""
|
|
770
805
|
|
|
771
806
|
BINDINGS = [
|
|
772
|
-
Binding("ctrl+b", "toggle_sidebar", "
|
|
773
|
-
Binding("
|
|
774
|
-
Binding("
|
|
775
|
-
Binding("
|
|
776
|
-
Binding("
|
|
777
|
-
Binding("
|
|
778
|
-
Binding("
|
|
779
|
-
Binding("
|
|
780
|
-
Binding("q", "quit", "Quit"),
|
|
807
|
+
Binding("ctrl+b", "toggle_sidebar", "Sidebar"),
|
|
808
|
+
Binding("ctrl+1", "switch_view('chat')", "Chat"),
|
|
809
|
+
Binding("ctrl+2", "switch_view('sync')", "Sync"),
|
|
810
|
+
Binding("ctrl+3", "switch_view('doctor')", "Doctor"),
|
|
811
|
+
Binding("ctrl+4", "switch_view('graph')", "Graph"),
|
|
812
|
+
Binding("ctrl+5", "switch_view('search')", "Search"),
|
|
813
|
+
Binding("ctrl+6", "switch_view('monitor')", "Monitor"),
|
|
814
|
+
Binding("ctrl+7", "switch_view('settings')", "Settings"),
|
|
815
|
+
Binding("ctrl+q", "quit", "Quit"),
|
|
781
816
|
]
|
|
782
817
|
|
|
783
|
-
sidebar_visible = reactive(
|
|
818
|
+
sidebar_visible = reactive(False)
|
|
784
819
|
|
|
785
820
|
def compose(self) -> ComposeResult:
|
|
786
821
|
yield Header(show_clock=True)
|
|
787
822
|
with Horizontal(id="main_layout"):
|
|
788
|
-
|
|
823
|
+
sidebar = Sidebar(id="sidebar_menu")
|
|
824
|
+
sidebar.display = self.sidebar_visible
|
|
825
|
+
yield sidebar
|
|
789
826
|
with ContentSwitcher(initial="chat", id="panel_switcher"):
|
|
790
827
|
yield ChatPanel(id="chat")
|
|
791
828
|
yield SyncPanel(id="sync")
|
|
@@ -14,8 +14,8 @@ async def test_tui_app_boot_and_default_view():
|
|
|
14
14
|
switcher = app.query_one("#panel_switcher")
|
|
15
15
|
assert switcher.current == "chat"
|
|
16
16
|
|
|
17
|
-
# Verify sidebar menu is
|
|
18
|
-
assert app.sidebar_visible is
|
|
17
|
+
# Verify sidebar menu is hidden initially
|
|
18
|
+
assert app.sidebar_visible is False
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@pytest.mark.asyncio
|
|
@@ -23,17 +23,17 @@ async def test_tui_sidebar_toggle():
|
|
|
23
23
|
"""Test sidebar visibility toggles on ctrl+b keystroke."""
|
|
24
24
|
app = MemoryOSTUIApp()
|
|
25
25
|
async with app.run_test() as pilot:
|
|
26
|
-
assert app.sidebar_visible is
|
|
26
|
+
assert app.sidebar_visible is False
|
|
27
27
|
|
|
28
28
|
# Send ctrl+b
|
|
29
29
|
await pilot.press("ctrl+b")
|
|
30
30
|
await pilot.pause()
|
|
31
|
-
assert app.sidebar_visible is
|
|
31
|
+
assert app.sidebar_visible is True
|
|
32
32
|
|
|
33
33
|
# Send ctrl+b again
|
|
34
34
|
await pilot.press("ctrl+b")
|
|
35
35
|
await pilot.pause()
|
|
36
|
-
assert app.sidebar_visible is
|
|
36
|
+
assert app.sidebar_visible is False
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
@pytest.mark.asyncio
|
|
@@ -47,39 +47,39 @@ async def test_tui_view_switching():
|
|
|
47
47
|
# Unfocus input before pressing hotkeys
|
|
48
48
|
app.set_focus(None)
|
|
49
49
|
|
|
50
|
-
# Press '
|
|
50
|
+
# Press 'ctrl+2' to go to Sync
|
|
51
51
|
app.set_focus(None)
|
|
52
|
-
await pilot.press("
|
|
52
|
+
await pilot.press("ctrl+2")
|
|
53
53
|
assert switcher.current == "sync"
|
|
54
54
|
|
|
55
|
-
# Press '
|
|
55
|
+
# Press 'ctrl+3' to go to Doctor
|
|
56
56
|
app.set_focus(None)
|
|
57
|
-
await pilot.press("
|
|
57
|
+
await pilot.press("ctrl+3")
|
|
58
58
|
assert switcher.current == "doctor"
|
|
59
59
|
|
|
60
|
-
# Press '
|
|
60
|
+
# Press 'ctrl+4' to go to Graph
|
|
61
61
|
app.set_focus(None)
|
|
62
|
-
await pilot.press("
|
|
62
|
+
await pilot.press("ctrl+4")
|
|
63
63
|
assert switcher.current == "graph"
|
|
64
64
|
|
|
65
|
-
# Press '
|
|
65
|
+
# Press 'ctrl+5' to go to Search
|
|
66
66
|
app.set_focus(None)
|
|
67
|
-
await pilot.press("
|
|
67
|
+
await pilot.press("ctrl+5")
|
|
68
68
|
assert switcher.current == "search"
|
|
69
69
|
|
|
70
|
-
# Press '
|
|
70
|
+
# Press 'ctrl+6' to go to Monitor
|
|
71
71
|
app.set_focus(None)
|
|
72
|
-
await pilot.press("
|
|
72
|
+
await pilot.press("ctrl+6")
|
|
73
73
|
assert switcher.current == "monitor"
|
|
74
74
|
|
|
75
|
-
# Press '
|
|
75
|
+
# Press 'ctrl+7' to go to Settings
|
|
76
76
|
app.set_focus(None)
|
|
77
|
-
await pilot.press("
|
|
77
|
+
await pilot.press("ctrl+7")
|
|
78
78
|
assert switcher.current == "settings"
|
|
79
79
|
|
|
80
|
-
# Press '
|
|
80
|
+
# Press 'ctrl+1' to return to Chat
|
|
81
81
|
app.set_focus(None)
|
|
82
|
-
await pilot.press("
|
|
82
|
+
await pilot.press("ctrl+1")
|
|
83
83
|
assert switcher.current == "chat"
|
|
84
84
|
|
|
85
85
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|