341 lines
9.9 KiB
Rust
341 lines
9.9 KiB
Rust
use crate::command::adapter::{AdapterError, InputAdapter, OutputAdapter};
|
|
use crate::command::context::AdapterContext;
|
|
use crate::command::response::{CommandResponse, MessageKind};
|
|
use crate::command::Command;
|
|
|
|
/// CLI 输入适配器
|
|
///
|
|
/// 将 CLI 的 "/new title" 等输入格式转换为 Command
|
|
pub struct CliInputAdapter;
|
|
|
|
impl CliInputAdapter {
|
|
/// 创建新的 CLI 输入适配器
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Default for CliInputAdapter {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl InputAdapter for CliInputAdapter {
|
|
fn try_parse(
|
|
&self,
|
|
input: &str,
|
|
_ctx: AdapterContext,
|
|
) -> Result<Option<Command>, AdapterError> {
|
|
let trimmed = input.trim();
|
|
|
|
// 解析 /new 命令
|
|
if trimmed == "/new" {
|
|
return Ok(Some(Command::CreateSession { title: None }));
|
|
}
|
|
|
|
if let Some(title) = trimmed.strip_prefix("/new ") {
|
|
let title = title.trim();
|
|
return Ok(Some(Command::CreateSession {
|
|
title: Some(title.to_string()),
|
|
}));
|
|
}
|
|
|
|
// 解析 /save 命令 - 保存当前话题
|
|
if trimmed == "/save" {
|
|
return Ok(Some(Command::SaveTopic {
|
|
filepath: None,
|
|
}));
|
|
}
|
|
|
|
if let Some(filepath) = trimmed.strip_prefix("/save ") {
|
|
let filepath = filepath.trim();
|
|
return Ok(Some(Command::SaveTopic {
|
|
filepath: Some(filepath.to_string()),
|
|
}));
|
|
}
|
|
|
|
// 解析 /save-session 命令 - 保存整个会话
|
|
if trimmed == "/save-session" {
|
|
return Ok(Some(Command::SaveSession {
|
|
filepath: None,
|
|
include_all: false,
|
|
}));
|
|
}
|
|
|
|
if let Some(args) = trimmed.strip_prefix("/save-session ") {
|
|
let args = args.trim();
|
|
// 解析参数:可能是 "all"、路径、或 "all 路径"
|
|
let (include_all, filepath) = if args == "all" {
|
|
// /save-session all - 保存全部消息
|
|
(true, None)
|
|
} else if args.starts_with("all ") {
|
|
// /save-session all <filepath> - 保存全部消息到指定路径
|
|
let path = args[4..].trim();
|
|
(true, Some(path.to_string()))
|
|
} else {
|
|
// /save-session <filepath> - 保存活跃消息到指定路径
|
|
(false, Some(args.to_string()))
|
|
};
|
|
return Ok(Some(Command::SaveSession { filepath, include_all }));
|
|
}
|
|
|
|
// 解析 /list 命令
|
|
if trimmed == "/list" {
|
|
return Ok(Some(Command::ListSessions {
|
|
include_archived: false,
|
|
}));
|
|
}
|
|
|
|
if trimmed == "/list all" {
|
|
return Ok(Some(Command::ListSessions {
|
|
include_archived: true,
|
|
}));
|
|
}
|
|
|
|
// 解析 /use 命令 - 切换会话(支持 session_id 或序号)
|
|
if let Some(session_id) = trimmed.strip_prefix("/use ") {
|
|
let session_id = session_id.trim();
|
|
return Ok(Some(Command::SwitchSession {
|
|
session_id: session_id.to_string(),
|
|
}));
|
|
}
|
|
|
|
// 解析 /current 命令 - 获取当前会话信息
|
|
if trimmed == "/current" {
|
|
return Ok(Some(Command::GetCurrentSession));
|
|
}
|
|
|
|
// 不是命令,返回 None
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
/// CLI 输出适配器
|
|
///
|
|
/// 将 CommandResponse 转换为 CLI 可读的字符串
|
|
pub struct CliOutputAdapter;
|
|
|
|
impl CliOutputAdapter {
|
|
/// 创建新的 CLI 输出适配器
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Default for CliOutputAdapter {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl OutputAdapter for CliOutputAdapter {
|
|
type Output = String;
|
|
|
|
fn adapt(&self, response: CommandResponse) -> String {
|
|
if let Some(error) = response.error {
|
|
return format!("Error [{}]: {}", error.code, error.message);
|
|
}
|
|
|
|
if !response.success {
|
|
return "Error: Unknown error".to_string();
|
|
}
|
|
|
|
let mut lines = Vec::new();
|
|
|
|
for msg in &response.messages {
|
|
match msg.kind {
|
|
MessageKind::Text | MessageKind::Notification => {
|
|
lines.push(msg.content.clone());
|
|
}
|
|
MessageKind::Error => {
|
|
lines.push(format!("Error: {}", msg.content));
|
|
}
|
|
_ => {
|
|
// 其他类型在 CLI 中简单显示
|
|
lines.push(msg.content.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
if lines.is_empty() {
|
|
"Success".to_string()
|
|
} else {
|
|
lines.join("\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_new_without_title() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/new", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(cmd, Command::CreateSession { title: None }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_new_with_title() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/new my session", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(
|
|
cmd,
|
|
Command::CreateSession {
|
|
title: Some(ref t)
|
|
} if t == "my session"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_not_command() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("hello world", ctx).unwrap();
|
|
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_output_adapter_success() {
|
|
let adapter = CliOutputAdapter::new();
|
|
let request_id = uuid::Uuid::new_v4();
|
|
let response = CommandResponse::success(request_id)
|
|
.with_message(MessageKind::Notification, "Session created: abc123");
|
|
|
|
let output = adapter.adapt(response);
|
|
|
|
assert!(output.contains("Session created: abc123"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_output_adapter_error() {
|
|
let adapter = CliOutputAdapter::new();
|
|
let request_id = uuid::Uuid::new_v4();
|
|
let response = CommandResponse::error(
|
|
request_id,
|
|
crate::command::response::CommandError::new("TEST_ERROR", "something failed"),
|
|
);
|
|
|
|
let output = adapter.adapt(response);
|
|
|
|
assert!(output.contains("Error [TEST_ERROR]"));
|
|
assert!(output.contains("something failed"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_save_topic_without_path() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/save", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(cmd, Command::SaveTopic { filepath: None }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_save_topic_with_path() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/save ./debug/topic.md", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(
|
|
cmd,
|
|
Command::SaveTopic {
|
|
filepath: Some(ref p),
|
|
} if p == "./debug/topic.md"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_save_session_without_path() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/save-session", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(cmd, Command::SaveSession { filepath: None, include_all: false }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_save_session_with_path() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/save-session ./debug/session.md", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(
|
|
cmd,
|
|
Command::SaveSession {
|
|
filepath: Some(ref p),
|
|
include_all: false,
|
|
} if p == "./debug/session.md"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_save_session_all() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/save-session all", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(cmd, Command::SaveSession { filepath: None, include_all: true }));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_input_adapter_save_session_all_with_path() {
|
|
let adapter = CliInputAdapter::new();
|
|
let ctx = AdapterContext::new("test");
|
|
|
|
let result = adapter.try_parse("/save-session all ./debug/session.md", ctx).unwrap();
|
|
|
|
assert!(result.is_some());
|
|
let cmd = result.unwrap();
|
|
assert!(matches!(
|
|
cmd,
|
|
Command::SaveSession {
|
|
filepath: Some(ref p),
|
|
include_all: true,
|
|
} if p == "./debug/session.md"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cli_output_adapter_save_success() {
|
|
let adapter = CliOutputAdapter::new();
|
|
let request_id = uuid::Uuid::new_v4();
|
|
let response = CommandResponse::success(request_id)
|
|
.with_message(MessageKind::Notification, "Topic saved to: topic.md")
|
|
.with_metadata("filepath", "topic.md");
|
|
|
|
let output = adapter.adapt(response);
|
|
|
|
assert!(output.contains("Topic saved to: topic.md"));
|
|
}
|
|
}
|