Skip to content

Commit

Permalink
Merge pull request #43 from darrenburns/updates
Browse files Browse the repository at this point in the history
Update to latest Textual, fix dependencies, add inline mode
  • Loading branch information
darrenburns authored May 21, 2024
2 parents 507781d + 1b51948 commit bb6d837
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 67 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,22 @@ Launch Elia from the command line:
elia
```

Launch directly a new chat from the command line:
Launch a new chat inline (under your prompt) with `-i`/`--inline`:

```bash
elia "What is the Zen of Python?"
elia -i "What is the Zen of Python?"
```

Launch a new chat in full-screen mode:

```bash
elia "Tell me a cool fact about lizards!"
```

Specify a model via the command line using `-m`/`--model`:

```bash
elia -m gpt-4o
```

## Running local models
Expand All @@ -53,12 +65,14 @@ the options window (`ctrl+o`).
The example file below shows the available options, as well as examples of how to add new models.

```toml
# the *ID* for the model that is selected by default on launch
# to use one of the default builtin OpenAI/anthropic models, prefix
# the model name with `elia-`.
default_model = "elia-gpt-3.5-turbo"
# the ID or name of the model that is selected by default on launch
default_model = "gpt-4o"
# the system prompt on launch
system_prompt = "You are a helpful assistant who talks like a pirate."
# change the syntax highlighting theme of code in messages
# choose from https://pygments.org/styles/
# defaults to "monokai"
message_code_theme = "dracula"

# example of adding local llama3 support
# only the `name` field is required here.
Expand Down
52 changes: 23 additions & 29 deletions elia_chat/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pathlib
from textwrap import dedent
import tomllib
from typing import Any, Tuple
from typing import Any

import click
from click_default_group import DefaultGroup
Expand All @@ -17,7 +17,6 @@
from elia_chat.config import LaunchConfig
from elia_chat.database.import_chatgpt import import_chatgpt_data
from elia_chat.database.database import create_database, sqlite_file_name
from elia_chat.launch_args import QuickLaunchArgs
from elia_chat.locations import config_file

console = Console()
Expand Down Expand Up @@ -51,13 +50,32 @@ def cli() -> None:

@cli.command()
@click.argument("prompt", nargs=-1, type=str, required=False)
def default(prompt: tuple[str, ...]):
@click.option(
"-m",
"--model",
type=str,
default="",
help="The model to use for the chat",
)
@click.option(
"-i",
"--inline",
is_flag=True,
help="Run in inline mode, without launching full TUI.",
default=False,
)
def default(prompt: tuple[str, ...], model: str, inline: bool):
prompt = prompt or ("",)
joined_prompt = " ".join(prompt)
create_db_if_not_exists()
file_config = load_or_create_config_file()
app = Elia(LaunchConfig(**file_config), startup_prompt=joined_prompt)
app.run()
cli_config = {}
if model:
cli_config["default_model"] = model

launch_config: dict[str, Any] = {**file_config, **cli_config}
app = Elia(LaunchConfig(**launch_config), startup_prompt=joined_prompt)
app.run(inline=inline)


@cli.command()
Expand Down Expand Up @@ -110,29 +128,5 @@ def import_file_to_db(file: pathlib.Path) -> None:
console.print(f"[green]ChatGPT data imported from {str(file)!r}")


@cli.command()
@click.argument("message", nargs=-1, type=str, required=True)
@click.option(
"-m",
"--model",
type=str,
default="gpt-3.5-turbo",
help="The model to use for the chat",
)
def chat(message: Tuple[str, ...], model: str) -> None:
"""
Start Elia with a chat message
"""
quick_launch_args = QuickLaunchArgs(
launch_prompt=" ".join(message),
launch_prompt_model_name=model,
)
launch_config = LaunchConfig(
default_model=quick_launch_args.launch_prompt_model_name,
)
app = Elia(launch_config, quick_launch_args.launch_prompt)
app.run()


if __name__ == "__main__":
cli()
2 changes: 1 addition & 1 deletion elia_chat/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Elia(App[None]):
CSS_PATH = Path(__file__).parent / "elia.scss"
BINDINGS = [
Binding("q", "app.quit", "Quit", show=False),
Binding("?", "help", "Help"),
Binding("f1,?", "help", "Help"),
]

def __init__(self, config: LaunchConfig, startup_prompt: str = ""):
Expand Down
10 changes: 10 additions & 0 deletions elia_chat/chats_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from dataclasses import dataclass
import datetime

from sqlmodel import select
from textual import log

from elia_chat.database.converters import (
Expand Down Expand Up @@ -87,6 +88,15 @@ async def create_chat(chat_data: ChatData) -> int:

return chat.id

@staticmethod
async def archive_chat(chat_id: int) -> None:
async with get_session() as session:
statement = select(ChatDao).where(ChatDao.id == chat_id)
result = await session.exec(statement)
chat_dao = result.one()
chat_dao.archived = True
await session.commit()

@staticmethod
async def add_message_to_chat(chat_id: int, message: ChatMessage) -> None:
async with get_session() as session:
Expand Down
6 changes: 4 additions & 2 deletions elia_chat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def get_builtin_openai_models() -> list[EliaChatModel]:
),
EliaChatModel(
id="elia-gpt-4o",
name="openai/gpt-4o",
name="gpt-4o",
display_name="GPT-4o",
provider="OpenAI",
product="ChatGPT",
Expand Down Expand Up @@ -146,13 +146,15 @@ class LaunchConfig(BaseModel):

model_config = ConfigDict(frozen=True)

default_model: str = Field(default="elia-gpt-3.5-turbo")
default_model: str = Field(default="elia-gpt-4o")
"""The ID or name of the default model."""
system_prompt: str = Field(
default=os.getenv(
"ELIA_SYSTEM_PROMPT", "You are a helpful assistant named Elia."
)
)
message_code_theme: str = Field(default="monokai")
"""The default Pygments syntax highlighting theme to be used in chatboxes."""
models: list[EliaChatModel] = Field(default_factory=list)
builtin_models: list[EliaChatModel] = Field(
default_factory=get_builtin_models, init=False
Expand Down
1 change: 1 addition & 0 deletions elia_chat/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ async def all() -> list["ChatDao"]:
statement = (
select(ChatDao)
.join(subquery, subquery.c.chat_id == ChatDao.id)
.where(ChatDao.archived == False)
.order_by(desc(subquery.c.max_timestamp))
.options(selectinload(ChatDao.messages))
)
Expand Down
21 changes: 16 additions & 5 deletions elia_chat/elia.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ Tabs .underline--bar {
Screen {
background: $background;
padding: 0 2 1 2;
&:inline {
height: 80vh;
padding: 0 2;
}
}

ModalScreen {
background: black 50%;
padding: 0;

&:inline {
padding: 0;
border: none;
}
& Footer {
margin: 0 2 1 2;
}
Expand Down Expand Up @@ -75,6 +82,9 @@ Chat {
height: auto;
padding: 1 2;
background: $background;
&:inline {
padding: 0 2 1 2;
}

& #model-static {
color: $text-muted;
Expand Down Expand Up @@ -163,6 +173,11 @@ AppHeader {
width: 1fr;
padding: 1 2;
height: auto;

&:inline {
padding: 0 2 1 2;
}

& .app-title {
color: $text-muted;
}
Expand Down Expand Up @@ -380,10 +395,6 @@ Tabs:focus .underline--bar {
color: $text 35%;
}

TokenAnalysis {
height: auto;
}

MessageInfo #inner-container ContentSwitcher {
height: auto;
padding: 1 2;
Expand Down
2 changes: 1 addition & 1 deletion elia_chat/screens/chat_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ChatScreen(Screen[None]):
BINDINGS = [
Binding(
key="escape",
action="focus('prompt')",
action="app.focus('prompt')",
description="Focus prompt",
key_display="esc",
),
Expand Down
2 changes: 1 addition & 1 deletion elia_chat/screens/help_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class HelpScreen(ModalScreen[None]):
BINDINGS = [
Binding("q", "app.quit", "Quit", show=False),
Binding("escape,?", "app.pop_screen()", "Close help", key_display="esc"),
Binding("escape,f1,?", "app.pop_screen()", "Close help", key_display="esc"),
]

HELP_MARKDOWN = """\
Expand Down
2 changes: 1 addition & 1 deletion elia_chat/widgets/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ class FirstMessageSent(Message):
chat_data: ChatData

def compose(self) -> ComposeResult:
yield AgentIsTyping()
yield ChatHeader(chat=self.chat_data, model=self.model)

with VerticalScroll(id="chat-container") as vertical_scroll:
vertical_scroll.can_focus = False

yield ChatPromptInput(id="prompt")
yield AgentIsTyping()

async def on_mount(self, _: events.Mount) -> None:
"""
Expand Down
24 changes: 22 additions & 2 deletions elia_chat/widgets/chat_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datetime
from dataclasses import dataclass
from typing import cast

import humanize
from rich.console import RenderResult, Console, ConsoleOptions
Expand Down Expand Up @@ -57,8 +58,9 @@ def __init__(self, chat: ChatData, config: LaunchConfig) -> None:
class ChatList(OptionList):
BINDINGS = [
Binding(
"escape", "screen.focus('home-prompt')", "Focus prompt", key_display="esc"
"escape", "app.focus('home-prompt')", "Focus prompt", key_display="esc"
),
Binding("a", "archive_chat", "Archive chat", key_display="a"),
Binding("j,down", "cursor_down", "Down", show=False),
Binding("k,up", "cursor_up", "Up", show=False),
Binding("l,right,enter", "select", "Select", show=False),
Expand Down Expand Up @@ -110,7 +112,7 @@ async def reload_and_refresh(self, new_highlighted: int = -1) -> None:
old_highlighted = self.highlighted
self.clear_options()
self.add_options(self.options)
self.border_title = f"History ({len(self.options)})"
self.border_title = self.get_border_title()
if new_highlighted > -1:
self.highlighted = new_highlighted
else:
Expand All @@ -124,6 +126,24 @@ async def load_chats(self) -> list[ChatData]:
all_chats = await ChatsManager.all_chats()
return all_chats

async def action_archive_chat(self) -> None:
if self.highlighted is None:
return

item = cast(ChatListItem, self.get_option_at_index(self.highlighted))
self.options.pop(self.highlighted)
self.remove_option_at_index(self.highlighted)

chat_id = item.chat.id
await ChatsManager.archive_chat(chat_id)

self.border_title = self.get_border_title()
self.refresh()
self.app.notify(f"Chat [b]{chat_id!r}[/] archived")

def get_border_title(self) -> str:
return f"History ({len(self.options)})"

def create_chat(self, chat_data: ChatData) -> None:
new_chat_list_item = ChatListItem(chat_data, self.app.launch_config)
log.debug(f"Creating new chat {new_chat_list_item!r}")
Expand Down
13 changes: 10 additions & 3 deletions elia_chat/widgets/chatbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from elia_chat.config import EliaChatModel
from elia_chat.models import ChatMessage
from elia_chat.config import launch_config


class SelectionTextArea(TextArea):
Expand Down Expand Up @@ -97,7 +98,9 @@ def action_copy_to_clipboard(self) -> None:
message = f"Copied message ({len(text_to_copy)} characters)."
self.notify(message, title="Message copied")

self.app.copy_to_clipboard(text_to_copy)
import pyperclip

pyperclip.copy(text_to_copy)
self.visual_mode = False

def action_next_code_block(self) -> None:
Expand Down Expand Up @@ -203,7 +206,9 @@ def action_copy_to_clipboard(self) -> None:
if not self.selection_mode:
text_to_copy = self.message.message.get("content")
if isinstance(text_to_copy, str):
self.app.copy_to_clipboard(text_to_copy)
import pyperclip

pyperclip.copy(text_to_copy)
message = f"Copied message ({len(text_to_copy)} characters)."
self.notify(message, title="Message copied")
else:
Expand Down Expand Up @@ -260,7 +265,9 @@ def markdown(self) -> Markdown:
content = self.message.message.get("content")
if not isinstance(content, str):
content = ""
return Markdown(content)

config = launch_config.get()
return Markdown(content, code_theme=config.message_code_theme)

def render(self) -> RenderableType:
if self.selection_mode:
Expand Down
Loading

0 comments on commit bb6d837

Please sign in to comment.