Chào các bạn! Mình là Minh, một frontend developer với 5 năm kinh nghiệm. Hôm nay mình sẽ chia sẻ cách mình đã tích hợp AI vào ứng dụng Vue3 với hiệu ứng "đánh chữ" cực kỳ mượt mà. Bài viết này dành cho người mới hoàn toàn, không cần biết nhiều về API đâu nhé!
🤔 Vấn đề thực tế
Khi mình xây dựng chatbot cho website công ty, yêu cầu là phải hiển thị câu trả lời theo kiểu "gõ từng chữ" như ChatGPT. Nghe có vẻ đơn giản nhưng khi bắt tay vào làm, mình đã gặp rất nhiều bối rối:
- API AI là gì? Làm sao gọi được?
- SSE (Server-Sent Events) hoạt động thế nào?
- Sao code chạy trên máy mình mà không chạy trên production?
- Tại sao token chết liền sau vài lần thử?
Đừng lo, sau bài hướng dẫn này, bạn sẽ tự tin tích hợp AI vào bất kỳ dự án Vue3 nào!
📚 Kiến thức nền tảng
SSE (Server-Sent Events) là gì?
Đơn giản nhất: SSE là cách server gửi dữ liệu đến trình duyệt theo dòng chảy liên tục. Thay vì đợi server trả về toàn bộ câu trả lời (có thể mất 10-30 giây), SSE cho phép hiển thị từng phần ngay khi có dữ liệu.
Gợi ý ảnh: Sơ đồ so sánh Request-Response thông thường vs SSE streaming
Tại sao chọn HolySheep AI?
Mình đã thử nhiều nhà cung cấp API AI, và HolySheep AI nổi bật với:
- Tỷ giá siêu rẻ: ¥1 = $1 (rẻ hơn 85% so với các provider phương Tây)
- Tốc độ cực nhanh: Latency chỉ dưới 50ms
- Thanh toán dễ dàng: Hỗ trợ WeChat và Alipay cho người dùng Việt Nam
- Dùng thử miễn phí: Nhận tín dụng miễn phí khi đăng ký tài khoản
- API tương thích: Dùng OpenAI-compatible endpoint, code cũ几乎不用 đổi
👉 Đăng ký tại đây để nhận credits miễn phí!
🚀 Bắt đầu thực hành
Bước 1: Chuẩn bị dự án Vue3
Giả sử bạn đã có project Vue3. Nếu chưa có, tạo nhanh bằng lệnh:
npm create vue@latest my-ai-chat
cd my-ai-chat
npm install
npm install axios
Gợi ý ảnh: Terminal hiển thị quá trình tạo project Vue3
Bước 2: Lấy API Key từ HolySheep
Sau khi đăng ký tài khoản tại HolySheep AI:
- Đăng nhập vào dashboard
- Vào mục "API Keys"
- Nhấn "Create New Key"
- Copy key (bắt đầu bằng
hs-)
Gợi ý ảnh: Screenshot vị trí API Keys trong dashboard HolySheep
⚠️ Lưu ý quan trọng: API Key giống như mật khẩu, không chia sẻ công khai! Nếu lỡ để lộ, hãy xóa key cũ và tạo key mới ngay.
Bước 3: Tạo composable xử lý AI
Mình sẽ tạo một composable để tái sử dụng logic AI ở nhiều nơi:
// src/composables/useAIChat.js
import { ref } from 'vue'
import axios from 'axios'
export function useAIChat() {
const messages = ref([])
const isLoading = ref(false)
const error = ref(null)
// Cấu hình API - QUAN TRỌNG: Sử dụng HolySheep endpoint
const API_URL = 'https://api.holysheep.ai/v1/chat/completions'
const API_KEY = 'YOUR_HOLYSHEEP_API_KEY' // Thay bằng key thật của bạn
const sendMessage = async (userMessage) => {
// Thêm tin nhắn người dùng vào danh sách
messages.value.push({
role: 'user',
content: userMessage
})
isLoading.value = true
error.value = null
try {
const response = await axios.post(
API_URL,
{
model: 'gpt-4o-mini', // Hoặc model bạn chọn
messages: messages.value,
stream: true // Bật streaming
},
{
headers: {
'Authorization': Bearer ${API_KEY},
'Content-Type': 'application/json'
},
responseType: 'stream' // Quan trọng cho SSE
}
)
// Xử lý stream response
const reader = response.data.getReader()
const decoder = new TextDecoder()
let assistantMessage = ''
// Thêm placeholder cho tin nhắn assistant
messages.value.push({
role: 'assistant',
content: ''
})
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
if (data === '[DONE]') continue
try {
const parsed = JSON.parse(data)
const content = parsed.choices?.[0]?.delta?.content || ''
if (content) {
assistantMessage += content
// Cập nhật tin nhắn assistant theo thời gian thực
messages.value[messages.value.length - 1].content = assistantMessage
}
} catch (e) {
// Bỏ qua parse error
}
}
}
}
} catch (err) {
error.value = 'Có lỗi xảy ra: ' + err.message
console.error('AI API Error:', err)
} finally {
isLoading.value = false
}
}
return {
messages,
isLoading,
error,
sendMessage
}
}
Bước 4: Tạo component Chat
Tiếp theo, tạo component Vue3 để hiển thị giao diện chat:
<template>
<div class="chat-container">
<!-- Header -->
<div class="chat-header">
<h2>💬 AI Chat Demo</h2>
<p class="powered-by">Powered by HolySheep AI</p>
</div>
<!-- Tin nhắn -->
<div class="messages-area" ref="messagesContainer">
<div
v-for="(msg, index) in messages"
:key="index"
:class="['message', msg.role]"
>
<div class="message-avatar">
{{ msg.role === 'user' ? '👤' : '🤖' }}
</div>
<div class="message-content">
{{ msg.content }}
<span v-if="msg.role === 'assistant' && isLoading && index === messages.length - 1" class="typing-indicator">
...
</span>
</div>
</div>
<div v-if="error" class="error-message">
⚠️ {{ error }}
</div>
</div>
<!-- Input -->
<div class="input-area">
<input
v-model="inputMessage"
@keyup.enter="handleSend"
placeholder="Nhập câu hỏi của bạn..."
:disabled="isLoading"
/>
<button @click="handleSend" :disabled="isLoading || !inputMessage.trim()">
{{ isLoading ? 'Đang trả lời...' : 'Gửi' }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue'
import { useAIChat } from '../composables/useAIChat'
const { messages, isLoading, error, sendMessage } = useAIChat()
const inputMessage = ref('')
const messagesContainer = ref(null)
// Tự động cuộn xuống khi có tin nhắn mới
watch(messages, () => {
nextTick(() => {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
}
})
}, { deep: true })
const handleSend = async () => {
if (!inputMessage.value.trim() || isLoading.value) return
const text = inputMessage.value
inputMessage.value = ''
await sendMessage(text)
}
</script>
<style scoped>
.chat-container {
max-width: 600px;
margin: 0 auto;
border: 1px solid #e0e0e0;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.chat-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 16px;
text-align: center;
}
.powered-by {
font-size: 12px;
opacity: 0.8;
margin-top: 4px;
}
.messages-area {
height: 400px;
overflow-y: auto;
padding: 16px;
background: #f5f5f5;
}
.message {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.message.user {
flex-direction: row-reverse;
}
.message-content {
max-width: 75%;
padding: 12px 16px;
border-radius: 12px;
line-height: 1.5;
}
.message.user .message-content {
background: #667eea;
color: white;
border-bottom-right-radius: 4px;
}
.message.assistant .message-content {
background: white;
color: #333;
border-bottom-left-radius: 4px;
}
.input-area {
display: flex;
padding: 16px;
background: white;
border-top: 1px solid #e0e0e0;
}
.input-area input {
flex: 1;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 24px;
outline: none;
font-size: 14px;
}
.input-area input:focus {
border-color: #667eea;
}
.input-area button {
margin-left: 12px;
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 24px;
cursor: pointer;
font-weight: 600;
}
.input-area button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
Bước 5: Tích hợp vào App.vue
<template>
<main>
<h1>Vue3 + AI Chat Demo</h1>
<ChatComponent />
</main>
</template>
<script setup>
import ChatComponent from './components/ChatComponent.vue'
</script>
<style>
body {
margin: 0;
font-family: 'Segoe UI', sans-serif;
background: #fafafa;
}
main {
padding: 40px 20px;
max-width: 800px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
</style>
✨ Thêm hiệu ứng máy đánh chữ
Để có trải nghiệm "đánh chữ" mượt mà hơn, mình sẽ cải thiện composable với animation:
// src/composables/useAIChatAdvanced.js
import { ref } from 'vue'
import axios from 'axios'
export function useAIChatAdvanced() {
const messages = ref([])
const isLoading = ref(false)
const currentContent = ref('') // Nội dung đang "gõ"
const error = ref(null)
const API_URL = 'https://api.holysheep.ai/v1/chat/completions'
const API_KEY = 'YOUR_HOLYSHEEP_API_KEY'
// Cấu hình tốc độ gõ (ms cho mỗi ký tự)
const TYPING_SPEED = 15 // 15ms = khá nhanh và mượt
const typeWriter = (text, callback) => {
let i = 0
const type = () => {
if (i < text.length) {
currentContent.value += text.charAt(i)
i++
callback(currentContent.value)
setTimeout(type, TYPING_SPEED)
}
}
type()
}
const sendMessage = async (userMessage) => {
messages.value.push({
role: 'user',
content: userMessage
})
isLoading.value = true
error.value = null
currentContent.value = ''
// Placeholder cho assistant
const assistantIndex = messages.value.length
messages.value.push({
role: 'assistant',
content: ''
})
try {
const response = await axios.post(
API_URL,
{
model: 'gpt-4o-mini',
messages: messages.value.slice(0, -1), // Không gửi placeholder
stream: true
},
{
headers: {
'Authorization': Bearer ${API_KEY},
'Content-Type': 'application/json'
},
responseType: 'stream'
}
)
const reader = response.data.getReader()
const decoder = new TextDecoder()
let fullContent = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6)
if (data === '[DONE]') continue
try {
const parsed = JSON.parse(data)
const content = parsed.choices?.[0]?.delta?.content || ''
if (content) {
fullContent += content
// Cập nhật nội dung thực
messages.value[assistantIndex].content = fullContent
}
} catch (e) {}
}
}
}
} catch (err) {
error.value = 'Lỗi kết nối API: ' + err.message
messages.value.splice(assistantIndex, 1) // Xóa placeholder
} finally {
isLoading.value = false
}
}
const clearChat = () => {
messages.value = []
currentContent.value = ''
error.value = null
}
return {
messages,
isLoading,
currentContent,
error,
sendMessage,
clearChat
}
}
📊 Bảng giá HolySheep AI 2026
Một trong những lý do mình chọn HolySheep là giá cả cực kỳ cạnh tranh:
| Model | Giá / 1M Tokens | So sánh |
|---|---|---|
| GPT-4.1 | $8 | Tiết kiệm 85%+ |
| Claude Sonnet 4.5 | $15 | Rẻ hơn nhiều |
| Gemini 2.5 Flash | $2.50 | Giá tốt nhất |
| DeepSeek V3.2 | $0.42 | Cực kỳ rẻ! |
Với tỷ giá ¥1 = $1, chi phí thực tế còn thấp hơn nữa cho người dùng Trung Quốc.
🔧 Xử lý CORS
Nếu bạn gặp lỗi CORS khi gọi API trực tiếp từ browser, có 2 cách giải quyết:
Cách 1: Proxy qua Vite (Khuyến nghị)
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'