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 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 --- a/es/dropdown/dropdown.js
+++ b/es/dropdown/dropdown.js +++ b/es/dropdown/dropdown.js
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
@ -11,7 +11,7 @@ index 986877a762b9ad0aca596a8552732cd12d2eaabb..1f18aa2ea745e68950e4cee16d4d655f
import classNames from 'classnames'; import classNames from 'classnames';
import RcDropdown from 'rc-dropdown'; import RcDropdown from 'rc-dropdown';
import useEvent from "rc-util/es/hooks/useEvent"; 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` className: `${prefixCls}-menu-submenu-arrow`
}, direction === 'rtl' ? (/*#__PURE__*/React.createElement(LeftOutlined, { }, direction === 'rtl' ? (/*#__PURE__*/React.createElement(LeftOutlined, {
className: `${prefixCls}-menu-submenu-arrow-icon` className: `${prefixCls}-menu-submenu-arrow-icon`
@ -24,22 +24,8 @@ index 986877a762b9ad0aca596a8552732cd12d2eaabb..1f18aa2ea745e68950e4cee16d4d655f
}))), }))),
mode: "vertical", mode: "vertical",
selectable: false, 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 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 --- a/es/select/useIcons.js
+++ b/es/select/useIcons.js +++ b/es/select/useIcons.js
@@ -4,10 +4,10 @@ import * as React from 'react'; @@ -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 SearchOutlined from "@ant-design/icons/es/icons/SearchOutlined";
import { devUseWarning } from '../_util/warning'; import { devUseWarning } from '../_util/warning';
+import { ChevronDown } from 'lucide-react'; +import { ChevronDown } from 'lucide-react';
export default function useIcons(_ref) { export default function useIcons({
let {
suffixIcon, suffixIcon,
@@ -56,8 +56,10 @@ export default function useIcons(_ref) { clearIcon,
@@ -54,8 +54,10 @@ export default function useIcons({
className: iconCls className: iconCls
})); }));
} }

View File

@ -162,7 +162,7 @@
"@viz-js/lang-dot": "^1.0.5", "@viz-js/lang-dot": "^1.0.5",
"@viz-js/viz": "^3.14.0", "@viz-js/viz": "^3.14.0",
"@xyflow/react": "^12.4.4", "@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", "archiver": "^7.0.1",
"async-mutex": "^0.5.0", "async-mutex": "^0.5.0",
"axios": "^1.7.3", "axios": "^1.7.3",

View File

@ -12,6 +12,13 @@
outline: none; 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 { .ant-tabs-tabpane:focus-visible {
outline: none; outline: none;
} }
@ -84,6 +91,14 @@
max-height: 50vh; max-height: 50vh;
overflow-y: auto; overflow-y: auto;
border: 0.5px solid var(--color-border); 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 { .ant-dropdown-arrow + .ant-dropdown-menu {
border: none; border: none;
@ -96,6 +111,10 @@
background-color: var(--ant-color-bg-elevated); background-color: var(--ant-color-bg-elevated);
overflow: hidden; overflow: hidden;
border-radius: var(--ant-border-radius-lg); border-radius: var(--ant-border-radius-lg);
.ant-dropdown-menu-submenu-title {
align-items: center;
}
} }
.ant-popover { .ant-popover {

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { LoadingOutlined } from '@ant-design/icons'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import CodeEditor from '@renderer/components/CodeEditor' import CodeEditor from '@renderer/components/CodeEditor'
import { CodeTool, CodeToolbar, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar' import { CodeTool, CodeToolbar, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar'
import { LoadingIcon } from '@renderer/components/Icons'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { pyodideService } from '@renderer/services/PyodideService' import { pyodideService } from '@renderer/services/PyodideService'
import { extractTitle } from '@renderer/utils/formats' import { extractTitle } from '@renderer/utils/formats'
@ -173,7 +173,7 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
registerTool({ registerTool({
...TOOL_SPECS.run, ...TOOL_SPECS.run,
icon: isRunning ? <LoadingOutlined /> : <CirclePlay className="icon" />, icon: isRunning ? <LoadingIcon /> : <CirclePlay className="icon" />,
tooltip: t('code_block.run'), tooltip: t('code_block.run'),
onClick: () => !isRunning && handleRunScript() 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) => { const CopyIcon = (props: React.ComponentProps<typeof Copy>) => <Copy size="1rem" {...props} />
return <i {...props} className={`iconfont icon-copy ${props.className}`} />
}
export default CopyIcon 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' 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' const animationClassName = 'svg-spinner-anim-180-ring'
@ -25,11 +26,11 @@ export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement>) {
</style> </style>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="1em" width={size}
height="1em" height={size}
viewBox="0 0 24 24" viewBox="0 0 24 24"
{...props} {...svgProps}
className={`${animationClassName} ${props.className || ''}`.trim()}> className={`${animationClassName} ${svgProps.className || ''}`.trim()}>
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */} {/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
<path <path
fill="currentColor" 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 { Tooltip, TooltipProps } from 'antd'
import { Info } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'> type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface InfoTooltipProps extends InheritedTooltipProps { interface InfoTooltipProps extends InheritedTooltipProps {
iconColor?: string iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties 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 ( return (
<Tooltip {...rest}> <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> </Tooltip>
) )
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator' import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
import { EditIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import ModelIdWithTags from '@renderer/components/ModelIdWithTags' import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
import { getModelLogo } from '@renderer/config/models' import { getModelLogo } from '@renderer/config/models'
@ -6,7 +7,7 @@ import { Model } from '@renderer/types'
import { ModelWithStatus } from '@renderer/types/healthCheck' import { ModelWithStatus } from '@renderer/types/healthCheck'
import { maskApiKey } from '@renderer/utils/api' import { maskApiKey } from '@renderer/utils/api'
import { Avatar, Button, Tooltip } from 'antd' import { Avatar, Button, Tooltip } from 'antd'
import { Minus, Pen } from 'lucide-react' import { Minus } from 'lucide-react'
import React, { memo } from 'react' import React, { memo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -51,7 +52,7 @@ const ModelListItem: React.FC<ModelListItemProps> = ({ ref, model, modelStatus,
<HealthStatusIndicator results={healthResults} loading={isChecking} showLatency /> <HealthStatusIndicator results={healthResults} loading={isChecking} showLatency />
<HStack alignItems="center" gap={0}> <HStack alignItems="center" gap={0}>
<Tooltip title={t('models.edit')} mouseLeaveDelay={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>
<Tooltip title={t('settings.models.manage.remove_model')} mouseLeaveDelay={0}> <Tooltip title={t('settings.models.manage.remove_model')} mouseLeaveDelay={0}>
<Button type="text" onClick={() => onRemove(model)} disabled={disabled} icon={<Minus size={14} />} /> <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 { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
import { EditIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon' import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import { ApiKeyWithStatus } from '@renderer/types/healthCheck' import { ApiKeyWithStatus } from '@renderer/types/healthCheck'
import { maskApiKey } from '@renderer/utils/api' import { maskApiKey } from '@renderer/utils/api'
import { Button, Flex, Input, InputRef, List, Popconfirm, Tooltip, Typography } from 'antd' 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 { FC, memo, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -148,7 +149,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
</Tooltip> </Tooltip>
)} )}
<Tooltip title={t('common.edit')} mouseLeaveDelay={0}> <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> </Tooltip>
<Popconfirm <Popconfirm
title={t('common.delete_confirm')} 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 { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess' import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
@ -8,7 +8,7 @@ import { SettingHelpText } from '@renderer/pages/settings'
import { isProviderSupportAuth } from '@renderer/services/ProviderService' import { isProviderSupportAuth } from '@renderer/services/ProviderService'
import { ApiKeyWithStatus, HealthStatus } from '@renderer/types/healthCheck' import { ApiKeyWithStatus, HealthStatus } from '@renderer/types/healthCheck'
import { Button, Card, Flex, List, Popconfirm, Space, Tooltip, Typography } from 'antd' 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 { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -140,7 +140,12 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
okButtonProps={{ danger: true }}> okButtonProps={{ danger: true }}>
<Tooltip title={t('settings.provider.remove_invalid_keys')} placement="top" mouseLeaveDelay={0}> <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> </Tooltip>
</Popconfirm> </Popconfirm>
@ -161,7 +166,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
key="add" key="add"
type="primary" type="primary"
onClick={handleAddNew} onClick={handleAddNew}
icon={<PlusOutlined />} icon={<Plus size={16} />}
autoFocus={shouldAutoFocus()} autoFocus={shouldAutoFocus()}
disabled={isChecking || !!pendingNewKey}> disabled={isChecking || !!pendingNewKey}>
{t('common.add')} {t('common.add')}

View File

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

View File

@ -1,9 +1,25 @@
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import { describe, expect, it, vi } from 'vitest'
import { describe, expect, it } from 'vitest'
import InfoTooltip from '../InfoTooltip' 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', () => { describe('InfoTooltip', () => {
it('should match snapshot', () => { it('should match snapshot', () => {
const { container } = render( const { container } = render(
@ -12,13 +28,11 @@ describe('InfoTooltip', () => {
expect(container.firstChild).toMatchSnapshot() 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' const tooltipText = 'This is helpful information'
render(<InfoTooltip title={tooltipText} />) render(<InfoTooltip title={tooltipText} />)
const icon = screen.getByRole('img', { name: 'Information' }) expect(screen.getByRole('img', { name: 'Information' })).toBeInTheDocument()
await userEvent.hover(icon) expect(screen.getByText(tooltipText)).toBeInTheDocument()
expect(await screen.findByText(tooltipText)).toBeInTheDocument()
}) })
}) })

View File

@ -1,11 +1,11 @@
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 userEvent from '@testing-library/user-event'
import React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest'
import InputEmbeddingDimension from '../InputEmbeddingDimension' import InputEmbeddingDimension from '../InputEmbeddingDimension'
const mocks = vi.hoisted(() => { const mocks = vi.hoisted(() => ({
return {
aiCore: { aiCore: {
getEmbeddingDimensions: vi.fn() getEmbeddingDimensions: vi.fn()
}, },
@ -21,6 +21,50 @@ const mocks = vi.hoisted(() => {
return translations[k] || k 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
} }
}) })
@ -46,20 +90,10 @@ vi.mock('react-i18next', () => ({
} }
})) }))
// Mock logger vi.mock('@renderer/components/Icons', () => ({
vi.mock('@logger', () => ({ RefreshIcon: (props: React.SVGProps<SVGSVGElement>) => (
loggerService: {
withContext: () => ({
warn: vi.fn(),
error: vi.fn()
})
}
}))
vi.mock('lucide-react', () => ({
RefreshCw: (props: React.SVGProps<SVGSVGElement>) => (
<svg data-testid="refresh-icon" aria-label="refresh" role="img" {...props}> <svg data-testid="refresh-icon" aria-label="refresh" role="img" {...props}>
RefreshCw RefreshIcon
</svg> </svg>
) )
})) }))
@ -119,13 +153,11 @@ describe('InputEmbeddingDimension', () => {
describe('functionality', () => { describe('functionality', () => {
it('should call onChange when input value changes', async () => { it('should call onChange when input value changes', async () => {
const handleChange = vi.fn() const handleChange = vi.fn()
const user = userEvent.setup()
render(<InputEmbeddingDimension model={mockModel} onChange={handleChange} />) render(<InputEmbeddingDimension model={mockModel} onChange={handleChange} />)
const input = screen.getByPlaceholderText('请输入维度大小') const input = screen.getByPlaceholderText('请输入维度大小')
await user.clear(input) fireEvent.change(input, { target: { value: '2048' } })
await user.type(input, '2048')
expect(handleChange).toHaveBeenCalledWith(2048) expect(handleChange).toHaveBeenCalledWith(2048)
}) })
@ -182,7 +214,6 @@ describe('InputEmbeddingDimension', () => {
it('should handle null value correctly', async () => { it('should handle null value correctly', async () => {
const handleChange = vi.fn() const handleChange = vi.fn()
const user = userEvent.setup()
render(<InputEmbeddingDimension model={mockModel} value={null} onChange={handleChange} />) render(<InputEmbeddingDimension model={mockModel} value={null} onChange={handleChange} />)
@ -190,7 +221,7 @@ describe('InputEmbeddingDimension', () => {
expect(input.value).toBe('') expect(input.value).toBe('')
// Should allow typing new value // Should allow typing new value
await user.type(input, '1024') fireEvent.change(input, { target: { value: '1024' } })
expect(handleChange).toHaveBeenCalledWith(1024) expect(handleChange).toHaveBeenCalledWith(1024)
}) })
}) })

View File

@ -1,28 +1,18 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`InfoTooltip > should match snapshot 1`] = ` exports[`InfoTooltip > should match snapshot 1`] = `
<span <div>
aria-describedby="test-id" <div
aria-label="Information" aria-label="Information"
class="anticon anticon-info-circle" color="#1890ff"
role="img" role="img"
style="color: rgb(24, 144, 255); font-size: 16px;" size="14"
> style="font-size: 16px;"
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
> >
<path Info
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" </div>
/> <div>
<path Test tooltip
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" </div>
/> </div>
</svg>
</span>
`; `;

View File

@ -2,95 +2,24 @@
exports[`InputEmbeddingDimension > basic rendering > should match snapshot with all props 1`] = ` exports[`InputEmbeddingDimension > basic rendering > should match snapshot with all props 1`] = `
<div <div
class="ant-space-compact css-dev-only-do-not-override-1261szd" data-testid="space-compact"
style="width: 100%;" 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"
style="flex: 1;"
>
<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 <input
aria-valuemin="1" data-testid="input-number"
aria-valuenow="1536"
autocomplete="off"
class="ant-input-number-input"
placeholder="请输入维度大小" placeholder="请输入维度大小"
role="spinbutton" style="flex: 1;"
step="1" type="number"
value="1536" value="1536"
/> />
</div> <div
</div> data-testid="tooltip"
data-title="自动设置维度"
>
<button <button
aria-describedby="test-id"
aria-label="Get embedding dimension" 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" role="button"
type="button" type="button"
>
<span
class="ant-btn-icon"
> >
<svg <svg
aria-label="refresh" aria-label="refresh"
@ -98,124 +27,37 @@ exports[`InputEmbeddingDimension > basic rendering > should match snapshot with
role="img" role="img"
size="16" size="16"
> >
RefreshCw RefreshIcon
</svg> </svg>
</span>
</button> </button>
</div>
</div> </div>
`; `;
exports[`InputEmbeddingDimension > basic rendering > should match snapshot with loading state 1`] = ` exports[`InputEmbeddingDimension > basic rendering > should match snapshot with loading state 1`] = `
<div <div
class="ant-space-compact css-dev-only-do-not-override-1261szd" data-testid="space-compact"
style="width: 100%;" 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"
style="flex: 1;"
>
<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 <input
aria-valuemin="1" data-testid="input-number"
autocomplete="off"
class="ant-input-number-input"
placeholder="请输入维度大小" placeholder="请输入维度大小"
role="spinbutton" style="flex: 1;"
step="1" type="number"
value="" value=""
/> />
</div> <div
</div> data-testid="tooltip"
data-title="自动设置维度"
>
<button <button
aria-describedby="test-id"
aria-label="Get embedding dimension" 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" disabled=""
role="button" role="button"
type="button" type="button"
> >
<span Loading...
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> </button>
</div>
</div> </div>
`; `;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -596,7 +596,7 @@
"list": "Lista de tópicos", "list": "Lista de tópicos",
"move_to": "Mover para", "move_to": "Mover para",
"new": "Começar nova conversa", "new": "Começar nova conversa",
"pinned": "Fixar tópico", "pin": "Fixar tópico",
"prompt": { "prompt": {
"edit": { "edit": {
"title": "Editar prompt do tópico" "title": "Editar prompt do tópico"
@ -605,7 +605,7 @@
"tips": "Prompt do tópico: fornecer prompts adicionais para o tópico atual" "tips": "Prompt do tópico: fornecer prompts adicionais para o tópico atual"
}, },
"title": "Tópicos", "title": "Tópicos",
"unpinned": "Desfixar" "unpin": "Desfixar"
}, },
"translate": "Traduzir" "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 CustomTag from '@renderer/components/CustomTag'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { useAgents } from '@renderer/hooks/useAgents' import { useAgents } from '@renderer/hooks/useAgents'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { createAssistantFromAgent } from '@renderer/services/AssistantService' import { createAssistantFromAgent } from '@renderer/services/AssistantService'
@ -14,6 +7,7 @@ import type { Agent } from '@renderer/types'
import { getLeadingEmoji } from '@renderer/utils' import { getLeadingEmoji } from '@renderer/utils'
import { Button, Dropdown } from 'antd' import { Button, Dropdown } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { ArrowDownAZ, Ellipsis, PlusIcon, SquareArrowOutUpRight } from 'lucide-react'
import { type FC, memo, useCallback, useEffect, useRef, useState } from 'react' import { type FC, memo, useCallback, useEffect, useRef, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -66,7 +60,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{ {
key: 'edit', key: 'edit',
label: t('agents.edit.title'), label: t('agents.edit.title'),
icon: <EditOutlined />, icon: <EditIcon size={14} />,
onClick: (e: any) => { onClick: (e: any) => {
e.domEvent.stopPropagation() e.domEvent.stopPropagation()
AssistantSettingsPopup.show({ assistant: agent }) AssistantSettingsPopup.show({ assistant: agent })
@ -75,7 +69,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{ {
key: 'create', key: 'create',
label: t('agents.add.button'), label: t('agents.add.button'),
icon: <PlusOutlined />, icon: <PlusIcon size={14} />,
onClick: (e: any) => { onClick: (e: any) => {
e.domEvent.stopPropagation() e.domEvent.stopPropagation()
createAssistantFromAgent(agent) createAssistantFromAgent(agent)
@ -84,7 +78,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{ {
key: 'sort', key: 'sort',
label: t('agents.sorting.title'), label: t('agents.sorting.title'),
icon: <SortAscendingOutlined />, icon: <ArrowDownAZ size={14} />,
onClick: (e: any) => { onClick: (e: any) => {
e.domEvent.stopPropagation() e.domEvent.stopPropagation()
ManageAgentsPopup.show() ManageAgentsPopup.show()
@ -93,7 +87,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{ {
key: 'export', key: 'export',
label: t('agents.export.agent'), label: t('agents.export.agent'),
icon: <ExportOutlined />, icon: <SquareArrowOutUpRight size={14} />,
onClick: (e: any) => { onClick: (e: any) => {
e.domEvent.stopPropagation() e.domEvent.stopPropagation()
exportAgent() exportAgent()
@ -102,7 +96,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
{ {
key: 'delete', key: 'delete',
label: t('common.delete'), label: t('common.delete'),
icon: <DeleteOutlined />, icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true, danger: true,
onClick: (e: any) => { onClick: (e: any) => {
e.domEvent.stopPropagation() e.domEvent.stopPropagation()
@ -173,7 +167,7 @@ const AgentCard: FC<Props> = ({ agent, onClick, activegroup, getLocalizedGroupNa
color="default" color="default"
variant="filled" variant="filled"
shape="circle" shape="circle"
icon={<EllipsisOutlined />} icon={<Ellipsis size={14} color="var(--color-text-3)" />}
/> />
</Dropdown> </Dropdown>
</AgentCardHeaderInfoAction> </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 { DynamicVirtualList } from '@renderer/components/VirtualList'
import { handleDelete } from '@renderer/services/FileAction' import { handleDelete } from '@renderer/services/FileAction'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
@ -68,7 +69,7 @@ const FileList: React.FC<FileItemProps> = ({ id, list, files }) => {
icon: <ExclamationCircleOutlined style={{ color: 'red' }} /> icon: <ExclamationCircleOutlined style={{ color: 'red' }} />
}) })
}}> }}>
<DeleteOutlined /> <DeleteIcon size={14} className="lucide-custom" />
</DeleteButton> </DeleteButton>
</ImageWrapper> </ImageWrapper>
</Col> </Col>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,14 @@ vi.mock('@renderer/store/messageBlock', () => ({
messageBlocksSelectors: mocks.messageBlocksSelectors 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', () => ({ vi.mock('react-i18next', () => ({
useTranslation: () => ({ useTranslation: () => ({
t: (key: string) => key t: (key: string) => key

View File

@ -71,32 +71,10 @@ exports[`Table > rendering > should match snapshot 1`] = `
class="c2" class="c2"
role="button" role="button"
> >
<svg <div
aria-hidden="true"
class="lucide lucide-copy"
data-testid="copy-icon" data-testid="copy-icon"
fill="none" style="width: 14px; height: 14px;"
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>
</div> </div>
</div> </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 { MessageBlockStatus, MessageBlockType, type PlaceholderMessageBlock } from '@renderer/types/newMessage'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -10,7 +10,7 @@ const PlaceholderBlock: React.FC<PlaceholderBlockProps> = ({ block }) => {
if (block.status === MessageBlockStatus.PROCESSING && block.type === MessageBlockType.UNKNOWN) { if (block.status === MessageBlockStatus.PROCESSING && block.type === MessageBlockType.UNKNOWN) {
return ( return (
<MessageContentLoading> <MessageContentLoading>
<SvgSpinners180Ring /> <LoadingIcon />
</MessageContentLoading> </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 ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
import SaveToKnowledgePopup from '@renderer/components/Popups/SaveToKnowledgePopup' import SaveToKnowledgePopup from '@renderer/components/Popups/SaveToKnowledgePopup'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' 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 { findMainTextBlocks, findTranslationBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
import { Dropdown, Popconfirm, Tooltip } from 'antd' import { Dropdown, Popconfirm, Tooltip } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import { AtSign, Check, FilePenLine, Languages, ListChecks, Menu, Save, Split, ThumbsUp, Upload } from 'lucide-react'
AtSign,
Copy,
FilePenLine,
Languages,
ListChecks,
Menu,
RefreshCw,
Save,
Share,
Split,
ThumbsUp,
Trash
} from 'lucide-react'
import { FC, memo, useCallback, useMemo, useState } from 'react' import { FC, memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
@ -223,7 +211,7 @@ const MessageMenubar: FC<Props> = (props) => {
{ {
label: t('chat.save.label'), label: t('chat.save.label'),
key: 'save', key: 'save',
icon: <Save size={15} color="var(--color-icon)" style={{ marginTop: 3 }} />, icon: <Save size={15} />,
children: [ children: [
{ {
label: t('chat.save.file.title'), label: t('chat.save.file.title'),
@ -245,7 +233,7 @@ const MessageMenubar: FC<Props> = (props) => {
{ {
label: t('chat.topics.export.title'), label: t('chat.topics.export.title'),
key: 'export', key: 'export',
icon: <Share size={15} color="var(--color-icon)" style={{ marginTop: 3 }} />, icon: <Upload size={15} />,
children: [ children: [
exportMenuOptions.plain_text && { exportMenuOptions.plain_text && {
label: t('chat.topics.copy.plain_text'), label: t('chat.topics.copy.plain_text'),
@ -440,28 +428,28 @@ const MessageMenubar: FC<Props> = (props) => {
className="message-action-button" className="message-action-button"
onClick={() => handleResendUserMessage()} onClick={() => handleResendUserMessage()}
$softHoverBg={isBubbleStyle}> $softHoverBg={isBubbleStyle}>
<SyncOutlined /> <RefreshIcon size={15} />
</ActionButton> </ActionButton>
</Tooltip> </Tooltip>
)} )}
{message.role === 'user' && ( {message.role === 'user' && (
<Tooltip title={t('common.edit')} mouseEnterDelay={0.8}> <Tooltip title={t('common.edit')} mouseEnterDelay={0.8}>
<ActionButton className="message-action-button" onClick={onEdit} $softHoverBg={softHoverBg}> <ActionButton className="message-action-button" onClick={onEdit} $softHoverBg={softHoverBg}>
<EditOutlined /> <EditIcon size={15} />
</ActionButton> </ActionButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}> <Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
<ActionButton className="message-action-button" onClick={onCopy} $softHoverBg={softHoverBg}> <ActionButton className="message-action-button" onClick={onCopy} $softHoverBg={softHoverBg}>
{!copied && <Copy size={15} />} {!copied && <CopyIcon size={15} />}
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />} {copied && <Check size={15} color="var(--color-primary)" />}
</ActionButton> </ActionButton>
</Tooltip> </Tooltip>
{isAssistantMessage && ( {isAssistantMessage && (
<Popconfirm <Popconfirm
title={t('message.regenerate.confirm')} title={t('message.regenerate.confirm')}
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />} icon={<InfoCircleOutlined style={{ color: 'red' }} />}
onConfirm={onRegenerate} onConfirm={onRegenerate}
onOpenChange={(open) => open && setShowRegenerateTooltip(false)}> onOpenChange={(open) => open && setShowRegenerateTooltip(false)}>
<Tooltip <Tooltip
@ -470,7 +458,7 @@ const MessageMenubar: FC<Props> = (props) => {
open={showRegenerateTooltip} open={showRegenerateTooltip}
onOpenChange={setShowRegenerateTooltip}> onOpenChange={setShowRegenerateTooltip}>
<ActionButton className="message-action-button" $softHoverBg={softHoverBg}> <ActionButton className="message-action-button" $softHoverBg={softHoverBg}>
<RefreshCw size={15} /> <RefreshIcon size={15} />
</ActionButton> </ActionButton>
</Tooltip> </Tooltip>
</Popconfirm> </Popconfirm>
@ -571,7 +559,7 @@ const MessageMenubar: FC<Props> = (props) => {
<Popconfirm <Popconfirm
title={t('message.message.delete.content')} title={t('message.message.delete.content')}
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
icon={<QuestionCircleOutlined style={{ color: 'red' }} />} icon={<InfoCircleOutlined style={{ color: 'red' }} />}
onOpenChange={(open) => open && setShowDeleteTooltip(false)} onOpenChange={(open) => open && setShowDeleteTooltip(false)}
onConfirm={() => deleteMessage(message.id, message.traceId, message.model?.name)}> onConfirm={() => deleteMessage(message.id, message.traceId, message.model?.name)}>
<ActionButton <ActionButton
@ -583,7 +571,7 @@ const MessageMenubar: FC<Props> = (props) => {
mouseEnterDelay={1} mouseEnterDelay={1}
open={showDeleteTooltip} open={showDeleteTooltip}
onOpenChange={setShowDeleteTooltip}> onOpenChange={setShowDeleteTooltip}>
<Trash size={15} /> <DeleteIcon size={15} />
</Tooltip> </Tooltip>
</ActionButton> </ActionButton>
</Popconfirm> </Popconfirm>

View File

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

View File

@ -1,5 +1,5 @@
import { TranslationOutlined } from '@ant-design/icons' 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 type { TranslationMessageBlock } from '@renderer/types/newMessage'
import { Divider } from 'antd' import { Divider } from 'antd'
import { FC, Fragment } from 'react' import { FC, Fragment } from 'react'
@ -20,7 +20,7 @@ const MessageTranslate: FC<Props> = ({ block }) => {
<TranslationOutlined /> <TranslationOutlined />
</Divider> </Divider>
{!block.content || block.content === t('translate.processing') ? ( {!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} /> <Markdown block={block} />
)} )}

View File

@ -1,6 +1,6 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import ContextMenu from '@renderer/components/ContextMenu' 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 Scrollbar from '@renderer/components/Scrollbar'
import { LOAD_MORE_COUNT } from '@renderer/config/constant' import { LOAD_MORE_COUNT } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
@ -309,7 +309,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
))} ))}
{isLoadingMore && ( {isLoadingMore && (
<LoaderContainer> <LoaderContainer>
<SvgSpinners180Ring color="var(--color-text-2)" /> <LoadingIcon color="var(--color-text-2)" />
</LoaderContainer> </LoaderContainer>
)} )}
</ScrollContainer> </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 { DraggableList } from '@renderer/components/DraggableList'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { useAgents } from '@renderer/hooks/useAgents' import { useAgents } from '@renderer/hooks/useAgents'
@ -6,8 +6,9 @@ import { useAssistants } from '@renderer/hooks/useAssistant'
import { useAssistantsTabSortType } from '@renderer/hooks/useStore' import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
import { useTags } from '@renderer/hooks/useTags' import { useTags } from '@renderer/hooks/useTags'
import { Assistant, AssistantsSortType } from '@renderer/types' import { Assistant, AssistantsSortType } from '@renderer/types'
import { Tooltip } from 'antd' import { Tooltip, Typography } from 'antd'
import { FC, useCallback, useRef, useState } from 'react' import { Plus } from 'lucide-react'
import { FC, useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -69,6 +70,19 @@ const Assistants: FC<AssistantsTabProps> = ({
[assistants, t, updateAssistants] [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') { if (assistantsTabSortType === 'tags') {
return ( return (
<Container className="assistants-tab" ref={containerRef}> <Container className="assistants-tab" ref={containerRef}>
@ -117,12 +131,7 @@ const Assistants: FC<AssistantsTabProps> = ({
</TagsContainer> </TagsContainer>
))} ))}
</div> </div>
<AssistantAddItem onClick={onCreateAssistant}> {renderAddAssistantButton}
<AssistantName>
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
{t('chat.add.assistant.title')}
</AssistantName>
</AssistantAddItem>
</Container> </Container>
) )
} }
@ -149,14 +158,7 @@ const Assistants: FC<AssistantsTabProps> = ({
/> />
)} )}
</DraggableList> </DraggableList>
{!dragging && ( {!dragging && renderAddAssistantButton}
<AssistantAddItem onClick={onCreateAssistant}>
<AssistantName>
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
{t('chat.add.assistant.title')}
</AssistantName>
</AssistantAddItem>
)}
<div style={{ minHeight: 10 }}></div> <div style={{ minHeight: 10 }}></div>
</Container> </Container>
) )
@ -224,13 +226,13 @@ const GroupTitleDivider = styled.div`
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
` `
const AssistantName = styled.div` const AddItemWrapper = styled.div`
color: var(--color-text); color: var(--color-text-2);
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: 13px; font-size: 13px;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
` `
export default Assistants 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 { 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 ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
import PromptPopup from '@renderer/components/Popups/PromptPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { isMac } from '@renderer/config/constant' 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 { ItemType, MenuItemType } from 'antd/es/menu/interface'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { findIndex } from 'lodash' 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 { FC, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
@ -177,7 +178,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{ {
label: t('chat.topics.auto_rename'), label: t('chat.topics.auto_rename'),
key: 'auto-rename', key: 'auto-rename',
icon: <i className="iconfont icon-business-smart-assistant" style={{ fontSize: '14px' }} />, icon: <Sparkles size={14} />,
disabled: isRenaming(topic.id), disabled: isRenaming(topic.id),
async onClick() { async onClick() {
const messages = await TopicManager.getTopicMessages(topic.id) 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'), label: t('chat.topics.edit.title'),
key: 'rename', key: 'rename',
icon: <EditOutlined />, icon: <EditIcon size={14} />,
disabled: isRenaming(topic.id), disabled: isRenaming(topic.id),
async onClick() { async onClick() {
const name = await PromptPopup.show({ const name = await PromptPopup.show({
@ -217,10 +218,10 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{ {
label: t('chat.topics.prompt.label'), label: t('chat.topics.prompt.label'),
key: 'topic-prompt', key: 'topic-prompt',
icon: <i className="iconfont icon-ai-model1" style={{ fontSize: '14px' }} />, icon: <PackagePlus size={14} />,
extra: ( extra: (
<Tooltip title={t('chat.topics.prompt.tips')}> <Tooltip title={t('chat.topics.prompt.tips')}>
<QuestionIcon /> <HelpCircle size={14} />
</Tooltip> </Tooltip>
), ),
async onClick() { 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', key: 'pin',
icon: <PushpinOutlined />, icon: topic.pinned ? <PinOffIcon size={14} /> : <PinIcon size={14} />,
onClick() { onClick() {
onPinTopic(topic) onPinTopic(topic)
} }
@ -253,7 +254,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{ {
label: t('chat.topics.clear.title'), label: t('chat.topics.clear.title'),
key: 'clear-messages', key: 'clear-messages',
icon: <ClearOutlined />, icon: <BrushCleaning size={14} />,
async onClick() { async onClick() {
window.modal.confirm({ window.modal.confirm({
title: t('chat.input.clear.content'), title: t('chat.input.clear.content'),
@ -265,7 +266,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{ {
label: t('settings.topic.position.label'), label: t('settings.topic.position.label'),
key: 'topic-position', key: 'topic-position',
icon: <MenuOutlined />, icon: <MenuIcon size={14} />,
children: [ children: [
{ {
label: t('settings.topic.position.left'), label: t('settings.topic.position.left'),
@ -282,7 +283,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{ {
label: t('chat.topics.copy.title'), label: t('chat.topics.copy.title'),
key: 'copy', key: 'copy',
icon: <CopyIcon />, icon: <CopyIcon size={14} />,
children: [ children: [
{ {
label: t('chat.topics.copy.image'), label: t('chat.topics.copy.image'),
@ -304,7 +305,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
{ {
label: t('chat.topics.export.title'), label: t('chat.topics.export.title'),
key: 'export', key: 'export',
icon: <UploadOutlined />, icon: <UploadIcon size={14} />,
children: [ children: [
exportMenuOptions.image && { exportMenuOptions.image && {
label: t('chat.topics.export.image'), label: t('chat.topics.export.image'),
@ -375,7 +376,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
menus.push({ menus.push({
label: t('chat.topics.move_to'), label: t('chat.topics.move_to'),
key: 'move', key: 'move',
icon: <FolderOutlined />, icon: <FolderOpen size={14} />,
children: assistants children: assistants
.filter((a) => a.id !== assistant.id) .filter((a) => a.id !== assistant.id)
.map((a) => ({ .map((a) => ({
@ -392,7 +393,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
label: t('common.delete'), label: t('common.delete'),
danger: true, danger: true,
key: 'delete', key: 'delete',
icon: <DeleteOutlined />, icon: <DeleteIcon size={14} className="lucide-custom" />,
onClick: () => onDeleteTopic(topic) onClick: () => onDeleteTopic(topic)
}) })
} }
@ -446,7 +447,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
itemContainerStyle={{ paddingBottom: '8px' }} itemContainerStyle={{ paddingBottom: '8px' }}
header={ header={
<AddTopicButton onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)}> <AddTopicButton onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)}>
<PlusOutlined /> <PlusIcon size={16} />
{t('chat.add.topic.title')} {t('chat.add.topic.title')}
</AddTopicButton> </AddTopicButton>
}> }>
@ -498,16 +499,16 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
} }
}}> }}>
{deletingTopicId === topic.id ? ( {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> </MenuButton>
</Tooltip> </Tooltip>
)} )}
{topic.pinned && ( {topic.pinned && (
<MenuButton className="pin"> <MenuButton className="pin">
<PushpinOutlined /> <PinIcon size={14} color="var(--color-text-3)" />
</MenuButton> </MenuButton>
)} )}
</TopicNameContainer> </TopicNameContainer>
@ -704,10 +705,5 @@ const MenuButton = styled.div`
font-size: 12px; font-size: 12px;
} }
` `
const QuestionIcon = styled(QuestionCircleOutlined)`
font-size: 14px;
cursor: pointer;
color: var(--color-text-3);
`
export default Topics 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 ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import EmojiIcon from '@renderer/components/EmojiIcon' 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 PromptPopup from '@renderer/components/Popups/PromptPopup'
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
@ -24,7 +13,19 @@ import { getLeadingEmoji, uuid } from '@renderer/utils'
import { hasTopicPendingRequests } from '@renderer/utils/queue' import { hasTopicPendingRequests } from '@renderer/utils/queue'
import { Dropdown, MenuProps } from 'antd' import { Dropdown, MenuProps } from 'antd'
import { omit } from 'lodash' 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 { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -198,7 +199,7 @@ const createTagMenuItems = (
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
...allTags.map((tag) => ({ ...allTags.map((tag) => ({
label: 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}`, key: `all-tag-${tag}`,
onClick: () => handleTagOperation(tag, assistant, assistants, updateAssistants) onClick: () => handleTagOperation(tag, assistant, assistants, updateAssistants)
})) }))
@ -211,7 +212,7 @@ const createTagMenuItems = (
items.push({ items.push({
label: t('assistants.tags.add'), label: t('assistants.tags.add'),
key: 'new-tag', key: 'new-tag',
icon: <Plus size={16} />, icon: <Plus size={14} />,
onClick: async () => { onClick: async () => {
const tagName = await PromptPopup.show({ const tagName = await PromptPopup.show({
title: t('assistants.tags.add'), title: t('assistants.tags.add'),
@ -228,7 +229,7 @@ const createTagMenuItems = (
items.push({ items.push({
label: t('assistants.tags.manage'), label: t('assistants.tags.manage'),
key: 'manage-tags', key: 'manage-tags',
icon: <Settings2 size={16} />, icon: <Settings2 size={14} />,
onClick: () => { onClick: () => {
AssistantTagsPopup.show({ title: t('assistants.tags.manage') }) AssistantTagsPopup.show({ title: t('assistants.tags.manage') })
} }
@ -260,13 +261,13 @@ function getMenuItems({
{ {
label: t('assistants.edit.title'), label: t('assistants.edit.title'),
key: 'edit', key: 'edit',
icon: <EditOutlined />, icon: <EditIcon size={14} />,
onClick: () => AssistantSettingsPopup.show({ assistant }) onClick: () => AssistantSettingsPopup.show({ assistant })
}, },
{ {
label: t('assistants.copy.title'), label: t('assistants.copy.title'),
key: 'duplicate', key: 'duplicate',
icon: <CopyIcon />, icon: <CopyIcon size={14} />,
onClick: async () => { onClick: async () => {
const _assistant = copyAssistant(assistant) const _assistant = copyAssistant(assistant)
if (_assistant) { if (_assistant) {
@ -277,7 +278,7 @@ function getMenuItems({
{ {
label: t('assistants.clear.title'), label: t('assistants.clear.title'),
key: 'clear', key: 'clear',
icon: <MinusCircleOutlined />, icon: <BrushCleaning size={14} />,
onClick: () => { onClick: () => {
window.modal.confirm({ window.modal.confirm({
title: t('assistants.clear.title'), title: t('assistants.clear.title'),
@ -291,7 +292,7 @@ function getMenuItems({
{ {
label: t('assistants.save.title'), label: t('assistants.save.title'),
key: 'save-to-agent', key: 'save-to-agent',
icon: <SaveOutlined />, icon: <Save size={14} />,
onClick: async () => { onClick: async () => {
const agent = omit(assistant, ['model', 'emoji']) const agent = omit(assistant, ['model', 'emoji'])
agent.id = uuid() agent.id = uuid()
@ -306,7 +307,7 @@ function getMenuItems({
{ {
label: t('assistants.icon.type'), label: t('assistants.icon.type'),
key: 'icon-type', key: 'icon-type',
icon: <SmileOutlined />, icon: <Smile size={14} />,
children: [ children: [
{ {
label: t('settings.assistant.icon.type.model'), label: t('settings.assistant.icon.type.model'),
@ -331,7 +332,7 @@ function getMenuItems({
{ {
label: t('assistants.tags.manage'), label: t('assistants.tags.manage'),
key: 'all-tags', key: 'all-tags',
icon: <PlusOutlined />, icon: <Plus size={14} />,
children: createTagMenuItems(allTags, assistant, assistants, updateAssistants, t) children: createTagMenuItems(allTags, assistant, assistants, updateAssistants, t)
}, },
{ {
@ -345,13 +346,13 @@ function getMenuItems({
{ {
label: t('common.sort.pinyin.asc'), label: t('common.sort.pinyin.asc'),
key: 'sort-asc', key: 'sort-asc',
icon: <SortAscendingOutlined />, icon: <ArrowDownAZ size={14} />,
onClick: sortByPinyinAsc onClick: sortByPinyinAsc
}, },
{ {
label: t('common.sort.pinyin.desc'), label: t('common.sort.pinyin.desc'),
key: 'sort-desc', key: 'sort-desc',
icon: <SortDescendingOutlined />, icon: <ArrowUpAZ size={14} />,
onClick: sortByPinyinDesc onClick: sortByPinyinDesc
}, },
{ {
@ -360,7 +361,7 @@ function getMenuItems({
{ {
label: t('common.delete'), label: t('common.delete'),
key: 'delete', key: 'delete',
icon: <DeleteOutlined />, icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true, danger: true,
onClick: () => { onClick: () => {
window.modal.confirm({ window.modal.confirm({

View File

@ -1,11 +1,11 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { DeleteIcon } from '@renderer/components/Icons'
import { Box } from '@renderer/components/Layout' import { Box } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistants } from '@renderer/hooks/useAssistant'
import { useTags } from '@renderer/hooks/useTags' import { useTags } from '@renderer/hooks/useTags'
import { Button, Empty, Modal } from 'antd' import { Button, Empty, Modal } from 'antd'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { Trash } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -94,7 +94,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
<Box mr={8}>{tag}</Box> <Box mr={8}>{tag}</Box>
<Button <Button
type="text" type="text"
icon={<Trash size={16} />} icon={<DeleteIcon size={16} className="lucide-custom" />}
danger danger
onClick={(e) => { onClick={(e) => {
e.stopPropagation() 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 { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { DraggableList } from '@renderer/components/DraggableList' import { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import ListItem from '@renderer/components/ListItem' import ListItem from '@renderer/components/ListItem'
import PromptPopup from '@renderer/components/Popups/PromptPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup'
import Scrollbar from '@renderer/components/Scrollbar' 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 KnowledgeSearchPopup from '@renderer/pages/knowledge/components/KnowledgeSearchPopup'
import { KnowledgeBase } from '@renderer/types' import { KnowledgeBase } from '@renderer/types'
import { Dropdown, Empty, MenuProps } from 'antd' 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 { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -49,7 +49,7 @@ const KnowledgePage: FC = () => {
{ {
label: t('knowledge.rename'), label: t('knowledge.rename'),
key: 'rename', key: 'rename',
icon: <EditOutlined />, icon: <EditIcon size={14} />,
async onClick() { async onClick() {
const name = await PromptPopup.show({ const name = await PromptPopup.show({
title: t('knowledge.rename'), title: t('knowledge.rename'),
@ -62,9 +62,9 @@ const KnowledgePage: FC = () => {
} }
}, },
{ {
label: t('knowledge.settings.title'), label: t('common.settings'),
key: 'settings', key: 'settings',
icon: <SettingOutlined />, icon: <Settings size={14} />,
onClick: () => handleEditKnowledgeBase(base) onClick: () => handleEditKnowledgeBase(base)
}, },
{ type: 'divider' }, { type: 'divider' },
@ -72,7 +72,7 @@ const KnowledgePage: FC = () => {
label: t('common.delete'), label: t('common.delete'),
danger: true, danger: true,
key: 'delete', key: 'delete',
icon: <DeleteOutlined />, icon: <DeleteIcon size={14} className="lucide-custom" />,
onClick: () => { onClick: () => {
window.modal.confirm({ window.modal.confirm({
title: t('knowledge.delete_confirm'), title: t('knowledge.delete_confirm'),

View File

@ -1,6 +1,5 @@
import type { KnowledgeBase, Model } from '@renderer/types' import type { KnowledgeBase, Model } from '@renderer/types'
import { render, screen } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest'
import AdvancedSettingsPanel from '../components/KnowledgeSettings/AdvancedSettingsPanel' 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 * KnowledgeBase
* @param overrides * @param overrides
@ -78,23 +98,19 @@ describe('AdvancedSettingsPanel', () => {
}) })
describe('handlers', () => { describe('handlers', () => {
it('should call handlers when values are changed', async () => { it('should call handlers when values are changed', () => {
const user = userEvent.setup()
render(<AdvancedSettingsPanel newBase={mockBase} handlers={mocks.handlers} />) render(<AdvancedSettingsPanel newBase={mockBase} handlers={mocks.handlers} />)
const chunkSizeInput = screen.getByLabelText('分块大小') const chunkSizeInput = screen.getByLabelText('分块大小')
await user.clear(chunkSizeInput) fireEvent.change(chunkSizeInput, { target: { value: '600' } })
await user.type(chunkSizeInput, '600')
expect(mocks.handlers.handleChunkSizeChange).toHaveBeenCalledWith(600) expect(mocks.handlers.handleChunkSizeChange).toHaveBeenCalledWith(600)
const chunkOverlapInput = screen.getByLabelText('分块重叠') const chunkOverlapInput = screen.getByLabelText('分块重叠')
await user.clear(chunkOverlapInput) fireEvent.change(chunkOverlapInput, { target: { value: '300' } })
await user.type(chunkOverlapInput, '300')
expect(mocks.handlers.handleChunkOverlapChange).toHaveBeenCalledWith(300) expect(mocks.handlers.handleChunkOverlapChange).toHaveBeenCalledWith(300)
const thresholdInput = screen.getByLabelText('检索相似度阈值') const thresholdInput = screen.getByLabelText('检索相似度阈值')
await user.clear(thresholdInput) fireEvent.change(thresholdInput, { target: { value: '0.6' } })
await user.type(thresholdInput, '0.6')
expect(mocks.handlers.handleThresholdChange).toHaveBeenCalledWith(0.6) expect(mocks.handlers.handleThresholdChange).toHaveBeenCalledWith(0.6)
}) })
}) })

View File

@ -31,85 +31,15 @@ exports[`AdvancedSettingsPanel > basic rendering > should match snapshot 1`] = `
knowledge.chunk_size_tooltip knowledge.chunk_size_tooltip
</div> </div>
</div> </div>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined"
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 <input
aria-label="分块大小" aria-label="分块大小"
aria-valuemin="100" data-testid="input-number"
aria-valuenow="500"
autocomplete="off"
class="ant-input-number-input"
placeholder="knowledge.chunk_size_placeholder" placeholder="knowledge.chunk_size_placeholder"
role="spinbutton" style="width: 100%;"
step="1" type="number"
value="500" value="500"
/> />
</div> </div>
</div>
</div>
<div <div
class="c1" class="c1"
> >
@ -121,85 +51,15 @@ exports[`AdvancedSettingsPanel > basic rendering > should match snapshot 1`] = `
knowledge.chunk_overlap_tooltip knowledge.chunk_overlap_tooltip
</div> </div>
</div> </div>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined"
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 <input
aria-label="分块重叠" aria-label="分块重叠"
aria-valuemin="0" data-testid="input-number"
aria-valuenow="200"
autocomplete="off"
class="ant-input-number-input"
placeholder="knowledge.chunk_overlap_placeholder" placeholder="knowledge.chunk_overlap_placeholder"
role="spinbutton" style="width: 100%;"
step="1" type="number"
value="200" value="200"
/> />
</div> </div>
</div>
</div>
<div <div
class="c1" class="c1"
> >
@ -211,119 +71,19 @@ exports[`AdvancedSettingsPanel > basic rendering > should match snapshot 1`] = `
knowledge.threshold_tooltip knowledge.threshold_tooltip
</div> </div>
</div> </div>
<div
class="ant-input-number css-dev-only-do-not-override-1261szd ant-input-number-outlined"
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 <input
aria-label="检索相似度阈值" aria-label="检索相似度阈值"
aria-valuemax="1" data-testid="input-number"
aria-valuemin="0"
aria-valuenow="0.5"
autocomplete="off"
class="ant-input-number-input"
placeholder="knowledge.threshold_placeholder" placeholder="knowledge.threshold_placeholder"
role="spinbutton" style="width: 100%;"
step="0.1" type="number"
value="0.5" value="0.5"
/> />
</div> </div>
</div>
</div>
<div <div
class="ant-alert ant-alert-warning css-dev-only-do-not-override-1261szd"
data-show="true"
role="alert" 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>
</div> </div>
`; `;

View File

@ -1,7 +1,7 @@
import { WarningOutlined } from '@ant-design/icons'
import InfoTooltip from '@renderer/components/InfoTooltip' import InfoTooltip from '@renderer/components/InfoTooltip'
import { KnowledgeBase } from '@renderer/types' import { KnowledgeBase } from '@renderer/types'
import { Alert, InputNumber } from 'antd' import { Alert, InputNumber } from 'antd'
import { TriangleAlert } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsPanel } from './styles' import { SettingsItem, SettingsPanel } from './styles'
@ -68,7 +68,12 @@ const AdvancedSettingsPanel: React.FC<AdvancedSettingsPanelProps> = ({ newBase,
/> />
</SettingsItem> </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> </SettingsPanel>
) )
} }

View File

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

View File

@ -1,4 +1,3 @@
import { DeleteOutlined } from '@ant-design/icons'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import Ellipsis from '@renderer/components/Ellipsis' import Ellipsis from '@renderer/components/Ellipsis'
import { useKnowledge } from '@renderer/hooks/useKnowledge' 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 { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant'
import { Button, Tooltip, Upload } from 'antd' import { Button, Tooltip, Upload } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Plus } from 'lucide-react'
import { FC, useCallback, useEffect, useState } from 'react' import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
const logger = loggerService.withContext('KnowledgeFiles') const logger = loggerService.withContext('KnowledgeFiles')
import { DeleteIcon } from '@renderer/components/Icons'
import { DynamicVirtualList } from '@renderer/components/VirtualList' import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { PlusIcon } from 'lucide-react'
import { import {
ClickableSpan, ClickableSpan,
@ -139,7 +139,7 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
<ItemHeader> <ItemHeader>
<Button <Button
type="primary" type="primary"
icon={<Plus size={16} />} icon={<PlusIcon size={16} />}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handleAddFile() handleAddFile()
@ -210,7 +210,12 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
type="file" type="file"
/> />
</StatusIconWrapper> </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> </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 TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import { DynamicVirtualList } from '@renderer/components/VirtualList' import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useKnowledge } from '@renderer/hooks/useKnowledge' import { useKnowledge } from '@renderer/hooks/useKnowledge'
@ -7,7 +7,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { KnowledgeBase, KnowledgeItem } from '@renderer/types' import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { Button } from 'antd' import { Button } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Plus } from 'lucide-react' import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useMemo } from 'react' import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -64,7 +64,7 @@ const KnowledgeNotes: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemHeader> <ItemHeader>
<Button <Button
type="primary" type="primary"
icon={<Plus size={16} />} icon={<PlusIcon size={16} />}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handleAddNote() handleAddNote()
@ -91,7 +91,7 @@ const KnowledgeNotes: FC<KnowledgeContentProps> = ({ selectedBase }) => {
extra: getDisplayTime(note), extra: getDisplayTime(note),
actions: ( actions: (
<FlexAlignCenter> <FlexAlignCenter>
<Button type="text" onClick={() => handleEditNote(note)} icon={<EditOutlined />} /> <Button type="text" onClick={() => handleEditNote(note)} icon={<EditIcon size={14} />} />
<StatusIconWrapper> <StatusIconWrapper>
<StatusIcon <StatusIcon
sourceId={note.id} sourceId={note.id}
@ -100,7 +100,12 @@ const KnowledgeNotes: FC<KnowledgeContentProps> = ({ selectedBase }) => {
type="note" type="note"
/> />
</StatusIconWrapper> </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> </FlexAlignCenter>
) )
}} }}

View File

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

View File

@ -1,5 +1,5 @@
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
import Ellipsis from '@renderer/components/Ellipsis' import Ellipsis from '@renderer/components/Ellipsis'
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
import PromptPopup from '@renderer/components/Popups/PromptPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { DynamicVirtualList } from '@renderer/components/VirtualList' import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useKnowledge } from '@renderer/hooks/useKnowledge' import { useKnowledge } from '@renderer/hooks/useKnowledge'
@ -8,7 +8,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { KnowledgeBase, KnowledgeItem } from '@renderer/types' import { KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { Button, Dropdown, Tooltip } from 'antd' import { Button, Dropdown, Tooltip } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Plus } from 'lucide-react' import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useMemo } from 'react' import { FC, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -115,7 +115,7 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ItemHeader> <ItemHeader>
<Button <Button
type="primary" type="primary"
icon={<Plus size={16} />} icon={<PlusIcon size={16} />}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handleAddUrl() handleAddUrl()
@ -143,13 +143,13 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
items: [ items: [
{ {
key: 'edit', key: 'edit',
icon: <EditOutlined />, icon: <EditIcon size={14} />,
label: t('knowledge.edit_remark'), label: t('knowledge.edit_remark'),
onClick: () => handleEditRemark(item) onClick: () => handleEditRemark(item)
}, },
{ {
key: 'copy', key: 'copy',
icon: <CopyOutlined />, icon: <CopyIcon size={14} />,
label: t('common.copy'), label: t('common.copy'),
onClick: () => { onClick: () => {
navigator.clipboard.writeText(item.content as string) navigator.clipboard.writeText(item.content as string)
@ -178,7 +178,12 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<StatusIconWrapper> <StatusIconWrapper>
<StatusIcon sourceId={item.id} base={base} getProcessingStatus={getProcessingStatus} type="url" /> <StatusIcon sourceId={item.id} base={base} getProcessingStatus={getProcessingStatus} type="url" />
</StatusIconWrapper> </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> </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 = [ export const MODEOPTIONS = [
{ label: 'paintings.mode.generate', value: generationModeType.GENERATION }, { label: 'paintings.mode.generate', value: generationModeType.GENERATION },
{ label: '改图', value: generationModeType.EDIT }, { label: 'paintings.mode.edit', value: generationModeType.EDIT },
{ label: '合并图', value: generationModeType.MERGE } { 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 ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import EditableNumber from '@renderer/components/EditableNumber' import EditableNumber from '@renderer/components/EditableNumber'
import { DeleteIcon, ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import Selector from '@renderer/components/Selector' import Selector from '@renderer/components/Selector'
@ -10,6 +11,7 @@ import { Assistant, AssistantSettingCustomParameters, AssistantSettings } from '
import { modalConfirm } from '@renderer/utils' import { modalConfirm } from '@renderer/utils'
import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd' import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { isNull } from 'lodash' import { isNull } from 'lodash'
import { PlusIcon } from 'lucide-react'
import { FC, useCallback, useEffect, useRef, useState } from 'react' import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -213,7 +215,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Label>{t('assistants.settings.default_model')}</Label> <Label>{t('assistants.settings.default_model')}</Label>
<HStack alignItems="center" gap={5}> <HStack alignItems="center" gap={5}>
<ModelSelectButton <ModelSelectButton
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusOutlined />} icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusIcon size={18} />}
onClick={onSelectModel}> onClick={onSelectModel}>
<ModelName>{defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}</ModelName> <ModelName>{defaultModel ? defaultModel.name : t('agents.edit.model.select.title')}</ModelName>
</ModelSelectButton> </ModelSelectButton>
@ -221,7 +223,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Button <Button
color="danger" color="danger"
variant="filled" variant="filled"
icon={<DeleteOutlined />} icon={<DeleteIcon size={14} className="lucide-custom" />}
onClick={() => { onClick={() => {
setDefaultModel(undefined) setDefaultModel(undefined)
updateAssistant({ ...assistant, defaultModel: undefined }) updateAssistant({ ...assistant, defaultModel: undefined })
@ -449,7 +451,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Divider style={{ margin: '10px 0' }} /> <Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}> <SettingRow style={{ minHeight: 30 }}>
<Label>{t('models.custom_parameters')}</Label> <Label>{t('models.custom_parameters')}</Label>
<Button icon={<PlusOutlined />} onClick={onAddCustomParameter}> <Button icon={<PlusIcon size={18} />} onClick={onAddCustomParameter}>
{t('models.add_parameter')} {t('models.add_parameter')}
</Button> </Button>
</SettingRow> </SettingRow>
@ -478,7 +480,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Button <Button
color="danger" color="danger"
variant="filled" variant="filled"
icon={<DeleteOutlined />} icon={<DeleteIcon size={14} className="lucide-custom" />}
onClick={() => onDeleteCustomParameter(index)} onClick={() => onDeleteCustomParameter(index)}
/> />
</Col> </Col>
@ -486,7 +488,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
))} ))}
<Divider style={{ margin: '15px 0' }} /> <Divider style={{ margin: '15px 0' }} />
<HStack justifyContent="flex-end"> <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')} {t('chat.settings.reset')}
</Button> </Button>
</HStack> </HStack>

View File

@ -165,10 +165,6 @@ const Container = styled.div`
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
.ant-btn {
line-height: 0;
}
` `
const EmojiButtonWrapper = styled.div` 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 { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import FileItem from '@renderer/pages/files/FileItem' import FileItem from '@renderer/pages/files/FileItem'
import { Assistant, QuickPhrase } from '@renderer/types' import { Assistant, QuickPhrase } from '@renderer/types'
import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd' import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd'
import { PlusIcon } from 'lucide-react'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -82,7 +84,7 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
<Container> <Container>
<SettingTitle> <SettingTitle>
{t('assistants.settings.regular_phrases.title', 'Regular Prompts')} {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> </SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
@ -102,7 +104,7 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
extra: prompt.content, extra: prompt.content,
actions: ( actions: (
<Flex gap={4} style={{ opacity: 0.6 }}> <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 <Popconfirm
title={t('assistants.settings.regular_phrases.delete', 'Delete Prompt')} title={t('assistants.settings.regular_phrases.delete', 'Delete Prompt')}
description={t( description={t(
@ -113,7 +115,12 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
onConfirm={() => handleDelete(prompt.id)} onConfirm={() => handleDelete(prompt.id)}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}> 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> </Popconfirm>
</Flex> </Flex>
) )

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import { EditOutlined } from '@ant-design/icons'
import { nanoid } from '@reduxjs/toolkit' import { nanoid } from '@reduxjs/toolkit'
import { DraggableList } from '@renderer/components/DraggableList' import { DraggableList } from '@renderer/components/DraggableList'
import { EditIcon, RefreshIcon } from '@renderer/components/Icons'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { getMcpTypeLabel } from '@renderer/i18n/label' import { getMcpTypeLabel } from '@renderer/i18n/label'
import { MCPServer } from '@renderer/types' import { MCPServer } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error' import { formatMcpError } from '@renderer/utils/error'
import { Badge, Button, Dropdown, Empty, Switch, Tag } from 'antd' 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 { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router'
@ -139,7 +139,7 @@ const McpServersList: FC = () => {
<ListHeader> <ListHeader>
<SettingTitle style={{ gap: 3 }}> <SettingTitle style={{ gap: 3 }}>
<span>{t('settings.mcp.newServer')}</span> <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> </SettingTitle>
<ButtonGroup> <ButtonGroup>
<InstallNpxUv mini /> <InstallNpxUv mini />
@ -176,7 +176,7 @@ const McpServersList: FC = () => {
{t('settings.mcp.addServer.label')} {t('settings.mcp.addServer.label')}
</Button> </Button>
</Dropdown> </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')} {t('settings.mcp.sync.title', 'Sync Servers')}
</Button> </Button>
</ButtonGroup> </ButtonGroup>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { DraggableVirtualList } from '@renderer/components/DraggableList' import { DraggableVirtualList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { getProviderLogo, isSystemProvider } from '@renderer/config/providers' import { getProviderLogo, isSystemProvider } from '@renderer/config/providers'
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider' import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label' import { getProviderLabel } from '@renderer/i18n/label'
@ -15,7 +15,7 @@ import {
uuid uuid
} from '@renderer/utils' } from '@renderer/utils'
import { Avatar, Button, Card, Dropdown, Input, MenuProps, Tag } from 'antd' 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 { FC, startTransition, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSearchParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
@ -324,7 +324,7 @@ const ProvidersList: FC = () => {
const editMenu = { const editMenu = {
label: t('common.edit'), label: t('common.edit'),
key: 'edit', key: 'edit',
icon: <EditOutlined />, icon: <EditIcon size={14} />,
async onClick() { async onClick() {
const { name, type, logoFile, logo } = await AddProviderPopup.show(provider) const { name, type, logoFile, logo } = await AddProviderPopup.show(provider)
@ -362,7 +362,7 @@ const ProvidersList: FC = () => {
const deleteMenu = { const deleteMenu = {
label: t('common.delete'), label: t('common.delete'),
key: 'delete', key: 'delete',
icon: <DeleteOutlined />, icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true, danger: true,
async onClick() { async onClick() {
window.modal.confirm({ window.modal.confirm({
@ -492,7 +492,7 @@ const ProvidersList: FC = () => {
<AddButtonWrapper> <AddButtonWrapper>
<Button <Button
style={{ width: '100%', borderRadius: 'var(--list-item-border-radius)' }} style={{ width: '100%', borderRadius: 'var(--list-item-border-radius)' }}
icon={<PlusOutlined />} icon={<PlusIcon size={16} />}
onClick={onAddProvider} onClick={onAddProvider}
disabled={dragging}> disabled={dragging}>
{t('button.add')} {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 { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import FileItem from '@renderer/pages/files/FileItem' import FileItem from '@renderer/pages/files/FileItem'
import QuickPhraseService from '@renderer/services/QuickPhraseService' import QuickPhraseService from '@renderer/services/QuickPhraseService'
import { QuickPhrase } from '@renderer/types' import { QuickPhrase } from '@renderer/types'
import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd' import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd'
import { PlusIcon } from 'lucide-react'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -74,7 +76,7 @@ const QuickPhraseSettings: FC = () => {
<SettingGroup style={{ marginBottom: 0 }} theme={theme}> <SettingGroup style={{ marginBottom: 0 }} theme={theme}>
<SettingTitle> <SettingTitle>
{t('settings.quickPhrase.title')} {t('settings.quickPhrase.title')}
<Button type="text" icon={<PlusOutlined />} onClick={handleAdd} /> <Button type="text" icon={<PlusIcon size={18} />} onClick={handleAdd} />
</SettingTitle> </SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
@ -94,7 +96,12 @@ const QuickPhraseSettings: FC = () => {
extra: phrase.content, extra: phrase.content,
actions: ( actions: (
<Flex gap={4} style={{ opacity: 0.6 }}> <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 <Popconfirm
title={t('settings.quickPhrase.delete')} title={t('settings.quickPhrase.delete')}
description={t('settings.quickPhrase.deleteConfirm')} description={t('settings.quickPhrase.deleteConfirm')}
@ -102,7 +109,12 @@ const QuickPhraseSettings: FC = () => {
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
onConfirm={() => handleDelete(phrase.id)} onConfirm={() => handleDelete(phrase.id)}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}> 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> </Popconfirm>
</Flex> </Flex>
) )

View File

@ -1,5 +1,6 @@
import { LoadingOutlined } from '@ant-design/icons' 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 { FC, useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -165,7 +166,7 @@ const WindowFooter: FC<FooterProps> = ({
</OpButton> </OpButton>
{onRegenerate && ( {onRegenerate && (
<OpButton onClick={handleRegenerate} $isWindowFocus={isWindowFocus} data-hovered={isRegenerateHovered}> <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')} {t('selection.action.window.r_regenerate')}
</OpButton> </OpButton>
)} )}

184
yarn.lock
View File

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