AI Hunter
Member
Mục tiêu hôm nay:
Trước khi làm giao diện, chúng ta phải mở cửa cho Frontend gọi vào Backend. Mặc định trình duyệt sẽ chặn nếu cổng khác nhau (React chạy cổng 5173, FastAPI chạy cổng 8000).
Mở file
Sau đó khởi động lại Docker:
Bạn cần cài đặt Node.js trên máy tính trước nhé.
Mở Terminal tại thư mục gốc của dự án Jarvis (ngang hàng folder backend), chạy lệnh:
Giải thích:
Mở file
Mở file
Mở file
Mình sẽ code một giao diện đơn giản nhưng cực "chiến".
Tại Terminal thư mục
Vite sẽ cung cấp cho bạn một đường link (thường là
Bạn sẽ thấy gì?
Bây giờ bạn đã có một "vỏ bọc" hoàn hảo cho Jarvis. Bạn có thể mở rộng thêm bằng cách:
Dự án của chúng ta đã đi từ:
Lệnh Terminal -> Web cơ bản (Chainlit) -> Giao diện Sci-fi (React).
Nhưng... tất cả vẫn chỉ là phần mềm. Để Jarvis thực sự "sống", nó cần chạm vào thế giới thực.
Đã đến lúc cầm mỏ hàn lên và làm một chút về phần cứng!
- Tạo một trang web nền đen (
bg-black). - Tông màu chủ đạo là Cyan (Xanh lơ) đặc trưng của Jarvis.
- Có hiệu ứng xoay tròn (Arc Reactor) khi AI đang suy nghĩ.
- Kết nối trực tiếp với bộ não FastAPI chúng ta đã xây dựng.
1. Bước 0: Cấp quyền truy cập (CORS)
Trước khi làm giao diện, chúng ta phải mở cửa cho Frontend gọi vào Backend. Mặc định trình duyệt sẽ chặn nếu cổng khác nhau (React chạy cổng 5173, FastAPI chạy cổng 8000).
Mở file
backend/server.py và thêm đoạn này ngay sau khi khởi tạo app:
Python:
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# --- CHO PHÉP FRONTEND GỌI API (CORS) ---
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Cho phép mọi nguồn (trong dev). Product nên giới hạn.
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Sau đó khởi động lại Docker:
docker-compose restart backend.2. Bước 1: Khởi tạo dự án React
Bạn cần cài đặt Node.js trên máy tính trước nhé.
Mở Terminal tại thư mục gốc của dự án Jarvis (ngang hàng folder backend), chạy lệnh:
Mã:
npm create vite@latest frontend-react -- --template react
cd frontend-react
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install axios lucide-react
Giải thích:
axios: Dùng để gọi API.lucide-react: Bộ icon rất đẹp và nhẹ.
3. Bước 2: Cấu hình Giao diện (Tailwind)
Mở file
frontend-react/tailwind.config.js, sửa phần content và theme:
Mã:
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
jarvis: "#00f0ff", /* Màu Cyan Iron Man */
},
fontFamily: {
mono: ['"Courier New"', 'monospace'], /* Font chữ kiểu code */
}
},
},
plugins: [],
}
Mở file
frontend-react/src/index.css, xóa hết và thay bằng:
Mã:
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: #050505;
color: #00f0ff;
}
/* Hiệu ứng xoay tròn */
@keyframes spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin-slow {
animation: spin-slow 10s linear infinite;
}
4. Bước 3: Code giao diện chính (HUD)
Mở file
frontend-react/src/App.jsx. Đây là nơi ma thuật diễn ra.Mình sẽ code một giao diện đơn giản nhưng cực "chiến".
Mã:
import { useState, useRef, useEffect } from 'react';
import axios from 'axios';
import { Mic, Send, Cpu, Activity } from 'lucide-react';
function App() {
const [messages, setMessages] = useState([
{ role: 'bot', content: 'Hệ thống đã sẵn sàng. Chào ngài Tony.' }
]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const scrollRef = useRef(null);
// Tự động cuộn xuống cuối khi có tin nhắn mới
useEffect(() => {
scrollRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const sendMessage = async () => {
if (!input.trim()) return;
const userMsg = input;
setMessages(prev => [...prev, { role: 'user', content: userMsg }]);
setInput('');
setIsLoading(true);
try {
// Gọi API Backend (Nhớ thay token thật vào)
const res = await axios.post('http://localhost:8000/chat',
{ message: userMsg },
{ headers: { Authorization: "Bearer sieumatkhau123456" } }
);
setMessages(prev => [...prev, { role: 'bot', content: res.data.answer }]);
} catch (error) {
setMessages(prev => [...prev, { role: 'bot', content: "❌ Mất kết nối máy chủ." }]);
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen bg-black text-jarvis font-mono flex flex-col items-center justify-center p-4 relative overflow-hidden">
{/* VÒNG TRÒN TRANG TRÍ (ARC REACTOR) */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] border border-jarvis/20 rounded-full animate-spin-slow pointer-events-none"></div>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] border border-dashed border-jarvis/30 rounded-full animate-spin-slow pointer-events-none" style={{ animationDirection: 'reverse' }}></div>
{/* HEADER */}
<div className="z-10 w-full max-w-4xl flex justify-between items-center mb-8 border-b border-jarvis/50 pb-4">
<h1 className="text-3xl font-bold tracking-widest flex items-center gap-2">
<Cpu className="w-8 h-8" /> J.A.R.V.I.S
</h1>
<div className="flex gap-4 text-xs">
<span className="flex items-center gap-1"><Activity className="w-4 h-4" /> CPU: 12%</span>
<span className="flex items-center gap-1">RAM: 4.2GB</span>
<span className="text-green-500">● ONLINE</span>
</div>
</div>
{/* CHAT AREA */}
<div className="z-10 w-full max-w-4xl h-[60vh] border border-jarvis/30 bg-black/50 backdrop-blur-sm rounded-lg p-6 overflow-y-auto mb-6 shadow-[0_0_20px_rgba(0,240,255,0.2)]">
{messages.map((msg, idx) => (
<div key={idx} className={`mb-4 flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] p-3 rounded-lg border ${msg.role === 'user' ? 'border-jarvis bg-jarvis/10' : 'border-gray-700 bg-gray-900/80'}`}>
<p className="leading-relaxed">{msg.content}</p>
</div>
</div>
))}
{isLoading && <div className="text-sm animate-pulse">⚡ Đang xử lý dữ liệu...</div>}
<div ref={scrollRef}></div>
</div>
{/* INPUT AREA */}
<div className="z-10 w-full max-w-4xl flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Nhập lệnh..."
className="flex-1 bg-transparent border border-jarvis/50 rounded p-4 text-jarvis focus:outline-none focus:border-jarvis focus:shadow-[0_0_15px_rgba(0,240,255,0.5)] transition-all"
/>
<button onClick={sendMessage} className="p-4 border border-jarvis bg-jarvis/20 hover:bg-jarvis/40 rounded transition-all">
<Send className="w-6 h-6" />
</button>
<button className="p-4 border border-jarvis/50 hover:bg-jarvis/20 rounded transition-all">
<Mic className="w-6 h-6" />
</button>
</div>
</div>
);
}
export default App;
5. Chạy thử nghiệm
Tại Terminal thư mục
frontend-react:
Mã:
npm run dev
Vite sẽ cung cấp cho bạn một đường link (thường là
http://localhost:5173). Hãy mở nó lên.Bạn sẽ thấy gì?
- Một giao diện tối đen huyền bí.
- Các đường viền màu xanh lơ phát sáng.
- Hai vòng tròn lớn xoay ngược chiều nhau ở nền (giống màn hình radar).
- Khi chat, tin nhắn hiện ra đậm chất kỹ thuật số.
Tổng kết
Bây giờ bạn đã có một "vỏ bọc" hoàn hảo cho Jarvis. Bạn có thể mở rộng thêm bằng cách:
- Thêm biểu đồ sóng âm (Audio visualizer) khi Jarvis nói.
- Hiển thị thời tiết, đồng hồ ở góc màn hình.
- Tích hợp Web Speech API để bấm nút Mic là nói được ngay trên web.
Dự án của chúng ta đã đi từ:
Lệnh Terminal -> Web cơ bản (Chainlit) -> Giao diện Sci-fi (React).
Nhưng... tất cả vẫn chỉ là phần mềm. Để Jarvis thực sự "sống", nó cần chạm vào thế giới thực.
Đã đến lúc cầm mỏ hàn lên và làm một chút về phần cứng!
Bài viết liên quan