Skip to content

Commit

Permalink
Merge pull request #46 from darrenburns/rename-chats
Browse files Browse the repository at this point in the history
Progress renaming chats
  • Loading branch information
darrenburns committed May 22, 2024
2 parents 3cc3506 + 8a69f27 commit 5c5e431
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 71 deletions.
6 changes: 5 additions & 1 deletion elia_chat/chats_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ async def get_chat(chat_id: int) -> ChatData:
chat_dao = await ChatDao.from_id(chat_id)
return chat_dao_to_chat_data(chat_dao)

@staticmethod
async def rename_chat(chat_id: int, new_title: str) -> None:
await ChatDao.rename_chat(chat_id, new_title)

@staticmethod
async def get_messages(
chat_id: int,
Expand Down Expand Up @@ -66,7 +70,7 @@ async def create_chat(chat_data: ChatData) -> int:
chat = ChatDao(
model=lookup_key,
title="",
started_at=datetime.datetime.now(datetime.UTC),
started_at=datetime.datetime.now(datetime.timezone.utc),
)
session.add(chat)
await session.commit()
Expand Down
10 changes: 9 additions & 1 deletion elia_chat/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async def all() -> list["ChatDao"]:
statement = (
select(ChatDao)
.join(subquery, subquery.c.chat_id == ChatDao.id)
.where(ChatDao.archived == False)
.where(ChatDao.archived == False) # noqa: E712
.order_by(desc(subquery.c.max_timestamp))
.options(selectinload(ChatDao.messages))
)
Expand All @@ -92,3 +92,11 @@ async def from_id(chat_id: int) -> "ChatDao":
)
result = await session.exec(statement)
return result.one()

@staticmethod
async def rename_chat(chat_id: int, new_title: str) -> None:
async with get_session() as session:
chat = await ChatDao.from_id(chat_id)
chat.title = new_title
session.add(chat)
await session.commit()
64 changes: 40 additions & 24 deletions elia_chat/elia.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ $main: #6C2BD9;
$main-darken-1: #5521B5;
$main-darken-2: #4A1D96;
$main-border-text-color: greenyellow 70%;
$main-border-color: $main-lighten-1 50%;
$main-border-color-focus: $main-lighten-1 100%;
$main-border-color: $main-lighten-1 90%;
$main-border-color-focus: $main-lighten-2 100%;

$left-border-trim: vkey $main-lighten-2 15%;

Expand Down Expand Up @@ -327,37 +327,53 @@ OptionList > .option-list--option-hover-highlighted-disabled {
background: $main 60%;
}

RenameChat {
& > Vertical {
background: $background 0%;
height: auto;
& Input {
padding: 0 4;
border: none;
border-bottom: hkey $main-border-color;
border-top: hkey $main-border-color;
border-subtitle-color: $main-border-text-color;
border-subtitle-background: $background;
}
}

}

ChatDetails {
align: center middle;
}
& > #container {
width: 90%;
height: 85%;
background: $background;
padding: 1 2;
border: wide $main-border-color-focus;
border-title-color: $main-border-text-color;
border-title-background: $background;
border-title-style: b;
border-subtitle-color: $text-muted;
border-subtitle-background: $background;

ChatDetails > #container {
width: 90%;
height: 85%;
background: $background;
padding: 1 2;
border: wide $main-border-color-focus;
border-title-color: $main-border-text-color;
border-title-background: $background;
border-title-style: b;
border-subtitle-color: $text-muted;
border-subtitle-background: $background;
& Markdown {
padding: 0;
margin: 0;
}

& Markdown {
padding: 0;
margin: 0;
}
& .heading {
color: $text-muted;
}

& .heading {
color: $text-muted;
}
& .datum {
text-style: i;
}

& .datum {
text-style: i;
}

}


MessageInfo #message-info-header {
dock: top;
width: 1fr;
Expand Down
9 changes: 6 additions & 3 deletions elia_chat/screens/chat_details.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import timezone
from typing import TYPE_CHECKING, cast
import humanize
from textual.app import ComposeResult
Expand Down Expand Up @@ -65,7 +66,9 @@ def compose(self) -> ComposeResult:

yield Label("First message", classes="heading")
if chat.create_timestamp:
create_timestamp = chat.create_timestamp.replace(tzinfo=None)
create_timestamp = chat.create_timestamp.replace(
tzinfo=timezone.utc
)
yield Label(
f"{humanize.naturaltime(create_timestamp)}",
classes="datum",
Expand All @@ -75,11 +78,11 @@ def compose(self) -> ComposeResult:

yield Rule()

update_time = chat.update_time
update_time = chat.update_time.replace(tzinfo=timezone.utc)
yield Label("Updated at", classes="heading")
if update_time:
yield Label(
f"{humanize.naturaltime(chat.update_time.replace(tzinfo=None))}",
f"{humanize.naturaltime(chat.update_time)}",
classes="datum",
)
else:
Expand Down
9 changes: 5 additions & 4 deletions elia_chat/screens/help_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ class HelpScreen(ModalScreen[None]):
On the chat screen, pressing `up` and `down` will navigate through messages,
but if you just wish to scroll a little, you can use `shift+up` and `shift+down`.
### The chat history
### The chat list
- `up,down,k,j`: Navigate through chats.
- `a`: Archive the highlighted chat.
- `pageup,pagedown`: Up/down a page.
- `home,end`: Go to first/last chat.
- `g,G`: Go to first/last chat.
Expand Down Expand Up @@ -120,10 +121,11 @@ class HelpScreen(ModalScreen[None]):
### The chat screen
Press `shift+tab` to focus the latest message (or move the cursor `up` from (0, 0)).
You can use the arrow keys to move up and down through messages.
- `ctrl+r`: Rename the chat (or click the chat title).
- `f2`: View more information about the chat.
_With a message focused_:
- `y,c`: Copy the raw Markdown of the message to the clipboard.
Expand All @@ -142,7 +144,6 @@ class HelpScreen(ModalScreen[None]):
- `G`: Focus the latest message.
- `m`: Move focus to the prompt box.
- `up,down,k,j`: Navigate through messages.
- `f2`: View more information about the chat.
"""

Expand Down
25 changes: 25 additions & 0 deletions elia_chat/screens/rename_chat_screen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from textual import on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.screen import ModalScreen
from textual.widgets import Input


class RenameChat(ModalScreen[str]):
BINDINGS = [
Binding("escape", "app.pop_screen", "Cancel", key_display="esc"),
Binding("enter", "app.pop_screen", "Save"),
]

def compose(self) -> ComposeResult:
with Vertical():
title_input = Input(placeholder="Enter a title...")
title_input.border_subtitle = (
"[[white]enter[/]] Save [[white]esc[/]] Cancel"
)
yield title_input

@on(Input.Submitted)
def close_screen(self, event: Input.Submitted) -> None:
self.dismiss(event.value)
19 changes: 16 additions & 3 deletions elia_chat/widgets/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from elia_chat.models import ChatData, ChatMessage
from elia_chat.screens.chat_details import ChatDetails
from elia_chat.widgets.agent_is_typing import AgentIsTyping
from elia_chat.widgets.chat_header import ChatHeader
from elia_chat.widgets.chat_header import ChatHeader, TitleStatic
from elia_chat.widgets.prompt_input import PromptInput
from elia_chat.widgets.chatbox import Chatbox

Expand All @@ -37,6 +37,7 @@ class ChatPromptInput(PromptInput):

class Chat(Widget):
BINDINGS = [
Binding("ctrl+r", "rename", "Rename", key_display="^r"),
Binding("shift+down", "scroll_container_down", show=False),
Binding("shift+up", "scroll_container_up", show=False),
Binding(
Expand Down Expand Up @@ -130,7 +131,7 @@ def restore_state_on_agent_failure(self, event: Chat.AgentResponseFailed) -> Non
async def new_user_message(self, content: str) -> None:
log.debug(f"User message submitted in chat {self.chat_data.id!r}: {content!r}")

now_utc = datetime.datetime.now(datetime.UTC)
now_utc = datetime.datetime.now(datetime.timezone.utc)
user_message: ChatCompletionUserMessageParam = {
"content": content,
"role": "user",
Expand Down Expand Up @@ -193,7 +194,7 @@ async def stream_agent_response(self) -> None:
"content": "",
"role": "assistant",
}
now = datetime.datetime.now(datetime.UTC)
now = datetime.datetime.now(datetime.timezone.utc)
message = ChatMessage(message=ai_message, model=model, timestamp=now)

response_chatbox = Chatbox(
Expand Down Expand Up @@ -268,6 +269,14 @@ async def on_cursor_up_from_prompt(self) -> None:
def move_focus_to_prompt(self) -> None:
self.query_one(ChatPromptInput).focus()

@on(TitleStatic.ChatRenamed)
async def handle_chat_rename(self, event: TitleStatic.ChatRenamed) -> None:
if event.chat_id == self.chat_data.id and event.new_title:
self.chat_data.title = event.new_title
header = self.query_one(ChatHeader)
header.update_header(self.chat_data, self.model)
await ChatsManager.rename_chat(event.chat_id, event.new_title)

def get_latest_chatbox(self) -> Chatbox:
return self.query(Chatbox).last()

Expand All @@ -277,6 +286,10 @@ def focus_latest_message(self) -> None:
except NoMatches:
pass

def action_rename(self) -> None:
title_static = self.query_one(TitleStatic)
title_static.begin_rename()

def action_focus_latest_message(self) -> None:
self.focus_latest_message()

Expand Down
50 changes: 48 additions & 2 deletions elia_chat/widgets/chat_header.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,58 @@
from __future__ import annotations
from dataclasses import dataclass

from rich.console import ConsoleRenderable, RichCast
from rich.markup import escape

from textual.app import ComposeResult
from textual.message import Message
from textual.widget import Widget
from textual.widgets import Static

from elia_chat.config import EliaChatModel
from elia_chat.models import ChatData
from elia_chat.screens.rename_chat_screen import RenameChat


class TitleStatic(Static):
@dataclass
class ChatRenamed(Message):
chat_id: int
new_title: str

def __init__(
self,
chat_id: int,
renderable: ConsoleRenderable | RichCast | str = "",
*,
expand: bool = False,
shrink: bool = False,
markup: bool = True,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
disabled: bool = False,
) -> None:
super().__init__(
renderable,
expand=expand,
shrink=shrink,
markup=markup,
name=name,
id=id,
classes=classes,
disabled=disabled,
)
self.chat_id = chat_id

def begin_rename(self) -> None:
self.app.push_screen(RenameChat(), callback=self.request_chat_rename)

def action_rename_chat(self) -> None:
self.begin_rename()

async def request_chat_rename(self, new_title: str) -> None:
self.post_message(self.ChatRenamed(self.chat_id, new_title))


class ChatHeader(Widget):
Expand Down Expand Up @@ -36,12 +81,13 @@ def update_header(self, chat: ChatData, model: EliaChatModel):

def title_static_content(self) -> str:
chat = self.chat
return escape(chat.short_preview) if chat else "Empty chat"
content = escape(chat.title or chat.short_preview) if chat else "Empty chat"
return f"[@click=rename_chat]{content}[/]"

def model_static_content(self) -> str:
model = self.model
return escape(model.display_name or model.name) if model else "Unknown model"

def compose(self) -> ComposeResult:
yield Static(self.title_static_content(), id="title-static")
yield TitleStatic(self.chat.id, self.title_static_content(), id="title-static")
yield Static(self.model_static_content(), id="model-static")
7 changes: 5 additions & 2 deletions elia_chat/widgets/chat_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ChatListItemRenderable:
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
now = datetime.datetime.now(datetime.UTC)
now = datetime.datetime.now(datetime.timezone.utc)
delta = now - self.chat.update_time
time_ago = humanize.naturaltime(delta)
time_ago_text = Text(time_ago, style="dim i")
Expand Down Expand Up @@ -139,7 +139,10 @@ async def action_archive_chat(self) -> None:

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

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

0 comments on commit 5c5e431

Please sign in to comment.