@readerseye2/cr_type 1.0.115 → 1.0.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/book/child-reading-progress.type.d.ts +154 -0
- package/dist/book/child-reading-progress.type.js +4 -0
- package/dist/book/index.d.ts +2 -0
- package/dist/book/index.js +2 -0
- package/dist/book/read-range.util.d.ts +11 -0
- package/dist/book/read-range.util.js +79 -0
- package/dist/gaze/eye-tracker.types.d.ts +11 -0
- package/dist/gaze/eye-tracker.types.js +2 -0
- package/dist/gaze/index.d.ts +1 -0
- package/dist/gaze/index.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/socket/socket-clientToServerEvents.type.d.ts +3 -0
- package/dist/socket/viewer-events.types.d.ts +22 -2
- package/package.json +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/** 읽은 구간 (globalIndex 범위, inclusive) */
|
|
2
|
+
export interface ReadRange {
|
|
3
|
+
from: number;
|
|
4
|
+
to: number;
|
|
5
|
+
}
|
|
6
|
+
/** 캘리브레이션 종류 */
|
|
7
|
+
export type CalibrationType = 'quick' | 'full';
|
|
8
|
+
/** 섹션 읽기 중 발생한 캘리브레이션 기간 */
|
|
9
|
+
export interface CalibrationPeriod {
|
|
10
|
+
type: CalibrationType;
|
|
11
|
+
/** 사용한 포인트 수 (quick: 1~2, full: 5) */
|
|
12
|
+
points: number;
|
|
13
|
+
startedAt: string;
|
|
14
|
+
endedAt: string;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
/** 캘리브레이션 결과 품질 (0~1, 기기 제공) */
|
|
17
|
+
quality?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ReadingProgressReport {
|
|
20
|
+
bookIdx: number;
|
|
21
|
+
sectionId: string;
|
|
22
|
+
/** 섹션 전체 GI 수 (ViewerSnapshot.totalItems) */
|
|
23
|
+
sectionGIMax: number;
|
|
24
|
+
/** 이 구간에서 뷰포트에 표시된 GI 범위의 최소값 */
|
|
25
|
+
scrolledFrom: number;
|
|
26
|
+
/** 이 구간에서 뷰포트에 표시된 GI 범위의 최대값 */
|
|
27
|
+
scrolledTo: number;
|
|
28
|
+
/**
|
|
29
|
+
* GI별 시선 dwell time (ms)
|
|
30
|
+
* - 키: globalIndex (string), 값: 해당 5초 윈도우 내 누적 dwell ms
|
|
31
|
+
* - 임계값 필터링 없이 전체 포함 (서버에서 threshold 적용)
|
|
32
|
+
* - 시선 추적 기기 없으면 빈 객체 {}
|
|
33
|
+
*
|
|
34
|
+
* 예: { "101": 300, "102": 450, "103": 67 }
|
|
35
|
+
*/
|
|
36
|
+
gazeDwellMap: Record<string, number>;
|
|
37
|
+
/** 이 구간 내 눈 깜빡임 횟수 (Tobii valid 전환 감지) */
|
|
38
|
+
blinkCount: number;
|
|
39
|
+
/** 소요 시간 ms */
|
|
40
|
+
durationMs: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ChildSectionProgress {
|
|
43
|
+
childIdx: number;
|
|
44
|
+
bookIdx: number;
|
|
45
|
+
sectionId: string;
|
|
46
|
+
sectionGIMax: number;
|
|
47
|
+
scrolledRanges: ReadRange[];
|
|
48
|
+
scrolledCount: number;
|
|
49
|
+
scrolledCoverage: number;
|
|
50
|
+
gazeReadRanges: ReadRange[];
|
|
51
|
+
gazeReadCount: number;
|
|
52
|
+
gazeReadCoverage: number;
|
|
53
|
+
totalReadMs: number;
|
|
54
|
+
lastGlobalIndex: number;
|
|
55
|
+
lastReadAt: string;
|
|
56
|
+
totalBlinks: number;
|
|
57
|
+
calibrations: CalibrationPeriod[];
|
|
58
|
+
createdAt: string;
|
|
59
|
+
updatedAt: string;
|
|
60
|
+
}
|
|
61
|
+
export interface ChildBookBookmark {
|
|
62
|
+
childIdx: number;
|
|
63
|
+
bookIdx: number;
|
|
64
|
+
lastReadSectionId: string;
|
|
65
|
+
lastGlobalIndex: number;
|
|
66
|
+
totalGIMax: number;
|
|
67
|
+
totalScrolledCount: number;
|
|
68
|
+
totalScrolledCoverage: number;
|
|
69
|
+
totalGazeReadCount: number;
|
|
70
|
+
totalGazeReadCoverage: number;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
updatedAt: string;
|
|
73
|
+
}
|
|
74
|
+
export interface ChildReadingLog {
|
|
75
|
+
childIdx: number;
|
|
76
|
+
bookIdx: number;
|
|
77
|
+
sectionId: string;
|
|
78
|
+
scrolledFrom: number;
|
|
79
|
+
scrolledTo: number;
|
|
80
|
+
/** 전체 dwell map 그대로 보존 */
|
|
81
|
+
gazeDwellMap: Record<string, number>;
|
|
82
|
+
blinkCount: number;
|
|
83
|
+
durationMs: number;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
}
|
|
86
|
+
export interface ChildCalibrationLog {
|
|
87
|
+
childIdx: number;
|
|
88
|
+
/** 읽기 세션 중 발생 시 세션 ID */
|
|
89
|
+
sessionId?: string;
|
|
90
|
+
/** 읽기 중이었던 섹션 */
|
|
91
|
+
sectionId?: string;
|
|
92
|
+
type: CalibrationType;
|
|
93
|
+
/** 사용한 포인트 수 */
|
|
94
|
+
points: number;
|
|
95
|
+
startedAt: string;
|
|
96
|
+
endedAt: string;
|
|
97
|
+
durationMs: number;
|
|
98
|
+
quality?: number;
|
|
99
|
+
createdAt: string;
|
|
100
|
+
}
|
|
101
|
+
export interface SegmentReadingSummary {
|
|
102
|
+
sectionId: string;
|
|
103
|
+
bookIdx: number;
|
|
104
|
+
sectionGIMax: number;
|
|
105
|
+
startedAt: string;
|
|
106
|
+
endedAt: string;
|
|
107
|
+
durationMs: number;
|
|
108
|
+
scrolledFrom: number;
|
|
109
|
+
scrolledTo: number;
|
|
110
|
+
scrolledGICount: number;
|
|
111
|
+
gazeReadGICount: number;
|
|
112
|
+
/** gazeReadGICount / scrolledGICount (시선 없으면 null) */
|
|
113
|
+
focusRatio: number | null;
|
|
114
|
+
/** 평균 읽기 속도 (GI per minute) */
|
|
115
|
+
readingSpeedGPM: number;
|
|
116
|
+
/** 평균 dwell time (ms per GI) */
|
|
117
|
+
avgDwellMs: number | null;
|
|
118
|
+
totalBlinks: number;
|
|
119
|
+
/** 분당 깜빡임 횟수 */
|
|
120
|
+
blinksPerMinute: number;
|
|
121
|
+
calibrations: CalibrationPeriod[];
|
|
122
|
+
}
|
|
123
|
+
export interface ChildReadingSessionSummary {
|
|
124
|
+
childIdx: number;
|
|
125
|
+
sessionId: string;
|
|
126
|
+
startedAt: string;
|
|
127
|
+
endedAt: string;
|
|
128
|
+
totalDurationMs: number;
|
|
129
|
+
segments: SegmentReadingSummary[];
|
|
130
|
+
totalScrolledGICount: number;
|
|
131
|
+
totalGazeReadGICount: number;
|
|
132
|
+
overallFocusRatio: number | null;
|
|
133
|
+
avgReadingSpeedGPM: number;
|
|
134
|
+
totalBlinks: number;
|
|
135
|
+
avgBlinksPerMinute: number;
|
|
136
|
+
}
|
|
137
|
+
/** GET /api/reading-progress/:bookIdx */
|
|
138
|
+
export interface ChildBookProgressResponse {
|
|
139
|
+
bookmark: ChildBookBookmark | null;
|
|
140
|
+
sections: ChildSectionProgress[];
|
|
141
|
+
}
|
|
142
|
+
/** 일별 읽기 통계 (aggregation 결과) */
|
|
143
|
+
export interface DailyReadingStat {
|
|
144
|
+
date: string;
|
|
145
|
+
totalMs: number;
|
|
146
|
+
books: number[];
|
|
147
|
+
}
|
|
148
|
+
/** GET /api/reading-progress/daily-stats */
|
|
149
|
+
export interface DailyReadingStatsResponse {
|
|
150
|
+
childIdx: number;
|
|
151
|
+
from: string;
|
|
152
|
+
to: string;
|
|
153
|
+
stats: DailyReadingStat[];
|
|
154
|
+
}
|
package/dist/book/index.d.ts
CHANGED
package/dist/book/index.js
CHANGED
|
@@ -18,3 +18,5 @@ __exportStar(require("./book.type"), exports);
|
|
|
18
18
|
__exportStar(require("./book-sections.type"), exports);
|
|
19
19
|
__exportStar(require("./book-status-request.type"), exports);
|
|
20
20
|
__exportStar(require("./tag.type"), exports);
|
|
21
|
+
__exportStar(require("./child-reading-progress.type"), exports);
|
|
22
|
+
__exportStar(require("./read-range.util"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReadRange } from './child-reading-progress.type';
|
|
2
|
+
/** 정렬된 ranges 배열에 newRange를 머지 (겹침/인접 합산) */
|
|
3
|
+
export declare function mergeReadRange(ranges: ReadRange[], newRange: ReadRange): ReadRange[];
|
|
4
|
+
/** 여러 newRanges를 기존 ranges에 순차 머지 */
|
|
5
|
+
export declare function mergeReadRanges(existing: ReadRange[], newRanges: ReadRange[]): ReadRange[];
|
|
6
|
+
/** ranges 내 고유 GI 총 개수 (각 구간 길이 합) */
|
|
7
|
+
export declare function sumReadRanges(ranges: ReadRange[]): number;
|
|
8
|
+
/** 개별 GI 배열 → 정렬된 ReadRange[] 변환 */
|
|
9
|
+
export declare function gisToRanges(gis: number[]): ReadRange[];
|
|
10
|
+
/** gazeDwellMap에서 threshold 이상인 GI만 추출하여 ReadRange[] 변환 */
|
|
11
|
+
export declare function filterDwellToRanges(gazeDwellMap: Record<string, number>, thresholdMs: number): ReadRange[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/book/read-range.util.ts
|
|
3
|
+
// ReadRange 유틸리티 함수 (서버/클라이언트 공용)
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.mergeReadRange = mergeReadRange;
|
|
6
|
+
exports.mergeReadRanges = mergeReadRanges;
|
|
7
|
+
exports.sumReadRanges = sumReadRanges;
|
|
8
|
+
exports.gisToRanges = gisToRanges;
|
|
9
|
+
exports.filterDwellToRanges = filterDwellToRanges;
|
|
10
|
+
/** 정렬된 ranges 배열에 newRange를 머지 (겹침/인접 합산) */
|
|
11
|
+
function mergeReadRange(ranges, newRange) {
|
|
12
|
+
if (ranges.length === 0)
|
|
13
|
+
return [newRange];
|
|
14
|
+
const result = [];
|
|
15
|
+
let merged = { ...newRange };
|
|
16
|
+
let inserted = false;
|
|
17
|
+
for (const r of ranges) {
|
|
18
|
+
if (inserted) {
|
|
19
|
+
result.push(r);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (r.to + 1 < merged.from) {
|
|
23
|
+
result.push(r);
|
|
24
|
+
}
|
|
25
|
+
else if (r.from > merged.to + 1) {
|
|
26
|
+
result.push(merged);
|
|
27
|
+
result.push(r);
|
|
28
|
+
inserted = true;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
merged = { from: Math.min(merged.from, r.from), to: Math.max(merged.to, r.to) };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!inserted)
|
|
35
|
+
result.push(merged);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/** 여러 newRanges를 기존 ranges에 순차 머지 */
|
|
39
|
+
function mergeReadRanges(existing, newRanges) {
|
|
40
|
+
let result = existing;
|
|
41
|
+
for (const nr of newRanges)
|
|
42
|
+
result = mergeReadRange(result, nr);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/** ranges 내 고유 GI 총 개수 (각 구간 길이 합) */
|
|
46
|
+
function sumReadRanges(ranges) {
|
|
47
|
+
let sum = 0;
|
|
48
|
+
for (const r of ranges)
|
|
49
|
+
sum += r.to - r.from + 1;
|
|
50
|
+
return sum;
|
|
51
|
+
}
|
|
52
|
+
/** 개별 GI 배열 → 정렬된 ReadRange[] 변환 */
|
|
53
|
+
function gisToRanges(gis) {
|
|
54
|
+
if (gis.length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
const sorted = [...new Set(gis)].sort((a, b) => a - b);
|
|
57
|
+
const ranges = [];
|
|
58
|
+
let from = sorted[0], to = sorted[0];
|
|
59
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
60
|
+
if (sorted[i] === to + 1)
|
|
61
|
+
to = sorted[i];
|
|
62
|
+
else {
|
|
63
|
+
ranges.push({ from, to });
|
|
64
|
+
from = sorted[i];
|
|
65
|
+
to = sorted[i];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
ranges.push({ from, to });
|
|
69
|
+
return ranges;
|
|
70
|
+
}
|
|
71
|
+
/** gazeDwellMap에서 threshold 이상인 GI만 추출하여 ReadRange[] 변환 */
|
|
72
|
+
function filterDwellToRanges(gazeDwellMap, thresholdMs) {
|
|
73
|
+
const passedGIs = [];
|
|
74
|
+
for (const [gi, ms] of Object.entries(gazeDwellMap)) {
|
|
75
|
+
if (ms >= thresholdMs)
|
|
76
|
+
passedGIs.push(Number(gi));
|
|
77
|
+
}
|
|
78
|
+
return gisToRanges(passedGIs);
|
|
79
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 지원하는 시선추적 프로바이더 타입 */
|
|
2
|
+
export type EyeTrackerType = 'eyedid' | 'seeso' | 'custom' | 'fake';
|
|
3
|
+
/** 단일 시선 샘플 (0~1 정규화 좌표) */
|
|
4
|
+
export interface GazeData {
|
|
5
|
+
/** 정규화된 X 좌표 (0~1, 좌→우) */
|
|
6
|
+
x: number;
|
|
7
|
+
/** 정규화된 Y 좌표 (0~1, 상→하) */
|
|
8
|
+
y: number;
|
|
9
|
+
/** 타임스탬프 (ms, Date.now()) */
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './eye-tracker.types';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./eye-tracker.types"), exports);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChatMessageReadRequest, ChatMessageRefreshRequest, MessageRequest } from "./socket-message.types";
|
|
2
2
|
import { SessionProgressPayload, SessionEventPayload, SessionSubscribePayload } from "./reading-section.types";
|
|
3
3
|
import { ViewerOpenPayload, ViewerClosePayload, GazeDataPayload, SessionHistoryListPayload, SessionHistoryGetPayload, SessionHistoryDeletePayload, UnifiedChunksGetPayload, UnifiedSegmentGetPayload } from "./unified-session.types";
|
|
4
|
+
import { ReadingProgressReport } from "../book/child-reading-progress.type";
|
|
4
5
|
export interface ClientToServerEvents {
|
|
5
6
|
'chat-message:send': (msg: MessageRequest) => void;
|
|
6
7
|
'chat-message:refresh': (msg: ChatMessageRefreshRequest) => void;
|
|
@@ -19,6 +20,8 @@ export interface ClientToServerEvents {
|
|
|
19
20
|
'session:subscribe': (payload: SessionSubscribePayload) => void;
|
|
20
21
|
/** 세션 구독 해제 */
|
|
21
22
|
'session:unsubscribe': (payload: SessionSubscribePayload) => void;
|
|
23
|
+
/** 읽기 진행 보고 (5초 주기 자동저장) */
|
|
24
|
+
'progress:reading-save': (payload: ReadingProgressReport) => void;
|
|
22
25
|
}
|
|
23
26
|
export interface NoticeToServerEvents {
|
|
24
27
|
'notice-message:send': (msg: MessageRequest) => void;
|
|
@@ -29,7 +29,7 @@ export type CRViewerState = {
|
|
|
29
29
|
viewportHeight: number;
|
|
30
30
|
};
|
|
31
31
|
/** 뷰어 이벤트 타입 */
|
|
32
|
-
export type ViewerEventType = 'global_index_change' | 'scroll' | 'page_change' | 'section_change' | 'mode_change' | 'settings_change' | 'render_start' | 'loading_start' | 'loading_end' | 'viewport_resize' | 'audio_control' | 'range_select' | 'range_change' | 'range_clear' | 'translate_request' | 'translate_loading_start' | 'translate_loading_end' | 'translate_modal_close';
|
|
32
|
+
export type ViewerEventType = 'global_index_change' | 'scroll' | 'page_change' | 'section_change' | 'mode_change' | 'settings_change' | 'render_start' | 'loading_start' | 'loading_end' | 'viewport_resize' | 'audio_control' | 'range_select' | 'range_change' | 'range_clear' | 'translate_request' | 'translate_loading_start' | 'translate_loading_end' | 'translate_modal_close' | 'calibration_start' | 'calibration_end' | 'show_gaze_change' | 'ask_calibration' | 'calibration_progress';
|
|
33
33
|
/** 오디오 상태 스냅샷 */
|
|
34
34
|
export type AudioSnapshot = {
|
|
35
35
|
status: 'idle' | 'loading' | 'playing' | 'paused' | 'ended' | 'error';
|
|
@@ -124,8 +124,28 @@ export type TranslateLoadingEndEvent = ViewerEventBase<'translate_loading_end',
|
|
|
124
124
|
translatedText: string;
|
|
125
125
|
}>;
|
|
126
126
|
export type TranslateModalCloseEvent = ViewerEventBase<'translate_modal_close', Record<string, never>>;
|
|
127
|
+
export type CalibrationStartEvent = ViewerEventBase<'calibration_start', {
|
|
128
|
+
type: 'quick' | 'full';
|
|
129
|
+
points: number;
|
|
130
|
+
}>;
|
|
131
|
+
export type CalibrationEndEvent = ViewerEventBase<'calibration_end', {
|
|
132
|
+
type: 'quick' | 'full';
|
|
133
|
+
points: number;
|
|
134
|
+
durationMs: number;
|
|
135
|
+
quality?: number;
|
|
136
|
+
}>;
|
|
137
|
+
export type ShowGazeChangeEvent = ViewerEventBase<'show_gaze_change', {
|
|
138
|
+
showGaze: boolean;
|
|
139
|
+
}>;
|
|
140
|
+
export type AskCalibrationEvent = ViewerEventBase<'ask_calibration', {
|
|
141
|
+
requestedAt: number;
|
|
142
|
+
}>;
|
|
143
|
+
export type CalibrationProgressEvent = ViewerEventBase<'calibration_progress', {
|
|
144
|
+
current: number;
|
|
145
|
+
total: number;
|
|
146
|
+
}>;
|
|
127
147
|
/** 모든 뷰어 이벤트 타입 (Union) */
|
|
128
|
-
export type ViewerEvent = GlobalIndexChangeEvent | ScrollEvent | PageChangeEvent | SectionChangeEvent | ModeChangeEvent | SettingsChangeEvent | RenderStartEvent | LoadingStartEvent | LoadingEndEvent | ViewportResizeEvent | AudioControlEvent | RangeSelectEvent | RangeChangeEvent | RangeClearEvent | TranslateRequestEvent | TranslateLoadingStartEvent | TranslateLoadingEndEvent | TranslateModalCloseEvent;
|
|
148
|
+
export type ViewerEvent = GlobalIndexChangeEvent | ScrollEvent | PageChangeEvent | SectionChangeEvent | ModeChangeEvent | SettingsChangeEvent | RenderStartEvent | LoadingStartEvent | LoadingEndEvent | ViewportResizeEvent | AudioControlEvent | RangeSelectEvent | RangeChangeEvent | RangeClearEvent | TranslateRequestEvent | TranslateLoadingStartEvent | TranslateLoadingEndEvent | TranslateModalCloseEvent | CalibrationStartEvent | CalibrationEndEvent | ShowGazeChangeEvent | AskCalibrationEvent | CalibrationProgressEvent;
|
|
129
149
|
export type ViewerEventCallback = (event: ViewerEvent) => void;
|
|
130
150
|
/** 저장용 이벤트 (any type) */
|
|
131
151
|
export type StoredEvent = StoredViewerEvent<ViewerEventType, unknown>;
|