feat: release v0.77.0 with full pending changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
coso
2026-03-03 01:28:44 +08:00
co-authored by Claude Opus 4.6
parent 0f1be80ba7
commit 4fadd09c7c
56 changed files with 3151 additions and 551 deletions
+1 -1
View File
@@ -25,7 +25,7 @@
- `sysinfo_service.rs` - Tauri 命令桥接(纯逻辑已迁移到 `crates/services/src/sysinfo_service.rs`)
- `file_browser_service.rs` - Tauri 命令桥接(纯逻辑已迁移到 `crates/services/src/file_browser_service.rs`)
- `update_check_service.rs` - 兼容导出层(纯逻辑已迁移到 `crates/services/src/update_check_service.rs`)
- `update_window.rs` - 更新提醒独立窗口管理
- `update_window.rs` - 更新提醒独立窗口管理(底部居中 Toast 窗口定位)
- `general_chat/` - 通用对话服务模块(会话管理、消息存储)
- `api_key_provider_service.rs` - API Key Provider 服务
- `kiro_event_service.rs` - Kiro 事件服务
+68
View File
@@ -479,6 +479,12 @@ pub struct NovelListRunsRequest {
pub limit: Option<i64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NovelDeleteCharacterRequest {
pub project_id: String,
pub character_id: String,
}
#[derive(Clone)]
pub struct NovelService {
db: DbConnection,
@@ -872,6 +878,45 @@ impl NovelService {
Ok(rows)
}
pub fn delete_character(&self, request: NovelDeleteCharacterRequest) -> Result<bool, String> {
self.ensure_project_exists(&request.project_id)?;
let now = chrono::Utc::now().timestamp_millis();
let mut conn = lock_db(&self.db)?;
let tx = conn
.transaction()
.map_err(|e| format!("开启事务失败: {e}"))?;
let exists = tx
.query_row(
"SELECT 1 FROM novel_characters WHERE id = ?1 AND project_id = ?2 LIMIT 1",
params![&request.character_id, &request.project_id],
|_| Ok(()),
)
.optional()
.map_err(|e| format!("检查角色是否存在失败: {e}"))?
.is_some();
if !exists {
tx.commit().map_err(|e| format!("提交事务失败: {e}"))?;
return Ok(false);
}
tx.execute(
"DELETE FROM novel_characters WHERE id = ?1 AND project_id = ?2",
params![&request.character_id, &request.project_id],
)
.map_err(|e| format!("删除角色失败: {e}"))?;
tx.execute(
"UPDATE novel_projects SET updated_at = ?1 WHERE id = ?2",
params![now, &request.project_id],
)
.map_err(|e| format!("更新项目时间失败: {e}"))?;
tx.commit().map_err(|e| format!("提交事务失败: {e}"))?;
Ok(true)
}
fn list_characters(&self, project_id: &str) -> Result<Vec<NovelCharacterRecord>, String> {
let conn = lock_db(&self.db)?;
let mut stmt = conn
@@ -1777,6 +1822,16 @@ fn looks_like_json_noise_line(line: &str) -> bool {
if trimmed.is_empty() {
return true;
}
let lowered = trimmed.to_ascii_lowercase();
if trimmed.starts_with("```")
|| trimmed.ends_with("```")
|| matches!(
lowered.as_str(),
"```" | "```json" | "```yaml" | "```yml" | "json" | "yaml" | "yml"
)
{
return true;
}
if matches!(trimmed, "{" | "}" | "[" | "]" | ",") {
return true;
}
@@ -2660,4 +2715,17 @@ mod tests {
let cards = parse_character_cards(raw);
assert!(cards.is_empty());
}
#[test]
fn parse_character_cards_should_ignore_markdown_fence_noise_in_fallback_lines() {
let raw = r#"
```json
林舟
```
"#;
let cards = parse_character_cards(raw);
assert_eq!(cards.len(), 1);
assert_eq!(cards[0]["name"], Value::String("林舟".to_string()));
}
}
+10 -9
View File
@@ -33,11 +33,11 @@ pub enum UpdateWindowError {
const UPDATE_WINDOW_LABEL: &str = "update-notification";
/// 窗口尺寸
const WINDOW_WIDTH: f64 = 340.0;
const WINDOW_HEIGHT: f64 = 280.0;
const WINDOW_WIDTH: f64 = 620.0;
const WINDOW_HEIGHT: f64 = 132.0;
/// 距离屏幕顶部的边距
const TOP_MARGIN: f64 = 80.0;
/// 距离屏幕底部的边距
const BOTTOM_MARGIN: f64 = 24.0;
/// 获取鼠标所在的显示器
fn get_monitor_at_cursor(app: &AppHandle) -> Option<tauri::Monitor> {
@@ -86,7 +86,7 @@ fn get_monitor_at_cursor(app: &AppHandle) -> Option<tauri::Monitor> {
None
}
/// 计算窗口位置(鼠标所在屏幕的顶部居中)
/// 计算窗口位置(鼠标所在屏幕的底部居中)
fn calculate_window_position(app: &AppHandle) -> (f64, f64) {
// 优先获取鼠标所在的显示器,否则使用主显示器
let monitor = get_monitor_at_cursor(app).or_else(|| app.primary_monitor().ok().flatten());
@@ -98,16 +98,17 @@ fn calculate_window_position(app: &AppHandle) -> (f64, f64) {
// 物理像素转换为逻辑像素
let screen_width = screen_size.width as f64 / scale_factor;
let screen_height = screen_size.height as f64 / scale_factor;
let screen_x = screen_pos.x as f64 / scale_factor;
let screen_y = screen_pos.y as f64 / scale_factor;
// 顶部居中定位
// 底部居中定位
let x = screen_x + (screen_width - WINDOW_WIDTH) / 2.0;
let y = screen_y + TOP_MARGIN;
let y = screen_y + screen_height - WINDOW_HEIGHT - BOTTOM_MARGIN;
debug!(
"更新窗口位置: 屏幕({}, {}), 尺寸: {}x?, 窗口位置: ({}, {})",
screen_x, screen_y, screen_width, x, y
"更新窗口位置: 屏幕({}, {}), 尺寸: {}x{}, 窗口位置: ({}, {})",
screen_x, screen_y, screen_width, screen_height, x, y
);
return (x, y);
}