Skip to content

Commit

Permalink
Add API Keys setup screen
Browse files Browse the repository at this point in the history
  • Loading branch information
zhuanghongji committed Jun 7, 2023
1 parent 87eed58 commit 354218f
Show file tree
Hide file tree
Showing 15 changed files with 861 additions and 21 deletions.
8 changes: 8 additions & 0 deletions src/components/SvgIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ const SVG_ICONS = {
v: '0 96 960 960',
d: 'm400 654 250-250q9-9 21-9t21 9q9 9 9 21t-9 21L421 717q-9 9-21 9t-21-9L268 606q-9-9-9-21t9-21q9-9 21-9t21 9l90 90Z',
},
'down-bottom': {
v: '0 -960 960 960',
d: 'M190-120q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T190-180h580q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T770-120H190Zm289.867-132Q474-252 469-254q-5-2-10-7L307-413q-8-8-8-20.8t9.391-22.4Q317-465 329.5-465q12.5 0 21.5 9l99 100v-454q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T510-810v454l96-96q8.442-8 20.721-8T648-451q9 9 9 21.5t-9 21.5L501-261q-5 5-10.133 7-5.134 2-11 2Z',
},
'filter-on': {
v: '0 96 960 960',
d: 'M430 816q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T430 756h100q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T530 816H430ZM150 396q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T150 336h660q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T810 396H150Zm120 210q-12.75 0-21.375-8.675-8.625-8.676-8.625-21.5 0-12.825 8.625-21.325T270 546h420q12.75 0 21.375 8.675 8.625 8.676 8.625 21.5 0 12.825-8.625 21.325T690 606H270Z',
Expand Down Expand Up @@ -315,6 +319,10 @@ const SVG_ICONS = {
v: '0 96 960 960',
d: 'm629 637-44-44q26-71-27-118t-115-24l-44-44q17-11 38-16t43-5q71 0 120.5 49.5T650 556q0 22-5.5 43.5T629 637Zm129 129-40-40q49-36 85.5-80.5T857 556q-50-111-150-175.5T490 316q-42 0-86 8t-69 19l-46-47q35-16 89.5-28T485 256q135 0 249 74t174 199q3 5 4 12t1 15q0 8-1 15.5t-4 12.5q-26 55-64 101t-86 81Zm36 204L648 827q-35 14-79 21.5t-89 7.5q-138 0-253-74T52 583q-3-6-4-12.5T47 556q0-8 1.5-15.5T52 528q21-45 53.5-87.5T182 360L77 255q-9-9-9-21t9-21q9-9 21.5-9t21.5 9l716 716q8 8 8 19.5t-8 20.5q-8 10-20.5 10t-21.5-9ZM223 402q-37 27-71.5 71T102 556q51 111 153.5 175.5T488 796q33 0 65-4t48-12l-64-64q-11 5-27 7.5t-30 2.5q-70 0-120-49t-50-121q0-15 2.5-30t7.5-27l-97-97Zm305 142Zm-116 58Z',
},
'vpn-key': {
v: '0 -960 960 960',
d: 'M280-240q-100 0-170-70T40-480q0-100 70-170t170-70q78 0 131.5 37.5T491-583h369q24.75 0 42.375 17.625T920-523v86q0 24.75-17.625 42.375T860-377h-46v77q0 24.75-17.625 42.375T754-240h-66q-24.75 0-42.375-17.625T628-300v-77H491q-26 62-79.5 99.5T280-240Zm0-60q71 0 116.5-47t53.374-90H692v137h62v-137h106v-86H450q-8-43-53.5-90T280-660q-75 0-127.5 52.5T100-480q0 75 52.5 127.5T280-300Zm0-112q29 0 48.5-19.5T348-480q0-29-19.5-48.5T280-548q-29 0-48.5 19.5T212-480q0 29 19.5 48.5T280-412Zm0-68Z',
},
} satisfies { [key: string]: Icon }

export type SvgIconName = keyof typeof SVG_ICONS
Expand Down
24 changes: 16 additions & 8 deletions src/http/apis/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,25 @@ export function useOpenAIApiUrlOptions() {
const [apiUrlPath] = useApiUrlPathPref()
const [apiKey] = useApiKeyPref()
const checkIsOptionsValid = (): boolean => {
let enterTip = ''
if (!apiUrl) {
enterTip = t('Enter API URL first')
} else if (!apiUrlPath) {
enterTip = t('Enter API URL Path first')
} else if (!apiKey) {
enterTip = t('Enter API Key first')
hapticError()
toast('warning', t('Warning'), t('Please enter the API URL first'), () =>
navigation.push('Settings')
)
return false
}
if (!apiUrlPath) {
hapticError()
toast('warning', t('Warning'), t('Please enter the API URL Path first'), () =>
navigation.push('Settings')
)
return false
}
if (enterTip) {
if (!apiKey) {
hapticError()
toast('warning', t('Warning'), enterTip, () => navigation.push('Settings'))
toast('warning', t('Warning'), t('Please enter the API Key first'), () => {
navigation.push('ApiKeys')
})
return false
}
return true
Expand Down
54 changes: 54 additions & 0 deletions src/http/apis/v1/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
interface OpenAIModels {
object: string
data: OpenAIModel[]
}

interface OpenAIModel {
id: string
object: string
created: number
owned_by: string
permission: OpenAIModelPermission[]
root: string
parent?: any
}

interface OpenAIModelPermission {
id: string
object: string
created: number
allow_create_engine: boolean
allow_sampling: boolean
allow_logprobs: boolean
allow_search_indices: boolean
allow_view: boolean
allow_fine_tuning: boolean
organization: string
group?: any
is_blocking: boolean
}

interface OpenAIModelsError {
message: string
type: string
param?: any
code: string
}

export async function requestOpenAIModels(apiKey: string): Promise<OpenAIModels> {
try {
const resp = await fetch('https://api.openai.com/v1/models', {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
},
})
const result = await resp.json()
if (result.error) {
return Promise.reject(result.error as OpenAIModelsError)
}
return result
} catch (e) {
return Promise.reject(e)
}
}
6 changes: 3 additions & 3 deletions src/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@
"Copied to clipboard": "Copied to clipboard",
"Country Not Supported": "Your IP address is from {{name}}, which is not a supported region of OpenAI. Continuing to use without extra network configuration may result in your account being blocked by OpenAI, regardless of your ChatGPT Plus subscription status or any remaining account balance. >>",
"Country Not Detected": "We were unable to check if your IP address is in a supported region of OpenAI. Please check your Internet connection, and ensure that you are accessing the API in a supported region of OpenAI, or your account may get banned regardless of your GPT Plus subscription status or any remaining account balance. >>",
"Enter API URL first": "Please enter the API URL first",
"Enter API URL Path first": "Please enter the API URL Path first",
"Enter API Key first": "Please enter the API Key first",
"Please enter the API URL first": "Please enter the API URL first",
"Please enter the API URL Path first": "Please enter the API URL Path first",
"Please enter the API Key first": "Please enter the API Key first",
"Warning": "Warning",
"Text recognition seems not stable on Android": "Text recognition seems not stable on Android",
"ChatMessageClearWarning": "The messages in this chat will be permanently deleted and cannot be recovered, are you sure to clear them ?",
Expand Down
6 changes: 3 additions & 3 deletions src/i18n/zh-Hans/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@
"Copied to clipboard": "已复制到剪贴板",
"Country Not Supported": "您的 IP 属地为 {{name}},不在 OpenAI 支持的地区范围内。如果不进行额外的网络配置继续使用,即使订阅了 ChatGPT Plus 或账号内仍有余额,您的账户也会被 OpenAI 封禁。 >>",
"Country Not Detected": "我们无法检查您的 IP 地址是否位于 OpenAI 的支持的地区,可能是由于您的网络环境无法访问 OpenAI。请检查您的网络连接,并确保您在支持的位置访问 API,否则即使订阅了 ChatGPT Plus 或账号内仍有余额,您的账户也会被 OpenAI 封禁。 >>",
"Enter API URL first": "请先输入 API 网址",
"Enter API URL Path first": "请先输入 API 网址路径",
"Enter API Key first": "请先输入 API 密钥",
"Please enter the API URL first": "请先输入 API 网址",
"Please enter the API URL Path first": "请先输入 API 网址路径",
"Please enter the API Key first": "请先输入 API 密钥",
"Warning": "警告",
"Text recognition seems not stable on Android": "文本识别在 Android 上似乎不太稳定",
"ChatMessageClearWarning": "该对话中的消息将被永久删除且不可恢复,确定清空?",
Expand Down
8 changes: 8 additions & 0 deletions src/screens/AppContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { colors } from '../res/colors'
import { useThemeDark } from '../themes/hooks'
import { TemplateScreen } from './_template'
import { ApiKeysScreen } from './api-keys'
import { AwesomePromptsScreen } from './awesome-prompts'
import { CustomChatScreen } from './custom-chat'
import { CustomChatInitScreen } from './custom-chat-init'
Expand Down Expand Up @@ -72,6 +73,13 @@ export function AppContent(): JSX.Element {
<RootStack.Screen name="ShareChat" component={ShareChatScreen} />
<RootStack.Screen name="Web" component={WebScreen} />
<RootStack.Screen name="PDF" component={PDFScreen} />
<RootStack.Screen
name="ApiKeys"
component={ApiKeysScreen}
options={{
animation: 'slide_from_bottom',
}}
/>
</RootStack.Navigator>
</AlertNotificationRoot>
)
Expand Down
143 changes: 143 additions & 0 deletions src/screens/api-keys/BottomInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { SvgIcon } from '../../components/SvgIcon'
import { colors } from '../../res/colors'
import { dimensions } from '../../res/dimensions'
import React, { useEffect, useRef, useState } from 'react'
import {
Pressable,
StyleProp,
StyleSheet,
TextInput,
TextStyle,
View,
ViewStyle,
} from 'react-native'
import Animated, {
Extrapolation,
interpolate,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { useSafeAreaFrame } from 'react-native-safe-area-context'

const HEIGHT = 64
const TEXT_LEFT = 16

export type BottomInputProps = {
style?: StyleProp<ViewStyle>
index: number
value: string
verified: boolean | undefined
verifying: boolean
onChangeText: (value: string) => void
}

export function BottomInput(props: BottomInputProps) {
const { style, index, value, verified, verifying, onChangeText } = props

const { width: frameWidth } = useSafeAreaFrame()
const inputWidth = frameWidth - dimensions.edgeTwice * 2

const anim = useSharedValue(0)
const inputAnimStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: interpolate(anim.value, [0, 1], [4, 0], Extrapolation.CLAMP),
},
],
}
})
const iconAnimStyle = useAnimatedStyle(() => {
return {
transform: [
{
rotate: `-${anim.value * 90}deg`,
},
],
}
})

let color = colors.black
let borderColor = colors.transparent
if (verified === true) {
color = colors.in
borderColor = colors.in
} else if (verified === false) {
color = colors.out
borderColor = colors.out
}

const inputRef = useRef<TextInput>(null)
const [focused, setFocused] = useState(false)
const inputing = focused || value ? true : false
useEffect(() => {
anim.value = withTiming(inputing ? 1 : 0)
}, [inputing])

return (
<View style={[styles.container, { width: inputWidth, borderColor }, style]}>
<Animated.View style={inputAnimStyle}>
<TextInput
ref={inputRef}
style={styles.text}
multiline={true}
numberOfLines={2}
value={value}
placeholder={` ${index + 1}th API Key`}
returnKeyType="done"
blurOnSubmit={true}
onChangeText={onChangeText}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
/>
</Animated.View>
<Pressable
style={[StyleSheet.absoluteFill, styles.rowCenter]}
pointerEvents={focused ? 'none' : 'auto'}
hitSlop={dimensions.hitSlop}
onPress={() => {
if (verifying) {
return
}
inputRef.current?.focus()
}}>
<Animated.View style={iconAnimStyle}>
<SvgIcon size={dimensions.iconMedium} color={color} name="vpn-key" />
</Animated.View>
</Pressable>
</View>
)
}

type Styles = {
container: ViewStyle
text: TextStyle
rowCenter: ViewStyle
}

const styles = StyleSheet.create<Styles>({
container: {
flexDirection: 'row',
height: HEIGHT,
alignItems: 'center',
marginBottom: dimensions.edge,
paddingHorizontal: dimensions.edge,
borderWidth: 1,
borderRadius: dimensions.borderRadius,
backgroundColor: colors.white,
},
text: {
flex: 1,
fontSize: 15,
lineHeight: 21,
marginLeft: TEXT_LEFT,
},
rowCenter: {
flexDirection: 'row',
height: HEIGHT,
alignItems: 'center',
paddingLeft: 8,
// backgroundColor: 'green',
},
})
Loading

0 comments on commit 354218f

Please sign in to comment.