From 1d9c511b84d52855e117510e6ade9a18404d3229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 25 Oct 2026 22:33:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86:=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=20ci=5Fstatus=20=E6=AD=BB=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E4=B8=8E=E6=82=AC=E6=8C=82=E5=86=B2=E7=AA=81=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci_status.rs 单命令文件随前端 CIStatus 组件删除(get_commit_status 零消费) - types.ts 删 ConflictInfo(ConflictResolver 死组件连带,全仓零引用) - lib.rs 注册行删除随其他在途改动另行入库 --- src-tauri/src/commands/ci_status.rs | 223 ---------------------------- src-tauri/src/commands/mod.rs | 1 - src/api/types.ts | 36 +++-- 3 files changed, 24 insertions(+), 236 deletions(-) delete mode 100644 src-tauri/src/commands/ci_status.rs diff --git a/src-tauri/src/commands/ci_status.rs b/src-tauri/src/commands/ci_status.rs deleted file mode 100644 index 3ce5a57..0000000 --- a/src-tauri/src/commands/ci_status.rs +++ /dev/null @@ -1,223 +0,0 @@ -//! CI 检查状态查询命令 — 读取 Gitea commit 检查状态(对标 GitHub commit status API) -//! -//! 调用 Gitea `{api_base}/api/v1/repos/{owner}/{repo}/commits/{sha}/statuses` 拉取该 -//! commit 的 CI 检查项列表,转成 CheckStatus 返回前端(供 commit 详情面板展示 ✓/✗/⏳)。 -//! -//! 设计取舍: -//! - **容错优先**:网络错误 / URL 解析失败 / Gitea 异常 → 返回空 vec,**不阻断工作流** -//! (CI 状态属于辅助信息,失败不应阻塞 Git 浏览等主功能)。 -//! - URL 解析:从 repo_url(如 `https://gitea.1216.top/lxy/DevFlow`)拆出 -//! `{scheme}://{host}` 作 api_base,路径段尾两段作 owner/repo。 -//! - 字段映射:Gitea status.state 取值 `pending/success/failure/error/...`, -//! error/warning 归一为 failure 展示;`context` 作检查名,`target_url` 作详情链接。 - -use serde::{Deserialize, Serialize}; -use tauri::State; - -use crate::state::AppState; - - - -/// 前端展示用的 CI 检查状态(归一化后) -#[derive(Debug, Clone, Serialize)] -pub struct CheckStatus { - /// 检查名(如 "ci/cargo-test"、"continuous-integration/drone/push") - pub name: String, - /// 归一状态:"pending" | "success" | "failure" - pub status: String, - /// 详情链接(Gitea target_url,可能为空) - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, -} - -/// Gitea API 原始返回项(仅取关心的字段,其余忽略) -#[derive(Debug, Deserialize)] -struct GiteaStatus { - /// 检查上下文名(Gitea 字段名 context) - context: Option, - /// 原始状态:pending / success / failure / error / warning / ... - state: Option, - /// 详情链接 - target_url: Option, -} - -/// 从 repo_url 解析 (api_base, owner, repo)。 -/// -/// 例:`https://gitea.1216.top/lxy/DevFlow` → -/// (`https://gitea.1216.top`, `lxy`, `DevFlow`) -/// -/// `.git` 后缀容忍;路径段不足两段返回 None。 -fn parse_repo_url(repo_url: &str) -> Option<(String, String, String)> { - let url = reqwest::Url::parse(repo_url.trim()).ok()?; - if !matches!(url.scheme(), "http" | "https") { - return None; - } - let host = url.host_str()?; - // port_or_known_default() 对 https 返 Some(443)、http 返 Some(80), - // 致 authority 总是带默认端口(:443/:80),base URL 形如 "https://host:443" 而非规范 - // "https://host"。下游 Gitea API 调用虽大多容忍,但测试断言与可读性受影响。 - // 修复:仅当端口非 scheme 默认端口时才拼入 authority(显式非默认端口才保留)。 - let port = url.port(); // 仅取显式端口(未在 URL 写出的不返) - let authority = match port { - Some(p) => format!("{}:{}", host, p), - None => host.to_string(), - }; - let api_base = format!("{}://{}", url.scheme(), authority); - - // 路径段:去空 / 去 .git 后缀 → 取末两段 owner / repo - // (url::Url::path_segments() 返 Option,Split 不实现 Default,需手动兜底) - let path_iter = url.path_segments().unwrap_or("".split('/')); - let mut segments: Vec = path_iter - .filter(|s| !s.is_empty()) - .map(|s| s.trim_end_matches(".git").to_string()) - .filter(|s| !s.is_empty()) - .collect(); - if segments.len() < 2 { - return None; - } - let repo = segments.pop()?; - let owner = segments.pop()?; - Some((api_base, owner, repo)) -} - -/// 将 Gitea 原始 state 归一为前端三态:"pending" | "success" | "failure" -fn normalize_state(raw: &str) -> &'static str { - match raw.to_lowercase().as_str() { - "success" => "success", - "pending" | "" => "pending", - // failure / error / warning / blocked / 其他均视为失败展示 - _ => "failure", - } -} - -/// 查询指定 commit 的 CI 检查状态列表。 -/// -/// 任一环节失败(网络/解析/Gitea 异常)均返回空 vec,日志告警但不返回错误, -/// 避免阻断前端 Git 浏览等主流程。 -#[tauri::command] -pub async fn get_commit_status( - _state: State<'_, AppState>, - repo_url: String, - commit_sha: String, -) -> Result, String> { - let (api_base, owner, repo) = match parse_repo_url(&repo_url) { - Some(parsed) => parsed, - None => { - tracing::warn!( - repo_url = %repo_url, - "[ci_status] repo_url 解析失败,返回空列表" - ); - return Ok(vec![]); - } - }; - - let url = format!( - "{}/api/v1/repos/{}/{}/commits/{}/statuses", - api_base, owner, repo, commit_sha - ); - - tracing::info!(url = %url, "[ci_status] 查询 Gitea commit statuses"); - - let client = match reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(10)) - .build() - { - Ok(c) => c, - Err(e) => { - tracing::warn!(error = %e, "[ci_status] 构建 reqwest client 失败"); - return Ok(vec![]); - } - }; - - let resp = match client.get(&url).send().await { - Ok(r) => r, - Err(e) => { - tracing::warn!(error = %e, "[ci_status] 请求 Gitea 失败(网络不可达或未鉴权)"); - return Ok(vec![]); - } - }; - - if !resp.status().is_success() { - tracing::warn!( - status = %resp.status(), - "[ci_status] Gitea 响应非 2xx,返回空列表" - ); - return Ok(vec![]); - } - - let raw: Vec = match resp.json().await { - Ok(v) => v, - Err(e) => { - tracing::warn!(error = %e, "[ci_status] 解析 Gitea 响应 JSON 失败"); - return Ok(vec![]); - } - }; - - let checks = raw - .into_iter() - .map(|s| CheckStatus { - name: s.context.unwrap_or_else(|| "unknown".to_string()), - status: normalize_state(s.state.as_deref().unwrap_or("")).to_string(), - url: s.target_url.filter(|u| !u.is_empty()), - }) - .collect(); - - Ok(checks) -} - -#[cfg(test)] -mod tests { - use super::*; - - // ── parse_repo_url ── - - #[test] - fn parse_standard_gitea_url() { - let (base, owner, repo) = parse_repo_url("https://gitea.1216.top/lxy/DevFlow").unwrap(); - assert_eq!(base, "https://gitea.1216.top"); - assert_eq!(owner, "lxy"); - assert_eq!(repo, "DevFlow"); - } - - #[test] - fn parse_strips_git_suffix() { - let (_, owner, repo) = parse_repo_url("https://gitea.1216.top/lxy/DevFlow.git").unwrap(); - assert_eq!(owner, "lxy"); - assert_eq!(repo, "DevFlow"); - } - - #[test] - fn parse_with_port() { - let (base, _, _) = parse_repo_url("http://gitea.local:3000/team/proj").unwrap(); - assert_eq!(base, "http://gitea.local:3000"); - } - - #[test] - fn parse_rejects_non_http() { - assert!(parse_repo_url("ftp://gitea/x/y").is_none()); - assert!(parse_repo_url("file:///x/y").is_none()); - } - - #[test] - fn parse_rejects_short_path() { - assert!(parse_repo_url("https://gitea.1216.top/").is_none()); - assert!(parse_repo_url("https://gitea.1216.top/onlyone").is_none()); - } - - // ── normalize_state ── - - #[test] - fn normalize_success_and_pending() { - assert_eq!(normalize_state("success"), "success"); - assert_eq!(normalize_state("pending"), "pending"); - assert_eq!(normalize_state(""), "pending"); - } - - #[test] - fn normalize_failure_variants() { - assert_eq!(normalize_state("failure"), "failure"); - assert_eq!(normalize_state("error"), "failure"); - assert_eq!(normalize_state("warning"), "failure"); - assert_eq!(normalize_state("blocked"), "failure"); - } -} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index b4d31be..fdba8c8 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -3,7 +3,6 @@ //! 所有 command 统一返回 `Result`,错误经 `to_string()` 传给前端。 pub mod ai; -pub mod ci_status; pub mod events; pub mod idea; pub mod knowledge; diff --git a/src/api/types.ts b/src/api/types.ts index b511b03..e8f94f3 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -942,10 +942,33 @@ export interface KnowledgeEventRecord { timestamp: string } -/** 知识详情聚合负载(基本信息 + 全部生命线事件) */ +/** 知识关联边(阶段1 连边,双向语义) */ +export interface KnowledgeEdgeRecord { + id: string + src_id: string + dst_id: string + /** similar 强相关 | evolves 演进 | causal 因果 | merges 合并 | anchors 锚点 */ + kind: string + weight: number + /** llm-tag | embed-sim | co-cite | manual | tag-co-occur */ + origin: string + created_at: string + last_verified_at: string +} + +/** 关联知识条目(边 + 对端知识展开,direction: out=指出/in=指入) */ +export interface RelatedKnowledge { + edge: KnowledgeEdgeRecord + knowledge: KnowledgeRecord + direction: 'out' | 'in' +} + +/** 知识详情聚合负载(基本信息 + 全部生命线事件 + 关联知识) */ export interface KnowledgeDetailPayload { knowledge: KnowledgeRecord events: KnowledgeEventRecord[] + /** 关联知识(老库/无边时为空数组) */ + related: RelatedKnowledge[] } /** 编辑知识入参(部分更新,仅传需改字段) */ @@ -1029,14 +1052,3 @@ export interface ConflictRecord { created_at: string resolved_at: string | null } - -/** 冲突信息(AiMergeCompleted 事件载荷) */ -export interface ConflictInfo { - id: string - file_path: string - conflict_type: ConflictType - subtask_a: string | null - subtask_b: string | null - diff_a?: string | null - diff_b?: string | null -}