diff --git a/packages/drawnix/src/drawnix.tsx b/packages/drawnix/src/drawnix.tsx index dec6b92c..a67a5c57 100644 --- a/packages/drawnix/src/drawnix.tsx +++ b/packages/drawnix/src/drawnix.tsx @@ -71,6 +71,8 @@ import { withArrowLineAutoCompleteExtend } from './plugins/with-arrow-line-auto- import { AutoCompleteShapePicker } from './components/auto-complete-shape-picker'; import { useAutoCompleteShapePicker } from './hooks/useAutoCompleteShapePicker'; import { withDefaultFill } from './plugins/with-default-fill'; +import { API_AUTH_ERROR_EVENT, ApiAuthErrorDetail } from './utils/api-auth-error-event'; +import { MessagePlugin } from 'tdesign-react'; const TTDDialog = lazy(() => import('./components/ttd-dialog/ttd-dialog').then(module => ({ default: module.TTDDialog }))); const SettingsDialog = lazy(() => import('./components/settings-dialog/settings-dialog').then(module => ({ default: module.SettingsDialog }))); @@ -224,6 +226,30 @@ export const Drawnix: React.FC = ({ } }, [board]); + // 监听 API 认证错误事件,自动打开设置对话框 + useEffect(() => { + const handleApiAuthError = (event: Event) => { + const customEvent = event as CustomEvent; + const { message } = customEvent.detail; + + // 显示错误提示 + MessagePlugin.error({ + content: 'API Key 无效或已过期,请重新配置', + duration: 5000, + }); + + console.error('[Drawnix] API auth error:', message); + + // 打开设置对话框 + setAppState(prev => ({ ...prev, openSettings: true })); + }; + + window.addEventListener(API_AUTH_ERROR_EVENT, handleApiAuthError); + return () => { + window.removeEventListener(API_AUTH_ERROR_EVENT, handleApiAuthError); + }; + }, []); + // Handle interrupted WorkZone elements after page refresh // Query task status from Service Worker and restore workflow state useEffect(() => { diff --git a/packages/drawnix/src/services/workflow-submission-service.ts b/packages/drawnix/src/services/workflow-submission-service.ts index 67ab58f7..109eaddd 100644 --- a/packages/drawnix/src/services/workflow-submission-service.ts +++ b/packages/drawnix/src/services/workflow-submission-service.ts @@ -12,6 +12,7 @@ import { Subject, Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import type { ParsedGenerationParams } from '../utils/ai-input-parser'; +import { isAuthError, dispatchApiAuthError } from '../utils/api-auth-error-event'; // ============================================================================ // Types @@ -570,6 +571,11 @@ class WorkflowSubmissionService { workflow.error = data.error; } + // 检测 401 认证错误,触发打开设置对话框 + if (data.error && isAuthError(data.error)) { + dispatchApiAuthError({ message: data.error, source: 'workflow' }); + } + this.events$.next({ type: 'failed', workflowId: data.workflowId, diff --git a/packages/drawnix/src/utils/api-auth-error-event.ts b/packages/drawnix/src/utils/api-auth-error-event.ts new file mode 100644 index 00000000..d5bcf17b --- /dev/null +++ b/packages/drawnix/src/utils/api-auth-error-event.ts @@ -0,0 +1,28 @@ +/** + * API 认证错误事件 + * 用于在 API 返回 401 错误时通知主线程打开设置对话框 + */ + +export const API_AUTH_ERROR_EVENT = 'api-auth-error'; + +export interface ApiAuthErrorDetail { + message: string; + source?: string; // 错误来源,如 'chat', 'workflow' 等 +} + +/** + * 检查错误信息是否包含 401 认证错误 + */ +export function isAuthError(error: string): boolean { + return error.includes('401') || + error.toLowerCase().includes('invalid token') || + error.toLowerCase().includes('unauthorized') || + error.toLowerCase().includes('authentication'); +} + +/** + * 触发 API 认证错误事件 + */ +export function dispatchApiAuthError(detail: ApiAuthErrorDetail): void { + window.dispatchEvent(new CustomEvent(API_AUTH_ERROR_EVENT, { detail })); +} diff --git a/packages/drawnix/src/utils/gemini-api/apiCalls.ts b/packages/drawnix/src/utils/gemini-api/apiCalls.ts index a6952875..d94c77c7 100644 --- a/packages/drawnix/src/utils/gemini-api/apiCalls.ts +++ b/packages/drawnix/src/utils/gemini-api/apiCalls.ts @@ -11,6 +11,7 @@ import { shouldUseSWTaskQueue } from '../../services/task-queue'; import { swTaskQueueClient } from '../../services/sw-client'; import { geminiSettings } from '../settings-manager'; import type { ChatParams, ChatMessage as SWChatMessage } from '../../services/sw-client/types'; +import { isAuthError, dispatchApiAuthError } from '../api-auth-error-event'; /** * 确保 SW 客户端已初始化 @@ -303,6 +304,11 @@ async function callApiStreamViaSW( const duration = Date.now() - startTime; console.error('[ApiCalls/SW] Stream error:', error); + // 检测 401 认证错误,触发打开设置对话框 + if (isAuthError(error)) { + dispatchApiAuthError({ message: error, source: 'chat' }); + } + analytics.trackAPICallFailure({ endpoint, model,