Compare commits
2 Commits
51029d4a2d
...
1412ced7a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 1412ced7a8 | |||
| 54241a9e4e |
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import litellm
|
import litellm
|
||||||
from prompt_toolkit import PromptSession
|
from prompt_toolkit import PromptSession
|
||||||
|
from prompt_toolkit.completion import WordCompleter
|
||||||
from prompt_toolkit.history import FileHistory
|
from prompt_toolkit.history import FileHistory
|
||||||
|
|
||||||
from pyra.chat.history import ConversationHistory
|
from pyra.chat.history import ConversationHistory
|
||||||
@@ -159,12 +160,15 @@ def start_chat() -> None:
|
|||||||
))
|
))
|
||||||
|
|
||||||
history = ConversationHistory(cfg, registry)
|
history = ConversationHistory(cfg, registry)
|
||||||
session: PromptSession = PromptSession(
|
|
||||||
history=FileHistory(str(_HISTORY_FILE)),
|
|
||||||
multiline=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
plugin_slash = registry.get_slash_commands()
|
plugin_slash = registry.get_slash_commands()
|
||||||
|
all_commands = list(_STATIC_COMMANDS) + list(plugin_slash)
|
||||||
|
session: PromptSession = PromptSession(
|
||||||
|
history=FileHistory(str(_HISTORY_FILE)),
|
||||||
|
completer=WordCompleter(all_commands, sentence=True),
|
||||||
|
complete_while_typing=False,
|
||||||
|
multiline=False,
|
||||||
|
)
|
||||||
|
|
||||||
provider = get_provider(cfg.ai.provider_id)
|
provider = get_provider(cfg.ai.provider_id)
|
||||||
render_system(
|
render_system(
|
||||||
|
|||||||
+69
-18
@@ -7,7 +7,7 @@ from textual.binding import Binding
|
|||||||
from textual.containers import Horizontal, VerticalScroll
|
from textual.containers import Horizontal, VerticalScroll
|
||||||
from textual.coordinate import Coordinate
|
from textual.coordinate import Coordinate
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, DataTable, Input, Label, Switch, TabbedContent, TabPane
|
from textual.widgets import Button, DataTable, Footer, Header, Input, Label, Switch, TabbedContent, TabPane
|
||||||
|
|
||||||
from pyra.config.manager import load_config, save_config
|
from pyra.config.manager import load_config, save_config
|
||||||
from pyra.plugins.base import BasePlugin, ConfigField
|
from pyra.plugins.base import BasePlugin, ConfigField
|
||||||
@@ -67,10 +67,11 @@ def _pfid(plugin_name: str, key: str) -> str:
|
|||||||
|
|
||||||
# ── Tab widgets ───────────────────────────────────────────────────────────────
|
# ── Tab widgets ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class _GeneralTab(Widget):
|
class _GeneralTab(VerticalScroll):
|
||||||
|
BINDINGS = [Binding("ctrl+s", "save", "Save")]
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
with VerticalScroll():
|
|
||||||
for f in GENERAL_FIELDS:
|
for f in GENERAL_FIELDS:
|
||||||
current = _get_nested(cfg, f.path)
|
current = _get_nested(cfg, f.path)
|
||||||
with Horizontal(classes="row"):
|
with Horizontal(classes="row"):
|
||||||
@@ -82,9 +83,16 @@ class _GeneralTab(Widget):
|
|||||||
with Horizontal(classes="actions"):
|
with Horizontal(classes="actions"):
|
||||||
yield Button("Save", id="save-general", variant="primary")
|
yield Button("Save", id="save-general", variant="primary")
|
||||||
|
|
||||||
|
def action_save(self) -> None:
|
||||||
|
self._do_save()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
if event.button.id != "save-general":
|
if event.button.id != "save-general":
|
||||||
return
|
return
|
||||||
|
self._do_save()
|
||||||
|
event.stop()
|
||||||
|
|
||||||
|
def _do_save(self) -> None:
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
for f in GENERAL_FIELDS:
|
for f in GENERAL_FIELDS:
|
||||||
wid = _fid(f.path)
|
wid = _fid(f.path)
|
||||||
@@ -95,10 +103,15 @@ class _GeneralTab(Widget):
|
|||||||
_set_nested(cfg, f.path, cfg_val)
|
_set_nested(cfg, f.path, cfg_val)
|
||||||
save_config(cfg)
|
save_config(cfg)
|
||||||
self.app.notify("General settings saved.")
|
self.app.notify("General settings saved.")
|
||||||
event.stop()
|
|
||||||
|
|
||||||
|
|
||||||
class _PluginsTab(Widget):
|
class _PluginsTab(Widget):
|
||||||
|
DEFAULT_CSS = "_PluginsTab { height: 1fr; width: 1fr; }"
|
||||||
|
BINDINGS = [
|
||||||
|
Binding("e", "enable_plugin", "Enable"),
|
||||||
|
Binding("d", "disable_plugin", "Disable"),
|
||||||
|
]
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
enabled = set(cfg.plugins.enabled)
|
enabled = set(cfg.plugins.enabled)
|
||||||
@@ -114,26 +127,35 @@ class _PluginsTab(Widget):
|
|||||||
)
|
)
|
||||||
yield table
|
yield table
|
||||||
with Horizontal(classes="actions"):
|
with Horizontal(classes="actions"):
|
||||||
yield Button("Enable", id="btn-enable", variant="success")
|
yield Button("Enable [e]", id="btn-enable", variant="success")
|
||||||
yield Button("Disable", id="btn-disable")
|
yield Button("Disable [d]", id="btn-disable")
|
||||||
|
|
||||||
|
def action_enable_plugin(self) -> None:
|
||||||
|
self._toggle_plugin("enable")
|
||||||
|
|
||||||
|
def action_disable_plugin(self) -> None:
|
||||||
|
self._toggle_plugin("disable")
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
btn_id = event.button.id
|
if event.button.id == "btn-enable":
|
||||||
if btn_id not in ("btn-enable", "btn-disable"):
|
self._toggle_plugin("enable")
|
||||||
return
|
event.stop()
|
||||||
|
elif event.button.id == "btn-disable":
|
||||||
|
self._toggle_plugin("disable")
|
||||||
|
event.stop()
|
||||||
|
|
||||||
|
def _toggle_plugin(self, action: str) -> None:
|
||||||
table = self.query_one("#plugins-table", DataTable)
|
table = self.query_one("#plugins-table", DataTable)
|
||||||
if table.row_count == 0:
|
if table.row_count == 0:
|
||||||
event.stop()
|
|
||||||
return
|
return
|
||||||
plugin_name = str(table.get_cell_at(Coordinate(table.cursor_coordinate.row, 0)))
|
plugin_name = str(table.get_cell_at(Coordinate(table.cursor_coordinate.row, 0)))
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
if btn_id == "btn-enable" and plugin_name not in cfg.plugins.enabled:
|
if action == "enable" and plugin_name not in cfg.plugins.enabled:
|
||||||
cfg.plugins.enabled.append(plugin_name)
|
cfg.plugins.enabled.append(plugin_name)
|
||||||
elif btn_id == "btn-disable" and plugin_name in cfg.plugins.enabled:
|
elif action == "disable" and plugin_name in cfg.plugins.enabled:
|
||||||
cfg.plugins.enabled.remove(plugin_name)
|
cfg.plugins.enabled.remove(plugin_name)
|
||||||
save_config(cfg)
|
save_config(cfg)
|
||||||
self._refresh_table()
|
self._refresh_table()
|
||||||
event.stop()
|
|
||||||
|
|
||||||
def _refresh_table(self) -> None:
|
def _refresh_table(self) -> None:
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
@@ -150,7 +172,9 @@ class _PluginsTab(Widget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class _PluginConfigTab(Widget):
|
class _PluginConfigTab(VerticalScroll):
|
||||||
|
BINDINGS = [Binding("ctrl+s", "save", "Save")]
|
||||||
|
|
||||||
def __init__(self, name: str, plugin: Any) -> None:
|
def __init__(self, name: str, plugin: Any) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._name = name
|
self._name = name
|
||||||
@@ -159,7 +183,6 @@ class _PluginConfigTab(Widget):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
settings = cfg.plugin_settings.get(self._name, {})
|
settings = cfg.plugin_settings.get(self._name, {})
|
||||||
with VerticalScroll():
|
|
||||||
for f in self._plugin.config_fields():
|
for f in self._plugin.config_fields():
|
||||||
current = settings.get(f.key, f.default)
|
current = settings.get(f.key, f.default)
|
||||||
with Horizontal(classes="row"):
|
with Horizontal(classes="row"):
|
||||||
@@ -173,9 +196,16 @@ class _PluginConfigTab(Widget):
|
|||||||
with Horizontal(classes="actions"):
|
with Horizontal(classes="actions"):
|
||||||
yield Button("Save", id=f"save-{self._name}", variant="primary")
|
yield Button("Save", id=f"save-{self._name}", variant="primary")
|
||||||
|
|
||||||
|
def action_save(self) -> None:
|
||||||
|
self._do_save()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
if event.button.id != f"save-{self._name}":
|
if event.button.id != f"save-{self._name}":
|
||||||
return
|
return
|
||||||
|
self._do_save()
|
||||||
|
event.stop()
|
||||||
|
|
||||||
|
def _do_save(self) -> None:
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
settings: dict[str, Any] = dict(cfg.plugin_settings.get(self._name, {}))
|
settings: dict[str, Any] = dict(cfg.plugin_settings.get(self._name, {}))
|
||||||
for f in self._plugin.config_fields():
|
for f in self._plugin.config_fields():
|
||||||
@@ -187,7 +217,6 @@ class _PluginConfigTab(Widget):
|
|||||||
cfg.plugin_settings[self._name] = settings
|
cfg.plugin_settings[self._name] = settings
|
||||||
save_config(cfg)
|
save_config(cfg)
|
||||||
self.app.notify(f"{self._name} settings saved.")
|
self.app.notify(f"{self._name} settings saved.")
|
||||||
event.stop()
|
|
||||||
|
|
||||||
|
|
||||||
# ── App ───────────────────────────────────────────────────────────────────────
|
# ── App ───────────────────────────────────────────────────────────────────────
|
||||||
@@ -196,18 +225,21 @@ class ConfigApp(App):
|
|||||||
TITLE = "Pyra Configuration"
|
TITLE = "Pyra Configuration"
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
Binding("q", "quit", "Quit"),
|
Binding("q", "quit", "Quit"),
|
||||||
Binding("escape", "quit", "Quit"),
|
Binding("escape", "quit", "Quit", show=False),
|
||||||
|
Binding("ctrl+right", "next_tab", "Next tab"),
|
||||||
|
Binding("ctrl+left", "prev_tab", "Prev tab"),
|
||||||
]
|
]
|
||||||
CSS = """
|
CSS = """
|
||||||
Screen { background: $surface; }
|
Screen { background: $surface; }
|
||||||
.row { height: 3; margin: 0 2; }
|
.row { height: 3; margin: 0 2; }
|
||||||
.row Label { width: 26; content-align: left middle; }
|
.row Label { width: 26; content-align: left middle; }
|
||||||
.hint { color: $text-muted; margin: 0 2 1 28; }
|
.hint { color: $foreground 50%; margin: 0 2 1 28; }
|
||||||
.actions { height: 3; align: right middle; margin: 1 2; }
|
.actions { height: 3; align: right middle; margin: 1 2; }
|
||||||
DataTable { height: 1fr; }
|
DataTable { height: 1fr; }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Header()
|
||||||
plugins = _installed_plugins()
|
plugins = _installed_plugins()
|
||||||
with TabbedContent():
|
with TabbedContent():
|
||||||
with TabPane("General"):
|
with TabPane("General"):
|
||||||
@@ -218,6 +250,25 @@ class ConfigApp(App):
|
|||||||
if plugin is not None and plugin.config_fields():
|
if plugin is not None and plugin.config_fields():
|
||||||
with TabPane(name):
|
with TabPane(name):
|
||||||
yield _PluginConfigTab(name, plugin)
|
yield _PluginConfigTab(name, plugin)
|
||||||
|
yield Footer()
|
||||||
|
|
||||||
|
def action_next_tab(self) -> None:
|
||||||
|
tc = self.query_one(TabbedContent)
|
||||||
|
panes = list(tc.query("TabPane"))
|
||||||
|
ids = [p.id for p in panes]
|
||||||
|
try:
|
||||||
|
tc.active = ids[(ids.index(tc.active) + 1) % len(ids)]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def action_prev_tab(self) -> None:
|
||||||
|
tc = self.query_one(TabbedContent)
|
||||||
|
panes = list(tc.query("TabPane"))
|
||||||
|
ids = [p.id for p in panes]
|
||||||
|
try:
|
||||||
|
tc.active = ids[(ids.index(tc.active) - 1) % len(ids)]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def launch_config_tui() -> None:
|
def launch_config_tui() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user