Merge pull request 'dev' (#31) from dev into main

Reviewed-on: #31
This commit was merged in pull request #31.
This commit is contained in:
2025-10-05 02:33:02 +02:00
14 changed files with 196 additions and 75 deletions

34
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,34 @@
# Pre-commit hooks configuration
# See https://pre-commit.com for more information
repos:
# General file checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: check-added-large-files
args: ['--maxkb=1000']
- id: mixed-line-ending
# Rust formatting
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
name: cargo fmt
description: Format Rust code with rustfmt
- id: cargo-check
name: cargo check
description: Check Rust code compilation
- id: clippy
name: cargo clippy
description: Lint Rust code with clippy
args: ['--all-features', '--', '-D', 'warnings']
# Optional: run on all files when config changes
default_install_hook_types: [pre-commit, pre-push]

View File

@@ -78,4 +78,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **AUR Package**: Owlen is now available on the Arch User Repository. - **AUR Package**: Owlen is now available on the Arch User Repository.
### Changed ### Changed
- **Build System**: Switched from OpenSSL to rustls for better cross-platform compatibility. - **Build System**: Switched from OpenSSL to rustls for better cross-platform compatibility.

View File

@@ -32,15 +32,14 @@ Unsure where to begin contributing to Owlen? You can start by looking through `g
The process for submitting a pull request is as follows: The process for submitting a pull request is as follows:
1. **Fork the repository** and create your branch from `main`. 1. **Fork the repository** and create your branch from `main`.
2. **Make your changes.** 2. **Set up pre-commit hooks** (see [Development Setup](#development-setup) above). This will automatically format and lint your code.
3. **Ensure the code lints and formats correctly.** 3. **Make your changes.**
- `cargo fmt --all`
- `cargo clippy --all -- -D warnings`
4. **Run the tests.** 4. **Run the tests.**
- `cargo test --all` - `cargo test --all`
5. **Add a clear, concise commit message.** We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. 5. **Commit your changes.** The pre-commit hooks will automatically run `cargo fmt`, `cargo check`, and `cargo clippy`. If you need to bypass the hooks (not recommended), use `git commit --no-verify`.
6. **Push to your fork** and submit a pull request to Owlen's `main` branch. 6. **Add a clear, concise commit message.** We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
7. **Include a clear description** of the problem and solution. Include the relevant issue number if applicable. 7. **Push to your fork** and submit a pull request to Owlen's `main` branch.
8. **Include a clear description** of the problem and solution. Include the relevant issue number if applicable.
## Development Setup ## Development Setup
@@ -52,6 +51,46 @@ cd owlen
cargo build cargo build
``` ```
### Pre-commit Hooks
We use [pre-commit](https://pre-commit.com/) to automatically run formatting and linting checks before each commit. This helps maintain code quality and consistency.
**Install pre-commit:**
```sh
# Arch Linux
sudo pacman -S pre-commit
# Other Linux/macOS
pip install pre-commit
# Verify installation
pre-commit --version
```
**Setup the hooks:**
```sh
cd owlen
pre-commit install
```
Once installed, the hooks will automatically run on every commit. You can also run them manually:
```sh
# Run on all files
pre-commit run --all-files
# Run on staged files only
pre-commit run
```
The pre-commit hooks will check:
- Code formatting (`cargo fmt`)
- Compilation (`cargo check`)
- Linting (`cargo clippy --all-features`)
- General file hygiene (trailing whitespace, EOF newlines, etc.)
## Coding Style ## Coding Style
- We use `cargo fmt` for automated code formatting. Please run it before committing your changes. - We use `cargo fmt` for automated code formatting. Please run it before committing your changes.

View File

@@ -659,4 +659,3 @@ specific requirements.
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.

View File

@@ -47,4 +47,3 @@ package() {
install -Dm644 "$theme" "$pkgdir/usr/share/$pkgname/themes/$(basename $theme)" install -Dm644 "$theme" "$pkgdir/usr/share/$pkgname/themes/$(basename $theme)"
done done
} }

View File

@@ -31,4 +31,4 @@ uuid = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
[dev-dependencies] [dev-dependencies]
tokio-test = { workspace = true } tokio-test = { workspace = true }

View File

@@ -1171,14 +1171,8 @@ impl ChatApp {
let description = let description =
if self.controller.config().storage.generate_descriptions { if self.controller.config().storage.generate_descriptions {
self.status = "Generating description...".to_string(); self.status = "Generating description...".to_string();
match self (self.controller.generate_conversation_description().await)
.controller .ok()
.generate_conversation_description()
.await
{
Ok(desc) => Some(desc),
Err(_) => None,
}
} else { } else {
None None
}; };
@@ -1780,7 +1774,7 @@ impl ChatApp {
stream, stream,
}) => { }) => {
// Step 3: Model loaded, now generating response // Step 3: Model loaded, now generating response
self.status = format!("Model loaded. Generating response... (streaming)"); self.status = "Model loaded. Generating response... (streaming)".to_string();
self.spawn_stream(response_id, stream); self.spawn_stream(response_id, stream);
match self.controller.mark_stream_placeholder(response_id, "") { match self.controller.mark_stream_placeholder(response_id, "") {

View File

@@ -1149,7 +1149,14 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) {
frame.render_widget(Clear, area); frame.render_widget(Clear, area);
let tab_index = app.help_tab_index(); let tab_index = app.help_tab_index();
let tabs = vec!["Navigation", "Editing", "Visual", "Commands", "Sessions", "Browsers"]; let tabs = [
"Navigation",
"Editing",
"Visual",
"Commands",
"Sessions",
"Browsers",
];
// Build tab line // Build tab line
let mut tab_spans = Vec::new(); let mut tab_spans = Vec::new();
@@ -1284,102 +1291,138 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) {
3 => vec![ 3 => vec![
// Commands // Commands
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("COMMAND MODE", Style::default().add_modifier(Modifier::BOLD).fg(theme.info)) "COMMAND MODE",
]), Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
)]),
Line::from(" Press ':' to enter command mode, then type one of:"), Line::from(" Press ':' to enter command mode, then type one of:"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("KEYBINDINGS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "KEYBINDINGS",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" Enter → execute command"), Line::from(" Enter → execute command"),
Line::from(" Esc → exit command mode"), Line::from(" Esc → exit command mode"),
Line::from(" Tab → autocomplete suggestion"), Line::from(" Tab → autocomplete suggestion"),
Line::from(" ↑/↓ → navigate suggestions"), Line::from(" ↑/↓ → navigate suggestions"),
Line::from(" Backspace → delete character"), Line::from(" Backspace → delete character"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("GENERAL", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "GENERAL",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" :h, :help → show this help"), Line::from(" :h, :help → show this help"),
Line::from(" :q, :quit → quit application"), Line::from(" :q, :quit → quit application"),
Line::from(" :reload → reload configuration and themes"), Line::from(" :reload → reload configuration and themes"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("CONVERSATION", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "CONVERSATION",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" :n, :new → start new conversation"), Line::from(" :n, :new → start new conversation"),
Line::from(" :c, :clear → clear current conversation"), Line::from(" :c, :clear → clear current conversation"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("MODEL & THEME", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "MODEL & THEME",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" :m, :model → open model selector"), Line::from(" :m, :model → open model selector"),
Line::from(" :themes → open theme selector"), Line::from(" :themes → open theme selector"),
Line::from(" :theme <name> → switch to a specific theme"), Line::from(" :theme <name> → switch to a specific theme"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("SESSION MANAGEMENT", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "SESSION MANAGEMENT",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" :save [name] → save current session (with optional name)"), Line::from(" :save [name] → save current session (with optional name)"),
Line::from(" :w [name] → alias for :save"), Line::from(" :w [name] → alias for :save"),
Line::from(" :load, :o, :open → browse and load saved sessions"), Line::from(" :load, :o, :open → browse and load saved sessions"),
Line::from(" :sessions, :ls → browse saved sessions"), Line::from(" :sessions, :ls → browse saved sessions"),
], ],
4 => vec![ // Sessions 4 => vec![
// Sessions
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("SESSION MANAGEMENT", Style::default().add_modifier(Modifier::BOLD).fg(theme.info)) "SESSION MANAGEMENT",
]), Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
)]),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("SAVING SESSIONS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "SAVING SESSIONS",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" :save → save with auto-generated name"), Line::from(" :save → save with auto-generated name"),
Line::from(" :save my-session → save with custom name"), Line::from(" :save my-session → save with custom name"),
Line::from(" • AI generates description automatically (configurable)"), Line::from(" • AI generates description automatically (configurable)"),
Line::from(" • Sessions stored in platform-specific directories"), Line::from(" • Sessions stored in platform-specific directories"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("LOADING SESSIONS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "LOADING SESSIONS",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" :load, :o, :open → browse and select session"), Line::from(" :load, :o, :open → browse and select session"),
Line::from(" :sessions, :ls → browse saved sessions"), Line::from(" :sessions, :ls → browse saved sessions"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("SESSION BROWSER KEYS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "SESSION BROWSER KEYS",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" j/k or ↑/↓ → navigate sessions"), Line::from(" j/k or ↑/↓ → navigate sessions"),
Line::from(" Enter → load selected session"), Line::from(" Enter → load selected session"),
Line::from(" d → delete selected session"), Line::from(" d → delete selected session"),
Line::from(" Esc → close browser"), Line::from(" Esc → close browser"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("STORAGE LOCATIONS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role)) "STORAGE LOCATIONS",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.user_message_role),
)]),
Line::from(" Linux → ~/.local/share/owlen/sessions"), Line::from(" Linux → ~/.local/share/owlen/sessions"),
Line::from(" Windows → %APPDATA%\\owlen\\sessions"), Line::from(" Windows → %APPDATA%\\owlen\\sessions"),
Line::from(" macOS → ~/Library/Application Support/owlen/sessions"), Line::from(" macOS → ~/Library/Application Support/owlen/sessions"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("CONTEXT PRESERVATION", Style::default().add_modifier(Modifier::BOLD).fg(theme.assistant_message_role)) "CONTEXT PRESERVATION",
]), Style::default()
.add_modifier(Modifier::BOLD)
.fg(theme.assistant_message_role),
)]),
Line::from(" • Full conversation history is preserved when saving"), Line::from(" • Full conversation history is preserved when saving"),
Line::from(" • All context is restored when loading a session"), Line::from(" • All context is restored when loading a session"),
Line::from(" • Continue conversations seamlessly across restarts"), Line::from(" • Continue conversations seamlessly across restarts"),
], ],
5 => vec![ // Browsers 5 => vec![
// Browsers
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("PROVIDER & MODEL BROWSERS", Style::default().add_modifier(Modifier::BOLD).fg(theme.info)) "PROVIDER & MODEL BROWSERS",
]), Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
)]),
Line::from(" Enter → select item"), Line::from(" Enter → select item"),
Line::from(" Esc → close browser"), Line::from(" Esc → close browser"),
Line::from(" ↑/↓ or j/k → navigate items"), Line::from(" ↑/↓ or j/k → navigate items"),
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![Span::styled(
Span::styled("THEME BROWSER", Style::default().add_modifier(Modifier::BOLD).fg(theme.info)) "THEME BROWSER",
]), Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
)]),
Line::from(" Enter → apply theme"), Line::from(" Enter → apply theme"),
Line::from(" Esc / q → close browser"), Line::from(" Esc / q → close browser"),
Line::from(" ↑/↓ or j/k → navigate themes"), Line::from(" ↑/↓ or j/k → navigate themes"),
@@ -1430,7 +1473,12 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) {
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
), ),
Span::raw(":Switch "), Span::raw(":Switch "),
Span::styled("1-6", Style::default().fg(theme.focused_panel_border).add_modifier(Modifier::BOLD)), Span::styled(
"1-6",
Style::default()
.fg(theme.focused_panel_border)
.add_modifier(Modifier::BOLD),
),
Span::raw(":Jump "), Span::raw(":Jump "),
Span::styled( Span::styled(
"Esc/q", "Esc/q",

View File

@@ -4,13 +4,13 @@ This guide documents breaking changes between versions of Owlen and provides ins
As Owlen is currently in its alpha phase (pre-v1.0), breaking changes may occur more frequently. We will do our best to document them here. As Owlen is currently in its alpha phase (pre-v1.0), breaking changes may occur more frequently. We will do our best to document them here.
--- ---
## Migrating from v0.1.x to v0.2.x (Example) ## Migrating from v0.1.x to v0.2.x (Example)
*This is a template for a future migration. No breaking changes have occurred yet.* *This is a template for a future migration. No breaking changes have occurred yet.*
Version 0.2.0 introduces a new configuration structure for providers. Version 0.2.0 introduces a new configuration structure for providers.
### Configuration File Changes ### Configuration File Changes

View File

@@ -1,9 +1,9 @@
// This example demonstrates a basic chat interaction without the TUI. // This example demonstrates a basic chat interaction without the TUI.
use owlen_core::model::Model; use owlen_core::model::Model;
use owlen_core::provider::Provider;
use owlen_core::session::Session; use owlen_core::session::Session;
use owlen_ollama::OllamaProvider; // Assuming you have an Ollama provider use owlen_ollama::OllamaProvider; // Assuming you have an Ollama provider
use owlen_core::provider::Provider;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), anyhow::Error> { async fn main() -> Result<(), anyhow::Error> {

View File

@@ -16,10 +16,16 @@ impl Provider for MyCustomProvider {
} }
async fn chat(&self, session: &Session, model: &Model) -> Result<String, anyhow::Error> { async fn chat(&self, session: &Session, model: &Model) -> Result<String, anyhow::Error> {
println!("Custom provider received chat request for model: {}", model.name); println!(
"Custom provider received chat request for model: {}",
model.name
);
// In a real implementation, you would send the session data to an API. // In a real implementation, you would send the session data to an API.
let message_count = session.get_messages().len(); let message_count = session.get_messages().len();
Ok(format!("This is a custom response. You have {} messages in your session.", message_count)) Ok(format!(
"This is a custom response. You have {} messages in your session.",
message_count
))
} }
} }

View File

@@ -23,6 +23,8 @@ fn main() {
println!("\nSession cleared."); println!("\nSession cleared.");
let messages_after_clear = session.get_messages(); let messages_after_clear = session.get_messages();
println!("Messages in session after clear: {}", messages_after_clear.len()); println!(
"Messages in session after clear: {}",
messages_after_clear.len()
);
} }

View File

@@ -20,4 +20,4 @@ selection_fg = "black"
cursor = "#d95f02" cursor = "#d95f02"
placeholder = "gray" placeholder = "gray"
error = "#c0392b" error = "#c0392b"
info = "green" info = "green"

View File

@@ -20,4 +20,4 @@ selection_fg = "#212121"
cursor = "#c2185b" cursor = "#c2185b"
placeholder = "#90a4ae" placeholder = "#90a4ae"
error = "#d32f2f" error = "#d32f2f"
info = "#388e3c" info = "#388e3c"