diff --git a/app/hosts/[hostname]/components/ScreenTimeTab.tsx b/app/hosts/[hostname]/components/ScreenTimeTab.tsx
index 2d851fb..317aaf8 100644
--- a/app/hosts/[hostname]/components/ScreenTimeTab.tsx
+++ b/app/hosts/[hostname]/components/ScreenTimeTab.tsx
@@ -141,9 +141,6 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) {
@@ -296,6 +293,10 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) {
{app.percentage}%
+
+ 路径: {app.processPath || '路径未知'}
+
+
}
@@ -111,12 +112,14 @@ const getLocalDateUtcMs = (parts: DateParts, tzOffsetMinutes: number) => {
const normalizeProcess = (processPath: string, title: string) => {
const rawProcessName = win32.basename(processPath || '').trim()
+ const rawProcessPath = processPath.trim()
const normalizedTitle = cleanTitle(title)
const fallbackId = normalizedTitle ? `title:${normalizedTitle.toLowerCase()}` : 'unknown'
return {
processId: rawProcessName ? rawProcessName.toLowerCase() : fallbackId,
- rawProcessName
+ rawProcessName,
+ rawProcessPath
}
}
@@ -228,6 +231,7 @@ const addDurationToUsageMap = (
usageMap: Map,
processId: string,
rawProcessName: string,
+ rawProcessPath: string,
title: string,
durationMs: number
) => {
@@ -240,12 +244,16 @@ const addDurationToUsageMap = (
if (!existing.rawProcessName && rawProcessName) {
existing.rawProcessName = rawProcessName
}
+ if (!existing.rawProcessPath && rawProcessPath) {
+ existing.rawProcessPath = rawProcessPath
+ }
return
}
usageMap.set(processId, {
processId,
rawProcessName,
+ rawProcessPath,
durationMs,
titleDurations: new Map([[title, durationMs]])
})
@@ -266,6 +274,7 @@ const buildAppList = (usageMap: Map) => {
processId: app.processId,
processName: resolveProcessDisplayName(app.rawProcessName, sortedTitles),
rawProcessName: app.rawProcessName,
+ processPath: app.rawProcessPath,
durationSeconds: Math.round(app.durationMs / 1000),
percentage: totalDurationMs > 0 ? Number(((app.durationMs / totalDurationMs) * 100).toFixed(2)) : 0,
titleCount: sortedTitles.length,
@@ -384,6 +393,7 @@ async function handleScreenTime(req: NextRequest) {
aggregateUsageMap,
process.processId,
process.rawProcessName,
+ process.rawProcessPath,
title,
aggregateContributionMs
)
@@ -393,6 +403,7 @@ async function handleScreenTime(req: NextRequest) {
selectedDayUsageMap,
process.processId,
process.rawProcessName,
+ process.rawProcessPath,
title,
selectedDayContributionMs
)
diff --git a/app/hosts/[hostname]/screenshots/route.ts b/app/hosts/[hostname]/screenshots/route.ts
index 16fac23..c18e18b 100644
--- a/app/hosts/[hostname]/screenshots/route.ts
+++ b/app/hosts/[hostname]/screenshots/route.ts
@@ -3,6 +3,7 @@ import { prisma } from '@/lib/prisma'
import { storeFile } from '@/lib/fileStorage'
import { push } from '@/lib/push'
import { withCors } from '@/lib/middleware'
+import { filterIgnoredWindows } from '@/lib/windowFilters'
import ffmpeg from 'fluent-ffmpeg'
import { writeFileSync, unlinkSync, mkdtempSync, readFileSync, rmSync } from 'fs'
import { tmpdir } from 'os'
@@ -28,7 +29,8 @@ async function handleScreenshotUpload(req: NextRequest) {
const formData = await req.formData()
const files: File[] = []
- const windowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
+ const rawWindowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
+ const windowsInfo = filterIgnoredWindows(rawWindowsInfo)
// Extract files from formData
for (const [key, value] of formData.entries()) {
diff --git a/app/hosts/[hostname]/types.ts b/app/hosts/[hostname]/types.ts
index 03cf231..86780fb 100644
--- a/app/hosts/[hostname]/types.ts
+++ b/app/hosts/[hostname]/types.ts
@@ -33,6 +33,7 @@ export interface ScreenTimeAppUsage {
processId: string;
processName: string;
rawProcessName: string;
+ processPath: string;
durationSeconds: number;
percentage: number;
titleCount: number;
diff --git a/envtests/cleanup-nvidia-overlay.ts b/envtests/cleanup-nvidia-overlay.ts
new file mode 100644
index 0000000..e51e9ce
--- /dev/null
+++ b/envtests/cleanup-nvidia-overlay.ts
@@ -0,0 +1,66 @@
+#!/usr/bin/env bun
+
+import { prisma } from '../lib/prisma'
+import { IGNORED_WINDOW_KEYWORDS } from '../lib/windowFilters'
+
+async function cleanupIgnoredWindows() {
+ console.log(`开始清理数据库中的忽略窗口记录: ${IGNORED_WINDOW_KEYWORDS.join(', ')}`)
+
+ const whereClause = {
+ OR: [
+ ...IGNORED_WINDOW_KEYWORDS.flatMap(keyword => ([
+ {
+ title: {
+ contains: keyword,
+ mode: 'insensitive' as const
+ }
+ },
+ {
+ path: {
+ contains: keyword,
+ mode: 'insensitive' as const
+ }
+ }
+ ]))
+ ]
+ }
+
+ const [matchedCount, affectedHosts] = await Promise.all([
+ prisma.window.count({ where: whereClause }),
+ prisma.window.findMany({
+ where: whereClause,
+ select: {
+ record: {
+ select: {
+ hostname: true
+ }
+ }
+ },
+ distinct: ['recordId']
+ })
+ ])
+
+ if (matchedCount === 0) {
+ console.log('没有找到需要清理的忽略窗口记录。')
+ return
+ }
+
+ const hostnames = Array.from(new Set(affectedHosts.map(item => item.record.hostname))).sort()
+ console.log(`匹配到 ${matchedCount} 条窗口记录,涉及 ${hostnames.length} 台主机。`)
+ console.log(`主机列表: ${hostnames.join(', ')}`)
+
+ const deleteResult = await prisma.window.deleteMany({
+ where: whereClause
+ })
+
+ console.log(`已删除 ${deleteResult.count} 条忽略窗口记录。`)
+}
+
+cleanupIgnoredWindows()
+ .catch((error) => {
+ console.error('清理忽略窗口失败:', error)
+ process.exitCode = 1
+ })
+ .finally(async () => {
+ await prisma.$disconnect()
+ })
\ No newline at end of file
diff --git a/lib/windowFilters.ts b/lib/windowFilters.ts
new file mode 100644
index 0000000..c0533c5
--- /dev/null
+++ b/lib/windowFilters.ts
@@ -0,0 +1,20 @@
+export const IGNORED_WINDOW_KEYWORDS = [
+ 'NVIDIA Overlay',
+ 'Twinkle Tray Panel'
+] as const
+
+const normalizedIgnoredWindowKeywords = IGNORED_WINDOW_KEYWORDS.map(keyword => keyword.toLowerCase())
+
+interface WindowLike {
+ title?: string | null
+ path?: string | null
+}
+
+export const isIgnoredWindow = ({ title, path }: WindowLike) => {
+ const normalizedValue = `${title ?? ''}\n${path ?? ''}`.toLowerCase()
+ return normalizedIgnoredWindowKeywords.some(keyword => normalizedValue.includes(keyword))
+}
+
+export const filterIgnoredWindows = (windows: T[]) => {
+ return windows.filter(window => !isIgnoredWindow(window))
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index b265531..c357b66 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,9 @@
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:reset": "prisma migrate reset",
- "db:studio": "prisma studio"
+ "db:studio": "prisma studio",
+ "db:cleanup:ignored-windows": "bun envtests/cleanup-nvidia-overlay.ts",
+ "db:cleanup:nvidia-overlay": "bun envtests/cleanup-nvidia-overlay.ts"
},
"dependencies": {
"@prisma/client": "^6.10.1",