AI Hunter
Member
Hôm nay chúng ta sẽ code tính năng "nhìn và hiểu". Bạn sẽ chĩa điện thoại vào một món đồ, chụp "tách" một cái, và Jarvis sẽ nói cho bạn biết đó là gì.
Các Model AI hiện đại (như GPT-4o) yêu cầu ảnh phải được gửi dưới dạng chuỗi ký tự (Base64).
Mở file
Thêm Import:
Thêm Endpoint Vision:
Restart lại Backend:
Tại thư mục
*Lưu ý: Camera trên máy ảo (Simulator) sẽ không hoạt động hoặc chỉ hiện màn hình đen. Bạn nên test trên điện thoại thật.*
Chúng ta sẽ thêm một nút "Camera" trên giao diện. Khi bấm vào, màn hình Chat sẽ ẩn đi, thay bằng màn hình Camera full-screen.
Mở file
Thêm Import:
Thêm State và Logic Camera:
Trong
Cập nhật Giao diện (Thêm nút Camera và View Camera):
Thêm Styles mới:
Chúc mừng! Jarvis của bạn giờ đã nhìn thấy thế giới.
Ứng dụng Mobile của chúng ta đã có đủ: Chat, Voice, Vision.
Nhưng để nó thực sự trở thành trung tâm điều khiển nhà (Smart Home), chúng ta cần một giao diện trực quan hơn là cứ phải nói "Bật đèn". Đôi khi, một cái nút gạt (Switch) vẫn là chân ái.
1. Nâng cấp Backend (Xử lý hình ảnh)
Các Model AI hiện đại (như GPT-4o) yêu cầu ảnh phải được gửi dưới dạng chuỗi ký tự (Base64).
Mở file
backend/server.py và cập nhật:Thêm Import:
Python:
import base64
# ... các import cũ
Thêm Endpoint Vision:
Python:
@app.post("/vision-chat")
async def vision_chat_endpoint(file: UploadFile = File(...), message: str = "Mô tả bức ảnh này"):
try:
# 1. Đọc file ảnh và mã hóa sang Base64
contents = await file.read()
base64_image = base64.b64encode(contents).decode('utf-8')
print(f"👁️ Nhận ảnh Vision. Câu hỏi: {message}")
# 2. Gửi cho GPT-4o Vision
# Lưu ý: Cần dùng model gpt-4o hoặc gpt-4-turbo mới hỗ trợ ảnh
response = chat_client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": message},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
},
},
],
}
],
max_tokens=300,
)
bot_answer = response.choices[0].message.content
return {"answer": bot_answer}
except Exception as e:
print(f"Lỗi Vision: {e}")
return {"answer": "Xin lỗi, mắt tôi đang bị mờ (Lỗi Server)."}
Restart lại Backend:
docker-compose restart backend.2. Cài đặt thư viện Camera (Frontend)
Tại thư mục
jarvis-mobile, chạy lệnh:
Mã:
npx expo install expo-camera
*Lưu ý: Camera trên máy ảo (Simulator) sẽ không hoạt động hoặc chỉ hiện màn hình đen. Bạn nên test trên điện thoại thật.*
3. Nâng cấp Frontend (App.js)
Chúng ta sẽ thêm một nút "Camera" trên giao diện. Khi bấm vào, màn hình Chat sẽ ẩn đi, thay bằng màn hình Camera full-screen.
Mở file
App.js:Thêm Import:
JavaScript:
import { CameraView, useCameraPermissions } from 'expo-camera'; // Expo SDK 50+ dùng CameraView
import { Camera, X, SwitchCamera } from 'lucide-react-native'; // Icon mới
import { useRef } from 'react';
Thêm State và Logic Camera:
Trong
export default function App():
JavaScript:
// ... State cũ ...
const [showCamera, setShowCamera] = useState(false); // Trạng thái hiển thị Camera
const [permission, requestPermission] = useCameraPermissions();
const cameraRef = useRef(null); // Để điều khiển chụp ảnh
// --- HÀM CHỤP ẢNH & GỬI ---
const takePicture = async () => {
if (cameraRef.current) {
try {
// 1. Chụp ảnh
const photo = await cameraRef.current.takePictureAsync({
quality: 0.5, // Giảm chất lượng cho nhẹ (0.0 - 1.0)
base64: false,
});
setShowCamera(false); // Đóng camera
setLoading(true);
// 2. Upload ảnh
const formData = new FormData();
formData.append('file', {
uri: photo.uri,
type: 'image/jpeg',
name: 'vision.jpg',
});
// Gửi kèm câu hỏi mặc định hoặc input hiện tại
const promptText = input.trim() || "Cái này là cái gì?";
// Trick: Gửi query string cho FastAPI
const res = await axios.post(`${API_URL}/vision-chat?message=${encodeURIComponent(promptText)}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${API_TOKEN}`
},
});
// 3. Hiển thị kết quả
const userMsg = { id: Date.now().toString(), role: 'user', text: "📷 [Đã gửi ảnh] " + promptText };
const botMsg = { id: (Date.now() + 1).toString(), role: 'bot', text: res.data.answer };
setMessages(prev => [...prev, userMsg, botMsg]);
speak(res.data.answer); // Đọc luôn kết quả
} catch (e) {
console.error(e);
Alert.alert("Lỗi", "Không gửi được ảnh.");
setLoading(false);
}
}
};
Cập nhật Giao diện (Thêm nút Camera và View Camera):
JavaScript:
// Nếu chưa cấp quyền Camera
if (!permission) {
// View loading...
return <View />;
}
if (!permission.granted) {
return (
<View style={styles.container}>
<Text style={{color:'white', textAlign:'center', marginTop:50}}>Cần quyền Camera</Text>
<TouchableOpacity onPress={requestPermission} style={styles.sendBtn}><Text>Cấp quyền</Text></TouchableOpacity>
</View>
);
}
// Nếu đang bật chế độ Camera -> Hiển thị Full màn hình
if (showCamera) {
return (
<View style={styles.container}>
<CameraView style={{ flex: 1 }} facing="back" ref={cameraRef}>
<View style={styles.cameraControls}>
<TouchableOpacity onPress={() => setShowCamera(false)} style={styles.closeBtn}>
<X color="white" size={30} />
</TouchableOpacity>
<TouchableOpacity onPress={takePicture} style={styles.captureBtn}>
<View style={styles.captureInner} />
</TouchableOpacity>
</View>
</CameraView>
</View>
);
}
// Giao diện Chat chính (Thêm nút Camera vào Input Container)
return (
<SafeAreaView style={styles.container}>
{/* ... Header & Chat List ... */}
<View style={styles.inputContainer}>
{/* Nút bật Camera */}
<TouchableOpacity onPress={() => setShowCamera(true)} style={styles.iconBtn}>
<Camera color="#00f0ff" size={24} />
</TouchableOpacity>
<TextInput
// ... (như cũ)
style={styles.inputWithIcon} // Sửa style chút cho đẹp
/>
{/* ... Nút Mic cũ ... */}
</View>
</SafeAreaView>
);
Thêm Styles mới:
JavaScript:
const styles = StyleSheet.create({
// ... Styles cũ ...
iconBtn: {
padding: 10,
marginRight: 5,
},
inputWithIcon: {
// Copy style input cũ nhưng sửa width lại chút nếu cần
flex: 1,
backgroundColor: '#111',
color: '#00f0ff',
borderWidth: 1,
borderColor: '#333',
borderRadius: 25,
paddingHorizontal: 15,
height: 50,
marginRight: 10,
},
// Style cho Camera Mode
cameraControls: {
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
justifyContent: 'center',
marginBottom: 30,
alignItems: 'flex-end',
},
captureBtn: {
width: 70,
height: 70,
borderRadius: 35,
backgroundColor: 'rgba(255,255,255,0.3)',
justifyContent: 'center',
alignItems: 'center',
},
captureInner: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'white',
},
closeBtn: {
position: 'absolute',
top: 50,
left: 20,
padding: 10,
backgroundColor: 'rgba(0,0,0,0.5)',
borderRadius: 20,
}
});
4. Thực chiến
- Mở App trên điện thoại thật.
- Bấm vào icon Camera (hình máy ảnh).
- Giao diện chuyển sang màn hình chụp ảnh.
- Chĩa vào cái bàn phím máy tính (hoặc chai nước).
- Bấm nút tròn trắng để chụp.
- Màn hình quay lại đoạn chat, hiện "Đang xử lý...".
- Jarvis: "Đây là một bàn phím cơ, có vẻ như là loại layout TKL, đèn nền LED RGB..." (Đồng thời đọc to lên).
Tổng kết
Chúc mừng! Jarvis của bạn giờ đã nhìn thấy thế giới.
- Bạn đi du lịch nước ngoài -> Chụp biển báo -> Jarvis dịch.
- Bạn thấy con côn trùng lạ -> Chụp -> Jarvis tra cứu.
- Bạn code sai -> Chụp màn hình -> Jarvis sửa.
Ứng dụng Mobile của chúng ta đã có đủ: Chat, Voice, Vision.
Nhưng để nó thực sự trở thành trung tâm điều khiển nhà (Smart Home), chúng ta cần một giao diện trực quan hơn là cứ phải nói "Bật đèn". Đôi khi, một cái nút gạt (Switch) vẫn là chân ái.
Bài viết liên quan