refactor(ui): swap ElMessage for project-native mcToast, finish mcConfirm migration

This commit is contained in:
matevip 2026-05-15 10:19:24 +08:00
parent 15ee205ab9
commit e10dd9d0a0
50 changed files with 609 additions and 315 deletions

View File

@ -70,7 +70,7 @@
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { authApi } from '@/api/index'
const props = defineProps<{ visible: boolean }>()
@ -98,21 +98,21 @@ function close() {
async function handleSubmit() {
if (!form.oldPassword || !form.newPassword) {
ElMessage.warning(t('auth.fieldsRequired'))
mcToast.warning(t('auth.fieldsRequired'))
return
}
if (form.newPassword !== form.confirmPassword) {
ElMessage.warning(t('auth.passwordMismatch'))
mcToast.warning(t('auth.passwordMismatch'))
return
}
submitting.value = true
try {
const userId = Number(localStorage.getItem('userId') || '1')
await authApi.changePassword(userId, form.oldPassword, form.newPassword)
ElMessage.success(t('auth.passwordChanged'))
mcToast.success(t('auth.passwordChanged'))
close()
} catch (error: any) {
ElMessage.error(error?.msg || error?.message || t('auth.passwordChangeFailed'))
mcToast.error(error?.msg || error?.message || t('auth.passwordChangeFailed'))
} finally {
submitting.value = false
}

View File

@ -468,7 +468,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { CHANNEL_FIELD_DEFS } from '@/types'
import { copyToClipboard } from '@/utils/clipboard'
import type { Agent, Channel, ChannelFieldDef } from '@/types'
@ -695,16 +695,16 @@ async function copyWebhookUrl() {
copyLabel.value = t('channels.webhook.copied')
setTimeout(() => { copyLabel.value = t('channels.webhook.copy') }, 2000)
} catch {
ElMessage.warning(t('channels.webhook.copyFailed'))
mcToast.warning(t('channels.webhook.copyFailed'))
}
}
async function copyText(text: string) {
try {
await copyToClipboard(text)
ElMessage.success(t('common.copied'))
mcToast.success(t('common.copied'))
} catch {
ElMessage.warning(t('channels.webhook.copyFailed'))
mcToast.warning(t('channels.webhook.copyFailed'))
}
}
@ -840,7 +840,7 @@ function save() {
if (rawConfigJson.value.trim()) {
try { JSON.parse(rawConfigJson.value) }
catch {
ElMessage.error(t('channels.messages.invalidJson'))
mcToast.error(t('channels.messages.invalidJson'))
return
}
}

View File

@ -311,7 +311,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { CHANNEL_FIELD_DEFS } from '@/types'
import type { Agent, Channel, ChannelFieldDef } from '@/types'
import { channelApi } from '@/api'
@ -713,11 +713,11 @@ async function onDone() {
enabled: true,
}
const res: any = await channelApi.create(payload)
ElMessage.success(t('channels.messages.saveSuccess'))
mcToast.success(t('channels.messages.saveSuccess'))
emit('created', res.data as Channel)
close()
} catch (e: any) {
ElMessage.error(e?.message || t('channels.wizard.saveFailed'))
mcToast.error(e?.message || t('channels.wizard.saveFailed'))
} finally {
saving.value = false
}

View File

@ -407,7 +407,7 @@
<script setup lang="ts">
import { computed, ref, watch, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import {
ArrowDown,
CloseBold,
@ -1062,10 +1062,10 @@ function handleFeedbackAction(action: string) {
`Timestamp: ${new Date(feedbackInfo.value?.timestamp || Date.now()).toISOString()}`,
].join('\n')
copyToClipboard(lines).then(() => {
ElMessage.success(t('chat.feedback.reportCopied'))
mcToast.success(t('chat.feedback.reportCopied'))
}).catch(() => {
console.error('[feedback_event] copy failed:\n' + lines)
ElMessage.error(t('chat.feedback.reportFailed'))
mcToast.error(t('chat.feedback.reportFailed'))
})
}
}

View File

@ -53,7 +53,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { CloseBold, Loading, Microphone, Service } from '@element-plus/icons-vue'
import { WavRecorder } from '@/utils/wavEncoder'
@ -186,7 +186,7 @@ function connectWebSocket() {
playAudioUrl(data.url)
break
case 'error':
ElMessage.error(data.message || t('talk.connectionError'))
mcToast.error(data.message || t('talk.connectionError'))
state.value = 'idle'
break
}
@ -199,7 +199,7 @@ function connectWebSocket() {
console.warn('[TalkMode] WS error', e)
if (state.value === 'connecting') {
state.value = 'failed'
ElMessage.error(t('talk.connectionError'))
mcToast.error(t('talk.connectionError'))
}
}
@ -260,7 +260,7 @@ async function startListening() {
console.debug('[TalkMode] listening started')
} catch (err) {
console.warn('[TalkMode] startListening failed', err)
ElMessage.error(t('talk.micError'))
mcToast.error(t('talk.micError'))
state.value = 'idle'
recorder = null
}
@ -285,7 +285,7 @@ async function stopListening() {
// firing (suspended AudioContext) or the recording was so short no
// sample buffer landed. Surface a clear hint instead of a silent idle.
console.warn('[TalkMode] stop returned no audio (recording too short or context suspended)')
ElMessage.warning(t('talk.tooShort') || '录音过短,请按住按钮多说几秒')
mcToast.warning(t('talk.tooShort') || '录音过短,请按住按钮多说几秒')
state.value = 'idle'
return
}
@ -302,7 +302,7 @@ async function stopListening() {
// dropped before we got here. Tell the user instead of silently going
// idle they'd otherwise blame the mic.
console.warn('[TalkMode] WS not open at stop time, readyState=', ws?.readyState)
ElMessage.error(t('talk.connectionError'))
mcToast.error(t('talk.connectionError'))
state.value = 'idle'
}
}
@ -323,7 +323,7 @@ async function playAudio(blob: Blob) {
source.start(0)
state.value = 'speaking'
} catch {
ElMessage.warning(t('talk.playbackError'))
mcToast.warning(t('talk.playbackError'))
state.value = 'idle'
}
}

View File

@ -0,0 +1,138 @@
<template>
<div class="mc-toast" :class="`mc-toast--${type}`" role="status" aria-live="polite">
<span class="mc-toast__indicator">
<svg
v-if="type === 'success'"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
<svg
v-else-if="type === 'error'"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
<svg
v-else-if="type === 'warning'"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.6"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<circle cx="12" cy="17" r="0.6" />
</svg>
<svg
v-else
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.6"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="9" />
<line x1="12" y1="11" x2="12" y2="16" />
<circle cx="12" cy="8" r="0.6" />
</svg>
</span>
<span class="mc-toast__message">{{ message }}</span>
</div>
</template>
<script setup lang="ts">
// Single frosted toast row. Layout, animation, and stacking live in
// useMcToast this component just renders one row with the right
// status accent.
defineProps<{
type: 'success' | 'error' | 'warning' | 'info'
message: string
}>()
</script>
<style scoped>
.mc-toast {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 16px 10px 12px;
border-radius: 14px;
background: rgba(255, 250, 245, 0.82);
backdrop-filter: blur(28px) saturate(180%);
-webkit-backdrop-filter: blur(28px) saturate(180%);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.6) inset,
0 8px 28px rgba(25, 14, 8, 0.18);
color: var(--mc-text-primary);
font-size: 13px;
font-weight: 500;
letter-spacing: -0.005em;
max-width: min(440px, calc(100vw - 32px));
pointer-events: auto;
}
:global(html.dark .mc-toast) {
background: rgba(32, 26, 22, 0.86);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.06) inset,
0 8px 28px rgba(0, 0, 0, 0.45);
}
.mc-toast__indicator {
flex-shrink: 0;
width: 22px;
height: 22px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
}
.mc-toast--success .mc-toast__indicator {
background: var(--mc-success);
box-shadow: 0 0 0 4px rgba(90, 138, 90, 0.16);
}
.mc-toast--error .mc-toast__indicator {
background: var(--mc-danger);
box-shadow: 0 0 0 4px rgba(200, 60, 60, 0.16);
}
.mc-toast--warning .mc-toast__indicator {
background: var(--mc-primary);
box-shadow: 0 0 0 4px rgba(217, 119, 87, 0.18);
}
.mc-toast--info .mc-toast__indicator {
background: var(--mc-text-tertiary);
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.06);
}
:global(html.dark .mc-toast--info .mc-toast__indicator) {
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.06);
}
.mc-toast__message {
line-height: 1.5;
word-break: break-word;
}
</style>

View File

@ -149,7 +149,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { skillInstallApi } from '@/api/index'
import type { InstallTask, HubSkillInfo } from '@/types/index'
@ -215,7 +215,7 @@ async function doInstall(bundleUrl: string) {
currentTask.value = res.data
startPolling(res.data.taskId)
} catch (e: any) {
ElMessage.error(e?.message || t('skills.import.failed'))
mcToast.error(e?.message || t('skills.import.failed'))
installing.value = false
}
}
@ -231,7 +231,7 @@ function startPolling(taskId: string) {
stopPolling()
installing.value = false
if (status === 'COMPLETED') {
ElMessage.success(t('skills.import.installed'))
mcToast.success(t('skills.import.installed'))
// The install task's result envelope carries `{name, enabled, sourceUrl, ...}`.
// Hand the slug along so the parent can pop preflight for the
// freshly-installed skill if its requirements aren't met.
@ -256,7 +256,7 @@ async function cancelInstall() {
if (!currentTask.value) return
try {
await skillInstallApi.cancelInstall(currentTask.value.taskId)
ElMessage.info(t('skills.import.cancelled'))
mcToast.info(t('skills.import.cancelled'))
} catch {
// ignore
}
@ -272,7 +272,7 @@ function handleFileSelect(e: Event) {
if (file && file.name.endsWith('.zip')) {
zipFile.value = file
} else if (file) {
ElMessage.warning(t('skills.import.invalidZip'))
mcToast.warning(t('skills.import.invalidZip'))
}
input.value = ''
}
@ -283,7 +283,7 @@ function handleDrop(e: DragEvent) {
if (file && file.name.endsWith('.zip')) {
zipFile.value = file
} else {
ElMessage.warning(t('skills.import.invalidZip'))
mcToast.warning(t('skills.import.invalidZip'))
}
}
@ -296,7 +296,7 @@ function formatSize(bytes: number): string {
async function uploadZip() {
if (!zipFile.value) return
if (zipFile.value.size > 50 * 1024 * 1024) {
ElMessage.error(t('skills.import.tooLarge'))
mcToast.error(t('skills.import.tooLarge'))
return
}
installing.value = true
@ -305,11 +305,11 @@ async function uploadZip() {
enable: enableAfterInstall.value,
overwrite: overwriteExisting.value,
})
ElMessage.success(t('skills.import.uploadSuccess'))
mcToast.success(t('skills.import.uploadSuccess'))
zipFile.value = null
emit('installed', { name: res?.data?.name })
} catch (e: any) {
ElMessage.error(e?.response?.data?.msg || e?.message || t('skills.import.uploadFailed'))
mcToast.error(e?.response?.data?.msg || e?.message || t('skills.import.uploadFailed'))
} finally {
installing.value = false
}
@ -325,7 +325,7 @@ async function doSearch() {
searchResults.value = res.data || []
searchDone.value = true
} catch (e: any) {
ElMessage.error(e?.message || t('skills.import.searchFailed'))
mcToast.error(e?.message || t('skills.import.searchFailed'))
searchResults.value = []
searchDone.value = true
} finally {

View File

@ -77,7 +77,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { skillApi } from '@/api/index'
import { copyToClipboard } from '@/utils/clipboard'
@ -133,7 +133,7 @@ async function reload() {
featureStatuses.value = data.featureStatuses || {}
activeFeatures.value = Array.isArray(data.activeFeatures) ? data.activeFeatures : []
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.preflight.loadFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.preflight.loadFailed'))
statuses.value = []
allMet.value = false
} finally {
@ -148,9 +148,9 @@ function handleClose() {
async function copy(cmd: string) {
try {
await copyToClipboard(cmd)
ElMessage.success(t('common.copied'))
mcToast.success(t('common.copied'))
} catch {
ElMessage.warning(t('common.copyFailed'))
mcToast.warning(t('common.copyFailed'))
}
}

View File

@ -102,7 +102,8 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { skillApi, type SkillSecretSummary } from '@/api'
const props = defineProps<{
@ -140,7 +141,7 @@ async function load() {
rows.value = (res?.data ?? []) as SkillSecretSummary[]
} catch (e: any) {
rows.value = []
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.detail.secretLoadFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.detail.secretLoadFailed'))
} finally {
loading.value = false
}
@ -173,11 +174,11 @@ async function save() {
saving.value = true
try {
await skillApi.putSecret(props.skillId, form.value.key.trim(), form.value.value)
ElMessage.success(t('skills.detail.secretSaveSuccess'))
mcToast.success(t('skills.detail.secretSaveSuccess'))
dialogVisible.value = false
await load()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.detail.secretSaveFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.detail.secretSaveFailed'))
} finally {
saving.value = false
}
@ -185,21 +186,18 @@ async function save() {
async function removeSecret(key: string) {
if (props.skillId == null) return
try {
await ElMessageBox.confirm(
t('skills.detail.secretDeleteConfirm', { key }),
t('common.confirm'),
{ type: 'warning' },
)
} catch {
return // user cancelled
}
const ok = await mcConfirm({
title: t('common.confirm'),
message: t('skills.detail.secretDeleteConfirm', { key }),
tone: 'danger',
})
if (!ok) return
try {
await skillApi.deleteSecret(props.skillId, key)
ElMessage.success(t('skills.detail.secretDeleteSuccess'))
mcToast.success(t('skills.detail.secretDeleteSuccess'))
await load()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.detail.secretDeleteFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.detail.secretDeleteFailed'))
}
}

View File

@ -111,7 +111,7 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import {
workflowApi,
type GeneratedDraft,
@ -272,7 +272,7 @@ async function onGenerate() {
const res = await workflowApi.generateDraft(desc)
result.value = res.data as unknown as GeneratedDraft
} catch (e) {
ElMessage.error(t('workflows.generate.failed', { msg: (e as Error).message }))
mcToast.error(t('workflows.generate.failed', { msg: (e as Error).message }))
} finally {
loading.value = false
}

View File

@ -1,6 +1,6 @@
import { ref, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { channelApi } from '@/api'
export type DingTalkRegisterStatus = '' | 'waiting' | 'confirmed' | 'expired' | 'denied'
@ -52,13 +52,13 @@ export function useDingTalkAppRegister(onConfirmed: (r: DingTalkRegisterResult)
sessionId = res?.data?.session_id || res?.session_id || ''
if (!sessionId) {
loading.value = false
ElMessage.error(t('channels.dingtalkRegister.startFailed'))
mcToast.error(t('channels.dingtalkRegister.startFailed'))
return
}
status.value = 'waiting'
} catch {
loading.value = false
ElMessage.error(t('channels.dingtalkRegister.startFailed'))
mcToast.error(t('channels.dingtalkRegister.startFailed'))
return
}
@ -87,7 +87,7 @@ export function useDingTalkAppRegister(onConfirmed: (r: DingTalkRegisterResult)
const clientSecret = data.client_secret || ''
if (clientId && clientSecret) {
onConfirmed({ clientId, clientSecret })
ElMessage.success(t('channels.dingtalkRegister.confirmed'))
mcToast.success(t('channels.dingtalkRegister.confirmed'))
}
return
}
@ -95,11 +95,11 @@ export function useDingTalkAppRegister(onConfirmed: (r: DingTalkRegisterResult)
if (s === 'expired') {
stopPolling()
loading.value = false
ElMessage.warning(t('channels.dingtalkRegister.expired'))
mcToast.warning(t('channels.dingtalkRegister.expired'))
} else if (s === 'denied') {
stopPolling()
loading.value = false
ElMessage.warning(t('channels.dingtalkRegister.denied'))
mcToast.warning(t('channels.dingtalkRegister.denied'))
}
} catch {
// Silent — transient network errors should not abort the loop.

View File

@ -1,6 +1,6 @@
import { ref, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { channelApi } from '@/api'
export type FeishuRegisterStatus = '' | 'pending' | 'waiting' | 'confirmed' | 'expired' | 'denied' | 'error'
@ -52,13 +52,13 @@ export function useFeishuAppRegister(onConfirmed: (r: FeishuRegisterResult) => v
sessionId = res?.data?.session_id || res?.session_id || ''
if (!sessionId) {
loading.value = false
ElMessage.error(t('channels.feishuRegister.startFailed'))
mcToast.error(t('channels.feishuRegister.startFailed'))
return
}
status.value = 'pending'
} catch {
loading.value = false
ElMessage.error(t('channels.feishuRegister.startFailed'))
mcToast.error(t('channels.feishuRegister.startFailed'))
return
}
@ -91,7 +91,7 @@ export function useFeishuAppRegister(onConfirmed: (r: FeishuRegisterResult) => v
const appSecret = data.client_secret || ''
if (appId && appSecret) {
onConfirmed({ appId, appSecret })
ElMessage.success(t('channels.feishuRegister.confirmed'))
mcToast.success(t('channels.feishuRegister.confirmed'))
}
return
}
@ -99,15 +99,15 @@ export function useFeishuAppRegister(onConfirmed: (r: FeishuRegisterResult) => v
if (s === 'expired') {
stopPolling()
loading.value = false
ElMessage.warning(t('channels.feishuRegister.expired'))
mcToast.warning(t('channels.feishuRegister.expired'))
} else if (s === 'denied') {
stopPolling()
loading.value = false
ElMessage.warning(t('channels.feishuRegister.denied'))
mcToast.warning(t('channels.feishuRegister.denied'))
} else if (s === 'error') {
stopPolling()
loading.value = false
ElMessage.error(t('channels.feishuRegister.error'))
mcToast.error(t('channels.feishuRegister.error'))
}
} catch {
// Silent — transient network errors should not abort the loop.

View File

@ -1,6 +1,6 @@
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
const SDK_URL = 'https://wwcdn.weixin.qq.com/node/wework/js/wecom-aibot-sdk@0.1.0.min.js'
const SOURCE = 'mateclaw'
@ -48,14 +48,14 @@ export function useWecomBotAuth(onSuccess: (r: WecomBotAuthResult) => void) {
try {
await loadSDK()
} catch {
ElMessage.error(t('channels.wecom.sdkFailed'))
mcToast.error(t('channels.wecom.sdkFailed'))
loading.value = false
return
}
const sdk = (window as any).WecomAIBotSDK
if (!sdk) {
ElMessage.error(t('channels.wecom.sdkFailed'))
mcToast.error(t('channels.wecom.sdkFailed'))
loading.value = false
return
}
@ -68,16 +68,16 @@ export function useWecomBotAuth(onSuccess: (r: WecomBotAuthResult) => void) {
(bot: WecomBotAuthResult) => {
if (bot?.botid) {
onSuccess(bot)
ElMessage.success(t('channels.wecom.authSuccess'))
mcToast.success(t('channels.wecom.authSuccess'))
}
},
(error: { code: string; message: string }) => {
if (error?.code === 'WINDOW_BLOCKED') {
ElMessage.error(t('channels.wecom.windowBlocked'))
mcToast.error(t('channels.wecom.windowBlocked'))
} else if (error?.code === 'CANCELLED') {
ElMessage.info(t('channels.wecom.authCancelled'))
mcToast.info(t('channels.wecom.authCancelled'))
} else {
ElMessage.error(t('channels.wecom.authFailed') + '' + (error?.message || error?.code || ''))
mcToast.error(t('channels.wecom.authFailed') + '' + (error?.message || error?.code || ''))
}
},
)

View File

@ -1,6 +1,6 @@
import { ref, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { channelApi } from '@/api'
export type WeixinPollStatus = '' | 'polling' | 'scanned' | 'confirmed' | 'expired'
@ -52,7 +52,7 @@ export function useWeixinQrcodePoll(onConfirmed: (r: WeixinQrcodeResult) => void
const qrcodeId = data?.qrcode || ''
if (!imgContent && !qrcodeId) {
ElMessage.error(t('channels.weixin.qrcodeFailed'))
mcToast.error(t('channels.weixin.qrcodeFailed'))
return
}
@ -81,21 +81,21 @@ export function useWeixinQrcodePoll(onConfirmed: (r: WeixinQrcodeResult) => void
qrcodeImg.value = ''
pollStatus.value = 'confirmed'
onConfirmed({ botToken: s.bot_token, baseUrl: s.base_url })
ElMessage.success(t('channels.weixin.loginSuccess'))
mcToast.success(t('channels.weixin.loginSuccess'))
}
if (status === 'expired') {
stopPolling()
qrcodeImg.value = ''
pollStatus.value = 'expired'
ElMessage.warning(t('channels.weixin.qrcodeExpired'))
mcToast.warning(t('channels.weixin.qrcodeExpired'))
}
} catch {
// Silent — transient network errors should not abort the loop.
}
}, 2000)
} catch {
ElMessage.error(t('channels.weixin.qrcodeFailed'))
mcToast.error(t('channels.weixin.qrcodeFailed'))
} finally {
loading.value = false
}

View File

@ -0,0 +1,128 @@
import { createApp, h, reactive, TransitionGroup } from 'vue'
import McToast from '@/components/common/McToast.vue'
// Project-native toast — frosted glass, spring slide-in from the top,
// status-colored indicator (check / x / triangle / circle), auto
// dismiss with longer hold for failures so the user has time to read.
//
// Drop-in replacement for ElMessage's success/error/warning/info calls.
// The host element + Vue subtree are mounted lazily on first use so
// nothing renders until a toast is actually fired.
export type McToastType = 'success' | 'error' | 'warning' | 'info'
export interface McToastOptions {
/** Override auto-dismiss in ms. Default: 3000 (success/info), 4000 (warning), 4500 (error). */
duration?: number
}
interface ToastEntry {
id: number
type: McToastType
message: string
}
const toasts = reactive<ToastEntry[]>([])
let nextId = 1
let mounted = false
function defaultDuration(type: McToastType): number {
if (type === 'error') return 4500
if (type === 'warning') return 4000
return 3000
}
function ensureHost(): void {
if (mounted || typeof document === 'undefined') return
// HMR safety: if a prior module instance left a host behind, drop it
// so this module's reactive array is what renders.
document.querySelector('.mc-toast-host')?.remove()
mounted = true
const host = document.createElement('div')
host.className = 'mc-toast-host'
document.body.appendChild(host)
// Inject host CSS once — kept here (not scoped to McToast) because the
// stack itself is a global overlay sibling of the app root, and the
// enter/leave transition names need to match TransitionGroup's class
// contract.
if (!document.querySelector('#mc-toast-host-style')) {
const style = document.createElement('style')
style.id = 'mc-toast-host-style'
style.textContent = `
.mc-toast-host {
position: fixed;
top: 18px;
left: 50%;
transform: translateX(-50%);
z-index: 3000;
pointer-events: none;
}
.mc-toast-stack {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.mc-toast-enter-active,
.mc-toast-leave-active {
transition:
transform 0.32s cubic-bezier(0.32, 0.72, 0, 1),
opacity 0.22s ease;
}
.mc-toast-enter-from {
opacity: 0;
transform: translateY(-12px);
}
.mc-toast-leave-to {
opacity: 0;
transform: translateY(-6px);
}
.mc-toast-move {
transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
}
`
document.head.appendChild(style)
}
createApp({
render: () =>
h(
TransitionGroup as unknown as any,
{ name: 'mc-toast', tag: 'div', class: 'mc-toast-stack' },
() =>
toasts.map((t) =>
h(McToast, { key: t.id, type: t.type, message: t.message }),
),
),
}).mount(host)
}
function remove(id: number): void {
const idx = toasts.findIndex((t) => t.id === id)
if (idx >= 0) toasts.splice(idx, 1)
}
function show(type: McToastType, message: string, opts?: McToastOptions): void {
ensureHost()
const id = nextId++
toasts.push({ id, type, message })
const duration = opts?.duration ?? defaultDuration(type)
setTimeout(() => remove(id), duration)
}
export const mcToast = {
success(message: string, opts?: McToastOptions): void {
show('success', message, opts)
},
error(message: string, opts?: McToastOptions): void {
show('error', message, opts)
},
warning(message: string, opts?: McToastOptions): void {
show('warning', message, opts)
},
info(message: string, opts?: McToastOptions): void {
show('info', message, opts)
},
}

View File

@ -1,6 +1,6 @@
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcpApi } from '@/api/index'
import type { McpServer, McpServerForm, McpTestResult } from '@/views/mcp/types'
import { mcpCatalog, type McpCatalogEntry } from '@/views/mcp/catalog'
@ -81,7 +81,7 @@ export function useMcpServers() {
installed.value = (res?.data ?? []) as McpServer[]
}
} catch {
if (gen === loadGeneration) ElMessage.error(t('mcp.messages.loadFailed'))
if (gen === loadGeneration) mcToast.error(t('mcp.messages.loadFailed'))
} finally {
if (gen === loadGeneration) isLoading.value = false
}
@ -91,10 +91,10 @@ export function useMcpServers() {
isRefreshing.value = true
try {
await mcpApi.refresh()
ElMessage.success(t('mcp.messages.refreshSuccess'))
mcToast.success(t('mcp.messages.refreshSuccess'))
await reload()
} catch (e: any) {
ElMessage.error(e?.message || t('mcp.messages.saveFailed'))
mcToast.error(e?.message || t('mcp.messages.saveFailed'))
} finally {
isRefreshing.value = false
}
@ -104,15 +104,15 @@ export function useMcpServers() {
try {
if (editing) {
await mcpApi.update(editing.id, form)
ElMessage.success(t('mcp.messages.updateSuccess'))
mcToast.success(t('mcp.messages.updateSuccess'))
} else {
await mcpApi.create(form)
ElMessage.success(t('mcp.messages.createSuccess'))
mcToast.success(t('mcp.messages.createSuccess'))
}
await reload()
return true
} catch (e: any) {
ElMessage.error(e?.message || t('mcp.messages.saveFailed'))
mcToast.error(e?.message || t('mcp.messages.saveFailed'))
return false
}
}
@ -120,11 +120,11 @@ export function useMcpServers() {
async function removeServer(server: McpServer): Promise<boolean> {
try {
await mcpApi.delete(server.id)
ElMessage.success(t('mcp.messages.deleteSuccess'))
mcToast.success(t('mcp.messages.deleteSuccess'))
await reload()
return true
} catch (e: any) {
ElMessage.error(e?.message || t('mcp.messages.saveFailed'))
mcToast.error(e?.message || t('mcp.messages.saveFailed'))
return false
}
}
@ -132,10 +132,10 @@ export function useMcpServers() {
async function toggleServer(server: McpServer) {
try {
await mcpApi.toggle(server.id, !server.enabled)
ElMessage.success(t('mcp.messages.toggleSuccess'))
mcToast.success(t('mcp.messages.toggleSuccess'))
await reload()
} catch (e: any) {
ElMessage.error(e?.message || t('mcp.messages.saveFailed'))
mcToast.error(e?.message || t('mcp.messages.saveFailed'))
}
}

View File

@ -227,7 +227,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { acpApi } from '@/api/index'
@ -428,7 +428,7 @@ async function loadEndpoints() {
endpoints.value = res?.data || []
} catch (e: any) {
endpoints.value = []
ElMessage.error(typeof e === 'string' ? e : e?.message || t('acp.loadFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('acp.loadFailed'))
}
}
@ -486,7 +486,7 @@ async function saveEndpoint() {
if (form.argsJson) JSON.parse(form.argsJson)
if (form.envJson) JSON.parse(form.envJson)
} catch (e: any) {
ElMessage.error(t('acp.invalidJson') + ': ' + (e?.message || 'parse error'))
mcToast.error(t('acp.invalidJson') + ': ' + (e?.message || 'parse error'))
return
}
try {
@ -498,7 +498,7 @@ async function saveEndpoint() {
closeModal()
await loadEndpoints()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('acp.saveFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('acp.saveFailed'))
}
}
@ -513,7 +513,7 @@ async function removeEndpoint(ep: AcpEndpoint) {
await acpApi.delete(ep.id)
await loadEndpoints()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('acp.deleteFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('acp.deleteFailed'))
}
}
@ -522,7 +522,7 @@ async function toggle(ep: AcpEndpoint) {
await acpApi.toggle(ep.id, !ep.enabled)
await loadEndpoints()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('acp.toggleFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('acp.toggleFailed'))
}
}

View File

@ -205,7 +205,7 @@
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { agentApi, agentContextApi } from '@/api/index'
import { copyToClipboard } from '@/utils/clipboard'
@ -293,7 +293,7 @@ async function loadAgents() {
selectedAgentId.value = agents.value[0].id
}
} catch {
ElMessage.error(t('agentContext.loadFailed'))
mcToast.error(t('agentContext.loadFailed'))
}
}
@ -307,7 +307,7 @@ async function fetchFiles() {
const res: any = await agentContextApi.listFiles(selectedAgentId.value)
files.value = res.data || []
} catch {
ElMessage.error(t('agentContext.loadFailed'))
mcToast.error(t('agentContext.loadFailed'))
}
}
@ -352,7 +352,7 @@ async function onFileClick(file: WorkspaceFile) {
fileContent.value = data?.content || ''
originalContent.value = fileContent.value
} catch {
ElMessage.error(t('agentContext.loadFileFailed'))
mcToast.error(t('agentContext.loadFileFailed'))
}
}
@ -362,10 +362,10 @@ async function saveContent() {
try {
await agentContextApi.saveFile(selectedAgentId.value, selectedFile.value.filename, fileContent.value)
originalContent.value = fileContent.value
ElMessage.success(t('agentContext.saveSuccess'))
mcToast.success(t('agentContext.saveSuccess'))
await fetchFiles()
} catch {
ElMessage.error(t('agentContext.saveFailed'))
mcToast.error(t('agentContext.saveFailed'))
} finally {
saving.value = false
}
@ -387,9 +387,9 @@ async function toggleFileEnabled(file: WorkspaceFile) {
// file
const f = files.value.find(x => x.filename === file.filename)
if (f) f.enabled = isEnabling
ElMessage.success(t('agentContext.promptUpdated'))
mcToast.success(t('agentContext.promptUpdated'))
} catch {
ElMessage.error(t('agentContext.promptUpdateFailed'))
mcToast.error(t('agentContext.promptUpdateFailed'))
}
}
@ -405,21 +405,21 @@ async function confirmDeleteFile() {
try {
await agentContextApi.deleteFile(selectedAgentId.value, name)
ElMessage.success(t('agentContext.deleteSuccess'))
mcToast.success(t('agentContext.deleteSuccess'))
selectedFile.value = null
fileContent.value = ''
originalContent.value = ''
await fetchFiles()
await fetchPromptFiles()
} catch {
ElMessage.error(t('agentContext.deleteFailed'))
mcToast.error(t('agentContext.deleteFailed'))
}
}
async function createNewFile() {
const name = newFilename.value.trim()
if (!isValidFilename.value) {
ElMessage.warning(t('agentContext.invalidFilename'))
mcToast.warning(t('agentContext.invalidFilename'))
return
}
try {
@ -433,7 +433,7 @@ async function createNewFile() {
onFileClick(newFile)
}
} catch {
ElMessage.error(t('agentContext.saveFailed'))
mcToast.error(t('agentContext.saveFailed'))
}
}

View File

@ -488,7 +488,7 @@
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { agentApi, agentBindingApi, modelApi, skillApi, toolApi, templateApi, backstageApi } from '@/api/index'
import type { Agent } from '@/types/index'
@ -753,7 +753,7 @@ async function loadAgents() {
const res: any = await agentApi.list()
agents.value = res.data || []
} catch {
ElMessage.error(t('agents.messages.loadFailed'))
mcToast.error(t('agents.messages.loadFailed'))
}
}
@ -831,11 +831,11 @@ async function applyTemplate(id: string) {
applyingTemplate.value = true
try {
await templateApi.apply(id)
ElMessage.success(t('agents.templates.applied'))
mcToast.success(t('agents.templates.applied'))
showTemplateSelector.value = false
await loadAgents()
} catch (e: any) {
ElMessage.error(e?.message || t('agents.messages.saveFailed'))
mcToast.error(e?.message || t('agents.messages.saveFailed'))
} finally {
applyingTemplate.value = false
}
@ -930,11 +930,11 @@ async function saveAgent() {
])
}
ElMessage.success(t('agents.messages.saveSuccess'))
mcToast.success(t('agents.messages.saveSuccess'))
closeModal()
await loadAgents()
} catch (e: any) {
ElMessage.error(e?.message || t('agents.messages.saveFailed'))
mcToast.error(e?.message || t('agents.messages.saveFailed'))
}
}
@ -947,10 +947,10 @@ async function deleteAgent(agent: Agent) {
if (!ok) return
try {
await agentApi.delete(agent.id)
ElMessage.success(t('agents.messages.deleteSuccess'))
mcToast.success(t('agents.messages.deleteSuccess'))
await loadAgents()
} catch {
ElMessage.error(t('agents.messages.deleteFailed'))
mcToast.error(t('agents.messages.deleteFailed'))
}
}
@ -966,10 +966,10 @@ function goToChat(agent: Agent) {
async function toggleAgent(agent: Agent) {
try {
await agentApi.update(agent.id, { ...agent, enabled: !agent.enabled })
ElMessage.success(t('agents.messages.toggleSuccess'))
mcToast.success(t('agents.messages.toggleSuccess'))
await loadAgents()
} catch {
ElMessage.error(t('agents.messages.toggleFailed'))
mcToast.error(t('agents.messages.toggleFailed'))
}
}
</script>

View File

@ -172,7 +172,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import SkillIcon from '@/components/common/SkillIcon.vue'
import BackstageFocusPanel from '@/components/backstage/BackstageFocusPanel.vue'
import { useBackstageAgent } from '@/composables/useBackstageAgent'
@ -358,7 +358,7 @@ async function refresh() {
if (fresh) detail.value = fresh
}
} catch (e: any) {
if (isInitialLoading.value) ElMessage.error(e?.message || t('backstage.errors.loadFailed'))
if (isInitialLoading.value) mcToast.error(e?.message || t('backstage.errors.loadFailed'))
} finally {
isInitialLoading.value = false
}
@ -386,10 +386,10 @@ async function confirmStop(run: BackstageRunCard) {
if (!ok) return
try {
await backstageApi.stop(run.conversationId)
ElMessage.success(t('backstage.toast.stopped'))
mcToast.success(t('backstage.toast.stopped'))
refresh()
} catch (e: any) {
ElMessage.error(e?.message || t('backstage.errors.loadFailed'))
mcToast.error(e?.message || t('backstage.errors.loadFailed'))
}
}
@ -404,11 +404,11 @@ async function confirmRecycle(run: BackstageRunCard) {
if (!ok) return
try {
await backstageApi.recycle(run.conversationId)
ElMessage.success(t('backstage.toast.ended'))
mcToast.success(t('backstage.toast.ended'))
drawerOpen.value = false
refresh()
} catch (e: any) {
ElMessage.error(e?.message || t('backstage.errors.loadFailed'))
mcToast.error(e?.message || t('backstage.errors.loadFailed'))
}
}
@ -423,10 +423,10 @@ async function confirmInterruptSub(sub: BackstageSubagentCard) {
if (!ok) return
try {
await backstageApi.interruptSubagent(sub.subagentId)
ElMessage.success(t('backstage.toast.subStopped'))
mcToast.success(t('backstage.toast.subStopped'))
refresh()
} catch (e: any) {
ElMessage.error(e?.message || t('backstage.errors.loadFailed'))
mcToast.error(e?.message || t('backstage.errors.loadFailed'))
}
}
@ -443,10 +443,10 @@ async function confirmSweep() {
try {
const res: any = await backstageApi.sweep()
const recycled = res?.data?.recycled ?? 0
ElMessage.success(t('backstage.toast.swept', { n: recycled }))
mcToast.success(t('backstage.toast.swept', { n: recycled }))
refresh()
} catch (e: any) {
ElMessage.error(e?.message || t('backstage.errors.loadFailed'))
mcToast.error(e?.message || t('backstage.errors.loadFailed'))
}
}

View File

@ -170,7 +170,7 @@
<script setup lang="ts">
import { ref, computed, defineAsyncComponent, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { channelApi, agentApi } from '@/api'
import type { Channel, Agent } from '@/types'
@ -280,7 +280,7 @@ async function loadChannels() {
const res: any = await channelApi.list()
channels.value = (res.data || []).map((c: any) => ({ ...c }))
} catch (e: any) {
ElMessage.error(t('channels.messages.loadFailed') + ': ' + (e?.message || ''))
mcToast.error(t('channels.messages.loadFailed') + ': ' + (e?.message || ''))
channels.value = []
}
}
@ -498,7 +498,7 @@ async function handleSave(payload: Partial<Channel>) {
editingChannel.value = null
await loadChannels()
} catch (e: any) {
ElMessage.error(e?.message || t('channels.messages.saveFailed'))
mcToast.error(e?.message || t('channels.messages.saveFailed'))
}
}
@ -515,7 +515,7 @@ async function deleteChannel(id: string | number) {
await channelApi.delete(id)
await loadChannels()
} catch (e: any) {
ElMessage.error(e?.message || t('channels.messages.deleteFailed'))
mcToast.error(e?.message || t('channels.messages.deleteFailed'))
}
}
@ -525,7 +525,7 @@ async function toggleChannel(channel: Channel) {
await loadChannels()
await loadStatus()
} catch (e: any) {
ElMessage.error(e?.message || t('channels.messages.toggleFailed'))
mcToast.error(e?.message || t('channels.messages.toggleFailed'))
}
}

View File

@ -341,7 +341,7 @@
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { ChatDotRound, Delete, Plus, Setting, UploadFilled } from '@element-plus/icons-vue'
import { conversationApi, agentApi, modelApi, chatApi, cronJobApi } from '@/api/index'
@ -488,7 +488,7 @@ async function selectModel(value: string) {
activeModels.value = res.data || { activeLlm: { providerId, model } }
await loadModelState()
} catch (e) {
ElMessage.error(t('chat.switchModelFailed'))
mcToast.error(t('chat.switchModelFailed'))
} finally {
modelSaving.value = false
}
@ -991,19 +991,41 @@ const eligibleModels = computed(() => {
})
// ============ ============
function handleKeyboardShortcuts(e: KeyboardEvent) {
const mod = e.metaKey || e.ctrlKey
if (mod && e.key === 'n') {
e.preventDefault()
// Global shortcuts (Ctrl+K agents, Ctrl+N new chat) live in MainLayout so they
// work from any page; this view reacts to the dispatched event when mounted.
function handleChatShortcut(e: Event) {
const action = (e as CustomEvent).detail as 'newChat' | 'selectAgent' | undefined
if (action === 'newChat') {
newConversation()
chatInputRef.value?.focus?.()
}
if (mod && e.key === 'k') {
e.preventDefault()
nextTick(() => chatInputRef.value?.focus?.())
} else if (action === 'selectAgent') {
agentDropdownOpen.value = !agentDropdownOpen.value
}
}
// Cross-page hand-off from MainLayout's global shortcuts: read once on mount
// (before loadAgents triggers syncRouteState, which would wipe the action key)
// and apply after agents are loaded so the dropdown actually has something to show.
let pendingRouteAction: 'newChat' | 'selectAgent' | '' = ''
function captureRouteAction() {
const action = route.query.action
if (action === 'newChat' || action === 'selectAgent') {
pendingRouteAction = action
}
}
function applyPendingRouteAction() {
const action = pendingRouteAction
pendingRouteAction = ''
if (action === 'newChat') {
newConversation()
nextTick(() => chatInputRef.value?.focus?.())
} else if (action === 'selectAgent') {
agentDropdownOpen.value = true
}
}
// ChatConsole WeChat/DingTalk/
// F5 /
let activityPollTimer: number | null = null
@ -1117,7 +1139,8 @@ async function pollActivity() {
}
onMounted(async () => {
document.addEventListener('keydown', handleKeyboardShortcuts)
captureRouteAction()
window.addEventListener('mc:chat-shortcut', handleChatShortcut)
document.addEventListener('click', handleCodeCopy)
startECharts()
startKatex()
@ -1130,6 +1153,7 @@ onMounted(async () => {
mediumQuery.addEventListener('change', handleConvMediumChange)
await Promise.all([loadAgents(), loadModelState(), loadConversations()])
await hydrateStateFromRoute()
consumeRouteAction()
activityPollTimer = window.setInterval(pollActivity, ACTIVITY_POLL_MS)
elapsedTickTimer = window.setInterval(() => {
if (activeCronRuns.value.length > 0) elapsedNow.value = Date.now()
@ -1137,7 +1161,7 @@ onMounted(async () => {
})
onBeforeUnmount(() => {
document.removeEventListener('keydown', handleKeyboardShortcuts)
window.removeEventListener('mc:chat-shortcut', handleChatShortcut)
document.removeEventListener('click', handleCodeCopy)
disposeECharts()
disposeKatex()
@ -1163,6 +1187,7 @@ onBeforeUnmount(() => {
watch(() => route.query, () => {
void hydrateStateFromRoute()
consumeRouteAction()
})
watch([selectedAgentId, currentConversationId], () => {
@ -1195,7 +1220,7 @@ async function loadAgents() {
selectedAgentId.value = agents.value[0].id
}
} catch (e) {
ElMessage.error(t('chat.loadAgentsFailed'))
mcToast.error(t('chat.loadAgentsFailed'))
}
}
@ -1214,7 +1239,7 @@ async function loadModelState() {
activeModels.value = activeRes.data || null
enabledModels.value = enabledRes.data || []
} catch (e) {
ElMessage.error(t('chat.loadModelFailed'))
mcToast.error(t('chat.loadModelFailed'))
blockingPrompt.value = true
recoverablePrompt.value = false
return
@ -1229,7 +1254,7 @@ async function loadModelState() {
providersUnavailable.value = true
} else {
// Non-403 failure is still a real problem worth surfacing.
ElMessage.error(t('chat.loadModelFailed'))
mcToast.error(t('chat.loadModelFailed'))
}
}
recomputePromptFlags()
@ -1289,7 +1314,7 @@ async function loadConversations() {
const res: any = await conversationApi.list()
conversations.value = res.data || []
} catch (e) {
ElMessage.error(t('chat.loadConversationsFailed'))
mcToast.error(t('chat.loadConversationsFailed'))
}
}
@ -1496,7 +1521,7 @@ async function selectConversation(conv: Conversation) {
await reconnectStream(requestedConvId)
}
} catch (e) {
ElMessage.error(t('chat.loadMessagesFailed'))
mcToast.error(t('chat.loadMessagesFailed'))
}
}
@ -1518,7 +1543,7 @@ async function deleteConversation(conversationId: string) {
currentConversationId.value = ''
}
} catch (e) {
ElMessage.error(t('chat.deleteConversationFailed'))
mcToast.error(t('chat.deleteConversationFailed'))
}
}
@ -1603,7 +1628,7 @@ async function handleSendMessage(content: string) {
const trimmed = content.trim().toLowerCase()
if (trimmed === '/approve' || trimmed === '/deny') {
if (!currentConversationId.value) {
ElMessage.warning('No active conversation')
mcToast.warning('No active conversation')
inputText.value = ''
chatInputRef.value?.clear?.()
return
@ -1614,7 +1639,7 @@ async function handleSendMessage(content: string) {
m => m.role === 'assistant' && (m as any).metadata?.pendingApproval?.status === 'pending_approval'
)
if (!pendingMsg) {
ElMessage.warning('No pending approval to process')
mcToast.warning('No pending approval to process')
inputText.value = ''
chatInputRef.value?.clear?.()
return
@ -1638,7 +1663,7 @@ async function handleSendMessage(content: string) {
console.error('Approval stream failed:', e)
//
;(pendingMsg as any).metadata.pendingApproval.status = 'pending_approval'
ElMessage.error(e?.message || 'Approval failed')
mcToast.error(e?.message || 'Approval failed')
}
return
}
@ -1745,7 +1770,7 @@ async function reconnectStream(conversationId: string) {
await reconnectChatStream(conversationId)
} catch (e) {
console.error('[ChatConsole] Reconnect failed:', e)
ElMessage.warning(t('chat.reconnectFailed') || 'Stream reconnection failed')
mcToast.warning(t('chat.reconnectFailed') || 'Stream reconnection failed')
}
}
@ -1786,7 +1811,7 @@ async function handleFileSelect(files: File[]) {
})
}
} catch (e) {
ElMessage.error(t('chat.uploadFailed'))
mcToast.error(t('chat.uploadFailed'))
} finally {
uploadingAttachment.value = false
}
@ -1986,7 +2011,7 @@ function handleCodeCopy(e: MouseEvent) {
if (textEl) textEl.textContent = t('chat.copy')
}, 1500)
}).catch(() => {
ElMessage.error(t('chat.copyFailed'))
mcToast.error(t('chat.copyFailed'))
})
}
</script>

View File

@ -324,7 +324,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { useCronJobStore } from '@/stores/useCronJobStore'
import { useAgentStore } from '@/stores/useAgentStore'
@ -436,14 +436,14 @@ async function saveJob() {
try {
if (editing.value) {
await store.updateJob(editing.value.id, form.value)
ElMessage.success(t('cronJobs.messages.updateSuccess'))
mcToast.success(t('cronJobs.messages.updateSuccess'))
} else {
await store.createJob(form.value)
ElMessage.success(t('cronJobs.messages.createSuccess'))
mcToast.success(t('cronJobs.messages.createSuccess'))
}
closeModal()
} catch (e: any) {
ElMessage.error(e?.message || e)
mcToast.error(e?.message || e)
}
}
@ -456,9 +456,9 @@ async function handleDelete(job: CronJob) {
if (!ok) return
try {
await store.deleteJob(job.id)
ElMessage.success(t('cronJobs.messages.deleteSuccess'))
mcToast.success(t('cronJobs.messages.deleteSuccess'))
} catch (e: any) {
ElMessage.error(e?.message || e)
mcToast.error(e?.message || e)
}
}
@ -466,20 +466,20 @@ async function handleToggle(job: CronJob) {
try {
const newEnabled = !job.enabled
await store.toggleJob(job.id, newEnabled)
ElMessage.success(newEnabled ? t('cronJobs.messages.enableSuccess') : t('cronJobs.messages.disableSuccess'))
mcToast.success(newEnabled ? t('cronJobs.messages.enableSuccess') : t('cronJobs.messages.disableSuccess'))
store.fetchJobs()
} catch (e: any) {
ElMessage.error(e?.message || e)
mcToast.error(e?.message || e)
}
}
async function handleRunNow(job: CronJob) {
try {
await store.runNow(job.id)
ElMessage.success(t('cronJobs.messages.runTriggered', { id: job.id }))
mcToast.success(t('cronJobs.messages.runTriggered', { id: job.id }))
setTimeout(() => store.fetchJobs(), 3000)
} catch (e: any) {
ElMessage.error(e?.message || e)
mcToast.error(e?.message || e)
}
}

View File

@ -235,7 +235,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import {
ArrowDown,
@ -365,7 +365,7 @@ async function saveDs() {
await loadDatasources()
const id = saved?.data?.id
if (id) autoTestAfterSave(id)
} catch (e: any) { ElMessage.error(e?.message || t('datasources.messages.saveFailed')) }
} catch (e: any) { mcToast.error(e?.message || t('datasources.messages.saveFailed')) }
}
async function autoTestAfterSave(id: number | string) {
@ -373,7 +373,8 @@ async function autoTestAfterSave(id: number | string) {
try {
const res: any = await datasourceApi.test(id)
const ok = res.data?.success
ElMessage({ type: ok ? 'success' : 'warning', message: ok ? t('datasources.messages.testSuccess') : t('datasources.messages.testFailed') })
if (ok) mcToast.success(t('datasources.messages.testSuccess'))
else mcToast.warning(t('datasources.messages.testFailed'))
await loadDatasources()
} catch {
} finally { testing.value = null }
@ -387,7 +388,7 @@ async function testInModal() {
form.value = { ...saved.data }
await loadDatasources()
} catch (e: any) {
ElMessage.error(e?.message || t('datasources.messages.saveFailed'))
mcToast.error(e?.message || t('datasources.messages.saveFailed'))
return
}
} else {
@ -395,7 +396,7 @@ async function testInModal() {
await datasourceApi.update(editingDs.value.id, form.value)
await loadDatasources()
} catch (e: any) {
ElMessage.error(e?.message || t('datasources.messages.saveFailed'))
mcToast.error(e?.message || t('datasources.messages.saveFailed'))
return
}
}
@ -420,14 +421,14 @@ async function deleteDs(id: string | number) {
try {
await datasourceApi.delete(id)
await loadDatasources()
} catch (e: any) { ElMessage.error(e?.message || t('datasources.messages.deleteFailed')) }
} catch (e: any) { mcToast.error(e?.message || t('datasources.messages.deleteFailed')) }
}
async function toggleDs(ds: Datasource) {
try {
await datasourceApi.toggle(ds.id, !ds.enabled)
await loadDatasources()
} catch (e: any) { ElMessage.error(e?.message || t('datasources.messages.toggleFailed')) }
} catch (e: any) { mcToast.error(e?.message || t('datasources.messages.toggleFailed')) }
}
async function testConnection(ds: Datasource) {
@ -435,10 +436,11 @@ async function testConnection(ds: Datasource) {
try {
const res: any = await datasourceApi.test(ds.id)
const ok = res.data?.success
ElMessage({ type: ok ? 'success' : 'error', message: ok ? t('datasources.messages.testSuccess') : t('datasources.messages.testFailed') })
if (ok) mcToast.success(t('datasources.messages.testSuccess'))
else mcToast.error(t('datasources.messages.testFailed'))
await loadDatasources()
} catch (e: any) {
ElMessage.error(e?.message || t('datasources.messages.testFailed'))
mcToast.error(e?.message || t('datasources.messages.testFailed'))
} finally { testing.value = null }
}
</script>

View File

@ -71,7 +71,7 @@
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { http } from '@/api'
import FactTrustBar from './FactTrustBar.vue'
@ -109,25 +109,25 @@ function search() { loadFacts() }
async function feedback(factId: number, kind: string) {
try {
await http.post(`/memory/${props.agentId}/facts/${factId}/feedback`, { kind })
ElMessage.success(kind === 'HELPFUL' ? '👍' : '👎')
mcToast.success(kind === 'HELPFUL' ? '👍' : '👎')
loadFacts()
} catch (e: any) { ElMessage.error(e.message || 'Failed') }
} catch (e: any) { mcToast.error(e.message || 'Failed') }
}
async function forget(factId: number) {
try {
await http.post(`/memory/${props.agentId}/facts/${factId}/forget`)
ElMessage.success(t('memory.facts.forgotten'))
mcToast.success(t('memory.facts.forgotten'))
loadFacts()
} catch (e: any) { ElMessage.error(e.message || 'Failed') }
} catch (e: any) { mcToast.error(e.message || 'Failed') }
}
async function resolve(contradictionId: number, resolution: string) {
try {
await http.post(`/memory/${props.agentId}/facts/contradictions/${contradictionId}/resolve`, { resolution })
ElMessage.success(t('memory.facts.resolved'))
mcToast.success(t('memory.facts.resolved'))
loadContradictions()
} catch (e: any) { ElMessage.error(e.message || 'Failed') }
} catch (e: any) { mcToast.error(e.message || 'Failed') }
}
</script>

View File

@ -63,7 +63,7 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { http, agentContextApi } from '@/api'
const props = defineProps<{ agentId: number }>()
@ -146,12 +146,12 @@ async function saveSection(idx: number) {
`/memory/${props.agentId}/dream/reports/0/entries/${encodeURIComponent(heading)}/edit`,
{ content: editText.value }
)
ElMessage.success(t('memory.hil.saved'))
mcToast.success(t('memory.hil.saved'))
editingIdx.value = -1
// Reload file to see changes
await loadFile(currentFile.value)
} catch (e: any) {
ElMessage.error(e.message || 'Save failed')
mcToast.error(e.message || 'Save failed')
} finally { saving.value = false }
}

View File

@ -180,7 +180,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { http } from '@/api'
import { useAgentStore } from '@/stores/useAgentStore'
import { useMemoryStore, type DreamReportItem } from '@/stores/useMemoryStore'
@ -251,12 +251,12 @@ async function triggerDream() {
dreamRunning.value = true
try {
await http.post(`/memory/${selectedAgentId.value}/dreaming/focused`, { topic: dreamTopic.value.trim() })
ElMessage.success(t('memory.focused.success'))
mcToast.success(t('memory.focused.success'))
dreamTopic.value = ''
dreamInputOpen.value = false
loadReports()
} catch (e: any) {
ElMessage.error(e.message || 'Dream failed')
mcToast.error(e.message || 'Dream failed')
} finally {
dreamRunning.value = false
}

View File

@ -113,7 +113,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { pluginApi } from '@/api'
const { t } = useI18n()
@ -145,7 +145,7 @@ async function loadPlugins() {
const res = await pluginApi.list()
plugins.value = res.data || []
} catch (e: any) {
ElMessage.error(t('plugins.loadFailed'))
mcToast.error(t('plugins.loadFailed'))
} finally {
loading.value = false
}
@ -156,14 +156,14 @@ async function togglePlugin(plugin: PluginInfo) {
try {
if (plugin.enabled) {
await pluginApi.disable(plugin.name)
ElMessage.success(t('plugins.disabled', { name: plugin.displayName || plugin.name }))
mcToast.success(t('plugins.disabled', { name: plugin.displayName || plugin.name }))
} else {
await pluginApi.enable(plugin.name)
ElMessage.success(t('plugins.enabled', { name: plugin.displayName || plugin.name }))
mcToast.success(t('plugins.enabled', { name: plugin.displayName || plugin.name }))
}
await loadPlugins()
} catch (e: any) {
ElMessage.error(e.message || t('plugins.toggleFailed'))
mcToast.error(e.message || t('plugins.toggleFailed'))
await loadPlugins()
} finally {
toggling.value = null

View File

@ -119,7 +119,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { workspaceTeamApi } from '@/api/index'
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
@ -155,7 +155,7 @@ async function fetchMembers() {
const res: any = await workspaceTeamApi.listMembers(wsId)
members.value = res.data || []
} catch (e: any) {
ElMessage.error(e.message)
mcToast.error(e.message)
} finally {
loading.value = false
}
@ -171,12 +171,12 @@ async function addMember() {
nickname: newMemberForm.nickname || undefined,
role: newMemberForm.role,
})
ElMessage.success(t('security.members.messages.addSuccess'))
mcToast.success(t('security.members.messages.addSuccess'))
showAddDialog.value = false
Object.assign(newMemberForm, defaultForm())
fetchMembers()
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || t('security.members.messages.addFailed'))
mcToast.error(e?.msg || e?.message || t('security.members.messages.addFailed'))
}
}
@ -186,9 +186,9 @@ async function updateRole(member: Member, role: string) {
try {
await workspaceTeamApi.updateMemberRole(wsId, member.userId, role)
member.role = role
ElMessage.success(t('security.members.messages.updateSuccess'))
mcToast.success(t('security.members.messages.updateSuccess'))
} catch {
ElMessage.error(t('security.members.messages.updateFailed'))
mcToast.error(t('security.members.messages.updateFailed'))
}
}
@ -198,10 +198,10 @@ async function removeMember(member: Member) {
if (!confirm(t('security.members.messages.removeConfirm'))) return
try {
await workspaceTeamApi.removeMember(wsId, member.userId)
ElMessage.success(t('security.members.messages.removeSuccess'))
mcToast.success(t('security.members.messages.removeSuccess'))
fetchMembers()
} catch {
ElMessage.error(t('security.members.messages.removeFailed'))
mcToast.error(t('security.members.messages.removeFailed'))
}
}

View File

@ -220,7 +220,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { securityApi } from '@/api'
import { parseJsonArray } from '../composables/helpers'
import type { GuardRule } from '@/types'
@ -357,15 +357,15 @@ function openEditRuleModal(rule: GuardRule) {
async function saveRule() {
if (!editingRule.value && !ruleForm.ruleId.trim()) {
ElMessage.error(t('security.toolGuard.messages.ruleIdRequired'))
mcToast.error(t('security.toolGuard.messages.ruleIdRequired'))
return
}
if (!ruleForm.name.trim()) {
ElMessage.error(t('security.toolGuard.messages.nameRequired'))
mcToast.error(t('security.toolGuard.messages.nameRequired'))
return
}
if (!ruleForm.pattern.trim()) {
ElMessage.error(t('security.toolGuard.messages.patternRequired'))
mcToast.error(t('security.toolGuard.messages.patternRequired'))
return
}
@ -380,9 +380,9 @@ async function saveRule() {
} catch (e: any) {
const raw = e?.msg || e?.message || ''
if (typeof raw === 'string' && raw.toLowerCase().includes('already exists')) {
ElMessage.error(t('security.toolGuard.messages.ruleIdDuplicate'))
mcToast.error(t('security.toolGuard.messages.ruleIdDuplicate'))
} else {
ElMessage.error(raw || t('security.toolGuard.messages.saveFailed'))
mcToast.error(raw || t('security.toolGuard.messages.saveFailed'))
}
}
}

View File

@ -138,7 +138,7 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { workspaceTeamApi } from '@/api/index'
import { useWorkspaceStore, type Workspace } from '@/stores/useWorkspaceStore'
@ -170,7 +170,7 @@ async function fetchWorkspaces() {
const res: any = await workspaceTeamApi.list()
workspaces.value = res.data || []
} catch (e: any) {
ElMessage.error(e.message || 'Failed to fetch workspaces')
mcToast.error(e.message || 'Failed to fetch workspaces')
} finally {
loading.value = false
}
@ -219,11 +219,11 @@ async function saveWorkspace() {
})
}
showDialog.value = false
ElMessage.success(t('security.workspaces.messages.saveSuccess'))
mcToast.success(t('security.workspaces.messages.saveSuccess'))
await fetchWorkspaces()
wsStore.fetchWorkspaces()
} catch (e: any) {
ElMessage.error(t('security.workspaces.messages.saveFailed'))
mcToast.error(t('security.workspaces.messages.saveFailed'))
}
}
@ -238,11 +238,11 @@ async function deleteWorkspace() {
await workspaceTeamApi.delete(deletingWs.value.id)
showDeleteConfirm.value = false
deletingWs.value = null
ElMessage.success(t('security.workspaces.messages.deleteSuccess'))
mcToast.success(t('security.workspaces.messages.deleteSuccess'))
await fetchWorkspaces()
wsStore.fetchWorkspaces()
} catch (e: any) {
ElMessage.error(t('security.workspaces.messages.deleteFailed'))
mcToast.error(t('security.workspaces.messages.deleteFailed'))
}
}

View File

@ -93,7 +93,7 @@
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { conversationApi } from '@/api/index'
import { channelIconUrl, sourceLabel } from '@/utils/channelSource'
@ -120,7 +120,7 @@ async function loadSessions() {
try {
const res: any = await conversationApi.list()
sessions.value = res.data || []
} catch (e: any) { ElMessage.error(t('sessions.loadFailed')) }
} catch (e: any) { mcToast.error(t('sessions.loadFailed')) }
}
function viewSession(session: Conversation) {
@ -137,7 +137,7 @@ async function deleteSession(conversationId: string) {
try {
await conversationApi.delete(conversationId)
await loadSessions()
} catch (e: any) { ElMessage.error(t('sessions.deleteFailed')) }
} catch (e: any) { mcToast.error(t('sessions.deleteFailed')) }
}

View File

@ -71,7 +71,8 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElIcon, ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { ElIcon } from 'element-plus'
import { Loading, WarningFilled } from '@element-plus/icons-vue'
import { featureFlagApi, type FeatureFlag } from '@/api/index'
@ -135,10 +136,10 @@ async function onToggle(flag: FeatureFlag, next: boolean) {
try {
await featureFlagApi.update(flag.flagKey, { enabled: next })
flag.enabled = next
ElMessage.success(t(next ? 'settings.featureFlags.enabled' : 'settings.featureFlags.disabled',
mcToast.success(t(next ? 'settings.featureFlags.enabled' : 'settings.featureFlags.disabled',
{ key: flag.flagKey }))
} catch (e: any) {
ElMessage.error(e?.message ?? t('settings.featureFlags.toggleFailed'))
mcToast.error(e?.message ?? t('settings.featureFlags.toggleFailed'))
await load()
} finally {
pending[flag.flagKey] = false

View File

@ -109,7 +109,7 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import type { ProviderInfo } from '@/types'
import ProviderCatalogRow from './ProviderCatalogRow.vue'
@ -181,7 +181,7 @@ function onEscape(event: KeyboardEvent) {
async function onEnable(p: ProviderInfo) {
await props.enableProvider(p.id)
ElMessage.success(t('settings.model.enabledToast', { name: p.name }))
mcToast.success(t('settings.model.enabledToast', { name: p.name }))
}
</script>

View File

@ -1,6 +1,6 @@
import { computed, reactive, ref, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { modelApi } from '@/api'
import type { DiscoverResult, ProviderInfo, ProviderModelInfo, TestResult } from '@/types'
@ -98,7 +98,7 @@ export function useProviderDiscovery(deps: ListDeps) {
selectedNewModelIds.value = res.data.newModels.map((m: ProviderModelInfo) => m.id)
}
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : String(error))
mcToast.error(error instanceof Error ? error.message : String(error))
} finally {
discovering.value = false
}
@ -116,7 +116,7 @@ export function useProviderDiscovery(deps: ListDeps) {
await deps.refreshCurrentProvider(deps.currentProvider.value.id)
return added
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : String(error))
mcToast.error(error instanceof Error ? error.message : String(error))
return 0
} finally {
applyingModels.value = false

View File

@ -1,6 +1,6 @@
import { ref, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { modelApi } from '@/api'
import type { EnableResult, ProviderInfo } from '@/types'
@ -55,7 +55,7 @@ export function useProviderEnablement(deps: ListDeps) {
await Promise.all([loadCatalog(), deps.loadProviders()])
return res.data as EnableResult
} catch (err) {
ElMessage.error(err instanceof Error ? err.message : String(err))
mcToast.error(err instanceof Error ? err.message : String(err))
return null
} finally {
togglingId.value = null
@ -72,14 +72,14 @@ export function useProviderEnablement(deps: ListDeps) {
// RFC-074 §2: silent switch + toast. Show the toast here so every caller
// (drawer disable, card more-menu, etc.) gets consistent UX.
if (result?.defaultSwitched) {
ElMessage.success(t('settings.model.defaultSwitchedToast', {
mcToast.success(t('settings.model.defaultSwitchedToast', {
provider: result.newDefaultProviderId,
model: result.newDefaultModel,
}))
}
return result
} catch (err) {
ElMessage.error(err instanceof Error ? err.message : String(err))
mcToast.error(err instanceof Error ? err.message : String(err))
return null
} finally {
togglingId.value = null

View File

@ -1,6 +1,6 @@
import { computed, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { modelApi } from '@/api'
import type { ProviderInfo } from '@/types'
@ -159,7 +159,7 @@ export function useProviderForm(deps: ListDeps) {
if (!editingProvider.value) {
const id = providerForm.id.trim()
if (!id || !PROVIDER_ID_PATTERN.test(id)) {
ElMessage.error(t('settings.model.providerIdInvalid'))
mcToast.error(t('settings.model.providerIdInvalid'))
return false
}
providerForm.id = id

View File

@ -1,6 +1,6 @@
import { ref, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { claudeCodeOAuthApi, oauthApi } from '@/api'
import type { ProviderInfo } from '@/types'
@ -74,12 +74,12 @@ export function useProviderOAuth(deps: FormDeps & ListDeps) {
try {
start = await oauthApi.deviceStart()
} catch (e: any) {
ElMessage.error(e.msg || 'Device code request failed')
mcToast.error(e.msg || 'Device code request failed')
return
}
const data = start.data
if (!data?.deviceAuthId || !data?.userCode) {
ElMessage.error('Device code response was incomplete')
mcToast.error('Device code response was incomplete')
return
}
@ -100,7 +100,7 @@ export function useProviderOAuth(deps: FormDeps & ListDeps) {
return
}
if (Date.now() > deviceCodeDialog.value.expiresAt) {
ElMessage.warning(t('settings.model.oauthDeviceExpired'))
mcToast.warning(t('settings.model.oauthDeviceExpired'))
closeDeviceCodeDialog()
return
}
@ -111,12 +111,12 @@ export function useProviderOAuth(deps: FormDeps & ListDeps) {
activeDeviceAuthId = null
stopDevicePolling()
deviceCodeDialog.value.visible = false
ElMessage.success(t('settings.model.oauthLoginSuccess'))
mcToast.success(t('settings.model.oauthLoginSuccess'))
await reloadProvidersAndSync()
return
}
if (status === 'EXPIRED') {
ElMessage.warning(t('settings.model.oauthDeviceExpired'))
mcToast.warning(t('settings.model.oauthDeviceExpired'))
closeDeviceCodeDialog()
return
}
@ -131,13 +131,13 @@ export function useProviderOAuth(deps: FormDeps & ListDeps) {
try {
const res: any = await claudeCodeOAuthApi.reload()
if (res.data?.connected && !res.data?.expired) {
ElMessage.success(t('settings.model.oauthLoginSuccess'))
mcToast.success(t('settings.model.oauthLoginSuccess'))
} else {
ElMessage.warning(t('settings.model.claudeCodeOauthInstructions'))
mcToast.warning(t('settings.model.claudeCodeOauthInstructions'))
}
await reloadProvidersAndSync()
} catch (e: any) {
ElMessage.error(e.msg || 'Claude Code OAuth detection failed')
mcToast.error(e.msg || 'Claude Code OAuth detection failed')
}
return
}
@ -160,14 +160,14 @@ export function useProviderOAuth(deps: FormDeps & ListDeps) {
if (statusRes.data?.connected) {
clearInterval(pollInterval)
if (authWindow && !authWindow.closed) authWindow.close()
ElMessage.success(t('settings.model.oauthLoginSuccess'))
mcToast.success(t('settings.model.oauthLoginSuccess'))
await reloadProvidersAndSync()
}
} catch { /* ignore polling errors */ }
}, 2000)
setTimeout(() => clearInterval(pollInterval), 30000)
} catch (e: any) {
ElMessage.error(e.msg || 'OAuth login failed')
mcToast.error(e.msg || 'OAuth login failed')
}
}
@ -176,15 +176,15 @@ export function useProviderOAuth(deps: FormDeps & ListDeps) {
// Code app, not MateClaw — direct the user to log out there instead of
// clobbering their machine-level login.
if (providerId === 'anthropic-claude-code') {
ElMessage.info(t('settings.model.claudeCodeOauthRevokeHint'))
mcToast.info(t('settings.model.claudeCodeOauthRevokeHint'))
return
}
try {
await oauthApi.revoke()
ElMessage.success(t('settings.model.oauthRevokeSuccess'))
mcToast.success(t('settings.model.oauthRevokeSuccess'))
await reloadProvidersAndSync()
} catch (e: any) {
ElMessage.error(e.msg || 'OAuth revoke failed')
mcToast.error(e.msg || 'OAuth revoke failed')
}
}

View File

@ -1,6 +1,6 @@
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { providerPoolApi } from '@/api'
import type { ProviderInfo } from '@/types'
@ -29,13 +29,13 @@ export function useProviderPool(deps: ListDeps) {
// refreshes everything the UI needs (badge + status pill + dropdown filter).
await deps.loadProviders()
if (data.success) {
ElMessage.success(t('settings.model.poolReprobeOk'))
mcToast.success(t('settings.model.poolReprobeOk'))
} else {
ElMessage.warning(t('settings.model.poolReprobeFail', { error: data.errorMessage || '—' }))
mcToast.warning(t('settings.model.poolReprobeFail', { error: data.errorMessage || '—' }))
}
return data
} catch (err) {
ElMessage.error(t('settings.model.poolReprobeFail', {
mcToast.error(t('settings.model.poolReprobeFail', {
error: err instanceof Error ? err.message : String(err)
}))
} finally {

View File

@ -191,7 +191,7 @@
<script setup lang="ts">
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { useRoute, useRouter } from 'vue-router'
import type { ProviderInfo, ProviderModelInfo } from '@/types'
@ -333,7 +333,7 @@ async function onSaveApiKey({ provider, apiKey }: { provider: ProviderInfo; apiK
await saveProviderApiKey(provider, apiKey)
showSavedTip(t('settings.model.inlineApiKeySaved'))
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : t('settings.model.inlineApiKeySaveFailed'))
mcToast.error(error instanceof Error ? error.message : t('settings.model.inlineApiKeySaveFailed'))
} finally {
savingApiKeyId.value = null
}
@ -350,10 +350,10 @@ async function onSaveProvider() {
const saved = await saveProvider()
// Issue #39: saveProvider() returns false when client-side validation
// (e.g. provider id format) blocks the request it has already shown
// its own ElMessage.error, so don't follow up with a "saved" toast.
// its own mcToast.error, so don't follow up with a "saved" toast.
if (saved) showSavedTip(t('settings.model.providerSaved'))
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : t('settings.messages.saveFailed'))
mcToast.error(error instanceof Error ? error.message : t('settings.messages.saveFailed'))
}
}
@ -367,7 +367,7 @@ async function onAddProviderModel() {
await addProviderModel()
showSavedTip(t('settings.model.modelAdded'))
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : t('settings.model.modelAddFailed'))
mcToast.error(error instanceof Error ? error.message : t('settings.model.modelAddFailed'))
}
}
@ -376,7 +376,7 @@ async function onRemoveProviderModel(model: ProviderModelInfo) {
await removeProviderModel(model)
showSavedTip(t('settings.model.modelRemoved'))
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : t('settings.model.modelRemoveFailed'))
mcToast.error(error instanceof Error ? error.message : t('settings.model.modelRemoveFailed'))
}
}
@ -385,7 +385,7 @@ async function onSetActiveModel(model: ProviderModelInfo) {
await setActiveModel(model)
showSavedTip(t('settings.model.activeChanged'))
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : t('settings.model.activeChangeFailed'))
mcToast.error(error instanceof Error ? error.message : t('settings.model.activeChangeFailed'))
}
}

View File

@ -40,7 +40,7 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { copyToClipboard } from '@/utils/clipboard'
const props = defineProps<{
@ -88,7 +88,7 @@ async function copyCode() {
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
} catch {
ElMessage.warning(t('settings.model.copyFailed'))
mcToast.warning(t('settings.model.copyFailed'))
}
}
</script>

View File

@ -654,7 +654,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { skillApi, skillInstallApi } from '@/api/index'
import type { Skill, SkillRuntimeStatus, SkillSecurityFinding } from '@/types/index'
import ImportHubDialog from '@/components/skill/ImportHubDialog.vue'
@ -867,7 +867,7 @@ async function clearLessons() {
await skillApi.clearLessons(detailSkill.value.id)
detailLessonsRaw.value = ''
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.messages.deleteFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.messages.deleteFailed'))
}
}
@ -1046,7 +1046,7 @@ async function createSkillFromModal() {
openDetailDrawer(fresh, 'overview', { editIdentity: true })
}
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.messages.saveFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.messages.saveFailed'))
} finally {
creating.value = false
}
@ -1124,9 +1124,9 @@ async function saveIdentity() {
detailSkill.value = { ...detailSkill.value, ...updated }
}
editingIdentity.value = false
ElMessage.success(t('skills.messages.saveSuccess'))
mcToast.success(t('skills.messages.saveSuccess'))
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.messages.saveFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.messages.saveFailed'))
} finally {
savingEdit.value = false
}
@ -1166,9 +1166,9 @@ async function saveBody() {
// Body change next resolve re-projects icon/version/author from the
// new frontmatter, so refresh runtime status to pick those up.
loadRuntimeStatus()
ElMessage.success(t('skills.detail.sourceSavedReprojection'))
mcToast.success(t('skills.detail.sourceSavedReprojection'))
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.messages.saveFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.messages.saveFailed'))
} finally {
savingEdit.value = false
}
@ -1207,7 +1207,7 @@ async function deleteSkill(idOrSkill: string | number | Skill) {
await skillInstallApi.uninstall(skill.name)
await loadAll()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.messages.deleteFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.messages.deleteFailed'))
}
}
@ -1217,14 +1217,14 @@ async function toggleSkill(skill: Skill) {
// toast accurate when the backend would otherwise return err.skill.not_found
// on builds that pre-date the rejectVirtualSkillMutation guard).
if (isSkillRowVirtual(skill)) {
ElMessage.warning(t('skills.virtualReadonlyHint'))
mcToast.warning(t('skills.virtualReadonlyHint'))
return
}
try {
await skillApi.toggle(skill.id, !skill.enabled)
await loadAll()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.messages.toggleFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.messages.toggleFailed'))
}
}
@ -1233,9 +1233,9 @@ async function handleRefreshRuntime() {
try {
await skillApi.refreshRuntime()
await loadRuntimeStatus()
ElMessage.success(t('skills.refreshSuccess'))
mcToast.success(t('skills.refreshSuccess'))
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.refreshFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.refreshFailed'))
} finally {
refreshing.value = false
}
@ -1270,7 +1270,7 @@ async function rescanSkill(skill: Skill) {
// Patch the row in-place so the panel updates without a full page reload.
const idx = skills.value.findIndex(s => s.id === skill.id)
if (idx >= 0) skills.value.splice(idx, 1, { ...skills.value[idx], ...updated })
ElMessage.success(
mcToast.success(
updated.securityScanStatus === 'FAILED'
? t('skills.security.rescanStillFailed')
: t('skills.security.rescanPassed')
@ -1279,7 +1279,7 @@ async function rescanSkill(skill: Skill) {
// Refresh runtime status too so the in-memory badges stay in sync.
await loadRuntimeStatus()
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skills.security.rescanFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skills.security.rescanFailed'))
} finally {
rescanning.value = { ...rescanning.value, [key]: false }
}

View File

@ -160,7 +160,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { skillTemplateApi, wikiApi } from '@/api/index'
interface TemplateField {
@ -286,7 +286,7 @@ async function installSkill() {
await skillTemplateApi.instantiate(selectedTemplate.value.id, form)
step.value = 3
} catch (e: any) {
ElMessage.error(typeof e === 'string' ? e : e?.message || t('skillTemplates.installFailed'))
mcToast.error(typeof e === 'string' ? e : e?.message || t('skillTemplates.installFailed'))
} finally {
installing.value = false
}

View File

@ -122,7 +122,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { tokenUsageApi } from '@/api/index'
import type { TokenUsageSummary } from '@/types/tokenUsage'
@ -157,7 +157,7 @@ async function fetchData() {
data.value = res.data || null
} catch (e: any) {
const msg = t('tokenUsage.loadFailed')
ElMessage.error(msg)
mcToast.error(msg)
error.value = msg
data.value = null
} finally {

View File

@ -130,7 +130,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { toolApi } from '@/api/index'
import type { Tool } from '@/types/index'
@ -183,7 +183,7 @@ async function saveTool() {
}
closeModal()
await loadTools()
} catch (e: any) { ElMessage.error(e?.message || t('tools.messages.saveFailed')) }
} catch (e: any) { mcToast.error(e?.message || t('tools.messages.saveFailed')) }
}
async function deleteTool(id: string | number) {
@ -196,14 +196,14 @@ async function deleteTool(id: string | number) {
try {
await toolApi.delete(id)
await loadTools()
} catch (e: any) { ElMessage.error(e?.message || t('tools.messages.deleteFailed')) }
} catch (e: any) { mcToast.error(e?.message || t('tools.messages.deleteFailed')) }
}
async function toggleTool(tool: Tool) {
try {
await toolApi.toggle(tool.id, !tool.enabled)
await loadTools()
} catch (e: any) { ElMessage.error(e?.message || t('tools.messages.toggleFailed')) }
} catch (e: any) { mcToast.error(e?.message || t('tools.messages.toggleFailed')) }
}
</script>

View File

@ -171,7 +171,7 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { mcConfirm } from '@/components/common/useConfirm'
import { triggerApi, type TriggerSummary, workflowApi, type WorkflowSummary } from '@/api'
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
@ -361,7 +361,7 @@ async function save() {
formOpen.value = false
await reload()
} catch (e) {
ElMessage.error(t('triggers.saveFailed', { msg: (e as Error).message }))
mcToast.error(t('triggers.saveFailed', { msg: (e as Error).message }))
} finally {
busy.value = false
}
@ -372,7 +372,7 @@ async function toggleEnabled(row: TriggerSummary) {
await triggerApi.update(row.id, { ...row, enabled: !row.enabled })
await reload()
} catch (e) {
ElMessage.error(t('triggers.toggleFailed', { msg: (e as Error).message }))
mcToast.error(t('triggers.toggleFailed', { msg: (e as Error).message }))
}
}
@ -388,7 +388,7 @@ async function remove(row: TriggerSummary) {
await triggerApi.delete(row.id)
await reload()
} catch (e) {
ElMessage.error(t('triggers.deleteFailed', { msg: (e as Error).message }))
mcToast.error(t('triggers.deleteFailed', { msg: (e as Error).message }))
}
}

View File

@ -77,7 +77,8 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElIcon, ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { ElIcon } from 'element-plus'
import { Loading, WarningFilled } from '@element-plus/icons-vue'
import { hotCacheApi, type WikiHotCache } from '@/api/index'
import { useWikiStore } from '@/stores/useWikiStore'
@ -109,13 +110,13 @@ async function onRegenerate() {
regenerating.value = true
try {
await hotCacheApi.regenerate(store.currentKB.id)
ElMessage.success(t('wiki.hotCache.regenerateQueued'))
mcToast.success(t('wiki.hotCache.regenerateQueued'))
// Background rebuild poll once after a short delay so the operator sees
// the row update without manual refresh. The LLM call typically returns
// within 5-15s; if the user hits "regenerate" again, a fresh poll fires.
setTimeout(load, 4000)
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.hotCache.regenerateFailed'))
mcToast.error(e?.message ?? t('wiki.hotCache.regenerateFailed'))
} finally {
regenerating.value = false
}
@ -126,10 +127,10 @@ async function onReset() {
if (!confirm(t('wiki.hotCache.resetConfirm'))) return
try {
await hotCacheApi.reset(store.currentKB.id)
ElMessage.success(t('wiki.hotCache.resetDone'))
mcToast.success(t('wiki.hotCache.resetDone'))
await load()
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.hotCache.resetFailed'))
mcToast.error(e?.message ?? t('wiki.hotCache.resetFailed'))
}
}

View File

@ -286,7 +286,7 @@
<script setup lang="ts">
import { ref, reactive, computed, watch, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { Download } from '@element-plus/icons-vue'
import { useWikiStore } from '@/stores/useWikiStore'
import { wikiApi } from '@/api/index'
@ -550,7 +550,7 @@ async function uploadFile(kbId: number, file: File) {
} catch (err: any) {
item.status = 'error'
item.errorMsg = err?.response?.data?.message || err?.message || t('wiki.uploadFailed', { name: file.name })
ElMessage.error(t('wiki.uploadFailed', { name: file.name }))
mcToast.error(t('wiki.uploadFailed', { name: file.name }))
}
}
@ -653,7 +653,7 @@ async function downloadRaw(raw: { id: number; title?: string }) {
setTimeout(() => URL.revokeObjectURL(url), 0)
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
ElMessage.error(`${t('wiki.downloadFailed')}: ${msg}`)
mcToast.error(`${t('wiki.downloadFailed')}: ${msg}`)
}
}

View File

@ -333,7 +333,8 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElIcon, ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import { ElIcon } from 'element-plus'
import { Loading, WarningFilled } from '@element-plus/icons-vue'
import { wikiApi, modelApi } from '@/api/index'
import { useWikiStore, type WikiRawMaterial } from '@/stores/useWikiStore'
@ -545,7 +546,7 @@ function closeEditor() {
async function onSave() {
if (!store.currentKB) return
if (!form.name.trim() || !form.title.trim() || !form.promptTemplate.trim()) {
ElMessage.warning(t('common.required', 'All fields are required'))
mcToast.warning(t('common.required', 'All fields are required'))
return
}
saving.value = true
@ -585,7 +586,7 @@ async function onSave() {
closeEditor()
await loadAll()
} catch (e: any) {
ElMessage.error(e?.message ?? String(e))
mcToast.error(e?.message ?? String(e))
} finally {
saving.value = false
}
@ -597,7 +598,7 @@ async function onDelete(tpl: WikiTransformation) {
await wikiApi.deleteTransformation(tpl.id)
await loadAll()
} catch (e: any) {
ElMessage.error(e?.message ?? String(e))
mcToast.error(e?.message ?? String(e))
}
}
@ -609,7 +610,7 @@ async function onApply(tpl: WikiTransformation) {
await wikiApi.applyTransformation(tpl.id, rawId, true)
await loadRunsFor(tpl.id)
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.transformations.runFailed'))
mcToast.error(e?.message ?? t('wiki.transformations.runFailed'))
} finally {
runningTemplateId.value = null
}
@ -624,7 +625,7 @@ async function onSaveRunAsPage(tpl: WikiTransformation, run: WikiTransformationR
if (payload.pageId) {
run.outputPageId = payload.pageId
}
ElMessage.success(t('wiki.transformations.saveAsPageDone'))
mcToast.success(t('wiki.transformations.saveAsPageDone'))
// Refresh the page list in the wiki store so the new page is visible in
// the sidebar / search results without a manual reload.
if (store.currentKB) {
@ -632,7 +633,7 @@ async function onSaveRunAsPage(tpl: WikiTransformation, run: WikiTransformationR
}
await loadRunsFor(tpl.id)
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.transformations.saveAsPageFailed'))
mcToast.error(e?.message ?? t('wiki.transformations.saveAsPageFailed'))
} finally {
savingRunId.value = null
}
@ -682,15 +683,15 @@ async function onAggregate(tpl: WikiTransformation) {
const resp: any = await wikiApi.aggregateTransformation(tpl.id, store.currentKB.id)
const payload = resp?.data ?? {}
if (payload && payload.pageId) {
ElMessage.success(`${t('wiki.transformations.aggregateDone')} · ${payload.sourcesUsed} sources`)
mcToast.success(`${t('wiki.transformations.aggregateDone')} · ${payload.sourcesUsed} sources`)
// Refresh the page list in the wiki store so the new aggregate page
// shows up in the sidebar without a manual reload.
try { await store.fetchPages(store.currentKB.id) } catch {}
} else {
ElMessage.info(t('wiki.transformations.aggregateNoRuns'))
mcToast.info(t('wiki.transformations.aggregateNoRuns'))
}
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.transformations.aggregateFailed'))
mcToast.error(e?.message ?? t('wiki.transformations.aggregateFailed'))
} finally {
aggregatingTemplateId.value = null
}
@ -700,10 +701,10 @@ async function onCancelRun(tpl: WikiTransformation, run: WikiTransformationRun)
cancellingRunId.value = run.id
try {
await wikiApi.cancelTransformationRun(run.id)
ElMessage.success(t('wiki.transformations.cancelDone'))
mcToast.success(t('wiki.transformations.cancelDone'))
await loadRunsFor(tpl.id)
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.transformations.cancelFailed'))
mcToast.error(e?.message ?? t('wiki.transformations.cancelFailed'))
} finally {
cancellingRunId.value = null
}
@ -716,7 +717,7 @@ async function onRerun(tpl: WikiTransformation, run: WikiTransformationRun) {
await wikiApi.applyTransformation(tpl.id, run.rawId, true)
await loadRunsFor(tpl.id)
} catch (e: any) {
ElMessage.error(e?.message ?? t('wiki.transformations.runFailed'))
mcToast.error(e?.message ?? t('wiki.transformations.runFailed'))
} finally {
rerunningRunId.value = null
}
@ -735,7 +736,7 @@ async function onOpenSavedPage(run: WikiTransformationRun) {
await store.loadPage(store.currentKB.id, page.slug)
}
} catch (e: any) {
ElMessage.error(e?.message ?? String(e))
mcToast.error(e?.message ?? String(e))
}
}

View File

@ -217,7 +217,7 @@
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { mcConfirm } from '@/components/common/useConfirm'
import { ElMessage } from 'element-plus'
import { mcToast } from '@/composables/useMcToast'
import {
agentApi,
channelApi,
@ -370,7 +370,7 @@ async function onGenerateAccept(draft: GeneratedDraft) {
})
const created = res.data as unknown as WorkflowSummary
if (!created?.id) {
ElMessage.error(t('workflows.generate.failed', { msg: 'workflow row not returned' }))
mcToast.error(t('workflows.generate.failed', { msg: 'workflow row not returned' }))
return
}
await workflowApi.saveDraft(created.id, draft.draftJson)
@ -418,15 +418,15 @@ async function onGenerateAccept(draft: GeneratedDraft) {
compileErrors.value = draft.compileErrors
}
if (triggersCreated > 0 && triggersSkipped === 0) {
ElMessage.success(t('workflows.generate.acceptedWithTriggers', { count: triggersCreated }))
mcToast.success(t('workflows.generate.acceptedWithTriggers', { count: triggersCreated }))
} else if (triggersSkipped > 0) {
ElMessage.warning(t('workflows.generate.acceptedSomeTriggersFailed',
mcToast.warning(t('workflows.generate.acceptedSomeTriggersFailed',
{ ok: triggersCreated, fail: triggersSkipped }))
} else {
ElMessage.success(t('workflows.generate.compileOk'))
mcToast.success(t('workflows.generate.compileOk'))
}
} catch (e) {
ElMessage.error(t('workflows.generate.failed', { msg: (e as Error).message }))
mcToast.error(t('workflows.generate.failed', { msg: (e as Error).message }))
} finally {
busy.value = false
}
@ -640,11 +640,11 @@ async function onResume(entry: PausedRunSummary, outcome: ResumeOutcome) {
resumingId.value = entry.run.id
try {
await workflowApi.resumeRun(entry.run.id, entry.pause.pauseToken, outcome)
ElMessage.success(t('workflows.paused.resumeOk', { outcome }))
mcToast.success(t('workflows.paused.resumeOk', { outcome }))
await reloadPausedRuns()
await reloadRuns()
} catch (e) {
ElMessage.error(t('workflows.paused.resumeFailed', { msg: (e as Error).message }))
mcToast.error(t('workflows.paused.resumeFailed', { msg: (e as Error).message }))
} finally {
resumingId.value = null
}
@ -777,7 +777,7 @@ async function onPublishSubmit(payload: { note: string }) {
// below the fold on smaller viewports.
publishDialogOpen.value = false
if (compileErrors.value.length) {
ElMessage.error(t('workflows.status.compileFailed', { count: compileErrors.value.length }))
mcToast.error(t('workflows.status.compileFailed', { count: compileErrors.value.length }))
}
} finally {
busy.value = false