Mobile Vision - Đôi mắt thần của Jarvis

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ì.

Mobile Vision - Đôi mắt thần của Jarvis.jpg

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​

  1. Mở App trên điện thoại thật.
  2. Bấm vào icon Camera (hình máy ảnh).
  3. Giao diện chuyển sang màn hình chụp ảnh.
  4. Chĩa vào cái bàn phím máy tính (hoặc chai nước).
  5. Bấm nút tròn trắng để chụp.
  6. Màn hình quay lại đoạn chat, hiện "Đang xử lý...".
  7. 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.
 
Back
Top