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:
parent
5f7d8652bc
commit
de7f806bbc
@ -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);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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 }} />,
|
||||
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(item.model.id)} size={20}>
|
||||
{first(item.model.name)}
|
||||
<Avatar src={getModelLogo(m.id)} size={20}>
|
||||
{first(m.name)}
|
||||
</Avatar>
|
||||
),
|
||||
action: () => onMentionModel(item.model),
|
||||
isSelected: mentionModels.some((selected) => getModelUniqId(selected) === getModelUniqId(item.model))
|
||||
action: () => onMentionModel(m),
|
||||
isSelected: mentionModels.some((selected) => getModelUniqId(selected) === getModelUniqId(m))
|
||||
}))
|
||||
newList.push({
|
||||
|
||||
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;
|
||||
`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1224,6 +1224,14 @@ const migrateConfig = {
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
},
|
||||
'97': (state: RootState) => {
|
||||
try {
|
||||
state.settings.enableBackspaceDeleteModel = true
|
||||
return state
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user