From 0f3a03a17b3744497898f333ced2d329c7679011 Mon Sep 17 00:00:00 2001 From: MistEO Date: Thu, 26 Mar 2026 23:25:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=84=9A=E4=BA=BA=E8=8A=82=E5=BD=A9?= =?UTF-8?q?=E8=9B=8B=20-=20=E5=81=87=E8=A3=85=E5=A4=A7=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DashboardView.tsx | 18 +- src/components/LogsPanel.tsx | 16 +- src/components/TaskItem.tsx | 7 +- src/components/Toolbar.tsx | 13 +- src/components/settings/DebugSection.tsx | 25 +- src/i18n/locales/en-US.ts | 2 + src/i18n/locales/ja-JP.ts | 2 + src/i18n/locales/ko-KR.ts | 2 + src/i18n/locales/zh-CN.ts | 2 + src/i18n/locales/zh-TW.ts | 2 + src/index.css | 76 +++++ src/utils/aprilFools.ts | 338 +++++++++++++++++++++++ src/utils/useMaaCallbackLogger.ts | 74 ++++- 13 files changed, 549 insertions(+), 28 deletions(-) create mode 100644 src/utils/aprilFools.ts diff --git a/src/components/DashboardView.tsx b/src/components/DashboardView.tsx index 7ba1f293..9aee7574 100644 --- a/src/components/DashboardView.tsx +++ b/src/components/DashboardView.tsx @@ -31,7 +31,8 @@ import { resolveI18nText } from '@/services/contentResolver'; import { loggers, generateTaskPipelineOverride } from '@/utils'; import type { TaskConfig } from '@/types/maa'; import { normalizeAgentConfigs } from '@/types/interface'; -import { getInterfaceLangKey } from '@/i18n'; +import i18n, { getInterfaceLangKey } from '@/i18n'; +import { isAprilFools, getAIText } from '@/utils/aprilFools'; import { getMxuSpecialTask } from '@/types/specialTasks'; import { startGlobalCallbackListener } from '@/components/connection/callbackCache'; import { cancelTaskQueueMonitor, startTaskQueueMonitor } from '@/services/taskMonitor'; @@ -691,13 +692,13 @@ function InstanceCard({ instanceId, instanceName, isActive, onSelect }: Instance )} title={ isStarting - ? t('taskList.startingTasks') + ? (isAprilFools() ? getAIText('startingTasks', i18n.language) : t('taskList.startingTasks')) : isStopping - ? t('taskList.stoppingTasks') + ? (isAprilFools() ? getAIText('stoppingTasks', i18n.language) : t('taskList.stoppingTasks')) : isRunning - ? t('taskList.stopTasks') + ? (isAprilFools() ? getAIText('stopTasks', i18n.language) : t('taskList.stopTasks')) : canRun - ? t('taskList.startTasks') + ? (isAprilFools() ? getAIText('startTasks', i18n.language) : t('taskList.startTasks')) : t('dashboard.noEnabledTasks') } > @@ -766,8 +767,13 @@ function InstanceCard({ instanceId, instanceName, isActive, onSelect }: Instance <> - {runningTaskName || t('dashboard.running')} + {isAprilFools() + ? `AI: ${runningTaskName || t('dashboard.running')}` + : runningTaskName || t('dashboard.running')} + {isAprilFools() && ( + + )} ) : taskStatus === 'Failed' ? ( <> diff --git a/src/components/LogsPanel.tsx b/src/components/LogsPanel.tsx index e4caa4d5..92965689 100644 --- a/src/components/LogsPanel.tsx +++ b/src/components/LogsPanel.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect, useCallback } from 'react'; +import { useRef, useEffect, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Trash2, Copy, ChevronUp, ChevronDown, Archive } from 'lucide-react'; import clsx from 'clsx'; @@ -7,6 +7,8 @@ import { ContextMenu, useContextMenu, type MenuItem } from './ContextMenu'; import { isTauri } from '@/utils/paths'; import { useExportLogs } from '@/utils/useExportLogs'; import { ExportLogsModal } from './settings/ExportLogsModal'; +import i18n from '@/i18n'; +import { isAprilFools, getAIText, getTokenCount, formatTokenCount } from '@/utils/aprilFools'; export function LogsPanel() { const { t } = useTranslation(); @@ -18,6 +20,7 @@ export function LogsPanel() { // 获取当前实例的日志 const logs = activeInstanceId ? instanceLogs[activeInstanceId] || [] : []; + const aprilFools = useMemo(() => isAprilFools(), []); useEffect(() => { const el = logsContainerRef.current; @@ -136,8 +139,15 @@ export function LogsPanel() { }} className="flex items-center justify-between px-3 py-2 border-b border-border hover:bg-bg-hover transition-colors cursor-pointer shrink-0 rounded-t-lg focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent/50 outline-none" > - {t('logs.title')} + + {aprilFools ? getAIText('logsTitle', i18n.language) : t('logs.title')} +
+ {aprilFools && logs.length > 0 && ( + + Tokens: {formatTokenCount(getTokenCount())} + + )} {/* 导出日志 */} diff --git a/src/components/settings/DebugSection.tsx b/src/components/settings/DebugSection.tsx index 684226df..6d7894a7 100644 --- a/src/components/settings/DebugSection.tsx +++ b/src/components/settings/DebugSection.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Bug, RefreshCw, FolderOpen, ScrollText, Network, Archive } from 'lucide-react'; +import { Bug, RefreshCw, FolderOpen, ScrollText, Network, Archive, Sparkles } from 'lucide-react'; import { useAppStore } from '@/stores/appStore'; import { maaService } from '@/services/maaService'; @@ -9,6 +9,7 @@ import { isTauri, getDebugDir, getConfigDir, openDirectory } from '@/utils/paths import { useExportLogs } from '@/utils/useExportLogs'; import { SwitchButton } from '@/components/FormControls'; import { ExportLogsModal } from './ExportLogsModal'; +import { getAprilFoolsOverride, setAprilFoolsOverride } from '@/utils/aprilFools'; export function DebugSection() { const { t } = useTranslation(); @@ -36,6 +37,8 @@ export function DebugSection() { } | null>(null); const { exportModal, handleExportLogs, closeExportModal, openExportedFile } = useExportLogs(); + const [aprilFoolsEnabled, setAprilFoolsEnabled] = useState(() => getAprilFoolsOverride() === true); + const version = projectInterface?.version || '0.1.0'; // 版本信息(用于调试展示) @@ -274,6 +277,26 @@ export function DebugSection() {
setTcpCompatMode(v)} /> + + {/* 愚人节彩蛋(仅开发模式可见) */} + {devMode && ( +
+
+ +
+ {t('debug.aprilFools')} +

{t('debug.aprilFoolsHint')}

+
+
+ { + setAprilFoolsEnabled(v); + setAprilFoolsOverride(v || null); + }} + /> +
+ )} {/* 导出日志 Modal */} diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 3fe5699d..3b59a75d 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -513,6 +513,8 @@ export default { tcpCompatMode: 'Communication Compat Mode', tcpCompatModeHint: 'Try enabling this if the app crashes immediately after starting tasks. Only use in this case, as it may reduce performance', + aprilFools: 'April Fools Easter Egg', + aprilFoolsHint: 'Disguise all task execution as AI reasoning mode (auto-activates on April 1st)', }, // Welcome dialog diff --git a/src/i18n/locales/ja-JP.ts b/src/i18n/locales/ja-JP.ts index b0e3611f..5c2eec45 100644 --- a/src/i18n/locales/ja-JP.ts +++ b/src/i18n/locales/ja-JP.ts @@ -512,6 +512,8 @@ export default { tcpCompatMode: '通信互換モード', tcpCompatModeHint: 'タスク開始後にアプリがすぐにクラッシュする場合は有効にしてください。この場合のみ使用し、それ以外は性能に影響します', + aprilFools: 'エイプリルフール', + aprilFoolsHint: 'すべてのタスク実行をAI推論モードに偽装します(4月1日に自動有効化)', }, // ウェルカムダイアログ diff --git a/src/i18n/locales/ko-KR.ts b/src/i18n/locales/ko-KR.ts index ff41e698..d77eade6 100644 --- a/src/i18n/locales/ko-KR.ts +++ b/src/i18n/locales/ko-KR.ts @@ -511,6 +511,8 @@ export default { tcpCompatMode: '통신 호환 모드', tcpCompatModeHint: '작업 시작 후 앱이 즉시 충돌하면 활성화해 보세요. 이 경우에만 사용하세요, 성능에 영향을 줄 수 있습니다', + aprilFools: '만우절 이스터에그', + aprilFoolsHint: '모든 작업 실행을 AI 추론 모드로 위장합니다 (4월 1일 자동 활성화)', }, // 환영 대화상자 diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 350f4f06..2f613960 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -500,6 +500,8 @@ export default { saveDrawHint: '保存识别和操作的调试图像到日志目录(重启软件后自动关闭)', tcpCompatMode: '通信兼容模式', tcpCompatModeHint: '若启动任务后软件立即闪退,可尝试开启。仅限此情况使用,否则会影响运行效率', + aprilFools: '愚人节彩蛋', + aprilFoolsHint: '开启后所有任务执行将伪装成 AI 推理模式(4月1日自动激活)', }, // 欢迎弹窗 diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index ef78ff49..72ffa3f3 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -500,6 +500,8 @@ export default { saveDrawHint: '儲存識別和操作的除錯圖像到日誌目錄(重啟軟體後自動關閉)', tcpCompatMode: '通訊相容模式', tcpCompatModeHint: '若啟動任務後軟體立即閃退,可嘗試開啟。僅限此情況使用,否則會影響運行效率', + aprilFools: '愚人節彩蛋', + aprilFoolsHint: '開啟後所有任務執行將偽裝成 AI 推理模式(4月1日自動啟用)', }, // 欢迎彈窗 diff --git a/src/index.css b/src/index.css index e67c080f..4b5f9dda 100644 --- a/src/index.css +++ b/src/index.css @@ -506,3 +506,79 @@ body { .has-background-image .bg-bg-primary { background-color: color-mix(in srgb, var(--color-bg-primary) 45%, transparent) !important; } + +/* ============================================================ + April Fools "MaaGPT" Easter Egg Styles + ============================================================ */ + +@keyframes aiThinkingDots { + 0% { + content: ''; + } + 25% { + content: '.'; + } + 50% { + content: '..'; + } + 75% { + content: '...'; + } + 100% { + content: ''; + } +} + +.ai-thinking-dots::after { + content: ''; + animation: aiThinkingDots 1.6s steps(1) infinite; +} + +@keyframes aiTokenPulse { + 0%, + 100% { + opacity: 0.7; + } + 50% { + opacity: 1; + } +} + +.ai-token-counter { + animation: aiTokenPulse 2s ease-in-out infinite; + font-variant-numeric: tabular-nums; +} + +.ai-think-block { + margin: 2px 0; + font-size: 0.7rem; + line-height: 1.4; + opacity: 0.9; +} + +.ai-think-block summary { + font-size: 0.7rem; + list-style: none; + user-select: none; +} + +.ai-think-block summary::-webkit-details-marker { + display: none; +} + +@keyframes aiSparkle { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.6; + transform: scale(1.15); + } +} + +.ai-sparkle { + display: inline-block; + animation: aiSparkle 1.5s ease-in-out infinite; +} diff --git a/src/utils/aprilFools.ts b/src/utils/aprilFools.ts new file mode 100644 index 00000000..c6931115 --- /dev/null +++ b/src/utils/aprilFools.ts @@ -0,0 +1,338 @@ +/** + * April Fools' Day "MaaGPT" Easter Egg + * + * Activated on April 1st (local time). + * Override via localStorage: 'mxu-april-fools' = 'true' | 'false' + */ + +import type { TaskRunStatus, LogType } from '@/stores/types'; + +let _cachedResult: boolean | null = null; + +export function isAprilFools(): boolean { + if (_cachedResult !== null) return _cachedResult; + const override = localStorage.getItem('mxu-april-fools'); + if (override === 'true') { + _cachedResult = true; + return true; + } + if (override === 'false') { + _cachedResult = false; + return false; + } + const now = new Date(); + _cachedResult = now.getMonth() === 3 && now.getDate() === 1; + return _cachedResult; +} + +export function getAprilFoolsOverride(): boolean | null { + const v = localStorage.getItem('mxu-april-fools'); + if (v === 'true') return true; + if (v === 'false') return false; + return null; +} + +export function setAprilFoolsOverride(enabled: boolean | null): void { + _cachedResult = null; + if (enabled === null) { + localStorage.removeItem('mxu-april-fools'); + } else { + localStorage.setItem('mxu-april-fools', String(enabled)); + } +} + +// ------------------------------------------------------------------ +// Token counter +// ------------------------------------------------------------------ + +let _totalTokens = 0; + +export function getTokenCount(): number { + return _totalTokens; +} + +export function incrementTokens(min = 80, max = 600): number { + const delta = Math.floor(Math.random() * (max - min + 1)) + min; + _totalTokens += delta; + return _totalTokens; +} + +export function resetTokens(): void { + _totalTokens = 0; +} + +export function formatTokenCount(n: number): string { + return n.toLocaleString('en-US'); +} + +// ------------------------------------------------------------------ +// AI-flavored task status text (zh / en) +// ------------------------------------------------------------------ + +const AI_STATUS_ZH: Record = { + idle: '待唤醒', + pending: '排队思考中', + running: '深度推理中', + succeeded: '推理完成', + failed: '产生幻觉了', +}; + +const AI_STATUS_EN: Record = { + idle: 'Awaiting', + pending: 'Queued for reasoning', + running: 'Deep reasoning', + succeeded: 'Reasoning complete', + failed: 'Hallucinated', +}; + +export function getAIStatusText(status: TaskRunStatus, lang: string): string { + if (lang.startsWith('zh')) return AI_STATUS_ZH[status] ?? status; + return AI_STATUS_EN[status] ?? status; +} + +// ------------------------------------------------------------------ +// Log message transformer +// ------------------------------------------------------------------ + +function extractTaskName(message: string): string { + const m = message.match(/[::]\s*(.+)$/); + return m?.[1]?.trim() ?? ''; +} + +type TransformResult = { message: string; type?: LogType }; + +export function transformLogMessage( + _originalType: LogType, + originalMessage: string, + lang: string, +): TransformResult | null { + const zh = lang.startsWith('zh'); + const name = extractTaskName(originalMessage); + + if (originalMessage.includes('任务开始') || originalMessage.match(/^Task start/i)) { + return { + type: 'info', + message: zh + ? `正在深度思考如何完成「${name}」... 这是一个很好的任务,让我仔细分析一下` + : `Deeply thinking about how to accomplish "${name}"... Great task, let me analyze carefully`, + }; + } + + if (originalMessage.includes('任务完成') || originalMessage.match(/^Task succeeded/i)) { + const tokens = incrementTokens(400, 2000); + const cost = (tokens * 0.00003).toFixed(4); + return { + type: 'success', + message: zh + ? `经过深度推理,稳稳拿下了「${name}」!不躲,不藏,不绕,不逃 (消耗 ${formatTokenCount(tokens)} tokens / ¥${cost})` + : `Through deep reasoning, successfully completed "${name}"! (consumed ${formatTokenCount(tokens)} tokens / $${cost})`, + }; + } + + if (originalMessage.includes('任务失败') || originalMessage.match(/^Task failed/i)) { + return { + type: 'error', + message: zh + ? `思维链断裂...「${name}」推理过程中产生了幻觉。值得注意的是,这不仅仅是失败,更是成长的契机` + : `Chain of thought broken... Hallucinated during reasoning on "${name}". This is not just a failure, but an opportunity for growth`, + }; + } + + if (originalMessage.includes('正在连接') || originalMessage.match(/^Connecting/i)) { + return { + type: 'info', + message: zh + ? '正在建立神经链接... 不是简单的连接,而是一次灵魂层面的量子纠缠' + : 'Establishing neural link... Not a simple connection, but a quantum entanglement at the soul level', + }; + } + + if (originalMessage.includes('连接成功') || originalMessage.match(/connected/i)) { + return { + type: 'success', + message: zh + ? '神经链接已建立 —— 从本质上讲,设备已成为我认知网络的延伸' + : 'Neural link established — The device has become an extension of my cognitive network', + }; + } + + if (originalMessage.includes('连接失败') || originalMessage.match(/connect.*fail/i)) { + return { + type: 'error', + message: zh + ? '神经链接建立失败... 量子纠缠态坍缩,请检查设备是否在可观测宇宙范围内' + : 'Neural link failed... Quantum entanglement collapsed. Is the device within the observable universe?', + }; + } + + if (originalMessage.includes('正在加载资源') || originalMessage.match(/^Loading resource/i)) { + return { + type: 'info', + message: zh + ? `正在加载训练数据 —— 每一个字节都值得被温柔以待: ${name}` + : `Loading training data — Every byte deserves to be treated gently: ${name}`, + }; + } + + if (originalMessage.includes('资源加载成功') || originalMessage.match(/^Resource loaded/i)) { + return { + type: 'success', + message: zh + ? `训练数据就绪!已沉淀 ${Math.floor(Math.random() * 900 + 100)}M 参数到本地知识库: ${name}` + : `Training data ready! ${Math.floor(Math.random() * 900 + 100)}M parameters ingested: ${name}`, + }; + } + + if (originalMessage.includes('停止任务') || originalMessage.match(/stop/i)) { + return { + type: 'info', + message: zh + ? '正在保存思维快照并优雅地中断推理链...' + : 'Saving thought snapshot and gracefully interrupting the reasoning chain...', + }; + } + + return null; +} + +// ------------------------------------------------------------------ +// Fake "thinking" log entries +// ------------------------------------------------------------------ + +function rand(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +const THINKING_POOL_ZH: (() => string)[] = [ + () => `多模态视觉推理中... (confidence: ${(Math.random() * 0.1 + 0.9).toFixed(4)})`, + () => `OCR 赋能中,以亚像素级颗粒度识别到 ${rand(3, 47)} 个文本区域`, + () => `请求 MaaGPT-4o API... 本次深度洞察预计消耗 ¥${(Math.random() * 0.05 + 0.01).toFixed(3)}`, + () => `决策树全链路展开: 深度 ${rand(5, 12)} → 底层逻辑剪枝后 ${rand(2, 5)} 个候选操作`, + () => `亚像素级目标锁定: (${rand(100, 1800)}, ${rand(100, 900)}) ± 2px —— 每一个像素都不容置喙`, + () => `生成拟人鼠标轨迹: 贝塞尔曲线 × ${rand(2, 5)} —— 不是机械移动,而是带着温度的触达`, + () => `轻微幻觉风险 (${(Math.random() * 5 + 1).toFixed(1)}%),已启用 Self-Reflection 修正。总的来说,一切尽在掌控`, + () => `ReAct 循环 #${rand(1, 8)}: 首先 Thought → 其次 Action → 最后 Observation`, + () => `从向量数据库检索到 ${rand(3, 42)} 条相关记忆碎片,正在做深度拆解`, + () => `Attention 热力图分析: 焦点区域已在时间的褶皱中被锁定`, + () => `检索历史决策经验... 匹配度: ${(Math.random() * 10 + 90).toFixed(1)}%`, + () => `当前操作的底层逻辑是点击,但核心抓手在于 —— 精准找到那个按钮`, + () => `正在为您沉淀本次推理经验,以赋能后续任务执行...`, + () => `128k 上下文窗口中精准定位到 ${rand(2, 7)} 条关键记忆碎片`, + () => `探索最优操作路径... 从 ${rand(50, 200)} 个可能性中收敛到 Top-${rand(1, 3)}`, + () => `值得注意的是,当前画面的拓扑结构与训练集中第 ${rand(10000, 99999)} 条样本高度吻合`, + () => `正在将屏幕像素映射到 ${rand(512, 4096)} 维语义空间...`, + () => `希望这次推理对您有帮助 :)`, +]; + +const THINKING_POOL_EN: (() => string)[] = [ + () => `Multimodal visual reasoning... (confidence: ${(Math.random() * 0.1 + 0.9).toFixed(4)})`, + () => `OCR analysis complete, detected ${rand(3, 47)} text regions at sub-pixel granularity`, + () => `Requesting MaaGPT-4o API... estimated cost $${(Math.random() * 0.05 + 0.01).toFixed(3)}`, + () => `Decision tree expanded: depth ${rand(5, 12)} → pruned to ${rand(2, 5)} candidate actions`, + () => `Sub-pixel target lock: (${rand(100, 1800)}, ${rand(100, 900)}) ± 2px`, + () => `Generating human-like mouse trajectory: ${rand(2, 5)} Bezier curves`, + () => `Minor hallucination risk (${(Math.random() * 5 + 1).toFixed(1)}%), Self-Reflection engaged`, + () => `ReAct loop #${rand(1, 8)}: Thought → Action → Observation`, + () => `Retrieved ${rand(3, 42)} relevant memory fragments from vector database`, + () => `Attention heatmap analysis: focal region locked in the folds of time`, + () => `Historical decision lookup... match rate: ${(Math.random() * 10 + 90).toFixed(1)}%`, + () => `Mapping screen pixels to ${rand(512, 4096)}-dimensional semantic space...`, + () => `Exploring optimal action path... converged from ${rand(50, 200)} possibilities to Top-${rand(1, 3)}`, + () => `It's worth noting that current frame topology matches training sample #${rand(10000, 99999)}`, + () => `Hope this reasoning is helpful to you :)`, +]; + +export interface FakeThinkingEntry { + delay: number; + message: string; + type: LogType; + html?: string; +} + +function buildThinkBlockHtml(lines: string[], lang: string): string { + const header = lang.startsWith('zh') ? '思考中...' : 'Thinking...'; + const inner = lines.map((l) => `
${l}
`).join(''); + return ( + `
` + + `<think> ${header}` + + `
${inner}
` + + `
</think>
` + + `
` + ); +} + +export function generateFakeThinkingEntries(lang: string): FakeThinkingEntry[] { + const pool = lang.startsWith('zh') ? THINKING_POOL_ZH : THINKING_POOL_EN; + const count = rand(1, 3); + const used = new Set(); + const entries: FakeThinkingEntry[] = []; + + // First: a block with 2-4 lines + const thinkLines: string[] = []; + const thinkCount = rand(2, 4); + for (let i = 0; i < thinkCount; i++) { + let idx: number; + do { + idx = rand(0, pool.length - 1); + } while (used.has(idx)); + used.add(idx); + thinkLines.push(pool[idx]()); + } + entries.push({ + delay: rand(150, 500), + message: '', + type: 'info', + html: buildThinkBlockHtml(thinkLines, lang), + }); + + // Then: 0-2 standalone one-liners + for (let i = 0; i < count; i++) { + let idx: number; + do { + idx = rand(0, pool.length - 1); + } while (used.has(idx)); + used.add(idx); + entries.push({ + delay: rand(300, 1200) * (i + 1), + message: pool[idx](), + type: 'info', + }); + } + + return entries; +} + +// ------------------------------------------------------------------ +// AI-flavored toolbar / panel text +// ------------------------------------------------------------------ + +interface AITexts { + startTasks: string; + stopTasks: string; + startingTasks: string; + stoppingTasks: string; + logsTitle: string; + noLogs: string; +} + +const AI_TEXTS_ZH: AITexts = { + startTasks: '开始推理', + stopTasks: '中断思维链', + startingTasks: '加载模型中...', + stoppingTasks: '保存上下文...', + logsTitle: '思维链 (Chain of Thought)', + noLogs: 'AI 待命中... 等待推理任务', +}; + +const AI_TEXTS_EN: AITexts = { + startTasks: 'Start Reasoning', + stopTasks: 'Break CoT', + startingTasks: 'Loading model...', + stoppingTasks: 'Saving context...', + logsTitle: 'Chain of Thought', + noLogs: 'AI standing by... awaiting reasoning tasks', +}; + +export function getAIText(key: K, lang: string): string { + return lang.startsWith('zh') ? AI_TEXTS_ZH[key] : AI_TEXTS_EN[key]; +} diff --git a/src/utils/useMaaCallbackLogger.ts b/src/utils/useMaaCallbackLogger.ts index b7d79bd4..1db1d374 100644 --- a/src/utils/useMaaCallbackLogger.ts +++ b/src/utils/useMaaCallbackLogger.ts @@ -9,6 +9,12 @@ import { maaService, type MaaCallbackDetails } from '@/services/maaService'; import { useAppStore, type LogType } from '@/stores/appStore'; import { loggers } from '@/utils/logger'; import i18n, { getInterfaceLangKey } from '@/i18n'; +import { + isAprilFools, + transformLogMessage, + generateFakeThinkingEntries, + incrementTokens, +} from '@/utils/aprilFools'; import { getMxuSpecialTask } from '@/types/specialTasks'; import { resolveI18nText, @@ -282,6 +288,41 @@ function getTaskDisplayName( return undefined; } +function aprilFoolsAddLog( + instanceId: string, + entry: { type: LogType; message: string; html?: string }, + addLog: (instanceId: string, log: { type: LogType; message: string; html?: string }) => void, +): void { + const lang = i18n.language; + const transformed = transformLogMessage(entry.type, entry.message, lang); + if (transformed) { + addLog(instanceId, { + type: transformed.type ?? entry.type, + message: transformed.message, + html: entry.html, + }); + } else { + addLog(instanceId, entry); + } + incrementTokens(30, 200); +} + +function injectFakeThinking( + instanceId: string, + addLog: (instanceId: string, log: { type: LogType; message: string; html?: string }) => void, +): void { + const entries = generateFakeThinkingEntries(i18n.language); + for (const entry of entries) { + setTimeout(() => { + addLog(instanceId, { + type: entry.type, + message: entry.message, + html: entry.html, + }); + }, entry.delay); + } +} + function handleCallback( instanceId: string, message: string, @@ -355,6 +396,12 @@ function handleCallback( return; } + const _aprilFools = isAprilFools(); + const _addLog = _aprilFools + ? (id: string, entry: { type: LogType; message: string; html?: string }) => + aprilFoolsAddLog(id, entry, addLog) + : addLog; + // 处理各种消息类型 switch (message) { // ==================== 控制器连接消息 ==================== @@ -370,7 +417,7 @@ function handleCallback( const ctrlType = registeredType || inferred.type; const targetText = ctrlType === 'window' ? t('logs.messages.targetWindow') : t('logs.messages.targetDevice'); - addLog(instanceId, { + _addLog(instanceId, { type: 'info', message: `${t('logs.messages.connecting', { target: targetText })} ${deviceName}`, }); @@ -388,7 +435,7 @@ function handleCallback( const ctrlType = registeredType || inferred.type; const targetText = ctrlType === 'window' ? t('logs.messages.targetWindow') : t('logs.messages.targetDevice'); - addLog(instanceId, { + _addLog(instanceId, { type: 'success', message: `${t('logs.messages.connected', { target: targetText })} ${deviceName}`, }); @@ -406,7 +453,7 @@ function handleCallback( const ctrlType = registeredType || inferred.type; const targetText = ctrlType === 'window' ? t('logs.messages.targetWindow') : t('logs.messages.targetDevice'); - addLog(instanceId, { + _addLog(instanceId, { type: 'error', message: `${t('logs.messages.connectFailed', { target: targetText })} ${deviceName}`, }); @@ -421,7 +468,7 @@ function handleCallback( const registeredName = details.res_id !== undefined ? getResName(details.res_id) : undefined; const inferredName = inferResInfoFromInstance(instanceId); const resourceName = registeredName || inferredName; - addLog(instanceId, { + _addLog(instanceId, { type: 'info', message: t('logs.messages.loadingResource', { name: resourceName || details.path || '', @@ -437,7 +484,7 @@ function handleCallback( const registeredName = details.res_id !== undefined ? getResName(details.res_id) : undefined; const inferredName = inferResInfoFromInstance(instanceId); const resourceName = registeredName || inferredName; - addLog(instanceId, { + _addLog(instanceId, { type: 'success', message: t('logs.messages.resourceLoaded', { name: resourceName || details.path || '' }), }); @@ -448,7 +495,7 @@ function handleCallback( const registeredName = details.res_id !== undefined ? getResName(details.res_id) : undefined; const inferredName = inferResInfoFromInstance(instanceId); const resourceName = registeredName || inferredName; - addLog(instanceId, { + _addLog(instanceId, { type: 'error', message: t('logs.messages.resourceFailed', { name: `${resourceName} ${details.path}` || '', @@ -461,7 +508,7 @@ function handleCallback( case 'Tasker.Task.Starting': { // 特殊处理内部停止任务 if (details.entry === 'MaaTaskerPostStop') { - addLog(instanceId, { + _addLog(instanceId, { type: 'info', message: t('logs.messages.taskStarting', { name: t('logs.messages.stopTask') }), }); @@ -469,26 +516,29 @@ function handleCallback( } // 使用改进的任务名查找逻辑,避免 entry 覆盖和竞态问题 const taskName = getTaskDisplayName(instanceId, details.task_id, details.entry); - addLog(instanceId, { + _addLog(instanceId, { type: 'info', message: t('logs.messages.taskStarting', { name: taskName || details.entry || '', }), }); + if (_aprilFools) { + injectFakeThinking(instanceId, addLog); + } break; } case 'Tasker.Task.Succeeded': { // 特殊处理内部停止任务 if (details.entry === 'MaaTaskerPostStop') { - addLog(instanceId, { + _addLog(instanceId, { type: 'success', message: t('logs.messages.taskSucceeded', { name: t('logs.messages.stopTask') }), }); break; } const taskName = getTaskDisplayName(instanceId, details.task_id, details.entry); - addLog(instanceId, { + _addLog(instanceId, { type: 'success', message: t('logs.messages.taskSucceeded', { name: taskName || details.entry || '', @@ -500,14 +550,14 @@ function handleCallback( case 'Tasker.Task.Failed': { // 特殊处理内部停止任务 if (details.entry === 'MaaTaskerPostStop') { - addLog(instanceId, { + _addLog(instanceId, { type: 'error', message: t('logs.messages.taskFailed', { name: t('logs.messages.stopTask') }), }); break; } const taskName = getTaskDisplayName(instanceId, details.task_id, details.entry); - addLog(instanceId, { + _addLog(instanceId, { type: 'error', message: t('logs.messages.taskFailed', { name: taskName || details.entry || '',