feat(CustomCollapse, KnowledgeContent): enhance collapsible behavior and UI updates

- Added `activeKey` prop to `CustomCollapse` for better control over active panels.
- Updated styles in `CustomCollapse` for improved visual consistency.
- Refactored `KnowledgeContent` to include expand/collapse functionality for file and directory sections, enhancing user experience.
- Added translations for "collapse" in multiple languages.
- Minor style adjustments across various components for better UI consistency.
This commit is contained in:
kangfenmao 2025-04-08 19:38:15 +08:00
parent 3aaa1848f0
commit 8bcb31071e
17 changed files with 424 additions and 377 deletions

View File

@ -7,6 +7,7 @@ interface CustomCollapseProps {
children: React.ReactNode children: React.ReactNode
destroyInactivePanel?: boolean destroyInactivePanel?: boolean
defaultActiveKey?: string[] defaultActiveKey?: string[]
activeKey?: string[]
collapsible?: 'header' | 'icon' | 'disabled' collapsible?: 'header' | 'icon' | 'disabled'
} }
@ -16,6 +17,7 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
children, children,
destroyInactivePanel = false, destroyInactivePanel = false,
defaultActiveKey = ['1'], defaultActiveKey = ['1'],
activeKey,
collapsible = undefined collapsible = undefined
}) => { }) => {
const CollapseStyle = { const CollapseStyle = {
@ -27,7 +29,10 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
header: { header: {
padding: '8px 16px', padding: '8px 16px',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between' justifyContent: 'space-between',
background: 'var(--color-background-soft)',
borderTopLeftRadius: '8px',
borderTopRightRadius: '8px'
}, },
body: { body: {
borderTop: '0.5px solid var(--color-border)' borderTop: '0.5px solid var(--color-border)'
@ -38,6 +43,7 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
bordered={false} bordered={false}
style={CollapseStyle} style={CollapseStyle}
defaultActiveKey={defaultActiveKey} defaultActiveKey={defaultActiveKey}
activeKey={activeKey}
destroyInactivePanel={destroyInactivePanel} destroyInactivePanel={destroyInactivePanel}
collapsible={collapsible} collapsible={collapsible}
items={[ items={[

View File

@ -1,13 +1,14 @@
import React from 'react' import React, { CSSProperties } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
interface DividerWithTextProps { interface DividerWithTextProps {
text: string text: string
style?: CSSProperties
} }
const DividerWithText: React.FC<DividerWithTextProps> = ({ text }) => { const DividerWithText: React.FC<DividerWithTextProps> = ({ text, style }) => {
return ( return (
<DividerContainer> <DividerContainer style={style}>
<DividerText>{text}</DividerText> <DividerText>{text}</DividerText>
<DividerLine /> <DividerLine />
</DividerContainer> </DividerContainer>

View File

@ -70,7 +70,7 @@ const TextContainer = styled.div`
overflow: hidden; overflow: hidden;
` `
const TitleText = styled.div` const TitleText = styled.div<{ $active?: boolean }>`
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;

View File

@ -85,8 +85,8 @@ const ModelTagsWithLabel: FC<ModelTagsProps> = ({
</CustomTag> </CustomTag>
)} )}
{showToolsCalling && isFunctionCallingModel(model) && ( {showToolsCalling && isFunctionCallingModel(model) && (
<CustomTag size={size} color="#d45ea3" icon={<ToolOutlined />} tooltip={t('models.function_calling')}> <CustomTag size={size} color="#f18737" icon={<ToolOutlined />} tooltip={t('models.type.function_calling')}>
{_showLabel ? t('models.function_calling') : ''} {_showLabel ? t('models.type.function_calling') : ''}
</CustomTag> </CustomTag>
)} )}
{isEmbeddingModel(model) && ( {isEmbeddingModel(model) && (

View File

@ -277,6 +277,7 @@
"duplicate": "Duplicate", "duplicate": "Duplicate",
"edit": "Edit", "edit": "Edit",
"expand": "Expand", "expand": "Expand",
"collapse": "Collapse",
"footnote": "Reference content", "footnote": "Reference content",
"footnotes": "References", "footnotes": "References",
"fullscreen": "Entered fullscreen mode. Press F11 to exit", "fullscreen": "Entered fullscreen mode. Press F11 to exit",

View File

@ -277,6 +277,7 @@
"duplicate": "複製", "duplicate": "複製",
"edit": "編集", "edit": "編集",
"expand": "展開", "expand": "展開",
"collapse": "折りたたむ",
"footnote": "引用内容", "footnote": "引用内容",
"footnotes": "脚注", "footnotes": "脚注",
"fullscreen": "全画面モードに入りました。F11キーで終了します", "fullscreen": "全画面モードに入りました。F11キーで終了します",

View File

@ -277,6 +277,7 @@
"duplicate": "Дублировать", "duplicate": "Дублировать",
"edit": "Редактировать", "edit": "Редактировать",
"expand": "Развернуть", "expand": "Развернуть",
"collapse": "Свернуть",
"footnote": "Цитируемый контент", "footnote": "Цитируемый контент",
"footnotes": "Сноски", "footnotes": "Сноски",
"fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода", "fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода",

View File

@ -277,6 +277,7 @@
"duplicate": "复制", "duplicate": "复制",
"edit": "编辑", "edit": "编辑",
"expand": "展开", "expand": "展开",
"collapse": "折叠",
"footnote": "引用内容", "footnote": "引用内容",
"footnotes": "引用内容", "footnotes": "引用内容",
"fullscreen": "已进入全屏模式,按 F11 退出", "fullscreen": "已进入全屏模式,按 F11 退出",

View File

@ -277,6 +277,7 @@
"duplicate": "複製", "duplicate": "複製",
"edit": "編輯", "edit": "編輯",
"expand": "展開", "expand": "展開",
"collapse": "折疊",
"footnote": "引用內容", "footnote": "引用內容",
"footnotes": "引用", "footnotes": "引用",
"fullscreen": "已進入全螢幕模式,按 F11 結束", "fullscreen": "已進入全螢幕模式,按 F11 結束",

View File

@ -93,7 +93,6 @@ const FileItem: React.FC<FileItemProps> = ({ fileInfo, style }) => {
} }
const FileItemCard = styled.div` const FileItemCard = styled.div`
background: rgba(255, 255, 255, 0.04);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
border: 0.5px solid var(--color-border); border: 0.5px solid var(--color-border);

View File

@ -223,7 +223,7 @@ const ContentContainer = styled.div`
` `
const SideNav = styled.div` const SideNav = styled.div`
width: var(--assistants-width); width: var(--settings-width);
border-right: 0.5px solid var(--color-border); border-right: 0.5px solid var(--color-border);
padding: 7px 12px; padding: 7px 12px;
user-select: none; user-select: none;

View File

@ -1,11 +1,13 @@
import { import {
ColumnHeightOutlined,
CopyOutlined, CopyOutlined,
DeleteOutlined, DeleteOutlined,
EditOutlined, EditOutlined,
PlusOutlined, PlusOutlined,
RedoOutlined, RedoOutlined,
SearchOutlined, SearchOutlined,
SettingOutlined SettingOutlined,
VerticalAlignMiddleOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import CustomTag from '@renderer/components/CustomTag' import CustomTag from '@renderer/components/CustomTag'
import Ellipsis from '@renderer/components/Ellipsis' import Ellipsis from '@renderer/components/Ellipsis'
@ -19,10 +21,10 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { FileType, FileTypes, KnowledgeBase, KnowledgeItem } from '@renderer/types' import { FileType, FileTypes, KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant' import { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant'
import { Alert, Button, Dropdown, Empty, Flex, message, Tooltip, Upload } from 'antd' import { Alert, Button, Dropdown, Empty, message, Tag, Tooltip, Upload } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import VirtualList from 'rc-virtual-list' import VirtualList from 'rc-virtual-list'
import { FC } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -42,6 +44,7 @@ const fileTypes = [...bookExts, ...thirdPartyApplicationExts, ...documentExts, .
const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => { const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [expandAll, setExpandAll] = useState(false)
const { const {
base, base,
@ -230,366 +233,389 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
} }
return ( return (
<MainContent> <MainContainer>
{!base?.version && ( <HeaderContainer>
<Alert message={t('knowledge.not_support')} type="error" style={{ marginBottom: 20 }} showIcon />
)}
{!providerName && (
<Alert message={t('knowledge.no_provider')} type="error" style={{ marginBottom: 20 }} showIcon />
)}
<CustomCollapse
label={<CollapseLabel label={t('files.title')} count={fileItems.length} />}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddFile()
}}
disabled={disabled}>
{t('knowledge.add_file')}
</Button>
}>
<Dragger
showUploadList={false}
customRequest={({ file }) => handleDrop([file as File])}
multiple={true}
accept={fileTypes.join(',')}
style={{ marginTop: 10, background: 'transparent' }}>
<p className="ant-upload-text">{t('knowledge.drag_file')}</p>
<p className="ant-upload-hint">
{t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })}
</p>
</Dragger>
<FlexColumn>
{fileItems.length === 0 ? (
<EmptyView />
) : (
<VirtualList
data={fileItems.reverse()}
height={fileItems.length > 5 ? 400 : fileItems.length * 75}
itemHeight={75}
itemKey="id"
styles={{
verticalScrollBar: {
width: 6
},
verticalScrollBarThumb: {
background: 'var(--color-scrollbar-thumb)'
}
}}>
{(item) => {
const file = item.content as FileType
return (
<div style={{ height: '75px', paddingTop: '12px' }}>
<FileItem
key={item.id}
fileInfo={{
name: (
<ClickableSpan onClick={() => window.api.file.openPath(file.path)}>
<Ellipsis>
<Tooltip title={file.origin_name}>{file.origin_name}</Tooltip>
</Ellipsis>
</ClickableSpan>
),
ext: file.ext,
extra: `${dayjs(file.created_at).format('MM-DD HH:mm')} · ${formatFileSize(file.size)}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && (
<Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />
)}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="file"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
</div>
)
}}
</VirtualList>
)}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.directories')} count={directoryItems.length} />}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddDirectory()
}}
disabled={disabled}>
{t('knowledge.add_directory')}
</Button>
}>
<FlexColumn>
{directoryItems.length === 0 && <EmptyView />}
{directoryItems.reverse().map((item) => (
<FileItem
key={item.id}
fileInfo={{
name: (
<ClickableSpan onClick={() => window.api.file.openPath(item.content as string)}>
<Ellipsis>
<Tooltip title={item.content as string}>{item.content as string}</Tooltip>
</Ellipsis>
</ClickableSpan>
),
ext: '.folder',
extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
getProcessingPercent={getProgressingPercentForItem}
type="directory"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.urls')} count={urlItems.length} />}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddUrl()
}}
disabled={disabled}>
{t('knowledge.add_url')}
</Button>
}>
<FlexColumn>
{urlItems.length === 0 && <EmptyView />}
{urlItems.reverse().map((item) => (
<FileItem
key={item.id}
fileInfo={{
name: (
<Dropdown
menu={{
items: [
{
key: 'edit',
icon: <EditOutlined />,
label: t('knowledge.edit_remark'),
onClick: () => handleEditRemark(item)
},
{
key: 'copy',
icon: <CopyOutlined />,
label: t('common.copy'),
onClick: () => {
navigator.clipboard.writeText(item.content as string)
message.success(t('message.copied'))
}
}
]
}}
trigger={['contextMenu']}>
<ClickableSpan>
<Tooltip title={item.content as string}>
<Ellipsis>
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
{item.remark || (item.content as string)}
</a>
</Ellipsis>
</Tooltip>
</ClickableSpan>
</Dropdown>
),
ext: '.url',
extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
<StatusIconWrapper>
<StatusIcon sourceId={item.id} base={base} getProcessingStatus={getProcessingStatus} type="url" />
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.sitemaps')} count={sitemapItems.length} />}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddSitemap()
}}
disabled={disabled}>
{t('knowledge.add_sitemap')}
</Button>
}>
<FlexColumn>
{sitemapItems.length === 0 && <EmptyView />}
{sitemapItems.reverse().map((item) => (
<FileItem
key={item.id}
fileInfo={{
name: (
<ClickableSpan>
<Tooltip title={item.content as string}>
<Ellipsis>
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
{item.content as string}
</a>
</Ellipsis>
</Tooltip>
</ClickableSpan>
),
ext: '.sitemap',
extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="sitemap"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.notes')} count={noteItems.length} />}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddNote()
}}
disabled={disabled}>
{t('knowledge.add_note')}
</Button>
}>
<FlexColumn>
{noteItems.length === 0 && <EmptyView />}
{noteItems.reverse().map((note) => (
<FileItem
key={note.id}
fileInfo={{
name: <span onClick={() => handleEditNote(note)}>{(note.content as string).slice(0, 50)}...</span>,
ext: '.txt',
extra: `${dayjs(note.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
<Button type="text" onClick={() => handleEditNote(note)} icon={<EditOutlined />} />
<StatusIconWrapper>
<StatusIcon
sourceId={note.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="note"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(note)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<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> <ModelInfo>
<Button
type="text"
icon={<SettingOutlined />}
onClick={() => KnowledgeSettingsPopup.show({ base })}
size="small"
/>
<div className="model-row"> <div className="model-row">
<div className="label-column"> <div className="label-column">
<label>{t('models.embedding_model')}</label> <label>{t('models.embedding_model')}</label>
</div> </div>
<div className="tag-column"> <Tooltip title={providerName} placement="bottom">
{providerName && <CustomTag color="#af21af">{providerName}</CustomTag>} <div className="tag-column">
<CustomTag color="#0000ff">{base.model.name}</CustomTag> <Tag color="geekblue" style={{ borderRadius: 20, margin: 0 }}>
<CustomTag color="#00b1b1">{t('models.dimensions', { dimensions: base.dimensions || 0 })}</CustomTag> {base.model.name}
</div> </Tag>
</div>
</Tooltip>
<Tag color="cyan" style={{ borderRadius: 20, margin: 0 }}>
{t('models.dimensions', { dimensions: base.dimensions || 0 })}
</Tag>
</div> </div>
{base.rerankModel && ( {base.rerankModel && (
<div className="model-row"> <div className="model-row">
<div className="label-column"> <div className="label-column">
<label>{t('models.rerank_model')}</label> <label>{t('models.rerank_model')}</label>
</div> </div>
<div className="tag-column"> <Tooltip title={rerankModelProviderName} placement="bottom">
{rerankModelProviderName && <CustomTag color="#af21af">{rerankModelProviderName}</CustomTag>} <div className="tag-column">
<CustomTag color="#0000ff">{base.rerankModel?.name}</CustomTag> <Tag color="green" style={{ borderRadius: 20, margin: 0 }}>
</div> {base.rerankModel?.name}
</Tag>
</div>
</Tooltip>
</div> </div>
)} )}
</ModelInfo> </ModelInfo>
</CustomCollapse> <HStack gap={8} alignItems="center">
<Button
size="small"
shape="round"
onClick={() => KnowledgeSearchPopup.show({ base })}
icon={<SearchOutlined />}
disabled={disabled}>
{t('knowledge.search')}
</Button>
<Tooltip title={expandAll ? t('common.collapse') : t('common.expand')}>
<Button
size="small"
shape="circle"
onClick={() => setExpandAll(!expandAll)}
icon={expandAll ? <VerticalAlignMiddleOutlined /> : <ColumnHeightOutlined />}
disabled={disabled}
/>
</Tooltip>
</HStack>
</HeaderContainer>
<MainContent>
{!base?.version && (
<Alert message={t('knowledge.not_support')} type="error" style={{ marginBottom: 20 }} showIcon />
)}
{!providerName && (
<Alert message={t('knowledge.no_provider')} type="error" style={{ marginBottom: 20 }} showIcon />
)}
<CustomCollapse
label={<CollapseLabel label={t('files.title')} count={fileItems.length} />}
defaultActiveKey={['1']}
activeKey={expandAll ? ['1'] : undefined}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddFile()
}}
disabled={disabled}>
{t('knowledge.add_file')}
</Button>
}>
<Dragger
showUploadList={false}
customRequest={({ file }) => handleDrop([file as File])}
multiple={true}
accept={fileTypes.join(',')}
style={{ marginTop: 10, background: 'transparent' }}>
<p className="ant-upload-text">{t('knowledge.drag_file')}</p>
<p className="ant-upload-hint">
{t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })}
</p>
</Dragger>
<BottomSpacer /> <FlexColumn>
</MainContent> {fileItems.length === 0 ? (
<EmptyView />
) : (
<VirtualList
data={fileItems.reverse()}
height={fileItems.length > 5 ? 400 : fileItems.length * 75}
itemHeight={75}
itemKey="id"
styles={{
verticalScrollBar: {
width: 6
},
verticalScrollBarThumb: {
background: 'var(--color-scrollbar-thumb)'
}
}}>
{(item) => {
const file = item.content as FileType
return (
<div style={{ height: '75px', paddingTop: '12px' }}>
<FileItem
key={item.id}
fileInfo={{
name: (
<ClickableSpan onClick={() => window.api.file.openPath(file.path)}>
<Ellipsis>
<Tooltip title={file.origin_name}>{file.origin_name}</Tooltip>
</Ellipsis>
</ClickableSpan>
),
ext: file.ext,
extra: `${dayjs(file.created_at).format('MM-DD HH:mm')} · ${formatFileSize(file.size)}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && (
<Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />
)}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="file"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
</div>
)
}}
</VirtualList>
)}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.directories')} count={directoryItems.length} />}
defaultActiveKey={[]}
activeKey={expandAll ? ['1'] : undefined}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddDirectory()
}}
disabled={disabled}>
{t('knowledge.add_directory')}
</Button>
}>
<FlexColumn>
{directoryItems.length === 0 && <EmptyView />}
{directoryItems.reverse().map((item) => (
<FileItem
key={item.id}
fileInfo={{
name: (
<ClickableSpan onClick={() => window.api.file.openPath(item.content as string)}>
<Ellipsis>
<Tooltip title={item.content as string}>{item.content as string}</Tooltip>
</Ellipsis>
</ClickableSpan>
),
ext: '.folder',
extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
getProcessingPercent={getProgressingPercentForItem}
type="directory"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.urls')} count={urlItems.length} />}
defaultActiveKey={[]}
activeKey={expandAll ? ['1'] : undefined}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddUrl()
}}
disabled={disabled}>
{t('knowledge.add_url')}
</Button>
}>
<FlexColumn>
{urlItems.length === 0 && <EmptyView />}
{urlItems.reverse().map((item) => (
<FileItem
key={item.id}
fileInfo={{
name: (
<Dropdown
menu={{
items: [
{
key: 'edit',
icon: <EditOutlined />,
label: t('knowledge.edit_remark'),
onClick: () => handleEditRemark(item)
},
{
key: 'copy',
icon: <CopyOutlined />,
label: t('common.copy'),
onClick: () => {
navigator.clipboard.writeText(item.content as string)
message.success(t('message.copied'))
}
}
]
}}
trigger={['contextMenu']}>
<ClickableSpan>
<Tooltip title={item.content as string}>
<Ellipsis>
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
{item.remark || (item.content as string)}
</a>
</Ellipsis>
</Tooltip>
</ClickableSpan>
</Dropdown>
),
ext: '.url',
extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="url"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.sitemaps')} count={sitemapItems.length} />}
defaultActiveKey={[]}
activeKey={expandAll ? ['1'] : undefined}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddSitemap()
}}
disabled={disabled}>
{t('knowledge.add_sitemap')}
</Button>
}>
<FlexColumn>
{sitemapItems.length === 0 && <EmptyView />}
{sitemapItems.reverse().map((item) => (
<FileItem
key={item.id}
fileInfo={{
name: (
<ClickableSpan>
<Tooltip title={item.content as string}>
<Ellipsis>
<a href={item.content as string} target="_blank" rel="noopener noreferrer">
{item.content as string}
</a>
</Ellipsis>
</Tooltip>
</ClickableSpan>
),
ext: '.sitemap',
extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
{item.uniqueId && <Button type="text" icon={<RefreshIcon />} onClick={() => refreshItem(item)} />}
<StatusIconWrapper>
<StatusIcon
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="sitemap"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
<CustomCollapse
label={<CollapseLabel label={t('knowledge.notes')} count={noteItems.length} />}
defaultActiveKey={[]}
activeKey={expandAll ? ['1'] : undefined}
extra={
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation()
handleAddNote()
}}
disabled={disabled}>
{t('knowledge.add_note')}
</Button>
}>
<FlexColumn>
{noteItems.length === 0 && <EmptyView />}
{noteItems.reverse().map((note) => (
<FileItem
key={note.id}
fileInfo={{
name: <span onClick={() => handleEditNote(note)}>{(note.content as string).slice(0, 50)}...</span>,
ext: '.txt',
extra: `${dayjs(note.created_at).format('MM-DD HH:mm')}`,
actions: (
<FlexAlignCenter>
<Button type="text" onClick={() => handleEditNote(note)} icon={<EditOutlined />} />
<StatusIconWrapper>
<StatusIcon
sourceId={note.id}
base={base}
getProcessingStatus={getProcessingStatus}
type="note"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(note)} icon={<DeleteOutlined />} />
</FlexAlignCenter>
)
}}
/>
))}
</FlexColumn>
</CustomCollapse>
</MainContent>
</MainContainer>
) )
} }
@ -598,7 +624,7 @@ const EmptyView = () => <Empty style={{ margin: 0 }} styles={{ image: { display:
const CollapseLabel = ({ label, count }: { label: string; count: number }) => { const CollapseLabel = ({ label, count }: { label: string; count: number }) => {
return ( return (
<HStack alignItems="center" gap={10}> <HStack alignItems="center" gap={10}>
<label>{label}</label> <label style={{ fontWeight: 600 }}>{label}</label>
<CustomTag size={12} color={count ? '#008001' : '#cccccc'}> <CustomTag size={12} color={count ? '#008001' : '#cccccc'}>
{count} {count}
</CustomTag> </CustomTag>
@ -606,35 +632,50 @@ const CollapseLabel = ({ label, count }: { label: string; count: number }) => {
) )
} }
const MainContent = styled(Scrollbar)` const MainContainer = styled.div`
display: flex; display: flex;
width: 100%; width: 100%;
flex-direction: column; flex-direction: column;
padding-bottom: 50px;
padding: 15px;
position: relative; position: relative;
gap: 16px; `
const MainContent = styled(Scrollbar)`
padding: 15px 20px;
display: flex;
flex-direction: column;
flex: 1;
gap: 20px;
padding-bottom: 50px;
padding-right: 12px;
`
const HeaderContainer = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 16px;
border-bottom: 0.5px solid var(--color-border);
` `
const ModelInfo = styled.div` const ModelInfo = styled.div`
display: flex; display: flex;
flex-direction: column;
gap: 8px;
padding: 5px;
color: var(--color-text-3); color: var(--color-text-3);
flex-direction: row;
align-items: center;
gap: 8px;
height: 50px;
.model-header { .model-header {
display: flex; display: flex;
gap: 8px; gap: 8px;
align-items: center; align-items: center;
margin-bottom: 4px;
} }
.model-row { .model-row {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 10px; gap: 10px;
margin-top: 16px;
} }
.label-column { .label-column {
@ -672,10 +713,6 @@ const ClickableSpan = styled.span`
width: 0; width: 0;
` `
const BottomSpacer = styled.div`
min-height: 20px;
`
const StatusIconWrapper = styled.div` const StatusIconWrapper = styled.div`
width: 36px; width: 36px;
height: 36px; height: 36px;

View File

@ -178,7 +178,7 @@ const MainContent = styled(Scrollbar)`
` `
const SideNav = styled.div` const SideNav = styled.div`
width: var(--assistants-width); min-width: var(--settings-width);
border-right: 0.5px solid var(--color-border); border-right: 0.5px solid var(--color-border);
padding: 12px 10px; padding: 12px 10px;
display: flex; display: flex;

View File

@ -161,7 +161,7 @@ const DataSettings: FC = () => {
<MenuList> <MenuList>
{menuItems.map((item) => {menuItems.map((item) =>
item.isDivider ? ( item.isDivider ? (
<DividerWithText key={item.key} text={item.text || ''} /> // 动态传递分隔符文字 <DividerWithText key={item.key} text={item.text || ''} style={{ margin: '8px 0' }} /> // 动态传递分隔符文字
) : ( ) : (
<ListItem <ListItem
key={item.key} key={item.key}

View File

@ -251,7 +251,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
key={group} key={group}
label={ label={
<Flex align="center" gap={10}> <Flex align="center" gap={10}>
<span>{group}</span> <span style={{ fontWeight: 600 }}>{group}</span>
<CustomTag color="#02B96B" size={10}> <CustomTag color="#02B96B" size={10}>
{modelGroups[group].length} {modelGroups[group].length}
</CustomTag> </CustomTag>
@ -308,7 +308,6 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
<Flex gap={4} align="center"> <Flex gap={4} align="center">
{renderLatencyText(modelStatus)} {renderLatencyText(modelStatus)}
{renderStatusIndicator(modelStatus)} {renderStatusIndicator(modelStatus)}
<Button <Button
type="text" type="text"
onClick={() => !isChecking && onEditModel(model)} onClick={() => !isChecking && onEditModel(model)}

View File

@ -274,7 +274,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
}, [apiKey, provider, updateProvider]) }, [apiKey, provider, updateProvider])
return ( return (
<SettingContainer theme={theme}> <SettingContainer theme={theme} style={{ background: 'var(--color-background)' }}>
<SettingTitle> <SettingTitle>
<Flex align="center" gap={8}> <Flex align="center" gap={8}>
<ProviderName>{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}</ProviderName> <ProviderName>{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}</ProviderName>

View File

@ -12,7 +12,7 @@ export const SettingContainer = styled.div<{ theme?: ThemeMode }>`
padding-top: 15px; padding-top: 15px;
overflow-y: scroll; overflow-y: scroll;
font-family: Ubuntu; 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 { &::-webkit-scrollbar {
display: none; display: none;