Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ export const DEEPLINK_EVENTS = {
// 全局通知相关事件
export const NOTIFICATION_EVENTS = {
SHOW_ERROR: 'notification:show-error', // 显示错误通知
SYS_NOTIFY_CLICKED: 'notification:sys-notify-clicked' // 系统通知点击事件
SYS_NOTIFY_CLICKED: 'notification:sys-notify-clicked', // 系统通知点击事件
DATA_RESET_COMPLETE_DEV: 'notification:data-reset-complete-dev' // 开发环境数据重置完成通知
}

export const SHORTCUT_EVENTS = {
Expand Down
104 changes: 104 additions & 0 deletions src/main/presenter/devicePresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import path from 'path'
import { app, dialog } from 'electron'
import { nanoid } from 'nanoid'
import axios from 'axios'
import { is } from '@electron-toolkit/utils'
import { eventBus, SendTarget } from '../../eventbus'
import { NOTIFICATION_EVENTS } from '../../events'
const execAsync = promisify(exec)

export class DevicePresenter implements IDevicePresenter {
Expand Down Expand Up @@ -276,6 +279,107 @@ export class DevicePresenter implements IDevicePresenter {
})
}

/**
* 根据类型重置数据
* @param resetType 重置类型:'chat' | 'config' | 'all'
*/
async resetDataByType(resetType: 'chat' | 'config' | 'all'): Promise<void> {
try {
const userDataPath = app.getPath('userData')

const removeDirectory = (dirPath: string): void => {
if (fs.existsSync(dirPath)) {
fs.readdirSync(dirPath).forEach((file) => {
const currentPath = path.join(dirPath, file)
if (fs.lstatSync(currentPath).isDirectory()) {
removeDirectory(currentPath)
} else {
fs.unlinkSync(currentPath)
}
})
fs.rmdirSync(dirPath)
}
}

const removeFile = (filePath: string): void => {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath)
}
}

switch (resetType) {
case 'chat': {
// 只删除聊天数据
const dbPath = path.join(userDataPath, 'app_db')
console.log('Resetting chat data:', dbPath)
removeDirectory(dbPath)
break
}

case 'config': {
// 删除配置文件
console.log('Resetting configuration files')
const configFiles = [
path.join(userDataPath, 'app-settings.json'),
path.join(userDataPath, 'mcp-settings.json'),
path.join(userDataPath, 'model-config.json'),
path.join(userDataPath, 'custom_prompts.json')
]

configFiles.forEach((filePath) => {
try {
removeFile(filePath)
console.log('Removed config file:', filePath)
} catch (error) {
console.warn('Failed to remove config file:', filePath, error)
}
})

try {
removeDirectory(path.join(userDataPath, 'provider_models'))
console.log('Removed provider_models directory')
} catch (error) {
console.warn('Failed to remove provider_models directory:', error)
}
break
}

case 'all': {
// 删除整个用户数据目录
console.log('Performing complete reset of user data:', userDataPath)
removeDirectory(userDataPath)
break
}

default:
throw new Error(`Unknown reset type: ${resetType}`)
}

this.restartAppWithDelay()
} catch (error) {
console.error('resetDataByType failed:', error)
throw error
}
}

private restartAppWithDelay(): void {
try {
if (is.dev) {
console.log('开发环境下数据重置完成,发送通知到渲染进程')
eventBus.sendToRenderer(NOTIFICATION_EVENTS.DATA_RESET_COMPLETE_DEV, SendTarget.ALL_WINDOWS)
return
}

setTimeout(() => {
app.relaunch()
app.exit()
}, 1000)
} catch (error) {
console.error('重启失败:', error)
throw error
}
}

/**
* 选择目录
* @returns 返回所选目录的路径,如果用户取消则返回null
Expand Down
12 changes: 12 additions & 0 deletions src/renderer/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useToast } from './components/ui/toast/use-toast'
import { useSettingsStore } from '@/stores/settings'
import { useThemeStore } from '@/stores/theme'
import { useLanguageStore } from '@/stores/language'
import { useI18n } from 'vue-i18n'
import TranslatePopup from '@/components/popup/TranslatePopup.vue'
import ModelCheckDialog from '@/components/settings/ModelCheckDialog.vue'
import { useModelCheckStore } from '@/stores/modelCheck'
Expand All @@ -26,6 +27,7 @@ const settingsStore = useSettingsStore()
const themeStore = useThemeStore()
const langStore = useLanguageStore()
const modelCheckStore = useModelCheckStore()
const { t } = useI18n()
// 错误通知队列及当前正在显示的错误
const errorQueue = ref<Array<{ id: string; title: string; message: string; type: string }>>([])
const currentErrorId = ref<string | null>(null)
Expand Down Expand Up @@ -208,6 +210,15 @@ onMounted(() => {
handleGoSettings()
})

window.electron.ipcRenderer.on(NOTIFICATION_EVENTS.DATA_RESET_COMPLETE_DEV, () => {
toast({
title: t('settings.data.resetCompleteDevTitle'),
description: t('settings.data.resetCompleteDevMessage'),
variant: 'default',
duration: 15000
})
})

window.electron.ipcRenderer.on(NOTIFICATION_EVENTS.SYS_NOTIFY_CLICKED, (_, msg) => {
let threadId: string | null = null

Expand Down Expand Up @@ -283,6 +294,7 @@ onBeforeUnmount(() => {
window.electron.ipcRenderer.removeAllListeners(SHORTCUT_EVENTS.CREATE_NEW_CONVERSATION)
window.electron.ipcRenderer.removeAllListeners(SHORTCUT_EVENTS.GO_SETTINGS)
window.electron.ipcRenderer.removeAllListeners(NOTIFICATION_EVENTS.SYS_NOTIFY_CLICKED)
window.electron.ipcRenderer.removeAllListeners(NOTIFICATION_EVENTS.DATA_RESET_COMPLETE_DEV)
})
</script>

Expand Down
116 changes: 115 additions & 1 deletion src/renderer/src/components/settings/DataSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,90 @@
</DialogContent>
</Dialog>

<!-- 分割线 -->
<Separator class="my-4" />

<!-- 数据重置选项 -->
<AlertDialog v-model:open="isResetDialogOpen">
<AlertDialogTrigger as-child>
<div
class="p-2 flex flex-row items-center gap-2 hover:bg-accent rounded-lg cursor-pointer"
:dir="languageStore.dir"
>
<Icon icon="lucide:rotate-ccw" class="w-4 h-4 text-destructive" />
<span class="text-sm font-medium text-destructive">{{
t('settings.data.resetData')
}}</span>
</div>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ t('settings.data.resetConfirmTitle') }}</AlertDialogTitle>
<AlertDialogDescription>
{{ t('settings.data.resetConfirmDescription') }}
</AlertDialogDescription>
</AlertDialogHeader>
<div class="p-4">
<RadioGroup v-model="resetType" class="flex flex-col gap-3">
<div
class="flex items-start space-x-3 cursor-pointer hover:bg-accent rounded-lg p-2 -m-2"
@click="resetType = 'chat'"
>
<RadioGroupItem value="chat" id="reset-chat" class="mt-1" />
<div class="flex flex-col">
<Label for="reset-chat" class="font-medium cursor-pointer">{{
t('settings.data.resetChatData')
}}</Label>
<p class="text-xs text-muted-foreground">
{{ t('settings.data.resetChatDataDesc') }}
</p>
</div>
</div>
<div
class="flex items-start space-x-3 cursor-pointer hover:bg-accent rounded-lg p-2 -m-2"
@click="resetType = 'config'"
>
<RadioGroupItem value="config" id="reset-config" class="mt-1" />
<div class="flex flex-col">
<Label for="reset-config" class="font-medium cursor-pointer">{{
t('settings.data.resetConfig')
}}</Label>
<p class="text-xs text-muted-foreground">
{{ t('settings.data.resetConfigDesc') }}
</p>
</div>
</div>
<div
class="flex items-start space-x-3 cursor-pointer hover:bg-accent rounded-lg p-2 -m-2"
@click="resetType = 'all'"
>
<RadioGroupItem value="all" id="reset-all" class="mt-1" />
<div class="flex flex-col">
<Label for="reset-all" class="font-medium cursor-pointer">{{
t('settings.data.resetAll')
}}</Label>
<p class="text-xs text-muted-foreground">{{ t('settings.data.resetAllDesc') }}</p>
</div>
</div>
</RadioGroup>
</div>
<AlertDialogFooter>
<AlertDialogCancel @click="closeResetDialog">
{{ t('dialog.cancel') }}
</AlertDialogCancel>
<AlertDialogAction
:class="
cn('bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90')
"
:disabled="isResetting"
@click="handleReset"
>
{{ isResetting ? t('settings.data.resetting') : t('settings.data.confirmReset') }}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

<AlertDialog :open="!!syncStore.importResult">
<AlertDialogContent>
<AlertDialogHeader>
Expand Down Expand Up @@ -154,26 +238,37 @@ import {
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
AlertDialogTitle,
AlertDialogTrigger
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Label } from '@/components/ui/label'
import { Separator } from '@/components/ui/separator'
import { useSyncStore } from '@/stores/sync'
import { useLanguageStore } from '@/stores/language'
import { usePresenter } from '@/composables/usePresenter'
import { cn } from '@/lib/utils'

const { t } = useI18n()
const languageStore = useLanguageStore()
const syncStore = useSyncStore()
const devicePresenter = usePresenter('devicePresenter')

const isImportDialogOpen = ref(false)
const importMode = ref('increment')

const isResetDialogOpen = ref(false)
const resetType = ref<'chat' | 'config' | 'all'>('chat')
const isResetting = ref(false)

// 使用计算属性处理双向绑定
const syncEnabled = computed({
get: () => syncStore.syncEnabled,
Expand Down Expand Up @@ -211,4 +306,23 @@ const handleAlertAction = () => {
}
syncStore.clearImportResult()
}

const closeResetDialog = () => {
isResetDialogOpen.value = false
resetType.value = 'chat'
}

const handleReset = async () => {
if (isResetting.value) return

isResetting.value = true
try {
await devicePresenter.resetDataByType(resetType.value)
closeResetDialog()
} catch (error) {
console.error('重置数据失败:', error)
} finally {
isResetting.value = false
}
}
</script>
3 changes: 2 additions & 1 deletion src/renderer/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export const DEEPLINK_EVENTS = {
// 全局通知相关事件
export const NOTIFICATION_EVENTS = {
SHOW_ERROR: 'notification:show-error', // 显示错误通知
SYS_NOTIFY_CLICKED: 'notification:sys-notify-clicked' // 系统通知点击事件
SYS_NOTIFY_CLICKED: 'notification:sys-notify-clicked', // 系统通知点击事件
DATA_RESET_COMPLETE_DEV: 'notification:data-reset-complete-dev' // 开发环境数据重置完成通知
}
export const SHORTCUT_EVENTS = {
ZOOM_IN: 'shortcut:zoom-in',
Expand Down
15 changes: 14 additions & 1 deletion src/renderer/src/i18n/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,20 @@
"importing": "Importing...",
"confirmImport": "Confirm Import",
"importSuccessTitle": "Import Successful",
"importErrorTitle": "Import Failed"
"importErrorTitle": "Import Failed",
"resetData": "Reset Data",
"resetConfirmTitle": "Confirm Data Reset",
"resetConfirmDescription": "Please select the type of data to reset. This operation cannot be undone and the application will restart automatically after reset.",
"resetChatData": "Reset Chat Data",
"resetChatDataDesc": "Delete all chat history and conversation records",
"resetConfig": "Reset Configuration",
"resetConfigDesc": "Delete all app settings, model configurations and custom prompts",
"resetAll": "Complete Reset",
"resetAllDesc": "Delete all data including chat history, configurations and cache files",
"resetting": "Resetting...",
"confirmReset": "Confirm Reset",
"resetCompleteDevTitle": "Data Reset Complete",
"resetCompleteDevMessage": "Please manually restart the application in development mode. Stop the current process and run pnpm run dev again"
},
"model": {
"title": "Model Settings",
Expand Down
15 changes: 14 additions & 1 deletion src/renderer/src/i18n/fa-IR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,20 @@
"importing": "در حال وارد کردن...",
"confirmImport": "پذیرش وارد کردن",
"importSuccessTitle": "وارد کردن موفق",
"importErrorTitle": "وارد کردن ناموفق"
"importErrorTitle": "وارد کردن ناموفق",
"resetData": "بازنشانی داده‌ها",
"resetConfirmTitle": "پذیرش بازنشانی داده‌ها",
"resetConfirmDescription": "لطفاً نوع داده‌هایی که می‌خواهید بازنشانی کنید را انتخاب کنید. این عملیات غیرقابل بازگشت است و پس از بازنشانی، برنامه به طور خودکار مجدداً راه‌اندازی خواهد شد.",
"resetChatData": "بازنشانی داده‌های گفت‌وگو",
"resetChatDataDesc": "حذف تمام تاریخچه گفت‌وگوها و سوابق مکالمات",
"resetConfig": "بازنشانی پیکربندی",
"resetConfigDesc": "حذف تمام تنظیمات برنامه، پیکربندی‌های مدل و دستورکارهای سفارشی",
"resetAll": "بازنشانی کامل",
"resetAllDesc": "حذف تمام داده‌ها شامل تاریخچه گفت‌وگوها، پیکربندی‌ها و فایل‌های کش",
"resetting": "در حال بازنشانی...",
"confirmReset": "پذیرش بازنشانی",
"resetCompleteDevTitle": "بازنشانی داده‌ها تکمیل شد",
"resetCompleteDevMessage": "در محیط توسعه لطفاً برنامه را به صورت دستی مجدداً راه‌اندازی کنید. فرآیند فعلی را متوقف کرده و pnpm run dev را دوباره اجرا کنید"
},
"model": {
"title": "تنظیمات مدل",
Expand Down
15 changes: 14 additions & 1 deletion src/renderer/src/i18n/fr-FR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,20 @@
"importing": "Importation en cours...",
"confirmImport": "Confirmer l'importation",
"importSuccessTitle": "Importation réussie",
"importErrorTitle": "Échec de l'importation"
"importErrorTitle": "Échec de l'importation",
"resetData": "Réinitialiser les données",
"resetConfirmTitle": "Confirmer la réinitialisation des données",
"resetConfirmDescription": "Veuillez sélectionner le type de données à réinitialiser. Cette opération est irréversible et l'application redémarrera automatiquement après la réinitialisation.",
"resetChatData": "Réinitialiser les données de chat",
"resetChatDataDesc": "Supprimer tout l'historique des conversations et les enregistrements de dialogue",
"resetConfig": "Réinitialiser la configuration",
"resetConfigDesc": "Supprimer tous les paramètres de l'application, les configurations de modèles et les prompts personnalisés",
"resetAll": "Réinitialisation complète",
"resetAllDesc": "Supprimer toutes les données, y compris l'historique des conversations, les configurations et les fichiers de cache",
"resetting": "Réinitialisation en cours...",
"confirmReset": "Confirmer la réinitialisation",
"resetCompleteDevTitle": "Réinitialisation des données terminée",
"resetCompleteDevMessage": "En environnement de développement, veuillez redémarrer l'application manuellement. Arrêtez le processus actuel et exécutez à nouveau pnpm run dev"
},
"model": {
"title": "Paramètres du modèle",
Expand Down
Loading