From beb712a24733433f86330b5d2100dba6492b1918 Mon Sep 17 00:00:00 2001 From: abu <2261983469@qq.com> Date: Wed, 11 Mar 2026 08:56:46 +0800 Subject: [PATCH] Add authentication handling for search errors and show login modal --- components/SearchBar.tsx | 7 +++++++ hooks/useSearch.ts | 40 ++++++++++++++++++++++++++++++++++++++++ lib/auth-events.ts | 22 ++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 lib/auth-events.ts diff --git a/components/SearchBar.tsx b/components/SearchBar.tsx index bb46ef4..632cde4 100644 --- a/components/SearchBar.tsx +++ b/components/SearchBar.tsx @@ -5,6 +5,7 @@ import { useMatrixStore } from '@/stores/useMatrixStore' import { useSearch } from '@/hooks/useSearch' import { useT } from '@/i18n' import { quotaApi } from '@/lib/api' +import { AUTH_REQUIRED_EVENT } from '@/lib/auth-events' import { getVisitorHeaders } from '@/lib/visitor' export interface SearchBarHandle { @@ -37,6 +38,12 @@ const SearchBar = forwardRef(function SearchBar(_props, ref) { // Fetch quota on mount useEffect(() => { fetchQuota() }, [fetchQuota]) + useEffect(() => { + const onAuthRequired = () => setShowLoginModal((prev) => prev || true) + window.addEventListener(AUTH_REQUIRED_EVENT, onAuthRequired) + return () => window.removeEventListener(AUTH_REQUIRED_EVENT, onAuthRequired) + }, []) + // Refetch quota when search completes (isSearching: true → false) useEffect(() => { if (prevSearchingRef.current && !isSearching) { diff --git a/hooks/useSearch.ts b/hooks/useSearch.ts index a5272c2..c3a67fe 100644 --- a/hooks/useSearch.ts +++ b/hooks/useSearch.ts @@ -9,9 +9,23 @@ import { useRef, useCallback } from 'react' import { useMatrixStore } from '@/stores/useMatrixStore' import { SSEClient } from '@/lib/sse-client' import { searchApi, extractApi, paperApi } from '@/lib/api' +import { emitAuthRequired, isAuthErrorMessage } from '@/lib/auth-events' import { getVisitorHeaders } from '@/lib/visitor' import type { Paper, Column, ColumnSuggestion } from '@/types' +function resolveErrorMessage(payload: unknown): string { + if (typeof payload === 'string') return payload + if ( + payload && + typeof payload === 'object' && + 'message' in payload && + typeof payload.message === 'string' + ) { + return payload.message + } + return '' +} + export function useSearch() { const { isSearching, @@ -68,6 +82,9 @@ export function useSearch() { onError: (err) => { console.error('Extraction SSE error:', err) setIsExtracting(false) + if (isAuthErrorMessage(err.message)) { + emitAuthRequired() + } }, }, getVisitorHeaders()) }, @@ -124,6 +141,13 @@ export function useSearch() { columnIds.push(col.id) // 收到 column 时检查是否已有足够论文,立即开始抽取 tryEarlyExtraction() + } else if (event === 'error') { + const message = resolveErrorMessage(data) + console.error('Search stream event error:', message || data) + setIsSearching(false) + if (isAuthErrorMessage(message)) { + emitAuthRequired() + } } }, onComplete: (data) => { @@ -139,6 +163,9 @@ export function useSearch() { onError: (err) => { console.error('Search SSE error:', err) setIsSearching(false) + if (isAuthErrorMessage(err.message)) { + emitAuthRequired() + } }, }, getVisitorHeaders()) }, @@ -172,6 +199,9 @@ export function useSearch() { onError: (err) => { console.error('Column extraction error:', err) setIsExtracting(false) + if (isAuthErrorMessage(err.message)) { + emitAuthRequired() + } }, }, getVisitorHeaders()) }, @@ -218,6 +248,13 @@ export function useSearch() { if (event === 'paper') { addPaper(data as Paper) newPaperIds.push(data.id) + } else if (event === 'error') { + const message = resolveErrorMessage(data) + console.error('Load more stream event error:', message || data) + setIsSearching(false) + if (isAuthErrorMessage(message)) { + emitAuthRequired() + } } // 加载更多时忽略 session / column 事件 }, @@ -232,6 +269,9 @@ export function useSearch() { onError: (err) => { console.error('Load more error:', err) setIsSearching(false) + if (isAuthErrorMessage(err.message)) { + emitAuthRequired() + } }, }, getVisitorHeaders()) }, diff --git a/lib/auth-events.ts b/lib/auth-events.ts new file mode 100644 index 0000000..a1991bc --- /dev/null +++ b/lib/auth-events.ts @@ -0,0 +1,22 @@ +export const AUTH_REQUIRED_EVENT = 'auth-required' + +const AUTH_EVENT_DEDUPE_MS = 1500 +let lastAuthEventAt = 0 + +export function emitAuthRequired() { + if (typeof window === 'undefined') return + const now = Date.now() + if (now - lastAuthEventAt < AUTH_EVENT_DEDUPE_MS) return + lastAuthEventAt = now + window.dispatchEvent(new Event(AUTH_REQUIRED_EVENT)) +} + +export function isAuthErrorMessage(message: string): boolean { + const text = message.toLowerCase() + return ( + text.includes('401') || + text.includes('unauthorized') || + text.includes('auth token not found in header') || + text.includes('token not found') + ) +}