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:
parent
3aaa1848f0
commit
8bcb31071e
@ -7,6 +7,7 @@ interface CustomCollapseProps {
|
||||
children: React.ReactNode
|
||||
destroyInactivePanel?: boolean
|
||||
defaultActiveKey?: string[]
|
||||
activeKey?: string[]
|
||||
collapsible?: 'header' | 'icon' | 'disabled'
|
||||
}
|
||||
|
||||
@ -16,6 +17,7 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
|
||||
children,
|
||||
destroyInactivePanel = false,
|
||||
defaultActiveKey = ['1'],
|
||||
activeKey,
|
||||
collapsible = undefined
|
||||
}) => {
|
||||
const CollapseStyle = {
|
||||
@ -27,7 +29,10 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
|
||||
header: {
|
||||
padding: '8px 16px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
justifyContent: 'space-between',
|
||||
background: 'var(--color-background-soft)',
|
||||
borderTopLeftRadius: '8px',
|
||||
borderTopRightRadius: '8px'
|
||||
},
|
||||
body: {
|
||||
borderTop: '0.5px solid var(--color-border)'
|
||||
@ -38,6 +43,7 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
|
||||
bordered={false}
|
||||
style={CollapseStyle}
|
||||
defaultActiveKey={defaultActiveKey}
|
||||
activeKey={activeKey}
|
||||
destroyInactivePanel={destroyInactivePanel}
|
||||
collapsible={collapsible}
|
||||
items={[
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
import React, { CSSProperties } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface DividerWithTextProps {
|
||||
text: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const DividerWithText: React.FC<DividerWithTextProps> = ({ text }) => {
|
||||
const DividerWithText: React.FC<DividerWithTextProps> = ({ text, style }) => {
|
||||
return (
|
||||
<DividerContainer>
|
||||
<DividerContainer style={style}>
|
||||
<DividerText>{text}</DividerText>
|
||||
<DividerLine />
|
||||
</DividerContainer>
|
||||
|
||||
@ -70,7 +70,7 @@ const TextContainer = styled.div`
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const TitleText = styled.div`
|
||||
const TitleText = styled.div<{ $active?: boolean }>`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@ -85,8 +85,8 @@ const ModelTagsWithLabel: FC<ModelTagsProps> = ({
|
||||
</CustomTag>
|
||||
)}
|
||||
{showToolsCalling && isFunctionCallingModel(model) && (
|
||||
<CustomTag size={size} color="#d45ea3" icon={<ToolOutlined />} tooltip={t('models.function_calling')}>
|
||||
{_showLabel ? t('models.function_calling') : ''}
|
||||
<CustomTag size={size} color="#f18737" icon={<ToolOutlined />} tooltip={t('models.type.function_calling')}>
|
||||
{_showLabel ? t('models.type.function_calling') : ''}
|
||||
</CustomTag>
|
||||
)}
|
||||
{isEmbeddingModel(model) && (
|
||||
|
||||
@ -277,6 +277,7 @@
|
||||
"duplicate": "Duplicate",
|
||||
"edit": "Edit",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"footnote": "Reference content",
|
||||
"footnotes": "References",
|
||||
"fullscreen": "Entered fullscreen mode. Press F11 to exit",
|
||||
|
||||
@ -277,6 +277,7 @@
|
||||
"duplicate": "複製",
|
||||
"edit": "編集",
|
||||
"expand": "展開",
|
||||
"collapse": "折りたたむ",
|
||||
"footnote": "引用内容",
|
||||
"footnotes": "脚注",
|
||||
"fullscreen": "全画面モードに入りました。F11キーで終了します",
|
||||
|
||||
@ -277,6 +277,7 @@
|
||||
"duplicate": "Дублировать",
|
||||
"edit": "Редактировать",
|
||||
"expand": "Развернуть",
|
||||
"collapse": "Свернуть",
|
||||
"footnote": "Цитируемый контент",
|
||||
"footnotes": "Сноски",
|
||||
"fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода",
|
||||
|
||||
@ -277,6 +277,7 @@
|
||||
"duplicate": "复制",
|
||||
"edit": "编辑",
|
||||
"expand": "展开",
|
||||
"collapse": "折叠",
|
||||
"footnote": "引用内容",
|
||||
"footnotes": "引用内容",
|
||||
"fullscreen": "已进入全屏模式,按 F11 退出",
|
||||
|
||||
@ -277,6 +277,7 @@
|
||||
"duplicate": "複製",
|
||||
"edit": "編輯",
|
||||
"expand": "展開",
|
||||
"collapse": "折疊",
|
||||
"footnote": "引用內容",
|
||||
"footnotes": "引用",
|
||||
"fullscreen": "已進入全螢幕模式,按 F11 結束",
|
||||
|
||||
@ -93,7 +93,6 @@ const FileItem: React.FC<FileItemProps> = ({ fileInfo, style }) => {
|
||||
}
|
||||
|
||||
const FileItemCard = styled.div`
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 0.5px solid var(--color-border);
|
||||
|
||||
@ -223,7 +223,7 @@ const ContentContainer = styled.div`
|
||||
`
|
||||
|
||||
const SideNav = styled.div`
|
||||
width: var(--assistants-width);
|
||||
width: var(--settings-width);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
padding: 7px 12px;
|
||||
user-select: none;
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import {
|
||||
ColumnHeightOutlined,
|
||||
CopyOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
PlusOutlined,
|
||||
RedoOutlined,
|
||||
SearchOutlined,
|
||||
SettingOutlined
|
||||
SettingOutlined,
|
||||
VerticalAlignMiddleOutlined
|
||||
} from '@ant-design/icons'
|
||||
import CustomTag from '@renderer/components/CustomTag'
|
||||
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 { formatFileSize } from '@renderer/utils'
|
||||
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 VirtualList from 'rc-virtual-list'
|
||||
import { FC } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -42,6 +44,7 @@ const fileTypes = [...bookExts, ...thirdPartyApplicationExts, ...documentExts, .
|
||||
|
||||
const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
const { t } = useTranslation()
|
||||
const [expandAll, setExpandAll] = useState(false)
|
||||
|
||||
const {
|
||||
base,
|
||||
@ -230,366 +233,389 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<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} />}
|
||||
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>
|
||||
}>
|
||||
<MainContainer>
|
||||
<HeaderContainer>
|
||||
<ModelInfo>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => KnowledgeSettingsPopup.show({ base })}
|
||||
size="small"
|
||||
/>
|
||||
<div className="model-row">
|
||||
<div className="label-column">
|
||||
<label>{t('models.embedding_model')}</label>
|
||||
</div>
|
||||
<div className="tag-column">
|
||||
{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>
|
||||
<Tooltip title={providerName} placement="bottom">
|
||||
<div className="tag-column">
|
||||
<Tag color="geekblue" style={{ borderRadius: 20, margin: 0 }}>
|
||||
{base.model.name}
|
||||
</Tag>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tag color="cyan" style={{ borderRadius: 20, margin: 0 }}>
|
||||
{t('models.dimensions', { dimensions: base.dimensions || 0 })}
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
<Tooltip title={rerankModelProviderName} placement="bottom">
|
||||
<div className="tag-column">
|
||||
<Tag color="green" style={{ borderRadius: 20, margin: 0 }}>
|
||||
{base.rerankModel?.name}
|
||||
</Tag>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</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 />
|
||||
</MainContent>
|
||||
<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} />}
|
||||
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 }) => {
|
||||
return (
|
||||
<HStack alignItems="center" gap={10}>
|
||||
<label>{label}</label>
|
||||
<label style={{ fontWeight: 600 }}>{label}</label>
|
||||
<CustomTag size={12} color={count ? '#008001' : '#cccccc'}>
|
||||
{count}
|
||||
</CustomTag>
|
||||
@ -606,35 +632,50 @@ const CollapseLabel = ({ label, count }: { label: string; count: number }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const MainContent = styled(Scrollbar)`
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
padding-bottom: 50px;
|
||||
padding: 15px;
|
||||
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`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 5px;
|
||||
color: var(--color-text-3);
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 50px;
|
||||
|
||||
.model-header {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.model-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.label-column {
|
||||
@ -672,10 +713,6 @@ const ClickableSpan = styled.span`
|
||||
width: 0;
|
||||
`
|
||||
|
||||
const BottomSpacer = styled.div`
|
||||
min-height: 20px;
|
||||
`
|
||||
|
||||
const StatusIconWrapper = styled.div`
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
@ -178,7 +178,7 @@ const MainContent = styled(Scrollbar)`
|
||||
`
|
||||
|
||||
const SideNav = styled.div`
|
||||
width: var(--assistants-width);
|
||||
min-width: var(--settings-width);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
padding: 12px 10px;
|
||||
display: flex;
|
||||
|
||||
@ -161,7 +161,7 @@ const DataSettings: FC = () => {
|
||||
<MenuList>
|
||||
{menuItems.map((item) =>
|
||||
item.isDivider ? (
|
||||
<DividerWithText key={item.key} text={item.text || ''} /> // 动态传递分隔符文字
|
||||
<DividerWithText key={item.key} text={item.text || ''} style={{ margin: '8px 0' }} /> // 动态传递分隔符文字
|
||||
) : (
|
||||
<ListItem
|
||||
key={item.key}
|
||||
|
||||
@ -251,7 +251,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
|
||||
key={group}
|
||||
label={
|
||||
<Flex align="center" gap={10}>
|
||||
<span>{group}</span>
|
||||
<span style={{ fontWeight: 600 }}>{group}</span>
|
||||
<CustomTag color="#02B96B" size={10}>
|
||||
{modelGroups[group].length}
|
||||
</CustomTag>
|
||||
@ -308,7 +308,6 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
|
||||
<Flex gap={4} align="center">
|
||||
{renderLatencyText(modelStatus)}
|
||||
{renderStatusIndicator(modelStatus)}
|
||||
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => !isChecking && onEditModel(model)}
|
||||
|
||||
@ -274,7 +274,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
}, [apiKey, provider, updateProvider])
|
||||
|
||||
return (
|
||||
<SettingContainer theme={theme}>
|
||||
<SettingContainer theme={theme} style={{ background: 'var(--color-background)' }}>
|
||||
<SettingTitle>
|
||||
<Flex align="center" gap={8}>
|
||||
<ProviderName>{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}</ProviderName>
|
||||
|
||||
@ -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