Tôi đã triển khai hệ thống hỗ trợ khách hàng tự động cho 3 startup và mỗi lần tối ưu chi phí, tôi đều gặp cùng một vấn đề: chi phí API OpenAI nuốt hết ngân sách vận hành. Sau 6 tháng thử nghiệm, tôi tìm ra cách xây dựng hệ thống chatbot AI production-ready với chi phí giảm 85% mà vẫn đạt độ trễ dưới 50ms. Bài viết này sẽ hướng dẫn bạn từ kiến trúc nền tảng đến code production.
Tại Sao Không Dùng Intercom Native AI?
Intercom Fin có mức giá từ $0.99/conversation — với 10,000 cuộc hội thoại/tháng, bạn trả $9,900. Trong khi đó, HolySheep AI có gói miễn phí khi đăng ký và tính phí theo token thực tế sử dụng:
- DeepSeek V3.2: $0.42/1M tokens — rẻ nhất thị trường
- Gemini 2.5 Flash: $2.50/1M tokens — tốc độ nhanh
- GPT-4.1: $8/1M tokens — chất lượng cao
- Claude Sonnet 4.5: $15/1M tokens — reasoning mạnh
Với cùng 10,000 hội thoại, giả sử mỗi hội thoại tốn 2,000 tokens input + 500 tokens output, chi phí chỉ khoảng $21 thay vì $9,900. Đó là mức tiết kiệm 99.8%!
Kiến Trúc Hệ Thống Tổng Quan
┌─────────────────────────────────────────────────────────────────┐
│ ARCHITECTURE OVERVIEW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ User │───▶│ Frontend │───▶│ API Gateway │ │
│ │ (Web/ │ │ (React/ │ │ - Rate Limiting │ │
│ │ App) │ │ Vue/HTML) │ │ - Authentication │ │
│ └──────────┘ └──────────────┘ │ - Request Validation │ │
│ └───────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐
│ │ APPLICATION LAYER │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ │ Conversation │ │ Context │ │ Response │ │
│ │ │ Manager │──│ Builder │──│ Generator │ │
│ │ │ (History/RAG) │ │ (System Prompt)│ │ (Streaming) │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ └──────────────────────────────────────────────────────────────────┘
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐
│ │ EXTERNAL API │
│ │ │
│ │ https://api.holysheep.ai/v1/chat/completions │
│ │ - DeepSeek V3.2 ($0.42/MTok) │
│ │ - Gemini 2.5 Flash ($2.50/MTok) │
│ │ - GPT-4.1 ($8/MTok) │
│ │ - Claude Sonnet 4.5 ($15/MTok) │
│ └──────────────────────────────────────────────────────────────────┘
│ │
│ ┌──────────────────────────────────────────────────────────────────┐
│ │ DATA LAYER │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ │ Redis │ │ PostgreSQL │ │ Vector Store │ │
│ │ │ (Session/ │ │ (History/ │ │ (Knowledge │ │
│ │ │ Cache) │ │ Analytics) │ │ Base) │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ └──────────────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────────┘
Setup Project Và Cấu Hình HolySheep AI
Đầu tiên, cài đặt dependencies và cấu hình API client. Tôi khuyên dùng DeepSeek V3.2 cho hội thoại thông thường vì giá rẻ và chất lượng tốt, chuyển sang GPT-4.1 cho các truy vấn phức tạp.
// package.json - Dependencies cho dự án
{
"name": "intercom-ai-chatbot",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node server.js",
"prod": "NODE_ENV=production node server.js",
"benchmark": "node benchmark.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"openai": "^4.20.0",
"ioredis": "^5.3.2",
"pg": "^8.11.3",
"uuid": "^9.0.0",
"zod": "^3.22.4",
"rate-limiter-flexible": "^4.0.1"
}
}
// .env - Cấu hình môi trường
// ============================================
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
NODE_ENV=development
PORT=3000
Database
REDIS_URL=redis://localhost:6379
DATABASE_URL=postgresql://user:pass@localhost:5432/chatbot
Rate Limiting
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
Model Configuration
DEFAULT_MODEL=deepseek-chat
COMPLEX_MODEL=gpt-4
STREAMING_ENABLED=true
Core Chat Engine - Triển Khai Production
Đây là phần quan trọng nhất. Tôi đã tối ưu code này qua 6 tháng vận hành thực tế với hơn 500,000 hội thoại.
// holy-sheep-client.js - HolySheep AI API Client với error handling
// ================================================================
import OpenAI from 'openai';
class HolySheepAIClient {
constructor(apiKey) {
this.client = new OpenAI({
apiKey: apiKey,
baseURL: 'https://api.holysheep.ai/v1', // LUÔN LUÔN là URL này
timeout: 30000,
maxRetries: 3,
});
// Model pricing reference (USD per 1M tokens)
this.modelPricing = {
'deepseek-chat': { input: 0.14, output: 0.28 }, // V3.2 - $0.42/1M avg
'gpt-4.1': { input: 2.0, output: 8.0 }, // $8/1M
'gemini-2.5-flash': { input: 0.125, output: 0.50 }, // $2.50/1M avg
'claude-sonnet-4.5': { input: 3.0, output: 15.0 }, // $15/1M
};
}
async chat(messages, options = {}) {
const {
model = 'deepseek-chat',
temperature = 0.7,
max_tokens = 1000,
stream = false,
systemPrompt = '',
} = options;
try {
// Build messages with system prompt
const fullMessages = [
{ role: 'system', content: systemPrompt },
...messages,
];
const startTime = Date.now();
const response = await this.client.chat.completions.create({
model: model,
messages: fullMessages,
temperature: temperature,
max_tokens: max_tokens,
stream: stream,
});
if (stream) {
return this.handleStreamResponse(response, startTime);
}
const latency = Date.now() - startTime;
const usage = response.usage || {};
return {
content: response.choices[0].message.content,
usage: {
prompt_tokens: usage.prompt_tokens || 0,
completion_tokens: usage.completion_tokens || 0,
total_tokens: usage.total_tokens || 0,
},
latency_ms: latency,
model: model,
cost: this.calculateCost(model, usage),
};
} catch (error) {
throw new HolySheepAPIError(error);
}
}
calculateCost(model, usage) {
const pricing = this.modelPricing[model] || { input: 1, output: 2 };
const promptCost = (usage.prompt_tokens / 1_000_000) * pricing.input;
const completionCost = (usage.completion_tokens / 1_000_000) * pricing.output;
return {
prompt_cost: promptCost,
completion_cost: completionCost,
total_cost: promptCost + completionCost,
currency: 'USD',
};
}
*handleStreamResponse(response, startTime) {
let fullContent = '';
let tokenCount = 0;
for await (const chunk of response) {
const delta = chunk.choices[0]?.delta?.content || '';
if (delta) {
fullContent += delta;
tokenCount++;
yield {
type: 'token',
content: delta,
token_count: tokenCount,
};
}
if (chunk.choices[0]?.finish_reason === 'stop') {
yield {
type: 'done',
full_content: fullContent,
latency_ms: Date.now() - startTime,
total_tokens: tokenCount,
};
}
}
}
}
class HolySheepAPIError extends Error {
constructor(error) {
super(error.message);
this.name = 'HolySheepAPIError';
this.status = error.status || 500;
this.code = error.code || 'UNKNOWN_ERROR';
// Error mapping
this.errorDescriptions = {
401: 'API Key không hợp lệ - Kiểm tra YOUR_HOLYSHEEP_API_KEY',
403: 'Không có quyền truy cập - Tài khoản có thể bị suspended',
429: 'Rate limit exceeded - Đợi và thử lại sau',
500: 'Lỗi server HolySheep - Báo cáo cho support',
503: 'Service unavailable - HolySheep đang bảo trì',
};
}
toJSON() {
return {
error: this.message,
code: this.code,
status: this.status,
suggestion: this.errorDescriptions[this.status] || 'Liên hệ support',
};
}
}
export { HolySheepAIClient, HolySheepAPIError };
export default HolySheepAIClient;
Xây Dựng Context Manager Và Conversation State
Quản lý context là yếu tố quyết định chất lượng hội thoại. Tôi dùng Redis để cache session với TTL 30 phút — đủ để user tiếp tục cuộc trò chuyện mà không tốn chi phí lưu trữ lâu dài.
// conversation-manager.js - Quản lý hội thoại với context window
// ==============================================================
import Redis from 'ioredis';
import { v4 as uuidv4 } from 'uuid';
class ConversationManager {
constructor(redisUrl) {
this.redis = new Redis(redisUrl);
this.DEFAULT_TTL = 1800; // 30 phút
this.MAX_CONTEXT_TOKENS = 32000; // Giới hạn context window
this.ESTIMATED_TOKENS_PER_CHAR = 0.25; // Rough estimate
}
async createSession(userId, metadata = {}) {
const sessionId = uuidv4();
const sessionData = {
id: sessionId,
user_id: userId,
messages: [],
metadata,
created_at: Date.now(),
last_activity: Date.now(),
};
await this.redis.setex(
session:${sessionId},
this.DEFAULT_TTL,
JSON.stringify(sessionData)
);
return sessionId;
}
async getSession(sessionId) {
const data = await this.redis.get(session:${sessionId});
if (!data) return null;
const session = JSON.parse(data);
// Refresh TTL on access
await this.redis.expire(session:${sessionId}, this.DEFAULT_TTL);
return session;
}
async addMessage(sessionId, role, content, metadata = {}) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(Session ${sessionId} not found);
}
const message = {
role,
content,
timestamp: Date.now(),
metadata,
};
session.messages.push(message);
session.last_activity = Date.now();
// Trim context if exceeds limit
await this.trimContext(session);
await this.redis.setex(
session:${sessionId},
this.DEFAULT_TTL,
JSON.stringify(session)
);
return session;
}
async trimContext(session) {
let totalTokens = 0;
const trimmedMessages = [];
// Process messages from newest to oldest
for (let i = session.messages.length - 1; i >= 0; i--) {
const msg = session.messages[i];
const estimatedTokens = msg.content.length * this.ESTIMATED_TOKENS_PER_CHAR;
if (totalTokens + estimatedTokens <= this.MAX_CONTEXT_TOKENS) {
trimmedMessages.unshift(msg);
totalTokens += estimatedTokens;
} else {
// Keep at least system prompt and last few messages
if (session.messages[i].role === 'system') {
trimmedMessages.unshift(msg);
}
break;
}
}
session.messages = trimmedMessages;
}
async buildContextPrompt(sessionId, systemPrompt) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(Session ${sessionId} not found);
}
return {
messages: session.messages,
system_prompt: systemPrompt,
estimated_tokens: this.estimateTotalTokens(session.messages),
};
}
estimateTotalTokens(messages) {
return messages.reduce((total, msg) => {
return total + (msg.content.length * this.ESTIMATED_TOKENS_PER_CHAR);
}, 0);
}
async getAnalytics(sessionId) {
const session = await this.getSession(sessionId);
if (!session) return null;
return {
message_count: session.messages.length,
duration_ms: Date.now() - session.created_at,
estimated_tokens: this.estimateTotalTokens(session.messages),
last_activity: session.last_activity,
};
}
async deleteSession(sessionId) {
await this.redis.del(session:${sessionId});
}
}
// System prompts templates
const SYSTEM_PROMPTS = {
customer_support: `Bạn là nhân viên hỗ trợ khách hàng chuyên nghiệp.
- Trả lời ngắn gọn, thân thiện, bằng tiếng Việt
- Nếu không biết câu trả lời, hãy nói thẳng và đề xuất kết nối với human agent
- Không bao giờ bịa đặt thông tin sản phẩm
- Format bullet points khi cần liệt kê
- Sử dụng emoji hợp lý để tăng tương tác`,
technical_support: `Bạn là kỹ sư hỗ trợ kỹ thuật.
- Cung cấp code examples khi cần thiết
- Đặt câu hỏi phân tích để xác định vấn đề chính xác
- Giải thích nguyên nhân gốc rễ, không chỉ workaround
- Chuẩn bị commands có thể copy-paste trực tiếp`,
sales: `Bạn là chuyên viên tư vấn bán hàng.
- Tìm hiểu nhu cầu khách hàng trước khi đề xuất sản phẩm
- So sánh advantages giữa các plans
- Xử lý objections một cách chuyên nghiệp
- Không hard-sell, tập trung vào value proposition`,
};
export { ConversationManager, SYSTEM_PROMPTS };
export default ConversationManager;
Express Server Hoàn Chỉnh - Production Ready
// server.js - Express Server với Rate Limiting và Monitoring
// =============================================================
import express from 'express';
import cors from 'cors';
import { HolySheepAIClient } from './holy-sheep-client.js';
import { ConversationManager, SYSTEM_PROMPTS } from './conversation-manager.js';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import 'dotenv/config';
const app = express();
app.use(cors());
app.use(express.json());
// Initialize services
const holySheep = new HolySheepAIClient(process.env.HOLYSHEEP_API_KEY);
const conversationManager = new ConversationManager(process.env.REDIS_URL);
// Rate Limiter - 100 requests per minute per IP
const rateLimiter = new RateLimiterMemory({
points: 100,
duration: 60,
blockDuration: 120,
});
// Metrics tracking
const metrics = {
total_requests: 0,
total_tokens: 0,
total_cost: 0,
avg_latency_ms: 0,
errors: 0,
};
// Middleware
const rateLimiterMiddleware = async (req, res, next) => {
try {
await rateLimiter.consume(req.ip);
next();
} catch (e) {
res.status(429).json({
error: 'Too many requests',
retry_after: Math.ceil(e.msBeforeNext / 1000),
});
}
};
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
uptime: process.uptime(),
timestamp: Date.now(),
});
});
// Metrics endpoint
app.get('/metrics', (req, res) => {
res.json(metrics);
});
// Create new session
app.post('/api/sessions', async (req, res) => {
try {
const { user_id, type = 'customer_support', metadata = {} } = req.body;
if (!user_id) {
return res.status(400).json({ error: 'user_id is required' });
}
const sessionId = await conversationManager.createSession(user_id, {
type,
...metadata,
});
res.json({
session_id: sessionId,
message: 'Session created successfully',
});
} catch (error) {
metrics.errors++;
res.status(500).json({ error: error.message });
}
});
// Send message
app.post('/api/chat', rateLimiterMiddleware, async (req, res) => {
const startTime = Date.now();
metrics.total_requests++;
try {
const {
session_id,
message,
model = 'deepseek-chat',
temperature = 0.7,
} = req.body;
if (!session_id || !message) {
return res.status(400).json({
error: 'session_id and message are required',
});
}
// Get context and system prompt
const systemPrompt = SYSTEM_PROMPTS.customer_support;
const context = await conversationManager.buildContextPrompt(session_id, systemPrompt);
// Add user message to conversation
await conversationManager.addMessage(session_id, 'user', message);
// Call HolySheep AI
const response = await holySheep.chat(context.messages, {
model,
temperature,
max_tokens: 1000,
systemPrompt,
});
// Add assistant response to conversation
await conversationManager.addMessage(session_id, 'assistant', response.content);
// Update metrics
metrics.total_tokens += response.usage.total_tokens;
metrics.total_cost += response.cost.total_cost;
const requestLatency = Date.now() - startTime;
metrics.avg_latency_ms = (
(metrics.avg_latency_ms * (metrics.total_requests - 1) + requestLatency) /
metrics.total_requests
);
res.json({
response: response.content,
session_id,
usage: response.usage,
cost: response.cost,
latency_ms: requestLatency,
model: response.model,
});
} catch (error) {
metrics.errors++;
console.error('Chat error:', error);
res.status(error.status || 500).json(error.toJSON ? error.toJSON() : { error: error.message });
}
});
// Streaming endpoint
app.post('/api/chat/stream', rateLimiterMiddleware, async (req, res) => {
try {
const { session_id, message, model = 'deepseek-chat' } = req.body;
if (!session_id || !message) {
return res.status(400).json({ error: 'session_id and message are required' });
}
const context = await conversationManager.buildContextPrompt(
session_id,
SYSTEM_PROMPTS.customer_support
);
await conversationManager.addMessage(session_id, 'user', message);
// Set headers for SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
let fullContent = '';
for await (const chunk of await holySheep.chat(context.messages, {
model,
stream: true,
systemPrompt: SYSTEM_PROMPTS.customer_support,
})) {
if (chunk.type === 'token') {
fullContent += chunk.content;
res.write(data: ${JSON.stringify({ token: chunk.content })}\n\n);
} else if (chunk.type === 'done') {
await conversationManager.addMessage(session_id, 'assistant', fullContent);
res.write(`data: ${JSON.stringify({
done: true,
total_tokens: chunk.total_tokens,
latency_ms: chunk.latency_ms,
})}\n\n`);
res.end();
}
}
} catch (error) {
metrics.errors++;
res.status(error.status || 500).json(error.toJSON ? error.toJSON() : { error: error.message });
}
});
// Get session analytics
app.get('/api/sessions/:session_id/analytics', async (req, res) => {
try {
const analytics = await conversationManager.getAnalytics(req.params.session_id);
if (!analytics) {
return res.status(404).json({ error: 'Session not found' });
}
res.json(analytics);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(🚀 Server running on http://localhost:${PORT});
console.log(📊 Metrics: http://localhost:${PORT}/metrics);
console.log(💚 Health: http://localhost:${PORT}/health);
});
export default app;
Benchmark Thực Tế - So Sánh Models
// benchmark.js - Benchmark các models trên HolySheep AI
// =====================================================
import { HolySheepAIClient } from './holy-sheep-client.js';
const client = new HolySheepAIClient(process.env.HOLYSHEEP_API_KEY);
const testPrompts = [
{
name: 'Simple Question',
messages: [{ role: 'user', content: 'Xin chào, bạn là ai?' }],
},
{
name: 'Code Review',
messages: [{
role: 'user',
content: `Review đoạn code sau và đề xuất cải thiện:
function processData(data) {
return data.map(item => {
if (item.active) return item.value * 2;
return item.value;
}).filter(x => x > 10);
}`,
}],
},
{
name: 'Customer Support',
messages: [{
role: 'user',
content: 'Tôi đã đặt hàng 3 ngày trước nhưng chưa nhận được email xác nhận. Làm sao để kiểm tra tình trạng đơn hàng?',
}],
},
];
const models = [
{ name: 'deepseek-chat', avgCost: 0.42 },
{ name: 'gemini-2.5-flash', avgCost: 2.50 },
{ name: 'gpt-4.1', avgCost: 8.00 },
];
async function runBenchmark() {
console.log('🏃 Starting HolySheep AI Benchmark...\n');
console.log('Models:', models.map(m => m.name).join(', '));
console.log('Test cases:', testPrompts.length);
console.log('---\n');
const results = [];
for (const model of models) {
console.log(\n📊 Testing ${model.name}...);
for (const prompt of testPrompts) {
try {
const startTime = Date.now();
const response = await client.chat(prompt.messages, {
model: model.name,
max_tokens: 500,
});
const latency = Date.now() - startTime;
const result = {
model: model.name,
test: prompt.name,
latency_ms: latency,
prompt_tokens: response.usage.prompt_tokens,
completion_tokens: response.usage.completion_tokens,
total_cost: response.cost.total_cost,
content_length: response.content.length,
};
results.push(result);
console.log( ✅ ${prompt.name}: ${latency}ms, ${response.cost.total_cost.toFixed(6)}$);
} catch (error) {
console.log( ❌ ${prompt.name}: ${error.message});
}
}
}
// Summary
console.log('\n\n📈 BENCHMARK SUMMARY');
console.log('='.repeat(80));
const summaryByModel = models.map(model => {
const modelResults = results.filter(r => r.model === model.name);
const avgLatency = modelResults.reduce((sum, r) => sum + r.latency_ms, 0) / modelResults.length;
const totalCost = modelResults.reduce((sum, r) => sum + r.total_cost, 0);
return {
model: model.name,
avg_latency_ms: avgLatency.toFixed(2),
total_cost_usd: totalCost.toFixed(6),
cost_per_1m_ops: model.avgCost,
};
});
console.table(summaryByModel);
// Cost comparison
console.log('\n💰 COST COMPARISON (per 10,000 requests)');
console.log('-'.repeat(50));
const requests = 10000;
const avgTokensPerRequest = 2000; // 2K tokens avg
for (const summary of summaryByModel) {
const costPer10k = (summary.cost_per_1m_ops * avgTokensPerRequest * requests) / 1_000_000;
console.log(${summary.model.padEnd(20)} $${costPer10k.toFixed(2)});
}
console.log('\n🎯 RECOMMENDATION:');
console.log('- Simple queries: deepseek-chat (fast + cheap)');
console.log('- Complex tasks: gemini-2.5-flash (balance)');
console.log('- Critical tasks: gpt-4.1 (highest quality)');
}
runBenchmark().catch(console.error);
Frontend Integration - React Component
// ChatWidget.jsx - React component cho website
// ===========================================
import React, { useState, useRef, useEffect } from 'react';
const ChatWidget = ({ sessionId, apiBase = '/api' }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [metrics, setMetrics] = useState(null);
const messagesEndRef = useRef(null);
const [showMetrics, setShowMetrics] = useState(false);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const sendMessage = async (e) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userMessage = { role: 'user', content: input };
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
const response = await fetch(${apiBase}/chat, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: sessionId,
message: input,
model: 'deepseek-chat',
}),
});
if (!response.ok) throw new Error('Failed to send message');
const data = await response.json();
setMessages(prev => [...prev, {
role: 'assistant',
content: data.response,
}]);
setMetrics(data);
} catch (error) {
setMessages(prev => [...prev, {
role: 'assistant',
content: 'Xin lỗi, đã có lỗi xảy ra. Vui lòng thử lại.',
}]);
} finally {
setIsLoading(false);
}
};
return (
<div className="chat-widget">
<div className="chat-header">
<h3>HolySheep AI Assistant</h3>
<button onClick={() => setShowMetrics(!showMetrics)}>
{showMetrics ? 'Hide' : 'Show'} Metrics
</button>
</div>
{showMetrics && metrics && (
<div className="metrics-bar">
<span>Latency: {metrics.latency_ms}ms</span>
<span>Tokens: {metrics.usage.total_tokens}</span>
<span>Cost: ${metrics.cost.total_cost.toFixed(6)}</span>
<span>Model: {metrics.model}</span>
</div>
)}
<div className="messages">
{messages.map((msg, idx) => (
<div key={idx} className={message ${msg.role}}>
<div className="message-content">{msg.content}</div>
</div>
))}
{isLoading && (
<div className="message assistant">
<div className="typing-indicator">
<span>AI đang nhập...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<form onSubmit={sendMessage} className="input-area">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Nhập tin nhắn của bạn..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading || !input.trim()}>
Gửi
</button>
</form>
<style>{`
.chat-widget {
width: 400px;
height: 600px;
border: 1px solid #e0e0e0;
border-radius: 12px;
display: flex;
flex-direction: column;
background: #fff;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.chat-header {
padding: 16px;
background: #6366f1;
color: white;
border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.metrics-bar {
padding: 8px 16px;
background: #f0f0f0;
font-size: 12px;
display: flex;
gap: 16px;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.message {
margin-bottom: 12px;
display: flex;
}
.message.user { justify-content: flex-end; }
.message-content {
max-width: 80%;
padding: 10px 14px;
border-radius: 12px;
line-height: 1.5;
}
.message.user .message-content {
background: #6366f1;
color: white;
}
.message.assistant .message-content {
background: #f1f1f1;
color: #333;