refactor(prompt): extract file path logic and implement auto-import on first launch (#214)
- Extract prompt file path logic to dedicated prompt_files module - Refactor PromptService to use centralized path resolution - Implement auto-import for existing prompt files on first startup - Add comprehensive unit tests for auto-import functionality
This commit is contained in:
42
src-tauri/Cargo.lock
generated
42
src-tauri/Cargo.lock
generated
@@ -576,6 +576,7 @@ dependencies = [
|
|||||||
"rquickjs",
|
"rquickjs",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serial_test",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
@@ -585,6 +586,7 @@ dependencies = [
|
|||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-store",
|
"tauri-plugin-store",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
|
"tempfile",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.2",
|
"toml 0.8.2",
|
||||||
@@ -3727,6 +3729,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scc"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
|
||||||
|
dependencies = [
|
||||||
|
"sdd",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "0.8.22"
|
||||||
@@ -3790,6 +3801,12 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sdd"
|
||||||
|
version = "3.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "seahash"
|
name = "seahash"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -3962,6 +3979,31 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serial_test"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"scc",
|
||||||
|
"serial_test_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serial_test_derive"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serialize-to-javascript"
|
name = "serialize-to-javascript"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|||||||
@@ -57,3 +57,7 @@ lto = "thin"
|
|||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serial_test = "3"
|
||||||
|
tempfile = "3"
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ pub struct PromptRoot {
|
|||||||
|
|
||||||
use crate::config::{copy_file, get_app_config_dir, get_app_config_path, write_json_file};
|
use crate::config::{copy_file, get_app_config_dir, get_app_config_path, write_json_file};
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
|
use crate::prompt_files::prompt_file_path;
|
||||||
use crate::provider::ProviderManager;
|
use crate::provider::ProviderManager;
|
||||||
|
|
||||||
/// 应用类型
|
/// 应用类型
|
||||||
@@ -122,8 +123,12 @@ impl MultiAppConfig {
|
|||||||
let config_path = get_app_config_path();
|
let config_path = get_app_config_path();
|
||||||
|
|
||||||
if !config_path.exists() {
|
if !config_path.exists() {
|
||||||
log::info!("配置文件不存在,创建新的多应用配置");
|
log::info!("配置文件不存在,创建新的多应用配置并自动导入提示词");
|
||||||
return Ok(Self::default());
|
// 使用新的方法,支持自动导入提示词
|
||||||
|
let config = Self::default_with_auto_import()?;
|
||||||
|
// 立即保存到磁盘
|
||||||
|
config.save()?;
|
||||||
|
return Ok(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试读取文件
|
// 尝试读取文件
|
||||||
@@ -213,4 +218,253 @@ impl MultiAppConfig {
|
|||||||
AppType::Gemini => &mut self.mcp.gemini,
|
AppType::Gemini => &mut self.mcp.gemini,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 创建默认配置并自动导入已存在的提示词文件
|
||||||
|
fn default_with_auto_import() -> Result<Self, AppError> {
|
||||||
|
log::info!("首次启动,创建默认配置并检测提示词文件");
|
||||||
|
|
||||||
|
let mut config = Self::default();
|
||||||
|
|
||||||
|
// 为每个应用尝试自动导入提示词
|
||||||
|
Self::auto_import_prompt_if_exists(&mut config, AppType::Claude)?;
|
||||||
|
Self::auto_import_prompt_if_exists(&mut config, AppType::Codex)?;
|
||||||
|
Self::auto_import_prompt_if_exists(&mut config, AppType::Gemini)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查并自动导入单个应用的提示词文件
|
||||||
|
fn auto_import_prompt_if_exists(
|
||||||
|
config: &mut Self,
|
||||||
|
app: AppType,
|
||||||
|
) -> Result<(), AppError> {
|
||||||
|
let file_path = prompt_file_path(&app)?;
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if !file_path.exists() {
|
||||||
|
log::debug!("提示词文件不存在,跳过自动导入: {file_path:?}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取文件内容
|
||||||
|
let content = match std::fs::read_to_string(&file_path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("读取提示词文件失败: {file_path:?}, 错误: {e}");
|
||||||
|
return Ok(()); // 失败时不中断,继续处理其他应用
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查内容是否为空
|
||||||
|
if content.trim().is_empty() {
|
||||||
|
log::debug!("提示词文件内容为空,跳过导入: {file_path:?}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("发现提示词文件,自动导入: {file_path:?}");
|
||||||
|
|
||||||
|
// 创建提示词对象
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs() as i64;
|
||||||
|
|
||||||
|
let id = format!("auto-imported-{timestamp}");
|
||||||
|
let prompt = crate::prompt::Prompt {
|
||||||
|
id: id.clone(),
|
||||||
|
name: format!(
|
||||||
|
"初始提示词 {}",
|
||||||
|
chrono::Local::now().format("%Y-%m-%d %H:%M")
|
||||||
|
),
|
||||||
|
content,
|
||||||
|
description: Some("首次启动时自动导入".to_string()),
|
||||||
|
enabled: true, // 自动启用
|
||||||
|
created_at: Some(timestamp),
|
||||||
|
updated_at: Some(timestamp),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 插入到对应的应用配置中
|
||||||
|
let prompts = match app {
|
||||||
|
AppType::Claude => &mut config.prompts.claude.prompts,
|
||||||
|
AppType::Codex => &mut config.prompts.codex.prompts,
|
||||||
|
AppType::Gemini => &mut config.prompts.gemini.prompts,
|
||||||
|
};
|
||||||
|
|
||||||
|
prompts.insert(id, prompt);
|
||||||
|
|
||||||
|
log::info!("自动导入完成: {}", app.as_str());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serial_test::serial;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
struct TempHome {
|
||||||
|
dir: TempDir,
|
||||||
|
original_home: Option<String>,
|
||||||
|
original_userprofile: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TempHome {
|
||||||
|
fn new() -> Self {
|
||||||
|
let dir = TempDir::new().expect("failed to create temp home");
|
||||||
|
let original_home = env::var("HOME").ok();
|
||||||
|
let original_userprofile = env::var("USERPROFILE").ok();
|
||||||
|
|
||||||
|
env::set_var("HOME", dir.path());
|
||||||
|
env::set_var("USERPROFILE", dir.path());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
dir,
|
||||||
|
original_home,
|
||||||
|
original_userprofile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TempHome {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
match &self.original_home {
|
||||||
|
Some(value) => env::set_var("HOME", value),
|
||||||
|
None => env::remove_var("HOME"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.original_userprofile {
|
||||||
|
Some(value) => env::set_var("USERPROFILE", value),
|
||||||
|
None => env::remove_var("USERPROFILE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_prompt_file(app: AppType, content: &str) {
|
||||||
|
let path = crate::prompt_files::prompt_file_path(&app).expect("prompt path");
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent).expect("create parent dir");
|
||||||
|
}
|
||||||
|
fs::write(path, content).expect("write prompt");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn auto_imports_existing_prompt_when_config_missing() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
write_prompt_file(AppType::Claude, "# hello");
|
||||||
|
|
||||||
|
let config = MultiAppConfig::load().expect("load config");
|
||||||
|
|
||||||
|
assert_eq!(config.prompts.claude.prompts.len(), 1);
|
||||||
|
let prompt = config
|
||||||
|
.prompts
|
||||||
|
.claude
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.expect("prompt exists");
|
||||||
|
assert!(prompt.enabled);
|
||||||
|
assert_eq!(prompt.content, "# hello");
|
||||||
|
|
||||||
|
let config_path = crate::config::get_app_config_path();
|
||||||
|
assert!(
|
||||||
|
config_path.exists(),
|
||||||
|
"auto import should persist config to disk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn skips_empty_prompt_files_during_import() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
write_prompt_file(AppType::Claude, " \n ");
|
||||||
|
|
||||||
|
let config = MultiAppConfig::load().expect("load config");
|
||||||
|
assert!(
|
||||||
|
config.prompts.claude.prompts.is_empty(),
|
||||||
|
"empty files must be ignored"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn auto_import_happens_only_once() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
write_prompt_file(AppType::Claude, "first version");
|
||||||
|
|
||||||
|
let first = MultiAppConfig::load().expect("load config");
|
||||||
|
assert_eq!(first.prompts.claude.prompts.len(), 1);
|
||||||
|
let claude_prompt = first
|
||||||
|
.prompts
|
||||||
|
.claude
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.expect("prompt exists")
|
||||||
|
.content
|
||||||
|
.clone();
|
||||||
|
assert_eq!(claude_prompt, "first version");
|
||||||
|
|
||||||
|
// 覆盖文件内容,但保留 config.json
|
||||||
|
write_prompt_file(AppType::Claude, "second version");
|
||||||
|
let second = MultiAppConfig::load().expect("load config again");
|
||||||
|
|
||||||
|
assert_eq!(second.prompts.claude.prompts.len(), 1);
|
||||||
|
let prompt = second
|
||||||
|
.prompts
|
||||||
|
.claude
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.expect("prompt exists");
|
||||||
|
assert_eq!(
|
||||||
|
prompt.content, "first version",
|
||||||
|
"should not re-import when config already exists"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn auto_imports_gemini_prompt_on_first_launch() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
write_prompt_file(AppType::Gemini, "# Gemini Prompt\n\nTest content");
|
||||||
|
|
||||||
|
let config = MultiAppConfig::load().expect("load config");
|
||||||
|
|
||||||
|
assert_eq!(config.prompts.gemini.prompts.len(), 1);
|
||||||
|
let prompt = config
|
||||||
|
.prompts
|
||||||
|
.gemini
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.expect("gemini prompt exists");
|
||||||
|
assert!(prompt.enabled, "gemini prompt should be enabled");
|
||||||
|
assert_eq!(prompt.content, "# Gemini Prompt\n\nTest content");
|
||||||
|
assert_eq!(prompt.description, Some("首次启动时自动导入".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn auto_imports_all_three_apps_prompts() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
write_prompt_file(AppType::Claude, "# Claude prompt");
|
||||||
|
write_prompt_file(AppType::Codex, "# Codex prompt");
|
||||||
|
write_prompt_file(AppType::Gemini, "# Gemini prompt");
|
||||||
|
|
||||||
|
let config = MultiAppConfig::load().expect("load config");
|
||||||
|
|
||||||
|
// 验证所有三个应用的提示词都被导入
|
||||||
|
assert_eq!(config.prompts.claude.prompts.len(), 1);
|
||||||
|
assert_eq!(config.prompts.codex.prompts.len(), 1);
|
||||||
|
assert_eq!(config.prompts.gemini.prompts.len(), 1);
|
||||||
|
|
||||||
|
// 验证所有提示词都被启用
|
||||||
|
assert!(config.prompts.claude.prompts.values().next().unwrap().enabled);
|
||||||
|
assert!(config.prompts.codex.prompts.values().next().unwrap().enabled);
|
||||||
|
assert!(config.prompts.gemini.prompts.values().next().unwrap().enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mod gemini_config; // 新增
|
|||||||
mod init_status;
|
mod init_status;
|
||||||
mod mcp;
|
mod mcp;
|
||||||
mod prompt;
|
mod prompt;
|
||||||
|
mod prompt_files;
|
||||||
mod provider;
|
mod provider;
|
||||||
mod services;
|
mod services;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|||||||
38
src-tauri/src/prompt_files.rs
Normal file
38
src-tauri/src/prompt_files.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::app_config::AppType;
|
||||||
|
use crate::codex_config::get_codex_auth_path;
|
||||||
|
use crate::config::get_claude_settings_path;
|
||||||
|
use crate::error::AppError;
|
||||||
|
use crate::gemini_config::get_gemini_dir;
|
||||||
|
|
||||||
|
/// 返回指定应用所使用的提示词文件路径。
|
||||||
|
pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
||||||
|
let base_dir = match app {
|
||||||
|
AppType::Claude => get_claude_settings_path()
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_path_buf())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
dirs::home_dir()
|
||||||
|
.expect("无法获取用户目录")
|
||||||
|
.join(".claude")
|
||||||
|
}),
|
||||||
|
AppType::Codex => get_codex_auth_path()
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_path_buf())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
dirs::home_dir()
|
||||||
|
.expect("无法获取用户目录")
|
||||||
|
.join(".codex")
|
||||||
|
}),
|
||||||
|
AppType::Gemini => get_gemini_dir(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let filename = match app {
|
||||||
|
AppType::Claude => "CLAUDE.md",
|
||||||
|
AppType::Codex => "AGENTS.md",
|
||||||
|
AppType::Gemini => "GEMINI.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(base_dir.join(filename))
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::app_config::AppType;
|
use crate::app_config::AppType;
|
||||||
use crate::config::write_text_file;
|
use crate::config::write_text_file;
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
use crate::prompt::Prompt;
|
use crate::prompt::Prompt;
|
||||||
|
use crate::prompt_files::prompt_file_path;
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
|
|
||||||
pub struct PromptService;
|
pub struct PromptService;
|
||||||
@@ -44,7 +44,7 @@ impl PromptService {
|
|||||||
|
|
||||||
// 如果是已启用的提示词,同步更新到对应的文件
|
// 如果是已启用的提示词,同步更新到对应的文件
|
||||||
if is_enabled {
|
if is_enabled {
|
||||||
let target_path = Self::get_prompt_file_path(&app)?;
|
let target_path = prompt_file_path(&app)?;
|
||||||
write_text_file(&target_path, &prompt.content)?;
|
write_text_file(&target_path, &prompt.content)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ impl PromptService {
|
|||||||
|
|
||||||
pub fn enable_prompt(state: &AppState, app: AppType, id: &str) -> Result<(), AppError> {
|
pub fn enable_prompt(state: &AppState, app: AppType, id: &str) -> Result<(), AppError> {
|
||||||
// 先保存当前文件内容(如果存在且没有对应的提示词)
|
// 先保存当前文件内容(如果存在且没有对应的提示词)
|
||||||
let target_path = Self::get_prompt_file_path(&app)?;
|
let target_path = prompt_file_path(&app)?;
|
||||||
if target_path.exists() {
|
if target_path.exists() {
|
||||||
let mut cfg = state.config.write()?;
|
let mut cfg = state.config.write()?;
|
||||||
let prompts = match app {
|
let prompts = match app {
|
||||||
@@ -99,7 +99,7 @@ impl PromptService {
|
|||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() as i64;
|
.as_secs() as i64;
|
||||||
let backup_id = format!("backup-{}", timestamp);
|
let backup_id = format!("backup-{timestamp}");
|
||||||
let backup_prompt = Prompt {
|
let backup_prompt = Prompt {
|
||||||
id: backup_id.clone(),
|
id: backup_id.clone(),
|
||||||
name: format!("原始提示词 {}", chrono::Local::now().format("%Y-%m-%d %H:%M")),
|
name: format!("原始提示词 {}", chrono::Local::now().format("%Y-%m-%d %H:%M")),
|
||||||
@@ -133,7 +133,7 @@ impl PromptService {
|
|||||||
prompt.enabled = true;
|
prompt.enabled = true;
|
||||||
write_text_file(&target_path, &prompt.content)?;
|
write_text_file(&target_path, &prompt.content)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(AppError::InvalidInput(format!("提示词 {} 不存在", id)));
|
return Err(AppError::InvalidInput(format!("提示词 {id} 不存在")));
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(cfg);
|
drop(cfg);
|
||||||
@@ -141,38 +141,8 @@ impl PromptService {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
|
||||||
let base_dir = match app {
|
|
||||||
AppType::Claude => crate::config::get_claude_settings_path()
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.to_path_buf())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
dirs::home_dir()
|
|
||||||
.expect("无法获取用户目录")
|
|
||||||
.join(".claude")
|
|
||||||
}),
|
|
||||||
AppType::Codex => crate::codex_config::get_codex_auth_path()
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.to_path_buf())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
dirs::home_dir()
|
|
||||||
.expect("无法获取用户目录")
|
|
||||||
.join(".codex")
|
|
||||||
}),
|
|
||||||
AppType::Gemini => crate::gemini_config::get_gemini_dir(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let filename = match app {
|
|
||||||
AppType::Claude => "CLAUDE.md",
|
|
||||||
AppType::Codex => "AGENTS.md",
|
|
||||||
AppType::Gemini => "GEMINI.md",
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(base_dir.join(filename))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn import_from_file(state: &AppState, app: AppType) -> Result<String, AppError> {
|
pub fn import_from_file(state: &AppState, app: AppType) -> Result<String, AppError> {
|
||||||
let file_path = Self::get_prompt_file_path(&app)?;
|
let file_path = prompt_file_path(&app)?;
|
||||||
|
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
return Err(AppError::Message("提示词文件不存在".to_string()));
|
return Err(AppError::Message("提示词文件不存在".to_string()));
|
||||||
@@ -184,7 +154,7 @@ impl PromptService {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() as i64;
|
.as_secs() as i64;
|
||||||
|
|
||||||
let id = format!("imported-{}", timestamp);
|
let id = format!("imported-{timestamp}");
|
||||||
let prompt = Prompt {
|
let prompt = Prompt {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
name: format!(
|
name: format!(
|
||||||
@@ -203,7 +173,7 @@ impl PromptService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_file_content(app: AppType) -> Result<Option<String>, AppError> {
|
pub fn get_current_file_content(app: AppType) -> Result<Option<String>, AppError> {
|
||||||
let file_path = Self::get_prompt_file_path(&app)?;
|
let file_path = prompt_file_path(&app)?;
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user