feat: 0.2.27 - 抽离数据库基础设施模块

- 新增 ias-db crate,集中管理 PostgreSQL 连接池和 SQLx 迁移
- 迁移 ias-service/migrations 到 ias-db/migrations
- ias-main 改用 ias_db::connect_db(),ias-service 只接 PgPool
- 新增操作日志 HTTP 查询接口 (GET /v1/logs, GET /v1/logs/{id})
- 新增操作日志 Web 前端页面,支持按级别/模块/关键词筛选
- 消息队列关键节点增加操作日志写入
- ias-mail 新增 mark_remote_message_seen,IMAP 已读同步
- 工作流配置加载改为文件级容错 (load_configs_with_warnings)
- ias-context 工具调用说明更新为 query_api_docs + call_http_api
- 新增高德地图地理编码 HTTP 能力配置

模块版本: ias-db 0.1.0, ias-ai 0.1.14, ias-context 0.1.6, ias-http 0.1.15, ias-mail 0.1.16, ias-service 0.1.25, ias-main 0.2.27
This commit is contained in:
2026-07-08 17:44:21 +08:00
parent 7d5140c8d2
commit 7deae286ca
83 changed files with 1440 additions and 107 deletions
+70 -5
View File
@@ -4,6 +4,12 @@ use tokio::fs;
use crate::workflow::flow::start_flow;
use crate::workflow::types::FlowConfig;
#[derive(Debug, Clone)]
pub struct LoadedFlowConfigs {
pub configs: Vec<FlowConfig>,
pub warnings: Vec<String>,
}
pub async fn call_flow(path: String, args: String) -> String {
let all_flow = match load_configs(path).await {
Ok(flows) => flows,
@@ -26,20 +32,27 @@ pub async fn call_flow(path: String, args: String) -> String {
}
pub async fn load_tools_define(path: String) -> anyhow::Result<String> {
let configs = load_configs(path).await?;
Ok(serde_json::to_string(&configs).unwrap_or_default())
let loaded = load_configs_with_warnings(path).await?;
Ok(serde_json::to_string(&loaded.configs).unwrap_or_default())
}
pub async fn load_configs(path: String) -> anyhow::Result<Vec<FlowConfig>> {
Ok(load_configs_with_warnings(path).await?.configs)
}
pub async fn load_configs_with_warnings(path: String) -> anyhow::Result<LoadedFlowConfigs> {
let mut configs: Vec<FlowConfig> = vec![];
let mut warnings: Vec<String> = vec![];
let config_files = scan_config_dir(path).await?;
if !config_files.is_empty() {
for file in config_files {
let config = load_yaml(file).await?;
configs.push(config);
match load_yaml(file.clone()).await {
Ok(config) => configs.push(config),
Err(error) => warnings.push(format!("跳过工具配置 {file}: {error}")),
}
}
}
Ok(configs)
Ok(LoadedFlowConfigs { configs, warnings })
}
pub async fn load_yaml(path: String) -> anyhow::Result<FlowConfig> {
@@ -82,3 +95,55 @@ async fn list_file() {
println!();
}
#[tokio::test]
async fn load_configs_skips_invalid_yaml_files() {
let dir = unique_test_dir("load_configs_skips_invalid_yaml_files");
tokio::fs::create_dir_all(&dir).await.unwrap();
tokio::fs::write(
dir.join("good.yaml"),
r#"
name: good_tool
desc: good config
params: []
steps: []
"#,
)
.await
.unwrap();
tokio::fs::write(
dir.join("bad.yaml"),
r#"
name: bad_tool
steps:
- query:
- key: value
"#,
)
.await
.unwrap();
let loaded = load_configs_with_warnings(dir.display().to_string())
.await
.unwrap();
assert_eq!(loaded.configs.len(), 1);
assert_eq!(loaded.configs[0].name, "good_tool");
assert_eq!(loaded.warnings.len(), 1);
assert!(loaded.warnings[0].contains("bad.yaml"));
let configs = load_configs(dir.display().to_string()).await.unwrap();
assert_eq!(configs.len(), 1);
assert_eq!(configs[0].name, "good_tool");
let _ = tokio::fs::remove_dir_all(&dir).await;
}
#[cfg(test)]
fn unique_test_dir(name: &str) -> std::path::PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
std::env::temp_dir().join(format!("ias-ai-{name}-{}-{nanos}", std::process::id()))
}