mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 09:40:30 +08:00
fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback. - Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper. - Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
This commit is contained in:
@@ -141,9 +141,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { inject, ref } from 'vue';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import type { Session } from '@/composables/useSessions';
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog';
|
||||
import LanguageSwitcher from '@/components/shared/LanguageSwitcher.vue';
|
||||
import StyledMenu from '@/components/shared/StyledMenu.vue';
|
||||
import ProviderConfigDialog from '@/components/chat/ProviderConfigDialog.vue';
|
||||
@@ -183,6 +184,8 @@ const emit = defineEmits<{
|
||||
const { t } = useI18n();
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined);
|
||||
|
||||
const sidebarCollapsed = ref(true);
|
||||
const showProviderConfigDialog = ref(false);
|
||||
|
||||
@@ -199,10 +202,10 @@ function toggleSidebar() {
|
||||
localStorage.setItem('sidebarCollapsed', JSON.stringify(sidebarCollapsed.value));
|
||||
}
|
||||
|
||||
function handleDeleteConversation(session: Session) {
|
||||
async function handleDeleteConversation(session: Session) {
|
||||
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
||||
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
||||
if (window.confirm(message)) {
|
||||
if (await askForConfirmation(message, confirmDialog)) {
|
||||
emit('deleteConversation', session.session_id);
|
||||
}
|
||||
}
|
||||
@@ -359,4 +362,3 @@ function handleDeleteConversation(session: Session) {
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -42,8 +42,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { inject, ref } from 'vue';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog';
|
||||
|
||||
export interface Project {
|
||||
project_id: string;
|
||||
@@ -72,6 +73,8 @@ const emit = defineEmits<{
|
||||
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined);
|
||||
|
||||
const expanded = ref(props.initialExpanded);
|
||||
|
||||
// 从 localStorage 读取项目展开状态
|
||||
@@ -85,9 +88,9 @@ function toggleExpanded() {
|
||||
localStorage.setItem('projectsExpanded', JSON.stringify(expanded.value));
|
||||
}
|
||||
|
||||
function handleDeleteProject(project: Project) {
|
||||
async function handleDeleteProject(project: Project) {
|
||||
const message = tm('project.confirmDelete', { title: project.title });
|
||||
if (window.confirm(message)) {
|
||||
if (await askForConfirmation(message, confirmDialog)) {
|
||||
emit('deleteProject', project.project_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import type { Project } from '@/components/chat/ProjectList.vue';
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog';
|
||||
|
||||
interface Session {
|
||||
session_id: string;
|
||||
@@ -69,14 +71,16 @@ const emit = defineEmits<{
|
||||
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined);
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
return new Date(dateString).toLocaleString();
|
||||
}
|
||||
|
||||
function handleDeleteSession(session: Session) {
|
||||
async function handleDeleteSession(session: Session) {
|
||||
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
||||
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
||||
if (window.confirm(message)) {
|
||||
if (await askForConfirmation(message, confirmDialog)) {
|
||||
emit('deleteSession', session.session_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,7 @@ import axios from 'axios';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
|
||||
import ItemCard from '@/components/shared/ItemCard.vue';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'McpServersSection',
|
||||
@@ -382,18 +383,21 @@ export default {
|
||||
this.showError(this.tm('dialogs.addServer.errors.jsonParse', { error: e.message }));
|
||||
}
|
||||
},
|
||||
deleteServer(server) {
|
||||
async deleteServer(server) {
|
||||
const serverName = server.name || server;
|
||||
if (confirm(this.tm('dialogs.confirmDelete', { name: serverName }))) {
|
||||
axios.post('/api/tools/mcp/delete', { name: serverName })
|
||||
.then(response => {
|
||||
this.getServers();
|
||||
this.showSuccess(response.data.message || this.tm('messages.deleteSuccess'));
|
||||
})
|
||||
.catch(error => {
|
||||
this.showError(this.tm('messages.deleteError', { error: error.response?.data?.message || error.message }));
|
||||
});
|
||||
const message = this.tm('dialogs.confirmDelete', { name: serverName });
|
||||
if (!(await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.post('/api/tools/mcp/delete', { name: serverName })
|
||||
.then(response => {
|
||||
this.getServers();
|
||||
this.showSuccess(response.data.message || this.tm('messages.deleteSuccess'));
|
||||
})
|
||||
.catch(error => {
|
||||
this.showError(this.tm('messages.deleteError', { error: error.response?.data?.message || error.message }));
|
||||
});
|
||||
},
|
||||
editServer(server) {
|
||||
const configCopy = { ...server };
|
||||
|
||||
@@ -367,13 +367,16 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, inject, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { useI18n } from '@/i18n/composables'
|
||||
import { askForConfirmation, resolveConfirmDialog } from '@/utils/confirmDialog'
|
||||
import WaitingForRestart from './WaitingForRestart.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const confirmDialog = resolveConfirmDialog(inject('$confirm', undefined))
|
||||
|
||||
const isOpen = ref(false)
|
||||
const activeTab = ref('export')
|
||||
const wfr = ref(null)
|
||||
@@ -844,7 +847,7 @@ const restoreFromList = async (filename) => {
|
||||
|
||||
// 删除备份
|
||||
const deleteBackup = async (filename) => {
|
||||
if (!confirm(t('features.settings.backup.list.confirmDelete'))) return
|
||||
if (!(await askForConfirmation(t('features.settings.backup.list.confirmDelete'), confirmDialog))) return
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/backup/delete', { filename })
|
||||
@@ -992,4 +995,4 @@ defineExpose({ open })
|
||||
.non-interactive-chip:hover {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -307,6 +307,7 @@
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'PersonaForm',
|
||||
@@ -596,8 +597,8 @@ export default {
|
||||
|
||||
async deletePersona() {
|
||||
if (!this.editingPersona) return;
|
||||
|
||||
if (!confirm(this.tm('messages.deleteConfirm', { id: this.editingPersona.persona_id }))) {
|
||||
|
||||
if (!(await askForConfirmationDialog(this.tm('messages.deleteConfirm', { id: this.editingPersona.persona_id }), resolveConfirmDialog(this.$confirm)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ref, computed, inject, onMounted, nextTick, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { getProviderIcon } from '@/utils/providerUtils'
|
||||
import { askForConfirmation as askForConfirmationDialog, type ConfirmDialogHandler } from '@/utils/confirmDialog'
|
||||
|
||||
export interface UseProviderSourcesOptions {
|
||||
defaultTab?: string
|
||||
@@ -37,25 +38,10 @@ export function resolveDefaultTab(value?: string) {
|
||||
export function useProviderSources(options: UseProviderSourcesOptions) {
|
||||
const { tm, showMessage } = options
|
||||
|
||||
type ConfirmDialogOptions = {
|
||||
title?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
type ConfirmDialogHandler = (options: ConfirmDialogOptions) => Promise<boolean>
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined)
|
||||
|
||||
async function askForConfirmation(message: string) {
|
||||
if (confirmDialog) {
|
||||
try {
|
||||
return await confirmDialog({ message })
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return window.confirm(message)
|
||||
return askForConfirmationDialog(message, confirmDialog)
|
||||
}
|
||||
|
||||
// ===== State =====
|
||||
|
||||
29
dashboard/src/utils/confirmDialog.ts
Normal file
29
dashboard/src/utils/confirmDialog.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type ConfirmDialogOptions = {
|
||||
title?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type ConfirmDialogHandler = (options: ConfirmDialogOptions) => Promise<boolean>
|
||||
|
||||
export function resolveConfirmDialog(candidate: unknown): ConfirmDialogHandler | undefined {
|
||||
if (typeof candidate === 'function') {
|
||||
return candidate as ConfirmDialogHandler
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export async function askForConfirmation(
|
||||
message: string,
|
||||
confirmDialog?: ConfirmDialogHandler
|
||||
): Promise<boolean> {
|
||||
if (confirmDialog) {
|
||||
try {
|
||||
return await confirmDialog({ message })
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return window.confirm(message)
|
||||
}
|
||||
@@ -190,6 +190,7 @@ import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
import StandaloneChat from '@/components/chat/StandaloneChat.vue';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'ConfigPage',
|
||||
@@ -473,8 +474,9 @@ export default {
|
||||
this.createNewConfig();
|
||||
}
|
||||
},
|
||||
confirmDeleteConfig(config) {
|
||||
if (confirm(this.tm('configManagement.confirmDelete').replace('{name}', config.name))) {
|
||||
async confirmDeleteConfig(config) {
|
||||
const message = this.tm('configManagement.confirmDelete').replace('{name}', config.name);
|
||||
if (await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm))) {
|
||||
this.deleteConfig(config.id);
|
||||
}
|
||||
},
|
||||
@@ -658,4 +660,4 @@ export default {
|
||||
padding: 0;
|
||||
border-radius: 0 0 16px 16px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -333,6 +333,7 @@ import { useCommonStore } from '@/stores/common';
|
||||
import { useCustomizerStore } from '@/stores/customizer';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import MessageList from '@/components/chat/MessageList.vue';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'ConversationPage',
|
||||
@@ -744,9 +745,9 @@ export default {
|
||||
},
|
||||
|
||||
// 关闭对话历史对话框
|
||||
closeHistoryDialog() {
|
||||
async closeHistoryDialog() {
|
||||
if (this.isEditingHistory) {
|
||||
if (confirm(this.tm('dialogs.view.confirmClose'))) {
|
||||
if (await askForConfirmationDialog(this.tm('dialogs.view.confirmClose'), resolveConfirmDialog(this.$confirm))) {
|
||||
this.dialogView = false;
|
||||
}
|
||||
} else {
|
||||
@@ -1133,4 +1134,4 @@ export default {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -197,6 +197,7 @@ import AddNewPlatform from '@/components/platform/AddNewPlatform.vue';
|
||||
import { useCommonStore } from '@/stores/common';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import { getPlatformIcon, getTutorialLink } from '@/utils/platformUtils';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
export default {
|
||||
name: 'PlatformPage',
|
||||
@@ -451,15 +452,18 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
deletePlatform(platform) {
|
||||
if (confirm(`${this.messages.deleteConfirm} ${platform.id}?`)) {
|
||||
axios.post('/api/config/platform/delete', { id: platform.id }).then((res) => {
|
||||
this.getConfig();
|
||||
this.showSuccess(res.data.message || this.messages.deleteSuccess);
|
||||
}).catch((err) => {
|
||||
this.showError(err.response?.data?.message || err.message);
|
||||
});
|
||||
async deletePlatform(platform) {
|
||||
const message = `${this.messages.deleteConfirm} ${platform.id}?`;
|
||||
if (!(await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.post('/api/config/platform/delete', { id: platform.id }).then((res) => {
|
||||
this.getConfig();
|
||||
this.showSuccess(res.data.message || this.messages.deleteSuccess);
|
||||
}).catch((err) => {
|
||||
this.showError(err.response?.data?.message || err.message);
|
||||
});
|
||||
},
|
||||
|
||||
platformStatusChange(platform) {
|
||||
|
||||
@@ -522,6 +522,7 @@
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables'
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog'
|
||||
|
||||
export default {
|
||||
name: 'SessionManagementPage',
|
||||
@@ -1503,7 +1504,8 @@ export default {
|
||||
},
|
||||
|
||||
async deleteGroup(group) {
|
||||
if (!confirm(`确定要删除分组 "${group.name}" 吗?`)) return
|
||||
const message = `确定要删除分组 "${group.name}" 吗?`
|
||||
if (!(await askForConfirmationDialog(message, resolveConfirmDialog(this.$confirm)))) return
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/session/group/delete', { id: group.id })
|
||||
|
||||
@@ -240,14 +240,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, inject, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { useModuleI18n } from '@/i18n/composables'
|
||||
import { askForConfirmation, type ConfirmDialogHandler } from '@/utils/confirmDialog'
|
||||
|
||||
const { tm: t } = useModuleI18n('features/knowledge-base/document')
|
||||
const route = useRoute()
|
||||
|
||||
const confirmDialog = inject<ConfirmDialogHandler | undefined>('$confirm', undefined)
|
||||
|
||||
const kbId = ref(route.params.kbId as string)
|
||||
const docId = ref(route.params.docId as string)
|
||||
|
||||
@@ -356,7 +359,7 @@ const viewChunk = (chunk: any) => {
|
||||
|
||||
// 删除分块
|
||||
const deleteChunk = async (chunk: any) => {
|
||||
if (!confirm(t('chunks.deleteConfirm'))) return
|
||||
if (!(await askForConfirmation(t('chunks.deleteConfirm'), confirmDialog))) return
|
||||
try {
|
||||
const response = await axios.post('/api/kb/chunk/delete', {
|
||||
chunk_id: chunk.chunk_id,
|
||||
|
||||
@@ -260,6 +260,7 @@ import PersonaCard from './PersonaCard.vue';
|
||||
import PersonaForm from '@/components/shared/PersonaForm.vue';
|
||||
import CreateFolderDialog from './CreateFolderDialog.vue';
|
||||
import MoveToFolderDialog from './MoveToFolderDialog.vue';
|
||||
import { askForConfirmation as askForConfirmationDialog, resolveConfirmDialog } from '@/utils/confirmDialog';
|
||||
|
||||
import type { Folder, FolderTreeNode } from '@/components/folder/types';
|
||||
|
||||
@@ -420,7 +421,8 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
async confirmDeletePersona(persona: Persona) {
|
||||
if (!confirm(this.tm('messages.deleteConfirm', { id: persona.persona_id }))) {
|
||||
const confirmDialog = resolveConfirmDialog((this as any).$confirm);
|
||||
if (!(await askForConfirmationDialog(this.tm('messages.deleteConfirm', { id: persona.persona_id }), confirmDialog))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user