Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/adamqeqj003-dotcom_novel-article-slimmer.json

1 line
39 KiB
JSON

{"repo": "adamqeqj003-dotcom/novel-article-slimmer", "files": {"src-tauri/src/lib.rs": {"dcfea8440b9a5dba269afa8c928bc9380d666a7c": {"t": 1785745857.615167, "n": 18378, "h": "6ee962cc88e58d7c", "c": "use std::collections::BTreeSet;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::time::{SystemTime, UNIX_EPOCH};\nuse std::time::Duration;\n\nuse serde::{Deserialize, Serialize};\n\npub mod application;\npub mod desktop;\npub mod domain;\npub mod infrastructure;\nmod runtime;\n\nconst PROJECT_NAME: &str = \"网文智能精简工具\";\nconst PROJECT_PHASE: &str = \"T012\";\nconst DEFAULT_BASE_URL: &str = \"https://api.deepseek.com/v1\";\nconst API_KEY_SERVICE_NAME: &str = \"com.novelslimmer.app\";\nconst API_KEY_ACCOUNT_NAME: &str = \"primary-api-key\";\nconst DEFAULT_SYSTEM_PROMPT: &str = include_str!(concat!(\n env!(\"CARGO_MANIFEST_DIR\"),\n \"/../项目物料-小说文本示例等/软件用提示词.md\"\n));\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AppDefaults {\n api_key: String,\n base_url: String,\n system_prompt: String,\n pricing_plan: domain::PricingPlan,\n}\n\n#[derive(Debug, Clone, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct ConnectionProbeRequest {\n api_key: String,\n base_url: String,\n model: String,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct ConnectionProbeResult {\n reachable: bool,\n message: String,\n}\n\n#[derive(Debug, Clone, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AvailableModelsRequest {\n api_key: String,\n base_url: String,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AvailableModelsResult {\n models: Vec<String>,\n message: String,\n}\n\nfn candidate_project_roots() -> Vec<PathBuf> {\n let mut roots = Vec::new();\n\n if let Ok(current_dir) = std::env::current_dir() {\n roots.push(current_dir);\n }\n\n if let Some(project_root) = Path::new(env!(\"CARGO_MANIFEST_DIR\")).parent() {\n let project_root = project_root.to_path_buf();\n if !roots.iter().any(|candidate| candidate == &project_root) {\n roots.push(project_root);\n }\n }\n\n roots\n}\n\nfn read_default_api_key() -> String {\n if let Ok(Some(api_key)) = read_secure_api_key() {\n return api_key;\n }\n\n for root in candidate_project_roots() {\n let env_file = root.join(\".eva\");\n let Ok(content) = fs::read_to_string(&env_file) else {\n continue;\n };\n\n for line in content.lines() {\n if let Some(value) = line.trim().strip_prefix(\"DEEPSEEK_API_KEY=\") {\n return value.trim().to_string();\n }\n }\n }\n\n String::new()\n}\n\n#[cfg(windows)]\nfn api_key_entry() -> Result<keyring::Entry, String> {\n keyring::Entry::new(API_KEY_SERVICE_NAME, API_KEY_ACCOUNT_NAME)\n .map_err(|error| format!(\"安全存储初始化失败。 {error}\"))\n}\n\n#[cfg(not(windows))]\nfn api_key_entry() -> Result<(), String> {\n Err(\"当前平台暂未接通系统安全存储。\".into())\n}\n\n#[cfg(windows)]\nfn read_secure_api_key() -> Result<Option<String>, String> {\n let entry = api_key_entry()?;\n match entry.get_password() {\n Ok(api_key) => Ok(Some(api_key)),\n Err(keyring::Error::NoEntry) => Ok(None),\n Err(error) => Err(format!(\"读取系统安全存储失败。 {error}\")),\n }\n}\n\n#[cfg(not(windows))]\nfn read_secure_api_key() -> Result<Option<String>, String> {\n let _ = api_key_entry()?;\n Ok(None)\n}\n\n#[cfg(windows)]\nfn write_secure_api_key(api_key: &str) -> Result<(), String> {\n let entry = api_key_entry()?;\n let normalized = api_key.trim();\n if normalized.is_empty() {\n match entry.delete_credential() {\n Ok(_) | Err(keyring::Error::NoEntry) => Ok(()),\n Err(error) => Err(format!(\"清除系统安全存储失败。 {error}\")),\n }\n } else {\n entry\n .set_password(normalized)\n .map_err(|error| format!(\"写入系统安全存储失败。 {error}\"))\n }\n}\n\n#[cfg(not(windows))]\nfn write_secure_api_key(_api_key: &str) -> Result<(), String> {\n let _ = api_key_entry()?;\n Ok(())\n}\n\n#[tauri::command]\nfn bootstrap_shell_status() -> String {\n format!(\"{PROJECT_NAME} {PROJECT_PHASE} contract baseline ready\")\n}\n\n#[tauri::command]\nfn preview_domain_contract() -> domain::DomainContractPreview {\n domain::domain_contract_preview()\n}\n\n#[tauri::command]\nfn sync_runtime_snapshot(runtime: tauri::State<'_, runtime::RuntimeEngineState>) -> domain::RuntimeSnapshot {\n runtime.snapshot()\n}\n\n#[tauri::command]\nfn load_app_defaults() -> AppDefaults {\n AppDefaults {\n api_key: read_default_api_key(),\n base_url: DEFAULT_BASE_URL.into(),\n system_prompt: DEFAULT_SYSTEM_PROMPT.trim().to_string(),\n pricing_plan: domain::PricingPlan::baseline(),\n }\n}\n\n#[tauri::command]\nfn store_api_key_securely(api_key: String) -> Result<(), String> {\n write_secure_api_key(&api_key)\n}\n\nfn trim_probe_body(body: String) -> String {\n let trimmed = body.trim();\n let shortened = trimmed.chars().take(180).collect::<String>();\n if trimmed.chars().count() > 180 {\n format!(\"{shortened}...\")\n } else {\n shortened\n }\n}\n\nfn normalize_base_url(base_url: &str) -> String {\n base_url.trim().trim_end_matches('/').to_string()\n}\n\nfn build_api_client() -> Result<reqwest::blocking::Client, String> {\n reqwest::blocking::Client::builder()\n .timeout(Duration::from_secs(20))\n .build()\n .map_err(|error| error.to_string())\n}\n\nfn extract_available_model_ids(payload: &serde_json::Value) -> Vec<String> {\n let candidate_items = payload\n .get(\"data\")\n .and_then(serde_json::Value::as_array)\n .or_else(|| payload.get(\"models\").and_then(serde_json::Value::as_array))\n .cloned()\n .or_else(|| payload.as_array().cloned())\n .unwrap_or_default();\n\n candidate_items\n .iter()\n .filter_map(|item| item.get(\"id\").and_then(serde_json::Value::as_str))\n .map(str::trim)\n .filter(|model_id| !model_id.is_empty())\n .map(str::to_string)\n .collect::<BTreeSet<_>>()\n .into_iter()\n .collect()\n}\n\nfn run_connection_probe(request: ConnectionProbeRequest) -> Result<ConnectionProbeResult, String> {\n let api_key = request.api_key.trim().to_string();\n if api_key.is_empty() {\n return Err(\"请先填写 API Key,再测试连通性。\".into());\n }\n\n let base_url = normalize_base_url(&request.base_url);\n if base_url.is_empty() {\n return Err(\"请先填写 Base URL,再测试连通性。\".into());\n }\n\n let model = request.model.trim().to_string();\n if model.is_empty() {\n return Err(\"请先填写模型名称,再测试连通性。\".into());\n }\n\n let client = build_api_client()?;\n let response = client\n .post(format!(\"{base_url}/chat/completions\"))\n .bearer_auth(api_key)\n .json(&serde_json::json!({\n \"model\": model,\n \"messages\": [\n { \"role\": \"system\", \"content\": \"You are a connectivity probe.\" },\n { \"role\": \"user\", \"content\": \"ping\" }\n ],\n \"max_tokens\": 1,\n \"stream\": false\n }))\n .send()\n .map_err(|error| format!(\"连通性测试失败。 {error}\"))?;\n\n match response.status().as_u16() {\n 200..=299 => Ok(ConnectionProbeResult {\n reachable: true,\n message: format!(\"接口已连通,当前模型 `{}` 可正常响应。\", request.model.trim()),\n }),\n 401 => Err(\"API Key 无效,请检查后重试。\".into()),\n 404 => Err(\"接口地址或模型名称不可用,请检查 Base URL 和模型名称。\".into()),\n 429 => Ok(ConnectionProbeResult {\n reachable: true,\n message: \"接口已连通,但当前账号正在限流,请稍后再试。\".into(),\n }),\n status => {\n let body = response.text().unwrap_or_default();\n Err(format!(\n \"连通性测试失败,HTTP {}。{}\",\n status,\n if body.trim().is_empty() {\n String::new()\n } else {\n format!(\" 返回内容:{}\", trim_probe_body(body))\n }\n ))\n }\n }\n}\n\nfn run_fetch_available_models(\n request: AvailableModelsRequest,\n) -> Result<AvailableModelsResult, String> {\n let api_key = request.api_key.trim().to_string();\n if api_key.is_empty() {\n return Err(\"请先填写 API Key,再拉取模型列表。\".into());\n }\n\n let base_url = normalize_base_url(&request.base_url);\n if base_url.is_empty() {\n return Err(\"请先填写 Base URL,再拉取模型列表。\".into());\n }\n\n let client = build_api_client()?;\n let response = client\n .get(format!(\"{base_url}/models\"))\n .bearer_auth(api_key)\n .send()\n .map_err(|error| format!(\"拉取模型列表失败。 {error}\"))?;\n\n match response.status().as_u16() {\n 200..=299 => {\n let payload = response\n .json::<serde_json::Value>()\n .map_err(|error| format!(\"模型列表解析失败。 {error}\"))?;\n let models = extract_available_model_ids(&payload);\n if models.is_empty() {\n return Err(\n \"接口已返回成功,但没有解析到模型列表,请检查该平台是否兼容 OpenAI `/models` 接口。\"\n .into(),\n );\n }\n\n Ok(AvailableModelsResult {\n message: format!(\"已获取 {} 个模型,可直接选择,也可以继续手动填写。\", models.len()),\n models,\n })\n }\n 401 => Err(\"API Key 无效,请检查后重试。\".into()),\n 404 => Err(\"当前接口没有暴露模型列表,请检查 Base URL,或改为手动填写模型名称。\".into()),\n 429 => Err(\"当前接口正在限流,暂时无法拉取模型列表,请稍后再试。\".into()),\n status => {\n let body = response.text().unwrap_or_default();\n Err(format!(\n \"拉取模型列表失败,HTTP {}。{}\",\n status,\n if body.trim().is_empty() {\n String::new()\n } else {\n format!(\" 返回内容:{}\", trim_probe_body(body))\n }\n ))\n }\n }\n}\n\n#[tauri::command]\nasync fn probe_connection(\n request: ConnectionProbeRequest,\n) -> Result<ConnectionProbeResult, String> {\n tauri::async_runtime::spawn_blocking(move || run_connection_probe(request))\n .await\n .map_err(|error| error.to_string())?\n}\n\n#[tauri::command]\nasync fn fetch_available_models(\n request: AvailableModelsRequest,\n) -> Result<AvailableModelsResult, String> {\n tauri::async_runtime::spawn_blocking(move || run_fetch_available_models(request))\n .await\n .map_err(|error| error.to_string())?\n}\n\n#[tauri::command]\nfn export_runtime_logs(\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeLogExport, String> {\n let snapshot = runtime.snapshot();\n let export_root = std::env::temp_dir()\n .join(\"NovelSlimmer\")\n .join(\"log-exports\");\n fs::create_dir_all(&export_root).map_err(|error| error.to_string())?;\n\n let unix_seconds = SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .map_err(|error| error.to_string())?\n .as_secs();\n let export_path = export_root.join(format!(\"runtime-tail-{unix_seconds}.log\"));\n let log_text = snapshot\n .log_items\n .iter()\n .map(|entry| {\n let task_marker = entry\n .task_id\n .as_deref()\n .map(|task_id| format!(\" [{task_id}]\"))\n .unwrap_or_default();\n format!(\n \"{} [{}]{} {}\",\n entry.timestamp,\n match entry.level {\n domain::RuntimeLogLevel::Info => \"INFO\",\n domain::RuntimeLogLevel::Warning => \"WARN\",\n domain::RuntimeLogLevel::Error => \"ERROR\",\n },\n task_marker,\n entry.message\n )\n })\n .collect::<Vec<_>>()\n .join(\"\\n\");\n fs::write(&export_path, log_text).map_err(|error| error.to_string())?;\n\n Ok(domain::RuntimeLogExport {\n export_file_path: export_path.to_string_lossy().into_owned(),\n exported_count: snapshot.log_items.len() as u32,\n })\n}\n\n#[tauri::command]\nasync fn preflight_import_files(\n request: domain::TxtImportRequest,\n) -> Result<Vec<domain::ImportedBookPreflight>, String> {\n tauri::async_runtime::spawn_blocking(move || application::preflight_import_files(request))\n .await\n .map_err(|error| error.to_string())?\n}\n\n#[tauri::command]\nfn start_runtime_tasks(\n request: runtime::StartRuntimeRequest,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.start_runtime(request)\n}\n\n#[tauri::command]\nfn resume_runtime_task(\n request: runtime::RuntimeTaskActionRequest,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.resume_task(request)\n}\n\n#[tauri::command]\nfn restart_runtime_task(\n request: runtime::RuntimeTaskActionRequest,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.restart_task(request)\n}\n\n#[tauri::command]\nfn interrupt_all_runtime_tasks(\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> domain::RuntimeSnapshot {\n runtime.interrupt_all()\n}\n\n#[tauri::command]\nfn interrupt_runtime_task(\n task_id: String,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.interrupt_task(&task_id)\n}\n\n#[tauri::command]\nfn remove_runtime_task(\n task_id: String,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.remove_task(&task_id)\n}\n\n#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n tauri::Builder::default()\n .manage(desktop::build_state())\n .manage(runtime::RuntimeEngineState::default())\n .plugin(tauri_plugin_dialog::init())\n .plugin(tauri_plugin_opener::init())\n .plugin(tauri_plugin_window_state::Builder::default().build())\n .setup(|app| {\n desktop::configure(&app.handle())?;\n Ok(())\n })\n .invoke_handler(tauri::generate_handler![\n bootstrap_shell_status,\n preview_domain_contract,\n sync_runtime_snapshot,\n load_app_defaults,\n store_api_key_securely,\n probe_connection,\n fetch_available_models,\n export_runtime_logs,\n preflight_import_files,\n start_runtime_tasks,\n resume_runtime_task,\n restart_runtime_task,\n interrupt_all_runtime_tasks,\n interrupt_runtime_task,\n remove_runtime_task,\n desktop::desktop_shell_snapshot,\n desktop::set_background_task_active,\n desktop::hide_main_window_command,\n desktop::request_app_exit\n ])\n .run(tauri::generate_context!())\n .expect(\"error while running tauri application\");\n}\n\n#[cfg(test)]\nmod tests {\n use std::fs;\n\n use serde_json::json;\n\n use super::{\n bootstrap_shell_status, extract_available_model_ids, load_app_defaults,\n preview_domain_contract,\n };\n use crate::{application, domain};\n use crate::domain::TaskStatus;\n\n #[test]\n fn reports_phase_zero_contract_status() {\n let status = bootstrap_shell_status();\n\n assert!(status.contains(\"网文智能精简工具\"));\n assert!(status.contains(\"T012\"));\n }\n\n #[test]\n fn exposes_domain_contract_preview_for_the_ui() {\n let preview = preview_domain_contract();\n\n assert_eq!(preview.example_task.status, TaskStatus::Ready);\n assert_eq!(preview.task_statuses.len(), 13);\n assert_eq!(preview.domain_error_codes.len(), 13);\n }\n\n #[test]\n fn rejects_preflight_when_api_key_is_missing() {\n let result = application::preflight_import_files(crate::domain::TxtImportRequest {\n api_key: String::new(),\n output_dir: String::new(),\n chapter_threshold: 15000,\n file_paths: vec![\"C:/books/demo.txt\".into()],\n });\n\n assert_eq!(result.unwrap_err(), \"请先配置 API Key\");\n }\n\n #[test]\n fn syncs_runtime_snapshot_as_empty_on_first_launch() {\n let snapshot = domain::build_empty_runtime_snapshot();\n\n assert_eq!(snapshot.backend_connection_status, \"已连接\");\n assert_eq!(snapshot.tail_buffer_limit, 200);\n assert!(snapshot.tasks.is_empty());\n assert!(snapshot.log_items.is_empty());\n }\n\n #[test]\n fn loads_deepseek_app_defaults() {\n let defaults = load_app_defaults();\n\n assert_eq!(defaults.base_url, \"https://api.deepseek.com/v1\");\n assert_eq!(defaults.pricing_plan.provider, \"deepseek\");\n assert_eq!(defaults.pricing_plan.model, \"deepseek-chat\");\n assert_eq!(defaults.pricing_plan.plan_id, \"plan-deepseek-chat\");\n assert_eq!(defaults.pricing_plan.billing_base, \"1000000\");\n assert_eq!(defaults.pricing_plan.input_unit_price, \"1.000000\");\n assert_eq!(defaults.pricing_plan.output_unit_price, \"2.000000\");\n assert!(!defaults.system_prompt.is_empty());\n }\n\n #[test]\n fn extracts_model_ids_from_openai_compatible_payload() {\n let payload = json!({\n \"object\": \"list\",\n \"data\": [\n { \"id\": \"deepseek-chat\" },\n { \"id\": \"gpt-4o-mini\" },\n { \"id\": \"deepseek-chat\" },\n { \"id\": \" \" }\n ]\n });\n\n let models = extract_available_model_ids(&payload);\n\n assert_eq!(models, vec![\"deepseek-chat\", \"gpt-4o-mini\"]);\n }\n\n #[test]\n fn exports_runtime_logs_to_a_physical_file() {\n let snapshot = domain::build_empty_runtime_snapshot();\n let export_root = std::env::temp_dir()\n .join(\"NovelSlimmer\")\n .join(\"log-exports\");\n fs::create_dir_all(&export_root).expect(\"export root should exist\");\n let export_path = export_root.join(\"runtime-tail-test.log\");\n let log_text = snapshot\n .log_items\n .iter()\n .map(|entry| entry.message.clone())\n .collect::<Vec<_>>()\n .join(\"\\n\");\n fs::write(&export_path, log_text).expect(\"export file should be written\");\n let export = domain::RuntimeLogExport {\n export_file_path: export_path.to_string_lossy().into_owned(),\n exported_count: snapshot.log_items.len() as u32,\n };\n let log_text = fs::read_to_string(&export.export_file_path)\n .expect(\"exported runtime log file should exist\");\n\n assert_eq!(export.exported_count, 0);\n assert!(log_text.is_empty());\n\n let _ = fs::remove_file(export.export_file_path);\n }\n}\n"}, "ca1bd39177828d2d30ad9df407a715edddf195ca": {"t": 1785757306.8555388, "n": 18378, "h": "6ee962cc88e58d7c", "c": "use std::collections::BTreeSet;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::time::{SystemTime, UNIX_EPOCH};\nuse std::time::Duration;\n\nuse serde::{Deserialize, Serialize};\n\npub mod application;\npub mod desktop;\npub mod domain;\npub mod infrastructure;\nmod runtime;\n\nconst PROJECT_NAME: &str = \"网文智能精简工具\";\nconst PROJECT_PHASE: &str = \"T012\";\nconst DEFAULT_BASE_URL: &str = \"https://api.deepseek.com/v1\";\nconst API_KEY_SERVICE_NAME: &str = \"com.novelslimmer.app\";\nconst API_KEY_ACCOUNT_NAME: &str = \"primary-api-key\";\nconst DEFAULT_SYSTEM_PROMPT: &str = include_str!(concat!(\n env!(\"CARGO_MANIFEST_DIR\"),\n \"/../项目物料-小说文本示例等/软件用提示词.md\"\n));\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AppDefaults {\n api_key: String,\n base_url: String,\n system_prompt: String,\n pricing_plan: domain::PricingPlan,\n}\n\n#[derive(Debug, Clone, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct ConnectionProbeRequest {\n api_key: String,\n base_url: String,\n model: String,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct ConnectionProbeResult {\n reachable: bool,\n message: String,\n}\n\n#[derive(Debug, Clone, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AvailableModelsRequest {\n api_key: String,\n base_url: String,\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct AvailableModelsResult {\n models: Vec<String>,\n message: String,\n}\n\nfn candidate_project_roots() -> Vec<PathBuf> {\n let mut roots = Vec::new();\n\n if let Ok(current_dir) = std::env::current_dir() {\n roots.push(current_dir);\n }\n\n if let Some(project_root) = Path::new(env!(\"CARGO_MANIFEST_DIR\")).parent() {\n let project_root = project_root.to_path_buf();\n if !roots.iter().any(|candidate| candidate == &project_root) {\n roots.push(project_root);\n }\n }\n\n roots\n}\n\nfn read_default_api_key() -> String {\n if let Ok(Some(api_key)) = read_secure_api_key() {\n return api_key;\n }\n\n for root in candidate_project_roots() {\n let env_file = root.join(\".eva\");\n let Ok(content) = fs::read_to_string(&env_file) else {\n continue;\n };\n\n for line in content.lines() {\n if let Some(value) = line.trim().strip_prefix(\"DEEPSEEK_API_KEY=\") {\n return value.trim().to_string();\n }\n }\n }\n\n String::new()\n}\n\n#[cfg(windows)]\nfn api_key_entry() -> Result<keyring::Entry, String> {\n keyring::Entry::new(API_KEY_SERVICE_NAME, API_KEY_ACCOUNT_NAME)\n .map_err(|error| format!(\"安全存储初始化失败。 {error}\"))\n}\n\n#[cfg(not(windows))]\nfn api_key_entry() -> Result<(), String> {\n Err(\"当前平台暂未接通系统安全存储。\".into())\n}\n\n#[cfg(windows)]\nfn read_secure_api_key() -> Result<Option<String>, String> {\n let entry = api_key_entry()?;\n match entry.get_password() {\n Ok(api_key) => Ok(Some(api_key)),\n Err(keyring::Error::NoEntry) => Ok(None),\n Err(error) => Err(format!(\"读取系统安全存储失败。 {error}\")),\n }\n}\n\n#[cfg(not(windows))]\nfn read_secure_api_key() -> Result<Option<String>, String> {\n let _ = api_key_entry()?;\n Ok(None)\n}\n\n#[cfg(windows)]\nfn write_secure_api_key(api_key: &str) -> Result<(), String> {\n let entry = api_key_entry()?;\n let normalized = api_key.trim();\n if normalized.is_empty() {\n match entry.delete_credential() {\n Ok(_) | Err(keyring::Error::NoEntry) => Ok(()),\n Err(error) => Err(format!(\"清除系统安全存储失败。 {error}\")),\n }\n } else {\n entry\n .set_password(normalized)\n .map_err(|error| format!(\"写入系统安全存储失败。 {error}\"))\n }\n}\n\n#[cfg(not(windows))]\nfn write_secure_api_key(_api_key: &str) -> Result<(), String> {\n let _ = api_key_entry()?;\n Ok(())\n}\n\n#[tauri::command]\nfn bootstrap_shell_status() -> String {\n format!(\"{PROJECT_NAME} {PROJECT_PHASE} contract baseline ready\")\n}\n\n#[tauri::command]\nfn preview_domain_contract() -> domain::DomainContractPreview {\n domain::domain_contract_preview()\n}\n\n#[tauri::command]\nfn sync_runtime_snapshot(runtime: tauri::State<'_, runtime::RuntimeEngineState>) -> domain::RuntimeSnapshot {\n runtime.snapshot()\n}\n\n#[tauri::command]\nfn load_app_defaults() -> AppDefaults {\n AppDefaults {\n api_key: read_default_api_key(),\n base_url: DEFAULT_BASE_URL.into(),\n system_prompt: DEFAULT_SYSTEM_PROMPT.trim().to_string(),\n pricing_plan: domain::PricingPlan::baseline(),\n }\n}\n\n#[tauri::command]\nfn store_api_key_securely(api_key: String) -> Result<(), String> {\n write_secure_api_key(&api_key)\n}\n\nfn trim_probe_body(body: String) -> String {\n let trimmed = body.trim();\n let shortened = trimmed.chars().take(180).collect::<String>();\n if trimmed.chars().count() > 180 {\n format!(\"{shortened}...\")\n } else {\n shortened\n }\n}\n\nfn normalize_base_url(base_url: &str) -> String {\n base_url.trim().trim_end_matches('/').to_string()\n}\n\nfn build_api_client() -> Result<reqwest::blocking::Client, String> {\n reqwest::blocking::Client::builder()\n .timeout(Duration::from_secs(20))\n .build()\n .map_err(|error| error.to_string())\n}\n\nfn extract_available_model_ids(payload: &serde_json::Value) -> Vec<String> {\n let candidate_items = payload\n .get(\"data\")\n .and_then(serde_json::Value::as_array)\n .or_else(|| payload.get(\"models\").and_then(serde_json::Value::as_array))\n .cloned()\n .or_else(|| payload.as_array().cloned())\n .unwrap_or_default();\n\n candidate_items\n .iter()\n .filter_map(|item| item.get(\"id\").and_then(serde_json::Value::as_str))\n .map(str::trim)\n .filter(|model_id| !model_id.is_empty())\n .map(str::to_string)\n .collect::<BTreeSet<_>>()\n .into_iter()\n .collect()\n}\n\nfn run_connection_probe(request: ConnectionProbeRequest) -> Result<ConnectionProbeResult, String> {\n let api_key = request.api_key.trim().to_string();\n if api_key.is_empty() {\n return Err(\"请先填写 API Key,再测试连通性。\".into());\n }\n\n let base_url = normalize_base_url(&request.base_url);\n if base_url.is_empty() {\n return Err(\"请先填写 Base URL,再测试连通性。\".into());\n }\n\n let model = request.model.trim().to_string();\n if model.is_empty() {\n return Err(\"请先填写模型名称,再测试连通性。\".into());\n }\n\n let client = build_api_client()?;\n let response = client\n .post(format!(\"{base_url}/chat/completions\"))\n .bearer_auth(api_key)\n .json(&serde_json::json!({\n \"model\": model,\n \"messages\": [\n { \"role\": \"system\", \"content\": \"You are a connectivity probe.\" },\n { \"role\": \"user\", \"content\": \"ping\" }\n ],\n \"max_tokens\": 1,\n \"stream\": false\n }))\n .send()\n .map_err(|error| format!(\"连通性测试失败。 {error}\"))?;\n\n match response.status().as_u16() {\n 200..=299 => Ok(ConnectionProbeResult {\n reachable: true,\n message: format!(\"接口已连通,当前模型 `{}` 可正常响应。\", request.model.trim()),\n }),\n 401 => Err(\"API Key 无效,请检查后重试。\".into()),\n 404 => Err(\"接口地址或模型名称不可用,请检查 Base URL 和模型名称。\".into()),\n 429 => Ok(ConnectionProbeResult {\n reachable: true,\n message: \"接口已连通,但当前账号正在限流,请稍后再试。\".into(),\n }),\n status => {\n let body = response.text().unwrap_or_default();\n Err(format!(\n \"连通性测试失败,HTTP {}。{}\",\n status,\n if body.trim().is_empty() {\n String::new()\n } else {\n format!(\" 返回内容:{}\", trim_probe_body(body))\n }\n ))\n }\n }\n}\n\nfn run_fetch_available_models(\n request: AvailableModelsRequest,\n) -> Result<AvailableModelsResult, String> {\n let api_key = request.api_key.trim().to_string();\n if api_key.is_empty() {\n return Err(\"请先填写 API Key,再拉取模型列表。\".into());\n }\n\n let base_url = normalize_base_url(&request.base_url);\n if base_url.is_empty() {\n return Err(\"请先填写 Base URL,再拉取模型列表。\".into());\n }\n\n let client = build_api_client()?;\n let response = client\n .get(format!(\"{base_url}/models\"))\n .bearer_auth(api_key)\n .send()\n .map_err(|error| format!(\"拉取模型列表失败。 {error}\"))?;\n\n match response.status().as_u16() {\n 200..=299 => {\n let payload = response\n .json::<serde_json::Value>()\n .map_err(|error| format!(\"模型列表解析失败。 {error}\"))?;\n let models = extract_available_model_ids(&payload);\n if models.is_empty() {\n return Err(\n \"接口已返回成功,但没有解析到模型列表,请检查该平台是否兼容 OpenAI `/models` 接口。\"\n .into(),\n );\n }\n\n Ok(AvailableModelsResult {\n message: format!(\"已获取 {} 个模型,可直接选择,也可以继续手动填写。\", models.len()),\n models,\n })\n }\n 401 => Err(\"API Key 无效,请检查后重试。\".into()),\n 404 => Err(\"当前接口没有暴露模型列表,请检查 Base URL,或改为手动填写模型名称。\".into()),\n 429 => Err(\"当前接口正在限流,暂时无法拉取模型列表,请稍后再试。\".into()),\n status => {\n let body = response.text().unwrap_or_default();\n Err(format!(\n \"拉取模型列表失败,HTTP {}。{}\",\n status,\n if body.trim().is_empty() {\n String::new()\n } else {\n format!(\" 返回内容:{}\", trim_probe_body(body))\n }\n ))\n }\n }\n}\n\n#[tauri::command]\nasync fn probe_connection(\n request: ConnectionProbeRequest,\n) -> Result<ConnectionProbeResult, String> {\n tauri::async_runtime::spawn_blocking(move || run_connection_probe(request))\n .await\n .map_err(|error| error.to_string())?\n}\n\n#[tauri::command]\nasync fn fetch_available_models(\n request: AvailableModelsRequest,\n) -> Result<AvailableModelsResult, String> {\n tauri::async_runtime::spawn_blocking(move || run_fetch_available_models(request))\n .await\n .map_err(|error| error.to_string())?\n}\n\n#[tauri::command]\nfn export_runtime_logs(\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeLogExport, String> {\n let snapshot = runtime.snapshot();\n let export_root = std::env::temp_dir()\n .join(\"NovelSlimmer\")\n .join(\"log-exports\");\n fs::create_dir_all(&export_root).map_err(|error| error.to_string())?;\n\n let unix_seconds = SystemTime::now()\n .duration_since(UNIX_EPOCH)\n .map_err(|error| error.to_string())?\n .as_secs();\n let export_path = export_root.join(format!(\"runtime-tail-{unix_seconds}.log\"));\n let log_text = snapshot\n .log_items\n .iter()\n .map(|entry| {\n let task_marker = entry\n .task_id\n .as_deref()\n .map(|task_id| format!(\" [{task_id}]\"))\n .unwrap_or_default();\n format!(\n \"{} [{}]{} {}\",\n entry.timestamp,\n match entry.level {\n domain::RuntimeLogLevel::Info => \"INFO\",\n domain::RuntimeLogLevel::Warning => \"WARN\",\n domain::RuntimeLogLevel::Error => \"ERROR\",\n },\n task_marker,\n entry.message\n )\n })\n .collect::<Vec<_>>()\n .join(\"\\n\");\n fs::write(&export_path, log_text).map_err(|error| error.to_string())?;\n\n Ok(domain::RuntimeLogExport {\n export_file_path: export_path.to_string_lossy().into_owned(),\n exported_count: snapshot.log_items.len() as u32,\n })\n}\n\n#[tauri::command]\nasync fn preflight_import_files(\n request: domain::TxtImportRequest,\n) -> Result<Vec<domain::ImportedBookPreflight>, String> {\n tauri::async_runtime::spawn_blocking(move || application::preflight_import_files(request))\n .await\n .map_err(|error| error.to_string())?\n}\n\n#[tauri::command]\nfn start_runtime_tasks(\n request: runtime::StartRuntimeRequest,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.start_runtime(request)\n}\n\n#[tauri::command]\nfn resume_runtime_task(\n request: runtime::RuntimeTaskActionRequest,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.resume_task(request)\n}\n\n#[tauri::command]\nfn restart_runtime_task(\n request: runtime::RuntimeTaskActionRequest,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.restart_task(request)\n}\n\n#[tauri::command]\nfn interrupt_all_runtime_tasks(\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> domain::RuntimeSnapshot {\n runtime.interrupt_all()\n}\n\n#[tauri::command]\nfn interrupt_runtime_task(\n task_id: String,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.interrupt_task(&task_id)\n}\n\n#[tauri::command]\nfn remove_runtime_task(\n task_id: String,\n runtime: tauri::State<'_, runtime::RuntimeEngineState>,\n) -> Result<domain::RuntimeSnapshot, String> {\n runtime.remove_task(&task_id)\n}\n\n#[cfg_attr(mobile, tauri::mobile_entry_point)]\npub fn run() {\n tauri::Builder::default()\n .manage(desktop::build_state())\n .manage(runtime::RuntimeEngineState::default())\n .plugin(tauri_plugin_dialog::init())\n .plugin(tauri_plugin_opener::init())\n .plugin(tauri_plugin_window_state::Builder::default().build())\n .setup(|app| {\n desktop::configure(&app.handle())?;\n Ok(())\n })\n .invoke_handler(tauri::generate_handler![\n bootstrap_shell_status,\n preview_domain_contract,\n sync_runtime_snapshot,\n load_app_defaults,\n store_api_key_securely,\n probe_connection,\n fetch_available_models,\n export_runtime_logs,\n preflight_import_files,\n start_runtime_tasks,\n resume_runtime_task,\n restart_runtime_task,\n interrupt_all_runtime_tasks,\n interrupt_runtime_task,\n remove_runtime_task,\n desktop::desktop_shell_snapshot,\n desktop::set_background_task_active,\n desktop::hide_main_window_command,\n desktop::request_app_exit\n ])\n .run(tauri::generate_context!())\n .expect(\"error while running tauri application\");\n}\n\n#[cfg(test)]\nmod tests {\n use std::fs;\n\n use serde_json::json;\n\n use super::{\n bootstrap_shell_status, extract_available_model_ids, load_app_defaults,\n preview_domain_contract,\n };\n use crate::{application, domain};\n use crate::domain::TaskStatus;\n\n #[test]\n fn reports_phase_zero_contract_status() {\n let status = bootstrap_shell_status();\n\n assert!(status.contains(\"网文智能精简工具\"));\n assert!(status.contains(\"T012\"));\n }\n\n #[test]\n fn exposes_domain_contract_preview_for_the_ui() {\n let preview = preview_domain_contract();\n\n assert_eq!(preview.example_task.status, TaskStatus::Ready);\n assert_eq!(preview.task_statuses.len(), 13);\n assert_eq!(preview.domain_error_codes.len(), 13);\n }\n\n #[test]\n fn rejects_preflight_when_api_key_is_missing() {\n let result = application::preflight_import_files(crate::domain::TxtImportRequest {\n api_key: String::new(),\n output_dir: String::new(),\n chapter_threshold: 15000,\n file_paths: vec![\"C:/books/demo.txt\".into()],\n });\n\n assert_eq!(result.unwrap_err(), \"请先配置 API Key\");\n }\n\n #[test]\n fn syncs_runtime_snapshot_as_empty_on_first_launch() {\n let snapshot = domain::build_empty_runtime_snapshot();\n\n assert_eq!(snapshot.backend_connection_status, \"已连接\");\n assert_eq!(snapshot.tail_buffer_limit, 200);\n assert!(snapshot.tasks.is_empty());\n assert!(snapshot.log_items.is_empty());\n }\n\n #[test]\n fn loads_deepseek_app_defaults() {\n let defaults = load_app_defaults();\n\n assert_eq!(defaults.base_url, \"https://api.deepseek.com/v1\");\n assert_eq!(defaults.pricing_plan.provider, \"deepseek\");\n assert_eq!(defaults.pricing_plan.model, \"deepseek-chat\");\n assert_eq!(defaults.pricing_plan.plan_id, \"plan-deepseek-chat\");\n assert_eq!(defaults.pricing_plan.billing_base, \"1000000\");\n assert_eq!(defaults.pricing_plan.input_unit_price, \"1.000000\");\n assert_eq!(defaults.pricing_plan.output_unit_price, \"2.000000\");\n assert!(!defaults.system_prompt.is_empty());\n }\n\n #[test]\n fn extracts_model_ids_from_openai_compatible_payload() {\n let payload = json!({\n \"object\": \"list\",\n \"data\": [\n { \"id\": \"deepseek-chat\" },\n { \"id\": \"gpt-4o-mini\" },\n { \"id\": \"deepseek-chat\" },\n { \"id\": \" \" }\n ]\n });\n\n let models = extract_available_model_ids(&payload);\n\n assert_eq!(models, vec![\"deepseek-chat\", \"gpt-4o-mini\"]);\n }\n\n #[test]\n fn exports_runtime_logs_to_a_physical_file() {\n let snapshot = domain::build_empty_runtime_snapshot();\n let export_root = std::env::temp_dir()\n .join(\"NovelSlimmer\")\n .join(\"log-exports\");\n fs::create_dir_all(&export_root).expect(\"export root should exist\");\n let export_path = export_root.join(\"runtime-tail-test.log\");\n let log_text = snapshot\n .log_items\n .iter()\n .map(|entry| entry.message.clone())\n .collect::<Vec<_>>()\n .join(\"\\n\");\n fs::write(&export_path, log_text).expect(\"export file should be written\");\n let export = domain::RuntimeLogExport {\n export_file_path: export_path.to_string_lossy().into_owned(),\n exported_count: snapshot.log_items.len() as u32,\n };\n let log_text = fs::read_to_string(&export.export_file_path)\n .expect(\"exported runtime log file should exist\");\n\n assert_eq!(export.exported_count, 0);\n assert!(log_text.is_empty());\n\n let _ = fs::remove_file(export.export_file_path);\n }\n}\n"}}}}