11/**
2- * 窗口智能评分与过滤模块
2+ * 窗口智能评分与过滤模块(组合入口)
33 *
4- * 基于关键词匹配对 desktopCapturer 返回的窗口列表进行评分和排序,
4+ * 编排信号层(windowSignals)与规则层(windowRules):对 desktopCapturer
5+ * 返回的窗口列表附加系统信号(进程/几何/前台)与记忆 boost 后双向打分排序,
56 * 过滤系统/不可见窗口,将最可能是网课/直播/会议的窗口排在前面。
6- * 仅针对 Windows 平台优化关键词列表 。
7+ * 无信号注入时退化为纯标题评分(与旧版行为一致) 。
78 *
8- * @ai -context: 采集窗口智能评分过滤:排除自身窗口(含旧品牌'课伴'关键词——防旧版本窗口被采集,为兼容保留勿删)与系统窗口。
9+ * @ai -context: 采集窗口智能评分过滤:排除自身窗口(含旧品牌'课伴'关键词——
10+ * 防旧版本窗口被采集,为兼容保留勿删)与系统窗口。信号源全部可选:native
11+ * 缺失时仅标题关键词评分,保证行为不回归。
12+ * @ai -context EN: Composition entry for window scoring. All signal sources
13+ * are optional; without them the output matches legacy title-only behavior.
914 */
15+ import { scoreWindow , type Confidence , type WindowSignalInput } from './windowRules.js' ;
1016
1117// ================================================================
1218// 类型定义
@@ -17,110 +23,68 @@ export interface ScoredWindow {
1723 title : string ;
1824 thumbnail : string ;
1925 score : number ;
20- matched ?: string ; // 命中的关键词(用于前端显示推荐理由)
26+ matched ?: string ;
27+ processName ?: string ;
28+ confidence ?: Confidence ;
29+ reasons ?: string [ ] ;
30+ /** 未被娱乐负分主导的学习分 */
31+ learningScore ?: number ;
32+ /** 记忆命中的课程名(供渲染层回填课程) */
33+ memoryCourseName ?: string ;
34+ /** 是否前台窗口(渲染层自动选中规则②使用) */
35+ isForeground ?: boolean ;
2136}
2237
23- // ================================================================
24- // 关键词评分规则
25- // ================================================================
26-
27- const SCORE_RULES = {
28- /** 高优先级:网课/直播/会议关键词 (+100) */
29- highPriority : [
30- '网课' , '直播' , '课程' , '课堂' , '学习' , '讲座' , '培训' ,
31- '腾讯会议' , '钉钉' , 'Zoom' , 'Teams' , 'Meet' , 'Webex' ,
32- '中国大学MOOC' , '学堂在线' , '智慧树' , '学习通' , '雨课堂' ,
33- 'Bilibili' , '哔哩哔哩' , 'YouTube' , '网易公开课' ,
34- 'Coursera' , 'edX' , 'Udemy' ,
35- ] ,
36- /** 中优先级:浏览器/视频播放器 (+50) */
37- mediumPriority : [
38- 'Chrome' , 'Edge' , 'Firefox' , 'Brave' , 'Opera' ,
39- 'PotPlayer' , 'VLC' , 'mpv' , 'Windows Media' ,
40- 'EV录屏' , 'OBS' , 'MPC-HC' ,
41- ] ,
42- /** 黑名单:系统/工具窗口(直接过滤) */
43- blacklist : [
44- 'Program Manager' ,
45- 'Taskbar' ,
46- 'Settings' ,
47- '设置' ,
48- 'Windows Input Experience' ,
49- 'MSCTFIME' ,
50- 'Default IME' ,
51- 'Electron' ,
52- 'Entropy decrease' ,
53- '课伴' ,
54- '熵减' ,
55- ] ,
56- } as const ;
57-
58- const HIGH_SCORE = 100 ;
59- const MEDIUM_SCORE = 50 ;
60-
61- // ================================================================
62- // 评分逻辑
63- // ================================================================
64-
65- /**
66- * 对窗口标题进行关键词匹配评分
67- * @returns score 和 matched 关键词,若命中黑名单返回 null
68- */
69- function scoreTitle ( title : string ) : { score : number ; matched ?: string } | null {
70- const lowerTitle = title . toLowerCase ( ) ;
71-
72- // 黑名单检测
73- for ( const keyword of SCORE_RULES . blacklist ) {
74- if ( lowerTitle . includes ( keyword . toLowerCase ( ) ) ) {
75- return null ; // 过滤
76- }
77- }
78-
79- // 高优先级匹配
80- for ( const keyword of SCORE_RULES . highPriority ) {
81- if ( lowerTitle . includes ( keyword . toLowerCase ( ) ) ) {
82- return { score : HIGH_SCORE , matched : keyword } ;
83- }
84- }
85-
86- // 中优先级匹配
87- for ( const keyword of SCORE_RULES . mediumPriority ) {
88- if ( lowerTitle . includes ( keyword . toLowerCase ( ) ) ) {
89- return { score : MEDIUM_SCORE , matched : keyword } ;
90- }
91- }
92-
93- // 无匹配,基础分 0
94- return { score : 0 } ;
38+ export interface ScoreAndFilterOptions {
39+ /** 按 source id 注入的信号 */
40+ signalsBySourceId ?: Map < string , WindowSignalInput > ;
41+ /** 记忆查找注入(windowHistory 包装) */
42+ memoryLookup ?: ( processName : string , title : string ) => { courseName ?: string ; boost : number } | null ;
9543}
9644
97- /**
98- * 对窗口列表进行评分、过滤和排序
99- * @param windows 原始窗口列表(来自 desktopCapturer)
100- * @returns 过滤并排序后的窗口列表(降序)
101- */
10245export function scoreAndFilterWindows (
10346 windows : { id : string ; title : string ; thumbnail : string } [ ] ,
47+ options ?: ScoreAndFilterOptions ,
10448) : ScoredWindow [ ] {
10549 const scored : ScoredWindow [ ] = [ ] ;
10650
10751 for ( const win of windows ) {
108- // 过滤空标题窗口
10952 if ( ! win . title || win . title . trim ( ) === '' ) continue ;
11053
111- const result = scoreTitle ( win . title ) ;
112- if ( result === null ) continue ; // 命中黑名单
54+ const signal : WindowSignalInput = {
55+ title : win . title ,
56+ ...( options ?. signalsBySourceId ?. get ( win . id ) ?? { } ) ,
57+ } ;
58+
59+ const result = scoreWindow ( signal ) ;
60+ if ( result . filtered ) continue ;
11361
114- scored . push ( {
62+ const out : ScoredWindow = {
11563 id : win . id ,
11664 title : win . title ,
11765 thumbnail : win . thumbnail ,
11866 score : result . score ,
119- matched : result . matched ,
120- } ) ;
67+ matched : result . reasons [ 0 ] ,
68+ reasons : result . reasons ,
69+ confidence : result . confidence ,
70+ learningScore : result . score - result . entertainmentPenalty ,
71+ } ;
72+
73+ if ( signal . processName ) out . processName = signal . processName ;
74+ if ( signal . isForeground ) out . isForeground = true ;
75+
76+ // 记忆查找以标题为锚:进程缺失时传空串降级调用,不阻断记忆命中
77+ if ( options ?. memoryLookup ) {
78+ const mem = options . memoryLookup ( signal . processName ?? '' , win . title ) ;
79+ if ( mem ) {
80+ out . score += mem . boost ;
81+ out . memoryCourseName = mem . courseName ;
82+ }
83+ }
84+
85+ scored . push ( out ) ;
12186 }
12287
123- // 按 score 降序排列,同分按 id 稳定排序
12488 scored . sort ( ( a , b ) => {
12589 if ( b . score !== a . score ) return b . score - a . score ;
12690 return a . id . localeCompare ( b . id ) ;
0 commit comments