mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-22 17:00:14 +08:00
feat(CustomHeaderPopup): add custom headers management for providers
- Introduced a new CustomHeaderPopup component for managing extra headers for providers. - Integrated the popup into the ProviderSetting component, allowing users to edit headers via a modal. - Refactored ApiKeyListPopup to use a styled container for improved layout.
This commit is contained in:
parent
e273ddcfb0
commit
f7fa665f3a
@ -10,6 +10,7 @@ import { Button, Card, Flex, List, Popconfirm, Space, Tooltip, Typography } from
|
||||
import { Trash } from 'lucide-react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { isLlmProvider, useApiKeys } from './hook'
|
||||
import ApiKeyItem from './item'
|
||||
@ -87,7 +88,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
|
||||
: keys
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListContainer>
|
||||
{/* Keys 列表 */}
|
||||
<Card
|
||||
size="small"
|
||||
@ -122,7 +123,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Flex align="center" justify="space-between" style={{ marginTop: '0.5rem' }}>
|
||||
<Flex dir="row" align="center" justify="space-between" style={{ marginTop: 15 }}>
|
||||
{/* 帮助文本 */}
|
||||
<SettingHelpText>{t('settings.provider.api_key.tip')}</SettingHelpText>
|
||||
|
||||
@ -166,7 +167,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
|
||||
</Button>
|
||||
</Space>
|
||||
</Flex>
|
||||
</>
|
||||
</ListContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -222,3 +223,8 @@ export const DocPreprocessApiKeyList: FC<SpecificApiKeyListProps> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ListContainer = styled.div`
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
`
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
import CodeEditor from '@renderer/components/CodeEditor'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import { Provider } from '@renderer/types'
|
||||
import { Modal, Space } from 'antd'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingHelpText } from '..'
|
||||
|
||||
interface ShowParams {
|
||||
provider: Provider
|
||||
}
|
||||
|
||||
interface Props extends ShowParams {
|
||||
resolve: (data: any) => void
|
||||
}
|
||||
|
||||
const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
const { updateProvider } = useProvider(provider.id)
|
||||
const [headerText, setHeaderText] = useState<string>(JSON.stringify(provider.extra_headers || {}, null, 2))
|
||||
|
||||
const onUpdateHeaders = useCallback(() => {
|
||||
try {
|
||||
const headers = headerText.trim() ? JSON.parse(headerText) : {}
|
||||
updateProvider({ ...provider, extra_headers: headers })
|
||||
window.message.success({ content: t('message.save.success.title') })
|
||||
} catch (error) {
|
||||
window.message.error({ content: t('settings.provider.copilot.invalid_json') })
|
||||
}
|
||||
}, [headerText, provider, updateProvider, t])
|
||||
|
||||
const onOk = () => {
|
||||
onUpdateHeaders()
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
resolve({})
|
||||
}
|
||||
|
||||
CustomHeaderPopup.hide = onCancel
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('settings.provider.copilot.custom_headers')}
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Space.Compact direction="vertical" style={{ width: '100%', marginTop: 5 }}>
|
||||
<SettingHelpText>{t('settings.provider.copilot.headers_description')}</SettingHelpText>
|
||||
<CodeEditor
|
||||
value={headerText}
|
||||
language="json"
|
||||
onChange={(value) => setHeaderText(value)}
|
||||
placeholder={`{\n "Header-Name": "Header-Value"\n}`}
|
||||
options={{
|
||||
lint: true,
|
||||
collapsible: false,
|
||||
wrappable: true,
|
||||
lineNumbers: true,
|
||||
foldGutter: true,
|
||||
highlightActiveLine: true,
|
||||
keymap: true
|
||||
}}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const TopViewKey = 'CustomHeaderPopup'
|
||||
|
||||
export default class CustomHeaderPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
TopView.hide(TopViewKey)
|
||||
}}
|
||||
/>,
|
||||
TopViewKey
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { CheckOutlined, CloseCircleFilled, LoadingOutlined } from '@ant-design/icons'
|
||||
import { isOpenAIProvider } from '@renderer/aiCore/clients/ApiClientFactory'
|
||||
import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert'
|
||||
import CodeEditor from '@renderer/components/CodeEditor'
|
||||
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { ApiKeyConnectivity, ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup'
|
||||
@ -19,7 +18,7 @@ import { lightbulbVariants } from '@renderer/utils/motionVariants'
|
||||
import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd'
|
||||
import Link from 'antd/es/typography/Link'
|
||||
import { debounce, isEmpty } from 'lodash'
|
||||
import { List, Settings2, SquareArrowOutUpRight } from 'lucide-react'
|
||||
import { Settings2, SquareArrowOutUpRight } from 'lucide-react'
|
||||
import { motion } from 'motion/react'
|
||||
import { FC, useCallback, useDeferredValue, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -33,6 +32,7 @@ import {
|
||||
SettingSubtitle,
|
||||
SettingTitle
|
||||
} from '..'
|
||||
import CustomHeaderPopup from './CustomHeaderPopup'
|
||||
import DMXAPISettings from './DMXAPISettings'
|
||||
import GithubCopilotSettings from './GithubCopilotSettings'
|
||||
import GPUStackSettings from './GPUStackSettings'
|
||||
@ -80,8 +80,6 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
checking: false
|
||||
})
|
||||
|
||||
const [headerText, setHeaderText] = useState<string>(JSON.stringify(provider.extra_headers || {}, null, 2))
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedUpdateApiKey = useCallback(
|
||||
debounce((value) => {
|
||||
@ -311,16 +309,6 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
setApiHost(provider.apiHost)
|
||||
}, [provider.apiHost, provider.id])
|
||||
|
||||
const onUpdateHeaders = useCallback(() => {
|
||||
try {
|
||||
const headers = headerText.trim() ? JSON.parse(headerText) : {}
|
||||
updateProvider({ ...provider, extra_headers: headers })
|
||||
window.message.success({ content: t('message.save.success.title') })
|
||||
} catch (error) {
|
||||
window.message.error({ content: t('settings.provider.copilot.invalid_json') })
|
||||
}
|
||||
}, [headerText, provider, updateProvider, t])
|
||||
|
||||
return (
|
||||
<SettingContainer theme={theme} style={{ background: 'var(--color-background)' }}>
|
||||
<SettingTitle>
|
||||
@ -367,7 +355,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
{t('settings.provider.api_key')}
|
||||
{provider.id !== 'copilot' && (
|
||||
<Tooltip title={t('settings.provider.api.key.list.open')} mouseEnterDelay={0.5}>
|
||||
<Button type="text" size="small" onClick={openApiKeyList} icon={<List size={14} />} />
|
||||
<Button type="text" size="small" onClick={openApiKeyList} icon={<Settings2 size={14} />} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</SettingSubtitle>
|
||||
@ -410,7 +398,15 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
)}
|
||||
{!isDmxapi && (
|
||||
<>
|
||||
<SettingSubtitle>{t('settings.provider.api_host')}</SettingSubtitle>
|
||||
<SettingSubtitle style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
{t('settings.provider.api_host')}
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() => CustomHeaderPopup.show({ provider })}
|
||||
icon={<Settings2 size={14} />}
|
||||
/>
|
||||
</SettingSubtitle>
|
||||
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
||||
<Input
|
||||
value={apiHost}
|
||||
@ -437,32 +433,6 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{provider.id !== 'copilot' && (
|
||||
<>
|
||||
<SettingSubtitle style={{ marginTop: 5 }}>
|
||||
{t('settings.provider.copilot.custom_headers')}
|
||||
</SettingSubtitle>
|
||||
<Space.Compact direction="vertical" style={{ width: '100%', marginTop: 5 }}>
|
||||
<SettingHelpText>{t('settings.provider.copilot.headers_description')}</SettingHelpText>
|
||||
<CodeEditor
|
||||
value={headerText}
|
||||
language="json"
|
||||
onChange={(value) => setHeaderText(value)}
|
||||
onBlur={onUpdateHeaders}
|
||||
placeholder={`{\n "Header-Name": "Header-Value"\n}`}
|
||||
options={{
|
||||
lint: true,
|
||||
collapsible: false,
|
||||
wrappable: true,
|
||||
lineNumbers: true,
|
||||
foldGutter: true,
|
||||
highlightActiveLine: true,
|
||||
keymap: true
|
||||
}}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isAzureOpenAI && (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user