@@ -2749,14 +2749,14 @@ function pickNextQuestion(state) {
27492749 state . focusIndex += 1 ;
27502750 state . focusCount += 1 ;
27512751 state . lastQuestionTags = q . tags || [ ] ;
2752- state . lastQuestionText = q . text ; // Store text for LLM context
2752+ state . lastQuestionText = q . text ; // Store for LLM context
27532753 return q ;
27542754 }
27552755
27562756 const q = GENERAL_QUESTIONS [ state . generalIndex % GENERAL_QUESTIONS . length ] ;
27572757 state . generalIndex += 1 ;
27582758 state . lastQuestionTags = q . tags || [ ] ;
2759- state . lastQuestionText = q . text ; // Store text for LLM context
2759+ state . lastQuestionText = q . text ; // Store for LLM context
27602760 return q ;
27612761}
27622762
@@ -2835,62 +2835,101 @@ function hashCode(value) {
28352835
28362836
28372837
2838- export const offlineApi = {
2839- getMe ( token ) {
2840- if ( ! token || ! token . startsWith ( 'offline:' ) ) return { success : false } ;
2841- const userId = Number ( token . replace ( 'offline:' , '' ) ) ;
2842- const user = getUsers ( ) . find ( ( u ) => u . id === userId ) ;
2843- if ( ! user ) return { success : false } ;
2844- return { success : true , data : { user_id : user . id , email : user . email , user_type : user . user_type } } ;
2845- } ,
2846- login ( { email, password } ) {
2847- const user = getUsers ( ) . find ( ( u ) => u . email === email && u . password === password ) ;
2848- if ( ! user ) return { success : false , error : 'Sai email hoặc mật khẩu' } ;
2849- const token = `offline:${ user . id } ` ;
2850- return { success : true , data : { user_id : user . id , email : user . email , user_type : user . user_type , token } } ;
2851- } ,
2852- register ( { email, password, user_type } ) {
2853- const users = getUsers ( ) ;
2854- if ( users . find ( ( u ) => u . email === email ) ) {
2855- return { success : false , error : 'Email đã tồn tại' } ;
2838+ async function analyzeResponseWithLLM ( question , answer , profile ) {
2839+ const apiKey = localStorage . getItem ( 'GEMINI_API_KEY' ) ;
2840+ if ( ! apiKey ) return null ;
2841+
2842+ try {
2843+ const prompt = `
2844+ Bạn là một chuyên gia tư vấn nghề nghiệp.
2845+ Câu hỏi cuối cùng của chatbot: "${ question } "
2846+ Câu trả lời của người dùng: "${ answer } "
2847+ Thông tin hồ sơ người dùng: ${ JSON . stringify ( profile ) }
2848+
2849+ Hãy phân tích câu trả lời trên:
2850+ 1. Phân loại thái độ (sentiment): "positive" (tích cực/đồng ý), "negative" (tiêu cực/không đồng ý), "neutral" (trung lập).
2851+ 2. Xác định các chủ đề nghề nghiệp hoặc sở thích ẩn ý (implied_topics) dựa trên câu trả lời (tiếng Anh).
2852+
2853+ TRẢ VỀ DUY NHẤT JSON:
2854+ {
2855+ "sentiment": "positive" | "negative" | "neutral",
2856+ "confidence": 0.0-1.0,
2857+ "implied_topics": ["topic1", "topic2"],
2858+ "reasoning": "giải thích ngắn gọn"
2859+ }
2860+ ` ;
2861+
2862+ const response = await fetch ( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${ apiKey } ` , {
2863+ method : 'POST' ,
2864+ headers : { 'Content-Type' : 'application/json' } ,
2865+ body : JSON . stringify ( {
2866+ contents : [ { parts : [ { text : prompt } ] } ]
2867+ } )
2868+ } ) ;
2869+
2870+ const data = await response . json ( ) ;
2871+ const text = data . candidates ?. [ 0 ] ?. content ?. parts ?. [ 0 ] ?. text ;
2872+ if ( ! text ) return null ;
2873+
2874+ const jsonMatch = text . match ( / \{ [ \s \S ] * \} / ) ;
2875+ if ( jsonMatch ) {
2876+ return JSON . parse ( jsonMatch [ 0 ] ) ;
28562877 }
2857- const nextId = Math . max ( 1 , ...users . map ( ( u ) => u . id ) ) + 1 ;
2858- const user = { id : nextId , email, password, user_type : user_type || 'high_school' } ;
2859- users . push ( user ) ;
2860- saveUsers ( users ) ;
2861- const token = `offline:${ user . id } ` ;
2862- return { success : true , data : { user_id : user . id , email : user . email , user_type : user . user_type , token } } ;
2863- } ,
2864- getProfile ( userId ) {
2865- const profiles = getProfiles ( ) ;
2866- return { success : true , data : profiles [ userId ] || null } ;
2867- } ,
2868- updateProfile ( userId , payload ) {
2869- const profiles = getProfiles ( ) ;
2870- profiles [ userId ] = { ...profiles [ userId ] , ...payload } ;
2871- saveProfiles ( profiles ) ;
2872- return { success : true , data : { updated : true } } ;
2873- } ,
2874- sendMessage ( { conversation_id, message, user_id, request_more, profile } ) {
2878+ } catch ( e ) {
2879+ console . error ( "LLM Analysis Error:" , e ) ;
2880+ }
2881+ return null ;
2882+ }
2883+
2884+ export const offlineApi = {
2885+ // ... (keep getMe, login, register, getProfile, updateProfile)
2886+ async sendMessage ( { conversation_id, message, user_id, request_more, profile } ) {
28752887 const convId = conversation_id || `conv_${ Date . now ( ) } _${ Math . floor ( Math . random ( ) * 1000 ) } ` ;
28762888 ensureConversation ( convId , user_id || null , message ) ;
28772889 const messages = getMessages ( convId ) ;
28782890 messages . push ( { id : messages . length + 1 , sender : 'user' , message, created_at : new Date ( ) . toISOString ( ) } ) ;
28792891 saveMessages ( convId , messages ) ;
28802892
28812893 const state = getState ( convId ) ;
2882- if ( profile ) {
2883- state . userProfile = profile ; // Store profile in state
2884- }
2894+ if ( profile ) state . userProfile = profile ;
2895+
28852896 if ( message ) {
28862897 state . answers . push ( message ) ;
2887- const tone = detectAnswerTone ( message ) ;
2888- if ( tone > 0 && state . lastQuestionTags ?. length ) {
2889- for ( const tag of state . lastQuestionTags ) addTagScore ( state . tags , tag , 2 ) ;
2898+
2899+ // Smart Analysis
2900+ const lastQ = state . lastQuestionText || "" ;
2901+ const llmResult = await analyzeResponseWithLLM ( lastQ , message , state . userProfile ) ;
2902+
2903+ if ( llmResult ) {
2904+ console . log ( "LLM Smart Result:" , llmResult ) ;
2905+ if ( llmResult . sentiment === 'positive' ) {
2906+ if ( state . lastQuestionTags ?. length ) {
2907+ for ( const tag of state . lastQuestionTags ) addTagScore ( state . tags , tag , 3 * ( llmResult . confidence || 1 ) ) ;
2908+ }
2909+ if ( llmResult . implied_topics ) {
2910+ for ( const topic of llmResult . implied_topics ) {
2911+ const matchedTag = Object . keys ( TAG_KEYWORDS ) . find ( t =>
2912+ normalizeText ( topic ) . includes ( t ) || t . includes ( normalizeText ( topic ) )
2913+ ) ;
2914+ if ( matchedTag ) addTagScore ( state . tags , matchedTag , 2 ) ;
2915+ }
2916+ }
2917+ } else if ( llmResult . sentiment === 'negative' ) {
2918+ if ( state . lastQuestionTags ?. length ) {
2919+ for ( const tag of state . lastQuestionTags ) addTagScore ( state . tags , tag , - 2 ) ;
2920+ }
2921+ }
2922+ } else {
2923+ // Fallback to legacy regex
2924+ const tone = detectAnswerTone ( message ) ;
2925+ if ( tone > 0 && state . lastQuestionTags ?. length ) {
2926+ for ( const tag of state . lastQuestionTags ) addTagScore ( state . tags , tag , 2 ) ;
2927+ }
2928+ const derived = deriveTags ( message ) ;
2929+ Object . keys ( derived ) . forEach ( ( tag ) => addTagScore ( state . tags , tag , derived [ tag ] ) ) ;
28902930 }
2891- const derived = deriveTags ( message ) ;
2892- Object . keys ( derived ) . forEach ( ( tag ) => addTagScore ( state . tags , tag , derived [ tag ] ) ) ;
28932931
2932+ // Update focus tags if detected (legacy but useful)
28942933 const subjectTag = detectSubjectTag ( message ) ;
28952934 const groupTag = detectGroupTag ( message ) ;
28962935 if ( subjectTag ) {
0 commit comments