|
1 | 1 | import { useTranslation } from 'react-i18next'
|
2 | 2 | import PropTypes from 'prop-types'
|
3 |
| -import { ModelMode, Models } from '../../config/index.mjs' |
| 3 | +import { apiModeToModelName, modelNameToDesc } from '../../utils/index.mjs' |
| 4 | +import { PencilIcon, TrashIcon } from '@primer/octicons-react' |
| 5 | +import { useState } from 'react' |
| 6 | +import { |
| 7 | + AlwaysCustomGroups, |
| 8 | + CustomApiKeyGroups, |
| 9 | + CustomUrlGroups, |
| 10 | + ModelGroups, |
| 11 | +} from '../../config/index.mjs' |
4 | 12 |
|
5 | 13 | ApiModes.propTypes = {
|
6 | 14 | config: PropTypes.object.isRequired,
|
7 | 15 | updateConfig: PropTypes.func.isRequired,
|
8 | 16 | }
|
9 | 17 |
|
| 18 | +const defaultApiMode = { |
| 19 | + groupName: 'chatgptWebModelKeys', |
| 20 | + itemName: 'chatgptFree35', |
| 21 | + isCustom: false, |
| 22 | + customName: '', |
| 23 | + customUrl: '', |
| 24 | + apiKey: '', |
| 25 | + active: true, |
| 26 | +} |
| 27 | + |
10 | 28 | export function ApiModes({ config, updateConfig }) {
|
11 | 29 | const { t } = useTranslation()
|
| 30 | + const [editing, setEditing] = useState(false) |
| 31 | + const [editingApiMode, setEditingApiMode] = useState(defaultApiMode) |
| 32 | + const [editingIndex, setEditingIndex] = useState(-1) |
| 33 | + |
| 34 | + const editingComponent = ( |
| 35 | + <div style={{ display: 'flex', flexDirection: 'column', '--spacing': '4px' }}> |
| 36 | + <div style={{ display: 'flex', gap: '12px' }}> |
| 37 | + <button |
| 38 | + onClick={(e) => { |
| 39 | + e.preventDefault() |
| 40 | + setEditing(false) |
| 41 | + }} |
| 42 | + > |
| 43 | + {t('Cancel')} |
| 44 | + </button> |
| 45 | + <button |
| 46 | + onClick={(e) => { |
| 47 | + e.preventDefault() |
| 48 | + if (editingIndex === -1) { |
| 49 | + updateConfig({ |
| 50 | + customApiModes: [...config.customApiModes, editingApiMode], |
| 51 | + }) |
| 52 | + } else { |
| 53 | + const customApiModes = [...config.customApiModes] |
| 54 | + customApiModes[editingIndex] = editingApiMode |
| 55 | + updateConfig({ customApiModes }) |
| 56 | + } |
| 57 | + setEditing(false) |
| 58 | + }} |
| 59 | + > |
| 60 | + {t('Save')} |
| 61 | + </button> |
| 62 | + </div> |
| 63 | + <div style={{ display: 'flex', gap: '4px', alignItems: 'center', whiteSpace: 'noWrap' }}> |
| 64 | + {t('Type')} |
| 65 | + <select |
| 66 | + value={editingApiMode.groupName} |
| 67 | + onChange={(e) => { |
| 68 | + const groupName = e.target.value |
| 69 | + const itemName = ModelGroups[groupName].value[0] |
| 70 | + setEditingApiMode({ ...editingApiMode, groupName, itemName }) |
| 71 | + }} |
| 72 | + > |
| 73 | + {Object.entries(ModelGroups).map(([groupName, { desc }]) => ( |
| 74 | + <option key={groupName} value={groupName}> |
| 75 | + {t(desc)} |
| 76 | + </option> |
| 77 | + ))} |
| 78 | + </select> |
| 79 | + </div> |
| 80 | + <div style={{ display: 'flex', gap: '4px', alignItems: 'center', whiteSpace: 'noWrap' }}> |
| 81 | + {t('Mode')} |
| 82 | + <select |
| 83 | + value={editingApiMode.itemName} |
| 84 | + onChange={(e) => { |
| 85 | + const itemName = e.target.value |
| 86 | + const isCustom = itemName === 'custom' |
| 87 | + setEditingApiMode({ ...editingApiMode, itemName, isCustom }) |
| 88 | + }} |
| 89 | + > |
| 90 | + {ModelGroups[editingApiMode.groupName].value.map((itemName) => ( |
| 91 | + <option key={itemName} value={itemName}> |
| 92 | + {modelNameToDesc(itemName, t)} |
| 93 | + </option> |
| 94 | + ))} |
| 95 | + {!AlwaysCustomGroups.includes(editingApiMode.groupName) && ( |
| 96 | + <option value="custom">{t('Custom')}</option> |
| 97 | + )} |
| 98 | + </select> |
| 99 | + {(editingApiMode.isCustom || AlwaysCustomGroups.includes(editingApiMode.groupName)) && ( |
| 100 | + <input |
| 101 | + type="text" |
| 102 | + value={editingApiMode.customName} |
| 103 | + placeholder={t('Model Name')} |
| 104 | + onChange={(e) => setEditingApiMode({ ...editingApiMode, customName: e.target.value })} |
| 105 | + /> |
| 106 | + )} |
| 107 | + </div> |
| 108 | + {CustomUrlGroups.includes(editingApiMode.groupName) && |
| 109 | + (editingApiMode.isCustom || AlwaysCustomGroups.includes(editingApiMode.groupName)) && ( |
| 110 | + <input |
| 111 | + type="text" |
| 112 | + value={editingApiMode.customUrl} |
| 113 | + placeholder={t('API Url')} |
| 114 | + onChange={(e) => setEditingApiMode({ ...editingApiMode, customUrl: e.target.value })} |
| 115 | + /> |
| 116 | + )} |
| 117 | + {CustomApiKeyGroups.includes(editingApiMode.groupName) && |
| 118 | + (editingApiMode.isCustom || AlwaysCustomGroups.includes(editingApiMode.groupName)) && ( |
| 119 | + <input |
| 120 | + type="password" |
| 121 | + value={editingApiMode.apiKey} |
| 122 | + placeholder={t('API Key')} |
| 123 | + onChange={(e) => setEditingApiMode({ ...editingApiMode, apiKey: e.target.value })} |
| 124 | + /> |
| 125 | + )} |
| 126 | + </div> |
| 127 | + ) |
12 | 128 |
|
13 | 129 | return (
|
14 | 130 | <>
|
15 |
| - {config.apiModes.map((modelName) => { |
16 |
| - let desc |
17 |
| - if (modelName.includes('-')) { |
18 |
| - const splits = modelName.split('-') |
19 |
| - if (splits[0] in Models) |
20 |
| - desc = `${t(Models[splits[0]].desc)} (${t(ModelMode[splits[1]])})` |
21 |
| - } else { |
22 |
| - if (modelName in Models) desc = t(Models[modelName].desc) |
23 |
| - } |
24 |
| - if (desc) |
25 |
| - return ( |
26 |
| - <label key={modelName}> |
| 131 | + {config.customApiModes.map( |
| 132 | + (apiMode, index) => |
| 133 | + apiMode.groupName && |
| 134 | + apiMode.itemName && |
| 135 | + (editing && editingIndex === index ? ( |
| 136 | + editingComponent |
| 137 | + ) : ( |
| 138 | + <label key={index} style={{ display: 'flex', alignItems: 'center' }}> |
27 | 139 | <input
|
28 | 140 | type="checkbox"
|
29 |
| - checked={config.activeApiModes.includes(modelName)} |
| 141 | + checked={apiMode.active} |
30 | 142 | onChange={(e) => {
|
31 |
| - const checked = e.target.checked |
32 |
| - const activeApiModes = config.activeApiModes.filter((i) => i !== modelName) |
33 |
| - if (checked) activeApiModes.push(modelName) |
34 |
| - updateConfig({ activeApiModes }) |
| 143 | + const customApiModes = [...config.customApiModes] |
| 144 | + customApiModes[index] = { ...apiMode, active: e.target.checked } |
| 145 | + updateConfig({ customApiModes }) |
35 | 146 | }}
|
36 | 147 | />
|
37 |
| - {desc} |
| 148 | + {modelNameToDesc(apiModeToModelName(apiMode), t)} |
| 149 | + <div style={{ flexGrow: 1 }} /> |
| 150 | + <div style={{ display: 'flex', gap: '12px' }}> |
| 151 | + <div |
| 152 | + style={{ cursor: 'pointer' }} |
| 153 | + onClick={(e) => { |
| 154 | + e.preventDefault() |
| 155 | + setEditing(true) |
| 156 | + setEditingApiMode(apiMode) |
| 157 | + setEditingIndex(index) |
| 158 | + }} |
| 159 | + > |
| 160 | + <PencilIcon /> |
| 161 | + </div> |
| 162 | + <div |
| 163 | + style={{ cursor: 'pointer' }} |
| 164 | + onClick={(e) => { |
| 165 | + e.preventDefault() |
| 166 | + const customApiModes = [...config.customApiModes] |
| 167 | + customApiModes.splice(index, 1) |
| 168 | + updateConfig({ customApiModes }) |
| 169 | + }} |
| 170 | + > |
| 171 | + <TrashIcon /> |
| 172 | + </div> |
| 173 | + </div> |
38 | 174 | </label>
|
39 |
| - ) |
40 |
| - })} |
| 175 | + )), |
| 176 | + )} |
| 177 | + <div style={{ height: '30px' }} /> |
| 178 | + {editing ? ( |
| 179 | + editingIndex === -1 ? ( |
| 180 | + editingComponent |
| 181 | + ) : undefined |
| 182 | + ) : ( |
| 183 | + <button |
| 184 | + onClick={(e) => { |
| 185 | + e.preventDefault() |
| 186 | + setEditing(true) |
| 187 | + setEditingApiMode(defaultApiMode) |
| 188 | + setEditingIndex(-1) |
| 189 | + }} |
| 190 | + > |
| 191 | + {t('New')} |
| 192 | + </button> |
| 193 | + )} |
41 | 194 | </>
|
42 | 195 | )
|
43 | 196 | }
|
0 commit comments