diff --git a/electron/services/videoService.ts b/electron/services/videoService.ts index 5420076..a24c02c 100644 --- a/electron/services/videoService.ts +++ b/electron/services/videoService.ts @@ -97,7 +97,7 @@ class VideoService { return realMd5 } } catch (e) { - // Silently fail + // 忽略错误 } } } @@ -105,11 +105,22 @@ class VideoService { // 方法2:使用 wcdbService.execQuery 查询加密的 hardlink.db if (dbPath) { - const encryptedDbPaths = [ - join(dbPath, wxid, 'db_storage', 'hardlink', 'hardlink.db'), - join(dbPath, cleanedWxid, 'db_storage', 'hardlink', 'hardlink.db') - ] - + // 检查 dbPath 是否已经包含 wxid + const dbPathLower = dbPath.toLowerCase() + const wxidLower = wxid.toLowerCase() + const cleanedWxidLower = cleanedWxid.toLowerCase() + const dbPathContainsWxid = dbPathLower.includes(wxidLower) || dbPathLower.includes(cleanedWxidLower) + + const encryptedDbPaths: string[] = [] + if (dbPathContainsWxid) { + // dbPath 已包含 wxid,不需要再拼接 + encryptedDbPaths.push(join(dbPath, 'db_storage', 'hardlink', 'hardlink.db')) + } else { + // dbPath 不包含 wxid,需要拼接 + encryptedDbPaths.push(join(dbPath, wxid, 'db_storage', 'hardlink', 'hardlink.db')) + encryptedDbPaths.push(join(dbPath, cleanedWxid, 'db_storage', 'hardlink', 'hardlink.db')) + } + for (const p of encryptedDbPaths) { if (existsSync(p)) { try { @@ -129,6 +140,7 @@ class VideoService { } } } catch (e) { + // 忽略错误 } } } @@ -155,7 +167,6 @@ class VideoService { * 文件命名: {md5}.mp4, {md5}.jpg, {md5}_thumb.jpg */ async getVideoInfo(videoMd5: string): Promise { - const dbPath = this.getDbPath() const wxid = this.getMyWxid() @@ -166,7 +177,19 @@ class VideoService { // 先尝试从数据库查询真正的视频文件名 const realVideoMd5 = await this.queryVideoFileName(videoMd5) || videoMd5 - const videoBaseDir = join(dbPath, wxid, 'msg', 'video') + // 检查 dbPath 是否已经包含 wxid,避免重复拼接 + const dbPathLower = dbPath.toLowerCase() + const wxidLower = wxid.toLowerCase() + const cleanedWxid = this.cleanWxid(wxid) + + let videoBaseDir: string + if (dbPathLower.includes(wxidLower) || dbPathLower.includes(cleanedWxid.toLowerCase())) { + // dbPath 已经包含 wxid,直接使用 + videoBaseDir = join(dbPath, 'msg', 'video') + } else { + // dbPath 不包含 wxid,需要拼接 + videoBaseDir = join(dbPath, wxid, 'msg', 'video') + } if (!existsSync(videoBaseDir)) { return { exists: false } @@ -202,7 +225,7 @@ class VideoService { } } } catch (e) { - console.error('[VideoService] Error searching for video:', e) + // 忽略错误 } return { exists: false } diff --git a/electron/services/wcdbCore.ts b/electron/services/wcdbCore.ts index d6124dd..fe4f18a 100644 --- a/electron/services/wcdbCore.ts +++ b/electron/services/wcdbCore.ts @@ -191,7 +191,7 @@ export class WcdbCore { } private isLogEnabled(): boolean { - if (process.env.WEFLOW_WORKER === '1') return false + // 移除 Worker 线程的日志禁用逻辑,允许在 Worker 中记录日志 if (process.env.WCDB_LOG_ENABLED === '1') return true return this.logEnabled } diff --git a/src/pages/ChatPage.scss b/src/pages/ChatPage.scss index ea3329d..b86bf2d 100644 --- a/src/pages/ChatPage.scss +++ b/src/pages/ChatPage.scss @@ -2146,8 +2146,7 @@ } .video-placeholder, -.video-loading, -.video-unavailable { +.video-loading { min-width: 120px; min-height: 80px; display: flex; @@ -2167,6 +2166,46 @@ } } +.video-unavailable { + min-width: 160px; + min-height: 120px; + border-radius: 12px; + background: var(--bg-tertiary); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + color: var(--text-tertiary); + font-size: 12px; + border: none; + cursor: pointer; + text-align: center; + -webkit-app-region: no-drag; + transition: transform 0.15s ease, box-shadow 0.15s ease; + + svg { + width: 24px; + height: 24px; + opacity: 0.6; + } + + &.clicked { + transform: scale(0.98); + box-shadow: 0 0 0 2px var(--primary-light); + } + + &:disabled { + cursor: default; + opacity: 0.7; + } +} + +.video-action { + font-size: 11px; + color: var(--text-quaternary); +} + .video-loading { .spin { animation: spin 1s linear infinite; diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index fdbc300..ee7da3b 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -2155,6 +2155,9 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o }, [isVoice, message.localId, requestVoiceTranscript]) // 视频懒加载 + const videoAutoLoadTriggered = useRef(false) + const [videoClicked, setVideoClicked] = useState(false) + useEffect(() => { if (!isVideo || !videoContainerRef.current) return @@ -2178,19 +2181,18 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o return () => observer.disconnect() }, [isVideo]) - // 加载视频信息 - useEffect(() => { - if (!isVideo || !isVideoVisible || videoInfo || videoLoading) return - if (!videoMd5) { - - return - } - - + // 视频加载中状态引用,避免依赖问题 + const videoLoadingRef = useRef(false) + + // 加载视频信息(添加重试机制) + const requestVideoInfo = useCallback(async () => { + if (!videoMd5 || videoLoadingRef.current) return + + videoLoadingRef.current = true setVideoLoading(true) - window.electronAPI.video.getVideoInfo(videoMd5).then((result: { success: boolean; exists: boolean; videoUrl?: string; coverUrl?: string; thumbUrl?: string; error?: string }) => { - - if (result && result.success) { + try { + const result = await window.electronAPI.video.getVideoInfo(videoMd5) + if (result && result.success && result.exists) { setVideoInfo({ exists: result.exists, videoUrl: result.videoUrl, @@ -2198,16 +2200,25 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o thumbUrl: result.thumbUrl }) } else { - console.error('[Video Debug] Video info failed:', result) setVideoInfo({ exists: false }) } - }).catch((err: unknown) => { - console.error('[Video Debug] getVideoInfo error:', err) + } catch (err) { setVideoInfo({ exists: false }) - }).finally(() => { + } finally { + videoLoadingRef.current = false setVideoLoading(false) - }) - }, [isVideo, isVideoVisible, videoInfo, videoLoading, videoMd5]) + } + }, [videoMd5]) + + // 视频进入视野时自动加载 + useEffect(() => { + if (!isVideo || !isVideoVisible) return + if (videoInfo?.exists) return // 已成功加载,不需要重试 + if (videoAutoLoadTriggered.current) return + + videoAutoLoadTriggered.current = true + void requestVideoInfo() + }, [isVideo, isVideoVisible, videoInfo, requestVideoInfo]) // 根据设置决定是否自动转写 @@ -2366,16 +2377,27 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o ) } - // 视频不存在 + // 视频不存在 - 添加点击重试功能 if (!videoInfo?.exists || !videoInfo.videoUrl) { return ( -
+
+ 视频未找到 + {videoClicked ? '已点击…' : '点击重试'} + ) }