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:

Đừ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:

👉 Đă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:

  1. Đăng nhập vào dashboard
  2. Vào mục "API Keys"
  3. Nhấn "Create New Key"
  4. 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'