hotfix: 优化一些issue反馈 (#4758)

feat(Inputbar, Settings): add backspace delete model functionality and localization updates

- Implemented a new setting to enable backspace key functionality for deleting models/attachments in the Inputbar.
- Added corresponding localization strings for English, Japanese, Russian, Chinese (Simplified and Traditional) in the i18n files.
- Updated the QuickPanelBody styling to inherit border-radius.
- Migrated the new setting to the state management for persistence.

Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
This commit is contained in:
Teo 2025-04-13 21:07:00 +08:00 committed by GitHub
parent 5f7d8652bc
commit de7f806bbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 106 additions and 49 deletions

View File

@ -545,6 +545,7 @@ const QuickPanelBody = styled.div`
background-color: rgba(240, 240, 240, 0.5);
backdrop-filter: blur(35px) saturate(150%);
z-index: -1;
border-radius: inherit;
body[theme-mode='dark'] & {
background-color: rgba(40, 40, 40, 0.4);

View File

@ -1132,6 +1132,7 @@
"messages.input.show_estimated_tokens": "Show estimated tokens",
"messages.input.title": "Input Settings",
"messages.input.enable_quick_triggers": "Enable '/' and '@' triggers",
"messages.input.enable_delete_model": "Enable the backspace key to delete models/attachments.",
"messages.markdown_rendering_input_message": "Markdown render input message",
"messages.math_engine": "Math engine",
"messages.metrics": "{{time_first_token_millsec}}ms to first token | {{token_speed}} tok/sec",

View File

@ -1131,6 +1131,7 @@
"messages.input.show_estimated_tokens": "推定トークン数を表示",
"messages.input.title": "入力設定",
"messages.input.enable_quick_triggers": "'/' と '@' を有効にしてクイックメニューを表示します。",
"messages.input.enable_delete_model": "バックスペースキーでモデル/添付ファイルを削除します。",
"messages.markdown_rendering_input_message": "Markdownで入力メッセージをレンダリング",
"messages.math_engine": "数式エンジン",
"messages.metrics": "最初のトークンまでの時間 {{time_first_token_millsec}}ms | トークン速度 {{token_speed}} tok/sec",

View File

@ -1131,6 +1131,7 @@
"messages.input.show_estimated_tokens": "Показывать затраты токенов",
"messages.input.title": "Настройки ввода",
"messages.input.enable_quick_triggers": "Включите '/' и '@', чтобы вызвать быстрое меню.",
"messages.input.enable_delete_model": "Включите удаление модели/вложения с помощью клавиши Backspace",
"messages.markdown_rendering_input_message": "Отображение ввода в формате Markdown",
"messages.math_engine": "Математический движок",
"messages.metrics": "{{time_first_token_millsec}}ms до первого токена | {{token_speed}} tok/sec",

View File

@ -1132,6 +1132,7 @@
"messages.input.show_estimated_tokens": "显示预估 Token 数",
"messages.input.title": "输入设置",
"messages.input.enable_quick_triggers": "启用 '/' 和 '@' 触发快捷菜单",
"messages.input.enable_delete_model": "启用删除键删除输入的模型/附件",
"messages.markdown_rendering_input_message": "Markdown 渲染输入消息",
"messages.math_engine": "数学公式引擎",
"messages.metrics": "首字时延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",

View File

@ -1131,6 +1131,7 @@
"messages.input.show_estimated_tokens": "顯示預估 Token 數",
"messages.input.title": "輸入設定",
"messages.input.enable_quick_triggers": "啟用 '/' 和 '@' 觸發快捷選單",
"messages.input.enable_delete_model": "啟用刪除鍵刪除模型/附件",
"messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息",
"messages.math_engine": "Markdown 渲染輸入訊息",
"messages.metrics": "首字延遲 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",

View File

@ -80,7 +80,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
pasteLongTextThreshold,
showInputEstimatedTokens,
autoTranslateWithSpace,
enableQuickPanelTriggers
enableQuickPanelTriggers,
enableBackspaceDeleteModel
} = useSettings()
const [expended, setExpend] = useState(false)
const [estimateTokenCount, setEstimateTokenCount] = useState(0)
@ -467,21 +468,12 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
return event.preventDefault()
}
if (event.key === 'Backspace' && text.trim() === '' && mentionModels.length > 0) {
if (enableBackspaceDeleteModel && event.key === 'Backspace' && text.trim() === '' && mentionModels.length > 0) {
setMentionModels((prev) => prev.slice(0, -1))
return event.preventDefault()
}
if (event.key === 'Backspace' && text.trim() === '' && selectedKnowledgeBases.length > 0) {
setSelectedKnowledgeBases((prev) => {
const newSelectedKnowledgeBases = prev.slice(0, -1)
updateAssistant({ ...assistant, knowledge_bases: newSelectedKnowledgeBases })
return newSelectedKnowledgeBases
})
return event.preventDefault()
}
if (event.key === 'Backspace' && text.trim() === '' && files.length > 0) {
if (enableBackspaceDeleteModel && event.key === 'Backspace' && text.trim() === '' && files.length > 0) {
setFiles((prev) => prev.slice(0, -1))
return event.preventDefault()
}

View File

@ -8,11 +8,13 @@ import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService'
import { Model } from '@renderer/types'
import { Avatar, Tooltip } from 'antd'
import { useLiveQuery } from 'dexie-react-hooks'
import { first, sortBy } from 'lodash'
import { AtSign } from 'lucide-react'
import { FC, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'
import { FC, useCallback, useImperativeHandle, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import styled from 'styled-components'
export interface MentionModelsButtonRef {
openQuickPanel: () => void
@ -27,47 +29,84 @@ interface Props {
const MentionModelsButton: FC<Props> = ({ ref, mentionModels, onMentionModel, ToolbarButton }) => {
const { providers } = useProviders()
const [pinnedModels, setPinnedModels] = useState<string[]>([])
const { t } = useTranslation()
const navigate = useNavigate()
const quickPanel = useQuickPanel()
const pinnedModels = useLiveQuery(
async () => {
const setting = await db.settings.get('pinned:models')
return setting?.value || []
},
[],
[]
)
const modelItems = useMemo(() => {
// Get all models from providers
const allModels = providers
.filter((p) => p.models && p.models.length > 0)
.flatMap((p) =>
const items: QuickPanelListItem[] = []
if (pinnedModels.length > 0) {
const pinnedItems = providers.flatMap((p) =>
p.models
.filter((m) => !isEmbeddingModel(m))
.filter((m) => !isRerankModel(m))
.filter((m) => !isEmbeddingModel(m) && !isRerankModel(m))
.filter((m) => pinnedModels.includes(getModelUniqId(m)))
.map((m) => ({
model: m,
provider: p,
isPinned: pinnedModels.includes(getModelUniqId(m))
label: (
<>
<ProviderName>{p.isSystem ? t(`provider.${p.id}`) : p.name}</ProviderName>
<span style={{ opacity: 0.8 }}> | {m.name}</span>
</>
),
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: (
<Avatar src={getModelLogo(m.id)} size={20}>
{first(m.name)}
</Avatar>
),
action: () => onMentionModel(m),
isSelected: mentionModels.some((selected) => getModelUniqId(selected) === getModelUniqId(m))
}))
)
// Sort by pinned status and name
const newList: QuickPanelListItem[] = sortBy(allModels, ['isPinned', 'model.name'])
.reverse()
.map((item) => ({
label: `${item.provider.isSystem ? t(`provider.${item.provider.id}`) : item.provider.name} | ${item.model.name}`,
description: <ModelTagsWithLabel model={item.model} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: (
<Avatar src={getModelLogo(item.model.id)} size={20}>
{first(item.model.name)}
</Avatar>
),
action: () => onMentionModel(item.model),
isSelected: mentionModels.some((selected) => getModelUniqId(selected) === getModelUniqId(item.model))
}))
newList.push({
if (pinnedItems.length > 0) {
items.push(...sortBy(pinnedItems, ['label']))
}
}
providers.forEach((p) => {
const providerModels = p.models
.filter((m) => !isEmbeddingModel(m) && !isRerankModel(m))
.filter((m) => !pinnedModels.includes(getModelUniqId(m)))
.map((m) => ({
label: (
<>
<ProviderName>{p.isSystem ? t(`provider.${p.id}`) : p.name}</ProviderName>
<span style={{ opacity: 0.8 }}> | {m.name}</span>
</>
),
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: (
<Avatar src={getModelLogo(m.id)} size={20}>
{first(m.name)}
</Avatar>
),
action: () => onMentionModel(m),
isSelected: mentionModels.some((selected) => getModelUniqId(selected) === getModelUniqId(m))
}))
if (providerModels.length > 0) {
items.push(...sortBy(providerModels, ['label']))
}
})
items.push({
label: t('settings.models.add.add_model') + '...',
icon: <PlusOutlined />,
action: () => navigate('/settings/provider'),
isSelected: false
})
return newList
return items
}, [providers, t, pinnedModels, mentionModels, onMentionModel, navigate])
const openQuickPanel = useCallback(() => {
@ -90,14 +129,6 @@ const MentionModelsButton: FC<Props> = ({ ref, mentionModels, onMentionModel, To
}
}, [openQuickPanel, quickPanel])
useEffect(() => {
const loadPinnedModels = async () => {
const setting = await db.settings.get('pinned:models')
setPinnedModels(setting?.value || [])
}
loadPinnedModels()
}, [])
useImperativeHandle(ref, () => ({
openQuickPanel
}))
@ -112,3 +143,6 @@ const MentionModelsButton: FC<Props> = ({ ref, mentionModels, onMentionModel, To
}
export default MentionModelsButton
const ProviderName = styled.span`
font-weight: 500;
`

View File

@ -27,6 +27,7 @@ import {
setCodeShowLineNumbers,
setCodeStyle,
setCodeWrappable,
setEnableBackspaceDeleteModel,
setEnableQuickPanelTriggers,
setFontSize,
setMathEngine,
@ -91,7 +92,8 @@ const SettingsTab: FC<Props> = (props) => {
multiModelMessageStyle,
thoughtAutoCollapse,
messageNavigation,
enableQuickPanelTriggers
enableQuickPanelTriggers,
enableBackspaceDeleteModel
} = useSettings()
const onUpdateAssistantSettings = (settings: Partial<AssistantSettings>) => {
@ -608,6 +610,15 @@ const SettingsTab: FC<Props> = (props) => {
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('settings.messages.input.enable_delete_model')}</SettingRowTitleSmall>
<Switch
size="small"
checked={enableBackspaceDeleteModel}
onChange={(checked) => dispatch(setEnableBackspaceDeleteModel(checked))}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
<StyledSelect

View File

@ -1224,6 +1224,14 @@ const migrateConfig = {
} catch (error) {
return state
}
},
'97': (state: RootState) => {
try {
state.settings.enableBackspaceDeleteModel = true
return state
} catch (error) {
return state
}
}
}

View File

@ -113,6 +113,7 @@ export interface SettingsState {
// 隐私设置
enableDataCollection: boolean
enableQuickPanelTriggers: boolean
enableBackspaceDeleteModel: boolean
exportMenuOptions: {
image: boolean
markdown: boolean
@ -212,6 +213,7 @@ export const initialState: SettingsState = {
showOpenedMinappsInSidebar: true,
enableDataCollection: false,
enableQuickPanelTriggers: false,
enableBackspaceDeleteModel: true,
exportMenuOptions: {
image: true,
markdown: true,
@ -483,6 +485,9 @@ const settingsSlice = createSlice({
},
setEnableQuickPanelTriggers: (state, action: PayloadAction<boolean>) => {
state.enableQuickPanelTriggers = action.payload
},
setEnableBackspaceDeleteModel: (state, action: PayloadAction<boolean>) => {
state.enableBackspaceDeleteModel = action.payload
}
}
})
@ -570,7 +575,8 @@ export const {
setShowOpenedMinappsInSidebar,
setEnableDataCollection,
setEnableQuickPanelTriggers,
setExportMenuOptions
setExportMenuOptions,
setEnableBackspaceDeleteModel
} = settingsSlice.actions
export default settingsSlice.reducer