From f7f0e2fb738157139d374d2f4a90d83d4ef10bc7 Mon Sep 17 00:00:00 2001 From: feie9454 Date: Sat, 25 Apr 2026 13:18:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20DisplayName=20=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- app/hosts/[hostname]/screen-time/route.ts | 46 ++++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/app/hosts/[hostname]/screen-time/route.ts b/app/hosts/[hostname]/screen-time/route.ts index 3bd46af..1510c76 100644 --- a/app/hosts/[hostname]/screen-time/route.ts +++ b/app/hosts/[hostname]/screen-time/route.ts @@ -44,6 +44,29 @@ 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 @@ -54,6 +77,8 @@ 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 => { @@ -205,7 +230,24 @@ const buildCommonSubstringLabel = (titles: TitleUsage[]) => { return trimLabelSeparators(shared) } -const resolveProcessDisplayName = (rawProcessName: string, titles: TitleUsage[]) => { +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 processBaseName = stripExtension(rawProcessName).toLowerCase() const totalDurationMs = titles.reduce((total, item) => total + item.durationMs, 0) const weightedTokenLabel = trimLabelSeparators(buildWeightedTokenLabel(titles, totalDurationMs)) @@ -272,7 +314,7 @@ const buildAppList = (usageMap: Map) => { return { processId: app.processId, - processName: resolveProcessDisplayName(app.rawProcessName, sortedTitles), + processName: resolveProcessDisplayName(app.rawProcessName, app.rawProcessPath, sortedTitles), rawProcessName: app.rawProcessName, processPath: app.rawProcessPath, durationSeconds: Math.round(app.durationMs / 1000),