@@ -14,9 +14,10 @@ const llmScorer = require('../services/llmScorer');
1414const { isEnabled : isLlmEnabled } = llmScorer ;
1515
1616const MEMORY_MESSAGES = [ ] ;
17- const MIN_CONF_SCORE = 40 ;
17+ const MIN_CONF_SCORE = 1 ;
1818const MIN_CONF_COUNT = 5 ;
19- const MAX_QUESTIONS = 25 ;
19+ const MAX_QUESTIONS = 50 ;
20+ const MIN_REFINEMENT_QUESTIONS = 5 ;
2021
2122const DEFAULT_SUGGESTED_QUESTIONS = {
2223 high_school : [
@@ -33,6 +34,12 @@ const DEFAULT_SUGGESTED_QUESTIONS = {
3334 ]
3435} ;
3536
37+ const DEFAULT_SUGGESTED_ANSWERS = {
38+ high_school : [ 'Có' , 'Có thể' , 'Không' ] ,
39+ university : [ 'Có' , 'Có thể' , 'Không' ] ,
40+ professional : [ 'Có' , 'Có thể' , 'Không' ]
41+ } ;
42+
3643function safeParse ( value , fallback ) {
3744 try {
3845 return JSON . parse ( value ) ;
@@ -48,7 +55,7 @@ function isUserAskingQuestion(message) {
4855 return / ( l a g i | l à g ì | n h u t h e n a o | n h ư t h ế n à o | t a i s a o | t ạ i s a o | b a o n h i e u | b a o l â u | c o n e n | c ó n ê n | l a m s a o | l à m s a o | n g h e n a o | n g h ề n à o | n g a n h n a o | n g à n h n à o | t u v a n | t ư v ấ n ) / i. test ( text ) ;
4956}
5057
51- function pickSuggestedQuestions ( suggestions , userType ) {
58+ function pickSuggestedQuestions ( suggestions , userType , mode = 'question' ) {
5259 const normalized = Array . isArray ( suggestions )
5360 ? suggestions
5461 . map ( ( item ) => String ( item || '' ) . trim ( ) )
@@ -59,19 +66,11 @@ function pickSuggestedQuestions(suggestions, userType) {
5966 if ( unique . length > 0 ) return unique ;
6067
6168 const safeUserType = String ( userType || 'high_school' ) ;
62- const fallbacks = DEFAULT_SUGGESTED_QUESTIONS [ safeUserType ] || DEFAULT_SUGGESTED_QUESTIONS . high_school ;
69+ const fallbackMap = mode === 'answer' ? DEFAULT_SUGGESTED_ANSWERS : DEFAULT_SUGGESTED_QUESTIONS ;
70+ const fallbacks = fallbackMap [ safeUserType ] || fallbackMap . high_school ;
6371 return Array . from ( new Set ( fallbacks ) ) . slice ( 0 , 2 ) ;
6472}
6573
66- function appendSuggestedQuestion ( botReply , suggestedQuestion ) {
67- const base = String ( botReply || '' ) . trim ( ) ;
68- if ( ! base ) return '' ;
69- const suggestion = String ( suggestedQuestion || '' ) . trim ( ) ;
70- if ( ! suggestion ) return base ;
71- if ( / s u g g e s t e d q u e s t i o n \s * : / i. test ( base ) ) return base ;
72- return `${ base } \n\nSuggested question: ${ suggestion } ` ;
73- }
74-
7574function normalizeRecommendationsWithProbability ( recommendations ) {
7675 if ( ! Array . isArray ( recommendations ) || recommendations . length === 0 ) return [ ] ;
7776
@@ -99,6 +98,22 @@ function normalizeRecommendationsWithProbability(recommendations) {
9998 } ) ) ;
10099}
101100
101+ function getQuestionCount ( state ) {
102+ const all = [ ...( state ?. answers || [ ] ) , ...( state ?. refinementAnswers || [ ] ) ] ;
103+ return all . filter ( ( a ) => String ( a ?. questionId || '' ) . startsWith ( 'ai_question_' ) ) . length ;
104+ }
105+
106+ function getRefinementQuestionCount ( state ) {
107+ const all = [ ...( state ?. answers || [ ] ) , ...( state ?. refinementAnswers || [ ] ) ] ;
108+ return all . filter ( ( a ) => String ( a ?. questionId || '' ) . startsWith ( 'ai_refine_' ) ) . length ;
109+ }
110+
111+ function isAffirmativeRefinementRequest ( message ) {
112+ const text = String ( message || '' ) . trim ( ) . toLowerCase ( ) ;
113+ if ( ! text ) return false ;
114+ return / ( c h u a h a i l o n g | c h ư a h à i l ò n g | h o i t i e p | h ỏ i t h ê m | h o i t h e m | m u o n t h e m | m u ố n t h ê m | t h e m c a u h o i | t h ê m c â u h ỏ i | t i e p t u c | t i ế p t ụ c ) / i. test ( text ) ;
115+ }
116+
102117function buildProfileFromState ( profile , state , fallbackEducationLevel ) {
103118 const safeProfile = { ...( state ?. profile || { } ) , ...( profile || { } ) } ;
104119 if ( ! state ) {
@@ -200,9 +215,18 @@ router.post('/message', optionalAuth, async (req, res) => {
200215 recordAnswer ( convId , questionId , message , state . lastQuestionText ) ;
201216 }
202217
218+ state . refinementMode = Boolean ( state . refinementMode ) || Boolean ( request_more ) ;
219+ state . refinementQuestionsAsked = Number ( state . refinementQuestionsAsked || 0 ) ;
220+
221+ if ( request_more || isAffirmativeRefinementRequest ( message ) ) {
222+ state . refinementMode = true ;
223+ }
224+
203225 // Combine all answers for AI analysis
204226 const allAnswers = [ ...( state . answers || [ ] ) , ...( state . refinementAnswers || [ ] ) ] ;
205227 const totalAnswers = allAnswers . length ;
228+ const aiQuestionCount = getQuestionCount ( state ) ;
229+ const refinementQuestionCount = getRefinementQuestionCount ( state ) ;
206230
207231 const historyForAgent = allAnswers . map ( ( a ) => ( {
208232 q : a ?. question || '' ,
@@ -218,7 +242,9 @@ router.post('/message', optionalAuth, async (req, res) => {
218242 let completed = false ;
219243
220244 // Use AI to generate recommendations when we have enough information
221- if ( totalAnswers >= minAnswersForRecommendation && totalAnswers <= maxQuestions && ! request_more ) {
245+ const canConcludeByRefinement = ! state . refinementMode || refinementQuestionCount >= MIN_REFINEMENT_QUESTIONS ;
246+
247+ if ( totalAnswers >= minAnswersForRecommendation && totalAnswers <= maxQuestions && ! request_more && canConcludeByRefinement ) {
222248 try {
223249 const aiRec = await llmScorer . generateCareerRecommendations ( {
224250 userType : effectiveUserType || 'high_school' ,
@@ -237,7 +263,7 @@ router.post('/message', optionalAuth, async (req, res) => {
237263 }
238264
239265 // Force completion if we've asked too many questions
240- if ( ! completed && totalAnswers >= maxQuestions ) {
266+ if ( ! completed && totalAnswers >= maxQuestions && canConcludeByRefinement ) {
241267 try {
242268 const aiRec = await llmScorer . generateCareerRecommendations ( {
243269 userType : effectiveUserType || 'high_school' ,
@@ -254,33 +280,35 @@ router.post('/message', optionalAuth, async (req, res) => {
254280 }
255281 }
256282
257- if ( request_more ) {
283+ if ( state . refinementMode ) {
258284 completed = false ;
259285 recommendations = null ;
260286 }
261287
262288 let botReply = '' ;
263289 let nextNode = null ;
264290 let suggestedQuestions = [ ] ;
291+ let suggestionMode = 'question' ;
265292 const userAskedQuestion = isUserAskingQuestion ( message ) ;
266293
267294 if ( completed && Array . isArray ( recommendations ) && recommendations . length ) {
268295 const top = recommendations . slice ( 0 , 10 ) ;
269296 const lines = top . map ( ( r , idx ) => `${ idx + 1 } . ${ r . career_name } (${ r . match_score } /100)` ) ;
270297 botReply = `Mình đã phân tích xong và tìm ra những nghề nghiệp phù hợp nhất với bạn.\n\nTop gợi ý:\n${ lines . join ( '\n' ) } \n\nChi tiết phân tích đã được cập nhật trong biểu đồ bên dưới.` ;
271298 nextNode = null ;
272- suggestedQuestions = pickSuggestedQuestions ( [ ] , effectiveUserType || 'high_school' ) ;
299+ suggestionMode = 'question' ;
300+ suggestedQuestions = pickSuggestedQuestions ( [ ] , effectiveUserType || 'high_school' , suggestionMode ) ;
273301 } else {
274302 // Use AI to generate the next question
275303 if ( ! llmScorer . isEnabled ( ) ) {
276304 botReply = 'Hiện backend chưa được cấu hình LLM (thiếu GROQ_API_KEY), nên hệ thống không thể tư vấn. Bạn hãy cấu hình GROQ_API_KEY trên Render và redeploy backend để tiếp tục.' ;
277305 nextNode = 'ai_chat' ;
278- const fallbackSuggestions = pickSuggestedQuestions ( [ ] , effectiveUserType || 'high_school' ) ;
306+ const fallbackSuggestions = pickSuggestedQuestions ( [ ] , effectiveUserType || 'high_school' , 'question' ) ;
279307 return res . status ( 503 ) . json ( {
280308 success : false ,
281309 error : botReply ,
282310 data : {
283- bot_reply : appendSuggestedQuestion ( botReply , fallbackSuggestions [ 0 ] || '' ) ,
311+ bot_reply : botReply ,
284312 options : fallbackSuggestions ,
285313 suggested_questions : fallbackSuggestions ,
286314 suggested_question : fallbackSuggestions [ 0 ] || '' ,
@@ -299,8 +327,11 @@ router.post('/message', optionalAuth, async (req, res) => {
299327
300328 if ( aiReply && aiReply . bot_reply ) {
301329 botReply = aiReply . bot_reply ;
302- nextNode = 'ai_chat' ;
303- suggestedQuestions = pickSuggestedQuestions ( aiReply . suggested_questions , effectiveUserType || 'high_school' ) ;
330+ nextNode = 'ai_answer' ;
331+ state . lastQuestionId = 'ai_answer' ;
332+ state . lastQuestionText = aiReply . bot_reply ;
333+ suggestionMode = 'question' ;
334+ suggestedQuestions = pickSuggestedQuestions ( aiReply . suggested_questions , effectiveUserType || 'high_school' , suggestionMode ) ;
304335 }
305336 }
306337
@@ -313,22 +344,28 @@ router.post('/message', optionalAuth, async (req, res) => {
313344 } ) ;
314345
315346 if ( aiQuestion && aiQuestion . question ) {
316- state . lastQuestionId = 'ai_chat' ;
347+ const questionPrefix = state . refinementMode ? 'ai_refine_' : 'ai_question_' ;
348+ const nextQuestionIndex = ( state . refinementMode ? refinementQuestionCount : aiQuestionCount ) + 1 ;
349+ state . lastQuestionId = `${ questionPrefix } ${ nextQuestionIndex } ` ;
317350 state . lastQuestionText = aiQuestion . question ;
318351 botReply = aiQuestion . question ;
319352 nextNode = 'ai_chat' ;
320- suggestedQuestions = pickSuggestedQuestions ( aiQuestion . options , effectiveUserType || 'high_school' ) ;
353+ suggestionMode = 'answer' ;
354+ suggestedQuestions = pickSuggestedQuestions ( aiQuestion . options , effectiveUserType || 'high_school' , suggestionMode ) ;
355+ if ( state . refinementMode ) {
356+ state . refinementQuestionsAsked = Number ( state . refinementQuestionsAsked || 0 ) + 1 ;
357+ }
321358 } else {
322359 botReply = 'Cảm ơn bạn đã chia sẻ. Mình đang phân tích thông tin để đưa ra gợi ý phù hợp nhất. Vui lòng đợi một chút...' ;
323360 nextNode = 'ai_chat' ;
324- suggestedQuestions = pickSuggestedQuestions ( [ ] , effectiveUserType || 'high_school' ) ;
361+ suggestionMode = 'question' ;
362+ suggestedQuestions = pickSuggestedQuestions ( [ ] , effectiveUserType || 'high_school' , suggestionMode ) ;
325363 }
326364 }
327365 }
328366
329- suggestedQuestions = pickSuggestedQuestions ( suggestedQuestions , effectiveUserType || 'high_school' ) ;
367+ suggestedQuestions = pickSuggestedQuestions ( suggestedQuestions , effectiveUserType || 'high_school' , suggestionMode ) ;
330368 const suggestedQuestion = suggestedQuestions [ 0 ] || '' ;
331- botReply = appendSuggestedQuestion ( botReply , suggestedQuestion ) ;
332369
333370 if ( userId ) {
334371 await saveMessage ( convId , userId , 'bot' , botReply , nextNode ) ;
@@ -348,7 +385,11 @@ router.post('/message', optionalAuth, async (req, res) => {
348385 next_node : nextNode ,
349386 conversation_id : convId ,
350387 recommendations : recommendations || undefined ,
351- completed
388+ completed,
389+ ai_question_count : aiQuestionCount ,
390+ refinement_question_count : refinementQuestionCount ,
391+ refinement_mode : Boolean ( state . refinementMode ) ,
392+ min_refinement_questions : MIN_REFINEMENT_QUESTIONS
352393 }
353394 } ) ;
354395
0 commit comments