kader 0.1.4__tar.gz → 0.1.5__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.
- {kader-0.1.4 → kader-0.1.5}/PKG-INFO +1 -1
- {kader-0.1.4 → kader-0.1.5}/cli/app.py +59 -25
- {kader-0.1.4 → kader-0.1.5}/cli/app.tcss +9 -10
- {kader-0.1.4 → kader-0.1.5}/cli/utils.py +13 -8
- {kader-0.1.4 → kader-0.1.5}/cli/widgets/confirmation.py +5 -5
- {kader-0.1.4 → kader-0.1.5}/cli/widgets/conversation.py +1 -1
- {kader-0.1.4 → kader-0.1.5}/cli/widgets/loading.py +1 -1
- {kader-0.1.4 → kader-0.1.5}/pyproject.toml +1 -1
- {kader-0.1.4 → kader-0.1.5}/uv.lock +1 -1
- {kader-0.1.4 → kader-0.1.5}/.github/workflows/ci.yml +0 -0
- {kader-0.1.4 → kader-0.1.5}/.github/workflows/release.yml +0 -0
- {kader-0.1.4 → kader-0.1.5}/.gitignore +0 -0
- {kader-0.1.4 → kader-0.1.5}/.python-version +0 -0
- {kader-0.1.4 → kader-0.1.5}/.qwen/QWEN.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/.qwen/agents/technical-writer.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/.qwen/agents/test-automation-specialist.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/README.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/cli/README.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/cli/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/cli/__main__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/cli/widgets/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/.gitignore +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/README.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/memory_example.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/ollama_example.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/planning_agent_example.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/python_developer/main.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/python_developer/template.yaml +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/react_agent_example.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/simple_agent.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/todo_agent/main.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/examples/tools_example.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/agent/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/agent/agents.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/agent/base.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/agent/logger.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/config.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/memory/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/memory/conversation.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/memory/session.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/memory/state.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/memory/types.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/prompts/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/prompts/agent_prompts.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/prompts/base.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/prompts/templates/planning_agent.j2 +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/prompts/templates/react_agent.j2 +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/providers/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/providers/base.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/providers/mock.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/providers/ollama.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/README.md +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/__init__.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/base.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/exec_commands.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/filesys.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/filesystem.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/protocol.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/rag.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/todo.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/utils.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/kader/tools/web.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/conftest.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/providers/test_mock.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/providers/test_ollama.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/providers/test_providers_base.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/test_agent_logger.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/test_agent_logger_integration.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/test_base_agent.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/test_file_memory.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/test_todo_tool.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/tools/test_exec_commands.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/tools/test_filesys_tools.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/tools/test_filesystem_tools.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/tools/test_rag.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/tools/test_tools_base.py +0 -0
- {kader-0.1.4 → kader-0.1.5}/tests/tools/test_web.py +0 -0
|
@@ -10,12 +10,12 @@ from textual.app import App, ComposeResult
|
|
|
10
10
|
from textual.binding import Binding
|
|
11
11
|
from textual.containers import Container, Horizontal, Vertical
|
|
12
12
|
from textual.widgets import (
|
|
13
|
-
DirectoryTree,
|
|
14
13
|
Footer,
|
|
15
14
|
Header,
|
|
16
15
|
Input,
|
|
17
16
|
Markdown,
|
|
18
17
|
Static,
|
|
18
|
+
Tree,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
from kader.agent.agents import ReActAgent
|
|
@@ -66,11 +66,18 @@ MIN_WIDTH = 89
|
|
|
66
66
|
MIN_HEIGHT = 29
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
class ASCIITree(Tree):
|
|
70
|
+
"""A Tree widget that uses no icons."""
|
|
71
|
+
|
|
72
|
+
ICON_NODE = ""
|
|
73
|
+
ICON_NODE_EXPANDED = ""
|
|
74
|
+
|
|
75
|
+
|
|
69
76
|
class KaderApp(App):
|
|
70
77
|
"""Main Kader CLI application."""
|
|
71
78
|
|
|
72
79
|
TITLE = "Kader CLI"
|
|
73
|
-
SUB_TITLE = "
|
|
80
|
+
SUB_TITLE = f"v{get_version('kader')}"
|
|
74
81
|
CSS_PATH = "app.tcss"
|
|
75
82
|
|
|
76
83
|
BINDINGS = [
|
|
@@ -180,7 +187,7 @@ class KaderApp(App):
|
|
|
180
187
|
if event.confirmed:
|
|
181
188
|
if tool_message:
|
|
182
189
|
conversation.add_message(tool_message, "assistant")
|
|
183
|
-
conversation.add_message("
|
|
190
|
+
conversation.add_message("(+) Executing tool...", "assistant")
|
|
184
191
|
# Restart spinner
|
|
185
192
|
try:
|
|
186
193
|
spinner = self.query_one(LoadingSpinner)
|
|
@@ -188,7 +195,7 @@ class KaderApp(App):
|
|
|
188
195
|
except Exception:
|
|
189
196
|
pass
|
|
190
197
|
else:
|
|
191
|
-
conversation.add_message("
|
|
198
|
+
conversation.add_message("(-) Tool execution skipped.", "assistant")
|
|
192
199
|
|
|
193
200
|
# Re-enable input
|
|
194
201
|
prompt_input = self.query_one("#prompt-input", Input)
|
|
@@ -210,7 +217,8 @@ class KaderApp(App):
|
|
|
210
217
|
models = OllamaProvider.get_supported_models()
|
|
211
218
|
if not models:
|
|
212
219
|
conversation.add_message(
|
|
213
|
-
"## Models
|
|
220
|
+
"## Models (^^)\n\n*No models found. Is Ollama running?*",
|
|
221
|
+
"assistant",
|
|
214
222
|
)
|
|
215
223
|
return
|
|
216
224
|
|
|
@@ -228,7 +236,7 @@ class KaderApp(App):
|
|
|
228
236
|
|
|
229
237
|
except Exception as e:
|
|
230
238
|
conversation.add_message(
|
|
231
|
-
f"## Models
|
|
239
|
+
f"## Models (^^)\n\n*Error fetching models: {e}*", "assistant"
|
|
232
240
|
)
|
|
233
241
|
|
|
234
242
|
def on_model_selector_model_selected(
|
|
@@ -248,7 +256,7 @@ class KaderApp(App):
|
|
|
248
256
|
self._agent = self._create_agent(self._current_model)
|
|
249
257
|
|
|
250
258
|
conversation.add_message(
|
|
251
|
-
f"
|
|
259
|
+
f"(+) Model changed from `{old_model}` to `{self._current_model}`",
|
|
252
260
|
"assistant",
|
|
253
261
|
)
|
|
254
262
|
|
|
@@ -285,8 +293,8 @@ class KaderApp(App):
|
|
|
285
293
|
with Horizontal(id="main-container"):
|
|
286
294
|
# Sidebar with directory tree
|
|
287
295
|
with Vertical(id="sidebar"):
|
|
288
|
-
yield Static("
|
|
289
|
-
yield
|
|
296
|
+
yield Static("Files", id="sidebar-title")
|
|
297
|
+
yield ASCIITree(str(Path.cwd().name), id="directory-tree")
|
|
290
298
|
|
|
291
299
|
# Main content area
|
|
292
300
|
with Vertical(id="content-area"):
|
|
@@ -316,9 +324,32 @@ class KaderApp(App):
|
|
|
316
324
|
# Check initial size
|
|
317
325
|
self._check_terminal_size()
|
|
318
326
|
|
|
327
|
+
# Start background update check
|
|
319
328
|
# Start background update check
|
|
320
329
|
threading.Thread(target=self._check_for_updates, daemon=True).start()
|
|
321
330
|
|
|
331
|
+
# Initial tree population
|
|
332
|
+
self._refresh_directory_tree()
|
|
333
|
+
|
|
334
|
+
def _populate_tree(self, node, path: Path) -> None:
|
|
335
|
+
"""Recursively populate the tree with ASCII symbols."""
|
|
336
|
+
try:
|
|
337
|
+
# Sort: directories first, then files
|
|
338
|
+
items = sorted(
|
|
339
|
+
path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())
|
|
340
|
+
)
|
|
341
|
+
for child in items:
|
|
342
|
+
if child.name.startswith((".", "__pycache__")):
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
if child.is_dir():
|
|
346
|
+
new_node = node.add(f"[+] {child.name}", expand=False)
|
|
347
|
+
self._populate_tree(new_node, child)
|
|
348
|
+
else:
|
|
349
|
+
node.add(f"{child.name}")
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
352
|
+
|
|
322
353
|
def _check_for_updates(self) -> None:
|
|
323
354
|
"""Check for package updates in background thread."""
|
|
324
355
|
try:
|
|
@@ -369,7 +400,7 @@ class KaderApp(App):
|
|
|
369
400
|
except Exception:
|
|
370
401
|
if too_small:
|
|
371
402
|
# Show warning overlay
|
|
372
|
-
warning_text = f"""
|
|
403
|
+
warning_text = f"""<!> Terminal Too Small
|
|
373
404
|
|
|
374
405
|
Current: {width}x{height}
|
|
375
406
|
Minimum: {MIN_WIDTH}x{MIN_HEIGHT}
|
|
@@ -406,7 +437,7 @@ Please resize your terminal."""
|
|
|
406
437
|
self._cycle_theme()
|
|
407
438
|
theme_name = THEME_NAMES[self._current_theme_index]
|
|
408
439
|
conversation.add_message(
|
|
409
|
-
f"
|
|
440
|
+
f"{{~}} Theme changed to **{theme_name}**!", "assistant"
|
|
410
441
|
)
|
|
411
442
|
elif cmd == "/clear":
|
|
412
443
|
conversation.clear_messages()
|
|
@@ -482,7 +513,7 @@ Please resize your terminal."""
|
|
|
482
513
|
|
|
483
514
|
except Exception as e:
|
|
484
515
|
spinner.stop()
|
|
485
|
-
error_msg = f"
|
|
516
|
+
error_msg = f"(-) **Error:** {str(e)}\n\nMake sure Ollama is running and the model `{self._current_model}` is available."
|
|
486
517
|
conversation.add_message(error_msg, "assistant")
|
|
487
518
|
self.notify(f"Error: {e}", severity="error")
|
|
488
519
|
|
|
@@ -530,10 +561,13 @@ Please resize your terminal."""
|
|
|
530
561
|
self.notify("Directory tree refreshed!", severity="information")
|
|
531
562
|
|
|
532
563
|
def _refresh_directory_tree(self) -> None:
|
|
533
|
-
"""Refresh the directory tree
|
|
564
|
+
"""Refresh the directory tree with ASCII symbols."""
|
|
534
565
|
try:
|
|
535
|
-
tree = self.query_one("#directory-tree",
|
|
536
|
-
tree.
|
|
566
|
+
tree = self.query_one("#directory-tree", ASCIITree)
|
|
567
|
+
tree.clear()
|
|
568
|
+
tree.root.label = str(Path.cwd().name)
|
|
569
|
+
self._populate_tree(tree.root, Path.cwd())
|
|
570
|
+
tree.root.expand()
|
|
537
571
|
except Exception:
|
|
538
572
|
pass # Silently ignore if tree not found
|
|
539
573
|
|
|
@@ -550,12 +584,12 @@ Please resize your terminal."""
|
|
|
550
584
|
self._session_manager.save_conversation(self._current_session_id, messages)
|
|
551
585
|
|
|
552
586
|
conversation.add_message(
|
|
553
|
-
f"
|
|
587
|
+
f"(+) Session saved!\n\n**Session ID:** `{self._current_session_id}`",
|
|
554
588
|
"assistant",
|
|
555
589
|
)
|
|
556
590
|
self.notify("Session saved!", severity="information")
|
|
557
591
|
except Exception as e:
|
|
558
|
-
conversation.add_message(f"
|
|
592
|
+
conversation.add_message(f"(-) Error saving session: {e}", "assistant")
|
|
559
593
|
self.notify(f"Error: {e}", severity="error")
|
|
560
594
|
|
|
561
595
|
def _handle_load_session(
|
|
@@ -567,7 +601,7 @@ Please resize your terminal."""
|
|
|
567
601
|
session = self._session_manager.get_session(session_id)
|
|
568
602
|
if not session:
|
|
569
603
|
conversation.add_message(
|
|
570
|
-
f"
|
|
604
|
+
f"(-) Session `{session_id}` not found.\n\nUse `/sessions` to see available sessions.",
|
|
571
605
|
"assistant",
|
|
572
606
|
)
|
|
573
607
|
return
|
|
@@ -589,12 +623,12 @@ Please resize your terminal."""
|
|
|
589
623
|
|
|
590
624
|
self._current_session_id = session_id
|
|
591
625
|
conversation.add_message(
|
|
592
|
-
f"
|
|
626
|
+
f"(+) Session `{session_id}` loaded with {len(messages)} messages.",
|
|
593
627
|
"assistant",
|
|
594
628
|
)
|
|
595
629
|
self.notify("Session loaded!", severity="information")
|
|
596
630
|
except Exception as e:
|
|
597
|
-
conversation.add_message(f"
|
|
631
|
+
conversation.add_message(f"(-) Error loading session: {e}", "assistant")
|
|
598
632
|
self.notify(f"Error: {e}", severity="error")
|
|
599
633
|
|
|
600
634
|
def _handle_list_sessions(self, conversation: ConversationView) -> None:
|
|
@@ -604,13 +638,13 @@ Please resize your terminal."""
|
|
|
604
638
|
|
|
605
639
|
if not sessions:
|
|
606
640
|
conversation.add_message(
|
|
607
|
-
"
|
|
641
|
+
"[ ] No saved sessions found.\n\nUse `/save` to save the current session.",
|
|
608
642
|
"assistant",
|
|
609
643
|
)
|
|
610
644
|
return
|
|
611
645
|
|
|
612
646
|
lines = [
|
|
613
|
-
"## Saved Sessions
|
|
647
|
+
"## Saved Sessions [=]\n",
|
|
614
648
|
"| Session ID | Created | Updated |",
|
|
615
649
|
"|------------|---------|---------|",
|
|
616
650
|
]
|
|
@@ -635,7 +669,7 @@ Please resize your terminal."""
|
|
|
635
669
|
model = self._agent.provider.model
|
|
636
670
|
|
|
637
671
|
lines = [
|
|
638
|
-
"## Usage Costs
|
|
672
|
+
"## Usage Costs ($)\n",
|
|
639
673
|
f"**Model:** `{model}`\n",
|
|
640
674
|
"### Cost Breakdown",
|
|
641
675
|
"| Type | Amount |",
|
|
@@ -654,12 +688,12 @@ Please resize your terminal."""
|
|
|
654
688
|
|
|
655
689
|
if cost.total_cost == 0.0:
|
|
656
690
|
lines.append(
|
|
657
|
-
"\n>
|
|
691
|
+
"\n> (!) *Note: Ollama runs locally, so there are no API costs.*"
|
|
658
692
|
)
|
|
659
693
|
|
|
660
694
|
conversation.add_message("\n".join(lines), "assistant")
|
|
661
695
|
except Exception as e:
|
|
662
|
-
conversation.add_message(f"
|
|
696
|
+
conversation.add_message(f"(-) Error getting costs: {e}", "assistant")
|
|
663
697
|
self.notify(f"Error: {e}", severity="error")
|
|
664
698
|
|
|
665
699
|
|
|
@@ -42,7 +42,7 @@ Header {
|
|
|
42
42
|
background: $primary;
|
|
43
43
|
color: $text;
|
|
44
44
|
text-style: bold;
|
|
45
|
-
height:
|
|
45
|
+
height: 1;
|
|
46
46
|
dock: top;
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -75,9 +75,9 @@ FooterKey > .footer-key--key {
|
|
|
75
75
|
/* ===== Sidebar (Directory Tree) ===== */
|
|
76
76
|
|
|
77
77
|
#sidebar {
|
|
78
|
-
width:
|
|
79
|
-
min-width:
|
|
80
|
-
max-width:
|
|
78
|
+
width: 22;
|
|
79
|
+
min-width: 18;
|
|
80
|
+
max-width: 35;
|
|
81
81
|
background: $surface;
|
|
82
82
|
border-right: thick $primary;
|
|
83
83
|
padding: 0;
|
|
@@ -126,7 +126,6 @@ DirectoryTree:focus > .directory-tree--cursor {
|
|
|
126
126
|
#conversation {
|
|
127
127
|
height: 1fr;
|
|
128
128
|
background: $background;
|
|
129
|
-
border-bottom: thick $surface;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
131
|
ConversationView {
|
|
@@ -148,15 +147,15 @@ ConversationView {
|
|
|
148
147
|
|
|
149
148
|
#input-container {
|
|
150
149
|
height: auto;
|
|
151
|
-
min-height:
|
|
152
|
-
max-height:
|
|
153
|
-
background: $
|
|
150
|
+
min-height: 4;
|
|
151
|
+
max-height: 7;
|
|
152
|
+
background: $background;
|
|
154
153
|
padding: 1;
|
|
155
|
-
|
|
154
|
+
margin-bottom: 1;
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
#prompt-input {
|
|
159
|
-
background:
|
|
158
|
+
background: transparent;
|
|
160
159
|
border: round $primary;
|
|
161
160
|
padding: 0 1;
|
|
162
161
|
height: 3;
|
|
@@ -8,7 +8,7 @@ THEME_NAMES = ["dark", "ocean", "forest", "sunset"]
|
|
|
8
8
|
# Default model
|
|
9
9
|
DEFAULT_MODEL = "qwen3-coder:480b-cloud"
|
|
10
10
|
|
|
11
|
-
HELP_TEXT = """## Kader CLI Commands
|
|
11
|
+
HELP_TEXT = """## Kader CLI Commands
|
|
12
12
|
|
|
13
13
|
| Command | Description |
|
|
14
14
|
|---------|-------------|
|
|
@@ -23,7 +23,7 @@ HELP_TEXT = """## Kader CLI Commands 📖
|
|
|
23
23
|
| `/refresh` | Refresh file tree |
|
|
24
24
|
| `/exit` | Exit the CLI |
|
|
25
25
|
|
|
26
|
-
### Keyboard Shortcuts
|
|
26
|
+
### Keyboard Shortcuts
|
|
27
27
|
|
|
28
28
|
| Shortcut | Action |
|
|
29
29
|
|----------|--------|
|
|
@@ -33,7 +33,7 @@ HELP_TEXT = """## Kader CLI Commands 📖
|
|
|
33
33
|
| `Ctrl+R` | Refresh file tree |
|
|
34
34
|
| `Ctrl+Q` | Quit |
|
|
35
35
|
|
|
36
|
-
### Input Editing
|
|
36
|
+
### Input Editing
|
|
37
37
|
|
|
38
38
|
| Shortcut | Action |
|
|
39
39
|
|----------|--------|
|
|
@@ -44,7 +44,8 @@ HELP_TEXT = """## Kader CLI Commands 📖
|
|
|
44
44
|
|
|
45
45
|
### Tips:
|
|
46
46
|
- Type any question to chat with the AI
|
|
47
|
-
- Use **Tab** to navigate between panels
|
|
47
|
+
- Use **Tab** to navigate between panels
|
|
48
|
+
"""
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def get_models_text() -> str:
|
|
@@ -52,12 +53,16 @@ def get_models_text() -> str:
|
|
|
52
53
|
try:
|
|
53
54
|
models = OllamaProvider.get_supported_models()
|
|
54
55
|
if not models:
|
|
55
|
-
return "## Available Models
|
|
56
|
+
return "## Available Models (^^)\n\n*No models found. Is Ollama running?*"
|
|
56
57
|
|
|
57
|
-
lines = [
|
|
58
|
+
lines = [
|
|
59
|
+
"## Available Models (^^)\n",
|
|
60
|
+
"| Model | Status |",
|
|
61
|
+
"|-------|--------|",
|
|
62
|
+
]
|
|
58
63
|
for model in models:
|
|
59
|
-
lines.append(f"| {model} |
|
|
64
|
+
lines.append(f"| {model} | (+) Available |")
|
|
60
65
|
lines.append(f"\n*Currently using: **{DEFAULT_MODEL}***")
|
|
61
66
|
return "\n".join(lines)
|
|
62
67
|
except Exception as e:
|
|
63
|
-
return f"## Available Models
|
|
68
|
+
return f"## Available Models (^^)\n\n*Error fetching models: {e}*"
|
|
@@ -84,12 +84,12 @@ class InlineSelector(Widget, can_focus=True):
|
|
|
84
84
|
def __init__(self, message: str, options: list[str] = None, **kwargs) -> None:
|
|
85
85
|
super().__init__(**kwargs)
|
|
86
86
|
self.message = message
|
|
87
|
-
self.options = options or ["
|
|
87
|
+
self.options = options or ["(+) Yes", "(-) No"]
|
|
88
88
|
|
|
89
89
|
def compose(self) -> ComposeResult:
|
|
90
90
|
from textual.containers import Horizontal
|
|
91
91
|
|
|
92
|
-
yield Static(f"
|
|
92
|
+
yield Static(f">_ {self.message}", classes="message-text")
|
|
93
93
|
yield Static(
|
|
94
94
|
"↑↓ to select • Enter to confirm • Y/N for quick select",
|
|
95
95
|
classes="prompt-text",
|
|
@@ -230,7 +230,7 @@ class ModelSelector(Widget, can_focus=True):
|
|
|
230
230
|
self.selected_index = models.index(current_model)
|
|
231
231
|
|
|
232
232
|
def compose(self) -> ComposeResult:
|
|
233
|
-
yield Static("
|
|
233
|
+
yield Static("(^^) Select Model", classes="title-text")
|
|
234
234
|
yield Static(
|
|
235
235
|
"↑↓ to navigate • Enter to select • Esc to cancel", classes="prompt-text"
|
|
236
236
|
)
|
|
@@ -245,7 +245,7 @@ class ModelSelector(Widget, can_focus=True):
|
|
|
245
245
|
classes += " not-selected"
|
|
246
246
|
if is_current:
|
|
247
247
|
classes += " current"
|
|
248
|
-
label = f"
|
|
248
|
+
label = f" >> {model}" if is_selected else f" {model}"
|
|
249
249
|
if is_current:
|
|
250
250
|
label += " (current)"
|
|
251
251
|
yield Static(label, classes=classes, id=f"model-{i}")
|
|
@@ -272,7 +272,7 @@ class ModelSelector(Widget, can_focus=True):
|
|
|
272
272
|
new_option.remove_class("not-selected")
|
|
273
273
|
new_option.add_class("selected")
|
|
274
274
|
new_model = self.models[new_index]
|
|
275
|
-
new_label = f"
|
|
275
|
+
new_label = f" >> {new_model}"
|
|
276
276
|
if new_model == self.current_model:
|
|
277
277
|
new_label += " (current)"
|
|
278
278
|
new_option.update(new_label)
|
|
@@ -15,7 +15,7 @@ class Message(Static):
|
|
|
15
15
|
self.add_class(f"message-{role}")
|
|
16
16
|
|
|
17
17
|
def compose(self) -> ComposeResult:
|
|
18
|
-
prefix = "
|
|
18
|
+
prefix = "(**) **You:**" if self.role == "user" else "(^^) **Kader:**"
|
|
19
19
|
yield Markdown(f"{prefix}\n\n{self.content}")
|
|
20
20
|
|
|
21
21
|
|
|
@@ -21,7 +21,7 @@ class LoadingSpinner(Static):
|
|
|
21
21
|
}
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
SPINNER_FRAMES = ["
|
|
24
|
+
SPINNER_FRAMES = ["= ", "== ", "=== ", " ===", " ==", " =", " "]
|
|
25
25
|
|
|
26
26
|
frame_index: reactive[int] = reactive(0)
|
|
27
27
|
is_spinning: reactive[bool] = reactive(False)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|