feat(tui): add usage overlay and sidebar for displaying usage statistics and tasks

feat(tui): implement status bar with connection and turn state indicators
feat(tui): create workflow panel for agent status and progress visualization
feat(web): introduce web frontend interface with static file serving
feat(ws): add WebSocket interface for real-time communication and session management
This commit is contained in:
asepharyana
2026-07-20 09:04:57 +07:00
parent bceba665c0
commit da2ed6da25
454 changed files with 13979 additions and 29539 deletions
@@ -0,0 +1,72 @@
//! Conversation use-case implementation.
//!
//! `ConversationServiceImpl` implements [`ConversationService`] from the
//! domain layer. It is generic over `R: ConversationRepository`, delegating
//! all persistence to that adapter.
//!
//! # Flow
//!
//! Each method computes the session directory from the session ID, then
//! delegates the actual I/O to the injected `repo`. Error context is
//! added at this layer to identify which session caused the failure.
use std::path::PathBuf;
use tracing;
use zesdex_domain::cms::{Conversation, ConversationRepository, ServiceError};
use zesdex_domain::core::ChatMessage;
/// Service implementation for conversation CRUD operations.
///
/// Generic over `R: ConversationRepository` so the persistence layer
/// can be swapped without changing business logic.
pub struct ConversationServiceImpl<R> {
pub repo: R,
/// Base directory containing session subdirectories.
pub sessions_dir: PathBuf,
}
impl<R: ConversationRepository> ConversationServiceImpl<R> {
/// Create a new service with the given repository and sessions directory.
pub fn new(repo: R, sessions_dir: impl Into<PathBuf>) -> Self {
tracing::debug!("creating ConversationServiceImpl");
Self {
repo,
sessions_dir: sessions_dir.into(),
}
}
/// Compute the session directory for a given session id.
fn session_dir(&self, session_id: &str) -> PathBuf {
self.sessions_dir.join(session_id)
}
}
impl<R: ConversationRepository> zesdex_domain::cms::ConversationService
for ConversationServiceImpl<R>
{
fn load_conversation(&self, session_id: &str) -> Result<Conversation, ServiceError> {
tracing::debug!("loading conversation for session {session_id}");
let dir = self.session_dir(session_id);
self.repo.load(&dir).map_err(ServiceError::Repository)
}
fn save_conversation(&self, conv: &Conversation) -> Result<(), ServiceError> {
tracing::debug!("saving conversation for session {}", conv.session_id);
let dir = self.session_dir(&conv.session_id);
self.repo.save(&dir, conv)?;
Ok(())
}
fn add_message(
&self,
conv: &mut Conversation,
msg: ChatMessage,
) -> Result<(), ServiceError> {
tracing::debug!("adding message to session {}", conv.session_id);
conv.push(msg);
let dir = self.session_dir(&conv.session_id);
self.repo.save(&dir, conv)?;
Ok(())
}
}
@@ -0,0 +1,59 @@
//! Memory use-case implementation.
//!
//! `MemoryServiceImpl` implements [`MemoryService`] from the domain
//! layer. It is generic over `R: MemoryRepository`, delegating all
//! persistence to that adapter.
//!
//! # Flow
//!
//! Each method delegates to the injected `repo` with the configured
//! `memory_dir`. Error context is added at this layer to identify which
//! memory operation failed.
use std::path::PathBuf;
use tracing;
use zesdex_domain::cms::{Memory, MemoryRepository, ServiceError};
/// Service implementation for memory CRUD operations.
///
/// Generic over `R: MemoryRepository` so the persistence layer can be
/// swapped without changing business logic.
pub struct MemoryServiceImpl<R> {
pub repo: R,
/// Base directory for memory storage files.
pub memory_dir: PathBuf,
}
impl<R: MemoryRepository> MemoryServiceImpl<R> {
/// Create a new service with the given repository and memory directory.
pub fn new(repo: R, memory_dir: impl Into<PathBuf>) -> Self {
tracing::debug!("creating MemoryServiceImpl");
Self {
repo,
memory_dir: memory_dir.into(),
}
}
}
impl<R: MemoryRepository> zesdex_domain::cms::MemoryService for MemoryServiceImpl<R> {
fn list_memories(&self) -> Result<Vec<String>, ServiceError> {
tracing::debug!("listing memories from {:?}", self.memory_dir);
self.repo
.list(&self.memory_dir)
.map_err(ServiceError::Repository)
}
fn save_memory(&self, memory: &Memory) -> Result<(), ServiceError> {
tracing::debug!("saving memory '{}'", memory.name);
self.repo.save(&self.memory_dir, memory)?;
Ok(())
}
fn delete_memory(&self, name: &str) -> Result<(), ServiceError> {
tracing::debug!("deleting memory '{name}'");
self.repo
.delete(&self.memory_dir, name)
.map_err(ServiceError::Repository)
}
}
+18
View File
@@ -0,0 +1,18 @@
//! CMS use-case implementations.
//!
//! Contains concrete service types that implement the domain's CMS
//! service traits by coordinating injected repository dependencies.
//!
//! # Use Cases
//!
//! - [`conversation_service`] — `ConversationServiceImpl`: conversation CRUD
//! - [`memory_service`] — `MemoryServiceImpl`: long-term memory management
//! - [`settings_service`] — `SettingsServiceImpl`: settings & app-config management
pub mod conversation_service;
pub mod memory_service;
pub mod settings_service;
pub use conversation_service::ConversationServiceImpl;
pub use memory_service::MemoryServiceImpl;
pub use settings_service::SettingsServiceImpl;
@@ -0,0 +1,76 @@
//! Settings and app-config use-case implementation.
//!
//! `SettingsServiceImpl` implements [`SettingsService`] from the domain
//! layer. It is generic over `S: SettingsRepository` and `C: AppConfigRepository`,
//! delegating persistence to those adapters.
//!
//! # Flow
//!
//! Each method delegates to the appropriate injected repository with the
//! configured `base_dir`. The `update_provider` method coordinates between
//! both repositories: load app config → mutate provider map → save app config.
use std::path::PathBuf;
use tracing;
use zesdex_domain::cms::{
AppConfig, AppConfigRepository, ProviderConfig, ServiceError, Settings,
SettingsRepository,
};
/// Service implementation for settings and app-config operations.
///
/// Generic over `S: SettingsRepository` and `C: AppConfigRepository` so
/// the persistence layer can be swapped without changing business logic.
pub struct SettingsServiceImpl<S, C> {
pub settings_repo: S,
pub app_config_repo: C,
pub base_dir: PathBuf,
}
impl<S: SettingsRepository, C: AppConfigRepository> SettingsServiceImpl<S, C> {
/// Create a new service with the given repositories and base directory.
pub fn new(
settings_repo: S,
app_config_repo: C,
base_dir: impl Into<PathBuf>,
) -> Self {
tracing::debug!("creating SettingsServiceImpl");
Self {
settings_repo,
app_config_repo,
base_dir: base_dir.into(),
}
}
}
impl<S: SettingsRepository, C: AppConfigRepository>
zesdex_domain::cms::SettingsService for SettingsServiceImpl<S, C>
{
fn load_settings(&self) -> Result<Settings, ServiceError> {
tracing::debug!("loading settings");
self.settings_repo
.load(&self.base_dir)
.map_err(ServiceError::Repository)
}
fn save_settings(&self, settings: &Settings) -> Result<(), ServiceError> {
tracing::debug!("saving settings");
self.settings_repo.save(&self.base_dir, settings)?;
Ok(())
}
fn update_provider(
&self,
name: &str,
config: &ProviderConfig,
) -> Result<(), ServiceError> {
tracing::debug!("updating provider '{name}'");
let mut app_config: AppConfig = self.app_config_repo.load(&self.base_dir)?;
app_config
.providers
.insert(name.to_string(), config.clone());
self.app_config_repo.save(&self.base_dir, &app_config)?;
Ok(())
}
}