mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
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:
@@ -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 事件服务
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user