Skip to content

Commit

Permalink
feat: add api value editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Vali-98 committed Nov 29, 2024
1 parent cad89d7 commit d92e2da
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 7 deletions.
14 changes: 13 additions & 1 deletion app/components/APIManager/APIValueItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import SwitchComponent from '@components/SwitchTitle'
import { AntDesign } from '@expo/vector-icons'
import { APIManagerValue, APIState } from 'constants/API/APIManagerState'
import { Style } from 'constants/Global'
import { useState } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'

import EditAPIModal from './EditAPIModal'

type APIValueItemProps = {
item: APIManagerValue
index: number
}

const APIValueItem: React.FC<APIValueItemProps> = ({ item, index }) => {
const [showEditor, setShowEditor] = useState(false)
const { removeValue, editValue } = APIState.useAPIState((state) => ({
removeValue: state.removeValue,
editValue: state.editValue,
Expand All @@ -35,6 +39,14 @@ const APIValueItem: React.FC<APIValueItemProps> = ({ item, index }) => {

return (
<View style={item.active ? styles.longContainer : styles.longContainerInactive}>
<EditAPIModal
index={index}
originalValues={item}
show={showEditor}
close={() => {
setShowEditor(false)
}}
/>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<SwitchComponent
value={item.active}
Expand All @@ -60,7 +72,7 @@ const APIValueItem: React.FC<APIValueItemProps> = ({ item, index }) => {
size={24}
/>
</TouchableOpacity>
<TouchableOpacity>
<TouchableOpacity onPress={() => setShowEditor(true)}>
<AntDesign name="edit" color={Style.getColor('primary-text1')} size={24} />
</TouchableOpacity>
</View>
Expand Down
2 changes: 1 addition & 1 deletion app/components/APIManager/AddAPI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const AddAPI = () => {

return (
<View style={styles.mainContainer}>
<Stack.Screen options={{ title: 'New API' }} />
<Stack.Screen options={{ title: 'Add Connection' }} />
<ScrollView style={{ flex: 1 }}>
<Dropdown
value={template}
Expand Down
302 changes: 302 additions & 0 deletions app/components/APIManager/EditAPIModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import ButtonPrimary from '@components/Buttons/ButtonPrimary'
import FadeBackrop from '@components/FadeBackdrop'
import { APIConfiguration } from '@constants/API/APIBuilder.types'
import claudeModels from '@constants/API/ClaudeModels.json'
import { MaterialIcons } from '@expo/vector-icons'
import { APIManagerValue, APIState } from 'constants/API/APIManagerState'
import { Logger, Style } from 'constants/Global'
import { useEffect, useState } from 'react'
import {
Modal,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native'
import { Dropdown } from 'react-native-element-dropdown'

type EditAPIModalProps = {
index: number
show: boolean
close: () => void
originalValues: APIManagerValue
}

const EditAPIModal: React.FC<EditAPIModalProps> = ({ index, show, close, originalValues }) => {
const { editValue, getTemplates } = APIState.useAPIState((state) => ({
getTemplates: state.getTemplates,
editValue: state.editValue,
}))

const [template, setTemplate] = useState<APIConfiguration>(getTemplates()[0])

const [values, setValues] = useState<APIManagerValue>(originalValues)
const [modelList, setModelList] = useState<any[]>([])

useEffect(() => {
const newTemplate = getTemplates().find((item) => item.name === values.configName)
if (!newTemplate) {
Logger.log('Could not get valid template!', true)
close()
return
}

setTemplate(newTemplate)
}, [])

const handleGetModelList = async () => {
if (!template.features.useModel) return
if (template.defaultValues.modelEndpoint === '{{CLAUDE}}') {
setModelList(claudeModels.models)
return
}
let auth: any = {}
if (template.features.useKey) {
auth = { [template.request.authHeader]: template.request.authPrefix + values.key }
}
const result = await fetch(values.modelEndpoint, { headers: { ...auth } })
const data = await result.json()
if (result.status !== 200) {
Logger.log(`Could not retrieve models: ${data?.error?.message}`)
return
}
const models = getNestedValue(data, template.model.modelListParser)
setModelList(models)
}

useEffect(() => {
handleGetModelList()
}, [template])

return (
<Modal
transparent
onRequestClose={close}
statusBarTranslucent
visible={show}
animationType="fade">
<FadeBackrop
handleOverlayClick={() => {
close()
}}
/>
<View style={{ flex: 1 }} />
<View style={styles.mainContainer}>
<Text
style={{
color: Style.getColor('primary-text1'),
fontSize: 20,
fontWeight: '500',
paddingBottom: 24,
}}>
Edit Connection
</Text>

<ScrollView style={{ flex: 1 }} showsVerticalScrollIndicator={false}>
<View>
<Text style={styles.title}>Friendly Name</Text>
<TextInput
style={styles.input}
value={values.friendlyName}
onChangeText={(value) => {
setValues({ ...values, friendlyName: value })
}}
placeholder={template.defaultValues.endpoint}
placeholderTextColor={Style.getColor('primary-text2')}
/>
</View>

{template.ui.editableCompletionPath && (
<View>
<Text style={styles.title}>Completion URL</Text>
<Text style={styles.subtitle}>Note: Use full URL path</Text>
<TextInput
style={styles.input}
value={values.endpoint}
onChangeText={(value) => {
setValues({ ...values, endpoint: value })
}}
placeholder={template.defaultValues.endpoint}
placeholderTextColor={Style.getColor('primary-text2')}
/>
</View>
)}

{template.ui.editableModelPath && (
<View>
<Text style={styles.title}>Model URL</Text>
<Text style={styles.subtitle}>Note: Use full URL path</Text>
<TextInput
style={styles.input}
value={values.modelEndpoint}
onChangeText={(value) => {
setValues({ ...values, modelEndpoint: value })
}}
placeholder={template.defaultValues.modelEndpoint}
placeholderTextColor={Style.getColor('primary-text2')}
/>
</View>
)}

{template.features.useKey && (
<View>
<Text style={styles.title}>API Key</Text>
<Text style={styles.subtitle}>Key will not be shown</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TextInput
style={styles.input}
value={values.key}
onChangeText={(value) => {
setValues({ ...values, key: value })
}}
placeholder="Press save to confirm key"
placeholderTextColor={Style.getColor('primary-text2')}
secureTextEntry
/>
</View>
</View>
)}

{template.features.useModel && (
<View style={styles.dropdownContainer}>
<Text style={styles.title}>Model</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Dropdown
value={values.model}
data={modelList}
labelField={template.model.nameParser}
valueField={template.model.nameParser}
onChange={(item) => {
// if same return
setValues({ ...values, model: item })
}}
placeholder="Select Model"
{...Style.drawer.default}
/>
<TouchableOpacity
style={styles.button}
onPress={() => {
handleGetModelList()
}}>
<MaterialIcons
name="refresh"
color={Style.getColor('primary-text1')}
size={24}
/>
</TouchableOpacity>
</View>
</View>
)}

{template.features.useFirstMessage && (
<View>
<Text style={styles.title}>First Message</Text>
<Text style={styles.subtitle}>
Default first message sent to Claude
</Text>
<TextInput
style={{ ...styles.input, textAlignVertical: 'top' }}
value={values.firstMessage}
onChangeText={(value) => {
setValues({ ...values, firstMessage: value })
}}
numberOfLines={4}
placeholder="..."
placeholderTextColor={Style.getColor('primary-text2')}
/>
</View>
)}
{template.features.usePrefill && (
<View>
<Text style={styles.title}>Prefill</Text>
<Text style={styles.subtitle}>Leave blank to use default endpoint</Text>
<TextInput
style={{ ...styles.input, textAlignVertical: 'top' }}
value={values.prefill}
onChangeText={(value) => {
setValues({ ...values, prefill: value })
}}
numberOfLines={4}
placeholder="This prefill occurs on the final assistant message"
placeholderTextColor={Style.getColor('primary-text2')}
/>
</View>
)}
</ScrollView>
<ButtonPrimary
buttonStyle={{ marginTop: 8 }}
label="Save Changes"
onPress={() => {
editValue(values, index)
close()
}}
/>
</View>
</Modal>
)
}

export default EditAPIModal

const styles = StyleSheet.create({
mainContainer: {
marginVertical: 16,
paddingVertical: 24,
paddingHorizontal: 20,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
minHeight: '70%',
backgroundColor: Style.getColor('primary-surface1'),
},

title: {
paddingTop: 8,
color: Style.getColor('primary-text1'),
fontSize: 16,
},

subtitle: {
color: Style.getColor('primary-text2'),
},

input: {
flex: 1,
color: Style.getColor('primary-text1'),
borderColor: Style.getColor('primary-brand'),
borderWidth: 1,
paddingHorizontal: 8,
paddingVertical: 8,
marginVertical: 8,
borderRadius: 8,
},

button: {
padding: 5,
borderColor: Style.getColor('primary-brand'),
borderWidth: 1,
borderRadius: 4,
marginLeft: 8,
},

dropdownContainer: {
marginTop: 16,
},

modelInfo: {
borderRadius: 8,
backgroundColor: Style.getColor('primary-surface2'),
flex: 1,
paddingHorizontal: 16,
paddingTop: 12,
paddingBottom: 24,
},
})

const getNestedValue = (obj: any, path: string) => {
if (path === '') return obj
const keys = path.split('.')
const value = keys.reduce((acc, key) => acc?.[key], obj)
return value ?? null
}
8 changes: 3 additions & 5 deletions app/components/Buttons/ButtonPrimary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
interface ButtonPrimaryProps extends Omit<PressableProps, 'style'> {
labelStyle?: TextStyle
label?: string
pressStyle?: ViewStyle
unpressStyle?: ViewStyle
buttonStyle?: ViewStyle
opacity?: number
}

Expand All @@ -23,8 +22,7 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
const ButtonPrimary: React.FC<ButtonPrimaryProps> = ({
labelStyle,
label,
pressStyle,
unpressStyle,
buttonStyle,
children,
onPressIn,
opacity = 1,
Expand Down Expand Up @@ -57,7 +55,7 @@ const ButtonPrimary: React.FC<ButtonPrimaryProps> = ({
}}
{...rest}
style={StyleSheet.flatten([
pressStyle,
buttonStyle,
{
backgroundColor: Style.getColor('primary-brand'),
padding: 8,
Expand Down

0 comments on commit d92e2da

Please sign in to comment.