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
+55
View File
@@ -0,0 +1,55 @@
//! Authentication DTOs — login, register, and token refresh payloads.
use serde::{Deserialize, Serialize};
/// Request body for `POST /auth/login`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
/// Username or email identifier.
pub username: String,
/// Plaintext password.
pub password: String,
}
/// Request body for `POST /auth/register`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterRequest {
/// Desired username.
pub username: String,
/// Plaintext password (will be hashed server-side).
pub password: String,
/// Optional display name.
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
}
/// Request body for `POST /auth/refresh`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefreshRequest {
/// The refresh token issued during login.
pub refresh_token: String,
}
/// Response body for auth endpoints (login, register, refresh).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthResponse {
/// JWT access token (short-lived, typically 1 hour).
pub access_token: String,
/// JWT refresh token (long-lived, typically 7 days).
pub refresh_token: String,
/// Token type (always `"Bearer"`).
pub token_type: String,
/// Expiry of the access token in seconds.
pub expires_in: u64,
}
/// Claims exposed in the JWT payload, returned from introspection.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimsResponse {
/// Subject identifier (username).
pub sub: String,
/// Issued-at timestamp (epoch seconds).
pub iat: u64,
/// Expiry timestamp (epoch seconds).
pub exp: u64,
}
+136
View File
@@ -0,0 +1,136 @@
//! Conversation DTOs — message history read/write payloads.
use serde::{Deserialize, Serialize};
use zesdex_domain::core::{ChatMessage, Conversation};
/// Request body for appending a message to a conversation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddMessageRequest {
/// Message role: `"user"` or `"assistant"`.
pub role: String,
/// Message content text.
pub content: String,
}
/// Response body for a single conversation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationResponse {
/// Session ID this conversation belongs to.
pub session_id: String,
/// Messages in the conversation.
pub messages: Vec<MessageResponse>,
/// Total message count.
pub message_count: usize,
/// Model identifier used for this conversation.
pub model: String,
/// System prompt in effect.
pub system_prompt: String,
/// Max tokens configuration.
pub max_tokens: Option<u32>,
/// Temperature configuration.
pub temperature: Option<f32>,
}
impl From<Conversation> for ConversationResponse {
fn from(c: Conversation) -> Self {
let message_count = c.len();
let messages: Vec<MessageResponse> =
c.messages.into_iter().map(MessageResponse::from).collect();
ConversationResponse {
session_id: c.session_id,
messages,
message_count,
model: c.model,
system_prompt: c.system_prompt,
max_tokens: c.max_tokens,
temperature: c.temperature,
}
}
}
/// A single message in a conversation response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageResponse {
/// Message role.
pub role: String,
/// Message content (None for assistant messages with only tool calls).
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
/// Optional tool call information.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCallResponse>>,
/// Optional tool call result identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
impl From<ChatMessage> for MessageResponse {
fn from(m: ChatMessage) -> Self {
let tool_calls = m.tool_calls.map(|calls| {
calls
.into_iter()
.map(|tc| ToolCallResponse {
id: tc.id,
function: ToolFunctionResponse {
name: tc.function.name,
arguments: tc.function.arguments.to_string(),
},
})
.collect()
});
MessageResponse {
role: m.role.to_string(),
content: m.content,
tool_calls,
tool_call_id: m.tool_call_id,
}
}
}
/// A tool call reference in a message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallResponse {
/// Tool call ID.
pub id: String,
/// Function details.
pub function: ToolFunctionResponse,
}
/// A function invocation in a tool call.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunctionResponse {
/// Function name.
pub name: String,
/// JSON-encoded arguments.
pub arguments: String,
}
/// Request body for `POST /chat/completions`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionRequest {
/// The session ID to attach this completion to.
pub session_id: String,
/// Message content (user message).
pub message: String,
/// Optional model override.
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
/// Optional max tokens override.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
/// Optional temperature override.
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
}
/// Response body for `POST /chat/completions`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionResponse {
/// The assistant's reply.
pub reply: String,
/// Total prompt tokens consumed.
pub prompt_tokens: u64,
/// Total completion tokens generated.
pub completion_tokens: u64,
}
+15
View File
@@ -0,0 +1,15 @@
//! Error response DTO — JSON body returned for all API errors.
use serde::Serialize;
/// Standardised error response body.
///
/// Returned for all non-successful API responses. Contains a human-readable
/// `message` and the HTTP status `code` for machine parsing.
#[derive(Debug, Clone, Serialize)]
pub struct ErrorResponse {
/// Human-readable error description.
pub message: String,
/// HTTP status code (mirrors the response status).
pub code: u16,
}
+10
View File
@@ -0,0 +1,10 @@
//! Data Transfer Objects for the REST API.
//!
//! These types define the wire format for request bodies and response bodies.
//! They are intentionally independent of domain entities so the API contract
//! can evolve without coupling to the domain model.
pub mod auth;
pub mod conversation;
pub mod error;
pub mod session;
+57
View File
@@ -0,0 +1,57 @@
//! Session DTOs — create, list, and delete session payloads.
use serde::{Deserialize, Serialize};
use zesdex_domain::auth::Session;
/// Request body for `POST /sessions`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateSessionRequest {
/// Human-readable session title.
pub title: String,
}
/// Response body for a single session.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionResponse {
/// Unique session identifier.
pub id: String,
/// Epoch-millis timestamp of creation.
pub created_at: i64,
/// Epoch-millis timestamp of last update.
pub updated_at: i64,
/// Human-readable title.
pub title: String,
/// Model identifier string.
pub model: String,
/// Number of messages in this session.
pub message_count: u32,
/// Whether the session has been archived.
pub archived: bool,
/// Optional AI-generated summary.
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
impl From<Session> for SessionResponse {
fn from(s: Session) -> Self {
SessionResponse {
id: s.id,
created_at: s.created_at,
updated_at: s.updated_at,
title: s.title,
model: s.model,
message_count: s.message_count,
archived: s.archived,
summary: s.summary,
}
}
}
/// Response body for `GET /sessions`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionListResponse {
/// All (non-archived) sessions.
pub sessions: Vec<SessionResponse>,
/// Total count of sessions returned.
pub total: usize,
}