144 lines
5.5 KiB
Python
144 lines
5.5 KiB
Python
from __future__ import annotations
|
|
from typing import TYPE_CHECKING, cast
|
|
|
|
|
|
from rich.markup import escape
|
|
from rich.text import Text
|
|
from textual import on
|
|
from textual.app import ComposeResult
|
|
from textual.binding import Binding
|
|
from textual.containers import Vertical, VerticalScroll
|
|
from textual.screen import ModalScreen
|
|
from textual.widgets import Footer, RadioSet, RadioButton, Static, TextArea
|
|
|
|
from knowledge_platform.config import ModelConfig
|
|
from knowledge_platform.locations import config_file, theme_directory
|
|
from knowledge_platform.runtime_config import RuntimeConfig
|
|
from knowledge_platform.database.database import sqlite_file_name
|
|
|
|
if TYPE_CHECKING:
|
|
from knowledge_platform.app import KnowledgePlatformApp
|
|
|
|
|
|
class ModelRadioButton(RadioButton):
|
|
def __init__(
|
|
self,
|
|
model: ModelConfig,
|
|
label: str | Text = "",
|
|
value: bool = False,
|
|
button_first: bool = True,
|
|
*,
|
|
name: str | None = None,
|
|
id: str | None = None,
|
|
classes: str | None = None,
|
|
disabled: bool = False,
|
|
) -> None:
|
|
super().__init__(
|
|
label,
|
|
value,
|
|
button_first,
|
|
name=name,
|
|
id=id,
|
|
classes=classes,
|
|
disabled=disabled,
|
|
)
|
|
self.model = model
|
|
|
|
|
|
class OptionsModal(ModalScreen[RuntimeConfig]):
|
|
BINDINGS = [
|
|
Binding("q", "app.quit", "Quit", show=False),
|
|
Binding("escape", "app.pop_screen", "Close options", key_display="esc"),
|
|
]
|
|
|
|
def __init__(
|
|
self,
|
|
name: str | None = None,
|
|
id: str | None = None,
|
|
classes: str | None = None,
|
|
) -> None:
|
|
super().__init__(name, id, classes)
|
|
self.app_ref = cast("KnowledgePlatformApp", self.app)
|
|
self.runtime_config = self.app_ref.runtime_config
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with VerticalScroll(id="form-scrollable") as vs:
|
|
vs.border_title = "Session Options"
|
|
vs.can_focus = False
|
|
with RadioSet(id="available-models") as models_rs:
|
|
selected_model = self.runtime_config.selected_model
|
|
models_rs.border_title = "Available Models"
|
|
for model in self.app_ref.launch_config.all_models:
|
|
label = f"{escape(model.display_name or model.name)}"
|
|
provider = model.provider
|
|
if provider:
|
|
label += f" [i]by[/] {provider}"
|
|
|
|
ids_defined = selected_model.id and model.id
|
|
same_id = ids_defined and selected_model.id == model.id
|
|
same_name = selected_model.name == model.name
|
|
is_selected = same_id or same_name
|
|
yield ModelRadioButton(
|
|
model=model,
|
|
value=is_selected,
|
|
label=label,
|
|
)
|
|
system_prompt_ta = TextArea(
|
|
self.runtime_config.system_prompt, id="system-prompt-ta"
|
|
)
|
|
system_prompt_ta.border_title = "System Message"
|
|
yield system_prompt_ta
|
|
with Vertical(id="xdg-info") as xdg_info:
|
|
xdg_info.border_title = "More Information"
|
|
yield Static(f"{sqlite_file_name.absolute()}\n[dim]Database[/]\n")
|
|
yield Static(f"{config_file()}\n[dim]Config[/]\n")
|
|
yield Static(f"{theme_directory()}\n[dim]Themes directory[/]")
|
|
# TODO - yield and dock a label to the bottom explaining
|
|
# that the changes made here only apply to the current session
|
|
# We can probably do better when it comes to system prompts.
|
|
# Perhaps we could store saved prompts in the database.
|
|
yield Footer()
|
|
|
|
def on_mount(self) -> None:
|
|
system_prompt_ta = self.query_one("#system-prompt-ta", TextArea)
|
|
selected_model_rs = self.query_one("#available-models", RadioSet)
|
|
self.apply_overridden_subtitles(system_prompt_ta, selected_model_rs)
|
|
|
|
@on(RadioSet.Changed)
|
|
@on(TextArea.Changed)
|
|
def update_state(self, event: TextArea.Changed | RadioSet.Changed) -> None:
|
|
system_prompt_ta = self.query_one("#system-prompt-ta", TextArea)
|
|
selected_model_rs = self.query_one("#available-models", RadioSet)
|
|
if selected_model_rs.pressed_button is None:
|
|
selected_model_rs._selected = 0
|
|
assert selected_model_rs.pressed_button is not None
|
|
|
|
model_button = cast(ModelRadioButton, selected_model_rs.pressed_button)
|
|
model = model_button.model
|
|
self.app_ref.runtime_config = self.app_ref.runtime_config.model_copy(
|
|
update={
|
|
"system_prompt": system_prompt_ta.text,
|
|
"selected_model": model,
|
|
}
|
|
)
|
|
|
|
self.apply_overridden_subtitles(system_prompt_ta, selected_model_rs)
|
|
self.refresh()
|
|
|
|
def apply_overridden_subtitles(
|
|
self, system_prompt_ta: TextArea, selected_model_rs: RadioSet
|
|
) -> None:
|
|
if (
|
|
self.app_ref.launch_config.default_model
|
|
!= self.app_ref.runtime_config.selected_model.id
|
|
and self.app_ref.launch_config.default_model
|
|
!= self.app_ref.runtime_config.selected_model.name
|
|
):
|
|
selected_model_rs.border_subtitle = "overrides config"
|
|
else:
|
|
selected_model_rs.border_subtitle = ""
|
|
|
|
if system_prompt_ta.text != self.app_ref.launch_config.system_prompt:
|
|
system_prompt_ta.border_subtitle = "overrides config"
|
|
else:
|
|
system_prompt_ta.border_subtitle = "editable"
|