style(ProviderSettings): Refactor ProviderSettings UI (#4475)
* chore(version): 1.1.19 * style(ProviderSettings): Refactor ProviderSettings UI * style(CustomTag, ModelTagsWithLabel): enhance layout and styling for better UI consistency * refactor(CustomTag, ModelTagsWithLabel, MentionModelsButton): update props handling and improve component usage * feat(CustomTag, ModelTagsWithLabel): add tooltip support and improve label visibility based on container size * fix(ModelTagsWithLabel): adjust maxWidth for non-Chinese languages to improve layout * style(ModelList): add text overflow handling for list item names * feat(ModelList): enhance group label with item count using CustomTag * feat(FileItem): add style prop for customizable background color in FileItem component * style(index.scss): update border color variables for improved UI consistency * style(EditModelsPopup): update background color for model items to enhance visual distinction * style(HealthCheckPopup): update button size for improved usability * feat(CustomCollapse): add collapsible prop to customize collapse behavior * chore: remove hover models color --------- Co-authored-by: kangfenmao <kangfenmao@qq.com> Co-authored-by: eeee0717 <chentao020717Work@outlook.com>
This commit is contained in:
parent
037027f1f4
commit
3aaa1848f0
@ -36,7 +36,7 @@
|
||||
--color-text: var(--color-text-1);
|
||||
--color-icon: #ffffff99;
|
||||
--color-icon-white: #ffffff;
|
||||
--color-border: #ffffff15;
|
||||
--color-border: #ffffff19;
|
||||
--color-border-soft: #ffffff10;
|
||||
--color-border-mute: #ffffff05;
|
||||
--color-error: #f44336;
|
||||
@ -80,7 +80,7 @@ body {
|
||||
|
||||
body[theme-mode='light'] {
|
||||
--color-white: #ffffff;
|
||||
--color-white-soft: #f2f2f2;
|
||||
--color-white-soft: rgba(0, 0, 0, 0.04);
|
||||
--color-white-mute: #eee;
|
||||
|
||||
--color-black: #1b1b1f;
|
||||
@ -108,7 +108,7 @@ body[theme-mode='light'] {
|
||||
--color-text: var(--color-text-1);
|
||||
--color-icon: #00000099;
|
||||
--color-icon-white: #000000;
|
||||
--color-border: #00000015;
|
||||
--color-border: #00000019;
|
||||
--color-border-soft: #00000010;
|
||||
--color-border-mute: #00000005;
|
||||
--color-error: #f44336;
|
||||
|
||||
@ -5,9 +5,19 @@ interface CustomCollapseProps {
|
||||
label: React.ReactNode
|
||||
extra: React.ReactNode
|
||||
children: React.ReactNode
|
||||
destroyInactivePanel?: boolean
|
||||
defaultActiveKey?: string[]
|
||||
collapsible?: 'header' | 'icon' | 'disabled'
|
||||
}
|
||||
|
||||
const CustomCollapse: FC<CustomCollapseProps> = ({ label, extra, children }) => {
|
||||
const CustomCollapse: FC<CustomCollapseProps> = ({
|
||||
label,
|
||||
extra,
|
||||
children,
|
||||
destroyInactivePanel = false,
|
||||
defaultActiveKey = ['1'],
|
||||
collapsible = undefined
|
||||
}) => {
|
||||
const CollapseStyle = {
|
||||
width: '100%',
|
||||
background: 'transparent',
|
||||
@ -27,7 +37,9 @@ const CustomCollapse: FC<CustomCollapseProps> = ({ label, extra, children }) =>
|
||||
<Collapse
|
||||
bordered={false}
|
||||
style={CollapseStyle}
|
||||
defaultActiveKey={['1']}
|
||||
defaultActiveKey={defaultActiveKey}
|
||||
destroyInactivePanel={destroyInactivePanel}
|
||||
collapsible={collapsible}
|
||||
items={[
|
||||
{
|
||||
styles: CollapseItemStyles,
|
||||
|
||||
40
src/renderer/src/components/CustomTag.tsx
Normal file
40
src/renderer/src/components/CustomTag.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { Tooltip } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface CustomTagProps {
|
||||
icon?: React.ReactNode
|
||||
children?: React.ReactNode | string
|
||||
color: string
|
||||
size?: number
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
const CustomTag: FC<CustomTagProps> = ({ children, icon, color, size = 12, tooltip }) => {
|
||||
return (
|
||||
<Tooltip title={tooltip} placement="top">
|
||||
<Tag $color={color} $size={size}>
|
||||
{icon && icon} {children}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomTag
|
||||
|
||||
const Tag = styled.div<{ $color: string; $size: number }>`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: ${({ $size }) => $size / 3}px ${({ $size }) => $size * 0.8}px;
|
||||
border-radius: 99px;
|
||||
color: ${({ $color }) => $color};
|
||||
background-color: ${({ $color }) => $color + '20'};
|
||||
font-size: ${({ $size }) => $size}px;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
.iconfont {
|
||||
font-size: ${({ $size }) => $size}px;
|
||||
color: ${({ $color }) => $color};
|
||||
}
|
||||
`
|
||||
13
src/renderer/src/components/Icons/SVGIcon.tsx
Normal file
13
src/renderer/src/components/Icons/SVGIcon.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
export const StreamlineGoodHealthAndWellBeing = (props: SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 14 14" {...props}>
|
||||
{/* Icon from Streamline by Streamline - https://creativecommons.org/licenses/by/4.0/ */}
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="m10.097 12.468l-2.773-2.52c-1.53-1.522.717-4.423 2.773-2.045c2.104-2.33 4.303.57 2.773 2.045z"></path>
|
||||
<path d="M.621 6.088h1.367l1.823 3.19l4.101-7.747l1.823 3.646"></path>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
113
src/renderer/src/components/ModelTagsWithLabel.tsx
Normal file
113
src/renderer/src/components/ModelTagsWithLabel.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { EyeOutlined, GlobalOutlined, ToolOutlined } from '@ant-design/icons'
|
||||
import {
|
||||
isEmbeddingModel,
|
||||
isFunctionCallingModel,
|
||||
isReasoningModel,
|
||||
isRerankModel,
|
||||
isVisionModel,
|
||||
isWebSearchModel
|
||||
} from '@renderer/config/models'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { Model } from '@renderer/types'
|
||||
import { isFreeModel } from '@renderer/utils'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import CustomTag from './CustomTag'
|
||||
|
||||
interface ModelTagsProps {
|
||||
model: Model
|
||||
showFree?: boolean
|
||||
showReasoning?: boolean
|
||||
showToolsCalling?: boolean
|
||||
size?: number
|
||||
showLabel?: boolean
|
||||
}
|
||||
|
||||
const ModelTagsWithLabel: FC<ModelTagsProps> = ({
|
||||
model,
|
||||
showFree = true,
|
||||
showReasoning = true,
|
||||
showToolsCalling = true,
|
||||
size = 12,
|
||||
showLabel = true
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [_showLabel, _setShowLabel] = useState(showLabel)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const resizeObserver = useRef<ResizeObserver>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showLabel) return
|
||||
|
||||
if (containerRef.current) {
|
||||
const currentElement = containerRef.current
|
||||
resizeObserver.current = new ResizeObserver((entries) => {
|
||||
const maxWidth = i18n.language.startsWith('zh') ? 300 : 350
|
||||
|
||||
for (const entry of entries) {
|
||||
const { width } = entry.contentRect
|
||||
_setShowLabel(width >= maxWidth)
|
||||
}
|
||||
})
|
||||
resizeObserver.current.observe(currentElement)
|
||||
|
||||
return () => {
|
||||
if (resizeObserver.current) {
|
||||
resizeObserver.current.unobserve(currentElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}, [showLabel])
|
||||
|
||||
return (
|
||||
<Container ref={containerRef}>
|
||||
{isVisionModel(model) && (
|
||||
<CustomTag size={size} color="#00b96b" icon={<EyeOutlined />} tooltip={t('models.type.vision')}>
|
||||
{_showLabel ? t('models.type.vision') : ''}
|
||||
</CustomTag>
|
||||
)}
|
||||
{isWebSearchModel(model) && (
|
||||
<CustomTag size={size} color="#1677ff" icon={<GlobalOutlined />} tooltip={t('models.type.websearch')}>
|
||||
{_showLabel ? t('models.type.websearch') : ''}
|
||||
</CustomTag>
|
||||
)}
|
||||
{showReasoning && isReasoningModel(model) && (
|
||||
<CustomTag
|
||||
size={size}
|
||||
color="#6372bd"
|
||||
icon={<i className="iconfont icon-thinking" />}
|
||||
tooltip={t('models.type.reasoning')}>
|
||||
{_showLabel ? t('models.type.reasoning') : ''}
|
||||
</CustomTag>
|
||||
)}
|
||||
{showToolsCalling && isFunctionCallingModel(model) && (
|
||||
<CustomTag size={size} color="#d45ea3" icon={<ToolOutlined />} tooltip={t('models.function_calling')}>
|
||||
{_showLabel ? t('models.function_calling') : ''}
|
||||
</CustomTag>
|
||||
)}
|
||||
{isEmbeddingModel(model) && (
|
||||
<CustomTag size={size} color="#FFA500" icon={t('models.type.embedding')} tooltip={t('models.type.embedding')} />
|
||||
)}
|
||||
{showFree && isFreeModel(model) && (
|
||||
<CustomTag size={size} color="#7cb305" icon={t('models.type.free')} tooltip={t('models.type.free')} />
|
||||
)}
|
||||
{isRerankModel(model) && (
|
||||
<CustomTag size={size} color="#6495ED" icon={t('models.type.rerank')} tooltip={t('models.type.rerank')} />
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
|
||||
export default ModelTagsWithLabel
|
||||
@ -18,11 +18,13 @@ import styled from 'styled-components'
|
||||
|
||||
interface FileItemProps {
|
||||
fileInfo: {
|
||||
icon?: React.ReactNode
|
||||
name: React.ReactNode | string
|
||||
ext: string
|
||||
extra?: React.ReactNode | string
|
||||
actions: React.ReactNode
|
||||
}
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
const getFileIcon = (type?: string) => {
|
||||
@ -73,18 +75,18 @@ const getFileIcon = (type?: string) => {
|
||||
return <FileUnknownFilled />
|
||||
}
|
||||
|
||||
const FileItem: React.FC<FileItemProps> = ({ fileInfo }) => {
|
||||
const { name, ext, extra, actions } = fileInfo
|
||||
const FileItem: React.FC<FileItemProps> = ({ fileInfo, style }) => {
|
||||
const { name, ext, extra, actions, icon } = fileInfo
|
||||
|
||||
return (
|
||||
<FileItemCard>
|
||||
<FileItemCard style={style}>
|
||||
<CardContent>
|
||||
<FileIcon>{getFileIcon(ext)}</FileIcon>
|
||||
<Flex vertical gap={0} flex={1} style={{ width: '0px' }}>
|
||||
<FileIcon>{icon || getFileIcon(ext)}</FileIcon>
|
||||
<Flex vertical justify="center" gap={0} flex={1} style={{ width: '0px' }}>
|
||||
<FileName>{name}</FileName>
|
||||
{extra && <FileInfo>{extra}</FileInfo>}
|
||||
</Flex>
|
||||
{actions}
|
||||
<FileActions>{actions}</FileActions>
|
||||
</CardContent>
|
||||
</FileItemCard>
|
||||
)
|
||||
@ -96,7 +98,9 @@ const FileItemCard = styled.div`
|
||||
overflow: hidden;
|
||||
border: 0.5px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
transition: box-shadow 0.2s ease;
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
--shadow-color: rgba(0, 0, 0, 0.05);
|
||||
&:hover {
|
||||
box-shadow:
|
||||
@ -109,15 +113,19 @@ const FileItemCard = styled.div`
|
||||
`
|
||||
|
||||
const CardContent = styled.div`
|
||||
padding: 8px 16px;
|
||||
padding: 8px 8px 8px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const FileIcon = styled.div`
|
||||
max-height: 44px;
|
||||
color: var(--color-text-3);
|
||||
font-size: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const FileName = styled.div`
|
||||
@ -140,4 +148,11 @@ const FileInfo = styled.div`
|
||||
color: var(--color-text-2);
|
||||
`
|
||||
|
||||
const FileActions = styled.div`
|
||||
max-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export default memo(FileItem)
|
||||
|
||||
@ -66,7 +66,7 @@ const FileList: React.FC<FileItemProps> = ({ id, list, files }) => {
|
||||
<VirtualList
|
||||
data={list}
|
||||
height={window.innerHeight - 100}
|
||||
itemHeight={80}
|
||||
itemHeight={75}
|
||||
itemKey="key"
|
||||
style={{ padding: '0 16px 16px 16px' }}
|
||||
styles={{
|
||||
@ -80,7 +80,7 @@ const FileList: React.FC<FileItemProps> = ({ id, list, files }) => {
|
||||
{(item) => (
|
||||
<div
|
||||
style={{
|
||||
height: '80px',
|
||||
height: '75px',
|
||||
paddingTop: '12px'
|
||||
}}>
|
||||
<FileItem
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import ModelTags from '@renderer/components/ModelTags'
|
||||
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
|
||||
import { useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import { QuickPanelListItem } from '@renderer/components/QuickPanel/types'
|
||||
import { getModelLogo, isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
@ -51,7 +51,7 @@ const MentionModelsButton: FC<Props> = ({ ref, mentionModels, onMentionModel, To
|
||||
.reverse()
|
||||
.map((item) => ({
|
||||
label: `${item.provider.isSystem ? t(`provider.${item.provider.id}`) : item.provider.name} | ${item.model.name}`,
|
||||
description: <ModelTags model={item.model} />,
|
||||
description: <ModelTagsWithLabel model={item.model} showLabel={false} size={10} />,
|
||||
icon: (
|
||||
<Avatar src={getModelLogo(item.model.id)} size={20}>
|
||||
{first(item.model.name)}
|
||||
|
||||
@ -493,7 +493,6 @@ const TopicListItem = styled.div`
|
||||
}
|
||||
.menu {
|
||||
opacity: 1;
|
||||
background-color: var(--color-background-soft);
|
||||
&:hover {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
SearchOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons'
|
||||
import CustomTag from '@renderer/components/CustomTag'
|
||||
import Ellipsis from '@renderer/components/Ellipsis'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||
@ -18,7 +19,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
|
||||
import { FileType, FileTypes, KnowledgeBase, KnowledgeItem } from '@renderer/types'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant'
|
||||
import { Alert, Button, Dropdown, Empty, message, Tag, Tooltip, Upload } from 'antd'
|
||||
import { Alert, Button, Dropdown, Empty, Flex, message, Tooltip, Upload } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import VirtualList from 'rc-virtual-list'
|
||||
import { FC } from 'react'
|
||||
@ -269,8 +270,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
) : (
|
||||
<VirtualList
|
||||
data={fileItems.reverse()}
|
||||
height={fileItems.length > 5 ? 400 : fileItems.length * 80}
|
||||
itemHeight={80}
|
||||
height={fileItems.length > 5 ? 400 : fileItems.length * 75}
|
||||
itemHeight={75}
|
||||
itemKey="id"
|
||||
styles={{
|
||||
verticalScrollBar: {
|
||||
@ -283,7 +284,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
{(item) => {
|
||||
const file = item.content as FileType
|
||||
return (
|
||||
<div style={{ height: '80px', paddingTop: '12px' }}>
|
||||
<div style={{ height: '75px', paddingTop: '12px' }}>
|
||||
<FileItem
|
||||
key={item.id}
|
||||
fileInfo={{
|
||||
@ -537,45 +538,55 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
))}
|
||||
</FlexColumn>
|
||||
</CustomCollapse>
|
||||
<ModelInfo>
|
||||
<div className="model-header">
|
||||
<label>{t('knowledge.model_info')}</label>
|
||||
<Button icon={<SettingOutlined />} onClick={() => KnowledgeSettingsPopup.show({ base })} size="small" />
|
||||
</div>
|
||||
|
||||
<div className="model-row">
|
||||
<div className="label-column">
|
||||
<label>{t('models.embedding_model')}</label>
|
||||
</div>
|
||||
<div className="tag-column">
|
||||
{providerName && <Tag color="purple">{providerName}</Tag>}
|
||||
<Tag color="blue">{base.model.name}</Tag>
|
||||
<Tag color="cyan">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{base.rerankModel && (
|
||||
<CustomCollapse
|
||||
collapsible="icon"
|
||||
label={
|
||||
<Flex gap={8} align="center">
|
||||
{t('knowledge.model_info')}
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => KnowledgeSettingsPopup.show({ base })}
|
||||
size="small"
|
||||
/>
|
||||
</Flex>
|
||||
}
|
||||
extra={
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => KnowledgeSearchPopup.show({ base })}
|
||||
icon={<SearchOutlined />}
|
||||
disabled={disabled}>
|
||||
{t('knowledge.search')}
|
||||
</Button>
|
||||
}>
|
||||
<ModelInfo>
|
||||
<div className="model-row">
|
||||
<div className="label-column">
|
||||
<label>{t('models.rerank_model')}</label>
|
||||
<label>{t('models.embedding_model')}</label>
|
||||
</div>
|
||||
<div className="tag-column">
|
||||
{rerankModelProviderName && <Tag color="purple">{rerankModelProviderName}</Tag>}
|
||||
<Tag color="blue">{base.rerankModel?.name}</Tag>
|
||||
{providerName && <CustomTag color="#af21af">{providerName}</CustomTag>}
|
||||
<CustomTag color="#0000ff">{base.model.name}</CustomTag>
|
||||
<CustomTag color="#00b1b1">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</CustomTag>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ModelInfo>
|
||||
|
||||
<IndexSection>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => KnowledgeSearchPopup.show({ base })}
|
||||
icon={<SearchOutlined />}
|
||||
disabled={disabled}>
|
||||
{t('knowledge.search')}
|
||||
</Button>
|
||||
</IndexSection>
|
||||
{base.rerankModel && (
|
||||
<div className="model-row">
|
||||
<div className="label-column">
|
||||
<label>{t('models.rerank_model')}</label>
|
||||
</div>
|
||||
<div className="tag-column">
|
||||
{rerankModelProviderName && <CustomTag color="#af21af">{rerankModelProviderName}</CustomTag>}
|
||||
<CustomTag color="#0000ff">{base.rerankModel?.name}</CustomTag>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ModelInfo>
|
||||
</CustomCollapse>
|
||||
|
||||
<BottomSpacer />
|
||||
</MainContent>
|
||||
@ -588,9 +599,9 @@ const CollapseLabel = ({ label, count }: { label: string; count: number }) => {
|
||||
return (
|
||||
<HStack alignItems="center" gap={10}>
|
||||
<label>{label}</label>
|
||||
<Tag style={{ borderRadius: 100, padding: '0 10px' }} color={count ? 'green' : 'default'}>
|
||||
<CustomTag size={12} color={count ? '#008001' : '#cccccc'}>
|
||||
{count}
|
||||
</Tag>
|
||||
</CustomTag>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
@ -605,12 +616,6 @@ const MainContent = styled(Scrollbar)`
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const IndexSection = styled.div`
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const ModelInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -629,6 +634,7 @@ const ModelInfo = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.label-column {
|
||||
|
||||
@ -120,13 +120,11 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
|
||||
tooltip={t('settings.models.add.group_name.tooltip')}>
|
||||
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 15, textAlign: 'center' }}>
|
||||
<Flex justify="center" align="center" style={{ position: 'relative' }}>
|
||||
<div>
|
||||
<Button type="primary" htmlType="submit" size="middle">
|
||||
{t('settings.models.add.add_model')}
|
||||
</Button>
|
||||
</div>
|
||||
<Form.Item style={{ marginBottom: 0, textAlign: 'center' }}>
|
||||
<Flex justify="end" align="center" style={{ position: 'relative' }}>
|
||||
<Button type="primary" htmlType="submit" size="middle">
|
||||
{t('settings.models.add.add_model')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { LoadingOutlined, MinusOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import ModelTags from '@renderer/components/ModelTags'
|
||||
import { LoadingOutlined, MinusOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import CustomCollapse from '@renderer/components/CustomCollapse'
|
||||
import CustomTag from '@renderer/components/CustomTag'
|
||||
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
|
||||
import {
|
||||
getModelLogo,
|
||||
isEmbeddingModel,
|
||||
@ -12,11 +13,12 @@ import {
|
||||
SYSTEM_MODELS
|
||||
} from '@renderer/config/models'
|
||||
import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import FileItem from '@renderer/pages/files/FileItem'
|
||||
import { fetchModels } from '@renderer/services/ApiService'
|
||||
import { Model, Provider } from '@renderer/types'
|
||||
import { getDefaultGroupName, isFreeModel, runAsyncFunction } from '@renderer/utils'
|
||||
import { Avatar, Button, Empty, Flex, Modal, Popover, Radio, Tooltip } from 'antd'
|
||||
import Search from 'antd/es/input/Search'
|
||||
import { Avatar, Button, Empty, Flex, Modal, Tabs, Tooltip, Typography } from 'antd'
|
||||
import Input from 'antd/es/input/Input'
|
||||
import { groupBy, isEmpty, uniqBy } from 'lodash'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -163,51 +165,63 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
width="680px"
|
||||
styles={{
|
||||
content: { padding: 0 },
|
||||
header: { padding: 22, paddingBottom: 15 }
|
||||
header: { padding: '16px 22px 30px 22px' }
|
||||
}}
|
||||
centered>
|
||||
<SearchContainer>
|
||||
<Center>
|
||||
<Radio.Group
|
||||
size={i18n.language.startsWith('zh') ? 'middle' : 'small'}
|
||||
value={filterType}
|
||||
onChange={(e) => setFilterType(e.target.value)}
|
||||
buttonStyle="solid">
|
||||
<Radio.Button value="all">{t('models.all')}</Radio.Button>
|
||||
<Radio.Button value="reasoning">{t('models.type.reasoning')}</Radio.Button>
|
||||
<Radio.Button value="vision">{t('models.type.vision')}</Radio.Button>
|
||||
<Radio.Button value="websearch">{t('models.type.websearch')}</Radio.Button>
|
||||
<Radio.Button value="free">{t('models.type.free')}</Radio.Button>
|
||||
<Radio.Button value="embedding">{t('models.type.embedding')}</Radio.Button>
|
||||
<Radio.Button value="rerank">{t('models.type.rerank')}</Radio.Button>
|
||||
<Radio.Button value="function_calling">{t('models.type.function_calling')}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Center>
|
||||
<Search
|
||||
<Input
|
||||
prefix={<SearchOutlined />}
|
||||
size="large"
|
||||
ref={searchInputRef}
|
||||
placeholder={t('settings.provider.search_placeholder')}
|
||||
allowClear
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
onSearch={setSearchText}
|
||||
/>
|
||||
<Tabs
|
||||
size={i18n.language.startsWith('zh') ? 'middle' : 'small'}
|
||||
defaultActiveKey="all"
|
||||
items={[
|
||||
{ label: t('models.all'), key: 'all' },
|
||||
{ label: t('models.type.reasoning'), key: 'reasoning' },
|
||||
{ label: t('models.type.vision'), key: 'vision' },
|
||||
{ label: t('models.type.websearch'), key: 'websearch' },
|
||||
{ label: t('models.type.free'), key: 'free' },
|
||||
{ label: t('models.type.embedding'), key: 'embedding' },
|
||||
{ label: t('models.type.rerank'), key: 'rerank' },
|
||||
{ label: t('models.type.function_calling'), key: 'function_calling' }
|
||||
]}
|
||||
onChange={(key) => setFilterType(key)}
|
||||
/>
|
||||
</SearchContainer>
|
||||
<ListContainer>
|
||||
{Object.keys(modelGroups).map((group) => {
|
||||
{Object.keys(modelGroups).map((group, i) => {
|
||||
const isAllInProvider = modelGroups[group].every((model) => isModelInProvider(provider, model.id))
|
||||
return (
|
||||
<div key={group}>
|
||||
<ListHeader key={group}>
|
||||
{group}
|
||||
<div>
|
||||
<CustomCollapse
|
||||
key={i}
|
||||
defaultActiveKey={i >= 5 ? [] : ['1']}
|
||||
label={
|
||||
<Flex align="center" gap={10}>
|
||||
<span>{group}</span>
|
||||
<CustomTag color="#02B96B" size={10}>
|
||||
{modelGroups[group].length}
|
||||
</CustomTag>
|
||||
</Flex>
|
||||
}
|
||||
extra={
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
title={
|
||||
isAllInProvider
|
||||
? t(`settings.models.manage.remove_whole_group`)
|
||||
: t(`settings.models.manage.add_whole_group`)
|
||||
}
|
||||
placement="top">
|
||||
<Button
|
||||
type="text"
|
||||
icon={isAllInProvider ? <MinusOutlined /> : <PlusOutlined />}
|
||||
title={
|
||||
isAllInProvider
|
||||
? t(`settings.models.manage.remove_whole_group`)
|
||||
: t(`settings.models.manage.add_whole_group`)
|
||||
}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (isAllInProvider) {
|
||||
modelGroups[group]
|
||||
.filter((model) => isModelInProvider(provider, model.id))
|
||||
@ -217,40 +231,68 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ListHeader>
|
||||
{modelGroups[group].map((model) => {
|
||||
return (
|
||||
<ListItem key={model.id}>
|
||||
<ListItemHeader>
|
||||
<Avatar src={getModelLogo(model.id)} size={24}>
|
||||
{model?.name?.[0]?.toUpperCase()}
|
||||
</Avatar>
|
||||
<ListItemName>
|
||||
<Tooltip title={model.id} placement="top">
|
||||
<span style={{ cursor: 'help' }}>{model.name}</span>
|
||||
</Tooltip>
|
||||
<ModelTags model={model} />
|
||||
{!isEmpty(model.description) && (
|
||||
<Popover
|
||||
trigger="click"
|
||||
title={model.name}
|
||||
content={model.description}
|
||||
overlayStyle={{ maxWidth: 600 }}>
|
||||
<Question />
|
||||
</Popover>
|
||||
)}
|
||||
</ListItemName>
|
||||
</ListItemHeader>
|
||||
{isModelInProvider(provider, model.id) ? (
|
||||
<Button type="default" onClick={() => onRemoveModel(model)} icon={<MinusOutlined />} />
|
||||
) : (
|
||||
<Button type="primary" onClick={() => onAddModel(model)} icon={<PlusOutlined />} />
|
||||
)}
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}>
|
||||
<FlexColumn>
|
||||
{modelGroups[group].map((model) => (
|
||||
<FileItem
|
||||
style={{
|
||||
backgroundColor: isModelInProvider(provider, model.id)
|
||||
? 'rgba(0, 126, 0, 0.06)'
|
||||
: 'rgba(255, 255, 255, 0.04)'
|
||||
}}
|
||||
key={model.id}
|
||||
fileInfo={{
|
||||
icon: <Avatar src={getModelLogo(model.id)}>{model?.name?.[0]?.toUpperCase()}</Avatar>,
|
||||
name: (
|
||||
<ListItemName>
|
||||
<Tooltip
|
||||
styles={{
|
||||
root: {
|
||||
width: 'auto',
|
||||
maxWidth: '500px'
|
||||
}
|
||||
}}
|
||||
destroyTooltipOnHide
|
||||
title={
|
||||
<Typography.Text style={{ color: 'white' }} copyable={{ text: model.id }}>
|
||||
{model.id}
|
||||
</Typography.Text>
|
||||
}
|
||||
placement="top">
|
||||
<span style={{ cursor: 'help' }}>{model.name}</span>
|
||||
</Tooltip>
|
||||
</ListItemName>
|
||||
),
|
||||
extra: (
|
||||
<div style={{ marginTop: 6 }}>
|
||||
<ModelTagsWithLabel model={model} size={11} />
|
||||
|
||||
{model.description && (
|
||||
<Typography.Paragraph
|
||||
type="secondary"
|
||||
ellipsis={{ rows: 1, expandable: true }}
|
||||
style={{ marginBottom: 0, marginTop: 5 }}>
|
||||
{model.description}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
ext: '.model',
|
||||
actions: (
|
||||
<div>
|
||||
{isModelInProvider(provider, model.id) ? (
|
||||
<Button type="text" onClick={() => onRemoveModel(model)} icon={<MinusOutlined />} />
|
||||
) : (
|
||||
<Button type="text" onClick={() => onAddModel(model)} icon={<PlusOutlined />} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</FlexColumn>
|
||||
</CustomCollapse>
|
||||
)
|
||||
})}
|
||||
{isEmpty(list) && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('settings.models.empty')} />}
|
||||
@ -264,7 +306,6 @@ const SearchContainer = styled.div`
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 0 22px;
|
||||
padding-bottom: 10px;
|
||||
margin-top: -10px;
|
||||
|
||||
.ant-radio-group {
|
||||
@ -274,37 +315,21 @@ const SearchContainer = styled.div`
|
||||
`
|
||||
|
||||
const ListContainer = styled.div`
|
||||
max-height: 70vh;
|
||||
height: calc(100vh - 300px);
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
|
||||
const ListHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--color-background-soft);
|
||||
padding: 8px 22px;
|
||||
color: var(--color-text);
|
||||
opacity: 0.4;
|
||||
`
|
||||
|
||||
const ListItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 22px;
|
||||
`
|
||||
|
||||
const ListItemHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0 6px 16px 6px;
|
||||
margin-left: 16px;
|
||||
margin-right: 10px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const FlexColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
`
|
||||
|
||||
const ListItemName = styled.div`
|
||||
@ -314,8 +339,8 @@ const ListItemName = styled.div`
|
||||
gap: 10px;
|
||||
color: var(--color-text);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
margin-left: 6px;
|
||||
`
|
||||
|
||||
const ModelHeaderTitle = styled.div`
|
||||
@ -325,11 +350,6 @@ const ModelHeaderTitle = styled.div`
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const Question = styled(QuestionCircleOutlined)`
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
`
|
||||
|
||||
export default class EditModelsPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
|
||||
@ -160,7 +160,7 @@ const PopupContainer: React.FC<Props> = ({ title, apiKeys, resolve }) => {
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
<Button key="start" type="primary" onClick={onStart}>
|
||||
<Button key="start" type="primary" onClick={onStart} size="small">
|
||||
{t('settings.models.check.start')}
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
@ -102,18 +102,14 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ model, onUpdateModel, ope
|
||||
<Input placeholder={t('settings.models.add.group_name.placeholder')} spellCheck={false} />
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 15, textAlign: 'center' }}>
|
||||
<Flex justify="center" align="center" style={{ position: 'relative' }}>
|
||||
<div>
|
||||
<Button type="primary" htmlType="submit" size="middle">
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
<MoreSettingsRow
|
||||
onClick={() => setShowModelTypes(!showModelTypes)}
|
||||
style={{ position: 'absolute', right: 0 }}>
|
||||
<Flex justify="space-between" align="center" style={{ position: 'relative' }}>
|
||||
<MoreSettingsRow onClick={() => setShowModelTypes(!showModelTypes)}>
|
||||
{t('settings.moresetting')}
|
||||
<ExpandIcon>{showModelTypes ? <UpOutlined /> : <DownOutlined />}</ExpandIcon>
|
||||
</MoreSettingsRow>
|
||||
<Button type="primary" htmlType="submit" size="middle">
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
{showModelTypes && (
|
||||
|
||||
@ -5,20 +5,24 @@ import {
|
||||
ExclamationCircleFilled,
|
||||
LoadingOutlined,
|
||||
MinusCircleOutlined,
|
||||
MinusOutlined,
|
||||
PlusOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons'
|
||||
import ModelTags from '@renderer/components/ModelTags'
|
||||
import CustomCollapse from '@renderer/components/CustomCollapse'
|
||||
import CustomTag from '@renderer/components/CustomTag'
|
||||
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
||||
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import FileItem from '@renderer/pages/files/FileItem'
|
||||
import { ModelCheckStatus } from '@renderer/services/HealthCheckService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setModel } from '@renderer/store/assistants'
|
||||
import { Model } from '@renderer/types'
|
||||
import { maskApiKey } from '@renderer/utils/api'
|
||||
import { Avatar, Button, Card, Flex, Space, Tooltip, Typography } from 'antd'
|
||||
import { Avatar, Button, Flex, Tooltip, Typography } from 'antd'
|
||||
import { groupBy, sortBy, toPairs } from 'lodash'
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -240,71 +244,107 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(sortedModelGroups).map((group) => (
|
||||
<Card
|
||||
key={group}
|
||||
type="inner"
|
||||
title={group}
|
||||
extra={
|
||||
<Tooltip title={t('settings.models.manage.remove_whole_group')}>
|
||||
<HoveredRemoveIcon
|
||||
onClick={() =>
|
||||
modelGroups[group]
|
||||
.filter((model) => provider.models.some((m) => m.id === model.id))
|
||||
.forEach((model) => removeModel(model))
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
style={{ marginBottom: '10px', border: '0.5px solid var(--color-border)' }}
|
||||
size="small">
|
||||
{sortedModelGroups[group].map((model) => {
|
||||
const modelStatus = modelStatuses.find((status) => status.model.id === model.id)
|
||||
const isChecking = modelStatus?.checking === true
|
||||
console.log('model', model.id, getModelLogo(model.id))
|
||||
<Flex gap={12} vertical>
|
||||
{Object.keys(sortedModelGroups).map((group, i) => (
|
||||
<CustomCollapse
|
||||
defaultActiveKey={i <= 5 ? ['1'] : []}
|
||||
key={group}
|
||||
label={
|
||||
<Flex align="center" gap={10}>
|
||||
<span>{group}</span>
|
||||
<CustomTag color="#02B96B" size={10}>
|
||||
{modelGroups[group].length}
|
||||
</CustomTag>
|
||||
</Flex>
|
||||
}
|
||||
extra={
|
||||
<Tooltip title={t('settings.models.manage.remove_whole_group')}>
|
||||
<HoveredRemoveIcon
|
||||
onClick={() =>
|
||||
modelGroups[group]
|
||||
.filter((model) => provider.models.some((m) => m.id === model.id))
|
||||
.forEach((model) => removeModel(model))
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
}>
|
||||
<Flex gap={10} vertical style={{ marginTop: 10 }}>
|
||||
{sortedModelGroups[group].map((model) => {
|
||||
const modelStatus = modelStatuses.find((status) => status.model.id === model.id)
|
||||
const isChecking = modelStatus?.checking === true
|
||||
|
||||
return (
|
||||
<ModelListItem key={model.id}>
|
||||
<ModelListHeader>
|
||||
<Avatar src={getModelLogo(model.id)} size={22} style={{ marginRight: '8px' }}>
|
||||
{model?.name?.[0]?.toUpperCase()}
|
||||
</Avatar>
|
||||
<ModelNameRow>
|
||||
<span>{model?.name}</span>
|
||||
<ModelTags model={model} />
|
||||
</ModelNameRow>
|
||||
<SettingIcon
|
||||
onClick={() => !isChecking && onEditModel(model)}
|
||||
style={{ cursor: isChecking ? 'not-allowed' : 'pointer', opacity: isChecking ? 0.5 : 1 }}
|
||||
return (
|
||||
<FileItem
|
||||
key={model.id}
|
||||
fileInfo={{
|
||||
icon: <Avatar src={getModelLogo(model.id)}>{model?.name?.[0]?.toUpperCase()}</Avatar>,
|
||||
name: (
|
||||
<ListItemName>
|
||||
<Tooltip
|
||||
styles={{
|
||||
root: {
|
||||
width: 'auto',
|
||||
maxWidth: '500px'
|
||||
}
|
||||
}}
|
||||
destroyTooltipOnHide
|
||||
title={
|
||||
<Typography.Text style={{ color: 'white' }} copyable={{ text: model.id }}>
|
||||
{model.id}
|
||||
</Typography.Text>
|
||||
}
|
||||
placement="top">
|
||||
<span style={{ cursor: 'help' }}>{model.name}</span>
|
||||
</Tooltip>
|
||||
</ListItemName>
|
||||
),
|
||||
extra: (
|
||||
<div style={{ marginTop: 6 }}>
|
||||
<ModelTagsWithLabel model={model} size={11} />
|
||||
</div>
|
||||
),
|
||||
ext: '.model',
|
||||
actions: (
|
||||
<Flex gap={4} align="center">
|
||||
{renderLatencyText(modelStatus)}
|
||||
{renderStatusIndicator(modelStatus)}
|
||||
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => !isChecking && onEditModel(model)}
|
||||
disabled={isChecking}
|
||||
icon={<SettingOutlined />}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => !isChecking && removeModel(model)}
|
||||
disabled={isChecking}
|
||||
icon={<MinusOutlined />}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{renderLatencyText(modelStatus)}
|
||||
</ModelListHeader>
|
||||
<Space>
|
||||
{renderStatusIndicator(modelStatus)}
|
||||
<RemoveIcon
|
||||
onClick={() => !isChecking && removeModel(model)}
|
||||
style={{ cursor: isChecking ? 'not-allowed' : 'pointer', opacity: isChecking ? 0.5 : 1 }}
|
||||
/>
|
||||
</Space>
|
||||
</ModelListItem>
|
||||
)
|
||||
})}
|
||||
</Card>
|
||||
))}
|
||||
{docsWebsite && (
|
||||
<SettingHelpTextRow>
|
||||
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
|
||||
<SettingHelpLink target="_blank" href={docsWebsite}>
|
||||
{t(`provider.${provider.id}`) + ' '}
|
||||
{t('common.docs')}
|
||||
</SettingHelpLink>
|
||||
<SettingHelpText>{t('common.and')}</SettingHelpText>
|
||||
<SettingHelpLink target="_blank" href={modelsWebsite}>
|
||||
{t('common.models')}
|
||||
</SettingHelpLink>
|
||||
<SettingHelpText>{t('settings.provider.docs_more_details')}</SettingHelpText>
|
||||
</SettingHelpTextRow>
|
||||
)}
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
</CustomCollapse>
|
||||
))}
|
||||
{docsWebsite && (
|
||||
<SettingHelpTextRow>
|
||||
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
|
||||
<SettingHelpLink target="_blank" href={docsWebsite}>
|
||||
{t(`provider.${provider.id}`) + ' '}
|
||||
{t('common.docs')}
|
||||
</SettingHelpLink>
|
||||
<SettingHelpText>{t('common.and')}</SettingHelpText>
|
||||
<SettingHelpLink target="_blank" href={modelsWebsite}>
|
||||
{t('common.models')}
|
||||
</SettingHelpLink>
|
||||
<SettingHelpText>{t('settings.provider.docs_more_details')}</SettingHelpText>
|
||||
</SettingHelpTextRow>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex gap={10} style={{ marginTop: '10px' }}>
|
||||
<Button type="primary" onClick={onManageModel} icon={<EditOutlined />}>
|
||||
{t('button.manage')}
|
||||
@ -326,25 +366,20 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
|
||||
)
|
||||
}
|
||||
|
||||
const ModelListItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 5px 0;
|
||||
`
|
||||
|
||||
const ModelListHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ModelNameRow = styled.div`
|
||||
const ListItemName = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--color-text);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`
|
||||
|
||||
const RemoveIcon = styled(MinusCircleOutlined)`
|
||||
@ -365,21 +400,11 @@ const HoveredRemoveIcon = styled(RemoveIcon)`
|
||||
}
|
||||
`
|
||||
|
||||
const SettingIcon = styled(SettingOutlined)`
|
||||
margin-left: 2px;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
`
|
||||
|
||||
const StatusIndicator = styled.div<{ type: string }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: ${(props) => {
|
||||
switch (props.type) {
|
||||
@ -398,6 +423,7 @@ const StatusIndicator = styled.div<{ type: string }>`
|
||||
const ModelLatencyText = styled(Typography.Text)`
|
||||
margin-left: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
export default memo(ModelList)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CheckOutlined, ExportOutlined, HeartOutlined, LoadingOutlined, SettingOutlined } from '@ant-design/icons'
|
||||
import { CheckOutlined, ExportOutlined, LoadingOutlined, SettingOutlined } from '@ant-design/icons'
|
||||
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import OAuthButton from '@renderer/components/OAuth/OAuthButton'
|
||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
@ -383,7 +384,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
<SettingSubtitle style={{ marginBottom: 5 }}>
|
||||
<Space align="center" style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Space>
|
||||
<span>{t('common.models')}</span>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('common.models')}</SettingSubtitle>
|
||||
{!isEmpty(models) && <ModelListSearchBar onSearch={setModelSearchText} />}
|
||||
</Space>
|
||||
{!isEmpty(models) && (
|
||||
@ -391,7 +392,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<HeartOutlined />}
|
||||
icon={<StreamlineGoodHealthAndWellBeing />}
|
||||
onClick={onHealthCheck}
|
||||
loading={isHealthChecking}
|
||||
/>
|
||||
|
||||
@ -12,7 +12,7 @@ export const SettingContainer = styled.div<{ theme?: ThemeMode }>`
|
||||
padding-top: 15px;
|
||||
overflow-y: scroll;
|
||||
font-family: Ubuntu;
|
||||
background: ${(props) => (props.theme === 'dark' ? 'transparent' : 'var(--color-background-soft)')};
|
||||
/* background: ${(props) => (props.theme === 'dark' ? 'transparent' : 'var(--color-background-soft)')}; */
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user