nxtgauge-ai-assistant/src/chat/orchestrator.rs

161 lines
5.6 KiB
Rust

use std::sync::Arc;
use crate::{
chat::models::{ChatMessageRequest, ChatMessageResponse},
error::AppError,
forms::{models::FormExtractRequest, service::FormService},
jobs::{models::GenerateJobDescriptionRequest, service::JobsService},
providers::{
help_center::help_center_provider::HelpCenterProvider, llm::ai_provider::AiProvider,
},
tickets::{models::CreateTicketRequest, service::TicketService},
};
#[derive(Clone)]
pub struct ChatOrchestrator {
jobs_service: JobsService,
form_service: FormService,
help_center: Arc<dyn HelpCenterProvider>,
ticket_service: TicketService,
ai_provider: Arc<dyn AiProvider>,
}
impl ChatOrchestrator {
pub fn new(
jobs_service: JobsService,
form_service: FormService,
help_center: Arc<dyn HelpCenterProvider>,
ticket_service: TicketService,
ai_provider: Arc<dyn AiProvider>,
) -> Self {
Self {
jobs_service,
form_service,
help_center,
ticket_service,
ai_provider,
}
}
pub async fn handle_chat(
&self,
request: ChatMessageRequest,
) -> Result<ChatMessageResponse, AppError> {
let intent = classify_intent(&request.message);
match intent.as_str() {
"job_description_generation" => {
let jd = self
.jobs_service
.generate_description(GenerateJobDescriptionRequest {
role_title: request.message.clone(),
seniority: None,
department: None,
employment_type: None,
required_skills: vec!["communication".to_string()],
optional_skills: None,
responsibilities: None,
company_context: None,
})
.await?;
Ok(ChatMessageResponse {
intent,
reply: "Generated a draft job description.".to_string(),
data: serde_json::to_value(jd).unwrap_or(serde_json::Value::Null),
})
}
"form_filling_assistance" => {
let extracted = self
.form_service
.extract(FormExtractRequest {
raw_user_input: request.message.clone(),
expected_fields: None,
})
.await?;
Ok(ChatMessageResponse {
intent,
reply: extracted.suggested_next_step.clone(),
data: serde_json::to_value(extracted).unwrap_or(serde_json::Value::Null),
})
}
"help_article_retrieval" => {
let matches = self.help_center.search(&request.message).await?;
let reply = if matches.is_empty() {
"No help article match found yet.".to_string()
} else {
format!("Found {} help articles.", matches.len())
};
Ok(ChatMessageResponse {
intent,
reply,
data: serde_json::json!({ "matches": matches }),
})
}
"support_ticket_creation" => {
let created = self
.ticket_service
.create(CreateTicketRequest {
subject: request.message.chars().take(80).collect::<String>(),
description: request.message.clone(),
priority: "medium".to_string(),
category: "general".to_string(),
user_id: request.user_id.unwrap_or_else(|| "anonymous".to_string()),
conversation_id: request.conversation_id,
source: Some("chatbot".to_string()),
tags: Some(vec!["chat".to_string()]),
metadata: None,
})
.await?;
Ok(ChatMessageResponse {
intent,
reply: format!("Support ticket created: {}", created.ticket_id),
data: serde_json::to_value(created).unwrap_or(serde_json::Value::Null),
})
}
_ => {
let generic = self
.ai_provider
.complete(
"You are Nxtgauge workflow assistant. Keep answers concise and actionable.",
&request.message,
)
.await?;
Ok(ChatMessageResponse {
intent,
reply: generic,
data: serde_json::json!({}),
})
}
}
}
}
fn classify_intent(message: &str) -> String {
let text = message.to_lowercase();
if text.contains("job description") || text.contains("jd") || text.contains("role") {
return "job_description_generation".to_string();
}
if text.contains("form") || text.contains("field") || text.contains("fill") {
return "form_filling_assistance".to_string();
}
if text.contains("help")
|| text.contains("article")
|| text.contains("kb")
|| text.contains("docs")
{
return "help_article_retrieval".to_string();
}
if text.contains("ticket")
|| text.contains("support")
|| text.contains("issue")
|| text.contains("bug")
{
return "support_ticket_creation".to_string();
}
"general".to_string()
}