ChatClient migration (#145)

* Simplify client

* Fixups

* Update Cli-Client to use builder

* undeprecate legacy convos

* Allow Storage config in builder

* bug fixes

* Clippy fix

* fixes
This commit is contained in:
Jazz Turner-Baggs
2026-06-23 12:02:01 -07:00
committed by GitHub
parent e1921b944d
commit a5abefa314
12 changed files with 491 additions and 263 deletions
+13 -5
View File
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use arboard::Clipboard;
use crossbeam_channel::Receiver;
use logos_chat::{ChatClient, EphemeralRegistry, Event, RegistrationService, Transport};
use logos_chat::{ChatClient, ChatStore, Event, IdentityProvider, RegistrationService, Transport};
use serde::{Deserialize, Serialize};
use crate::utils::now;
@@ -41,8 +41,14 @@ pub struct AppState {
pub active_chat: Option<String>,
}
pub struct ChatApp<T: Transport, R: RegistrationService = EphemeralRegistry> {
pub client: ChatClient<T, R>,
pub struct ChatApp<I, T, R, S>
where
I: IdentityProvider + Send + 'static,
T: Transport,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
pub client: ChatClient<I, T, R, S>,
events: Receiver<Event>,
pub state: AppState,
/// Ephemeral command output — not persisted, cleared on chat switch.
@@ -53,13 +59,15 @@ pub struct ChatApp<T: Transport, R: RegistrationService = EphemeralRegistry> {
state_path: PathBuf,
}
impl<T, R> ChatApp<T, R>
impl<I, T, R, S> ChatApp<I, T, R, S>
where
I: IdentityProvider + Send,
T: Transport,
R: RegistrationService + Send + 'static,
S: ChatStore + Send,
{
pub fn new(
client: ChatClient<T, R>,
client: ChatClient<I, T, R, S>,
events: Receiver<Event>,
user_name: &str,
data_dir: &Path,
+38 -17
View File
@@ -8,7 +8,10 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};
use crossbeam_channel::Receiver;
use logos_chat::{ChatClient, Event, HttpRegistry, RegistrationService, StorageConfig, Transport};
use logos_chat::{
ChatClient, ChatClientBuilder, ChatStore, Event, HttpRegistry, IdentityProvider,
RegistrationService, StorageConfig, Transport,
};
use app::ChatApp;
@@ -113,14 +116,20 @@ fn run<T: Transport>(transport: T, cli: &Cli) -> Result<()> {
match cli.registry_url.as_deref() {
Some(url) => {
let registry = HttpRegistry::new(url);
let (client, events) =
ChatClient::open_with_registry(cli.name.clone(), storage, transport, registry)
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to open chat client with HTTP registry")?;
let (client, events) = ChatClientBuilder::new()
.transport(transport)
.storage_config(storage)
.registration(registry)
.build()
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to open chat client with HTTP registry")?;
launch_tui(client, events, cli)
}
None => {
let (client, events) = ChatClient::open(cli.name.clone(), storage, transport)
let (client, events) = ChatClientBuilder::new()
.transport(transport)
.storage_config(storage)
.build()
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to open chat client")?;
launch_tui(client, events, cli)
@@ -128,10 +137,16 @@ fn run<T: Transport>(transport: T, cli: &Cli) -> Result<()> {
}
}
fn launch_tui<T, R>(client: ChatClient<T, R>, events: Receiver<Event>, cli: &Cli) -> Result<()>
fn launch_tui<I, T, R, S>(
client: ChatClient<I, T, R, S>,
events: Receiver<Event>,
cli: &Cli,
) -> Result<()>
where
I: IdentityProvider + Send,
T: Transport,
R: RegistrationService + Send + 'static,
S: ChatStore + Send,
{
let mut app = ChatApp::new(client, events, &cli.name, &cli.data)?;
@@ -176,18 +191,22 @@ fn run_logos_delivery(cli: Cli) -> Result<()> {
.to_str()
.context("db path contains non-UTF-8 characters")?
.to_string();
logos_chat::ChatClient::open(
cli.name.clone(),
logos_chat::StorageConfig::Encrypted {
logos_chat::ChatClientBuilder::new()
.storage_config(logos_chat::StorageConfig::Encrypted {
path: db_str,
key: "chat-cli".to_string(),
},
delivery,
)
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to open persistent client")?
})
.transport(delivery)
.build()
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to open persistent client")?
}
None => logos_chat::ChatClient::new(cli.name.clone(), delivery),
None => logos_chat::ChatClientBuilder::new()
.transport(delivery)
.build()
.map_err(|e| anyhow::anyhow!("{e:?}"))
.context("failed to open chat client")?,
};
let mut app = ChatApp::new(client, events, &cli.name, &data_dir)?;
@@ -209,10 +228,12 @@ fn run_logos_delivery(cli: Cli) -> Result<()> {
)
}
fn run_app<T, R>(terminal: &mut ui::Tui, app: &mut ChatApp<T, R>) -> Result<()>
fn run_app<I, T, R, S>(terminal: &mut ui::Tui, app: &mut ChatApp<I, T, R, S>) -> Result<()>
where
I: IdentityProvider + Send,
T: Transport,
R: RegistrationService + Send + 'static,
S: ChatStore + Send,
{
loop {
app.process_incoming()?;
+43 -28
View File
@@ -16,7 +16,7 @@ use ratatui::{
widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
};
use logos_chat::{RegistrationService, Transport};
use logos_chat::{ChatStore, IdentityProvider, RegistrationService, Transport};
use crate::app::ChatApp;
@@ -38,10 +38,13 @@ pub fn restore() -> io::Result<()> {
}
/// Draw the UI.
pub fn draw<D: Transport, R: RegistrationService + Send + 'static>(
frame: &mut Frame,
app: &ChatApp<D, R>,
) {
pub fn draw<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
@@ -58,11 +61,13 @@ pub fn draw<D: Transport, R: RegistrationService + Send + 'static>(
draw_status(frame, app, chunks[3]);
}
fn draw_header<D: Transport, R: RegistrationService + Send + 'static>(
frame: &mut Frame,
app: &ChatApp<D, R>,
area: Rect,
) {
fn draw_header<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
let title = match app.current_session() {
Some(session) => {
let id = &session.chat_id[..8.min(session.chat_id.len())];
@@ -85,11 +90,13 @@ fn draw_header<D: Transport, R: RegistrationService + Send + 'static>(
frame.render_widget(header, area);
}
fn draw_messages<D: Transport, R: RegistrationService + Send + 'static>(
frame: &mut Frame,
app: &ChatApp<D, R>,
area: Rect,
) {
fn draw_messages<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
let remote_name = app
.current_session()
.map(|s| s.display_name())
@@ -175,11 +182,13 @@ fn draw_messages<D: Transport, R: RegistrationService + Send + 'static>(
frame.render_stateful_widget(messages_widget, area, &mut list_state);
}
fn draw_input<D: Transport, R: RegistrationService + Send + 'static>(
frame: &mut Frame,
app: &ChatApp<D, R>,
area: Rect,
) {
fn draw_input<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
// Inner width: area minus borders (2).
let inner_width = area.width.saturating_sub(2) as usize;
let input_len = app.input.len();
@@ -206,11 +215,13 @@ fn draw_input<D: Transport, R: RegistrationService + Send + 'static>(
frame.set_cursor_position((cursor_x, area.y + 1));
}
fn draw_status<D: Transport, R: RegistrationService + Send + 'static>(
frame: &mut Frame,
app: &ChatApp<D, R>,
area: Rect,
) {
fn draw_status<I, D, R, S>(frame: &mut Frame, app: &ChatApp<I, D, R, S>, area: Rect)
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
let status = Paragraph::new(app.status.as_str())
.style(Style::default().fg(Color::Gray))
.block(Block::default().title(" Status ").borders(Borders::ALL))
@@ -220,9 +231,13 @@ fn draw_status<D: Transport, R: RegistrationService + Send + 'static>(
}
/// Handle keyboard events.
pub fn handle_events<D: Transport, R: RegistrationService + Send + 'static>(
app: &mut ChatApp<D, R>,
) -> io::Result<bool> {
pub fn handle_events<I, D, R, S>(app: &mut ChatApp<I, D, R, S>) -> io::Result<bool>
where
I: IdentityProvider + Send + 'static,
D: Transport + Send + 'static,
R: RegistrationService + Send + 'static,
S: ChatStore + Send + 'static,
{
// Poll for events with a short timeout to allow checking incoming messages
if event::poll(std::time::Duration::from_millis(100))?
&& let Event::Key(key) = event::read()?