feat: WebDAV data backup and restore secondary confirmation #1326

close #1326
This commit is contained in:
kangfenmao 2025-02-12 15:18:53 +08:00
parent 38f665e484
commit 50438dd612
9 changed files with 30 additions and 16 deletions

View File

@ -1402,11 +1402,7 @@ export function isWebSearchModel(model: Model): boolean {
} }
if (provider.id === 'aihubmix') { if (provider.id === 'aihubmix') {
const models = [ const models = ['gemini-2.0-flash-search', 'gemini-2.0-flash-exp-search', 'gemini-2.0-pro-exp-02-05-search']
'gemini-2.0-flash-search',
'gemini-2.0-flash-exp-search',
'gemini-2.0-pro-exp-02-05-search'
]
return models.includes(model?.id) return models.includes(model?.id)
} }

View File

@ -433,6 +433,8 @@
"webdav.noSync": "Waiting for next backup", "webdav.noSync": "Waiting for next backup",
"webdav.syncError": "Backup Error", "webdav.syncError": "Backup Error",
"webdav.lastSync": "Last Backup", "webdav.lastSync": "Last Backup",
"webdav.restore.title": "Restore from WebDAV",
"webdav.restore.content": "Restore from WebDAV will overwrite the current data, continue?",
"notion.api_key": "Notion API Key", "notion.api_key": "Notion API Key",
"notion.database_id": "Notion Database ID", "notion.database_id": "Notion Database ID",
"notion.title": "Notion Configuration" "notion.title": "Notion Configuration"

View File

@ -424,6 +424,8 @@
"webdav.noSync": "次回のバックアップを待っています", "webdav.noSync": "次回のバックアップを待っています",
"webdav.syncError": "バックアップエラー", "webdav.syncError": "バックアップエラー",
"webdav.lastSync": "最終同期", "webdav.lastSync": "最終同期",
"webdav.restore.title": "WebDAVから復元",
"webdav.restore.content": "WebDAVから復元すると、現在のデータが上書きされます。続行しますか",
"notion.api_key": "Notion APIキー", "notion.api_key": "Notion APIキー",
"notion.database_id": "Notion データベースID", "notion.database_id": "Notion データベースID",
"notion.title": "Notion 設定" "notion.title": "Notion 設定"

View File

@ -425,6 +425,8 @@
"webdav.noSync": "Ожидание следующего резервного копирования", "webdav.noSync": "Ожидание следующего резервного копирования",
"webdav.syncError": "Ошибка резервного копирования", "webdav.syncError": "Ошибка резервного копирования",
"webdav.lastSync": "Последняя синхронизация", "webdav.lastSync": "Последняя синхронизация",
"webdav.restore.title": "Восстановление с WebDAV",
"webdav.restore.content": "Восстановление с WebDAV перезапишет текущие данные, продолжить?",
"notion.api_key": "Ключ API Notion", "notion.api_key": "Ключ API Notion",
"notion.database_id": "ID базы данных Notion", "notion.database_id": "ID базы данных Notion",
"notion.title": "Настройки Notion" "notion.title": "Настройки Notion"

View File

@ -432,6 +432,8 @@
"webdav.noSync": "等待下次备份", "webdav.noSync": "等待下次备份",
"webdav.syncError": "备份错误", "webdav.syncError": "备份错误",
"webdav.lastSync": "上次备份时间", "webdav.lastSync": "上次备份时间",
"webdav.restore.title": "从 WebDAV 恢复",
"webdav.restore.content": "从 WebDAV 恢复将覆盖当前数据,是否继续?",
"notion.api_key": "Notion 密钥", "notion.api_key": "Notion 密钥",
"notion.database_id": "Notion 数据库ID", "notion.database_id": "Notion 数据库ID",
"notion.title": "Notion 配置" "notion.title": "Notion 配置"

View File

@ -431,6 +431,8 @@
"webdav.noSync": "等待下次備份", "webdav.noSync": "等待下次備份",
"webdav.syncError": "備份錯誤", "webdav.syncError": "備份錯誤",
"webdav.lastSync": "上次同步時間", "webdav.lastSync": "上次同步時間",
"webdav.restore.title": "從 WebDAV 恢復",
"webdav.restore.content": "從 WebDAV 恢復將覆蓋當前資料,是否繼續?",
"notion.api_key": "Notion 金鑰", "notion.api_key": "Notion 金鑰",
"notion.database_id": "Notion 資料庫 ID", "notion.database_id": "Notion 資料庫 ID",
"notion.title": "Notion 配置" "notion.title": "Notion 配置"

View File

@ -5,7 +5,7 @@ import { backup, reset, restore } from '@renderer/services/BackupService'
import { RootState, useAppDispatch } from '@renderer/store' import { RootState, useAppDispatch } from '@renderer/store'
import { setNotionApiKey, setNotionDatabaseID } from '@renderer/store/settings' import { setNotionApiKey, setNotionDatabaseID } from '@renderer/store/settings'
import { AppInfo } from '@renderer/types' import { AppInfo } from '@renderer/types'
import { Button,Modal, Typography } from 'antd' import { Button, Modal, Typography } from 'antd'
import Input from 'antd/es/input/Input' import Input from 'antd/es/input/Input'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -23,17 +23,16 @@ const NotionSettings: FC = () => {
// 这里可以添加 Notion 相关的状态和逻辑 // 这里可以添加 Notion 相关的状态和逻辑
// 例如: // 例如:
const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey); const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey)
const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID); const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID)
const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionApiKey(e.target.value)) dispatch(setNotionApiKey(e.target.value))
}; }
const handleNotionDatabaseIdChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleNotionDatabaseIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionDatabaseID(e.target.value)) dispatch(setNotionDatabaseID(e.target.value))
}; }
return ( return (
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
@ -42,8 +41,8 @@ const NotionSettings: FC = () => {
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.data.notion.api_key')}</SettingRowTitle> <SettingRowTitle>{t('settings.data.notion.api_key')}</SettingRowTitle>
<HStack alignItems="center" gap="5px"> <HStack alignItems="center" gap="5px">
<Input.Password <Input
type="text" type="password"
value={notionApiKey || ''} value={notionApiKey || ''}
onChange={handleNotionTokenChange} onChange={handleNotionTokenChange}
onBlur={handleNotionTokenChange} onBlur={handleNotionTokenChange}
@ -165,7 +164,6 @@ const DataSettings: FC = () => {
</HStack> </HStack>
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
</SettingContainer> </SettingContainer>
) )
} }

View File

@ -66,6 +66,15 @@ const WebDavSettings: FC = () => {
setRestoring(false) setRestoring(false)
} }
const onPressRestore = () => {
window.modal.confirm({
title: t('settings.data.webdav.restore.title'),
content: t('settings.data.webdav.restore.content'),
centered: true,
onOk: onRestore
})
}
const onSyncIntervalChange = (value: number) => { const onSyncIntervalChange = (value: number) => {
setSyncInterval(value) setSyncInterval(value)
dispatch(_setWebdavSyncInterval(value)) dispatch(_setWebdavSyncInterval(value))
@ -158,7 +167,7 @@ const WebDavSettings: FC = () => {
<Button onClick={onBackup} icon={<SaveOutlined />} loading={backuping}> <Button onClick={onBackup} icon={<SaveOutlined />} loading={backuping}>
{t('settings.data.webdav.backup.button')} {t('settings.data.webdav.backup.button')}
</Button> </Button>
<Button onClick={onRestore} icon={<FolderOpenOutlined />} loading={restoring}> <Button onClick={onPressRestore} icon={<FolderOpenOutlined />} loading={restoring}>
{t('settings.data.webdav.restore.button')} {t('settings.data.webdav.restore.button')}
</Button> </Button>
</HStack> </HStack>

View File

@ -161,6 +161,7 @@ export default class OpenAIProvider extends BaseProvider {
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant) const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
let systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined let systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
if (['o1', 'o1-2024-12-17'].includes(model.id) || model.id.startsWith('o3')) { if (['o1', 'o1-2024-12-17'].includes(model.id) || model.id.startsWith('o3')) {
systemMessage = { systemMessage = {
role: 'developer', role: 'developer',
@ -344,7 +345,7 @@ export default class OpenAIProvider extends BaseProvider {
// 针对思考类模型的返回,总结仅截取</think>之后的内容 // 针对思考类模型的返回,总结仅截取</think>之后的内容
let content = response.choices[0].message?.content || '' let content = response.choices[0].message?.content || ''
content = content.replace(/^<think>(.*?)<\/think>/s, '') content = content.replace(/^<think>(.*?)<\/think>/s, '')
return removeSpecialCharacters(content.substring(0, 50)) return removeSpecialCharacters(content.substring(0, 50))
} }