Front-end
WIN WIN POWER
Trợ lý AI sẵn sàng tư vấn 24/7
Back-end
/*
Đây là code cho Backend (Supabase Edge Function) đã được cập nhật để sử dụng Google Gemini API.
Tệp này sẽ được đặt tại: /supabase/functions/chat-bot/index.ts
Nó sẽ xử lý logic nghiệp vụ, gọi Gemini API và TRÍCH XUẤT THÔNG TIN LEAD.
*/
import { serve } from ‘https://deno.land/std@0.177.0/http/server.ts’;
// Lấy API key của Gemini từ biến môi trường (an toàn)
const GEMINI_API_KEY = Deno.env.get(‘GEMINI_API_KEY’);
const GEMINI_API_URL = ‘https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent’;
// Hàm helper để chuyển đổi định dạng tin nhắn sang định dạng của Gemini API
function convertMessagesToGeminiFormat(messages: any[]) {
const systemInstruction = messages.find(m => m.role === ‘system’)?.content || ”;
const contents = messages
.filter(m => m.role !== ‘system’)
.map(m => {
const role = m.role === ‘assistant’ ? ‘model’ : m.role;
return {
role: role,
parts: [{ text: m.content }]
};
});
return { systemInstruction, contents };
}
/**
* Hàm trích xuất thông tin Tên, SĐT, Địa chỉ từ tin nhắn của khách hàng
* Dùng regex đơn giản để tìm kiếm các thông tin phổ biến.
*/
function extractLeadInfo(text: string): { customer_name: string | null, phone_number: string | null, address: string | null } {
// 1. Tìm Số điện thoại (chỉ tìm chuỗi số từ 8-12 chữ số)
const phoneMatch = text.match(/((0|\+84)\s?(\d{2,4})\s?\d{3,4}\s?\d{3,4})/);
let phoneNumber = phoneMatch ? phoneMatch[0].trim().replace(/\s/g, ”) : null;
// Loại bỏ SĐT ra khỏi văn bản để dễ tìm Tên và Địa chỉ hơn
let remainingText = text;
if (phoneNumber) {
remainingText = remainingText.replace(phoneMatch[0], ‘ ‘);
}
// 2. Tạm thời coi phần còn lại là Tên và Địa chỉ.
// Việc tách chính xác Tên và Địa chỉ bằng code rất phức tạp.
// Tốt nhất là lưu toàn bộ tin nhắn (messages_json) và sử dụng bot để trích xuất sau,
// nhưng ta vẫn cố gắng trích xuất cơ bản.
// Coi phần còn lại là Tên và Địa chỉ
const parts = remainingText.trim().split(/[,;]/).map(p => p.trim()).filter(p => p.length > 0);
let name = null;
let address = null;
if (parts.length >= 2) {
// Giả định phần đầu là tên, phần cuối là địa chỉ
name = parts[0];
address = parts.slice(1).join(‘, ‘).trim();
} else if (parts.length === 1) {
// Nếu chỉ còn một phần (ví dụ chỉ có tên)
name = parts[0];
}
return {
customer_name: name || null,
phone_number: phoneNumber,
address: address || null
};
}
serve(async (req) => {
// Xử lý CORS
if (req.method === ‘OPTIONS’) {
return new Response(‘ok’, {
headers: {
‘Access-Control-Allow-Origin’: ‘*’,
‘Access-Control-Allow-Methods’: ‘POST, OPTIONS’,
‘Access-Control-Allow-Headers’: ‘authorization, x-client-info, apikey, content-type’,
},
});
}
try {
if (!GEMINI_API_KEY) {
throw new Error(‘Missing GEMINI_API_KEY environment variable. Please set it via Supabase CLI.’);
}
// Lấy lịch sử tin nhắn từ client
const { messages } = await req.json();
if (!messages) {
throw new Error(‘Missing “messages” in request body’);
}
// Lấy tin nhắn cuối cùng của người dùng
const userMessage = messages[messages.length – 1].content;
const userMessageLower = userMessage.toLowerCase();
let responseContent = ”;
let skipAI = false;
let leadData = null; // Dữ liệu Lead sẽ được gửi về Frontend
// — LOGIC NGHIỆP VỤ (Thu thập thông tin khách hàng) —
const priceKeywords = [‘giá’, ‘báo giá’, ‘chi phí’, ‘giá cả’, ‘bao nhiêu tiền’];
const isAskingForPrice = priceKeywords.some(keyword => userMessageLower.includes(keyword));
// Kiểm tra xem tin nhắn trước đó của bot có phải là yêu cầu thông tin không
const lastBotMessage = messages[messages.length – 2]?.content || ”;
const wasAskingForInfo = lastBotMessage.includes(‘Tên, Số điện thoại và Địa chỉ’);
if (isAskingForPrice && !wasAskingForInfo) {
// 1. Khách hàng hỏi giá lần đầu
responseContent = ‘Dạ, để cung cấp báo giá chính xác, em cần thêm một số thông tin ạ. Anh/Chị vui lòng cho em xin Tên, Số điện thoại và Địa chỉ lắp đặt dự kiến nhé!’;
skipAI = true;
} else if (wasAskingForInfo && userMessage.trim().length > 5) { // Đã yêu cầu và người dùng đã nhập thông tin
// 2. Khách hàng vừa cung cấp thông tin
// TRÍCH XUẤT THÔNG TIN VÀ CHUẨN BỊ GỬI VỀ FRONTEND
leadData = extractLeadInfo(userMessage);
responseContent = ‘Cảm ơn Anh/Chị! Bộ phận kinh doanh của WIN WIN POWER sẽ liên hệ tư vấn và báo giá cho mình sớm nhất ạ. Anh/Chị còn cần em hỗ trợ thêm thông tin gì không ạ?’;
skipAI = true;
}
// — KẾT THÚC LOGIC NGHIỆP VỤ —
if (!skipAI) {
// 3. Các trường hợp còn lại, gọi Gemini API
const { systemInstruction, contents } = convertMessagesToGeminiFormat(messages);
const payload = {
contents: contents,
systemInstruction: {
parts: [{ text: systemInstruction }]
},
generationConfig: {
temperature: 0.5,
maxOutputTokens: 300,
}
};
const geminiResponse = await fetch(`${GEMINI_API_URL}?key=${GEMINI_API_KEY}`, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(payload)
});
if (!geminiResponse.ok) {
const errorText = await geminiResponse.text();
throw new Error(`Gemini API call failed: ${geminiResponse.status} – ${errorText}`);
}
const result = await geminiResponse.json();
// Lấy nội dung từ phản hồi của Gemini
responseContent = result?.candidates?.[0]?.content?.parts?.[0]?.text || ‘Xin lỗi, tôi không thể tạo phản hồi vào lúc này.’;
}
// Trả phản hồi về cho client, bao gồm cả leadData (nếu có)
return new Response(JSON.stringify({ reply: responseContent, leadData: leadData }), {
headers: {
‘Access-Control-Allow-Origin’: ‘*’,
‘Content-Type’: ‘application/json’,
},
});
} catch (error) {
console.error(‘Error handling request:’, error.message);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
‘Access-Control-Allow-Origin’: ‘*’,
‘Content-Type’: ‘application/json’,
},
});
}
});
Hướng dẫn
Chào bạn, để chatbot hoạt động, bạn cần làm theo 3 phần chính sau. Hãy kiên nhẫn một chút ở phần 1 và 2, bạn chỉ làm một lần duy nhất.
PHẦN 1: CÀI ĐẶT CƠ SỞ DỮ LIỆU (SUPABASE)
Đây là nơi lưu trữ các cuộc hội thoại.
- Tạo tài khoản Supabase:
- Truy cập supabase.com và đăng ký một tài khoản miễn phí.
- Tạo Project mới:
- Sau khi đăng nhập, nhấn “New Project”.
- Đặt tên cho project (ví dụ: “win-win-power-bot”), tạo mật khẩu Database.
- Chọn “Region” là “Southeast Asia” (Singapore) cho tốc độ nhanh nhất.
- Chờ vài phút để project được khởi tạo.
- Lấy API Keys:
- Trong project dashboard, vào Settings (biểu tượng bánh răng) -> API.
- Tìm mục “Project API Keys”.
- Copy 2 giá trị:
Project URL(Đây làSUPABASE_URL)anonpublickey (Đây làSUPABASE_ANON_KEY)
- Lưu lại 2 giá trị này để dán vào file
chatbot-wordpress.html.
- Tạo Bảng Lưu trữ:
- Quay lại trang chủ project, chọn SQL Editor (biểu tượng
</>). - Nhấn “New query”.
- Dán đoạn mã SQL dưới đây vào và nhấn RUN:
-- 1. Tạo bảng để lưu hội thoại và thông tin Lead CREATE TABLE public.conversations ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), conversation_id TEXT NOT NULL UNIQUE, messages_json JSONB, -- Lưu toàn bộ tin nhắn (user và bot) -- Cột mới để lưu thông tin khách hàng (Lead) được trích xuất customer_name TEXT NULL, phone_number TEXT NULL, address TEXT NULL ); -- 2. Kích hoạt Row Level Security (RLS) để bảo mật ALTER TABLE public.conversations ENABLE ROW LEVEL SECURITY; -- 3. Tạo Policy cho phép ai cũng có thể TẠO (INSERT) hội thoại -- Khách hàng của bạn sẽ dùng quyền này CREATE POLICY "Allow public insert" ON public.conversations FOR INSERT WITH CHECK (true); -- 4. Tạo Policy cho phép ai cũng có thể CẬP NHẬT (UPDATE) hội thoại -- Dùng để cập nhật tin nhắn mới và thông tin Lead (nếu có) CREATE POLICY "Allow public update" ON public.conversations FOR UPDATE USING (true); - Quay lại trang chủ project, chọn SQL Editor (biểu tượng
PHẦN 2: CÀI ĐẶT BACKEND AN TOÀN (SUPABASE EDGE FUNCTION)
Đây là phần gọi Gemini API an toàn mà không lộ API Key.
- Cài đặt Supabase CLI (Làm trên máy tính của bạn):
- Bạn cần cài đặt Supabase CLI để deploy function.
- Nếu dùng macOS:
brew install supabase/tap/supabase - Nếu dùng Windows:
scoop bucket add supabase; scoop install supabase - (Xem thêm tại: docs.supabase.com/guides/cli)
- Liên kết Project:
- Mở Terminal (Command Prompt) trên máy tính.
- Tạo một thư mục mới:
mkdir win-win-bot && cd win-win-bot - Đăng nhập:
supabase login - Liên kết đến project của bạn:
supabase link --project-ref YOUR_PROJECT_REF- (Bạn có thể tìm thấy
YOUR_PROJECT_REFtrong Settings -> General)
- (Bạn có thể tìm thấy
- Tạo Edge Function:
- Chạy lệnh:
supabase functions new chat-bot - Thao tác này sẽ tạo một thư mục:
supabase/functions/chat-bot/index.ts.
- Chạy lệnh:
- Dán Code Backend:
- Mở tệp
supabase/functions/chat-bot/index.tsvừa tạo. - Xóa toàn bộ nội dung cũ của nó.
- Copy toàn bộ nội dung của tệp
backend-function.ts(ở dưới) và dán vào.
- Mở tệp
- Cài đặt Gemini API Key (Bảo mật):
- Lấy API key của bạn từ Google AI Studio hoặc Google Cloud Vertex AI.
- Trong Terminal, chạy lệnh sau (thay
AIzaSy...your-key-herebằng key của bạn):supabase secrets set GEMINI_API_KEY=AIzaSy...your-key-here - Lưu ý: Bạn cần có tài khoản API đã kích hoạt.
- Deploy Function:
- Chạy lệnh:
supabase functions deploy chat-bot - Sau khi deploy thành công, Supabase sẽ trả về cho bạn một URL. Đây chính là
SUPABASE_EDGE_FUNCTION_URL. - Lưu lại URL này để dán vào file
chatbot-wordpress.html.
- Chạy lệnh:
PHẦN 3: THÊM CHATBOT VÀO WORDPRESS
Đây là bước cuối cùng!
- Mở tệp
chatbot-wordpress.html:- Mở tệp
chatbot-wordpress.html(ở dưới) bằng một trình soạn thảo văn bản (như Notepad, VSCode…).
- Mở tệp
- Cập nhật Cấu hình:
- Tìm đến phần “CẤU HÌNH DÀNH CHO QUẢN TRỊ VIÊN”.
- Dán 3 giá trị bạn đã lưu từ Phần 1 và Phần 2 vào:
SUPABASE_URLSUPABASE_ANON_KEYSUPABASE_EDGE_FUNCTION_URL
- Bạn cũng có thể chỉnh sửa
SYSTEM_PROMPTtại đây để thay đổi cách chatbot trả lời.
- Dán Code vào WordPress:
- Copy toàn bộ nội dung của tệp
chatbot-wordpress.html(sau khi đã cập nhật cấu hình). - Đăng nhập vào trang quản trị WordPress của bạn.
- Cách dễ nhất (Khuyên dùng):
- Vào Plugins -> Add New.
- Tìm và cài đặt plugin tên là “Insert Headers and Footers” (của WPBeginner).
- Kích hoạt (Activate) plugin.
- Vào Settings -> Insert Headers and Footers.
- Dán toàn bộ code đã copy vào ô “Scripts in Footer”.
- Nhấn Save.
- Cách khác (Nếu bạn biết về theme):
- Vào Appearance -> Theme File Editor.
- Ở bên phải, tìm và mở tệp
Theme Footer (footer.php). - Dán toàn bộ code ngay trước thẻ
</body>. - Nhấn Update File.
- Copy toàn bộ nội dung của tệp
- Kiểm tra kết quả:
- Mở trang web của bạn ở chế độ ẩn danh (incognito) để xem.
- Bạn sẽ thấy biểu tượng chat ở góc dưới bên phải.
- Thử chat và kiểm tra xem bot có trả lời và thu thập thông tin khi hỏi về giá không.
- Bạn có thể vào Supabase -> Table Editor -> bảng
conversationsđể xem các tin nhắn được lưu lại.
Chúc mừng! Bạn đã có một chatbot AI chuyên nghiệp cho WIN WIN POWER.