Merge pull request #176 from xunchahaha:dev

Dev
This commit is contained in:
xuncha
2026-02-02 22:58:09 +08:00
committed by GitHub
4 changed files with 118 additions and 34 deletions

View File

@@ -97,7 +97,7 @@ class VideoService {
return realMd5 return realMd5
} }
} catch (e) { } catch (e) {
// Silently fail // 忽略错误
} }
} }
} }
@@ -105,10 +105,21 @@ class VideoService {
// 方法2使用 wcdbService.execQuery 查询加密的 hardlink.db // 方法2使用 wcdbService.execQuery 查询加密的 hardlink.db
if (dbPath) { if (dbPath) {
const encryptedDbPaths = [ // 检查 dbPath 是否已经包含 wxid
join(dbPath, wxid, 'db_storage', 'hardlink', 'hardlink.db'), const dbPathLower = dbPath.toLowerCase()
join(dbPath, cleanedWxid, 'db_storage', 'hardlink', 'hardlink.db') 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) { for (const p of encryptedDbPaths) {
if (existsSync(p)) { if (existsSync(p)) {
@@ -129,6 +140,7 @@ class VideoService {
} }
} }
} catch (e) { } catch (e) {
// 忽略错误
} }
} }
} }
@@ -155,7 +167,6 @@ class VideoService {
* 文件命名: {md5}.mp4, {md5}.jpg, {md5}_thumb.jpg * 文件命名: {md5}.mp4, {md5}.jpg, {md5}_thumb.jpg
*/ */
async getVideoInfo(videoMd5: string): Promise<VideoInfo> { async getVideoInfo(videoMd5: string): Promise<VideoInfo> {
const dbPath = this.getDbPath() const dbPath = this.getDbPath()
const wxid = this.getMyWxid() const wxid = this.getMyWxid()
@@ -166,7 +177,19 @@ class VideoService {
// 先尝试从数据库查询真正的视频文件名 // 先尝试从数据库查询真正的视频文件名
const realVideoMd5 = await this.queryVideoFileName(videoMd5) || videoMd5 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)) { if (!existsSync(videoBaseDir)) {
return { exists: false } return { exists: false }
@@ -202,7 +225,7 @@ class VideoService {
} }
} }
} catch (e) { } catch (e) {
console.error('[VideoService] Error searching for video:', e) // 忽略错误
} }
return { exists: false } return { exists: false }

View File

@@ -191,7 +191,7 @@ export class WcdbCore {
} }
private isLogEnabled(): boolean { private isLogEnabled(): boolean {
if (process.env.WEFLOW_WORKER === '1') return false // 移除 Worker 线程的日志禁用逻辑,允许在 Worker 中记录日志
if (process.env.WCDB_LOG_ENABLED === '1') return true if (process.env.WCDB_LOG_ENABLED === '1') return true
return this.logEnabled return this.logEnabled
} }

View File

@@ -2146,8 +2146,7 @@
} }
.video-placeholder, .video-placeholder,
.video-loading, .video-loading {
.video-unavailable {
min-width: 120px; min-width: 120px;
min-height: 80px; min-height: 80px;
display: flex; 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 { .video-loading {
.spin { .spin {
animation: spin 1s linear infinite; animation: spin 1s linear infinite;

View File

@@ -2155,6 +2155,9 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
}, [isVoice, message.localId, requestVoiceTranscript]) }, [isVoice, message.localId, requestVoiceTranscript])
// 视频懒加载 // 视频懒加载
const videoAutoLoadTriggered = useRef(false)
const [videoClicked, setVideoClicked] = useState(false)
useEffect(() => { useEffect(() => {
if (!isVideo || !videoContainerRef.current) return if (!isVideo || !videoContainerRef.current) return
@@ -2178,19 +2181,18 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
return () => observer.disconnect() return () => observer.disconnect()
}, [isVideo]) }, [isVideo])
// 加载视频信息 // 视频加载中状态引用,避免依赖问题
useEffect(() => { const videoLoadingRef = useRef(false)
if (!isVideo || !isVideoVisible || videoInfo || videoLoading) return
if (!videoMd5) {
return
}
// 加载视频信息(添加重试机制)
const requestVideoInfo = useCallback(async () => {
if (!videoMd5 || videoLoadingRef.current) return
videoLoadingRef.current = true
setVideoLoading(true) setVideoLoading(true)
window.electronAPI.video.getVideoInfo(videoMd5).then((result: { success: boolean; exists: boolean; videoUrl?: string; coverUrl?: string; thumbUrl?: string; error?: string }) => { try {
const result = await window.electronAPI.video.getVideoInfo(videoMd5)
if (result && result.success) { if (result && result.success && result.exists) {
setVideoInfo({ setVideoInfo({
exists: result.exists, exists: result.exists,
videoUrl: result.videoUrl, videoUrl: result.videoUrl,
@@ -2198,16 +2200,25 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
thumbUrl: result.thumbUrl thumbUrl: result.thumbUrl
}) })
} else { } else {
console.error('[Video Debug] Video info failed:', result)
setVideoInfo({ exists: false }) setVideoInfo({ exists: false })
} }
}).catch((err: unknown) => { } catch (err) {
console.error('[Video Debug] getVideoInfo error:', err)
setVideoInfo({ exists: false }) setVideoInfo({ exists: false })
}).finally(() => { } finally {
videoLoadingRef.current = false
setVideoLoading(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) { if (!videoInfo?.exists || !videoInfo.videoUrl) {
return ( return (
<div className="video-unavailable" ref={videoContainerRef}> <button
className={`video-unavailable ${videoClicked ? 'clicked' : ''}`}
ref={videoContainerRef}
onClick={() => {
setVideoClicked(true)
setTimeout(() => setVideoClicked(false), 800)
videoAutoLoadTriggered.current = false
void requestVideoInfo()
}}
type="button"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polygon points="23 7 16 12 23 17 23 7"></polygon> <polygon points="23 7 16 12 23 17 23 7"></polygon>
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect> <rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
</svg> </svg>
<span></span> <span></span>
</div> <span className="video-action">{videoClicked ? '已点击…' : '点击重试'}</span>
</button>
) )
} }