Refactor socket handlers to improve performance and enhance thinking level management
This commit is contained in:
@@ -3,13 +3,18 @@ import { useMemo, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
FlatList,
|
FlatList,
|
||||||
|
Keyboard,
|
||||||
|
KeyboardAvoidingView,
|
||||||
Modal,
|
Modal,
|
||||||
|
Platform,
|
||||||
Pressable,
|
Pressable,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
useWindowDimensions,
|
||||||
View,
|
View,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { colors, monoFont } from "@/lib/theme";
|
import { colors, monoFont } from "@/lib/theme";
|
||||||
import type { ModelRef, ModelSummary } from "@/lib/types";
|
import type { ModelRef, ModelSummary } from "@/lib/types";
|
||||||
|
|
||||||
@@ -39,6 +44,10 @@ export function ModelSelector({
|
|||||||
}: ModelSelectorProps) {
|
}: ModelSelectorProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
const { height: windowHeight } = useWindowDimensions();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const sheetHeight = Math.round(windowHeight * 0.78);
|
||||||
|
|
||||||
const filteredModels = useMemo(() => {
|
const filteredModels = useMemo(() => {
|
||||||
const needle = query.trim().toLowerCase();
|
const needle = query.trim().toLowerCase();
|
||||||
if (!needle) {
|
if (!needle) {
|
||||||
@@ -55,6 +64,17 @@ export function ModelSelector({
|
|||||||
? models.find((model) => model.provider === selected.provider && model.id === selected.id)
|
? models.find((model) => model.provider === selected.provider && model.id === selected.id)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
Keyboard.dismiss();
|
||||||
|
setQuery("");
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (model: ModelRef) => {
|
||||||
|
onSelect(model);
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -77,12 +97,24 @@ export function ModelSelector({
|
|||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Modal visible={open} animationType="slide" transparent onRequestClose={() => setOpen(false)}>
|
<Modal visible={open} animationType="slide" transparent onRequestClose={close}>
|
||||||
<View style={styles.overlay}>
|
<KeyboardAvoidingView
|
||||||
<View style={styles.sheet}>
|
style={styles.overlay}
|
||||||
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
|
>
|
||||||
|
<Pressable style={styles.backdrop} onPress={close} accessibilityLabel="Close model selector" />
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.sheet,
|
||||||
|
{
|
||||||
|
height: sheetHeight,
|
||||||
|
paddingBottom: Math.max(insets.bottom, 16),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.title}>Choose model</Text>
|
<Text style={styles.title}>Choose model</Text>
|
||||||
<Pressable onPress={() => setOpen(false)} hitSlop={10}>
|
<Pressable onPress={close} hitSlop={10} accessibilityLabel="Close">
|
||||||
<Ionicons name="close" size={22} color={colors.textSecondary} />
|
<Ionicons name="close" size={22} color={colors.textSecondary} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
@@ -94,48 +126,53 @@ export function ModelSelector({
|
|||||||
style={styles.search}
|
style={styles.search}
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
|
returnKeyType="search"
|
||||||
/>
|
/>
|
||||||
{error ? <Text style={styles.error}>{error}</Text> : null}
|
{error ? <Text style={styles.error}>{error}</Text> : null}
|
||||||
{models.length === 0 ? (
|
<View style={styles.listRegion}>
|
||||||
<Text style={styles.empty}>{loading ? "Loading models…" : "No available models"}</Text>
|
{models.length === 0 ? (
|
||||||
) : (
|
<Text style={styles.empty}>{loading ? "Loading models…" : "No available models"}</Text>
|
||||||
<FlatList
|
) : (
|
||||||
data={filteredModels}
|
<FlatList
|
||||||
keyExtractor={(model) => `${model.provider}:${model.id}`}
|
style={styles.list}
|
||||||
keyboardShouldPersistTaps="handled"
|
data={filteredModels}
|
||||||
renderItem={({ item }) => {
|
keyExtractor={(model) => `${model.provider}:${model.id}`}
|
||||||
const isSelected =
|
keyboardShouldPersistTaps="handled"
|
||||||
item.provider === selected?.provider && item.id === selected?.id;
|
keyboardDismissMode="on-drag"
|
||||||
return (
|
contentContainerStyle={
|
||||||
<Pressable
|
filteredModels.length === 0 ? styles.listEmptyContent : styles.listContent
|
||||||
style={({ pressed }) => [
|
}
|
||||||
styles.option,
|
renderItem={({ item }) => {
|
||||||
pressed && styles.pressed,
|
const isSelected =
|
||||||
isSelected && styles.selected,
|
item.provider === selected?.provider && item.id === selected?.id;
|
||||||
]}
|
return (
|
||||||
onPress={() => {
|
<Pressable
|
||||||
onSelect({ provider: item.provider, id: item.id });
|
style={({ pressed }) => [
|
||||||
setOpen(false);
|
styles.option,
|
||||||
}}
|
pressed && styles.pressed,
|
||||||
>
|
isSelected && styles.selected,
|
||||||
<View style={styles.optionText}>
|
]}
|
||||||
<Text style={styles.optionId}>{item.id}</Text>
|
onPress={() => handleSelect({ provider: item.provider, id: item.id })}
|
||||||
<Text style={styles.optionMeta}>
|
>
|
||||||
{item.providerDisplayName ?? item.provider}
|
<View style={styles.optionText}>
|
||||||
{item.name && item.name !== item.id ? ` · ${item.name}` : ""}
|
<Text style={styles.optionId}>{item.id}</Text>
|
||||||
</Text>
|
<Text style={styles.optionMeta}>
|
||||||
</View>
|
{item.providerDisplayName ?? item.provider}
|
||||||
{isSelected ? (
|
{item.name && item.name !== item.id ? ` · ${item.name}` : ""}
|
||||||
<Ionicons name="checkmark" size={18} color={colors.accent} />
|
</Text>
|
||||||
) : null}
|
</View>
|
||||||
</Pressable>
|
{isSelected ? (
|
||||||
);
|
<Ionicons name="checkmark" size={18} color={colors.accent} />
|
||||||
}}
|
) : null}
|
||||||
ListEmptyComponent={<Text style={styles.empty}>No matching models</Text>}
|
</Pressable>
|
||||||
/>
|
);
|
||||||
)}
|
}}
|
||||||
|
ListEmptyComponent={<Text style={styles.empty}>No matching models</Text>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</KeyboardAvoidingView>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -160,14 +197,15 @@ const styles = StyleSheet.create({
|
|||||||
pressed: { backgroundColor: colors.surface },
|
pressed: { backgroundColor: colors.surface },
|
||||||
disabled: { opacity: 0.45 },
|
disabled: { opacity: 0.45 },
|
||||||
overlay: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0, 0, 0, 0.65)" },
|
overlay: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0, 0, 0, 0.65)" },
|
||||||
|
backdrop: { ...StyleSheet.absoluteFillObject },
|
||||||
sheet: {
|
sheet: {
|
||||||
maxHeight: "78%",
|
|
||||||
backgroundColor: colors.surface,
|
backgroundColor: colors.surface,
|
||||||
borderTopLeftRadius: 16,
|
borderTopLeftRadius: 16,
|
||||||
borderTopRightRadius: 16,
|
borderTopRightRadius: 16,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border,
|
borderColor: colors.border,
|
||||||
padding: 16,
|
paddingHorizontal: 16,
|
||||||
|
paddingTop: 16,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" },
|
header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" },
|
||||||
@@ -183,6 +221,21 @@ const styles = StyleSheet.create({
|
|||||||
fontFamily: monoFont,
|
fontFamily: monoFont,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
},
|
},
|
||||||
|
listRegion: {
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
listContent: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
listEmptyContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
paddingTop: 8,
|
||||||
|
},
|
||||||
option: {
|
option: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
Reference in New Issue
Block a user