Compare commits

..

No commits in common. "f7f0e2fb738157139d374d2f4a90d83d4ef10bc7" and "d1c5b818d9a249a2516374e80873473eec2de191" have entirely different histories.

7 changed files with 8 additions and 153 deletions

View File

@ -141,6 +141,9 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) {
<h2 className="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
{periodLabel}
</h2>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
@ -293,10 +296,6 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) {
<span>{app.percentage}%</span>
</div>
<div className="mt-2 truncate text-xs text-gray-500 dark:text-gray-400" title={app.processPath || '路径未知'}>
: {app.processPath || '路径未知'}
</div>
<div className="mt-3 h-2 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
<div
className="h-full rounded-full bg-blue-600 dark:bg-blue-500"

View File

@ -28,7 +28,6 @@ interface TitleUsage {
interface MutableAppUsage {
processId: string
rawProcessName: string
rawProcessPath: string
durationMs: number
titleDurations: Map<string, number>
}
@ -44,29 +43,6 @@ interface ScreenTimeRecordRow {
path: string | null
}
const PROCESS_DISPLAY_NAME_PRESETS = [
{ match: 'microsoft vs code\\code.exe', displayName: 'Visual Studio Code' },
{ match: 'msedge.exe', displayName: 'Microsoft Edge' },
{ match: 'windowsTerminal.exe', displayName: '终端' },
{ match: 'windowsterminal.exe', displayName: '终端' },
{ match: 'blender.exe', displayName: 'Blender' },
{ match: 'chrome.exe', displayName: 'Google Chrome' },
{ match: 'steamwebhelper.exe', displayName: 'Steam' },
{ match: 'steam.exe', displayName: 'Steam' },
{ match: 'bandizip.exe', displayName: 'Bandizip' },
{ match: 'explorer.exe', displayName: '文件资源管理器' },
{ match: 'devenv.exe', displayName: 'Visual Studio' },
{ match: 'idea64.exe', displayName: 'IntelliJ IDEA' },
{ match: 'pycharm64.exe', displayName: 'PyCharm' },
{ match: 'cursor.exe', displayName: 'Cursor' },
{ match: 'obsidian.exe', displayName: 'Obsidian' },
{ match: 'notion.exe', displayName: 'Notion' },
{ match: 'wechat.exe', displayName: '微信' },
{ match: 'qq.exe', displayName: 'QQ' },
{ match: 'teams.exe', displayName: 'Microsoft Teams' },
{ match: 'telegram.exe', displayName: 'Telegram' }
] as const
const cleanTitle = (title: string) => title.replace(/\s+/g, ' ').trim()
const trimLabelSeparators = (label: string) => label
@ -77,8 +53,6 @@ const trimLabelSeparators = (label: string) => label
const stripExtension = (value: string) => value.replace(/\.[^.]+$/, '')
const normalizeProcessPathForMatch = (value: string) => value.replace(/\//g, '\\').toLowerCase()
const toDateKey = ({ year, month, day }: DateParts) => `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
const parseDateParts = (value: string | null): DateParts | null => {
@ -137,14 +111,12 @@ 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,
rawProcessPath
rawProcessName
}
}
@ -230,24 +202,7 @@ const buildCommonSubstringLabel = (titles: TitleUsage[]) => {
return trimLabelSeparators(shared)
}
const resolvePresetProcessDisplayName = (rawProcessName: string, rawProcessPath: string) => {
const normalizedProcessName = rawProcessName.toLowerCase()
const normalizedProcessPath = normalizeProcessPathForMatch(rawProcessPath)
const matchedPreset = PROCESS_DISPLAY_NAME_PRESETS.find(({ match }) => {
const normalizedMatch = match.toLowerCase()
return normalizedProcessName === normalizedMatch || normalizedProcessPath.includes(normalizedMatch)
})
return matchedPreset?.displayName ?? ''
}
const resolveProcessDisplayName = (rawProcessName: string, rawProcessPath: string, titles: TitleUsage[]) => {
const presetDisplayName = resolvePresetProcessDisplayName(rawProcessName, rawProcessPath)
if (presetDisplayName) {
return presetDisplayName
}
const resolveProcessDisplayName = (rawProcessName: string, titles: TitleUsage[]) => {
const processBaseName = stripExtension(rawProcessName).toLowerCase()
const totalDurationMs = titles.reduce((total, item) => total + item.durationMs, 0)
const weightedTokenLabel = trimLabelSeparators(buildWeightedTokenLabel(titles, totalDurationMs))
@ -273,7 +228,6 @@ const addDurationToUsageMap = (
usageMap: Map<string, MutableAppUsage>,
processId: string,
rawProcessName: string,
rawProcessPath: string,
title: string,
durationMs: number
) => {
@ -286,16 +240,12 @@ 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]])
})
@ -314,9 +264,8 @@ const buildAppList = (usageMap: Map<string, MutableAppUsage>) => {
return {
processId: app.processId,
processName: resolveProcessDisplayName(app.rawProcessName, app.rawProcessPath, sortedTitles),
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,
@ -435,7 +384,6 @@ async function handleScreenTime(req: NextRequest) {
aggregateUsageMap,
process.processId,
process.rawProcessName,
process.rawProcessPath,
title,
aggregateContributionMs
)
@ -445,7 +393,6 @@ async function handleScreenTime(req: NextRequest) {
selectedDayUsageMap,
process.processId,
process.rawProcessName,
process.rawProcessPath,
title,
selectedDayContributionMs
)

View File

@ -3,7 +3,6 @@ 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'
@ -29,8 +28,7 @@ async function handleScreenshotUpload(req: NextRequest) {
const formData = await req.formData()
const files: File[] = []
const rawWindowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
const windowsInfo = filterIgnoredWindows(rawWindowsInfo)
const windowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
// Extract files from formData
for (const [key, value] of formData.entries()) {

View File

@ -33,7 +33,6 @@ export interface ScreenTimeAppUsage {
processId: string;
processName: string;
rawProcessName: string;
processPath: string;
durationSeconds: number;
percentage: number;
titleCount: number;

View File

@ -1,66 +0,0 @@
#!/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()
})

View File

@ -1,20 +0,0 @@
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 = <T extends WindowLike>(windows: T[]) => {
return windows.filter(window => !isIgnoredWindow(window))
}

View File

@ -10,9 +10,7 @@
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:reset": "prisma migrate reset",
"db:studio": "prisma studio",
"db:cleanup:ignored-windows": "bun envtests/cleanup-nvidia-overlay.ts",
"db:cleanup:nvidia-overlay": "bun envtests/cleanup-nvidia-overlay.ts"
"db:studio": "prisma studio"
},
"dependencies": {
"@prisma/client": "^6.10.1",