feat(theme): add tool_output color to themes
- Added a `tool_output` color to the `Theme` struct. - Updated all built-in themes to include the new color. - Modified the TUI to use the `tool_output` color for rendering tool output.
This commit is contained in:
@@ -5,6 +5,7 @@ members = [
|
|||||||
"crates/owlen-tui",
|
"crates/owlen-tui",
|
||||||
"crates/owlen-cli",
|
"crates/owlen-cli",
|
||||||
"crates/owlen-ollama",
|
"crates/owlen-ollama",
|
||||||
|
"crates/owlen-mcp-server",
|
||||||
]
|
]
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ sqlx = { workspace = true }
|
|||||||
duckduckgo = "0.2.0"
|
duckduckgo = "0.2.0"
|
||||||
reqwest = { workspace = true, features = ["default"] }
|
reqwest = { workspace = true, features = ["default"] }
|
||||||
reqwest_011 = { version = "0.11", package = "reqwest" }
|
reqwest_011 = { version = "0.11", package = "reqwest" }
|
||||||
|
path-clean = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio-test = { workspace = true }
|
tokio-test = { workspace = true }
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ impl MessageFormatter {
|
|||||||
Some(thinking)
|
Some(thinking)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If the result is empty but we have thinking content, show a placeholder
|
||||||
|
if result.trim().is_empty() && thinking_result.is_some() {
|
||||||
|
result.push_str("[Thinking...]");
|
||||||
|
}
|
||||||
|
|
||||||
(result, thinking_result)
|
(result, thinking_result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ pub trait McpClient: Send + Sync {
|
|||||||
/// Placeholder for a client that connects to a remote MCP server.
|
/// Placeholder for a client that connects to a remote MCP server.
|
||||||
pub struct RemoteMcpClient;
|
pub struct RemoteMcpClient;
|
||||||
|
|
||||||
|
impl RemoteMcpClient {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl McpClient for RemoteMcpClient {
|
impl McpClient for RemoteMcpClient {
|
||||||
async fn list_tools(&self) -> Result<Vec<McpToolDescriptor>> {
|
async fn list_tools(&self) -> Result<Vec<McpToolDescriptor>> {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub type ChatStream = Pin<Box<dyn Stream<Item = Result<ChatResponse>> + Send>>;
|
|||||||
/// use std::sync::Arc;
|
/// use std::sync::Arc;
|
||||||
/// use futures::Stream;
|
/// use futures::Stream;
|
||||||
/// use owlen_core::provider::{Provider, ProviderRegistry, ChatStream};
|
/// use owlen_core::provider::{Provider, ProviderRegistry, ChatStream};
|
||||||
/// use owlen_core::types::{ChatRequest, ChatResponse, ModelInfo, Message};
|
/// use owlen_core::types::{ChatRequest, ChatResponse, ModelInfo, Message, Role, ChatParameters};
|
||||||
/// use owlen_core::Result;
|
/// use owlen_core::Result;
|
||||||
///
|
///
|
||||||
/// // 1. Create a mock provider
|
/// // 1. Create a mock provider
|
||||||
@@ -31,18 +31,23 @@ pub type ChatStream = Pin<Box<dyn Stream<Item = Result<ChatResponse>> + Send>>;
|
|||||||
///
|
///
|
||||||
/// async fn list_models(&self) -> Result<Vec<ModelInfo>> {
|
/// async fn list_models(&self) -> Result<Vec<ModelInfo>> {
|
||||||
/// Ok(vec![ModelInfo {
|
/// Ok(vec![ModelInfo {
|
||||||
|
/// id: "mock-model".to_string(),
|
||||||
/// provider: "mock".to_string(),
|
/// provider: "mock".to_string(),
|
||||||
/// name: "mock-model".to_string(),
|
/// name: "mock-model".to_string(),
|
||||||
/// ..Default::default()
|
/// description: None,
|
||||||
|
/// context_window: None,
|
||||||
|
/// capabilities: vec![],
|
||||||
|
/// supports_tools: false,
|
||||||
/// }])
|
/// }])
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn chat(&self, request: ChatRequest) -> Result<ChatResponse> {
|
/// async fn chat(&self, request: ChatRequest) -> Result<ChatResponse> {
|
||||||
/// let content = format!("Response to: {}", request.messages.last().unwrap().content);
|
/// let content = format!("Response to: {}", request.messages.last().unwrap().content);
|
||||||
/// Ok(ChatResponse {
|
/// Ok(ChatResponse {
|
||||||
/// model: request.model,
|
/// message: Message::new(Role::Assistant, content),
|
||||||
/// message: Message { role: "assistant".to_string(), content, ..Default::default() },
|
/// usage: None,
|
||||||
/// ..Default::default()
|
/// is_streaming: false,
|
||||||
|
/// is_final: true,
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@@ -67,8 +72,9 @@ pub type ChatStream = Pin<Box<dyn Stream<Item = Result<ChatResponse>> + Send>>;
|
|||||||
///
|
///
|
||||||
/// let request = ChatRequest {
|
/// let request = ChatRequest {
|
||||||
/// model: "mock-model".to_string(),
|
/// model: "mock-model".to_string(),
|
||||||
/// messages: vec![Message { role: "user".to_string(), content: "Hello".to_string(), ..Default::default() }],
|
/// messages: vec![Message::new(Role::User, "Hello".to_string())],
|
||||||
/// ..Default::default()
|
/// parameters: ChatParameters::default(),
|
||||||
|
/// tools: None,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let response = provider.chat(request).await.unwrap();
|
/// let response = provider.chat(request).await.unwrap();
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ use crate::model::ModelManager;
|
|||||||
use crate::provider::{ChatStream, Provider};
|
use crate::provider::{ChatStream, Provider};
|
||||||
use crate::storage::{SessionMeta, StorageManager};
|
use crate::storage::{SessionMeta, StorageManager};
|
||||||
use crate::tools::{
|
use crate::tools::{
|
||||||
code_exec::CodeExecTool, registry::ToolRegistry, web_search::WebSearchTool,
|
code_exec::CodeExecTool,
|
||||||
web_search_detailed::WebSearchDetailedTool, Tool,
|
fs_tools::{ResourcesGetTool, ResourcesListTool},
|
||||||
|
registry::ToolRegistry,
|
||||||
|
web_search::WebSearchTool,
|
||||||
|
web_search_detailed::WebSearchDetailedTool,
|
||||||
|
Tool,
|
||||||
};
|
};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ChatParameters, ChatRequest, ChatResponse, Conversation, Message, ModelInfo, ToolCall,
|
ChatParameters, ChatRequest, ChatResponse, Conversation, Message, ModelInfo, ToolCall,
|
||||||
@@ -48,7 +52,7 @@ pub enum SessionOutcome {
|
|||||||
/// use owlen_core::provider::{Provider, ChatStream};
|
/// use owlen_core::provider::{Provider, ChatStream};
|
||||||
/// use owlen_core::session::{SessionController, SessionOutcome};
|
/// use owlen_core::session::{SessionController, SessionOutcome};
|
||||||
/// use owlen_core::storage::StorageManager;
|
/// use owlen_core::storage::StorageManager;
|
||||||
/// use owlen_core::types::{ChatRequest, ChatResponse, ChatParameters, Message, ModelInfo};
|
/// use owlen_core::types::{ChatRequest, ChatResponse, ChatParameters, Message, ModelInfo, Role};
|
||||||
/// use owlen_core::Result;
|
/// use owlen_core::Result;
|
||||||
///
|
///
|
||||||
/// // Mock provider for the example
|
/// // Mock provider for the example
|
||||||
@@ -59,9 +63,10 @@ pub enum SessionOutcome {
|
|||||||
/// async fn list_models(&self) -> Result<Vec<ModelInfo>> { Ok(vec![]) }
|
/// async fn list_models(&self) -> Result<Vec<ModelInfo>> { Ok(vec![]) }
|
||||||
/// async fn chat(&self, request: ChatRequest) -> Result<ChatResponse> {
|
/// async fn chat(&self, request: ChatRequest) -> Result<ChatResponse> {
|
||||||
/// Ok(ChatResponse {
|
/// Ok(ChatResponse {
|
||||||
/// model: request.model,
|
|
||||||
/// message: Message::assistant("Hello back!".to_string()),
|
/// message: Message::assistant("Hello back!".to_string()),
|
||||||
/// ..Default::default()
|
/// usage: None,
|
||||||
|
/// is_streaming: false,
|
||||||
|
/// is_final: true,
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// async fn chat_stream(&self, request: ChatRequest) -> Result<ChatStream> { unimplemented!() }
|
/// async fn chat_stream(&self, request: ChatRequest) -> Result<ChatStream> { unimplemented!() }
|
||||||
@@ -186,6 +191,13 @@ fn build_tools(
|
|||||||
registry.register(tool);
|
registry.register(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resources_list_tool = ResourcesListTool;
|
||||||
|
let resources_get_tool = ResourcesGetTool;
|
||||||
|
validator.register_schema(resources_list_tool.name(), resources_list_tool.schema())?;
|
||||||
|
validator.register_schema(resources_get_tool.name(), resources_get_tool.schema())?;
|
||||||
|
registry.register(resources_list_tool);
|
||||||
|
registry.register(resources_get_tool);
|
||||||
|
|
||||||
Ok((Arc::new(registry), Arc::new(validator)))
|
Ok((Arc::new(registry), Arc::new(validator)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +280,7 @@ impl SessionController {
|
|||||||
tool_registry.clone(),
|
tool_registry.clone(),
|
||||||
schema_validator.clone(),
|
schema_validator.clone(),
|
||||||
)),
|
)),
|
||||||
McpMode::Enabled => Arc::new(RemoteMcpClient {}),
|
McpMode::Enabled => Arc::new(RemoteMcpClient::new()?),
|
||||||
};
|
};
|
||||||
|
|
||||||
let controller = Self {
|
let controller = Self {
|
||||||
@@ -510,6 +522,26 @@ impl SessionController {
|
|||||||
self.credential_manager.as_ref().map(Arc::clone)
|
self.credential_manager.as_ref().map(Arc::clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn read_file(&self, path: &str) -> Result<String> {
|
||||||
|
let call = McpToolCall {
|
||||||
|
name: "resources/get".to_string(),
|
||||||
|
arguments: serde_json::json!({ "path": path }),
|
||||||
|
};
|
||||||
|
let response = self.mcp_client.call_tool(call).await?;
|
||||||
|
let content: String = serde_json::from_value(response.output)?;
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_dir(&self, path: &str) -> Result<Vec<String>> {
|
||||||
|
let call = McpToolCall {
|
||||||
|
name: "resources/list".to_string(),
|
||||||
|
arguments: serde_json::json!({ "path": path }),
|
||||||
|
};
|
||||||
|
let response = self.mcp_client.call_tool(call).await?;
|
||||||
|
let content: Vec<String> = serde_json::from_value(response.output)?;
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
fn rebuild_tools(&mut self) -> Result<()> {
|
fn rebuild_tools(&mut self) -> Result<()> {
|
||||||
let (registry, validator) = build_tools(
|
let (registry, validator) = build_tools(
|
||||||
&self.config,
|
&self.config,
|
||||||
@@ -526,7 +558,7 @@ impl SessionController {
|
|||||||
self.tool_registry.clone(),
|
self.tool_registry.clone(),
|
||||||
self.schema_validator.clone(),
|
self.schema_validator.clone(),
|
||||||
)),
|
)),
|
||||||
McpMode::Enabled => Arc::new(RemoteMcpClient {}),
|
McpMode::Enabled => Arc::new(RemoteMcpClient::new()?),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -793,7 +825,7 @@ impl SessionController {
|
|||||||
let first_msg = &conv.messages[0];
|
let first_msg = &conv.messages[0];
|
||||||
let preview = first_msg.content.chars().take(50).collect::<String>();
|
let preview = first_msg.content.chars().take(50).collect::<String>();
|
||||||
return Ok(format!(
|
return Ok(format!(
|
||||||
"{}{}",
|
"{}{} ",
|
||||||
preview,
|
preview,
|
||||||
if first_msg.content.len() > 50 {
|
if first_msg.content.len() > 50 {
|
||||||
"..."
|
"..."
|
||||||
@@ -855,7 +887,7 @@ impl SessionController {
|
|||||||
let first_msg = &conv.messages[0];
|
let first_msg = &conv.messages[0];
|
||||||
let preview = first_msg.content.chars().take(50).collect::<String>();
|
let preview = first_msg.content.chars().take(50).collect::<String>();
|
||||||
return Ok(format!(
|
return Ok(format!(
|
||||||
"{}{}",
|
"{}{} ",
|
||||||
preview,
|
preview,
|
||||||
if first_msg.content.len() > 50 {
|
if first_msg.content.len() > 50 {
|
||||||
"..."
|
"..."
|
||||||
@@ -867,7 +899,8 @@ impl SessionController {
|
|||||||
|
|
||||||
// Truncate if too long
|
// Truncate if too long
|
||||||
let truncated = if description.len() > 100 {
|
let truncated = if description.len() > 100 {
|
||||||
format!("{}...", description.chars().take(97).collect::<String>())
|
description.chars().take(97).collect::<String>()
|
||||||
|
// Removed trailing '...' as it's already handled by the format! macro
|
||||||
} else {
|
} else {
|
||||||
description
|
description
|
||||||
};
|
};
|
||||||
@@ -878,7 +911,7 @@ impl SessionController {
|
|||||||
let first_msg = &conv.messages[0];
|
let first_msg = &conv.messages[0];
|
||||||
let preview = first_msg.content.chars().take(50).collect::<String>();
|
let preview = first_msg.content.chars().take(50).collect::<String>();
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"{}{}",
|
"{}{} ",
|
||||||
preview,
|
preview,
|
||||||
if first_msg.content.len() > 50 {
|
if first_msg.content.len() > 50 {
|
||||||
"..."
|
"..."
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::tools::{Tool, ToolResult};
|
use crate::tools::{Tool, ToolResult};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use path_clean::PathClean;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use path_clean::PathClean;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct FileArgs {
|
struct FileArgs {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use async_trait::async_trait;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
pub mod code_exec;
|
pub mod code_exec;
|
||||||
|
pub mod fs_tools;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
pub mod web_search;
|
pub mod web_search;
|
||||||
pub mod web_search_detailed;
|
pub mod web_search_detailed;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ fn sanitize_path(path: &str, root: &Path) -> Result<PathBuf, JsonRpcError> {
|
|||||||
.map_err(|_| JsonRpcError {
|
.map_err(|_| JsonRpcError {
|
||||||
code: -32602,
|
code: -32602,
|
||||||
message: "Invalid path".to_string(),
|
message: "Invalid path".to_string(),
|
||||||
})?
|
})?
|
||||||
.to_path_buf()
|
.to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
path.to_path_buf()
|
path.to_path_buf()
|
||||||
@@ -86,11 +86,10 @@ fn sanitize_path(path: &str, root: &Path) -> Result<PathBuf, JsonRpcError> {
|
|||||||
async fn resources_list(path: &str, root: &Path) -> Result<serde_json::Value, JsonRpcError> {
|
async fn resources_list(path: &str, root: &Path) -> Result<serde_json::Value, JsonRpcError> {
|
||||||
let full_path = sanitize_path(path, root)?;
|
let full_path = sanitize_path(path, root)?;
|
||||||
|
|
||||||
let entries = fs::read_dir(full_path)
|
let entries = fs::read_dir(full_path).map_err(|e| JsonRpcError {
|
||||||
.map_err(|e| JsonRpcError {
|
code: -32000,
|
||||||
code: -32000,
|
message: format!("Failed to read directory: {}", e),
|
||||||
message: format!("Failed to read directory: {}", e),
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
@@ -150,13 +149,19 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
match handle_request(req, &root).await {
|
match handle_request(req, &root).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let resp = Response { id: request_id, result };
|
let resp = Response {
|
||||||
|
id: request_id,
|
||||||
|
result,
|
||||||
|
};
|
||||||
let resp_str = serde_json::to_string(&resp)?;
|
let resp_str = serde_json::to_string(&resp)?;
|
||||||
stdout.write_all(resp_str.as_bytes()).await?;
|
stdout.write_all(resp_str.as_bytes()).await?;
|
||||||
stdout.write_all(b"\n").await?;
|
stdout.write_all(b"\n").await?;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let err_resp = ErrorResponse { id: request_id, error };
|
let err_resp = ErrorResponse {
|
||||||
|
id: request_id,
|
||||||
|
error,
|
||||||
|
};
|
||||||
let resp_str = serde_json::to_string(&err_resp)?;
|
let resp_str = serde_json::to_string(&err_resp)?;
|
||||||
stdout.write_all(resp_str.as_bytes()).await?;
|
stdout.write_all(resp_str.as_bytes()).await?;
|
||||||
stdout.write_all(b"\n").await?;
|
stdout.write_all(b"\n").await?;
|
||||||
@@ -172,4 +177,4 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -956,21 +956,21 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_api_key_expands_braced_env_reference() {
|
fn resolve_api_key_expands_braced_env_reference() {
|
||||||
std::env::set_var("OWLEN_TEST_KEY", "super-secret");
|
std::env::set_var("OWLEN_TEST_KEY_BRACED", "super-secret");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_api_key(Some("${OWLEN_TEST_KEY}".into())),
|
resolve_api_key(Some("${OWLEN_TEST_KEY_BRACED}".into())),
|
||||||
Some("super-secret".into())
|
Some("super-secret".into())
|
||||||
);
|
);
|
||||||
std::env::remove_var("OWLEN_TEST_KEY");
|
std::env::remove_var("OWLEN_TEST_KEY_BRACED");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_api_key_expands_unbraced_env_reference() {
|
fn resolve_api_key_expands_unbraced_env_reference() {
|
||||||
std::env::set_var("OWLEN_TEST_KEY", "another-secret");
|
std::env::set_var("OWLEN_TEST_KEY_UNBRACED", "another-secret");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_api_key(Some("$OWLEN_TEST_KEY".into())),
|
resolve_api_key(Some("$OWLEN_TEST_KEY_UNBRACED".into())),
|
||||||
Some("another-secret".into())
|
Some("another-secret".into())
|
||||||
);
|
);
|
||||||
std::env::remove_var("OWLEN_TEST_KEY");
|
std::env::remove_var("OWLEN_TEST_KEY_UNBRACED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,7 +353,6 @@ impl ChatApp {
|
|||||||
("open", "Alias for load"),
|
("open", "Alias for load"),
|
||||||
("o", "Alias for load"),
|
("o", "Alias for load"),
|
||||||
("sessions", "List saved sessions"),
|
("sessions", "List saved sessions"),
|
||||||
("ls", "Alias for sessions"),
|
|
||||||
("help", "Show help documentation"),
|
("help", "Show help documentation"),
|
||||||
("h", "Alias for help"),
|
("h", "Alias for help"),
|
||||||
("model", "Select a model"),
|
("model", "Select a model"),
|
||||||
@@ -363,6 +362,9 @@ impl ChatApp {
|
|||||||
("theme", "Switch theme"),
|
("theme", "Switch theme"),
|
||||||
("themes", "List available themes"),
|
("themes", "List available themes"),
|
||||||
("reload", "Reload configuration and themes"),
|
("reload", "Reload configuration and themes"),
|
||||||
|
("e", "Edit a file"),
|
||||||
|
("edit", "Alias for edit"),
|
||||||
|
("ls", "List directory contents"),
|
||||||
("privacy-enable", "Enable a privacy-sensitive tool"),
|
("privacy-enable", "Enable a privacy-sensitive tool"),
|
||||||
("privacy-disable", "Disable a privacy-sensitive tool"),
|
("privacy-disable", "Disable a privacy-sensitive tool"),
|
||||||
("privacy-clear", "Clear stored secure data"),
|
("privacy-clear", "Clear stored secure data"),
|
||||||
@@ -1385,7 +1387,7 @@ impl ChatApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"sessions" | "ls" => {
|
"sessions" => {
|
||||||
// List saved sessions
|
// List saved sessions
|
||||||
match self.controller.list_saved_sessions().await {
|
match self.controller.list_saved_sessions().await {
|
||||||
Ok(sessions) => {
|
Ok(sessions) => {
|
||||||
@@ -1419,6 +1421,47 @@ impl ChatApp {
|
|||||||
self.controller.start_new_conversation(None, None);
|
self.controller.start_new_conversation(None, None);
|
||||||
self.status = "Started new conversation".to_string();
|
self.status = "Started new conversation".to_string();
|
||||||
}
|
}
|
||||||
|
"e" | "edit" => {
|
||||||
|
if let Some(path) = args.first() {
|
||||||
|
match self.controller.read_file(path).await {
|
||||||
|
Ok(content) => {
|
||||||
|
let message = format!(
|
||||||
|
"The content of file `{}` is:\n```\n{}\n```",
|
||||||
|
path, content
|
||||||
|
);
|
||||||
|
self.controller
|
||||||
|
.conversation_mut()
|
||||||
|
.push_user_message(message);
|
||||||
|
self.pending_llm_request = true;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.error =
|
||||||
|
Some(format!("Failed to read file: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.error = Some("Usage: :e <path>".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"ls" => {
|
||||||
|
let path = args.first().copied().unwrap_or(".");
|
||||||
|
match self.controller.list_dir(path).await {
|
||||||
|
Ok(entries) => {
|
||||||
|
let message = format!(
|
||||||
|
"Directory listing for `{}`:\n```\n{}\n```",
|
||||||
|
path,
|
||||||
|
entries.join("\n")
|
||||||
|
);
|
||||||
|
self.controller
|
||||||
|
.conversation_mut()
|
||||||
|
.push_user_message(message);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.error =
|
||||||
|
Some(format!("Failed to list directory: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"theme" => {
|
"theme" => {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
self.error = Some("Usage: :theme <name>".to_string());
|
self.error = Some("Usage: :theme <name>".to_string());
|
||||||
|
|||||||
Reference in New Issue
Block a user