feat(tui): add markdown rendering support and toggle command
- Introduce new `owlen-markdown` crate that converts Markdown strings to `ratatui::Text` with headings, lists, bold/italic, and inline code. - Add `render_markdown` config option (default true) and expose it via `app.render_markdown_enabled()`. - Implement `:markdown [on|off]` command to toggle markdown rendering. - Update help overlay to document the new markdown toggle. - Adjust UI rendering to conditionally apply markdown styling based on the markdown flag and code mode. - Wire the new crate into `owlen-tui` Cargo.toml.
This commit is contained in:
@@ -221,10 +221,11 @@ fn ensure_provider_entry(config: &mut Config, provider: &str, endpoint: &str) {
|
||||
if provider == "ollama"
|
||||
&& config.providers.contains_key("ollama-cloud")
|
||||
&& !config.providers.contains_key("ollama")
|
||||
&& let Some(mut legacy) = config.providers.remove("ollama-cloud")
|
||||
{
|
||||
legacy.provider_type = "ollama".to_string();
|
||||
config.providers.insert("ollama".to_string(), legacy);
|
||||
if let Some(mut legacy) = config.providers.remove("ollama-cloud") {
|
||||
legacy.provider_type = "ollama".to_string();
|
||||
config.providers.insert("ollama".to_string(), legacy);
|
||||
}
|
||||
}
|
||||
|
||||
core_config::ensure_provider_config(config, provider);
|
||||
@@ -315,8 +316,10 @@ fn unlock_vault(path: &Path) -> Result<encryption::VaultHandle> {
|
||||
use std::env;
|
||||
|
||||
if path.exists() {
|
||||
if let Ok(password) = env::var("OWLEN_MASTER_PASSWORD")
|
||||
&& !password.trim().is_empty()
|
||||
if let Some(password) = env::var("OWLEN_MASTER_PASSWORD")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|password| !password.is_empty())
|
||||
{
|
||||
return encryption::unlock_with_password(path.to_path_buf(), &password)
|
||||
.context("Failed to unlock vault with OWLEN_MASTER_PASSWORD");
|
||||
@@ -356,30 +359,31 @@ async fn hydrate_api_key(
|
||||
config: &mut Config,
|
||||
manager: Option<&Arc<CredentialManager>>,
|
||||
) -> Result<Option<String>> {
|
||||
if let Some(manager) = manager
|
||||
&& let Some(credentials) = manager.get_credentials(OLLAMA_CLOUD_CREDENTIAL_ID).await?
|
||||
{
|
||||
let credentials = match manager {
|
||||
Some(manager) => manager.get_credentials(OLLAMA_CLOUD_CREDENTIAL_ID).await?,
|
||||
None => None,
|
||||
};
|
||||
|
||||
if let Some(credentials) = credentials {
|
||||
let key = credentials.api_key.trim().to_string();
|
||||
if !key.is_empty() {
|
||||
set_env_if_missing("OLLAMA_API_KEY", &key);
|
||||
set_env_if_missing("OLLAMA_CLOUD_API_KEY", &key);
|
||||
}
|
||||
|
||||
if let Some(cfg) = provider_entry_mut(config)
|
||||
&& cfg.base_url.is_none()
|
||||
&& !credentials.endpoint.trim().is_empty()
|
||||
{
|
||||
cfg.base_url = Some(credentials.endpoint);
|
||||
let Some(cfg) = provider_entry_mut(config) else {
|
||||
return Ok(Some(key));
|
||||
};
|
||||
if cfg.base_url.is_none() && !credentials.endpoint.trim().is_empty() {
|
||||
cfg.base_url = Some(credentials.endpoint.clone());
|
||||
}
|
||||
return Ok(Some(key));
|
||||
}
|
||||
|
||||
if let Some(cfg) = provider_entry(config)
|
||||
&& let Some(key) = cfg
|
||||
.api_key
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
if let Some(key) = provider_entry(config)
|
||||
.and_then(|cfg| cfg.api_key.as_ref())
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
set_env_if_missing("OLLAMA_API_KEY", key);
|
||||
set_env_if_missing("OLLAMA_CLOUD_API_KEY", key);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::collapsible_if)] // TODO: Remove once Rust 2024 let-chains are available
|
||||
|
||||
//! OWLEN CLI - Chat TUI client
|
||||
|
||||
mod cloud;
|
||||
|
||||
@@ -151,8 +151,9 @@ fn handle_list(args: ListArgs) -> Result<()> {
|
||||
"", "Scope", "Name", "Transport"
|
||||
);
|
||||
for entry in scoped {
|
||||
if let Some(target_scope) = filter_scope
|
||||
&& entry.scope != target_scope
|
||||
if filter_scope
|
||||
.as_ref()
|
||||
.is_some_and(|target_scope| entry.scope != *target_scope)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -186,8 +187,9 @@ fn handle_list(args: ListArgs) -> Result<()> {
|
||||
.collect();
|
||||
|
||||
for entry in scoped_resources {
|
||||
if let Some(target_scope) = filter_scope
|
||||
&& entry.scope != target_scope
|
||||
if filter_scope
|
||||
.as_ref()
|
||||
.is_some_and(|target_scope| entry.scope != *target_scope)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user