feat(service): trigger Qdrant embedding on issue/repo/skill creation
- After issue_create: spawn embed_issue_chunked (non-blocking) - After skill_create/update: spawn embed_skill - After repo create/update in fctool: spawn embed_repo - Wire EmbedService through AppService, available for all triggers
This commit is contained in:
parent
93ec515f29
commit
62727a93a1
@ -169,6 +169,19 @@ pub async fn create_repo_exec(
|
|||||||
git2::Repository::init_bare(&repo_dir)
|
git2::Repository::init_bare(&repo_dir)
|
||||||
.map_err(|e| ToolError::ExecutionError(format!("Failed to init bare repo: {}", e)))?;
|
.map_err(|e| ToolError::ExecutionError(format!("Failed to init bare repo: {}", e)))?;
|
||||||
|
|
||||||
|
// Embed repo into Qdrant for semantic search (non-blocking)
|
||||||
|
if let Some(embed) = ctx.embed_service() {
|
||||||
|
let es = embed.clone();
|
||||||
|
let repo_id = model.id.to_string();
|
||||||
|
let repo_name = model.repo_name.clone();
|
||||||
|
let repo_desc = model.description.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = es.embed_repo(&repo_id, &repo_name, repo_desc.as_deref()).await {
|
||||||
|
tracing::warn!(error = %e, repo_id = %repo_id, "failed to embed repo");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"id": model.id.to_string(),
|
"id": model.id.to_string(),
|
||||||
"name": model.repo_name,
|
"name": model.repo_name,
|
||||||
@ -256,6 +269,19 @@ pub async fn update_repo_exec(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| ToolError::ExecutionError(e.to_string()))?;
|
.map_err(|e| ToolError::ExecutionError(e.to_string()))?;
|
||||||
|
|
||||||
|
// Re-embed repo on update (non-blocking)
|
||||||
|
if let Some(embed) = ctx.embed_service() {
|
||||||
|
let es = embed.clone();
|
||||||
|
let repo_id = model.id.to_string();
|
||||||
|
let repo_name = model.repo_name.clone();
|
||||||
|
let repo_desc = model.description.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = es.embed_repo(&repo_id, &repo_name, repo_desc.as_deref()).await {
|
||||||
|
tracing::warn!(error = %e, repo_id = %repo_id, "failed to re-embed repo on update");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"id": model.id.to_string(),
|
"id": model.id.to_string(),
|
||||||
"name": model.repo_name,
|
"name": model.repo_name,
|
||||||
|
|||||||
@ -292,6 +292,19 @@ impl AppService {
|
|||||||
let _ = this.triage_issue(project_name_clone, issue_number).await;
|
let _ = this.triage_issue(project_name_clone, issue_number).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Embed issue into Qdrant for semantic search (non-blocking)
|
||||||
|
if let Some(ref embed) = self.embed_service {
|
||||||
|
let issue_id = model.id.to_string();
|
||||||
|
let issue_title = model.title.clone();
|
||||||
|
let issue_body = model.body.clone();
|
||||||
|
let es = embed.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = es.embed_issue_chunked(&issue_id, &issue_title, issue_body.as_deref()).await {
|
||||||
|
tracing::warn!(error = %e, issue_id = %issue_id, "failed to embed issue");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(IssueResponse::from(model))
|
Ok(IssueResponse::from(model))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,7 @@ pub struct AppService {
|
|||||||
pub queue_producer: MessageProducer,
|
pub queue_producer: MessageProducer,
|
||||||
pub storage: Option<AppStorage>,
|
pub storage: Option<AppStorage>,
|
||||||
pub push: Option<WebPushService>,
|
pub push: Option<WebPushService>,
|
||||||
|
pub embed_service: Option<Arc<EmbedService>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppService {
|
impl AppService {
|
||||||
@ -185,6 +186,8 @@ impl AppService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let embed_service_for_app = embed_service.clone();
|
||||||
|
|
||||||
// Build ChatService if AI is configured; otherwise AI chat is disabled (graceful degradation)
|
// Build ChatService if AI is configured; otherwise AI chat is disabled (graceful degradation)
|
||||||
let chat_service: Option<Arc<ChatService>> =
|
let chat_service: Option<Arc<ChatService>> =
|
||||||
match (config.ai_api_key(), config.ai_basic_url()) {
|
match (config.ai_api_key(), config.ai_basic_url()) {
|
||||||
@ -276,6 +279,7 @@ impl AppService {
|
|||||||
queue_producer: message_producer,
|
queue_producer: message_producer,
|
||||||
storage,
|
storage,
|
||||||
push,
|
push,
|
||||||
|
embed_service: embed_service_for_app,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -104,6 +104,22 @@ impl AppService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let inserted = active.insert(&self.db).await?;
|
let inserted = active.insert(&self.db).await?;
|
||||||
|
|
||||||
|
// Embed skill into Qdrant (non-blocking)
|
||||||
|
if let Some(ref embed) = self.embed_service {
|
||||||
|
let es = embed.clone();
|
||||||
|
let sid = inserted.id;
|
||||||
|
let sname = inserted.name.clone();
|
||||||
|
let sdesc = inserted.description.clone();
|
||||||
|
let scontent = inserted.content.clone();
|
||||||
|
let sproj = inserted.project_uuid.to_string();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = es.embed_skill(sid, &sname, sdesc.as_deref(), &scontent, &sproj).await {
|
||||||
|
tracing::warn!(error = %e, skill_id = %sid, "failed to embed skill");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(SkillResponse::from(inserted))
|
Ok(SkillResponse::from(inserted))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +160,22 @@ impl AppService {
|
|||||||
active.updated_at = Set(Utc::now());
|
active.updated_at = Set(Utc::now());
|
||||||
|
|
||||||
let updated = active.update(&self.db).await?;
|
let updated = active.update(&self.db).await?;
|
||||||
|
|
||||||
|
// Re-embed skill on update (non-blocking)
|
||||||
|
if let Some(ref embed) = self.embed_service {
|
||||||
|
let es = embed.clone();
|
||||||
|
let sid = updated.id;
|
||||||
|
let sname = updated.name.clone();
|
||||||
|
let sdesc = updated.description.clone();
|
||||||
|
let scontent = updated.content.clone();
|
||||||
|
let sproj = updated.project_uuid.to_string();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = es.embed_skill(sid, &sname, sdesc.as_deref(), &scontent, &sproj).await {
|
||||||
|
tracing::warn!(error = %e, skill_id = %sid, "failed to re-embed skill on update");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(SkillResponse::from(updated))
|
Ok(SkillResponse::from(updated))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user