Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -0,0 +1,215 @@
//! Shared test fixtures for the knowledge crate: an in-memory
//! `IKnowledgeRepository`, a no-op event broadcaster, and a service factory.
use std::path::Path;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
use nomifun_common::TimestampMs;
use nomifun_db::models::{CreateKnowledgeTagParams, KnowledgeBaseRow, KnowledgeBindingRow, KnowledgeTagRow, UpdateKnowledgeTagParams};
use nomifun_db::{DbError, IKnowledgeRepository};
use crate::events::KnowledgeEventEmitter;
use crate::service::KnowledgeService;
#[derive(Default)]
pub(crate) struct MemRepo {
pub bases: Mutex<Vec<KnowledgeBaseRow>>,
/// Each binding row plus its ordered `kb_id` list (mirrors the
/// `knowledge_binding_bases` junction).
pub bindings: Mutex<Vec<(KnowledgeBindingRow, Vec<String>)>>,
next_binding_id: AtomicI64,
pub tags: Mutex<Vec<KnowledgeTagRow>>,
}
/// Build a binding row with the `target_id` written to the column selected by
/// `target_kind` (the in-memory analogue of the CHECK-constrained columns).
fn binding_row(
binding_id: i64,
kind: &str,
target_id: &str,
enabled: bool,
writeback: bool,
writeback_mode: &str,
writeback_eagerness: &str,
channel_write_enabled: bool,
updated_at: TimestampMs,
) -> KnowledgeBindingRow {
let mut row = KnowledgeBindingRow {
binding_id,
target_kind: kind.to_owned(),
target_workpath: None,
target_conv_id: None,
target_term_id: None,
target_companion_id: None,
enabled,
writeback,
writeback_mode: writeback_mode.to_owned(),
writeback_eagerness: writeback_eagerness.to_owned(),
channel_write_enabled,
updated_at,
};
// `target_conv_id` / `target_term_id` are integer FKs now; `target_workpath`
// / `target_companion_id` stay string keys. Route the test id to the right column
// with the right type.
match kind {
"workpath" => row.target_workpath = Some(target_id.to_owned()),
"conversation" => row.target_conv_id = target_id.parse::<i64>().ok(),
"terminal" => row.target_term_id = target_id.parse::<i64>().ok(),
"companion" => row.target_companion_id = Some(target_id.to_owned()),
_ => {}
}
row
}
#[async_trait::async_trait]
impl IKnowledgeRepository for MemRepo {
async fn insert_base(&self, row: &KnowledgeBaseRow) -> Result<(), DbError> {
self.bases.lock().unwrap().push(row.clone());
Ok(())
}
async fn update_base(&self, row: &KnowledgeBaseRow) -> Result<(), DbError> {
let mut bases = self.bases.lock().unwrap();
match bases.iter_mut().find(|r| r.id == row.id) {
Some(r) => {
*r = row.clone();
Ok(())
}
None => Err(DbError::NotFound(row.id.clone())),
}
}
async fn delete_base(&self, id: &str) -> Result<(), DbError> {
let mut bases = self.bases.lock().unwrap();
let before = bases.len();
bases.retain(|r| r.id != id);
if bases.len() == before {
Err(DbError::NotFound(id.to_owned()))
} else {
Ok(())
}
}
async fn get_base(&self, id: &str) -> Result<Option<KnowledgeBaseRow>, DbError> {
Ok(self.bases.lock().unwrap().iter().find(|r| r.id == id).cloned())
}
async fn list_bases(&self) -> Result<Vec<KnowledgeBaseRow>, DbError> {
Ok(self.bases.lock().unwrap().clone())
}
async fn get_binding(
&self,
kind: &str,
id: &str,
) -> Result<Option<(KnowledgeBindingRow, Vec<String>)>, DbError> {
Ok(self
.bindings
.lock()
.unwrap()
.iter()
.find(|(b, _)| b.target_kind == kind && b.target_id().as_deref() == Some(id))
.cloned())
}
async fn set_binding(
&self,
kind: &str,
id: &str,
kb_ids: &[String],
enabled: bool,
writeback: bool,
writeback_mode: &str,
writeback_eagerness: &str,
channel_write_enabled: bool,
updated_at: TimestampMs,
) -> Result<i64, DbError> {
let mut bindings = self.bindings.lock().unwrap();
// Reuse the existing binding_id on upsert; allocate a fresh one
// otherwise (the surrogate-key analogue of the real table).
let binding_id = bindings
.iter()
.find(|(b, _)| b.target_kind == kind && b.target_id().as_deref() == Some(id))
.map(|(b, _)| b.binding_id)
.unwrap_or_else(|| self.next_binding_id.fetch_add(1, Ordering::SeqCst) + 1);
bindings.retain(|(b, _)| !(b.target_kind == kind && b.target_id().as_deref() == Some(id)));
let row = binding_row(binding_id, kind, id, enabled, writeback, writeback_mode, writeback_eagerness, channel_write_enabled, updated_at);
bindings.push((row, kb_ids.to_vec()));
Ok(binding_id)
}
async fn delete_binding(&self, kind: &str, id: &str) -> Result<(), DbError> {
self.bindings
.lock()
.unwrap()
.retain(|(b, _)| !(b.target_kind == kind && b.target_id().as_deref() == Some(id)));
Ok(())
}
async fn list_bindings_using_kb(&self, kb_id: &str) -> Result<Vec<KnowledgeBindingRow>, DbError> {
let mut rows: Vec<KnowledgeBindingRow> = self
.bindings
.lock()
.unwrap()
.iter()
.filter(|(_, kb_ids)| kb_ids.iter().any(|k| k == kb_id))
.map(|(b, _)| b.clone())
.collect();
rows.sort_by(|a, b| a.target_kind.cmp(&b.target_kind).then(a.binding_id.cmp(&b.binding_id)));
Ok(rows)
}
async fn list_knowledge_tags(&self) -> Result<Vec<KnowledgeTagRow>, DbError> {
let mut tags = self.tags.lock().unwrap().clone();
tags.sort_by(|a, b| a.sort_order.cmp(&b.sort_order).then(a.key.cmp(&b.key)));
Ok(tags)
}
async fn create_knowledge_tag(&self, params: CreateKnowledgeTagParams) -> Result<(), DbError> {
self.tags.lock().unwrap().push(KnowledgeTagRow {
key: params.key,
label: params.label,
color: params.color,
sort_order: params.sort_order,
created_at: params.created_at,
});
Ok(())
}
async fn update_knowledge_tag(&self, key: &str, params: UpdateKnowledgeTagParams) -> Result<(), DbError> {
let mut tags = self.tags.lock().unwrap();
match tags.iter_mut().find(|t| t.key == key) {
Some(t) => {
if let Some(label) = params.label {
t.label = label;
}
if let Some(color) = params.color {
t.color = color;
}
if let Some(sort_order) = params.sort_order {
t.sort_order = sort_order;
}
Ok(())
}
None => Err(DbError::NotFound(format!("knowledge tag {key}"))),
}
}
async fn delete_knowledge_tag(&self, key: &str) -> Result<(), DbError> {
let mut tags = self.tags.lock().unwrap();
let before = tags.len();
tags.retain(|t| t.key != key);
if tags.len() == before {
Err(DbError::NotFound(format!("knowledge tag {key}")))
} else {
Ok(())
}
}
}
pub(crate) struct NoopBroadcaster;
impl nomifun_realtime::EventBroadcaster for NoopBroadcaster {
fn broadcast(&self, _event: nomifun_api_types::WebSocketMessage<serde_json::Value>) {}
}
pub(crate) fn make_service(data_dir: &Path) -> KnowledgeService {
KnowledgeService::new(
Arc::new(MemRepo::default()),
data_dir,
KnowledgeEventEmitter::new(Arc::new(NoopBroadcaster)),
)
}