Skip to content

Commit

Permalink
WIP, custom API Modes
Browse files Browse the repository at this point in the history
  • Loading branch information
josStorer committed Aug 6, 2024
1 parent 34cfb5c commit e4cd838
Show file tree
Hide file tree
Showing 16 changed files with 368 additions and 59 deletions.
19 changes: 9 additions & 10 deletions src/components/ConversationCard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import PropTypes from 'prop-types'
import Browser from 'webextension-polyfill'
import InputBox from '../InputBox'
import ConversationItem from '../ConversationItem'
import { createElementAtPosition, isFirefox, isMobile, isSafari } from '../../utils'
import {
createElementAtPosition,
isFirefox,
isMobile,
isSafari,
modelNameToDesc,
} from '../../utils'
import {
ArchiveIcon,
DesktopDownloadIcon,
Expand All @@ -16,7 +22,7 @@ import FileSaver from 'file-saver'
import { render } from 'preact'
import FloatingToolbar from '../FloatingToolbar'
import { useClampWindowSize } from '../../hooks/use-clamp-window-size'
import { bingWebModelKeys, getUserConfig, ModelMode, Models } from '../../config/index.mjs'
import { bingWebModelKeys, getUserConfig, Models } from '../../config/index.mjs'
import { useTranslation } from 'react-i18next'
import DeleteButton from '../DeleteButton'
import { useConfig } from '../../hooks/use-config.mjs'
Expand Down Expand Up @@ -370,14 +376,7 @@ function ConversationCard(props) {
}}
>
{config.activeApiModes.map((modelName) => {
let desc
if (modelName.includes('-')) {
const splits = modelName.split('-')
if (splits[0] in Models)
desc = `${t(Models[splits[0]].desc)} (${t(ModelMode[splits[1]])})`
} else {
if (modelName in Models) desc = t(Models[modelName].desc)
}
const desc = modelNameToDesc(modelName, t)
if (desc)
return (
<option
Expand Down
78 changes: 78 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,73 @@ export const poeWebModelKeys = [
]
export const moonshotApiModelKeys = ['moonshot_v1_8k', 'moonshot_v1_32k', 'moonshot_v1_128k']

export const AlwaysCustomGroups = [
'ollamaApiModelKeys',
'customApiModelKeys',
'azureOpenAiApiModelKeys',
]
export const CustomUrlGroups = ['customApiModelKeys']
export const CustomApiKeyGroups = ['customApiModelKeys']
export const ModelGroups = {
chatgptWebModelKeys: {
value: chatgptWebModelKeys,
desc: 'ChatGPT (Web)',
},
claudeWebModelKeys: {
value: claudeWebModelKeys,
desc: 'Claude.ai (Web)',
},
moonshotWebModelKeys: {
value: moonshotWebModelKeys,
desc: 'Kimi.Moonshot (Web)',
},
bingWebModelKeys: {
value: bingWebModelKeys,
desc: 'Bing (Web)',
},
bardWebModelKeys: {
value: bardWebModelKeys,
desc: 'Gemini (Web)',
},

chatgptApiModelKeys: {
value: chatgptApiModelKeys,
desc: 'ChatGPT (API)',
},
claudeApiModelKeys: {
value: claudeApiModelKeys,
desc: 'Claude.ai (API)',
},
moonshotApiModelKeys: {
value: moonshotApiModelKeys,
desc: 'Kimi.Moonshot (API)',
},
chatglmApiModelKeys: {
value: chatglmApiModelKeys,
desc: 'ChatGLM (API)',
},
ollamaApiModelKeys: {
value: ollamaApiModelKeys,
desc: 'Ollama (API)',
},
azureOpenAiApiModelKeys: {
value: azureOpenAiApiModelKeys,
desc: 'ChatGPT (Azure API)',
},
gptApiModelKeys: {
value: gptApiModelKeys,
desc: 'GPT Completion (API)',
},
githubThirdPartyApiModelKeys: {
value: githubThirdPartyApiModelKeys,
desc: 'Github Third Party Waylaidwanderer (API)',
},
customApiModelKeys: {
value: customApiModelKeys,
desc: 'Custom Model',
},
}

/**
* @typedef {object} Model
* @property {string} value
Expand Down Expand Up @@ -290,6 +357,17 @@ export const defaultConfig = {
'ollamaModel',
'azureOpenAi',
],
customApiModes: [
{
groupName: '',
itemName: '',
isCustom: false,
customName: '',
customUrl: '',
apiKey: '',
active: false,
},
],
activeSelectionTools: ['translate', 'summary', 'polish', 'code', 'ask'],
customSelectionTools: [
{
Expand Down
195 changes: 174 additions & 21 deletions src/popup/sections/ApiModes.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,196 @@
import { useTranslation } from 'react-i18next'
import PropTypes from 'prop-types'
import { ModelMode, Models } from '../../config/index.mjs'
import { apiModeToModelName, modelNameToDesc } from '../../utils/index.mjs'
import { PencilIcon, TrashIcon } from '@primer/octicons-react'
import { useState } from 'react'
import {
AlwaysCustomGroups,
CustomApiKeyGroups,
CustomUrlGroups,
ModelGroups,
} from '../../config/index.mjs'

ApiModes.propTypes = {
config: PropTypes.object.isRequired,
updateConfig: PropTypes.func.isRequired,
}

const defaultApiMode = {
groupName: 'chatgptWebModelKeys',
itemName: 'chatgptFree35',
isCustom: false,
customName: '',
customUrl: '',
apiKey: '',
active: true,
}

export function ApiModes({ config, updateConfig }) {
const { t } = useTranslation()
const [editing, setEditing] = useState(false)
const [editingApiMode, setEditingApiMode] = useState(defaultApiMode)
const [editingIndex, setEditingIndex] = useState(-1)

const editingComponent = (
<div style={{ display: 'flex', flexDirection: 'column', '--spacing': '4px' }}>
<div style={{ display: 'flex', gap: '12px' }}>
<button
onClick={(e) => {
e.preventDefault()
setEditing(false)
}}
>
{t('Cancel')}
</button>
<button
onClick={(e) => {
e.preventDefault()
if (editingIndex === -1) {
updateConfig({
customApiModes: [...config.customApiModes, editingApiMode],
})
} else {
const customApiModes = [...config.customApiModes]
customApiModes[editingIndex] = editingApiMode
updateConfig({ customApiModes })
}
setEditing(false)
}}
>
{t('Save')}
</button>
</div>
<div style={{ display: 'flex', gap: '4px', alignItems: 'center', whiteSpace: 'noWrap' }}>
{t('Type')}
<select
value={editingApiMode.groupName}
onChange={(e) => {
const groupName = e.target.value
const itemName = ModelGroups[groupName].value[0]
setEditingApiMode({ ...editingApiMode, groupName, itemName })
}}
>
{Object.entries(ModelGroups).map(([groupName, { desc }]) => (
<option key={groupName} value={groupName}>
{t(desc)}
</option>
))}
</select>
</div>
<div style={{ display: 'flex', gap: '4px', alignItems: 'center', whiteSpace: 'noWrap' }}>
{t('Mode')}
<select
value={editingApiMode.itemName}
onChange={(e) => {
const itemName = e.target.value
const isCustom = itemName === 'custom'
setEditingApiMode({ ...editingApiMode, itemName, isCustom })
}}
>
{ModelGroups[editingApiMode.groupName].value.map((itemName) => (
<option key={itemName} value={itemName}>
{modelNameToDesc(itemName, t)}
</option>
))}
{!AlwaysCustomGroups.includes(editingApiMode.groupName) && (
<option value="custom">{t('Custom')}</option>
)}
</select>
{(editingApiMode.isCustom || AlwaysCustomGroups.includes(editingApiMode.groupName)) && (
<input
type="text"
value={editingApiMode.customName}
placeholder={t('Model Name')}
onChange={(e) => setEditingApiMode({ ...editingApiMode, customName: e.target.value })}
/>
)}
</div>
{CustomUrlGroups.includes(editingApiMode.groupName) &&
(editingApiMode.isCustom || AlwaysCustomGroups.includes(editingApiMode.groupName)) && (
<input
type="text"
value={editingApiMode.customUrl}
placeholder={t('API Url')}
onChange={(e) => setEditingApiMode({ ...editingApiMode, customUrl: e.target.value })}
/>
)}
{CustomApiKeyGroups.includes(editingApiMode.groupName) &&
(editingApiMode.isCustom || AlwaysCustomGroups.includes(editingApiMode.groupName)) && (
<input
type="password"
value={editingApiMode.apiKey}
placeholder={t('API Key')}
onChange={(e) => setEditingApiMode({ ...editingApiMode, apiKey: e.target.value })}
/>
)}
</div>
)

return (
<>
{config.apiModes.map((modelName) => {
let desc
if (modelName.includes('-')) {
const splits = modelName.split('-')
if (splits[0] in Models)
desc = `${t(Models[splits[0]].desc)} (${t(ModelMode[splits[1]])})`
} else {
if (modelName in Models) desc = t(Models[modelName].desc)
}
if (desc)
return (
<label key={modelName}>
{config.customApiModes.map(
(apiMode, index) =>
apiMode.groupName &&
apiMode.itemName &&
(editing && editingIndex === index ? (
editingComponent
) : (
<label key={index} style={{ display: 'flex', alignItems: 'center' }}>
<input
type="checkbox"
checked={config.activeApiModes.includes(modelName)}
checked={apiMode.active}
onChange={(e) => {
const checked = e.target.checked
const activeApiModes = config.activeApiModes.filter((i) => i !== modelName)
if (checked) activeApiModes.push(modelName)
updateConfig({ activeApiModes })
const customApiModes = [...config.customApiModes]
customApiModes[index] = { ...apiMode, active: e.target.checked }
updateConfig({ customApiModes })
}}
/>
{desc}
{modelNameToDesc(apiModeToModelName(apiMode), t)}
<div style={{ flexGrow: 1 }} />
<div style={{ display: 'flex', gap: '12px' }}>
<div
style={{ cursor: 'pointer' }}
onClick={(e) => {
e.preventDefault()
setEditing(true)
setEditingApiMode(apiMode)
setEditingIndex(index)
}}
>
<PencilIcon />
</div>
<div
style={{ cursor: 'pointer' }}
onClick={(e) => {
e.preventDefault()
const customApiModes = [...config.customApiModes]
customApiModes.splice(index, 1)
updateConfig({ customApiModes })
}}
>
<TrashIcon />
</div>
</div>
</label>
)
})}
)),
)}
<div style={{ height: '30px' }} />
{editing ? (
editingIndex === -1 ? (
editingComponent
) : undefined
) : (
<button
onClick={(e) => {
e.preventDefault()
setEditing(true)
setEditingApiMode(defaultApiMode)
setEditingIndex(-1)
}}
>
{t('New')}
</button>
)}
</>
)
}
12 changes: 2 additions & 10 deletions src/popup/sections/GeneralPart.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next'
import { useState } from 'react'
import FileSaver from 'file-saver'
import { openUrl } from '../../utils/index.mjs'
import { openUrl, modelNameToDesc } from '../../utils/index.mjs'
import {
isUsingOpenAiApiKey,
isUsingAzureOpenAi,
Expand All @@ -13,7 +13,6 @@ import {
isUsingGithubThirdPartyApi,
isUsingMultiModeModel,
ModelMode,
Models,
ThemeMode,
TriggerMode,
isUsingMoonshotApi,
Expand Down Expand Up @@ -161,14 +160,7 @@ export function GeneralPart({ config, updateConfig }) {
}}
>
{config.activeApiModes.map((modelName) => {
let desc
if (modelName.includes('-')) {
const splits = modelName.split('-')
if (splits[0] in Models)
desc = `${t(Models[splits[0]].desc)} (${t(ModelMode[splits[1]])})`
} else {
if (modelName in Models) desc = t(Models[modelName].desc)
}
const desc = modelNameToDesc(modelName, t)
if (desc)
return (
<option
Expand Down
2 changes: 1 addition & 1 deletion src/popup/sections/SelectionTools.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function SelectionTools({ config, updateConfig }) {
</button>
</div>
{errorMessage && <div style={{ color: 'red' }}>{errorMessage}</div>}
<div style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
<div style={{ display: 'flex', gap: '4px', alignItems: 'center', whiteSpace: 'noWrap' }}>
{t('Name')}
<input
type="text"
Expand Down
Loading

0 comments on commit e4cd838

Please sign in to comment.