refactor: align lucide icons in antd button, use more lucide icons (#8805)

* refactor: align lucide icons in antd button

* refactor(AssistantsTab): use lucide icon and typography in add assistant button

* refactor: use lucide icons for assistant item dropdown

* refactor: use lucide icons in topic item dropdown

* refactor: use lucide icon in InfoTooltip, align ApiOptionsSettings expand icon

* refactor: use lucide icons in TokenCount

* refactor: use brush in assistant item dropdown

* test: update snapshot

* test: mock tooltip

* fix: token count alignment

* refactor: update icons in MessageMenubar, bump antd

* refactor: use lucide icons in MessageTools, make colors consistent

* refactor: use lucide icons in ProviderSetting

* test: simplify test with mocks

* refactor: use lucide icons in knowledge base dropdown

* refactor: export all custom icons, use EditIcon for lucide pen

* refactor: use lucide copy for CopyIcon, update tests

* refactor: use lucide icons in MessageMenubar

* refactor: improve pause and send button style

* refactor: export SvgSpinners180Ring as LoadingIcon

* refactor: use lucide icons in Agents, use DeleteIcon

* refactor: use Pencil as EditIcon

* fix: i18n key missing

* refactor: use lucide icons in Files

* refactor: use lucide icons in KnowledgeBase items

* refactor: use lucide icons in assistant settings

* refactor: use lucide icons in memory settings, add UserSelector

* chore: remove duplicate memory component

* refactor: use lucide icons in ProviderList

* refactor: use lucide icons in QuickPhraseSettings

* refactor: use lucide icons in McpSettings

* refactor: use lucide icons in DataSettings

* refactor: use lucide icons in DefaultAssistantSettings

* refactor: add icon to save

* refactor: add lucide-custom

* fix: icon position in ModelEditContent

* refactor: use ListMinus in ManageModelsList

* refactor: improve TokenCount alignment

* fix: topic pin/unpin i18n

* fix: self review

* fix: simplify knowledge base dropdown

* fix: remove plus icon color

* refactor: add ResetIcon and RefreshIcon
This commit is contained in:
one 2025-08-04 19:07:04 +08:00 committed by GitHub
parent 41e8a445ca
commit 0e1df2460e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 854 additions and 2162 deletions

View File

@ -1,5 +1,5 @@
diff --git a/es/dropdown/dropdown.js b/es/dropdown/dropdown.js
index 986877a762b9ad0aca596a8552732cd12d2eaabb..1f18aa2ea745e68950e4cee16d4d655f5c835fd5 100644
index 2e45574398ff68450022a0078e213cc81fe7454e..58ba7789939b7805a89f92b93d222f8fb1168bdf 100644
--- a/es/dropdown/dropdown.js
+++ b/es/dropdown/dropdown.js
@@ -2,7 +2,7 @@
@ -11,7 +11,7 @@ index 986877a762b9ad0aca596a8552732cd12d2eaabb..1f18aa2ea745e68950e4cee16d4d655f
import classNames from 'classnames';
import RcDropdown from 'rc-dropdown';
import useEvent from "rc-util/es/hooks/useEvent";
@@ -158,8 +158,10 @@ const Dropdown = props => {
@@ -160,8 +160,10 @@ const Dropdown = props => {
className: `${prefixCls}-menu-submenu-arrow`
}, direction === 'rtl' ? (/*#__PURE__*/React.createElement(LeftOutlined, {
className: `${prefixCls}-menu-submenu-arrow-icon`
@ -24,22 +24,8 @@ index 986877a762b9ad0aca596a8552732cd12d2eaabb..1f18aa2ea745e68950e4cee16d4d655f
}))),
mode: "vertical",
selectable: false,
diff --git a/es/dropdown/style/index.js b/es/dropdown/style/index.js
index 768c01783002c6901c85a73061ff6b3e776a60ce..39b1b95a56cdc9fb586a193c3adad5141f5cf213 100644
--- a/es/dropdown/style/index.js
+++ b/es/dropdown/style/index.js
@@ -240,7 +240,8 @@ const genBaseStyle = token => {
marginInlineEnd: '0 !important',
color: token.colorTextDescription,
fontSize: fontSizeIcon,
- fontStyle: 'normal'
+ fontStyle: 'normal',
+ marginTop: 3,
}
}
}),
diff --git a/es/select/useIcons.js b/es/select/useIcons.js
index 959115be936ef8901548af2658c5dcfdc5852723..c812edd52123eb0faf4638b1154fcfa1b05b513b 100644
index 572aaaa0899f429cbf8a7181f2eeada545f76dcb..4e175c8d7713dd6422f8bcdc74ee671a835de6ce 100644
--- a/es/select/useIcons.js
+++ b/es/select/useIcons.js
@@ -4,10 +4,10 @@ import * as React from 'react';
@ -51,10 +37,10 @@ index 959115be936ef8901548af2658c5dcfdc5852723..c812edd52123eb0faf4638b1154fcfa1
import SearchOutlined from "@ant-design/icons/es/icons/SearchOutlined";
import { devUseWarning } from '../_util/warning';
+import { ChevronDown } from 'lucide-react';
export default function useIcons(_ref) {
let {
suffixIcon,
@@ -56,8 +56,10 @@ export default function useIcons(_ref) {
export default function useIcons({
suffixIcon,
clearIcon,
@@ -54,8 +54,10 @@ export default function useIcons({
className: iconCls
}));
}

View File

@ -162,7 +162,7 @@
"@viz-js/lang-dot": "^1.0.5",
"@viz-js/viz": "^3.14.0",
"@xyflow/react": "^12.4.4",
"antd": "patch:antd@npm%3A5.24.7#~/.yarn/patches/antd-npm-5.24.7-356a553ae5.patch",
"antd": "patch:antd@npm%3A5.26.7#~/.yarn/patches/antd-npm-5.26.7-029c5c381a.patch",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
"axios": "^1.7.3",

View File

@ -12,6 +12,13 @@
outline: none;
}
// Align lucide icon in Button
.ant-btn .ant-btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.ant-tabs-tabpane:focus-visible {
outline: none;
}
@ -84,6 +91,14 @@
max-height: 50vh;
overflow-y: auto;
border: 0.5px solid var(--color-border);
// Align lucide icon in dropdown menu item extra
.ant-dropdown-menu-submenu-expand-icon,
.ant-dropdown-menu-item-extra {
display: inline-flex;
align-items: center;
justify-content: center;
}
}
.ant-dropdown-arrow + .ant-dropdown-menu {
border: none;
@ -96,6 +111,10 @@
background-color: var(--ant-color-bg-elevated);
overflow: hidden;
border-radius: var(--ant-border-radius-lg);
.ant-dropdown-menu-submenu-title {
align-items: center;
}
}
.ant-popover {

View File

@ -32,7 +32,7 @@
--color-border: #ffffff19;
--color-border-soft: #ffffff10;
--color-border-mute: #ffffff05;
--color-error: #f44336;
--color-error: #ff4d50;
--color-link: #338cff;
--color-code-background: #323232;
--color-hover: rgba(40, 40, 40, 1);
@ -73,8 +73,8 @@
--list-item-border-radius: 10px;
--color-status-success: #52c41a;
--color-status-error: #ff4d4f;
--color-status-success: green;
--color-status-error: var(--color-error);
--color-status-warning: #faad14;
}
@ -112,7 +112,7 @@
--color-border: #00000019;
--color-border-soft: #00000010;
--color-border-mute: #00000005;
--color-error: #f44336;
--color-error: #ff4d50;
--color-link: #1677ff;
--color-code-background: #e3e3e3;
--color-hover: var(--color-white-mute);

View File

@ -1,5 +1,5 @@
import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon } from '@renderer/components/Icons'
import { AsyncInitializer } from '@renderer/utils/asyncInitializer'
import { Flex, Spin } from 'antd'
import { debounce } from 'lodash'
@ -86,7 +86,7 @@ const GraphvizPreview: React.FC<BasicPreviewProps> = ({ children, setTools }) =>
}, [children, debouncedRender])
return (
<Spin spinning={isLoading} indicator={<SvgSpinners180Ring color="var(--color-text-2)" />}>
<Spin spinning={isLoading} indicator={<LoadingIcon color="var(--color-text-2)" />}>
<Flex vertical style={{ minHeight: isLoading ? '2rem' : 'auto' }}>
{error && <PreviewError>{error}</PreviewError>}
<StyledGraphviz ref={graphvizRef} className="graphviz special-preview" />

View File

@ -1,6 +1,6 @@
import { nanoid } from '@reduxjs/toolkit'
import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon } from '@renderer/components/Icons'
import { useMermaid } from '@renderer/hooks/useMermaid'
import { Flex, Spin } from 'antd'
import { debounce } from 'lodash'
@ -139,7 +139,7 @@ const MermaidPreview: React.FC<BasicPreviewProps> = ({ children, setTools }) =>
const isLoading = isLoadingMermaid || isRendering
return (
<Spin spinning={isLoading} indicator={<SvgSpinners180Ring color="var(--color-text-2)" />}>
<Spin spinning={isLoading} indicator={<LoadingIcon color="var(--color-text-2)" />}>
<Flex vertical style={{ minHeight: isLoading ? '2rem' : 'auto' }}>
{(mermaidError || error) && <PreviewError>{mermaidError || error}</PreviewError>}
<StyledMermaid ref={mermaidRef} className="mermaid special-preview" />

View File

@ -1,7 +1,7 @@
import { LoadingOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import CodeEditor from '@renderer/components/CodeEditor'
import { CodeTool, CodeToolbar, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar'
import { LoadingIcon } from '@renderer/components/Icons'
import { useSettings } from '@renderer/hooks/useSettings'
import { pyodideService } from '@renderer/services/PyodideService'
import { extractTitle } from '@renderer/utils/formats'
@ -173,7 +173,7 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
registerTool({
...TOOL_SPECS.run,
icon: isRunning ? <LoadingOutlined /> : <CirclePlay className="icon" />,
icon: isRunning ? <LoadingIcon /> : <CirclePlay className="icon" />,
tooltip: t('code_block.run'),
onClick: () => !isRunning && handleRunScript()
})

View File

@ -1,7 +1,5 @@
import { FC } from 'react'
import { Copy } from 'lucide-react'
const CopyIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
return <i {...props} className={`iconfont icon-copy ${props.className}`} />
}
const CopyIcon = (props: React.ComponentProps<typeof Copy>) => <Copy size="1rem" {...props} />
export default CopyIcon

View File

@ -0,0 +1,5 @@
import { Trash } from 'lucide-react'
const DeleteIcon = (props: React.ComponentProps<typeof Trash>) => <Trash size="1rem" {...props} />
export default DeleteIcon

View File

@ -0,0 +1,5 @@
import { Pencil } from 'lucide-react'
const EditIcon = (props: React.ComponentProps<typeof Pencil>) => <Pencil size="1rem" {...props} />
export default EditIcon

View File

@ -0,0 +1,5 @@
import { RefreshCw } from 'lucide-react'
const RefreshIcon = (props: React.ComponentProps<typeof RefreshCw>) => <RefreshCw size="1rem" {...props} />
export default RefreshIcon

View File

@ -0,0 +1,5 @@
import { RotateCcw } from 'lucide-react'
const ResetIcon = (props: React.ComponentProps<typeof RotateCcw>) => <RotateCcw size="1rem" {...props} />
export default ResetIcon

View File

@ -1,6 +1,7 @@
import { SVGProps } from 'react'
export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement>) {
export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement> & { size?: number | string }) {
const { size = '1em', ...svgProps } = props
// 避免与全局样式冲突
const animationClassName = 'svg-spinner-anim-180-ring'
@ -25,11 +26,11 @@ export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement>) {
</style>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
width={size}
height={size}
viewBox="0 0 24 24"
{...props}
className={`${animationClassName} ${props.className || ''}`.trim()}>
{...svgProps}
className={`${animationClassName} ${svgProps.className || ''}`.trim()}>
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
<path
fill="currentColor"

View File

@ -1,15 +0,0 @@
import { render } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import CopyIcon from '../CopyIcon'
describe('CopyIcon', () => {
it('should match snapshot with props and className', () => {
const onClick = vi.fn()
const { container } = render(
<CopyIcon className="custom-class" onClick={onClick} title="Copy to clipboard" data-testid="copy-icon" />
)
expect(container.firstChild).toMatchSnapshot()
})
})

View File

@ -1,9 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`CopyIcon > should match snapshot with props and className 1`] = `
<i
class="iconfont icon-copy custom-class"
data-testid="copy-icon"
title="Copy to clipboard"
/>
`;

View File

@ -0,0 +1,19 @@
export { default as CopyIcon } from './CopyIcon'
export { default as DeleteIcon } from './DeleteIcon'
export * from './DownloadIcons'
export { default as EditIcon } from './EditIcon'
export { default as FallbackFavicon } from './FallbackFavicon'
export { default as MinAppIcon } from './MinAppIcon'
export * from './NutstoreIcons'
export { default as OcrIcon } from './OcrIcon'
export { default as ReasoningIcon } from './ReasoningIcon'
export { default as RefreshIcon } from './RefreshIcon'
export { default as ResetIcon } from './ResetIcon'
export * from './SVGIcon'
export { default as LoadingIcon } from './SvgSpinners180Ring'
export { default as ToolIcon } from './ToolIcon'
export { default as ToolsCallingIcon } from './ToolsCallingIcon'
export { default as UnWrapIcon } from './UnWrapIcon'
export { default as VisionIcon } from './VisionIcon'
export { default as WebSearchIcon } from './WebSearchIcon'
export { default as WrapIcon } from './WrapIcon'

View File

@ -1,17 +1,18 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { Tooltip, TooltipProps } from 'antd'
import { Info } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface InfoTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const InfoTooltip = ({ iconColor = 'var(--color-text-3)', iconStyle, ...rest }: InfoTooltipProps) => {
const InfoTooltip = ({ iconColor = 'var(--color-text-3)', iconSize = 14, iconStyle, ...rest }: InfoTooltipProps) => {
return (
<Tooltip {...rest}>
<InfoCircleOutlined style={{ color: iconColor, ...iconStyle }} role="img" aria-label="Information" />
<Info size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Information" />
</Tooltip>
)
}

View File

@ -1,10 +1,10 @@
import { loggerService } from '@logger'
import AiProvider from '@renderer/aiCore'
import { RefreshIcon } from '@renderer/components/Icons'
import { useProvider } from '@renderer/hooks/useProvider'
import { Model } from '@renderer/types'
import { getErrorMessage } from '@renderer/utils'
import { Button, InputNumber, Space, Tooltip } from 'antd'
import { RefreshCw } from 'lucide-react'
import { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -77,7 +77,7 @@ const InputEmbeddingDimension = ({
<Button
role="button"
aria-label="Get embedding dimension"
icon={<RefreshCw size={16} />}
icon={<RefreshIcon size={16} />}
loading={loading}
disabled={disabled}
onClick={handleFetchDimension}

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger'
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon } from '@renderer/components/Icons'
import NewApiAddModelPopup from '@renderer/components/ModelList/NewApiAddModelPopup'
import NewApiBatchAddModelPopup from '@renderer/components/ModelList/NewApiBatchAddModelPopup'
import { TopView } from '@renderer/components/TopView'
@ -22,7 +22,7 @@ import { Button, Empty, Flex, Modal, Spin, Tabs, Tooltip } from 'antd'
import Input from 'antd/es/input/Input'
import { groupBy, isEmpty, uniqBy } from 'lodash'
import { debounce } from 'lodash'
import { Eraser, ListPlus, RefreshCcw, Search } from 'lucide-react'
import { ListMinus, ListPlus, RefreshCcw, Search } from 'lucide-react'
import { useCallback, useEffect, useMemo, useOptimistic, useRef, useState, useTransition } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -251,7 +251,7 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
mouseLeaveDelay={0}>
<Button
type="default"
icon={isAllFilteredInProvider ? <Eraser size={18} /> : <ListPlus size={18} />}
icon={isAllFilteredInProvider ? <ListMinus size={18} /> : <ListPlus size={18} />}
size="large"
onClick={(e) => {
e.stopPropagation()
@ -331,7 +331,7 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
</SearchContainer>
<Spin
spinning={isLoading}
indicator={<SvgSpinners180Ring color="var(--color-text-2)" style={{ opacity: loadingModels ? 1 : 0 }} />}>
indicator={<LoadingIcon color="var(--color-text-2)" style={{ opacity: loadingModels ? 1 : 0 }} />}>
<ListContainer>
{loadingModels || isEmpty(list) ? (
<Empty

View File

@ -13,7 +13,7 @@ import { Model, ModelCapability, ModelType, Provider } from '@renderer/types'
import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils'
import { Button, Checkbox, Divider, Flex, Form, Input, InputNumber, message, Modal, Select, Switch } from 'antd'
import { cloneDeep } from 'lodash'
import { ChevronDown, ChevronUp } from 'lucide-react'
import { ChevronDown, ChevronUp, SaveIcon } from 'lucide-react'
import { FC, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -167,8 +167,9 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
const val = form.getFieldValue('name')
navigator.clipboard.writeText((val.id || model.id) as string)
message.success(t('message.copied'))
}}>
<CopyIcon /> {t('chat.topics.copy.title')}
}}
icon={<CopyIcon size={16} />}>
{t('chat.topics.copy.title')}
</Button>
</Flex>
</Form.Item>
@ -210,7 +211,7 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ provider, model, onUpdate
style={{ color: 'var(--color-text-3)' }}>
{t('settings.moresetting.label')}
</Button>
<Button type="primary" htmlType="submit" size="middle">
<Button type="primary" htmlType="submit" icon={<SaveIcon size={16} />}>
{t('common.save')}
</Button>
</Flex>

View File

@ -1,7 +1,6 @@
import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar'
import CustomTag from '@renderer/components/CustomTag'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import AddModelPopup from '@renderer/components/ModelList/AddModelPopup'
import EditModelPopup from '@renderer/components/ModelList/EditModelPopup'
@ -160,7 +159,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
</HStack>
</HStack>
</SettingSubtitle>
<Spin spinning={isLoading} indicator={<SvgSpinners180Ring color="var(--color-text-2)" />}>
<Spin spinning={isLoading} indicator={<LoadingIcon color="var(--color-text-2)" />}>
{displayedModelGroups && !isEmpty(displayedModelGroups) ? (
<Flex gap={12} vertical>
{Object.keys(displayedModelGroups).map((group, i) => (

View File

@ -1,4 +1,5 @@
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
import { EditIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
import { getModelLogo } from '@renderer/config/models'
@ -6,7 +7,7 @@ import { Model } from '@renderer/types'
import { ModelWithStatus } from '@renderer/types/healthCheck'
import { maskApiKey } from '@renderer/utils/api'
import { Avatar, Button, Tooltip } from 'antd'
import { Minus, Pen } from 'lucide-react'
import { Minus } from 'lucide-react'
import React, { memo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -51,7 +52,7 @@ const ModelListItem: React.FC<ModelListItemProps> = ({ ref, model, modelStatus,
<HealthStatusIndicator results={healthResults} loading={isChecking} showLatency />
<HStack alignItems="center" gap={0}>
<Tooltip title={t('models.edit')} mouseLeaveDelay={0}>
<Button type="text" onClick={() => onEdit(model)} disabled={disabled} icon={<Pen size={14} />} />
<Button type="text" onClick={() => onEdit(model)} disabled={disabled} icon={<EditIcon size={14} />} />
</Tooltip>
<Tooltip title={t('settings.models.manage.remove_model')} mouseLeaveDelay={0}>
<Button type="text" onClick={() => onRemove(model)} disabled={disabled} icon={<Minus size={14} />} />

View File

@ -1,9 +1,10 @@
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
import { EditIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import { ApiKeyWithStatus } from '@renderer/types/healthCheck'
import { maskApiKey } from '@renderer/utils/api'
import { Button, Flex, Input, InputRef, List, Popconfirm, Tooltip, Typography } from 'antd'
import { Check, Minus, Pen, X } from 'lucide-react'
import { Check, Minus, X } from 'lucide-react'
import { FC, memo, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -148,7 +149,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
</Tooltip>
)}
<Tooltip title={t('common.edit')} mouseLeaveDelay={0}>
<Button type="text" icon={<Pen size={16} />} onClick={handleEdit} disabled={disabled} />
<Button type="text" icon={<EditIcon size={16} />} onClick={handleEdit} disabled={disabled} />
</Tooltip>
<Popconfirm
title={t('common.delete_confirm')}

View File

@ -1,4 +1,4 @@
import { PlusOutlined } from '@ant-design/icons'
import { DeleteIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import Scrollbar from '@renderer/components/Scrollbar'
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
@ -8,7 +8,7 @@ import { SettingHelpText } from '@renderer/pages/settings'
import { isProviderSupportAuth } from '@renderer/services/ProviderService'
import { ApiKeyWithStatus, HealthStatus } from '@renderer/types/healthCheck'
import { Button, Card, Flex, List, Popconfirm, Space, Tooltip, Typography } from 'antd'
import { Trash } from 'lucide-react'
import { Plus } from 'lucide-react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -140,7 +140,12 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
cancelText={t('common.cancel')}
okButtonProps={{ danger: true }}>
<Tooltip title={t('settings.provider.remove_invalid_keys')} placement="top" mouseLeaveDelay={0}>
<Button type="text" icon={<Trash size={16} />} disabled={isChecking || !!pendingNewKey} danger />
<Button
type="text"
icon={<DeleteIcon size={16} className="lucide-custom" />}
disabled={isChecking || !!pendingNewKey}
danger
/>
</Tooltip>
</Popconfirm>
@ -161,7 +166,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
key="add"
type="primary"
onClick={handleAddNew}
icon={<PlusOutlined />}
icon={<Plus size={16} />}
autoFocus={shouldAutoFocus()}
disabled={isChecking || !!pendingNewKey}>
{t('common.add')}

View File

@ -1,7 +1,8 @@
import { CopyIcon, DeleteIcon } from '@renderer/components/Icons'
import { useChatContext } from '@renderer/hooks/useChatContext'
import { Topic } from '@renderer/types'
import { Button, Tooltip } from 'antd'
import { Copy, Save, Trash, X } from 'lucide-react'
import { Save, X } from 'lucide-react'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -49,7 +50,7 @@ const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
shape="circle"
color="default"
variant="text"
icon={<Copy size={16} />}
icon={<CopyIcon size={16} />}
disabled={isActionDisabled}
onClick={() => handleAction('copy')}
/>
@ -60,7 +61,7 @@ const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
color="danger"
variant="text"
danger
icon={<Trash size={16} />}
icon={<DeleteIcon size={16} className="lucide-custom" />}
onClick={() => handleAction('delete')}
/>
</Tooltip>

View File

@ -1,9 +1,25 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import InfoTooltip from '../InfoTooltip'
vi.mock('antd', () => ({
Tooltip: ({ children, title }: { children: React.ReactNode; title: string }) => (
<div>
{children}
{title && <div>{title}</div>}
</div>
)
}))
vi.mock('lucide-react', () => ({
Info: ({ ref, ...props }) => (
<div {...props} ref={ref} role="img" aria-label="Information">
Info
</div>
)
}))
describe('InfoTooltip', () => {
it('should match snapshot', () => {
const { container } = render(
@ -12,13 +28,11 @@ describe('InfoTooltip', () => {
expect(container.firstChild).toMatchSnapshot()
})
it('should show tooltip on hover', async () => {
it('should pass title prop to the underlying Tooltip component', () => {
const tooltipText = 'This is helpful information'
render(<InfoTooltip title={tooltipText} />)
const icon = screen.getByRole('img', { name: 'Information' })
await userEvent.hover(icon)
expect(await screen.findByText(tooltipText)).toBeInTheDocument()
expect(screen.getByRole('img', { name: 'Information' })).toBeInTheDocument()
expect(screen.getByText(tooltipText)).toBeInTheDocument()
})
})

View File

@ -1,27 +1,71 @@
import { render, screen, waitFor } from '@testing-library/react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import InputEmbeddingDimension from '../InputEmbeddingDimension'
const mocks = vi.hoisted(() => {
return {
aiCore: {
getEmbeddingDimensions: vi.fn()
},
i18n: {
t: (k: string) => {
const translations: Record<string, string> = {
'knowledge.embedding_model_required': '请选择嵌入模型',
'knowledge.provider_not_found': '找不到提供商',
'message.error.get_embedding_dimensions': '获取嵌入维度失败',
'knowledge.dimensions_size_placeholder': '请输入维度大小',
'knowledge.dimensions_auto_set': '自动设置维度'
}
return translations[k] || k
const mocks = vi.hoisted(() => ({
aiCore: {
getEmbeddingDimensions: vi.fn()
},
i18n: {
t: (k: string) => {
const translations: Record<string, string> = {
'knowledge.embedding_model_required': '请选择嵌入模型',
'knowledge.provider_not_found': '找不到提供商',
'message.error.get_embedding_dimensions': '获取嵌入维度失败',
'knowledge.dimensions_size_placeholder': '请输入维度大小',
'knowledge.dimensions_auto_set': '自动设置维度'
}
return translations[k] || k
}
}
}))
// Mock antd components to prevent flaky snapshot tests
vi.mock('antd', () => {
const MockSpaceCompact: React.FC<React.PropsWithChildren<{ style?: React.CSSProperties }>> = ({
children,
style
}) => (
<div data-testid="space-compact" style={style}>
{children}
</div>
)
const MockInputNumber = ({ ref, value, onChange, placeholder, disabled, style }: any) => (
<input
ref={ref}
type="number"
data-testid="input-number"
placeholder={placeholder}
value={value ?? ''}
onChange={(e) => onChange(e.target.valueAsNumber)}
disabled={disabled}
style={style}
/>
)
const MockButton: React.FC<any> = ({ children, onClick, loading, disabled, icon, ...rest }) => (
<button type="button" onClick={onClick} disabled={disabled || loading} {...rest}>
{loading ? 'Loading...' : icon}
{children}
</button>
)
const MockTooltip: React.FC<React.PropsWithChildren<{ title: string }>> = ({ children, title }) => (
<div data-testid="tooltip" data-title={title}>
{children}
</div>
)
return {
Button: MockButton,
InputNumber: MockInputNumber,
Space: { Compact: MockSpaceCompact },
Tooltip: MockTooltip
}
})
// Mock dependencies
@ -46,20 +90,10 @@ vi.mock('react-i18next', () => ({
}
}))
// Mock logger
vi.mock('@logger', () => ({
loggerService: {
withContext: () => ({
warn: vi.fn(),
error: vi.fn()
})
}
}))
vi.mock('lucide-react', () => ({
RefreshCw: (props: React.SVGProps<SVGSVGElement>) => (
vi.mock('@renderer/components/Icons', () => ({
RefreshIcon: (props: React.SVGProps<SVGSVGElement>) => (
<svg data-testid="refresh-icon" aria-label="refresh" role="img" {...props}>
RefreshCw
RefreshIcon
</svg>
)
}))
@ -119,13 +153,11 @@ describe('InputEmbeddingDimension', () => {
describe('functionality', () => {
it('should call onChange when input value changes', async () => {
const handleChange = vi.fn()
const user = userEvent.setup()
render(<InputEmbeddingDimension model={mockModel} onChange={handleChange} />)
const input = screen.getByPlaceholderText('请输入维度大小')
await user.clear(input)
await user.type(input, '2048')
fireEvent.change(input, { target: { value: '2048' } })
expect(handleChange).toHaveBeenCalledWith(2048)
})
@ -182,7 +214,6 @@ describe('InputEmbeddingDimension', () => {
it('should handle null value correctly', async () => {
const handleChange = vi.fn()
const user = userEvent.setup()
render(<InputEmbeddingDimension model={mockModel} value={null} onChange={handleChange} />)
@ -190,7 +221,7 @@ describe('InputEmbeddingDimension', () => {
expect(input.value).toBe('')
// Should allow typing new value
await user.type(input, '1024')
fireEvent.change(input, { target: { value: '1024' } })
expect(handleChange).toHaveBeenCalledWith(1024)
})
})

View File

@ -1,28 +1,18 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`InfoTooltip > should match snapshot 1`] = `
<span
aria-describedby="test-id"
aria-label="Information"
class="anticon anticon-info-circle"
role="img"
style="color: rgb(24, 144, 255); font-size: 16px;"
>
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<div>
<div
aria-label="Information"
color="#1890ff"
role="img"
size="14"
style="font-size: 16px;"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
/>
<path
d="M464 336a48 48 0 1096 0 48 48 0 10-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
Info
</div>
<div>
Test tooltip
</div>
</div>
`;

View File

@ -2,95 +2,24 @@
exports[`InputEmbeddingDimension > basic rendering > should match snapshot with all props 1`] = `
<div
class="ant-space-compact css-dev-only-do-not-override-1261szd"
data-testid="space-compact"
style="width: 100%;"
>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined ant-input-number-compact-item ant-input-number-compact-first-item"
<input
data-testid="input-number"
placeholder="请输入维度大小"
style="flex: 1;"
type="number"
value="1536"
/>
<div
data-testid="tooltip"
data-title="自动设置维度"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-valuemin="1"
aria-valuenow="1536"
autocomplete="off"
class="ant-input-number-input"
placeholder="请输入维度大小"
role="spinbutton"
step="1"
value="1536"
/>
</div>
</div>
<button
aria-describedby="test-id"
aria-label="Get embedding dimension"
class="ant-btn css-dev-only-do-not-override-1261szd ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-icon-only ant-btn-compact-item ant-btn-compact-last-item"
role="button"
type="button"
>
<span
class="ant-btn-icon"
<button
aria-label="Get embedding dimension"
role="button"
type="button"
>
<svg
aria-label="refresh"
@ -98,124 +27,37 @@ exports[`InputEmbeddingDimension > basic rendering > should match snapshot with
role="img"
size="16"
>
RefreshCw
RefreshIcon
</svg>
</span>
</button>
</button>
</div>
</div>
`;
exports[`InputEmbeddingDimension > basic rendering > should match snapshot with loading state 1`] = `
<div
class="ant-space-compact css-dev-only-do-not-override-1261szd"
data-testid="space-compact"
style="width: 100%;"
>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined ant-input-number-compact-item ant-input-number-compact-first-item"
<input
data-testid="input-number"
placeholder="请输入维度大小"
style="flex: 1;"
type="number"
value=""
/>
<div
data-testid="tooltip"
data-title="自动设置维度"
>
<div
class="ant-input-number-handler-wrap"
<button
aria-label="Get embedding dimension"
disabled=""
role="button"
type="button"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-valuemin="1"
autocomplete="off"
class="ant-input-number-input"
placeholder="请输入维度大小"
role="spinbutton"
step="1"
value=""
/>
</div>
Loading...
</button>
</div>
<button
aria-describedby="test-id"
aria-label="Get embedding dimension"
class="ant-btn css-dev-only-do-not-override-1261szd ant-btn-default ant-btn-color-default ant-btn-variant-outlined ant-btn-icon-only ant-btn-loading ant-btn-compact-item ant-btn-compact-last-item"
role="button"
type="button"
>
<span
class="ant-btn-icon ant-btn-loading-icon"
>
<span
aria-label="loading"
class="anticon anticon-loading anticon-spin"
role="img"
>
<svg
aria-hidden="true"
data-icon="loading"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
/>
</svg>
</span>
</span>
</button>
</div>
`;

View File

@ -596,7 +596,7 @@
"list": "Topic List",
"move_to": "Move to",
"new": "New Topic",
"pinned": "Pinned Topics",
"pin": "Pin Topic",
"prompt": {
"edit": {
"title": "Edit Topic Prompts"
@ -605,7 +605,7 @@
"tips": "Topic Prompts: Additional supplementary prompts provided for the current topic"
},
"title": "Topics",
"unpinned": "Unpinned Topics"
"unpin": "Unpin Topic"
},
"translate": "Translate"
},
@ -1552,6 +1552,7 @@
"mode": {
"edit": "Edit",
"generate": "Draw",
"merge": "Merge",
"remix": "Remix",
"upscale": "Upscale"
},

View File

@ -596,7 +596,7 @@
"list": "トピックリスト",
"move_to": "移動先",
"new": "新しいトピック",
"pinned": "トピックを固定",
"pin": "トピックを固定",
"prompt": {
"edit": {
"title": "トピック提示語を編集する"
@ -605,7 +605,7 @@
"tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供"
},
"title": "トピック",
"unpinned": "固定解除"
"unpin": "固定解除"
},
"translate": "翻訳"
},
@ -1552,6 +1552,7 @@
"mode": {
"edit": "部分編集",
"generate": "画像生成",
"merge": "マージ",
"remix": "混合",
"upscale": "拡大"
},

View File

@ -596,7 +596,7 @@
"list": "Список топиков",
"move_to": "Переместить в",
"new": "Новый топик",
"pinned": "Закрепленные темы",
"pin": "Закрепленные темы",
"prompt": {
"edit": {
"title": "Редактировать подсказки темы"
@ -605,7 +605,7 @@
"tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы"
},
"title": "Топики",
"unpinned": "Открепленные темы"
"unpin": "Открепленные темы"
},
"translate": "Перевести"
},
@ -1552,6 +1552,7 @@
"mode": {
"edit": "Редактирование",
"generate": "Рисование",
"merge": "Слияние",
"remix": "Смешивание",
"upscale": "Увеличение"
},

View File

@ -596,7 +596,7 @@
"list": "话题列表",
"move_to": "移动到",
"new": "开始新对话",
"pinned": "固定话题",
"pin": "固定话题",
"prompt": {
"edit": {
"title": "编辑话题提示词"
@ -605,7 +605,7 @@
"tips": "话题提示词:针对当前话题提供额外的补充提示词"
},
"title": "话题",
"unpinned": "取消固定"
"unpin": "取消固定"
},
"translate": "翻译"
},
@ -1552,6 +1552,7 @@
"mode": {
"edit": "编辑",
"generate": "绘图",
"merge": "合并",
"remix": "混合",
"upscale": "高清增强"
},

View File

@ -596,7 +596,7 @@
"list": "話題列表",
"move_to": "移動到",
"new": "開始新對話",
"pinned": "固定話題",
"pin": "固定話題",
"prompt": {
"edit": {
"title": "編輯話題提示詞"
@ -605,7 +605,7 @@
"tips": "話題提示詞:針對目前話題提供額外的補充提示詞"
},
"title": "話題",
"unpinned": "取消固定"
"unpin": "取消固定"
},
"translate": "翻譯"
},
@ -1552,6 +1552,7 @@
"mode": {
"edit": "編輯",
"generate": "繪圖",
"merge": "合併",
"remix": "混合",
"upscale": "放大"
},

View File

@ -596,7 +596,7 @@
"list": "Λίστα θεμάτων",
"move_to": "Μετακίνηση στο",
"new": "Ξεκινήστε νέα συζήτηση",
"pinned": "Σταθερά θέματα",
"pin": "Σταθερά θέματα",
"prompt": {
"edit": {
"title": "Επεξεργασία προσδοκώμενων όριων"
@ -605,7 +605,7 @@
"tips": "Προσδοκώμενα όρια: προσθέτει επιπλέον επιστημονικές προσθήκες για το παρόν θέμα"
},
"title": "Θέματα",
"unpinned": "Αποστέλλω"
"unpin": "Ξεκαρφίτσωμα"
},
"translate": "Μετάφραση"
},

View File

@ -596,7 +596,7 @@
"list": "Lista de temas",
"move_to": "Mover a",
"new": "Iniciar nueva conversación",
"pinned": "Fijar tema",
"pin": "Fijar tema",
"prompt": {
"edit": {
"title": "Editar palabras clave del tema"
@ -605,7 +605,7 @@
"tips": "Palabras clave del tema: proporcionar indicaciones adicionales para el tema actual"
},
"title": "Tema",
"unpinned": "Quitar fijación"
"unpin": "Quitar fijación"
},
"translate": "Traducir"
},

View File

@ -596,7 +596,7 @@
"list": "Liste des sujets",
"move_to": "Déplacer vers",
"new": "Commencer une nouvelle conversation",
"pinned": "Fixer le sujet",
"pin": "Fixer le sujet",
"prompt": {
"edit": {
"title": "Modifier les indicateurs de sujet"
@ -605,7 +605,7 @@
"tips": "Indicateurs de sujet : fournir des indications supplémentaires pour le sujet actuel"
},
"title": "Sujet",
"unpinned": "Annuler le fixage"
"unpin": "Annuler le fixage"
},
"translate": "Traduire"
},

View File

@ -596,7 +596,7 @@
"list": "Lista de tópicos",
"move_to": "Mover para",
"new": "Começar nova conversa",
"pinned": "Fixar tópico",
"pin": "Fixar tópico",
"prompt": {
"edit": {
"title": "Editar prompt do tópico"
@ -605,7 +605,7 @@
"tips": "Prompt do tópico: fornecer prompts adicionais para o tópico atual"
},
"title": "Tópicos",
"unpinned": "Desfixar"
"unpin": "Desfixar"
},
"translate": "Traduzir"
},

View File

@ -1,12 +1,5 @@
import {
DeleteOutlined,
EditOutlined,
EllipsisOutlined,
ExportOutlined,
PlusOutlined,
SortAscendingOutlined
} from '@ant-design/icons'
import CustomTag from '@renderer/components/CustomTag'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { useAgents } from '@renderer/hooks/useAgents'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { createAssistantFromAgent } from '@renderer/services/AssistantService'
@ -14,6 +7,7 @@ import type { Agent } from '@renderer/types'
import { getLeadingEmoji } from '@renderer/utils'
import { Button, Dropdown } from 'antd'
import { t } from 'i18next'
import { ArrowDownAZ, Ellipsis, PlusIcon, SquareArrowOutUpRight } from 'lucide-react'
import { type FC, memo, useCallback, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
@ -66,7 +60,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{
key: 'edit',
label: t('agents.edit.title'),
icon: <EditOutlined />,
icon: <EditIcon size={14} />,
onClick: (e: any) => {
e.domEvent.stopPropagation()
AssistantSettingsPopup.show({ assistant: agent })
@ -75,7 +69,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{
key: 'create',
label: t('agents.add.button'),
icon: <PlusOutlined />,
icon: <PlusIcon size={14} />,
onClick: (e: any) => {
e.domEvent.stopPropagation()
createAssistantFromAgent(agent)
@ -84,7 +78,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{
key: 'sort',
label: t('agents.sorting.title'),
icon: <SortAscendingOutlined />,
icon: <ArrowDownAZ size={14} />,
onClick: (e: any) => {
e.domEvent.stopPropagation()
ManageAgentsPopup.show()
@ -93,7 +87,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{
key: 'export',
label: t('agents.export.agent'),
icon: <ExportOutlined />,
icon: <SquareArrowOutUpRight size={14} />,
onClick: (e: any) => {
e.domEvent.stopPropagation()
exportAgent()
@ -102,7 +96,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{
key: 'delete',
label: t('common.delete'),
icon: <DeleteOutlined />,
icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true,
onClick: (e: any) => {
e.domEvent.stopPropagation()
@ -173,7 +167,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
color="default"
variant="filled"
shape="circle"
icon={<EllipsisOutlined />}
icon={<Ellipsis size={14} color="var(--color-text-3)" />}
/>
</Dropdown>
</AgentCardHeaderInfoAction>

View File

@ -1,4 +1,5 @@
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { DeleteIcon } from '@renderer/components/Icons'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { handleDelete } from '@renderer/services/FileAction'
import FileManager from '@renderer/services/FileManager'
@ -68,7 +69,7 @@ const FileList: React.FC<FileItemProps> = ({ id, list, files }) => {
icon: <ExclamationCircleOutlined style={{ color: 'red' }} />
})
}}>
<DeleteOutlined />
<DeleteIcon size={14} className="lucide-custom" />
</DeleteButton>
</ImageWrapper>
</Col>

View File

@ -1,11 +1,6 @@
import {
DeleteOutlined,
EditOutlined,
ExclamationCircleOutlined,
SortAscendingOutlined,
SortDescendingOutlined
} from '@ant-design/icons'
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import ListItem from '@renderer/components/ListItem'
import db from '@renderer/databases'
import { getFileFieldLabel } from '@renderer/i18n/label'
@ -16,7 +11,14 @@ import { formatFileSize } from '@renderer/utils'
import { Button, Empty, Flex, Popconfirm } from 'antd'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
import { File as FileIcon, FileImage, FileText, FileType as FileTypeIcon } from 'lucide-react'
import {
ArrowDownNarrowWide,
ArrowUpWideNarrow,
File as FileIcon,
FileImage,
FileText,
FileType as FileTypeIcon
} from 'lucide-react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -54,7 +56,7 @@ const FilesPage: FC = () => {
created_at_unix: dayjs(file.created_at).unix(),
actions: (
<Flex align="center" gap={0} style={{ opacity: 0.7 }}>
<Button type="text" icon={<EditOutlined />} onClick={() => handleRename(file.id)} />
<Button type="text" icon={<EditIcon size={14} />} onClick={() => handleRename(file.id)} />
<Popconfirm
title={t('files.delete.title')}
description={t('files.delete.content')}
@ -62,7 +64,7 @@ const FilesPage: FC = () => {
cancelText={t('common.cancel')}
onConfirm={() => handleDelete(file.id, t)}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
<Button type="text" danger icon={<DeleteOutlined />} />
<Button type="text" danger icon={<DeleteIcon size={14} className="lucide-custom" />} />
</Popconfirm>
</Flex>
)
@ -108,7 +110,8 @@ const FilesPage: FC = () => {
}
}}>
{getFileFieldLabel(field)}
{sortField === field && (sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
{sortField === field &&
(sortOrder === 'desc' ? <ArrowUpWideNarrow size={12} /> : <ArrowDownNarrowWide size={12} />)}
</SortButton>
))}
</SortContainer>

View File

@ -940,8 +940,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
<TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
{loading && (
<Tooltip placement="top" title={t('chat.input.pause')} mouseLeaveDelay={0} arrow>
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>
<CirclePause style={{ color: 'var(--color-error)', fontSize: 20 }} />
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2 }}>
<CirclePause size={20} color="var(--color-error)" />
</ToolbarButton>
</Tooltip>
)}

View File

@ -15,6 +15,7 @@ const SendMessageButton: FC<Props> = ({ disabled, sendMessage }) => {
color: disabled ? 'var(--color-text-3)' : 'var(--color-primary)',
fontSize: 22,
transition: 'all 0.2s',
marginTop: 1,
marginRight: 2
}}
/>

View File

@ -1,8 +1,8 @@
import { ArrowUpOutlined, MenuOutlined } from '@ant-design/icons'
import { HStack, VStack } from '@renderer/components/Layout'
import MaxContextCount from '@renderer/components/MaxContextCount'
import { useSettings } from '@renderer/hooks/useSettings'
import { Divider, Popover } from 'antd'
import { ArrowUp, MenuIcon } from 'lucide-react'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -49,13 +49,14 @@ const TokenCount: FC<Props> = ({ estimateTokenCount, inputTokenCount, contextCou
<Popover content={PopoverContent} arrow={false}>
<HStack>
<HStack style={{ alignItems: 'center' }}>
<MenuOutlined /> {contextCount.current}
<MenuIcon size={12} className="icon" />
{contextCount.current}
<SlashSeparatorSpan>/</SlashSeparatorSpan>
<MaxContextCount maxContext={contextCount.max} />
</HStack>
<Divider type="vertical" style={{ marginTop: 0, marginLeft: 5, marginRight: 5 }} />
<Divider type="vertical" style={{ marginTop: 3, marginLeft: 5, marginRight: 3 }} />
<HStack style={{ alignItems: 'center' }}>
<ArrowUpOutlined />
<ArrowUp size={12} className="icon" />
{inputTokenCount}
<SlashSeparatorSpan>/</SlashSeparatorSpan>
{estimateTokenCount}
@ -77,8 +78,7 @@ const Container = styled.div`
display: flex;
align-items: center;
cursor: pointer;
.anticon {
font-size: 10px;
.icon {
margin-right: 3px;
}
@media (max-width: 800px) {

View File

@ -1,7 +1,8 @@
import { CopyIcon } from '@renderer/components/Icons'
import store from '@renderer/store'
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
import { Tooltip } from 'antd'
import { Check, Copy } from 'lucide-react'
import { Check } from 'lucide-react'
import React, { memo, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -40,11 +41,7 @@ const Table: React.FC<Props> = ({ children, node, blockId }) => {
<ToolbarWrapper className="table-toolbar">
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
<ToolButton role="button" aria-label={t('common.copy')} onClick={handleCopyTable}>
{copied ? (
<Check size={14} style={{ color: 'var(--color-primary)' }} data-testid="check-icon" />
) : (
<Copy size={14} data-testid="copy-icon" />
)}
{copied ? <Check size={14} color="var(--color-primary)" /> : <CopyIcon size={14} />}
</ToolButton>
</Tooltip>
</ToolbarWrapper>

View File

@ -28,6 +28,14 @@ vi.mock('@renderer/store/messageBlock', () => ({
messageBlocksSelectors: mocks.messageBlocksSelectors
}))
vi.mock('@renderer/components/Icons', () => ({
CopyIcon: ({ size }: { size: number }) => <div data-testid="copy-icon" style={{ width: size, height: size }} />
}))
vi.mock('lucide-react', () => ({
Check: ({ size }: { size: number }) => <div data-testid="check-icon" style={{ width: size, height: size }} />
}))
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key

View File

@ -71,32 +71,10 @@ exports[`Table > rendering > should match snapshot 1`] = `
class="c2"
role="button"
>
<svg
aria-hidden="true"
class="lucide lucide-copy"
<div
data-testid="copy-icon"
fill="none"
height="14"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="14"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="14"
rx="2"
ry="2"
width="14"
x="8"
y="8"
/>
<path
d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
/>
</svg>
style="width: 14px; height: 14px;"
/>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon } from '@renderer/components/Icons'
import { MessageBlockStatus, MessageBlockType, type PlaceholderMessageBlock } from '@renderer/types/newMessage'
import React from 'react'
import styled from 'styled-components'
@ -10,7 +10,7 @@ const PlaceholderBlock: React.FC<PlaceholderBlockProps> = ({ block }) => {
if (block.status === MessageBlockStatus.PROCESSING && block.type === MessageBlockType.UNKNOWN) {
return (
<MessageContentLoading>
<SvgSpinners180Ring />
<LoadingIcon />
</MessageContentLoading>
)
}

View File

@ -1,4 +1,5 @@
import { CheckOutlined, EditOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons'
import { InfoCircleOutlined } from '@ant-design/icons'
import { CopyIcon, DeleteIcon, EditIcon, RefreshIcon } from '@renderer/components/Icons'
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
import SaveToKnowledgePopup from '@renderer/components/Popups/SaveToKnowledgePopup'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
@ -32,20 +33,7 @@ import { removeTrailingDoubleSpaces } from '@renderer/utils/markdown'
import { findMainTextBlocks, findTranslationBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
import { Dropdown, Popconfirm, Tooltip } from 'antd'
import dayjs from 'dayjs'
import {
AtSign,
Copy,
FilePenLine,
Languages,
ListChecks,
Menu,
RefreshCw,
Save,
Share,
Split,
ThumbsUp,
Trash
} from 'lucide-react'
import { AtSign, Check, FilePenLine, Languages, ListChecks, Menu, Save, Split, ThumbsUp, Upload } from 'lucide-react'
import { FC, memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
@ -223,7 +211,7 @@ const MessageMenubar: FC<Props> = (props) => {
{
label: t('chat.save.label'),
key: 'save',
icon: <Save size={15} color="var(--color-icon)" style={{ marginTop: 3 }} />,
icon: <Save size={15} />,
children: [
{
label: t('chat.save.file.title'),
@ -245,7 +233,7 @@ const MessageMenubar: FC<Props> = (props) => {
{
label: t('chat.topics.export.title'),
key: 'export',
icon: <Share size={15} color="var(--color-icon)" style={{ marginTop: 3 }} />,
icon: <Upload size={15} />,
children: [
exportMenuOptions.plain_text && {
label: t('chat.topics.copy.plain_text'),
@ -440,28 +428,28 @@ const MessageMenubar: FC<Props> = (props) => {
className="message-action-button"
onClick={() => handleResendUserMessage()}
$softHoverBg={isBubbleStyle}>
<SyncOutlined />
<RefreshIcon size={15} />
</ActionButton>
</Tooltip>
)}
{message.role === 'user' && (
<Tooltip title={t('common.edit')} mouseEnterDelay={0.8}>
<ActionButton className="message-action-button" onClick={onEdit} $softHoverBg={softHoverBg}>
<EditOutlined />
<EditIcon size={15} />
</ActionButton>
</Tooltip>
)}
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
<ActionButton className="message-action-button" onClick={onCopy} $softHoverBg={softHoverBg}>
{!copied && <Copy size={15} />}
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
{!copied && <CopyIcon size={15} />}
{copied && <Check size={15} color="var(--color-primary)" />}
</ActionButton>
</Tooltip>
{isAssistantMessage && (
<Popconfirm
title={t('message.regenerate.confirm')}
okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
icon={<InfoCircleOutlined style={{ color: 'red' }} />}
onConfirm={onRegenerate}
onOpenChange={(open) => open && setShowRegenerateTooltip(false)}>
<Tooltip
@ -470,7 +458,7 @@ const MessageMenubar: FC<Props> = (props) => {
open={showRegenerateTooltip}
onOpenChange={setShowRegenerateTooltip}>
<ActionButton className="message-action-button" $softHoverBg={softHoverBg}>
<RefreshCw size={15} />
<RefreshIcon size={15} />
</ActionButton>
</Tooltip>
</Popconfirm>
@ -571,7 +559,7 @@ const MessageMenubar: FC<Props> = (props) => {
<Popconfirm
title={t('message.message.delete.content')}
okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
icon={<InfoCircleOutlined style={{ color: 'red' }} />}
onOpenChange={(open) => open && setShowDeleteTooltip(false)}
onConfirm={() => deleteMessage(message.id, message.traceId, message.model?.name)}>
<ActionButton
@ -583,7 +571,7 @@ const MessageMenubar: FC<Props> = (props) => {
mouseEnterDelay={1}
open={showDeleteTooltip}
onOpenChange={setShowDeleteTooltip}>
<Trash size={15} />
<DeleteIcon size={15} />
</Tooltip>
</ActionButton>
</Popconfirm>

View File

@ -1,5 +1,5 @@
import { CheckOutlined, CloseOutlined, ExpandOutlined, LoadingOutlined, WarningOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import { CopyIcon, LoadingIcon } from '@renderer/components/Icons'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { useSettings } from '@renderer/hooks/useSettings'
@ -19,7 +19,18 @@ import {
Tooltip
} from 'antd'
import { message } from 'antd'
import { ChevronDown, ChevronRight, CirclePlay, CircleX, PauseCircle, ShieldCheck } from 'lucide-react'
import {
Check,
ChevronDown,
ChevronRight,
CirclePlay,
CircleX,
Maximize,
PauseCircle,
ShieldCheck,
TriangleAlert,
X
} from 'lucide-react'
import { FC, memo, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -191,23 +202,23 @@ const MessageTools: FC<Props> = ({ block }) => {
switch (status) {
case 'pending':
label = t('message.tools.pending', 'Awaiting Approval')
icon = <LoadingOutlined spin style={{ marginLeft: 6, color: 'var(--status-color-warning)' }} />
icon = <LoadingIcon style={{ marginLeft: 6, color: 'var(--status-color-warning)' }} />
break
case 'invoking':
label = t('message.tools.invoking')
icon = <LoadingOutlined spin style={{ marginLeft: 6 }} />
icon = <LoadingIcon style={{ marginLeft: 6 }} />
break
case 'cancelled':
label = t('message.tools.cancelled')
icon = <CloseOutlined style={{ marginLeft: 6 }} />
icon = <X size={13} style={{ marginLeft: 6 }} className="lucide-custom" />
break
case 'done':
if (hasError) {
label = t('message.tools.error')
icon = <WarningOutlined style={{ marginLeft: 6 }} />
icon = <TriangleAlert size={13} style={{ marginLeft: 6 }} className="lucide-custom" />
} else {
label = t('message.tools.completed')
icon = <CheckOutlined style={{ marginLeft: 6 }} />
icon = <Check size={13} style={{ marginLeft: 6 }} className="lucide-custom" />
}
break
default:
@ -262,7 +273,7 @@ const MessageTools: FC<Props> = ({ block }) => {
})
}}
aria-label={t('common.expand')}>
<ExpandOutlined />
<Maximize size={14} />
</ActionButton>
</Tooltip>
{!isPending && !isInvoking && (
@ -274,8 +285,8 @@ const MessageTools: FC<Props> = ({ block }) => {
copyContent(JSON.stringify(result, null, 2), id)
}}
aria-label={t('common.copy')}>
{!copiedMap[id] && <i className="iconfont icon-copy"></i>}
{copiedMap[id] && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
{!copiedMap[id] && <CopyIcon size={14} />}
{copiedMap[id] && <Check size={14} color="var(--status-color-success)" />}
</ActionButton>
</Tooltip>
)}
@ -394,7 +405,7 @@ const MessageTools: FC<Props> = ({ block }) => {
e.stopPropagation()
handleAbortTool()
}}>
<PauseCircle className="lucide-custom" size={14} />
<PauseCircle size={14} className="lucide-custom" />
{t('chat.input.pause')}
</Button>
) : (
@ -572,10 +583,10 @@ const ExpandIcon = styled(ChevronRight)<{ $isActive?: boolean }>`
`
const CollapseContainer = styled(Collapse)`
--status-color-warning: var(--color-warning, #faad14);
--status-color-warning: var(--color-status-warning, #faad14);
--status-color-invoking: var(--color-primary);
--status-color-error: var(--color-error, #ff4d4f);
--status-color-success: var(--color-success, green);
--status-color-error: var(--color-status-error, #ff4d4f);
--status-color-success: var(--color-primary, green);
border-radius: 7px;
border: none;
background-color: var(--color-background);

View File

@ -1,5 +1,5 @@
import { TranslationOutlined } from '@ant-design/icons'
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon } from '@renderer/components/Icons'
import type { TranslationMessageBlock } from '@renderer/types/newMessage'
import { Divider } from 'antd'
import { FC, Fragment } from 'react'
@ -20,7 +20,7 @@ const MessageTranslate: FC<Props> = ({ block }) => {
<TranslationOutlined />
</Divider>
{!block.content || block.content === t('translate.processing') ? (
<SvgSpinners180Ring color="var(--color-text-2)" style={{ marginBottom: 15 }} />
<LoadingIcon color="var(--color-text-2)" style={{ marginBottom: 15 }} />
) : (
<Markdown block={block} />
)}

View File

@ -1,6 +1,6 @@
import { loggerService } from '@logger'
import ContextMenu from '@renderer/components/ContextMenu'
import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
import { LoadingIcon } from '@renderer/components/Icons'
import Scrollbar from '@renderer/components/Scrollbar'
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant'
@ -309,7 +309,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
))}
{isLoadingMore && (
<LoaderContainer>
<SvgSpinners180Ring color="var(--color-text-2)" />
<LoadingIcon color="var(--color-text-2)" />
</LoaderContainer>
)}
</ScrollContainer>

View File

@ -1,4 +1,4 @@
import { DownOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons'
import { DownOutlined, RightOutlined } from '@ant-design/icons'
import { DraggableList } from '@renderer/components/DraggableList'
import Scrollbar from '@renderer/components/Scrollbar'
import { useAgents } from '@renderer/hooks/useAgents'
@ -6,8 +6,9 @@ import { useAssistants } from '@renderer/hooks/useAssistant'
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
import { useTags } from '@renderer/hooks/useTags'
import { Assistant, AssistantsSortType } from '@renderer/types'
import { Tooltip } from 'antd'
import { FC, useCallback, useRef, useState } from 'react'
import { Tooltip, Typography } from 'antd'
import { Plus } from 'lucide-react'
import { FC, useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -69,6 +70,19 @@ const Assistants: FC<AssistantsTabProps> = ({
[assistants, t, updateAssistants]
)
const renderAddAssistantButton = useMemo(() => {
return (
<AssistantAddItem onClick={onCreateAssistant}>
<AddItemWrapper>
<Plus size={16} style={{ marginRight: 4, flexShrink: 0 }} />
<Typography.Text style={{ color: 'inherit' }} ellipsis={{ tooltip: t('chat.add.assistant.title') }}>
{t('chat.add.assistant.title')}
</Typography.Text>
</AddItemWrapper>
</AssistantAddItem>
)
}, [onCreateAssistant, t])
if (assistantsTabSortType === 'tags') {
return (
<Container className="assistants-tab" ref={containerRef}>
@ -117,12 +131,7 @@ const Assistants: FC<AssistantsTabProps> = ({
</TagsContainer>
))}
</div>
<AssistantAddItem onClick={onCreateAssistant}>
<AssistantName>
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
{t('chat.add.assistant.title')}
</AssistantName>
</AssistantAddItem>
{renderAddAssistantButton}
</Container>
)
}
@ -149,14 +158,7 @@ const Assistants: FC<AssistantsTabProps> = ({
/>
)}
</DraggableList>
{!dragging && (
<AssistantAddItem onClick={onCreateAssistant}>
<AssistantName>
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
{t('chat.add.assistant.title')}
</AssistantName>
</AssistantAddItem>
)}
{!dragging && renderAddAssistantButton}
<div style={{ minHeight: 10 }}></div>
</Container>
)
@ -224,13 +226,13 @@ const GroupTitleDivider = styled.div`
border-top: 1px solid var(--color-border);
`
const AssistantName = styled.div`
color: var(--color-text);
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
const AddItemWrapper = styled.div`
color: var(--color-text-2);
font-size: 13px;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
`
export default Assistants

View File

@ -1,17 +1,5 @@
import {
ClearOutlined,
CloseOutlined,
DeleteOutlined,
EditOutlined,
FolderOutlined,
MenuOutlined,
PlusOutlined,
PushpinOutlined,
QuestionCircleOutlined,
UploadOutlined
} from '@ant-design/icons'
import { DraggableVirtualList } from '@renderer/components/DraggableList'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { isMac } from '@renderer/config/constant'
@ -40,6 +28,19 @@ import { Dropdown, MenuProps, Tooltip } from 'antd'
import { ItemType, MenuItemType } from 'antd/es/menu/interface'
import dayjs from 'dayjs'
import { findIndex } from 'lodash'
import {
BrushCleaning,
FolderOpen,
HelpCircle,
MenuIcon,
PackagePlus,
PinIcon,
PinOffIcon,
PlusIcon,
Sparkles,
UploadIcon,
XIcon
} from 'lucide-react'
import { FC, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@ -177,7 +178,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('chat.topics.auto_rename'),
key: 'auto-rename',
icon: <i className="iconfont icon-business-smart-assistant" style={{ fontSize: '14px' }} />,
icon: <Sparkles size={14} />,
disabled: isRenaming(topic.id),
async onClick() {
const messages = await TopicManager.getTopicMessages(topic.id)
@ -200,7 +201,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('chat.topics.edit.title'),
key: 'rename',
icon: <EditOutlined />,
icon: <EditIcon size={14} />,
disabled: isRenaming(topic.id),
async onClick() {
const name = await PromptPopup.show({
@ -217,10 +218,10 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('chat.topics.prompt.label'),
key: 'topic-prompt',
icon: <i className="iconfont icon-ai-model1" style={{ fontSize: '14px' }} />,
icon: <PackagePlus size={14} />,
extra: (
<Tooltip title={t('chat.topics.prompt.tips')}>
<QuestionIcon />
<HelpCircle size={14} />
</Tooltip>
),
async onClick() {
@ -243,9 +244,9 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
}
},
{
label: topic.pinned ? t('chat.topics.unpinned') : t('chat.topics.pinned'),
label: topic.pinned ? t('chat.topics.unpin') : t('chat.topics.pin'),
key: 'pin',
icon: <PushpinOutlined />,
icon: topic.pinned ? <PinOffIcon size={14} /> : <PinIcon size={14} />,
onClick() {
onPinTopic(topic)
}
@ -253,7 +254,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('chat.topics.clear.title'),
key: 'clear-messages',
icon: <ClearOutlined />,
icon: <BrushCleaning size={14} />,
async onClick() {
window.modal.confirm({
title: t('chat.input.clear.content'),
@ -265,7 +266,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('settings.topic.position.label'),
key: 'topic-position',
icon: <MenuOutlined />,
icon: <MenuIcon size={14} />,
children: [
{
label: t('settings.topic.position.left'),
@ -282,7 +283,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('chat.topics.copy.title'),
key: 'copy',
icon: <CopyIcon />,
icon: <CopyIcon size={14} />,
children: [
{
label: t('chat.topics.copy.image'),
@ -304,7 +305,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{
label: t('chat.topics.export.title'),
key: 'export',
icon: <UploadOutlined />,
icon: <UploadIcon size={14} />,
children: [
exportMenuOptions.image && {
label: t('chat.topics.export.image'),
@ -375,7 +376,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
menus.push({
label: t('chat.topics.move_to'),
key: 'move',
icon: <FolderOutlined />,
icon: <FolderOpen size={14} />,
children: assistants
.filter((a) => a.id !== assistant.id)
.map((a) => ({
@ -392,7 +393,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
label: t('common.delete'),
danger: true,
key: 'delete',
icon: <DeleteOutlined />,
icon: <DeleteIcon size={14} className="lucide-custom" />,
onClick: () => onDeleteTopic(topic)
})
}
@ -446,7 +447,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
itemContainerStyle={{ paddingBottom: '8px' }}
header={
<AddTopicButton onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)}>
<PlusOutlined />
<PlusIcon size={16} />
{t('chat.add.topic.title')}
</AddTopicButton>
}>
@ -498,16 +499,16 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
}
}}>
{deletingTopicId === topic.id ? (
<DeleteOutlined style={{ color: 'var(--color-error)' }} />
<DeleteIcon size={14} color="var(--color-error)" />
) : (
<CloseOutlined />
<XIcon size={14} color="var(--color-text-3)" />
)}
</MenuButton>
</Tooltip>
)}
{topic.pinned && (
<MenuButton className="pin">
<PushpinOutlined />
<PinIcon size={14} color="var(--color-text-3)" />
</MenuButton>
)}
</TopicNameContainer>
@ -704,10 +705,5 @@ const MenuButton = styled.div`
font-size: 12px;
}
`
const QuestionIcon = styled(QuestionCircleOutlined)`
font-size: 14px;
cursor: pointer;
color: var(--color-text-3);
`
export default Topics

View File

@ -1,17 +1,6 @@
import {
CheckOutlined,
DeleteOutlined,
EditOutlined,
MinusCircleOutlined,
PlusOutlined,
SaveOutlined,
SmileOutlined,
SortAscendingOutlined,
SortDescendingOutlined
} from '@ant-design/icons'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import EmojiIcon from '@renderer/components/EmojiIcon'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
@ -24,7 +13,19 @@ import { getLeadingEmoji, uuid } from '@renderer/utils'
import { hasTopicPendingRequests } from '@renderer/utils/queue'
import { Dropdown, MenuProps } from 'antd'
import { omit } from 'lodash'
import { AlignJustify, Plus, Settings2, Tag, Tags } from 'lucide-react'
import {
AlignJustify,
ArrowDownAZ,
ArrowUpAZ,
BrushCleaning,
Check,
Plus,
Save,
Settings2,
Smile,
Tag,
Tags
} from 'lucide-react'
import { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -198,7 +199,7 @@ const createTagMenuItems = (
const items: MenuProps['items'] = [
...allTags.map((tag) => ({
label: tag,
icon: assistant.tags?.includes(tag) ? <CheckOutlined size={14} /> : <Tag size={12} />,
icon: assistant.tags?.includes(tag) ? <Check size={14} /> : <Tag size={14} />,
key: `all-tag-${tag}`,
onClick: () => handleTagOperation(tag, assistant, assistants, updateAssistants)
}))
@ -211,7 +212,7 @@ const createTagMenuItems = (
items.push({
label: t('assistants.tags.add'),
key: 'new-tag',
icon: <Plus size={16} />,
icon: <Plus size={14} />,
onClick: async () => {
const tagName = await PromptPopup.show({
title: t('assistants.tags.add'),
@ -228,7 +229,7 @@ const createTagMenuItems = (
items.push({
label: t('assistants.tags.manage'),
key: 'manage-tags',
icon: <Settings2 size={16} />,
icon: <Settings2 size={14} />,
onClick: () => {
AssistantTagsPopup.show({ title: t('assistants.tags.manage') })
}
@ -260,13 +261,13 @@ function getMenuItems({
{
label: t('assistants.edit.title'),
key: 'edit',
icon: <EditOutlined />,
icon: <EditIcon size={14} />,
onClick: () => AssistantSettingsPopup.show({ assistant })
},
{
label: t('assistants.copy.title'),
key: 'duplicate',
icon: <CopyIcon />,
icon: <CopyIcon size={14} />,
onClick: async () => {
const _assistant = copyAssistant(assistant)
if (_assistant) {
@ -277,7 +278,7 @@ function getMenuItems({
{
label: t('assistants.clear.title'),
key: 'clear',
icon: <MinusCircleOutlined />,
icon: <BrushCleaning size={14} />,
onClick: () => {
window.modal.confirm({
title: t('assistants.clear.title'),
@ -291,7 +292,7 @@ function getMenuItems({
{
label: t('assistants.save.title'),
key: 'save-to-agent',
icon: <SaveOutlined />,
icon: <Save size={14} />,
onClick: async () => {
const agent = omit(assistant, ['model', 'emoji'])
agent.id = uuid()
@ -306,7 +307,7 @@ function getMenuItems({
{
label: t('assistants.icon.type'),
key: 'icon-type',
icon: <SmileOutlined />,
icon: <Smile size={14} />,
children: [
{
label: t('settings.assistant.icon.type.model'),
@ -331,7 +332,7 @@ function getMenuItems({
{
label: t('assistants.tags.manage'),
key: 'all-tags',
icon: <PlusOutlined />,
icon: <Plus size={14} />,
children: createTagMenuItems(allTags, assistant, assistants, updateAssistants, t)
},
{
@ -345,13 +346,13 @@ function getMenuItems({
{
label: t('common.sort.pinyin.asc'),
key: 'sort-asc',
icon: <SortAscendingOutlined />,
icon: <ArrowDownAZ size={14} />,
onClick: sortByPinyinAsc
},
{
label: t('common.sort.pinyin.desc'),
key: 'sort-desc',
icon: <SortDescendingOutlined />,
icon: <ArrowUpAZ size={14} />,
onClick: sortByPinyinDesc
},
{
@ -360,7 +361,7 @@ function getMenuItems({
{
label: t('common.delete'),
key: 'delete',
icon: <DeleteOutlined />,
icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true,
onClick: () => {
window.modal.confirm({

View File

@ -1,11 +1,11 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { DeleteIcon } from '@renderer/components/Icons'
import { Box } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { useAssistants } from '@renderer/hooks/useAssistant'
import { useTags } from '@renderer/hooks/useTags'
import { Button, Empty, Modal } from 'antd'
import { isEmpty } from 'lodash'
import { Trash } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -94,7 +94,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
<Box mr={8}>{tag}</Box>
<Button
type="text"
icon={<Trash size={16} />}
icon={<DeleteIcon size={16} className="lucide-custom" />}
danger
onClick={(e) => {
e.stopPropagation()

View File

@ -1,6 +1,6 @@
import { DeleteOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import ListItem from '@renderer/components/ListItem'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import Scrollbar from '@renderer/components/Scrollbar'
@ -9,7 +9,7 @@ import { useShortcut } from '@renderer/hooks/useShortcuts'
import KnowledgeSearchPopup from '@renderer/pages/knowledge/components/KnowledgeSearchPopup'
import { KnowledgeBase } from '@renderer/types'
import { Dropdown, Empty, MenuProps } from 'antd'
import { Book, Plus } from 'lucide-react'
import { Book, Plus, Settings } from 'lucide-react'
import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -49,7 +49,7 @@ const KnowledgePage: FC = () => {
{
label: t('knowledge.rename'),
key: 'rename',
icon: <EditOutlined />,
icon: <EditIcon size={14} />,
async onClick() {
const name = await PromptPopup.show({
title: t('knowledge.rename'),
@ -62,9 +62,9 @@ const KnowledgePage: FC = () => {
}
},
{
label: t('knowledge.settings.title'),
label: t('common.settings'),
key: 'settings',
icon: <SettingOutlined />,
icon: <Settings size={14} />,
onClick: () => handleEditKnowledgeBase(base)
},
{ type: 'divider' },
@ -72,7 +72,7 @@ const KnowledgePage: FC = () => {
label: t('common.delete'),
danger: true,
key: 'delete',
icon: <DeleteOutlined />,
icon: <DeleteIcon size={14} className="lucide-custom" />,
onClick: () => {
window.modal.confirm({
title: t('knowledge.delete_confirm'),

View File

@ -1,6 +1,5 @@
import type { KnowledgeBase, Model } from '@renderer/types'
import { render, screen } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import AdvancedSettingsPanel from '../components/KnowledgeSettings/AdvancedSettingsPanel'
@ -36,6 +35,27 @@ vi.mock('react-i18next', () => ({
})
}))
vi.mock('lucide-react', () => ({
TriangleAlert: () => <span>warning</span>
}))
vi.mock('antd', () => ({
Alert: ({ message }: { message: string }) => <div role="alert">{message}</div>,
InputNumber: ({ ref, value, onChange, placeholder, disabled, style, 'aria-label': ariaLabel }: any) => (
<input
ref={ref}
type="number"
data-testid="input-number"
aria-label={ariaLabel}
placeholder={placeholder}
value={value ?? ''}
onChange={(e) => onChange(e.target.valueAsNumber)}
disabled={disabled}
style={style}
/>
)
}))
/**
* KnowledgeBase
* @param overrides
@ -78,23 +98,19 @@ describe('AdvancedSettingsPanel', () => {
})
describe('handlers', () => {
it('should call handlers when values are changed', async () => {
const user = userEvent.setup()
it('should call handlers when values are changed', () => {
render(<AdvancedSettingsPanel newBase={mockBase} handlers={mocks.handlers} />)
const chunkSizeInput = screen.getByLabelText('分块大小')
await user.clear(chunkSizeInput)
await user.type(chunkSizeInput, '600')
fireEvent.change(chunkSizeInput, { target: { value: '600' } })
expect(mocks.handlers.handleChunkSizeChange).toHaveBeenCalledWith(600)
const chunkOverlapInput = screen.getByLabelText('分块重叠')
await user.clear(chunkOverlapInput)
await user.type(chunkOverlapInput, '300')
fireEvent.change(chunkOverlapInput, { target: { value: '300' } })
expect(mocks.handlers.handleChunkOverlapChange).toHaveBeenCalledWith(300)
const thresholdInput = screen.getByLabelText('检索相似度阈值')
await user.clear(thresholdInput)
await user.type(thresholdInput, '0.6')
fireEvent.change(thresholdInput, { target: { value: '0.6' } })
expect(mocks.handlers.handleThresholdChange).toHaveBeenCalledWith(0.6)
})
})

View File

@ -31,84 +31,14 @@ exports[`AdvancedSettingsPanel > basic rendering > should match snapshot 1`] = `
knowledge.chunk_size_tooltip
</div>
</div>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined"
<input
aria-label="分块大小"
data-testid="input-number"
placeholder="knowledge.chunk_size_placeholder"
style="width: 100%;"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-label="分块大小"
aria-valuemin="100"
aria-valuenow="500"
autocomplete="off"
class="ant-input-number-input"
placeholder="knowledge.chunk_size_placeholder"
role="spinbutton"
step="1"
value="500"
/>
</div>
</div>
type="number"
value="500"
/>
</div>
<div
class="c1"
@ -121,84 +51,14 @@ exports[`AdvancedSettingsPanel > basic rendering > should match snapshot 1`] = `
knowledge.chunk_overlap_tooltip
</div>
</div>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined"
<input
aria-label="分块重叠"
data-testid="input-number"
placeholder="knowledge.chunk_overlap_placeholder"
style="width: 100%;"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-label="分块重叠"
aria-valuemin="0"
aria-valuenow="200"
autocomplete="off"
class="ant-input-number-input"
placeholder="knowledge.chunk_overlap_placeholder"
role="spinbutton"
step="1"
value="200"
/>
</div>
</div>
type="number"
value="200"
/>
</div>
<div
class="c1"
@ -211,119 +71,19 @@ exports[`AdvancedSettingsPanel > basic rendering > should match snapshot 1`] = `
knowledge.threshold_tooltip
</div>
</div>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined"
<input
aria-label="检索相似度阈值"
data-testid="input-number"
placeholder="knowledge.threshold_placeholder"
style="width: 100%;"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up"
role="button"
unselectable="on"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down"
role="button"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-label="检索相似度阈值"
aria-valuemax="1"
aria-valuemin="0"
aria-valuenow="0.5"
autocomplete="off"
class="ant-input-number-input"
placeholder="knowledge.threshold_placeholder"
role="spinbutton"
step="0.1"
value="0.5"
/>
</div>
</div>
type="number"
value="0.5"
/>
</div>
<div
class="ant-alert ant-alert-warning css-dev-only-do-not-override-1261szd"
data-show="true"
role="alert"
>
<span
aria-label="warning"
class="anticon anticon-warning ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="warning"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
避免修改这个高级设置。
</div>
</div>
避免修改这个高级设置。
</div>
</div>
`;

View File

@ -1,7 +1,7 @@
import { WarningOutlined } from '@ant-design/icons'
import InfoTooltip from '@renderer/components/InfoTooltip'
import { KnowledgeBase } from '@renderer/types'
import { Alert, InputNumber } from 'antd'
import { TriangleAlert } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsPanel } from './styles'
@ -68,7 +68,12 @@ const AdvancedSettingsPanel: React.FC<AdvancedSettingsPanelProps> = ({ newBase,
/>
</SettingsItem>
<Alert message={t('knowledge.chunk_size_change_warning')} type="warning" showIcon icon={<WarningOutlined />} />
<Alert
message={t('knowledge.chunk_size_change_warning')}
type="warning"
showIcon
icon={<TriangleAlert size={16} className="lucide-custom" />}
/>
</SettingsPanel>
)
}

View File

@ -1,6 +1,6 @@
import { DeleteOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import Ellipsis from '@renderer/components/Ellipsis'
import { DeleteIcon } from '@renderer/components/Icons'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
import FileItem from '@renderer/pages/files/FileItem'
@ -8,7 +8,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { Button, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -68,7 +68,7 @@ const KnowledgeDirectories: FC<KnowledgeContentProps> = ({ selectedBase, progres
<ItemHeader>
<Button
type="primary"
icon={<Plus size={16} />}
icon={<PlusIcon size={16} />}
onClick={(e) => {
e.stopPropagation()
handleAddDirectory()
@ -111,7 +111,12 @@ const KnowledgeDirectories: FC<KnowledgeContentProps> = ({ selectedBase, progres
type="directory"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
<Button
type="text"
danger
onClick={() => removeItem(item)}
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</FlexAlignCenter>
)
}}

View File

@ -1,4 +1,3 @@
import { DeleteOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import Ellipsis from '@renderer/components/Ellipsis'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
@ -11,14 +10,15 @@ import { formatFileSize, uuid } from '@renderer/utils'
import { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant'
import { Button, Tooltip, Upload } from 'antd'
import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const logger = loggerService.withContext('KnowledgeFiles')
import { DeleteIcon } from '@renderer/components/Icons'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { PlusIcon } from 'lucide-react'
import {
ClickableSpan,
@ -139,7 +139,7 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
<ItemHeader>
<Button
type="primary"
icon={<Plus size={16} />}
icon={<PlusIcon size={16} />}
onClick={(e) => {
e.stopPropagation()
handleAddFile()
@ -210,7 +210,12 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
type="file"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
<Button
type="text"
danger
onClick={() => removeItem(item)}
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</FlexAlignCenter>
)
}}

View File

@ -1,4 +1,4 @@
import { DeleteOutlined, EditOutlined } from '@ant-design/icons'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
@ -7,7 +7,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { Button } from 'antd'
import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -64,7 +64,7 @@ const KnowledgeNotes: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemHeader>
<Button
type="primary"
icon={<Plus size={16} />}
icon={<PlusIcon size={16} />}
onClick={(e) => {
e.stopPropagation()
handleAddNote()
@ -91,7 +91,7 @@ const KnowledgeNotes: FC<KnowledgeContentProps> = ({ selectedBase }) => {
extra: getDisplayTime(note),
actions: (
<FlexAlignCenter>
<Button type="text" onClick={() => handleEditNote(note)} icon={<EditOutlined />} />
<Button type="text" onClick={() => handleEditNote(note)} icon={<EditIcon size={14} />} />
<StatusIconWrapper>
<StatusIcon
sourceId={note.id}
@ -100,7 +100,12 @@ const KnowledgeNotes: FC<KnowledgeContentProps> = ({ selectedBase }) => {
type="note"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(note)} icon={<DeleteOutlined />} />
<Button
type="text"
danger
onClick={() => removeItem(note)}
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</FlexAlignCenter>
)
}}

View File

@ -1,6 +1,6 @@
import { DeleteOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import Ellipsis from '@renderer/components/Ellipsis'
import { DeleteIcon } from '@renderer/components/Icons'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
@ -9,7 +9,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { Button, message, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -87,7 +87,7 @@ const KnowledgeSitemaps: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemHeader>
<Button
type="primary"
icon={<Plus size={16} />}
icon={<PlusIcon size={16} />}
onClick={(e) => {
e.stopPropagation()
handleAddSitemap()
@ -133,7 +133,12 @@ const KnowledgeSitemaps: FC<KnowledgeContentProps> = ({ selectedBase }) => {
type="sitemap"
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
<Button
type="text"
danger
onClick={() => removeItem(item)}
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</FlexAlignCenter>
)
}}

View File

@ -1,5 +1,5 @@
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
import Ellipsis from '@renderer/components/Ellipsis'
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
@ -8,7 +8,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { Button, Dropdown, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -115,7 +115,7 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemHeader>
<Button
type="primary"
icon={<Plus size={16} />}
icon={<PlusIcon size={16} />}
onClick={(e) => {
e.stopPropagation()
handleAddUrl()
@ -143,13 +143,13 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
items: [
{
key: 'edit',
icon: <EditOutlined />,
icon: <EditIcon size={14} />,
label: t('knowledge.edit_remark'),
onClick: () => handleEditRemark(item)
},
{
key: 'copy',
icon: <CopyOutlined />,
icon: <CopyIcon size={14} />,
label: t('common.copy'),
onClick: () => {
navigator.clipboard.writeText(item.content as string)
@ -178,7 +178,12 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<StatusIconWrapper>
<StatusIcon sourceId={item.id} base={base} getProcessingStatus={getProcessingStatus} type="url" />
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
<Button
type="text"
danger
onClick={() => removeItem(item)}
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</FlexAlignCenter>
)
}}

File diff suppressed because it is too large Load Diff

View File

@ -76,8 +76,8 @@ export const DEFAULT_PAINTING: DmxapiPainting = {
export const MODEOPTIONS = [
{ label: 'paintings.mode.generate', value: generationModeType.GENERATION },
{ label: '改图', value: generationModeType.EDIT },
{ label: '合并图', value: generationModeType.MERGE }
{ label: 'paintings.mode.edit', value: generationModeType.EDIT },
{ label: 'paintings.mode.merge', value: generationModeType.MERGE }
]
// 获取模型分组数据

View File

@ -1,6 +1,7 @@
import { DeleteOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import { QuestionCircleOutlined } from '@ant-design/icons'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import EditableNumber from '@renderer/components/EditableNumber'
import { DeleteIcon, ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import Selector from '@renderer/components/Selector'
@ -10,6 +11,7 @@ import { Assistant, AssistantSettingCustomParameters, AssistantSettings } from '
import { modalConfirm } from '@renderer/utils'
import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { isNull } from 'lodash'
import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -213,7 +215,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Label>{t('assistants.settings.default_model')}</Label>
<HStack alignItems="center" gap={5}>
<ModelSelectButton
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusOutlined />}
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusIcon size={18} />}
onClick={onSelectModel}>
<ModelName>{defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}</ModelName>
</ModelSelectButton>
@ -221,7 +223,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Button
color="danger"
variant="filled"
icon={<DeleteOutlined />}
icon={<DeleteIcon size={14} className="lucide-custom" />}
onClick={() => {
setDefaultModel(undefined)
updateAssistant({ ...assistant, defaultModel: undefined })
@ -449,7 +451,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<Label>{t('models.custom_parameters')}</Label>
<Button icon={<PlusOutlined />} onClick={onAddCustomParameter}>
<Button icon={<PlusIcon size={18} />} onClick={onAddCustomParameter}>
{t('models.add_parameter')}
</Button>
</SettingRow>
@ -478,7 +480,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Button
color="danger"
variant="filled"
icon={<DeleteOutlined />}
icon={<DeleteIcon size={14} className="lucide-custom" />}
onClick={() => onDeleteCustomParameter(index)}
/>
</Col>
@ -486,7 +488,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
))}
<Divider style={{ margin: '15px 0' }} />
<HStack justifyContent="flex-end">
<Button onClick={onReset} style={{ width: 80 }} danger type="primary">
<Button onClick={onReset} danger type="primary" icon={<ResetIcon size={16} />}>
{t('chat.settings.reset')}
</Button>
</HStack>

View File

@ -165,10 +165,6 @@ const Container = styled.div`
flex: 1;
flex-direction: column;
overflow: hidden;
.ant-btn {
line-height: 0;
}
`
const EmojiButtonWrapper = styled.div`

View File

@ -1,8 +1,10 @@
import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons'
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import FileItem from '@renderer/pages/files/FileItem'
import { Assistant, QuickPhrase } from '@renderer/types'
import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd'
import { PlusIcon } from 'lucide-react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -82,7 +84,7 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
<Container>
<SettingTitle>
{t('assistants.settings.regular_phrases.title', 'Regular Prompts')}
<Button type="text" icon={<PlusOutlined />} onClick={handleAdd} />
<Button type="text" icon={<PlusIcon size={18} />} onClick={handleAdd} />
</SettingTitle>
<SettingDivider />
<SettingRow>
@ -102,7 +104,7 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
extra: prompt.content,
actions: (
<Flex gap={4} style={{ opacity: 0.6 }}>
<Button key="edit" type="text" icon={<EditOutlined />} onClick={() => handleEdit(prompt)} />
<Button key="edit" type="text" icon={<EditIcon size={14} />} onClick={() => handleEdit(prompt)} />
<Popconfirm
title={t('assistants.settings.regular_phrases.delete', 'Delete Prompt')}
description={t(
@ -113,7 +115,12 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
cancelText={t('common.cancel')}
onConfirm={() => handleDelete(prompt.id)}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
<Button key="delete" type="text" danger icon={<DeleteOutlined />} />
<Button
key="delete"
type="text"
danger
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</Popconfirm>
</Flex>
)

View File

@ -92,7 +92,6 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, tab, ...prop
<StyledModal
open={open}
onOk={onOk}
onClose={onCancel}
onCancel={onCancel}
afterClose={afterClose}
footer={null}

View File

@ -2,9 +2,7 @@ import {
CloudServerOutlined,
CloudSyncOutlined,
FileSearchOutlined,
FolderOpenOutlined,
LoadingOutlined,
SaveOutlined,
YuqueOutlined
} from '@ant-design/icons'
import DividerWithText from '@renderer/components/DividerWithText'
@ -22,7 +20,7 @@ import { AppInfo } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
import { occupiedDirs } from '@shared/config/constant'
import { Button, Progress, Switch, Typography } from 'antd'
import { FileText, FolderCog, FolderInput, Sparkle } from 'lucide-react'
import { FileText, FolderCog, FolderInput, FolderOpen, SaveIcon, Sparkle } from 'lucide-react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -588,10 +586,10 @@ const DataSettings: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<Button onClick={BackupPopup.show} icon={<SaveOutlined />}>
<Button onClick={BackupPopup.show} icon={<SaveIcon size={14} />}>
{t('settings.general.backup.button')}
</Button>
<Button onClick={RestorePopup.show} icon={<FolderOpenOutlined />}>
<Button onClick={RestorePopup.show} icon={<FolderOpen size={14} />}>
{t('settings.general.restore.button')}
</Button>
</HStack>

View File

@ -1,4 +1,5 @@
import CodeEditor from '@renderer/components/CodeEditor'
import { ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import TextBadge from '@renderer/components/TextBadge'
import { isMac, THEME_COLOR_PRESETS } from '@renderer/config/constant'
@ -18,7 +19,7 @@ import {
} from '@renderer/store/settings'
import { ThemeMode } from '@renderer/types'
import { Button, ColorPicker, Segmented, Switch } from 'antd'
import { Minus, Monitor, Moon, Plus, RotateCcw, Sun } from 'lucide-react'
import { Minus, Monitor, Moon, Plus, Sun } from 'lucide-react'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -247,7 +248,7 @@ const DisplaySettings: FC = () => {
<Button
onClick={() => handleZoomFactor(0, true)}
style={{ marginLeft: 8 }}
icon={<RotateCcw size="14" />}
icon={<ResetIcon size="14" />}
color="default"
variant="text"
/>

View File

@ -1,13 +1,13 @@
import { EditOutlined } from '@ant-design/icons'
import { nanoid } from '@reduxjs/toolkit'
import { DraggableList } from '@renderer/components/DraggableList'
import { EditIcon, RefreshIcon } from '@renderer/components/Icons'
import Scrollbar from '@renderer/components/Scrollbar'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { getMcpTypeLabel } from '@renderer/i18n/label'
import { MCPServer } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error'
import { Badge, Button, Dropdown, Empty, Switch, Tag } from 'antd'
import { MonitorCheck, Plus, RefreshCw, Settings2, SquareArrowOutUpRight } from 'lucide-react'
import { MonitorCheck, Plus, Settings2, SquareArrowOutUpRight } from 'lucide-react'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
@ -139,7 +139,7 @@ const McpServersList: FC = () => {
<ListHeader>
<SettingTitle style={{ gap: 3 }}>
<span>{t('settings.mcp.newServer')}</span>
<Button icon={<EditOutlined />} type="text" onClick={() => EditMcpJsonPopup.show()} shape="circle" />
<Button icon={<EditIcon size={14} />} type="text" onClick={() => EditMcpJsonPopup.show()} shape="circle" />
</SettingTitle>
<ButtonGroup>
<InstallNpxUv mini />
@ -176,7 +176,7 @@ const McpServersList: FC = () => {
{t('settings.mcp.addServer.label')}
</Button>
</Dropdown>
<Button icon={<RefreshCw size={16} />} type="default" onClick={onSyncServers} shape="round">
<Button icon={<RefreshIcon size={16} />} type="default" onClick={onSyncServers} shape="round">
{t('settings.mcp.sync.title', 'Sync Servers')}
</Button>
</ButtonGroup>

View File

@ -1,5 +1,5 @@
import { DeleteOutlined, SaveOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import { DeleteIcon } from '@renderer/components/Icons'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMCPServer, useMCPServers } from '@renderer/hooks/useMCPServers'
import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
@ -7,7 +7,7 @@ import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error'
import { Badge, Button, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { ChevronDown } from 'lucide-react'
import { ChevronDown, SaveIcon } from 'lucide-react'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate, useParams } from 'react-router'
@ -732,7 +732,12 @@ const McpSettings: React.FC = () => {
<ServerName className="text-nowrap">{server?.name}</ServerName>
{serverVersion && <VersionBadge count={serverVersion} color="blue" />}
</Flex>
<Button danger icon={<DeleteOutlined />} type="text" onClick={() => onDeleteMcpServer(server)} />
<Button
danger
icon={<DeleteIcon size={14} className="lucide-custom" />}
type="text"
onClick={() => onDeleteMcpServer(server)}
/>
</Flex>
<Flex align="center" gap={16}>
<Switch
@ -743,7 +748,7 @@ const McpSettings: React.FC = () => {
/>
<Button
type="primary"
icon={<SaveOutlined />}
icon={<SaveIcon size={14} />}
onClick={onSave}
loading={loading}
shape="round"

View File

@ -1,16 +1,6 @@
import {
CalendarOutlined,
DeleteOutlined,
EditOutlined,
ExclamationCircleOutlined,
MoreOutlined,
PlusOutlined,
ReloadOutlined,
UserAddOutlined,
UserDeleteOutlined,
UserOutlined
} from '@ant-design/icons'
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import { DeleteIcon, EditIcon, LoadingIcon, RefreshIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import TextBadge from '@renderer/components/TextBadge'
import { useTheme } from '@renderer/context/ThemeProvider'
@ -25,24 +15,10 @@ import {
setGlobalMemoryEnabled
} from '@renderer/store/memory'
import type { MemoryItem } from '@types'
import {
Avatar,
Badge,
Button,
Dropdown,
Empty,
Form,
Input,
Modal,
Pagination,
Select,
Space,
Spin,
Switch
} from 'antd'
import { Badge, Button, Dropdown, Empty, Flex, Form, Input, Modal, Pagination, Space, Spin, Switch } from 'antd'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { Brain, Settings2 } from 'lucide-react'
import { Brain, Calendar, MenuIcon, PlusIcon, Settings2, UserRound, UserRoundMinus, UserRoundPlus } from 'lucide-react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
@ -57,13 +33,13 @@ import {
SettingRowTitle,
SettingTitle
} from '../index'
import { DEFAULT_USER_ID } from './constants'
import UserSelector from './UserSelector'
const logger = loggerService.withContext('MemorySettings')
dayjs.extend(relativeTime)
const DEFAULT_USER_ID = 'default-user'
const { Option } = Select
const { TextArea } = Input
interface AddMemoryModalProps {
@ -112,10 +88,10 @@ const AddMemoryModal: React.FC<AddMemoryModalProps> = ({ visible, onCancel, onAd
onOk={() => form.submit()}
okButtonProps={{ loading: loading }}
title={
<Space>
<PlusOutlined style={{ color: 'var(--color-primary)' }} />
<Flex align="center" gap={8}>
<PlusIcon size={16} color="var(--color-primary)" />
<span>{t('memory.add_memory')}</span>
</Space>
</Flex>
}
styles={{
header: {
@ -170,10 +146,10 @@ const EditMemoryModal: React.FC<EditMemoryModalProps> = ({ visible, memory, onCa
return (
<Modal
title={
<Space>
<EditOutlined style={{ color: 'var(--color-primary)' }} />
<Flex align="center" gap={8}>
<EditIcon size={16} color="var(--color-primary)" />
<span>{t('memory.edit_memory')}</span>
</Space>
</Flex>
}
open={visible}
onCancel={onCancel}
@ -268,10 +244,10 @@ const AddUserModal: React.FC<AddUserModalProps> = ({ visible, onCancel, onAdd, e
}
}}
title={
<Space>
<UserAddOutlined style={{ color: 'var(--color-primary)' }} />
<Flex align="center" gap={8}>
<UserRoundPlus size={16} color="var(--color-primary)" />
<span>{t('memory.add_user')}</span>
</Space>
</Flex>
}>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item label={t('memory.new_user_id')} name="userId" rules={[{ validator: validateUserId }]}>
@ -279,7 +255,7 @@ const AddUserModal: React.FC<AddUserModalProps> = ({ visible, onCancel, onAdd, e
placeholder={t('memory.new_user_id_placeholder')}
maxLength={50}
size="large"
prefix={<UserOutlined />}
prefix={<UserRound size={16} />}
/>
</Form.Item>
<div
@ -324,10 +300,6 @@ const MemorySettings = () => {
return user === DEFAULT_USER_ID ? t('memory.default_user') : user
}
const getUserAvatar = (user: string) => {
return user === DEFAULT_USER_ID ? user.slice(0, 1).toUpperCase() : user.slice(0, 2).toUpperCase()
}
// Load unique users from database
const loadUniqueUsers = useCallback(async () => {
try {
@ -616,7 +588,7 @@ const MemorySettings = () => {
</HStack>
<HStack style={{ alignItems: 'center', gap: 10 }}>
<Switch checked={globalMemoryEnabled} onChange={handleGlobalMemoryToggle} />
<Button icon={<Settings2 size={16} />} onClick={() => setSettingsModalVisible(true)} />
<Button type="text" icon={<Settings2 size={16} />} onClick={() => setSettingsModalVisible(true)} />
</HStack>
</HStack>
</SettingGroup>
@ -632,52 +604,12 @@ const MemorySettings = () => {
{allMemories.length} {t('memory.total_memories')}
</SettingHelpText>
</div>
<Select
value={currentUser}
onChange={handleUserSwitch}
style={{ width: 200 }}
dropdownRender={(menu) => (
<>
{menu}
<div style={{ padding: '8px' }}>
<Button
type="text"
onClick={() => setAddUserModalVisible(true)}
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start'
}}>
<HStack alignItems="center" gap={10}>
<UserAddOutlined />
<span>{t('memory.add_new_user')}</span>
</HStack>
</Button>
</div>
</>
)}>
<Option value={DEFAULT_USER_ID}>
<HStack alignItems="center" gap={10}>
<Avatar size={20} style={{ background: 'var(--color-primary)' }}>
{getUserAvatar(DEFAULT_USER_ID)}
</Avatar>
<span>{t('memory.default_user')}</span>
</HStack>
</Option>
{uniqueUsers
.filter((user) => user !== DEFAULT_USER_ID)
.map((user) => (
<Option key={user} value={user}>
<HStack alignItems="center" gap={10}>
<Avatar size={20} style={{ background: 'var(--color-primary)' }}>
{getUserAvatar(user)}
</Avatar>
<span>{user}</span>
</HStack>
</Option>
))}
</Select>
<UserSelector
currentUser={currentUser}
uniqueUsers={uniqueUsers}
onUserSwitch={handleUserSwitch}
onAddUser={() => setAddUserModalVisible(true)}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -703,7 +635,7 @@ const MemorySettings = () => {
allowClear
style={{ width: 240 }}
/>
<Button type="primary" icon={<PlusOutlined />} onClick={() => setAddMemoryModalVisible(true)}>
<Button type="primary" icon={<PlusIcon size={18} />} onClick={() => setAddMemoryModalVisible(true)}>
{t('memory.add_memory')}
</Button>
<Dropdown
@ -712,7 +644,7 @@ const MemorySettings = () => {
{
key: 'refresh',
label: t('common.refresh'),
icon: <ReloadOutlined />,
icon: <RefreshIcon size={14} />,
onClick: () => loadMemories(currentUser)
},
{
@ -722,7 +654,7 @@ const MemorySettings = () => {
{
key: 'reset',
label: t('memory.reset_memories'),
icon: <DeleteOutlined />,
icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true,
onClick: () => handleResetMemories(currentUser)
},
@ -735,7 +667,7 @@ const MemorySettings = () => {
{
key: 'deleteUser',
label: t('memory.delete_user'),
icon: <UserDeleteOutlined />,
icon: <UserRoundMinus size={14} className="lucide-custom" />,
danger: true,
onClick: () => handleDeleteUser(currentUser)
}
@ -745,7 +677,7 @@ const MemorySettings = () => {
}}
trigger={['click']}
placement="bottomRight">
<Button icon={<MoreOutlined />}>{t('common.more')}</Button>
<Button icon={<MenuIcon size={16} />}>{t('common.more')}</Button>
</Dropdown>
</Space>
</div>
@ -765,7 +697,7 @@ const MemorySettings = () => {
</div>
<Button
type="primary"
icon={<PlusOutlined />}
icon={<PlusIcon size={18} />}
onClick={() => setAddMemoryModalVisible(true)}
size="large">
{t('memory.add_first_memory')}
@ -777,7 +709,7 @@ const MemorySettings = () => {
<>
{loading && (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 300 }}>
<Spin size="large" />
<Spin indicator={<LoadingIcon color="var(--color-text-2)" />} />
</div>
)}
@ -792,21 +724,21 @@ const MemorySettings = () => {
<MemoryItem key={memory.id}>
<div className="memory-header">
<div className="memory-meta">
<CalendarOutlined style={{ marginRight: 4 }} />
<Calendar size={14} style={{ marginRight: 4 }} />
<span>{memory.createdAt ? dayjs(memory.createdAt).fromNow() : '-'}</span>
</div>
<Space size="small">
<Button
type="text"
size="small"
icon={<EditOutlined />}
icon={<EditIcon size={14} />}
onClick={() => handleEditMemory(memory)}
/>
<Button
type="text"
size="small"
danger
icon={<DeleteOutlined />}
icon={<DeleteIcon size={14} className="lucide-custom" />}
onClick={() => {
window.modal.confirm({
centered: true,

View File

@ -0,0 +1,63 @@
import { HStack } from '@renderer/components/Layout'
import { Avatar, Button, Select, Space, Tooltip } from 'antd'
import { UserRoundPlus } from 'lucide-react'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { DEFAULT_USER_ID } from './constants'
interface UserSelectorProps {
currentUser: string
uniqueUsers: string[]
onUserSwitch: (userId: string) => void
onAddUser: () => void
}
const UserSelector: React.FC<UserSelectorProps> = ({ currentUser, uniqueUsers, onUserSwitch, onAddUser }) => {
const { t } = useTranslation()
const getUserAvatar = useCallback((user: string) => {
return user === DEFAULT_USER_ID ? user.slice(0, 1).toUpperCase() : user.slice(0, 2).toUpperCase()
}, [])
const renderLabel = useCallback(
(userId: string, userName: string) => {
return (
<HStack alignItems="center" gap={10}>
<Avatar size={20} style={{ background: 'var(--color-primary)' }}>
{getUserAvatar(userId)}
</Avatar>
<span>{userName}</span>
</HStack>
)
},
[getUserAvatar]
)
const options = useMemo(() => {
const defaultOption = {
value: DEFAULT_USER_ID,
label: renderLabel(DEFAULT_USER_ID, t('memory.default_user'))
}
const userOptions = uniqueUsers
.filter((user) => user !== DEFAULT_USER_ID)
.map((user) => ({
value: user,
label: renderLabel(user, user)
}))
return [defaultOption, ...userOptions]
}, [renderLabel, t, uniqueUsers])
return (
<Space.Compact>
<Select value={currentUser} onChange={onUserSwitch} style={{ width: 200 }} options={options} />
<Tooltip title={t('memory.add_new_user')}>
<Button type="default" onClick={onAddUser} icon={<UserRoundPlus size={16} />} />
</Tooltip>
</Space.Compact>
)
}
export default UserSelector

View File

@ -0,0 +1 @@
export const DEFAULT_USER_ID = 'default-user'

View File

@ -1,5 +1,6 @@
import { CloseCircleFilled, QuestionCircleOutlined } from '@ant-design/icons'
import EmojiPicker from '@renderer/components/EmojiPicker'
import { ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
@ -155,9 +156,9 @@ const AssistantSettings: FC = () => {
marginTop: 0
}}>
{t('settings.assistant.model_params')}
<Button onClick={onReset} style={{ width: 81 }}>
{t('chat.settings.reset')}
</Button>
<Tooltip title={t('common.reset')} mouseLeaveDelay={0}>
<Button type="text" onClick={onReset} icon={<ResetIcon size={16} />} />
</Tooltip>
</SettingSubtitle>
<SettingRow>
<HStack alignItems="center">

View File

@ -85,7 +85,8 @@ const ApiOptionsSettings = ({ providerId }: Props) => {
key: 'settings',
styles: {
header: {
paddingLeft: 0
paddingLeft: 0,
paddingRight: 6
},
body: {
padding: 0

View File

@ -1,5 +1,5 @@
import { CheckOutlined, CloseCircleFilled, LoadingOutlined } from '@ant-design/icons'
import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert'
import { LoadingIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import { ModelList } from '@renderer/components/ModelList'
import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup'
@ -16,7 +16,7 @@ import { formatErrorMessage } from '@renderer/utils/error'
import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd'
import Link from 'antd/es/typography/Link'
import { debounce, isEmpty } from 'lodash'
import { Settings2, SquareArrowOutUpRight } from 'lucide-react'
import { Check, Settings2, SquareArrowOutUpRight, TriangleAlert } from 'lucide-react'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -214,7 +214,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
return (
<Tooltip title={<ErrorOverlay>{apiKeyConnectivity.error}</ErrorOverlay>}>
<CloseCircleFilled style={{ color: 'var(--color-status-error)' }} />
<TriangleAlert size={16} color="var(--color-status-warning)" />
</Tooltip>
)
}
@ -285,9 +285,9 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
onClick={onCheckApi}
disabled={!apiHost || apiKeyConnectivity.checking}>
{apiKeyConnectivity.checking ? (
<LoadingOutlined spin />
<LoadingIcon />
) : apiKeyConnectivity.status === 'success' ? (
<CheckOutlined />
<Check size={16} className="lucide-custom" />
) : (
t('settings.provider.check')
)}

View File

@ -1,6 +1,6 @@
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import { DraggableVirtualList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { getProviderLogo, isSystemProvider } from '@renderer/config/providers'
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'
@ -15,7 +15,7 @@ import {
uuid
} from '@renderer/utils'
import { Avatar, Button, Card, Dropdown, Input, MenuProps, Tag } from 'antd'
import { Eye, EyeOff, Search, UserPen } from 'lucide-react'
import { Eye, EyeOff, PlusIcon, Search, UserPen } from 'lucide-react'
import { FC, startTransition, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSearchParams } from 'react-router-dom'
@ -324,7 +324,7 @@ const ProvidersList: FC = () => {
const editMenu = {
label: t('common.edit'),
key: 'edit',
icon: <EditOutlined />,
icon: <EditIcon size={14} />,
async onClick() {
const { name, type, logoFile, logo } = await AddProviderPopup.show(provider)
@ -362,7 +362,7 @@ const ProvidersList: FC = () => {
const deleteMenu = {
label: t('common.delete'),
key: 'delete',
icon: <DeleteOutlined />,
icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true,
async onClick() {
window.modal.confirm({
@ -492,7 +492,7 @@ const ProvidersList: FC = () => {
<AddButtonWrapper>
<Button
style={{ width: '100%', borderRadius: 'var(--list-item-border-radius)' }}
icon={<PlusOutlined />}
icon={<PlusIcon size={16} />}
onClick={onAddProvider}
disabled={dragging}>
{t('button.add')}

View File

@ -1,10 +1,12 @@
import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons'
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { useTheme } from '@renderer/context/ThemeProvider'
import FileItem from '@renderer/pages/files/FileItem'
import QuickPhraseService from '@renderer/services/QuickPhraseService'
import { QuickPhrase } from '@renderer/types'
import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd'
import { PlusIcon } from 'lucide-react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -74,7 +76,7 @@ const QuickPhraseSettings: FC = () => {
<SettingGroup style={{ marginBottom: 0 }} theme={theme}>
<SettingTitle>
{t('settings.quickPhrase.title')}
<Button type="text" icon={<PlusOutlined />} onClick={handleAdd} />
<Button type="text" icon={<PlusIcon size={18} />} onClick={handleAdd} />
</SettingTitle>
<SettingDivider />
<SettingRow>
@ -94,7 +96,12 @@ const QuickPhraseSettings: FC = () => {
extra: phrase.content,
actions: (
<Flex gap={4} style={{ opacity: 0.6 }}>
<Button key="edit" type="text" icon={<EditOutlined />} onClick={() => handleEdit(phrase)} />
<Button
key="edit"
type="text"
icon={<EditIcon size={14} />}
onClick={() => handleEdit(phrase)}
/>
<Popconfirm
title={t('settings.quickPhrase.delete')}
description={t('settings.quickPhrase.deleteConfirm')}
@ -102,7 +109,12 @@ const QuickPhraseSettings: FC = () => {
cancelText={t('common.cancel')}
onConfirm={() => handleDelete(phrase.id)}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
<Button key="delete" type="text" danger icon={<DeleteOutlined />} />
<Button
key="delete"
type="text"
danger
icon={<DeleteIcon size={14} className="lucide-custom" />}
/>
</Popconfirm>
</Flex>
)

View File

@ -1,5 +1,6 @@
import { LoadingOutlined } from '@ant-design/icons'
import { CircleX, Copy, Pause, RefreshCw } from 'lucide-react'
import { RefreshIcon } from '@renderer/components/Icons'
import { CircleX, Copy, Pause } from 'lucide-react'
import { FC, useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next'
@ -165,7 +166,7 @@ const WindowFooter: FC<FooterProps> = ({
</OpButton>
{onRegenerate && (
<OpButton onClick={handleRegenerate} $isWindowFocus={isWindowFocus} data-hovered={isRegenerateHovered}>
<RefreshCw size={14} className="btn-icon" />
<RefreshIcon size={14} className="btn-icon" />
{t('selection.action.window.r_regenerate')}
</OpButton>
)}

184
yarn.lock
View File

@ -84,7 +84,7 @@ __metadata:
languageName: node
linkType: hard
"@ant-design/colors@npm:^7.0.0, @ant-design/colors@npm:^7.2.0":
"@ant-design/colors@npm:^7.0.0":
version: 7.2.0
resolution: "@ant-design/colors@npm:7.2.0"
dependencies:
@ -93,6 +93,15 @@ __metadata:
languageName: node
linkType: hard
"@ant-design/colors@npm:^7.2.1":
version: 7.2.1
resolution: "@ant-design/colors@npm:7.2.1"
dependencies:
"@ant-design/fast-color": "npm:^2.0.6"
checksum: 10c0/4748a0bfb1ea98e08e29dcd4f7afd2781ae2119f783e6e9f80e889fd15fc19f7137e2a3d91f26bae2ab1ee76c04d520cc35f2bb0a708cd71e463f4d9deb4192d
languageName: node
linkType: hard
"@ant-design/cssinjs-utils@npm:^1.1.3":
version: 1.1.3
resolution: "@ant-design/cssinjs-utils@npm:1.1.3"
@ -4702,7 +4711,7 @@ __metadata:
languageName: node
linkType: hard
"@rc-component/trigger@npm:^2.0.0, @rc-component/trigger@npm:^2.1.1, @rc-component/trigger@npm:^2.2.6":
"@rc-component/trigger@npm:^2.0.0, @rc-component/trigger@npm:^2.1.1":
version: 2.2.6
resolution: "@rc-component/trigger@npm:2.2.6"
dependencies:
@ -4719,6 +4728,23 @@ __metadata:
languageName: node
linkType: hard
"@rc-component/trigger@npm:^2.3.0":
version: 2.3.0
resolution: "@rc-component/trigger@npm:2.3.0"
dependencies:
"@babel/runtime": "npm:^7.23.2"
"@rc-component/portal": "npm:^1.1.0"
classnames: "npm:^2.3.2"
rc-motion: "npm:^2.0.0"
rc-resize-observer: "npm:^1.3.1"
rc-util: "npm:^5.44.0"
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/84a0f3da8beca249ac86d2870ef9e1603ed26ff6b869a545e6d5c3793f23413cf6a11fafe001406a86c49917d31ec105776402dba834f1b79db98305ff8bd2f4
languageName: node
linkType: hard
"@reduxjs/toolkit@npm:^2.2.5":
version: 2.6.1
resolution: "@reduxjs/toolkit@npm:2.6.1"
@ -7702,7 +7728,7 @@ __metadata:
"@viz-js/lang-dot": "npm:^1.0.5"
"@viz-js/viz": "npm:^3.14.0"
"@xyflow/react": "npm:^12.4.4"
antd: "patch:antd@npm%3A5.24.7#~/.yarn/patches/antd-npm-5.24.7-356a553ae5.patch"
antd: "patch:antd@npm%3A5.26.7#~/.yarn/patches/antd-npm-5.26.7-029c5c381a.patch"
archiver: "npm:^7.0.1"
async-mutex: "npm:^0.5.0"
axios: "npm:^1.7.3"
@ -8017,11 +8043,11 @@ __metadata:
languageName: node
linkType: hard
"antd@npm:5.24.7":
version: 5.24.7
resolution: "antd@npm:5.24.7"
"antd@npm:5.26.7":
version: 5.26.7
resolution: "antd@npm:5.26.7"
dependencies:
"@ant-design/colors": "npm:^7.2.0"
"@ant-design/colors": "npm:^7.2.1"
"@ant-design/cssinjs": "npm:^1.23.0"
"@ant-design/cssinjs-utils": "npm:^1.1.3"
"@ant-design/fast-color": "npm:^2.0.6"
@ -8032,56 +8058,56 @@ __metadata:
"@rc-component/mutate-observer": "npm:^1.1.0"
"@rc-component/qrcode": "npm:~1.0.0"
"@rc-component/tour": "npm:~1.15.1"
"@rc-component/trigger": "npm:^2.2.6"
"@rc-component/trigger": "npm:^2.3.0"
classnames: "npm:^2.5.1"
copy-to-clipboard: "npm:^3.3.3"
dayjs: "npm:^1.11.11"
rc-cascader: "npm:~3.33.1"
rc-cascader: "npm:~3.34.0"
rc-checkbox: "npm:~3.5.0"
rc-collapse: "npm:~3.9.0"
rc-dialog: "npm:~9.6.0"
rc-drawer: "npm:~7.2.0"
rc-drawer: "npm:~7.3.0"
rc-dropdown: "npm:~4.2.1"
rc-field-form: "npm:~2.7.0"
rc-image: "npm:~7.11.1"
rc-image: "npm:~7.12.0"
rc-input: "npm:~1.8.0"
rc-input-number: "npm:~9.5.0"
rc-mentions: "npm:~2.20.0"
rc-menu: "npm:~9.16.1"
rc-motion: "npm:^2.9.5"
rc-notification: "npm:~5.6.3"
rc-notification: "npm:~5.6.4"
rc-pagination: "npm:~5.1.0"
rc-picker: "npm:~4.11.3"
rc-progress: "npm:~4.0.0"
rc-rate: "npm:~2.13.1"
rc-resize-observer: "npm:^1.4.3"
rc-segmented: "npm:~2.7.0"
rc-select: "npm:~14.16.6"
rc-select: "npm:~14.16.8"
rc-slider: "npm:~11.1.8"
rc-steps: "npm:~6.0.1"
rc-switch: "npm:~4.1.0"
rc-table: "npm:~7.50.4"
rc-tabs: "npm:~15.5.2"
rc-textarea: "npm:~1.10.0"
rc-table: "npm:~7.51.1"
rc-tabs: "npm:~15.6.1"
rc-textarea: "npm:~1.10.1"
rc-tooltip: "npm:~6.4.0"
rc-tree: "npm:~5.13.1"
rc-tree-select: "npm:~5.27.0"
rc-upload: "npm:~4.8.1"
rc-upload: "npm:~4.9.2"
rc-util: "npm:^5.44.4"
scroll-into-view-if-needed: "npm:^3.1.0"
throttle-debounce: "npm:^5.0.2"
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/d9b6d24cf0c74c3d22b5f799158694aba946e1971b0c1602b3319c85663b5ba6fc7b2135c91a0839d13ecd28a1643c5eb84de8a2586b8362e5a3a166c709b1ed
checksum: 10c0/2ff4469091790986faf7ad76854c3ea9ff231df2c2ba1eed93caf93986401193a8265d3b1a4074be79a9a9705a8c823508cfbbd370c5c8db04f7dcc90895ccf5
languageName: node
linkType: hard
"antd@patch:antd@npm%3A5.24.7#~/.yarn/patches/antd-npm-5.24.7-356a553ae5.patch":
version: 5.24.7
resolution: "antd@patch:antd@npm%3A5.24.7#~/.yarn/patches/antd-npm-5.24.7-356a553ae5.patch::version=5.24.7&hash=ff9154"
"antd@patch:antd@npm%3A5.26.7#~/.yarn/patches/antd-npm-5.26.7-029c5c381a.patch":
version: 5.26.7
resolution: "antd@patch:antd@npm%3A5.26.7#~/.yarn/patches/antd-npm-5.26.7-029c5c381a.patch::version=5.26.7&hash=a9067b"
dependencies:
"@ant-design/colors": "npm:^7.2.0"
"@ant-design/colors": "npm:^7.2.1"
"@ant-design/cssinjs": "npm:^1.23.0"
"@ant-design/cssinjs-utils": "npm:^1.1.3"
"@ant-design/fast-color": "npm:^2.0.6"
@ -8092,48 +8118,48 @@ __metadata:
"@rc-component/mutate-observer": "npm:^1.1.0"
"@rc-component/qrcode": "npm:~1.0.0"
"@rc-component/tour": "npm:~1.15.1"
"@rc-component/trigger": "npm:^2.2.6"
"@rc-component/trigger": "npm:^2.3.0"
classnames: "npm:^2.5.1"
copy-to-clipboard: "npm:^3.3.3"
dayjs: "npm:^1.11.11"
rc-cascader: "npm:~3.33.1"
rc-cascader: "npm:~3.34.0"
rc-checkbox: "npm:~3.5.0"
rc-collapse: "npm:~3.9.0"
rc-dialog: "npm:~9.6.0"
rc-drawer: "npm:~7.2.0"
rc-drawer: "npm:~7.3.0"
rc-dropdown: "npm:~4.2.1"
rc-field-form: "npm:~2.7.0"
rc-image: "npm:~7.11.1"
rc-image: "npm:~7.12.0"
rc-input: "npm:~1.8.0"
rc-input-number: "npm:~9.5.0"
rc-mentions: "npm:~2.20.0"
rc-menu: "npm:~9.16.1"
rc-motion: "npm:^2.9.5"
rc-notification: "npm:~5.6.3"
rc-notification: "npm:~5.6.4"
rc-pagination: "npm:~5.1.0"
rc-picker: "npm:~4.11.3"
rc-progress: "npm:~4.0.0"
rc-rate: "npm:~2.13.1"
rc-resize-observer: "npm:^1.4.3"
rc-segmented: "npm:~2.7.0"
rc-select: "npm:~14.16.6"
rc-select: "npm:~14.16.8"
rc-slider: "npm:~11.1.8"
rc-steps: "npm:~6.0.1"
rc-switch: "npm:~4.1.0"
rc-table: "npm:~7.50.4"
rc-tabs: "npm:~15.5.2"
rc-textarea: "npm:~1.10.0"
rc-table: "npm:~7.51.1"
rc-tabs: "npm:~15.6.1"
rc-textarea: "npm:~1.10.1"
rc-tooltip: "npm:~6.4.0"
rc-tree: "npm:~5.13.1"
rc-tree-select: "npm:~5.27.0"
rc-upload: "npm:~4.8.1"
rc-upload: "npm:~4.9.2"
rc-util: "npm:^5.44.4"
scroll-into-view-if-needed: "npm:^3.1.0"
throttle-debounce: "npm:^5.0.2"
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/e46e36f151ba0f2eef1190aaf772cbe2ab947de7d832aef2d872f6a28203a134e4ee71aa6f7a7c4c57979bf1955f1b4adfae733aca06f0e740a6b54a59d45a19
checksum: 10c0/5ac99ea57516c212b5da37bdb9ac6538ee0afa12adb9551be4215ef2be812ad79f46620042fd3a7cb94a2b6526072c5bfa1ae48be34946726fc3c03608f01e13
languageName: node
linkType: hard
@ -17619,9 +17645,9 @@ __metadata:
languageName: node
linkType: hard
"rc-cascader@npm:~3.33.1":
version: 3.33.1
resolution: "rc-cascader@npm:3.33.1"
"rc-cascader@npm:~3.34.0":
version: 3.34.0
resolution: "rc-cascader@npm:3.34.0"
dependencies:
"@babel/runtime": "npm:^7.25.7"
classnames: "npm:^2.3.1"
@ -17631,7 +17657,7 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/aa77ec3e5285179dfb924d08820532ac8c8ca413cbdf7a496dc11a666199f26a4c7d0f16357a1821cff776012ff93bcf0b07a9ce8dc8c45ab42567fc499fecab
checksum: 10c0/1fc8c55e0f78ff2be59e2bcd8faa53aafecebbb28f4bb9982ad39e8f9f9620e15d6119797c7890347e46b05c32b43177ece047e81ef04c22a9f041eb0dd53e0a
languageName: node
linkType: hard
@ -17680,9 +17706,9 @@ __metadata:
languageName: node
linkType: hard
"rc-drawer@npm:~7.2.0":
version: 7.2.0
resolution: "rc-drawer@npm:7.2.0"
"rc-drawer@npm:~7.3.0":
version: 7.3.0
resolution: "rc-drawer@npm:7.3.0"
dependencies:
"@babel/runtime": "npm:^7.23.9"
"@rc-component/portal": "npm:^1.1.1"
@ -17692,7 +17718,7 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/d6814998983100193d6a521745324a94fa8d65cbb979b2764d1d48a78d6b6e22f9d078ebb68545b68a0cc38f062298d11ed3635e93d004a60415d384330a8573
checksum: 10c0/e2c3211d6a3790813bf2c1626cebf3fdb3a4c48ab56bee2d208ba07dd0e5058154981563e89e02571d573dd56c2ddc65db33a0cf37c58820ecc4b08785e8d169
languageName: node
linkType: hard
@ -17725,9 +17751,9 @@ __metadata:
languageName: node
linkType: hard
"rc-image@npm:~7.11.1":
version: 7.11.1
resolution: "rc-image@npm:7.11.1"
"rc-image@npm:~7.12.0":
version: 7.12.0
resolution: "rc-image@npm:7.12.0"
dependencies:
"@babel/runtime": "npm:^7.11.2"
"@rc-component/portal": "npm:^1.0.2"
@ -17738,7 +17764,7 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/61db982d618c3d31e99bd1be8e7b49f53feff7170a67483aa97770e686cc7e9ab4dea787f345e0f7ea5704d42f390aeaf99ffb3d7684a2b838b1f4738306eff0
checksum: 10c0/229f848725f8cff5b6015eb0468a24a3d92c2aead48dc98abe19e6ff15840defc9b42f1a126b7f8180f678b5380ff99528bb89e972298ad456773e4070f33934
languageName: node
linkType: hard
@ -17821,9 +17847,9 @@ __metadata:
languageName: node
linkType: hard
"rc-notification@npm:~5.6.3":
version: 5.6.3
resolution: "rc-notification@npm:5.6.3"
"rc-notification@npm:~5.6.4":
version: 5.6.4
resolution: "rc-notification@npm:5.6.4"
dependencies:
"@babel/runtime": "npm:^7.10.1"
classnames: "npm:2.x"
@ -17832,7 +17858,7 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/33ba437ce879f28f2773c41044aca2a6fb63855eb1535b5ca79736016996bd265df3bdff536c2b111bd82b07d5a98c4b181dc7738e98baf0e129c04fd813feb0
checksum: 10c0/ea6a587b6a6057e8e6273d642cac5608b44948374ed636c9a83d104d21731c114b43036e33add05f755ceefb3f9258b881776672d5745c0e5d19f1d66449f37a
languageName: node
linkType: hard
@ -17953,7 +17979,7 @@ __metadata:
languageName: node
linkType: hard
"rc-select@npm:~14.16.2, rc-select@npm:~14.16.6":
"rc-select@npm:~14.16.2":
version: 14.16.6
resolution: "rc-select@npm:14.16.6"
dependencies:
@ -17971,6 +17997,24 @@ __metadata:
languageName: node
linkType: hard
"rc-select@npm:~14.16.8":
version: 14.16.8
resolution: "rc-select@npm:14.16.8"
dependencies:
"@babel/runtime": "npm:^7.10.1"
"@rc-component/trigger": "npm:^2.1.1"
classnames: "npm:2.x"
rc-motion: "npm:^2.0.1"
rc-overflow: "npm:^1.3.1"
rc-util: "npm:^5.16.1"
rc-virtual-list: "npm:^3.5.2"
peerDependencies:
react: "*"
react-dom: "*"
checksum: 10c0/45f93e270c4b5e5ffc4b0ba0ce5e5ea72fff591a9a7a19b460b1ead0517d17327af9a4c32ce3c7f92b765724f4dabd1aa7146f5a06db73be91c884fe13c92774
languageName: node
linkType: hard
"rc-slider@npm:~11.1.8":
version: 11.1.8
resolution: "rc-slider@npm:11.1.8"
@ -18013,9 +18057,9 @@ __metadata:
languageName: node
linkType: hard
"rc-table@npm:~7.50.4":
version: 7.50.4
resolution: "rc-table@npm:7.50.4"
"rc-table@npm:~7.51.1":
version: 7.51.1
resolution: "rc-table@npm:7.51.1"
dependencies:
"@babel/runtime": "npm:^7.10.1"
"@rc-component/context": "npm:^1.4.0"
@ -18026,13 +18070,13 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/ab5eb3db00bc31470d7dd5946c1a919247742703a3278ea2d9a33e719a08e170ca80977e615084c7cfcb54508a9c3da2449409cce16fc1d7ec1ea67596c30d79
checksum: 10c0/ba6b25b7ff5555c059bdd8d504846839a786eb3eb5701b0ac648fb65e8197ed5a9d046d06881df19f6b1fbe014097f7260cc9e1c25c7644278cc6a53c71198fb
languageName: node
linkType: hard
"rc-tabs@npm:~15.5.2":
version: 15.5.2
resolution: "rc-tabs@npm:15.5.2"
"rc-tabs@npm:~15.6.1":
version: 15.6.1
resolution: "rc-tabs@npm:15.6.1"
dependencies:
"@babel/runtime": "npm:^7.11.2"
classnames: "npm:2.x"
@ -18044,7 +18088,7 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/59b66c809da92b3d6943020bc71ce1702f9c92c59ca48d6922aaa739bec20796f96c9ce37fd8ef0553c21e312db7a51b1ab700c6b7993761727951386276585a
checksum: 10c0/b982582902684eae5ec1783af4de1d31bd8c8fc7419dec3abd883abfa51d7a270db01a73bc579341fc84b0c8a92fc2cbd0e8ed272ce31b4ea2ac5979eaf5215c
languageName: node
linkType: hard
@ -18064,6 +18108,22 @@ __metadata:
languageName: node
linkType: hard
"rc-textarea@npm:~1.10.1":
version: 1.10.2
resolution: "rc-textarea@npm:1.10.2"
dependencies:
"@babel/runtime": "npm:^7.10.1"
classnames: "npm:^2.2.1"
rc-input: "npm:~1.8.0"
rc-resize-observer: "npm:^1.0.0"
rc-util: "npm:^5.27.0"
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/ccfe7bae33187c382e12bc14e9f2617fe183a4d4e8c0d3b9f71455728172f31a6140d0855ff557b6c658daf31c7ff935a1a347a336f8106ddda84e042ab23448
languageName: node
linkType: hard
"rc-tooltip@npm:~6.4.0":
version: 6.4.0
resolution: "rc-tooltip@npm:6.4.0"
@ -18111,9 +18171,9 @@ __metadata:
languageName: node
linkType: hard
"rc-upload@npm:~4.8.1":
version: 4.8.1
resolution: "rc-upload@npm:4.8.1"
"rc-upload@npm:~4.9.2":
version: 4.9.2
resolution: "rc-upload@npm:4.9.2"
dependencies:
"@babel/runtime": "npm:^7.18.3"
classnames: "npm:^2.2.5"
@ -18121,7 +18181,7 @@ __metadata:
peerDependencies:
react: ">=16.9.0"
react-dom: ">=16.9.0"
checksum: 10c0/27555e114c1705b88558312beeda4b8d3e74d192c09f5a41d161045cbe621d372973acaffff8b9c79ebfe495962e2bf96aedffc9fadc96b5e7c7b5cd1af98b99
checksum: 10c0/2299f4a3d8fa87449178761fcdc3c574b21e34d2f6816ecb6c08729f699e67b23d1284f4077b9ceceb1cc8502d7c350311bc2d1dfacec7f6780d2bf9691888cb
languageName: node
linkType: hard