حلول الجوال
إجابات اختبار المعرفة
-
تُرجع
useStateمصفوفة من قيمتين: قيمة الحالة الحالية ودالة لتحديثها. مثال:const [messages, setMessages] = useState([]). -
useEffectمع[]يستدعيfetchMessagesمرة واحدة عند أول تحميل للمكوّن. أما استدعاؤه يدويًا (مثلًا بعد إرسال رسالة) فيهدف إلى تحديث القائمة فورًا. -
تعرض
FlatListالعناصر افتراضيًا (virtualization) — فلا تعرض إلا العناصر الظاهرة على الشاشة. أماScrollViewفتعرض كل العناصر دفعة واحدة، وهو أمر بطيء مع سجلات المحادثات الطويلة. -
response.json()تحوّل جسم استجابة HTTP من صيغة JSON إلى كائن JavaScript. -
يتكيّف سلوك لوحة المفاتيح حسب المنصة: يستخدم iOS القيمة
"padding"(تغيير حجم العرض)، ويستخدم Android القيمة"height"(تغيير الحجم مع تعديل الارتفاع).
حل التحدي 1
أضف إلى منطق الحالة:
const handleClearChat = async () => {
try {
await fetch(`${API_BASE_URL}/api/messages`, { method: "DELETE" });
setMessages([]);
} catch (err) {
console.error("Failed to clear:", err);
}
};
أضف زرًا في الترويسة:
<View style={styles.header}>
<Text style={styles.headerTitle}>Agentic Chat</Text>
<TouchableOpacity onPress={handleClearChat}>
<Text style={{ color: "#FFF", fontSize: 14 }}>Clear</Text>
</TouchableOpacity>
</View>
حدّث نمط الترويسة:
header: {
backgroundColor: "#007AFF",
paddingTop: 50,
paddingBottom: 15,
paddingHorizontal: 16,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
حل التحدي 2
أضف صورة رمزية بجانب كل فقاعة:
const renderMessage = ({ item }: { item: Message }) => {
const isUser = item.sender === "user";
return (
<View style={[styles.messageRow, isUser && styles.messageRowUser]}>
{!isUser && (
<View style={styles.avatar}>
<Text style={styles.avatarText}>A</Text>
</View>
)}
<View style={[styles.bubble, isUser ? styles.userBubble : styles.aiBubble]}>
<Text style={[styles.bubbleText, isUser ? styles.userText : styles.aiText]}>
{item.content}
</Text>
</View>
{isUser && (
<View style={[styles.avatar, styles.userAvatar]}>
<Text style={[styles.avatarText, styles.userAvatarText]}>U</Text>
</View>
)}
</View>
);
};
الأنماط:
messageRow: { flexDirection: "row", marginBottom: 8, alignItems: "flex-end" },
messageRowUser: { justifyContent: "flex-end" },
avatar: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: "#DDD",
justifyContent: "center",
alignItems: "center",
marginHorizontal: 6,
},
userAvatar: { backgroundColor: "#007AFF" },
avatarText: { color: "#666", fontWeight: "bold", fontSize: 14 },
userAvatarText: { color: "#FFF" },
حل التحدي 3
const [showScrollButton, setShowScrollButton] = useState(false);
const handleScroll = (event: any) => {
const offsetY = event.nativeEvent.contentOffset.y;
setShowScrollButton(offsetY > 200);
};
const scrollToBottom = () => {
flatListRef.current?.scrollToEnd({ animated: true });
};
أضف إلى FlatList:
<FlatList
onScroll={handleScroll}
scrollEventThrottle={100}
...
/>
أضف زرًا عائمًا:
{showScrollButton && (
<TouchableOpacity style={styles.scrollButton} onPress={scrollToBottom}>
<Text style={styles.scrollButtonText}>↓</Text>
</TouchableOpacity>
)}
الأنماط:
scrollButton: {
position: "absolute",
bottom: 80,
right: 16,
backgroundColor: "#007AFF",
width: 40,
height: 40,
borderRadius: 20,
justifyContent: "center",
alignItems: "center",
elevation: 4,
shadowColor: "#000",
shadowOpacity: 0.2,
shadowRadius: 4,
},
scrollButtonText: { color: "#FFF", fontSize: 20 },
مشروع مفتوح: محادثة متعددة المزوّدين
هذا مشروع أكبر. إليك مقاربة أولية للبدء:
1. إضافة حالة المزوّد
const [provider, setProvider] = useState("openai");
const [customApiKey, setCustomApiKey] = useState("");
const [showSettings, setShowSettings] = useState(false);
2. نافذة الإعدادات المنبثقة
{showSettings && (
<View style={styles.settingsOverlay}>
<View style={styles.settingsPanel}>
<Text style={styles.settingsTitle}>Settings</Text>
<Text>Provider:</Text>
{["openai", "anthropic", "deepseek"].map((p) => (
<TouchableOpacity key={p} onPress={() => setProvider(p)}>
<Text style={{ fontWeight: provider === p ? "bold" : "normal" }}>{p}</Text>
</TouchableOpacity>
))}
<TextInput
placeholder="API Key"
value={customApiKey}
onChangeText={setCustomApiKey}
secureTextEntry
/>
<TouchableOpacity onPress={() => setShowSettings(false)}>
<Text>Done</Text>
</TouchableOpacity>
</View>
</View>
)}
3. إرسال معلومات المزوّد في ترويسات الطلب
const response = await fetch(`${API_BASE_URL}/api/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Provider": provider,
"X-Api-Key": customApiKey,
},
body: JSON.stringify({ message: text }),
});
4. قراءة الترويسات في الواجهة الخلفية (routes.py)
provider = request.headers.get("X-Provider", "openai")
api_key = request.headers.get("X-Api-Key") or os.getenv("OPENAI_API_KEY")
if provider == "anthropic":
# استدعاء Anthropic API
elif provider == "deepseek":
# استدعاء DeepSeek API (متوافق مع OpenAI)
else:
# الاستدعاء الافتراضي لـ OpenAI