feat: Optimize QuickPanel (#4604)
* feat(QuickPanel): enhance close action options and improve input handling - Added 'enter_empty' as a new close action option for QuickPanel. - Refactored input handling to include a delay before clearing search text after panel closure. - Updated keyboard event handling to prevent default actions for specific keys. - Improved styling for selected and focused states in QuickPanel components. - Enhanced AttachmentPreview to utilize a separate FileNameRender component for better readability and functionality. * feat(AttachmentPreview): enhance file icon rendering and styling * feat(CustomTag): add closable functionality and improve styling - Enhanced CustomTag component to support closable tags with an onClose callback. - Updated styling for better visual integration and added hover effects for the close icon. - Refactored usage of CustomTag in AttachmentPreview, KnowledgeBaseInput, and MentionModelsInput components for consistency. * feat(SelectModelPopup, QuickPanel): update tag component and enhance search functionality * feat(Inputbar, SettingsTab): add enable quick panel triggers setting and update translations * feat(QuickPanel): integrate color library for dynamic styling and update package dependencies
This commit is contained in:
parent
f7f7d2bde8
commit
a0be911dc9
@ -72,6 +72,7 @@
|
|||||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||||
"@xyflow/react": "^12.4.4",
|
"@xyflow/react": "^12.4.4",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
"color": "^5.0.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"docx": "^9.0.2",
|
"docx": "^9.0.2",
|
||||||
"electron-log": "^5.1.5",
|
"electron-log": "^5.1.5",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { CloseOutlined } from '@ant-design/icons'
|
||||||
import { Tooltip } from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -8,13 +9,16 @@ interface CustomTagProps {
|
|||||||
color: string
|
color: string
|
||||||
size?: number
|
size?: number
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
|
closable?: boolean
|
||||||
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomTag: FC<CustomTagProps> = ({ children, icon, color, size = 12, tooltip }) => {
|
const CustomTag: FC<CustomTagProps> = ({ children, icon, color, size = 12, tooltip, closable = false, onClose }) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip} placement="top">
|
<Tooltip title={tooltip} placement="top">
|
||||||
<Tag $color={color} $size={size}>
|
<Tag $color={color} $size={size} $closable={closable}>
|
||||||
{icon && icon} {children}
|
{icon && icon} {children}
|
||||||
|
{closable && <CloseIcon $size={size} $color={color} onClick={onClose} />}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
@ -22,19 +26,42 @@ const CustomTag: FC<CustomTagProps> = ({ children, icon, color, size = 12, toolt
|
|||||||
|
|
||||||
export default CustomTag
|
export default CustomTag
|
||||||
|
|
||||||
const Tag = styled.div<{ $color: string; $size: number }>`
|
const Tag = styled.div<{ $color: string; $size: number; $closable: boolean }>`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: ${({ $size }) => $size / 3}px ${({ $size }) => $size * 0.8}px;
|
padding: ${({ $size }) => $size / 3}px ${({ $size }) => $size * 0.8}px;
|
||||||
|
padding-right: ${({ $closable, $size }) => ($closable ? $size * 1.8 : $size * 0.8)}px;
|
||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
color: ${({ $color }) => $color};
|
color: ${({ $color }) => $color};
|
||||||
background-color: ${({ $color }) => $color + '20'};
|
background-color: ${({ $color }) => $color + '20'};
|
||||||
font-size: ${({ $size }) => $size}px;
|
font-size: ${({ $size }) => $size}px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-size: ${({ $size }) => $size}px;
|
font-size: ${({ $size }) => $size}px;
|
||||||
color: ${({ $color }) => $color};
|
color: ${({ $color }) => $color};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const CloseIcon = styled(CloseOutlined)<{ $size: number; $color: string }>`
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: ${({ $size }) => $size * 0.8}px;
|
||||||
|
color: ${({ $color }) => $color};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: ${({ $size }) => $size * 0.2}px;
|
||||||
|
top: ${({ $size }) => $size * 0.2}px;
|
||||||
|
bottom: ${({ $size }) => $size * 0.2}px;
|
||||||
|
border-radius: 99px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
line-height: 1;
|
||||||
|
&:hover {
|
||||||
|
background-color: #da8a8a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -23,6 +23,7 @@ interface ModelTagsProps {
|
|||||||
showToolsCalling?: boolean
|
showToolsCalling?: boolean
|
||||||
size?: number
|
size?: number
|
||||||
showLabel?: boolean
|
showLabel?: boolean
|
||||||
|
style?: React.CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModelTagsWithLabel: FC<ModelTagsProps> = ({
|
const ModelTagsWithLabel: FC<ModelTagsProps> = ({
|
||||||
@ -31,7 +32,8 @@ const ModelTagsWithLabel: FC<ModelTagsProps> = ({
|
|||||||
showReasoning = true,
|
showReasoning = true,
|
||||||
showToolsCalling = true,
|
showToolsCalling = true,
|
||||||
size = 12,
|
size = 12,
|
||||||
showLabel = true
|
showLabel = true,
|
||||||
|
style
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [_showLabel, _setShowLabel] = useState(showLabel)
|
const [_showLabel, _setShowLabel] = useState(showLabel)
|
||||||
@ -64,7 +66,7 @@ const ModelTagsWithLabel: FC<ModelTagsProps> = ({
|
|||||||
}, [showLabel])
|
}, [showLabel])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container ref={containerRef}>
|
<Container ref={containerRef} style={style}>
|
||||||
{isVisionModel(model) && (
|
{isVisionModel(model) && (
|
||||||
<CustomTag
|
<CustomTag
|
||||||
size={size}
|
size={size}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { HStack } from '../Layout'
|
import { HStack } from '../Layout'
|
||||||
import ModelTags from '../ModelTags'
|
import ModelTagsWithLabel from '../ModelTagsWithLabel'
|
||||||
import Scrollbar from '../Scrollbar'
|
import Scrollbar from '../Scrollbar'
|
||||||
|
|
||||||
type MenuItem = Required<MenuProps>['items'][number]
|
type MenuItem = Required<MenuProps>['items'][number]
|
||||||
@ -130,7 +130,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
label: (
|
label: (
|
||||||
<ModelItem>
|
<ModelItem>
|
||||||
<ModelNameRow>
|
<ModelNameRow>
|
||||||
<span>{m?.name}</span> <ModelTags model={m} />
|
<span>{m?.name}</span> <ModelTagsWithLabel model={m} size={11} showLabel={false} />
|
||||||
</ModelNameRow>
|
</ModelNameRow>
|
||||||
<PinIcon
|
<PinIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -184,7 +184,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
|
|||||||
<span>
|
<span>
|
||||||
{m.model?.name} | {m.provider.isSystem ? t(`provider.${m.provider.id}`) : m.provider.name}
|
{m.model?.name} | {m.provider.isSystem ? t(`provider.${m.provider.id}`) : m.provider.name}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
<ModelTags model={m.model} />
|
<ModelTagsWithLabel model={m.model} size={11} showLabel={false} />
|
||||||
</ModelNameRow>
|
</ModelNameRow>
|
||||||
<PinIcon
|
<PinIcon
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -481,6 +481,10 @@ const StyledMenu = styled(Menu)`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export type QuickPanelCloseAction = 'enter' | 'click' | 'esc' | 'outsideclick' | string | undefined
|
export type QuickPanelCloseAction = 'enter' | 'click' | 'esc' | 'outsideclick' | 'enter_empty' | string | undefined
|
||||||
export type QuickPanelCallBackOptions = {
|
export type QuickPanelCallBackOptions = {
|
||||||
symbol: string
|
symbol: string
|
||||||
action: QuickPanelCloseAction
|
action: QuickPanelCloseAction
|
||||||
|
|||||||
@ -2,9 +2,12 @@ import { CheckOutlined, RightOutlined } from '@ant-design/icons'
|
|||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { classNames } from '@renderer/utils'
|
import { classNames } from '@renderer/utils'
|
||||||
import { Flex } from 'antd'
|
import { Flex } from 'antd'
|
||||||
|
import { theme } from 'antd'
|
||||||
|
import Color from 'color'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import React, { use, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { use, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
import * as tinyPinyin from 'tiny-pinyin'
|
||||||
|
|
||||||
import { QuickPanelContext } from './provider'
|
import { QuickPanelContext } from './provider'
|
||||||
import { QuickPanelCallBackOptions, QuickPanelCloseAction, QuickPanelListItem, QuickPanelOpenOptions } from './types'
|
import { QuickPanelCallBackOptions, QuickPanelCloseAction, QuickPanelListItem, QuickPanelOpenOptions } from './types'
|
||||||
@ -27,13 +30,19 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
throw new Error('QuickPanel must be used within a QuickPanelProvider')
|
throw new Error('QuickPanel must be used within a QuickPanelProvider')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { token } = theme.useToken()
|
||||||
|
const colorPrimary = Color(token.colorPrimary || '#008000')
|
||||||
|
const selectedColor = colorPrimary.alpha(0.15).toString()
|
||||||
|
const selectedColorHover = colorPrimary.alpha(0.2).toString()
|
||||||
|
|
||||||
const ASSISTIVE_KEY = isMac ? '⌘' : 'Ctrl'
|
const ASSISTIVE_KEY = isMac ? '⌘' : 'Ctrl'
|
||||||
const [isAssistiveKeyPressed, setIsAssistiveKeyPressed] = useState(false)
|
const [isAssistiveKeyPressed, setIsAssistiveKeyPressed] = useState(false)
|
||||||
|
|
||||||
// 避免上下翻页时,鼠标干扰
|
// 避免上下翻页时,鼠标干扰
|
||||||
const [isMouseOver, setIsMouseOver] = useState(false)
|
const [isMouseOver, setIsMouseOver] = useState(false)
|
||||||
|
|
||||||
const [index, setIndex] = useState(ctx.defaultIndex)
|
const [_index, setIndex] = useState(ctx.defaultIndex)
|
||||||
|
const index = useDeferredValue(_index)
|
||||||
const [historyPanel, setHistoryPanel] = useState<QuickPanelOpenOptions[]>([])
|
const [historyPanel, setHistoryPanel] = useState<QuickPanelOpenOptions[]>([])
|
||||||
|
|
||||||
const bodyRef = useRef<HTMLDivElement>(null)
|
const bodyRef = useRef<HTMLDivElement>(null)
|
||||||
@ -65,7 +74,21 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
filterText += item.description
|
filterText += item.description
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterText.toLowerCase().includes(_searchText.toLowerCase())
|
const lowerFilterText = filterText.toLowerCase()
|
||||||
|
const lowerSearchText = _searchText.toLowerCase()
|
||||||
|
|
||||||
|
if (lowerFilterText.includes(lowerSearchText)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tinyPinyin.isSupported() && /[\u4e00-\u9fa5]/.test(filterText)) {
|
||||||
|
const pinyinText = tinyPinyin.convertToPinyin(filterText, '', true)
|
||||||
|
if (pinyinText.toLowerCase().includes(lowerSearchText)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
setIndex(newList.length > 0 ? ctx.defaultIndex || 0 : -1)
|
setIndex(newList.length > 0 ? ctx.defaultIndex || 0 : -1)
|
||||||
@ -120,7 +143,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
if (textArea) {
|
if (textArea) {
|
||||||
setInputText(textArea.value)
|
setInputText(textArea.value)
|
||||||
}
|
}
|
||||||
} else if (action && !['outsideclick', 'esc'].includes(action)) {
|
} else if (action && !['outsideclick', 'esc', 'enter_empty'].includes(action)) {
|
||||||
clearSearchText(true)
|
clearSearchText(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -175,6 +198,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
}, [searchText])
|
}, [searchText])
|
||||||
|
|
||||||
// 获取当前输入的搜索词
|
// 获取当前输入的搜索词
|
||||||
|
const isComposing = useRef(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ctx.isVisible) return
|
if (!ctx.isVisible) return
|
||||||
|
|
||||||
@ -196,11 +220,25 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCompositionUpdate = () => {
|
||||||
|
isComposing.current = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCompositionEnd = () => {
|
||||||
|
isComposing.current = false
|
||||||
|
}
|
||||||
|
|
||||||
textArea.addEventListener('input', handleInput)
|
textArea.addEventListener('input', handleInput)
|
||||||
|
textArea.addEventListener('compositionupdate', handleCompositionUpdate)
|
||||||
|
textArea.addEventListener('compositionend', handleCompositionEnd)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
textArea.removeEventListener('input', handleInput)
|
textArea.removeEventListener('input', handleInput)
|
||||||
setSearchText('')
|
textArea.removeEventListener('compositionupdate', handleCompositionUpdate)
|
||||||
|
textArea.removeEventListener('compositionend', handleCompositionEnd)
|
||||||
|
setTimeout(() => {
|
||||||
|
setSearchText('')
|
||||||
|
}, 200) // 等待面板关闭动画结束后,再清空搜索词
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [ctx.isVisible])
|
}, [ctx.isVisible])
|
||||||
@ -236,7 +274,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Enter', 'Escape'].includes(e.key)) {
|
if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Escape'].includes(e.key)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setIsMouseOver(false)
|
setIsMouseOver(false)
|
||||||
@ -312,8 +350,16 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
|
if (isComposing.current) return
|
||||||
|
|
||||||
if (list?.[index]) {
|
if (list?.[index]) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
setIsMouseOver(false)
|
||||||
|
|
||||||
handleItemAction(list[index], 'enter')
|
handleItemAction(list[index], 'enter')
|
||||||
|
} else {
|
||||||
|
handleClose('enter_empty')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
@ -366,7 +412,11 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
}, [ctx.isVisible])
|
}, [ctx.isVisible])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QuickPanelContainer $pageSize={ctx.pageSize} className={ctx.isVisible ? 'visible' : ''}>
|
<QuickPanelContainer
|
||||||
|
$pageSize={ctx.pageSize}
|
||||||
|
$selectedColor={selectedColor}
|
||||||
|
$selectedColorHover={selectedColorHover}
|
||||||
|
className={ctx.isVisible ? 'visible' : ''}>
|
||||||
<QuickPanelBody ref={bodyRef} onMouseMove={() => setIsMouseOver(true)}>
|
<QuickPanelBody ref={bodyRef} onMouseMove={() => setIsMouseOver(true)}>
|
||||||
<QuickPanelContent ref={contentRef} $pageSize={ctx.pageSize} $isMouseOver={isMouseOver}>
|
<QuickPanelContent ref={contentRef} $pageSize={ctx.pageSize} $isMouseOver={isMouseOver}>
|
||||||
{list.map((item, i) => (
|
{list.map((item, i) => (
|
||||||
@ -450,9 +500,14 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuickPanelContainer = styled.div<{ $pageSize: number }>`
|
const QuickPanelContainer = styled.div<{
|
||||||
|
$pageSize: number
|
||||||
|
$selectedColor: string
|
||||||
|
$selectedColorHover: string
|
||||||
|
}>`
|
||||||
--focused-color: rgba(0, 0, 0, 0.06);
|
--focused-color: rgba(0, 0, 0, 0.06);
|
||||||
--selected-color: rgba(0, 0, 0, 0.03);
|
--selected-color: ${(props) => props.$selectedColor};
|
||||||
|
--selected-color-dark: ${(props) => props.$selectedColorHover};
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
@ -465,26 +520,35 @@ const QuickPanelContainer = styled.div<{ $pageSize: number }>`
|
|||||||
transition: max-height 0.2s ease;
|
transition: max-height 0.2s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&.visible {
|
&.visible {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
max-height: ${(props) => props.$pageSize * 31 + 100}px;
|
max-height: ${(props) => props.$pageSize * 31 + 100}px;
|
||||||
}
|
}
|
||||||
body[theme-mode='dark'] & {
|
body[theme-mode='dark'] & {
|
||||||
--focused-color: rgba(255, 255, 255, 0.1);
|
--focused-color: rgba(255, 255, 255, 0.1);
|
||||||
--selected-color: rgba(255, 255, 255, 0.03);
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const QuickPanelBody = styled.div`
|
const QuickPanelBody = styled.div`
|
||||||
background-color: rgba(240, 240, 240, 0.5);
|
|
||||||
backdrop-filter: blur(35px) saturate(150%);
|
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
border-width: 0.5px 0.5px 0 0.5px;
|
border-width: 0.5px 0.5px 0 0.5px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(--color-border);
|
border-color: var(--color-border);
|
||||||
body[theme-mode='dark'] & {
|
position: relative;
|
||||||
background-color: rgba(40, 40, 40, 0.4);
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(240, 240, 240, 0.5);
|
||||||
|
backdrop-filter: blur(35px) saturate(150%);
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
body[theme-mode='dark'] & {
|
||||||
|
background-color: rgba(40, 40, 40, 0.4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -541,6 +605,9 @@ const QuickPanelItem = styled.div`
|
|||||||
margin-bottom: 1px;
|
margin-bottom: 1px;
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: var(--selected-color);
|
background-color: var(--selected-color);
|
||||||
|
&.focused {
|
||||||
|
background-color: var(--selected-color-dark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.focused {
|
&.focused {
|
||||||
background-color: var(--focused-color);
|
background-color: var(--focused-color);
|
||||||
|
|||||||
@ -1113,6 +1113,7 @@
|
|||||||
"messages.input.send_shortcuts": "Send shortcuts",
|
"messages.input.send_shortcuts": "Send shortcuts",
|
||||||
"messages.input.show_estimated_tokens": "Show estimated tokens",
|
"messages.input.show_estimated_tokens": "Show estimated tokens",
|
||||||
"messages.input.title": "Input Settings",
|
"messages.input.title": "Input Settings",
|
||||||
|
"messages.input.enable_quick_triggers": "Enable '/' and '@' triggers",
|
||||||
"messages.markdown_rendering_input_message": "Markdown render input message",
|
"messages.markdown_rendering_input_message": "Markdown render input message",
|
||||||
"messages.math_engine": "Math engine",
|
"messages.math_engine": "Math engine",
|
||||||
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
|
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",
|
||||||
|
|||||||
@ -1112,6 +1112,7 @@
|
|||||||
"messages.input.send_shortcuts": "送信ショートカット",
|
"messages.input.send_shortcuts": "送信ショートカット",
|
||||||
"messages.input.show_estimated_tokens": "推定トークン数を表示",
|
"messages.input.show_estimated_tokens": "推定トークン数を表示",
|
||||||
"messages.input.title": "入力設定",
|
"messages.input.title": "入力設定",
|
||||||
|
"messages.input.enable_quick_triggers": "'/' と '@' を有効にしてクイックメニューを表示します。",
|
||||||
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
|
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
|
||||||
"messages.math_engine": "数式エンジン",
|
"messages.math_engine": "数式エンジン",
|
||||||
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
|
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",
|
||||||
|
|||||||
@ -1112,6 +1112,7 @@
|
|||||||
"messages.input.send_shortcuts": "Горячие клавиши для отправки",
|
"messages.input.send_shortcuts": "Горячие клавиши для отправки",
|
||||||
"messages.input.show_estimated_tokens": "Показывать затраты токенов",
|
"messages.input.show_estimated_tokens": "Показывать затраты токенов",
|
||||||
"messages.input.title": "Настройки ввода",
|
"messages.input.title": "Настройки ввода",
|
||||||
|
"messages.input.enable_quick_triggers": "Включите '/' и '@', чтобы вызвать быстрое меню.",
|
||||||
"messages.markdown_rendering_input_message": "Отображение ввода в формате Markdown",
|
"messages.markdown_rendering_input_message": "Отображение ввода в формате Markdown",
|
||||||
"messages.math_engine": "Математический движок",
|
"messages.math_engine": "Математический движок",
|
||||||
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
|
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",
|
||||||
|
|||||||
@ -1113,6 +1113,7 @@
|
|||||||
"messages.input.send_shortcuts": "发送快捷键",
|
"messages.input.send_shortcuts": "发送快捷键",
|
||||||
"messages.input.show_estimated_tokens": "显示预估 Token 数",
|
"messages.input.show_estimated_tokens": "显示预估 Token 数",
|
||||||
"messages.input.title": "输入设置",
|
"messages.input.title": "输入设置",
|
||||||
|
"messages.input.enable_quick_triggers": "启用 '/' 和 '@' 触发快捷菜单",
|
||||||
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
|
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
|
||||||
"messages.math_engine": "数学公式引擎",
|
"messages.math_engine": "数学公式引擎",
|
||||||
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||||
|
|||||||
@ -1112,6 +1112,7 @@
|
|||||||
"messages.input.send_shortcuts": "傳送快捷鍵",
|
"messages.input.send_shortcuts": "傳送快捷鍵",
|
||||||
"messages.input.show_estimated_tokens": "顯示預估 Token 數",
|
"messages.input.show_estimated_tokens": "顯示預估 Token 數",
|
||||||
"messages.input.title": "輸入設定",
|
"messages.input.title": "輸入設定",
|
||||||
|
"messages.input.enable_quick_triggers": "啟用 '/' 和 '@' 觸發快捷選單",
|
||||||
"messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息",
|
"messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息",
|
||||||
"messages.math_engine": "Markdown 渲染輸入訊息",
|
"messages.math_engine": "Markdown 渲染輸入訊息",
|
||||||
"messages.metrics": "首字延遲 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
"messages.metrics": "首字延遲 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
|
||||||
|
|||||||
@ -1,7 +1,22 @@
|
|||||||
import { FileOutlined } from '@ant-design/icons'
|
import {
|
||||||
|
FileExcelFilled,
|
||||||
|
FileImageFilled,
|
||||||
|
FileMarkdownFilled,
|
||||||
|
FilePdfFilled,
|
||||||
|
FilePptFilled,
|
||||||
|
FileTextFilled,
|
||||||
|
FileUnknownFilled,
|
||||||
|
FileWordFilled,
|
||||||
|
FileZipFilled,
|
||||||
|
FolderOpenFilled,
|
||||||
|
GlobalOutlined,
|
||||||
|
LinkOutlined
|
||||||
|
} from '@ant-design/icons'
|
||||||
|
import CustomTag from '@renderer/components/CustomTag'
|
||||||
import FileManager from '@renderer/services/FileManager'
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { FileType } from '@renderer/types'
|
import { FileType } from '@renderer/types'
|
||||||
import { ConfigProvider, Image, Tag } from 'antd'
|
import { formatFileSize } from '@renderer/utils'
|
||||||
|
import { Flex, Image, Tooltip } from 'antd'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -11,74 +26,128 @@ interface Props {
|
|||||||
setFiles: (files: FileType[]) => void
|
setFiles: (files: FileType[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttachmentPreview: FC<Props> = ({ files, setFiles }) => {
|
const FileNameRender: FC<{ file: FileType }> = ({ file }) => {
|
||||||
const [visibleId, setVisibleId] = useState('')
|
const [visible, setVisible] = useState<boolean>(false)
|
||||||
|
|
||||||
const isImage = (ext: string) => {
|
const isImage = (ext: string) => {
|
||||||
return ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'].includes(ext)
|
return ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'].includes(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
padding: 5
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
fresh
|
||||||
|
title={
|
||||||
|
<Flex vertical gap={2} align="center">
|
||||||
|
{isImage(file.ext) && (
|
||||||
|
<Image
|
||||||
|
style={{ width: 80, maxHeight: 200 }}
|
||||||
|
src={'file://' + FileManager.getSafePath(file)}
|
||||||
|
preview={{
|
||||||
|
visible: visible,
|
||||||
|
src: 'file://' + FileManager.getSafePath(file),
|
||||||
|
onVisibleChange: setVisible
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{formatFileSize(file.size)}
|
||||||
|
</Flex>
|
||||||
|
}>
|
||||||
|
<FileName
|
||||||
|
onClick={() => {
|
||||||
|
if (isImage(file.ext)) {
|
||||||
|
setVisible(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const path = FileManager.getSafePath(file)
|
||||||
|
if (path) {
|
||||||
|
window.api.file.openPath(path)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{FileManager.formatFileName(file)}
|
||||||
|
</FileName>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AttachmentPreview: FC<Props> = ({ files, setFiles }) => {
|
||||||
|
const getFileIcon = (type?: string) => {
|
||||||
|
if (!type) return <FileUnknownFilled />
|
||||||
|
|
||||||
|
const ext = type.toLowerCase()
|
||||||
|
|
||||||
|
if (['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'].includes(ext)) {
|
||||||
|
return <FileImageFilled />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.doc', '.docx'].includes(ext)) {
|
||||||
|
return <FileWordFilled />
|
||||||
|
}
|
||||||
|
if (['.xls', '.xlsx'].includes(ext)) {
|
||||||
|
return <FileExcelFilled />
|
||||||
|
}
|
||||||
|
if (['.ppt', '.pptx'].includes(ext)) {
|
||||||
|
return <FilePptFilled />
|
||||||
|
}
|
||||||
|
if (ext === '.pdf') {
|
||||||
|
return <FilePdfFilled />
|
||||||
|
}
|
||||||
|
if (['.md', '.markdown'].includes(ext)) {
|
||||||
|
return <FileMarkdownFilled />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.zip', '.rar', '.7z', '.tar', '.gz'].includes(ext)) {
|
||||||
|
return <FileZipFilled />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.txt', '.json', '.log', '.yml', '.yaml', '.xml', '.csv'].includes(ext)) {
|
||||||
|
return <FileTextFilled />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.url'].includes(ext)) {
|
||||||
|
return <LinkOutlined />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.sitemap'].includes(ext)) {
|
||||||
|
return <GlobalOutlined />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['.folder'].includes(ext)) {
|
||||||
|
return <FolderOpenFilled />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FileUnknownFilled />
|
||||||
|
}
|
||||||
|
|
||||||
if (isEmpty(files)) {
|
if (isEmpty(files)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<ConfigProvider
|
{files.map((file) => (
|
||||||
theme={{
|
<CustomTag
|
||||||
components: {
|
key={file.id}
|
||||||
Tag: {
|
icon={getFileIcon(file.ext)}
|
||||||
borderRadiusSM: 100
|
color="#37a5aa"
|
||||||
}
|
closable
|
||||||
}
|
onClose={() => setFiles(files.filter((f) => f.id !== file.id))}>
|
||||||
}}>
|
<FileNameRender file={file} />
|
||||||
{files.map((file) => (
|
</CustomTag>
|
||||||
<Tag
|
))}
|
||||||
key={file.id}
|
|
||||||
icon={<FileOutlined />}
|
|
||||||
bordered={false}
|
|
||||||
color="cyan"
|
|
||||||
closable
|
|
||||||
onClose={() => setFiles(files.filter((f) => f.id !== file.id))}>
|
|
||||||
<FileName
|
|
||||||
onClick={() => {
|
|
||||||
if (isImage(file.ext)) {
|
|
||||||
setVisibleId(file.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const path = FileManager.getSafePath(file)
|
|
||||||
if (path) {
|
|
||||||
window.api.file.openPath(path)
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{FileManager.formatFileName(file)}
|
|
||||||
{isImage(file.ext) && (
|
|
||||||
<Image
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
src={'file://' + FileManager.getSafePath(file)}
|
|
||||||
preview={{
|
|
||||||
visible: visibleId === file.id,
|
|
||||||
src: 'file://' + FileManager.getSafePath(file),
|
|
||||||
onVisibleChange: (value) => {
|
|
||||||
setVisibleId(value ? file.id : '')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</FileName>
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</ConfigProvider>
|
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentContainer = styled.div`
|
const ContentContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 5px 15px 5px 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px 0;
|
gap: 4px 4px;
|
||||||
padding: 5px 15px 0 10px;
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const FileName = styled.span`
|
const FileName = styled.span`
|
||||||
|
|||||||
@ -84,7 +84,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
pasteLongTextAsFile,
|
pasteLongTextAsFile,
|
||||||
pasteLongTextThreshold,
|
pasteLongTextThreshold,
|
||||||
showInputEstimatedTokens,
|
showInputEstimatedTokens,
|
||||||
autoTranslateWithSpace
|
autoTranslateWithSpace,
|
||||||
|
enableQuickPanelTriggers
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const [expended, setExpend] = useState(false)
|
const [expended, setExpend] = useState(false)
|
||||||
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
|
||||||
@ -533,7 +534,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
const cursorPosition = textArea?.selectionStart ?? 0
|
const cursorPosition = textArea?.selectionStart ?? 0
|
||||||
const lastSymbol = newText[cursorPosition - 1]
|
const lastSymbol = newText[cursorPosition - 1]
|
||||||
|
|
||||||
if (!quickPanel.isVisible && lastSymbol === '/') {
|
if (enableQuickPanelTriggers && !quickPanel.isVisible && lastSymbol === '/') {
|
||||||
quickPanel.open({
|
quickPanel.open({
|
||||||
title: t('settings.quickPanel.title'),
|
title: t('settings.quickPanel.title'),
|
||||||
list: quickPanelMenu,
|
list: quickPanelMenu,
|
||||||
@ -541,7 +542,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!quickPanel.isVisible && lastSymbol === '@') {
|
if (enableQuickPanelTriggers && !quickPanel.isVisible && lastSymbol === '@') {
|
||||||
mentionModelsButtonRef.current?.openQuickPanel()
|
mentionModelsButtonRef.current?.openQuickPanel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -881,12 +882,16 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
id="inputbar"
|
id="inputbar"
|
||||||
className={classNames('inputbar-container', inputFocus && 'focus')}
|
className={classNames('inputbar-container', inputFocus && 'focus')}
|
||||||
ref={containerRef}>
|
ref={containerRef}>
|
||||||
<AttachmentPreview files={files} setFiles={setFiles} />
|
{files.length > 0 && <AttachmentPreview files={files} setFiles={setFiles} />}
|
||||||
<KnowledgeBaseInput
|
{selectedKnowledgeBases.length > 0 && (
|
||||||
selectedKnowledgeBases={selectedKnowledgeBases}
|
<KnowledgeBaseInput
|
||||||
onRemoveKnowledgeBase={handleRemoveKnowledgeBase}
|
selectedKnowledgeBases={selectedKnowledgeBases}
|
||||||
/>
|
onRemoveKnowledgeBase={handleRemoveKnowledgeBase}
|
||||||
<MentionModelsInput selectedModels={mentionModels} onRemoveModel={handleRemoveModel} />
|
/>
|
||||||
|
)}
|
||||||
|
{mentionModels.length > 0 && (
|
||||||
|
<MentionModelsInput selectedModels={mentionModels} onRemoveModel={handleRemoveModel} />
|
||||||
|
)}
|
||||||
<Textarea
|
<Textarea
|
||||||
value={text}
|
value={text}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@ -1049,6 +1054,7 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
`
|
`
|
||||||
|
|
||||||
const InputBarContainer = styled.div`
|
const InputBarContainer = styled.div`
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { FileSearchOutlined } from '@ant-design/icons'
|
import { FileSearchOutlined } from '@ant-design/icons'
|
||||||
|
import CustomTag from '@renderer/components/CustomTag'
|
||||||
import { KnowledgeBase } from '@renderer/types'
|
import { KnowledgeBase } from '@renderer/types'
|
||||||
import { ConfigProvider, Flex, Tag } from 'antd'
|
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -9,34 +9,27 @@ const KnowledgeBaseInput: FC<{
|
|||||||
onRemoveKnowledgeBase: (knowledgeBase: KnowledgeBase) => void
|
onRemoveKnowledgeBase: (knowledgeBase: KnowledgeBase) => void
|
||||||
}> = ({ selectedKnowledgeBases, onRemoveKnowledgeBase }) => {
|
}> = ({ selectedKnowledgeBases, onRemoveKnowledgeBase }) => {
|
||||||
return (
|
return (
|
||||||
<Container gap="4px 0" wrap>
|
<Container>
|
||||||
<ConfigProvider
|
{selectedKnowledgeBases.map((knowledgeBase) => (
|
||||||
theme={{
|
<CustomTag
|
||||||
components: {
|
icon={<FileSearchOutlined />}
|
||||||
Tag: {
|
color="#3d9d0f"
|
||||||
borderRadiusSM: 100
|
key={knowledgeBase.id}
|
||||||
}
|
closable
|
||||||
}
|
onClose={() => onRemoveKnowledgeBase(knowledgeBase)}>
|
||||||
}}>
|
{knowledgeBase.name}
|
||||||
{selectedKnowledgeBases.map((knowledgeBase) => (
|
</CustomTag>
|
||||||
<Tag
|
))}
|
||||||
icon={<FileSearchOutlined />}
|
|
||||||
bordered={false}
|
|
||||||
color="success"
|
|
||||||
key={knowledgeBase.id}
|
|
||||||
closable
|
|
||||||
onClose={() => onRemoveKnowledgeBase(knowledgeBase)}>
|
|
||||||
{knowledgeBase.name}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</ConfigProvider>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled(Flex)`
|
const Container = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px 15px 0 10px;
|
padding: 5px 15px 5px 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 4px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default KnowledgeBaseInput
|
export default KnowledgeBaseInput
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const MentionModelsButton: FC<Props> = ({ ref, mentionModels, onMentionModel, To
|
|||||||
.reverse()
|
.reverse()
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
label: `${item.provider.isSystem ? t(`provider.${item.provider.id}`) : item.provider.name} | ${item.model.name}`,
|
label: `${item.provider.isSystem ? t(`provider.${item.provider.id}`) : item.provider.name} | ${item.model.name}`,
|
||||||
description: <ModelTagsWithLabel model={item.model} showLabel={false} size={10} />,
|
description: <ModelTagsWithLabel model={item.model} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
|
||||||
icon: (
|
icon: (
|
||||||
<Avatar src={getModelLogo(item.model.id)} size={20}>
|
<Avatar src={getModelLogo(item.model.id)} size={20}>
|
||||||
{first(item.model.name)}
|
{first(item.model.name)}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import CustomTag from '@renderer/components/CustomTag'
|
||||||
import { useProviders } from '@renderer/hooks/useProvider'
|
import { useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { ConfigProvider, Flex, Tag } from 'antd'
|
|
||||||
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'
|
||||||
@ -19,38 +19,27 @@ const MentionModelsInput: FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container gap="4px 0" wrap>
|
<Container>
|
||||||
<ConfigProvider
|
{selectedModels.map((model) => (
|
||||||
theme={{
|
<CustomTag
|
||||||
components: {
|
icon={<i className="iconfont icon-at" />}
|
||||||
Tag: {
|
color="#1677ff"
|
||||||
borderRadiusSM: 100
|
key={getModelUniqId(model)}
|
||||||
}
|
closable
|
||||||
}
|
onClose={() => onRemoveModel(model)}>
|
||||||
}}>
|
{model.name} ({getProviderName(model)})
|
||||||
{selectedModels.map((model) => (
|
</CustomTag>
|
||||||
<Tag
|
))}
|
||||||
icon={<i className="iconfont icon-at" />}
|
|
||||||
bordered={false}
|
|
||||||
color="processing"
|
|
||||||
key={getModelUniqId(model)}
|
|
||||||
closable
|
|
||||||
onClose={() => onRemoveModel(model)}>
|
|
||||||
{model.name} ({getProviderName(model)})
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</ConfigProvider>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled(Flex)`
|
const Container = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px 15px 10px;
|
padding: 5px 15px 5px 15px;
|
||||||
i.iconfont {
|
display: flex;
|
||||||
font-size: 12px;
|
flex-wrap: wrap;
|
||||||
margin-inline-end: 7px;
|
gap: 4px 4px;
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export default MentionModelsInput
|
export default MentionModelsInput
|
||||||
|
|||||||
@ -310,6 +310,7 @@ const Container = styled(Scrollbar)<ContainerProps>`
|
|||||||
padding: 10px 0 20px;
|
padding: 10px 0 20px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
|
z-index: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default Messages
|
export default Messages
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import {
|
|||||||
setCodeShowLineNumbers,
|
setCodeShowLineNumbers,
|
||||||
setCodeStyle,
|
setCodeStyle,
|
||||||
setCodeWrappable,
|
setCodeWrappable,
|
||||||
|
setEnableQuickPanelTriggers,
|
||||||
setFontSize,
|
setFontSize,
|
||||||
setMathEngine,
|
setMathEngine,
|
||||||
setMessageFont,
|
setMessageFont,
|
||||||
@ -88,7 +89,8 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
pasteLongTextThreshold,
|
pasteLongTextThreshold,
|
||||||
multiModelMessageStyle,
|
multiModelMessageStyle,
|
||||||
thoughtAutoCollapse,
|
thoughtAutoCollapse,
|
||||||
messageNavigation
|
messageNavigation,
|
||||||
|
enableQuickPanelTriggers
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
|
|
||||||
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
|
||||||
@ -570,6 +572,15 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitleSmall>{t('settings.messages.input.enable_quick_triggers')}</SettingRowTitleSmall>
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={enableQuickPanelTriggers}
|
||||||
|
onChange={(checked) => dispatch(setEnableQuickPanelTriggers(checked))}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
|
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
|
|||||||
@ -66,7 +66,6 @@ const TranslatePage: FC = () => {
|
|||||||
targetLanguage,
|
targetLanguage,
|
||||||
createdAt: new Date().toISOString()
|
createdAt: new Date().toISOString()
|
||||||
}
|
}
|
||||||
console.log('🌟TEO🌟 ~ saveTranslateHistory ~ history:', history)
|
|
||||||
await db.translate_history.add(history)
|
await db.translate_history.add(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 93,
|
version: 94,
|
||||||
blacklist: ['runtime', 'messages'],
|
blacklist: ['runtime', 'messages'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1184,6 +1184,14 @@ const migrateConfig = {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'94': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
state.settings.enableQuickPanelTriggers = false
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export interface SettingsState {
|
|||||||
showOpenedMinappsInSidebar: boolean
|
showOpenedMinappsInSidebar: boolean
|
||||||
// 隐私设置
|
// 隐私设置
|
||||||
enableDataCollection: boolean
|
enableDataCollection: boolean
|
||||||
|
enableQuickPanelTriggers: boolean
|
||||||
exportMenuOptions: {
|
exportMenuOptions: {
|
||||||
image: boolean
|
image: boolean
|
||||||
markdown: boolean
|
markdown: boolean
|
||||||
@ -208,6 +209,7 @@ export const initialState: SettingsState = {
|
|||||||
maxKeepAliveMinapps: 3,
|
maxKeepAliveMinapps: 3,
|
||||||
showOpenedMinappsInSidebar: true,
|
showOpenedMinappsInSidebar: true,
|
||||||
enableDataCollection: false,
|
enableDataCollection: false,
|
||||||
|
enableQuickPanelTriggers: false,
|
||||||
exportMenuOptions: {
|
exportMenuOptions: {
|
||||||
image: true,
|
image: true,
|
||||||
markdown: true,
|
markdown: true,
|
||||||
@ -476,6 +478,9 @@ const settingsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setExportMenuOptions: (state, action: PayloadAction<typeof initialState.exportMenuOptions>) => {
|
setExportMenuOptions: (state, action: PayloadAction<typeof initialState.exportMenuOptions>) => {
|
||||||
state.exportMenuOptions = action.payload
|
state.exportMenuOptions = action.payload
|
||||||
|
},
|
||||||
|
setEnableQuickPanelTriggers: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.enableQuickPanelTriggers = action.payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -562,6 +567,7 @@ export const {
|
|||||||
setMaxKeepAliveMinapps,
|
setMaxKeepAliveMinapps,
|
||||||
setShowOpenedMinappsInSidebar,
|
setShowOpenedMinappsInSidebar,
|
||||||
setEnableDataCollection,
|
setEnableDataCollection,
|
||||||
|
setEnableQuickPanelTriggers,
|
||||||
setExportMenuOptions
|
setExportMenuOptions
|
||||||
} = settingsSlice.actions
|
} = settingsSlice.actions
|
||||||
|
|
||||||
|
|||||||
36
yarn.lock
36
yarn.lock
@ -3937,6 +3937,7 @@ __metadata:
|
|||||||
axios: "npm:^1.7.3"
|
axios: "npm:^1.7.3"
|
||||||
babel-plugin-styled-components: "npm:^2.1.4"
|
babel-plugin-styled-components: "npm:^2.1.4"
|
||||||
browser-image-compression: "npm:^2.0.2"
|
browser-image-compression: "npm:^2.0.2"
|
||||||
|
color: "npm:^5.0.0"
|
||||||
dayjs: "npm:^1.11.11"
|
dayjs: "npm:^1.11.11"
|
||||||
dexie: "npm:^4.0.8"
|
dexie: "npm:^4.0.8"
|
||||||
dexie-react-hooks: "npm:^1.1.7"
|
dexie-react-hooks: "npm:^1.1.7"
|
||||||
@ -5305,6 +5306,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"color-convert@npm:^3.0.1":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "color-convert@npm:3.0.1"
|
||||||
|
dependencies:
|
||||||
|
color-name: "npm:^2.0.0"
|
||||||
|
checksum: 10c0/1ff3db76f4b247aec9062c079b96050f3bcde4fe2183fabf60652b25933fecb85b191bd92044ca60abece39927ad08a3e6d829d9fda9f505c1a1273d13dbc780
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"color-name@npm:1.1.3":
|
"color-name@npm:1.1.3":
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
resolution: "color-name@npm:1.1.3"
|
resolution: "color-name@npm:1.1.3"
|
||||||
@ -5312,6 +5322,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"color-name@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "color-name@npm:2.0.0"
|
||||||
|
checksum: 10c0/fc0304606e5c5941f4649a9975c03a2ecd52a22aba3dadb3309b3e4ee61d78c3e13ff245e80b9a930955d38c5f32a9004196a7456c4542822aa1fcfea8e928ed
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"color-name@npm:~1.1.4":
|
"color-name@npm:~1.1.4":
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
resolution: "color-name@npm:1.1.4"
|
resolution: "color-name@npm:1.1.4"
|
||||||
@ -5319,6 +5336,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"color-string@npm:^2.0.0":
|
||||||
|
version: 2.0.1
|
||||||
|
resolution: "color-string@npm:2.0.1"
|
||||||
|
dependencies:
|
||||||
|
color-name: "npm:^2.0.0"
|
||||||
|
checksum: 10c0/8547edb171cfcc9b56d54664560fba98afd065deedd6812e9545be6448c9c38f89dff51e38d18249b3670fa11647824cbcb77bfbb0c8bff8e37c53c9c0baecc1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"color-support@npm:^1.1.3":
|
"color-support@npm:^1.1.3":
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
resolution: "color-support@npm:1.1.3"
|
resolution: "color-support@npm:1.1.3"
|
||||||
@ -5328,6 +5354,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"color@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "color@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
color-convert: "npm:^3.0.1"
|
||||||
|
color-string: "npm:^2.0.0"
|
||||||
|
checksum: 10c0/fa5f2e84add2e1622abe016b917cca739535fc9845305db32043a5bde4b8164033f179fd1807ac3fe52c9ee7888f82d80e5ff90d1e2652454a2341ab3d23d086
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"colorette@npm:^2.0.20":
|
"colorette@npm:^2.0.20":
|
||||||
version: 2.0.20
|
version: 2.0.20
|
||||||
resolution: "colorette@npm:2.0.20"
|
resolution: "colorette@npm:2.0.20"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user