Open1

Expo Gemini

JboyHashimotoJboyHashimoto

健康診断をしてもらう

Geminiと対話するアプリを作る。

https://ai.google.dev/gemini-api/docs/quickstart?hl=ja#javascript

モジュールを追加しないと、.envを読み込めない?

{
  "name": "gemini-expo",
  "version": "1.0.0",
  "main": "index.ts",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@google/genai": "^1.8.0",
    "@google/generative-ai": "^0.24.1",
    "expo": "~53.0.17",
    "expo-status-bar": "~2.2.3",
    "react": "19.0.0",
    "react-native": "0.79.5",
    "react-native-dotenv": "^3.4.11"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@types/react": "~19.0.10",
    "typescript": "~5.8.3"
  },
  "private": true
}

.envにAPI KEYを設定しておく。

EXPO_PUBLIC_GEMINI_API_KEY=AIzaSyB7Y8Q***************

App.tsxにコードを記載しておく。

import React, { useState } from 'react';
import {
  StyleSheet,
  Text,
  View,
  TextInput,
  TouchableOpacity,
  ScrollView,
  ActivityIndicator,
  Alert,
  KeyboardAvoidingView,
  Platform,
} from 'react-native';
import { GoogleGenerativeAI } from '@google/generative-ai';

// @ts-ignore
const genAI = new GoogleGenerativeAI(process.env.EXPO_PUBLIC_GEMINI_API_KEY || '');

export default function App() {
  const [mood, setMood] = useState('');
  const [diagnosis, setDiagnosis] = useState('');
  const [loading, setLoading] = useState(false);

  const handleDiagnosis = async () => {
    if (!mood.trim()) {
      Alert.alert('エラー', '今日の気分を入力してください');
      return;
    }

    setLoading(true);
    setDiagnosis('');

    try {
      const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
      
      const prompt = `以下の気分から健康状態を診断し、アドバイスをください。
      
今日の気分: ${mood}

診断結果は以下の形式で回答してください:
1. 総合的な健康状態の評価
2. 心理的な状態の分析
3. 推奨される行動やアドバイス
4. 注意すべき点`;

      const result = await model.generateContent(prompt);
      const response = result.response;
      const text = response.text();
      
      setDiagnosis(text);
    } catch (error) {
      console.error('Error:', error);
      Alert.alert('エラー', 'AI診断中にエラーが発生しました');
    } finally {
      setLoading(false);
    }
  };

  return (
    <KeyboardAvoidingView 
      style={styles.container}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
    >
      <ScrollView contentContainerStyle={styles.scrollContainer}>
        <View style={styles.header}>
          <Text style={styles.title}>健康診断AI</Text>
          <Text style={styles.subtitle}>今日の気分から健康状態を診断します</Text>
        </View>

        <View style={styles.inputContainer}>
          <Text style={styles.label}>今日の気分を入力してください:</Text>
          <TextInput
            style={styles.textInput}
            multiline
            numberOfLines={6}
            placeholder="例:朝起きた時少し頭が重く、仕事中も集中力が続かなかった。昼食後は少し楽になったが、夕方にかけて疲労感が強くなった..."
            value={mood}
            onChangeText={setMood}
            textAlignVertical="top"
          />
        </View>

        <TouchableOpacity 
          style={[styles.button, loading && styles.buttonDisabled]}
          onPress={handleDiagnosis}
          disabled={loading}
        >
          {loading ? (
            <ActivityIndicator color="#ffffff" />
          ) : (
            <Text style={styles.buttonText}>診断する</Text>
          )}
        </TouchableOpacity>

        {diagnosis !== '' && (
          <View style={styles.resultContainer}>
            <Text style={styles.resultTitle}>診断結果:</Text>
            <ScrollView style={styles.resultScrollView}>
              <Text style={styles.resultText}>{diagnosis}</Text>
            </ScrollView>
          </View>
        )}
      </ScrollView>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  scrollContainer: {
    flexGrow: 1,
    padding: 20,
    paddingTop: 60,
  },
  header: {
    alignItems: 'center',
    marginBottom: 30,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
  },
  inputContainer: {
    marginBottom: 20,
  },
  label: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
    marginBottom: 10,
  },
  textInput: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 15,
    minHeight: 150,
    fontSize: 16,
    borderWidth: 1,
    borderColor: '#ddd',
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.1,
    shadowRadius: 3.84,
    elevation: 5,
  },
  button: {
    backgroundColor: '#4285F4',
    borderRadius: 25,
    paddingVertical: 15,
    paddingHorizontal: 40,
    alignSelf: 'center',
    marginBottom: 20,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  buttonDisabled: {
    backgroundColor: '#cccccc',
  },
  buttonText: {
    color: '#ffffff',
    fontSize: 18,
    fontWeight: 'bold',
  },
  resultContainer: {
    flex: 1,
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    marginTop: 10,
    borderWidth: 1,
    borderColor: '#ddd',
    maxHeight: 400,
  },
  resultTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 15,
  },
  resultScrollView: {
    flex: 1,
  },
  resultText: {
    fontSize: 16,
    lineHeight: 24,
    color: '#444',
  },
});