chore: release v0.2.2
- Fix session save serialization error handling - Cache web_search regex patterns with OnceLock for performance - Improve panic messages in tool_parser regex compilation - Add retroactive CHANGELOG entry for v0.2.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
+15
-1
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.2] - 2026-01-22
|
||||
|
||||
### Fixed
|
||||
- Session save no longer panics on serialization errors
|
||||
- Web search regex patterns are now cached for better performance
|
||||
- Improved panic messages for regex compilation failures
|
||||
|
||||
## [0.2.1] - 2026-01-22
|
||||
|
||||
### Fixed
|
||||
- Resolve clippy warnings for Rust 1.92
|
||||
|
||||
## [0.2.0] - 2026-01-20
|
||||
|
||||
### Changed
|
||||
@@ -99,7 +111,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Hooks system and config profiles
|
||||
- Example skills and launch assets
|
||||
|
||||
[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.0...HEAD
|
||||
[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.2...HEAD
|
||||
[0.2.2]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.1...v0.2.2
|
||||
[0.2.1]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.2.0...v0.2.1
|
||||
[0.2.0]: https://github.com/Hmbown/DeepSeek-TUI/releases/tag/v0.2.0
|
||||
[0.0.2]: https://github.com/Hmbown/DeepSeek-TUI/releases/tag/v0.0.2
|
||||
[0.0.1]: https://github.com/Hmbown/DeepSeek-CLI/releases/tag/v0.0.1
|
||||
|
||||
Generated
+1
-1
@@ -646,7 +646,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-tui"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arboard",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deepseek-tui"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2024"
|
||||
description = "Unofficial DeepSeek CLI - Just run 'deepseek' to start chatting"
|
||||
license = "MIT"
|
||||
|
||||
@@ -37,7 +37,11 @@ pub fn save(app: &mut App, path: Option<&str>) -> CommandResult {
|
||||
|
||||
match std::fs::create_dir_all(&sessions_dir) {
|
||||
Ok(()) => {
|
||||
match std::fs::write(&save_path, serde_json::to_string_pretty(&session).unwrap()) {
|
||||
let json = match serde_json::to_string_pretty(&session) {
|
||||
Ok(j) => j,
|
||||
Err(e) => return CommandResult::error(format!("Failed to serialize session: {e}")),
|
||||
};
|
||||
match std::fs::write(&save_path, json) {
|
||||
Ok(()) => {
|
||||
app.current_session_id = Some(session.metadata.id.clone());
|
||||
CommandResult::message(format!(
|
||||
|
||||
@@ -53,7 +53,8 @@ static THINKING_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
fn get_tool_call_regex() -> &'static Regex {
|
||||
TOOL_CALL_REGEX.get_or_init(|| {
|
||||
// Match [TOOL_CALL] ... [/TOOL_CALL] blocks
|
||||
Regex::new(r"(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]").unwrap()
|
||||
Regex::new(r"(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]")
|
||||
.expect("TOOL_CALL regex pattern is valid")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,21 +62,22 @@ fn get_xml_tool_call_regex() -> &'static Regex {
|
||||
XML_TOOL_CALL_REGEX.get_or_init(|| {
|
||||
// Match <deepseek:tool_call>...</deepseek:tool_call> or similar XML patterns
|
||||
Regex::new(r"(?s)<(?:deepseek:)?tool_call[^>]*>\s*(.*?)\s*</(?:deepseek:)?tool_call>")
|
||||
.unwrap()
|
||||
.expect("XML tool_call regex pattern is valid")
|
||||
})
|
||||
}
|
||||
|
||||
fn get_invoke_regex() -> &'static Regex {
|
||||
INVOKE_REGEX.get_or_init(|| {
|
||||
// Match <invoke name="tool_name">...</invoke> patterns
|
||||
Regex::new(r#"(?s)<invoke\s+name\s*=\s*"([^"]+)"[^>]*>(.*?)</invoke>"#).unwrap()
|
||||
Regex::new(r#"(?s)<invoke\s+name\s*=\s*"([^"]+)"[^>]*>(.*?)</invoke>"#)
|
||||
.expect("invoke regex pattern is valid")
|
||||
})
|
||||
}
|
||||
|
||||
fn get_thinking_regex() -> &'static Regex {
|
||||
THINKING_REGEX.get_or_init(|| {
|
||||
// Match thinking blocks including partial closing tags
|
||||
Regex::new(r"(?s)</?(?:think|thinking)[^>]*>").unwrap()
|
||||
Regex::new(r"(?s)</?(?:think|thinking)[^>]*>").expect("thinking regex pattern is valid")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+29
-8
@@ -8,8 +8,34 @@ use async_trait::async_trait;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
// Cached regex patterns for HTML parsing
|
||||
static TITLE_RE: OnceLock<Regex> = OnceLock::new();
|
||||
static SNIPPET_RE: OnceLock<Regex> = OnceLock::new();
|
||||
static TAG_RE: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
fn get_title_re() -> &'static Regex {
|
||||
TITLE_RE.get_or_init(|| {
|
||||
Regex::new(r#"<a[^>]*class=\"result__a\"[^>]*href=\"([^\"]+)\"[^>]*>(.*?)</a>"#)
|
||||
.expect("title regex pattern is valid")
|
||||
})
|
||||
}
|
||||
|
||||
fn get_snippet_re() -> &'static Regex {
|
||||
SNIPPET_RE.get_or_init(|| {
|
||||
Regex::new(
|
||||
r#"<a[^>]*class=\"result__snippet\"[^>]*>(.*?)</a>|<div[^>]*class=\"result__snippet\"[^>]*>(.*?)</div>"#,
|
||||
)
|
||||
.expect("snippet regex pattern is valid")
|
||||
})
|
||||
}
|
||||
|
||||
fn get_tag_re() -> &'static Regex {
|
||||
TAG_RE.get_or_init(|| Regex::new(r"<[^>]+>").expect("tag regex pattern is valid"))
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_RESULTS: usize = 5;
|
||||
const MAX_RESULTS: usize = 10;
|
||||
const DEFAULT_TIMEOUT_MS: u64 = 15_000;
|
||||
@@ -140,12 +166,8 @@ impl ToolSpec for WebSearchTool {
|
||||
}
|
||||
|
||||
fn parse_duckduckgo_results(html: &str, max_results: usize) -> Vec<WebSearchEntry> {
|
||||
let title_re =
|
||||
Regex::new(r#"<a[^>]*class=\"result__a\"[^>]*href=\"([^\"]+)\"[^>]*>(.*?)</a>"#).unwrap();
|
||||
let snippet_re = Regex::new(
|
||||
r#"<a[^>]*class=\"result__snippet\"[^>]*>(.*?)</a>|<div[^>]*class=\"result__snippet\"[^>]*>(.*?)</div>"#,
|
||||
)
|
||||
.unwrap();
|
||||
let title_re = get_title_re();
|
||||
let snippet_re = get_snippet_re();
|
||||
let snippets: Vec<String> = snippet_re
|
||||
.captures_iter(html)
|
||||
.filter_map(|cap| cap.get(1).or_else(|| cap.get(2)))
|
||||
@@ -202,8 +224,7 @@ fn normalize_text(text: &str) -> String {
|
||||
}
|
||||
|
||||
fn strip_html_tags(text: &str) -> String {
|
||||
let tag_re = Regex::new(r"<[^>]+>").unwrap();
|
||||
tag_re.replace_all(text, "").to_string()
|
||||
get_tag_re().replace_all(text, "").to_string()
|
||||
}
|
||||
|
||||
fn decode_html_entities(text: &str) -> String {
|
||||
|
||||
Reference in New Issue
Block a user