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:
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user