@tienne/gestalt 0.104.0 → 0.105.0
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/package.json +1 -1
- package/dist/plugin/role-agents/video-summarizer/AGENT.md +28 -4
- package/dist/plugin/skills/_shared/proactive-routing.md +1 -1
- package/dist/plugin/skills/video-digest/SKILL.md +72 -22
- package/dist/src/digest/render-html.d.ts.map +1 -1
- package/dist/src/digest/render-html.js +15 -9
- package/dist/src/digest/render-html.js.map +1 -1
- package/dist/src/digest/reports.d.ts +9 -1
- package/dist/src/digest/reports.d.ts.map +1 -1
- package/dist/src/digest/reports.js +24 -1
- package/dist/src/digest/reports.js.map +1 -1
- package/dist/src/digest/viewer-js.d.ts.map +1 -1
- package/dist/src/digest/viewer-js.js +56 -47
- package/dist/src/digest/viewer-js.js.map +1 -1
- package/dist/src/digest/viewer-logic.d.ts +22 -5
- package/dist/src/digest/viewer-logic.d.ts.map +1 -1
- package/dist/src/digest/viewer-logic.js +23 -4
- package/dist/src/digest/viewer-logic.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/role-agents/video-summarizer/AGENT.md +28 -4
- package/plugin/skills/_shared/proactive-routing.md +1 -1
- package/plugin/skills/video-digest/SKILL.md +72 -22
|
@@ -300,7 +300,7 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
// 사용자가 패널을 직접 움직이면 3초 동안 멈춘다
|
|
303
|
+
// 일시정지 중에도 하이라이트는 옮긴다. 사용자가 패널을 직접 움직이면 3초 동안 스크롤을 멈춘다
|
|
304
304
|
function setupScriptFollow(player) {
|
|
305
305
|
if (!player.live) return;
|
|
306
306
|
var panel = document.getElementById('script-body');
|
|
@@ -315,14 +315,20 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
315
315
|
var t = player.currentTime();
|
|
316
316
|
if (t === null || t === undefined) return;
|
|
317
317
|
var idx = findSegmentIndex(starts, t);
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
318
|
+
var changed = idx !== current;
|
|
319
|
+
if (changed) {
|
|
320
|
+
if (lines[current]) lines[current].classList.remove('is-playing');
|
|
321
|
+
current = idx;
|
|
322
|
+
}
|
|
321
323
|
var li = lines[idx];
|
|
322
324
|
if (!li) return;
|
|
323
|
-
li.classList.add('is-playing');
|
|
324
|
-
if (!panel
|
|
325
|
-
|
|
325
|
+
if (changed) li.classList.add('is-playing');
|
|
326
|
+
if (!panel) return;
|
|
327
|
+
var visible = isLineVisible(li.offsetTop, li.offsetHeight, panel.scrollTop, panel.clientHeight);
|
|
328
|
+
if (!shouldScrollScript(player.state(), changed, Date.now(), holdUntil, visible)) return;
|
|
329
|
+
var top = Math.max(0, li.offsetTop - panel.clientHeight / 3);
|
|
330
|
+
if (Math.abs(top - panel.scrollTop) <= 1) return;
|
|
331
|
+
panel.scrollTo({ top: top, behavior: reduceMotion ? 'auto' : 'smooth' });
|
|
326
332
|
}, 250);
|
|
327
333
|
}
|
|
328
334
|
|
|
@@ -344,8 +350,9 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
344
350
|
document.querySelectorAll('section.sec').forEach(function(s){ io.observe(s); });
|
|
345
351
|
}
|
|
346
352
|
|
|
347
|
-
// 읽는 소섹션이 바뀌면 영상을 그 구간 첫 문장으로 옮긴다.
|
|
353
|
+
// 읽는 소섹션이 바뀌면 영상을 그 구간 첫 문장으로 옮긴다. 재생 중이면 요약을 스크롤해도 영상을 옮기지 않고 빠르게 훑을 때는 멈춘 자리만 따른다.
|
|
348
354
|
// 방금 시각을 눌렀으면 유예가 끝난 뒤 영상이 멈춰 있을 때만 따라간다. 기다리는 사이 그 소섹션이 화면에서 빠지면 따라가지 않는다
|
|
355
|
+
// 앞선 이동의 버퍼링이 덜 끝났으면 재생 중으로 읽히므로 300ms 뒤 두 번까지 다시 본다
|
|
349
356
|
function setupSubsectionFollow(player) {
|
|
350
357
|
var subs = document.querySelectorAll('.sub[data-from]');
|
|
351
358
|
if (!player.live || !('IntersectionObserver' in window) || !subs.length) return;
|
|
@@ -354,6 +361,15 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
354
361
|
var t = player.currentTime();
|
|
355
362
|
return t === null || t === undefined ? -1 : findSegmentIndex(starts, t);
|
|
356
363
|
};
|
|
364
|
+
var follow = function(target, from, to, tries){
|
|
365
|
+
if (!player.live || pendingSub !== target) return;
|
|
366
|
+
if (shouldRetryFollow(player.state(), tries)) {
|
|
367
|
+
followTimer = setTimeout(function(){ follow(target, from, to, tries - 1); }, 300);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (!shouldFollowHeld(player.state(), currentIdx(), from, to, Date.now(), manualUntil)) return;
|
|
371
|
+
if (typeof starts[from] === 'number') player.cue(starts[from]);
|
|
372
|
+
};
|
|
357
373
|
var subIo = new IntersectionObserver(function(entries){
|
|
358
374
|
entries.forEach(function(en){
|
|
359
375
|
if (!en.isIntersecting) {
|
|
@@ -368,21 +384,16 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
368
384
|
var to = Number(en.target.getAttribute('data-to'));
|
|
369
385
|
clearTimeout(followTimer);
|
|
370
386
|
clearTimeout(heldTimer);
|
|
371
|
-
pendingSub = en.target;
|
|
387
|
+
var target = pendingSub = en.target;
|
|
372
388
|
followTimer = setTimeout(function(){
|
|
373
389
|
if (!player.live) return;
|
|
374
390
|
var now = Date.now();
|
|
375
391
|
// 시각을 누른 직후에 넘어온 소섹션은 버리지 않고 유예가 끝난 뒤 한 번만 다시 본다
|
|
376
392
|
if (now < manualUntil) {
|
|
377
|
-
heldTimer = setTimeout(function(){
|
|
378
|
-
if (!player.live) return;
|
|
379
|
-
if (!shouldFollowHeld(player.state(), currentIdx(), from, to, Date.now(), manualUntil)) return;
|
|
380
|
-
if (typeof starts[from] === 'number') player.cue(starts[from]);
|
|
381
|
-
}, manualUntil - now + 50);
|
|
393
|
+
heldTimer = setTimeout(function(){ follow(target, from, to, 2); }, manualUntil - now + 50);
|
|
382
394
|
return;
|
|
383
395
|
}
|
|
384
|
-
|
|
385
|
-
if (typeof starts[from] === 'number') player.cue(starts[from]);
|
|
396
|
+
follow(target, from, to, 2);
|
|
386
397
|
}, 400);
|
|
387
398
|
});
|
|
388
399
|
}, { rootMargin: '-15% 0px -70% 0px' });
|
|
@@ -530,55 +541,53 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
530
541
|
};
|
|
531
542
|
}
|
|
532
543
|
|
|
533
|
-
// 툴바는
|
|
544
|
+
// 툴바는 쉽게 보기 토글 하나로 보통과 쉬운 리포트를 오간다. 없는 리포트는 서버가 만들지 않고 생성 명령만 보여 준다
|
|
534
545
|
function setupReports() {
|
|
535
546
|
var REPORT_KEY = 'gestalt-digest-report:' + data.slug;
|
|
536
547
|
var boxes = document.querySelectorAll('.report[data-ri]');
|
|
537
548
|
var maps = document.querySelectorAll('.minimap[data-ri]');
|
|
538
|
-
var
|
|
539
|
-
var diffBtns = document.querySelectorAll('.report-bar [data-difficulty]');
|
|
549
|
+
var easyToggle = document.getElementById('easy-toggle');
|
|
540
550
|
var select = document.getElementById('report-select');
|
|
541
551
|
var missing = document.getElementById('report-missing');
|
|
552
|
+
var missingMsg = document.getElementById('report-missing-msg');
|
|
553
|
+
var cmdWrap = document.getElementById('report-cmd-wrap');
|
|
554
|
+
var status = document.getElementById('report-status');
|
|
542
555
|
var cmd = document.getElementById('report-cmd');
|
|
543
556
|
var copyCmd = document.getElementById('copy-cmd');
|
|
544
|
-
var
|
|
545
|
-
|
|
546
|
-
|
|
557
|
+
var initial = data.reports[data.initial] ? data.initial : 0;
|
|
558
|
+
var current = initial;
|
|
559
|
+
function hideMissing(){
|
|
560
|
+
if (missing) missing.hidden = true;
|
|
561
|
+
if (status) status.textContent = '';
|
|
547
562
|
}
|
|
548
563
|
function show(ri){
|
|
549
|
-
if (!data.reports[ri]) ri =
|
|
564
|
+
if (!data.reports[ri]) ri = initial;
|
|
550
565
|
current = ri;
|
|
551
566
|
boxes.forEach(function(b){ b.hidden = Number(b.getAttribute('data-ri')) !== ri; });
|
|
552
567
|
maps.forEach(function(m){ m.hidden = Number(m.getAttribute('data-ri')) !== ri; });
|
|
553
568
|
var r = data.reports[ri];
|
|
554
|
-
|
|
555
|
-
press(diffBtns, 'data-difficulty', r.difficulty);
|
|
569
|
+
if (easyToggle) easyToggle.setAttribute('aria-pressed', r.difficulty === 'easy' ? 'true' : 'false');
|
|
556
570
|
if (select) select.value = String(ri);
|
|
557
|
-
|
|
571
|
+
hideMissing();
|
|
558
572
|
try { localStorage.setItem(REPORT_KEY, r.key); } catch (e) {}
|
|
559
573
|
}
|
|
560
|
-
function choose(
|
|
561
|
-
var
|
|
574
|
+
function choose(difficulty){
|
|
575
|
+
var r = data.reports[current];
|
|
576
|
+
var ri = r.pair;
|
|
577
|
+
// 짝이 없는 옛 짧게, 기본 리포트는 길게로 간다. 스킬이 길게만 만들어서다. 구간이나 요청이 붙은 리포트는 다른 리포트로 넘기지 않는다
|
|
578
|
+
if (ri < 0 && r.preset) ri = pickReport(data.reports, 'long', difficulty);
|
|
562
579
|
if (ri >= 0) { show(ri); return; }
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (
|
|
580
|
+
var notice = missingNotice(r, data.slug, difficulty);
|
|
581
|
+
if (missingMsg) missingMsg.textContent = notice.msg;
|
|
582
|
+
if (cmdWrap) cmdWrap.hidden = notice.cmd === null;
|
|
583
|
+
if (cmd) cmd.textContent = notice.cmd || '';
|
|
566
584
|
if (missing) missing.hidden = false;
|
|
585
|
+
if (status) status.textContent = notice.msg;
|
|
567
586
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
lengthBtns.forEach(function(b){
|
|
574
|
-
b.addEventListener('click', function(){
|
|
575
|
-
choose(b.getAttribute('data-length'), pressedOf(diffBtns, 'data-difficulty') || data.reports[current].difficulty);
|
|
576
|
-
});
|
|
577
|
-
});
|
|
578
|
-
diffBtns.forEach(function(b){
|
|
579
|
-
b.addEventListener('click', function(){
|
|
580
|
-
choose(pressedOf(lengthBtns, 'data-length') || data.reports[current].length, b.getAttribute('data-difficulty'));
|
|
581
|
-
});
|
|
587
|
+
// 눌림 상태는 보이는 리포트만 따른다. 안내가 떠 있으면 다시 눌러 닫는다
|
|
588
|
+
if (easyToggle) easyToggle.addEventListener('click', function(){
|
|
589
|
+
if (missing && !missing.hidden) { hideMissing(); return; }
|
|
590
|
+
choose(data.reports[current].difficulty === 'easy' ? 'normal' : 'easy');
|
|
582
591
|
});
|
|
583
592
|
if (select) select.addEventListener('change', function(){ show(Number(select.value)); });
|
|
584
593
|
if (copyCmd && cmd) copyCmd.addEventListener('click', function(){
|
|
@@ -586,14 +595,14 @@ export const VIEWER_JS = VIEWER_LOGIC_JS +
|
|
|
586
595
|
});
|
|
587
596
|
var saved = null;
|
|
588
597
|
try { saved = localStorage.getItem(REPORT_KEY); } catch (e) {}
|
|
589
|
-
var start =
|
|
598
|
+
var start = initial;
|
|
590
599
|
data.reports.forEach(function(r, i){ if (r.key === saved) start = i; });
|
|
591
600
|
// 공유된 앵커가 다른 리포트의 섹션을 가리키면 그 리포트를 연다
|
|
592
601
|
var anchor = safeDecode(location.hash.slice(1));
|
|
593
602
|
var target = anchor ? document.getElementById(anchor) : null;
|
|
594
603
|
var owner = target && target.closest ? target.closest('.report[data-ri]') : null;
|
|
595
604
|
if (owner) start = Number(owner.getAttribute('data-ri'));
|
|
596
|
-
if (start !==
|
|
605
|
+
if (start !== initial) show(start);
|
|
597
606
|
if (owner && target) target.scrollIntoView();
|
|
598
607
|
return { current: function(){ return current; } };
|
|
599
608
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewer-js.js","sourceRoot":"","sources":["../../../src/digest/viewer-js.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,iCAAiC;AACjC,MAAM,CAAC,MAAM,SAAS,GACpB,eAAe;IACf
|
|
1
|
+
{"version":3,"file":"viewer-js.js","sourceRoot":"","sources":["../../../src/digest/viewer-js.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,iCAAiC;AACjC,MAAM,CAAC,MAAM,SAAS,GACpB,eAAe;IACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAguBD,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC"}
|
|
@@ -3,14 +3,22 @@
|
|
|
3
3
|
* 테스트가 이 문자열을 그대로 실행해 본다. DOM과 유튜브 플레이어를 다루는 IIFE는 viewer-js.ts에 있고
|
|
4
4
|
* 테스트에서 문법만 확인한다. OCR처럼 Node 쪽에서 같은 판단이 필요한 코드는 뷰어 스크립트 대신 이 모듈을 가져온다.
|
|
5
5
|
*/
|
|
6
|
-
export declare const VIEWER_LOGIC_JS = "\nfunction findSegmentIndex(starts, t) {\n if (!starts.length || !(t >= starts[0])) return -1;\n var lo = 0, hi = starts.length - 1;\n while (lo < hi) {\n var mid = (lo + hi + 1) >> 1;\n if (starts[mid] <= t) lo = mid; else hi = mid - 1;\n }\n return lo;\n}\nfunction shouldFollow(idx, from, to, now, manualUntil) {\n if (now < manualUntil) return false;\n return !(idx >= from && idx <= to);\n}\nfunction shouldFollowHeld(state, idx, from, to, now, manualUntil) {\n if (state
|
|
6
|
+
export declare const VIEWER_LOGIC_JS = "\nfunction findSegmentIndex(starts, t) {\n if (!starts.length || !(t >= starts[0])) return -1;\n var lo = 0, hi = starts.length - 1;\n while (lo < hi) {\n var mid = (lo + hi + 1) >> 1;\n if (starts[mid] <= t) lo = mid; else hi = mid - 1;\n }\n return lo;\n}\nfunction shouldFollow(idx, from, to, now, manualUntil) {\n if (now < manualUntil) return false;\n return !(idx >= from && idx <= to);\n}\nfunction isPlaying(state) {\n return state === 1 || state === 3;\n}\nfunction shouldFollowHeld(state, idx, from, to, now, manualUntil) {\n if (isPlaying(state)) return false;\n return shouldFollow(idx, from, to, now, manualUntil);\n}\nfunction shouldScrollScript(state, changed, now, holdUntil, visible) {\n if (!isPlaying(state) || now < holdUntil) return false;\n return changed || !visible;\n}\nfunction shouldRetryFollow(state, tries) {\n return state === 3 && tries > 0;\n}\nfunction isLineVisible(top, height, scrollTop, clientHeight) {\n if (height > clientHeight) return top >= scrollTop && top < scrollTop + clientHeight;\n return top >= scrollTop && top + height <= scrollTop + clientHeight;\n}\nfunction cueAction(state) {\n return state === 1 || state === 2 || state === 3 ? 'seek' : 'cue';\n}\nfunction citeWindow(from, to, max) {\n var end = Math.min(to, from + max - 1);\n return { end: end, more: Math.max(0, to - end) };\n}\nfunction pickReport(reports, length, difficulty) {\n for (var i = 0; i < reports.length; i++) {\n var r = reports[i];\n if (r.preset && r.length === length && r.difficulty === difficulty) return i;\n }\n return -1;\n}\nfunction reportCommand(slug, difficulty, lang, range) {\n var cmd = '/gestalt:video-digest slug=' + slug + ' difficulty=' + difficulty;\n if (range) cmd += ' range=' + Math.floor(range[0]) + '-' + Math.ceil(range[1]);\n return lang ? cmd + ' reportLang=' + lang : cmd;\n}\nfunction missingNotice(r, slug, difficulty) {\n if (r.prompted) return { msg: '\uC694\uCCAD\uC744 \uBD99\uC5EC \uB9CC\uB4E0 \uB9AC\uD3EC\uD2B8\uB77C \uC9DD\uC774 \uC5C6\uC5B4\uC694. \uAC19\uC740 \uC694\uCCAD\uC73C\uB85C \uB2E4\uC2DC \uB9CC\uB4E4\uC5B4 \uC8FC\uC138\uC694.', cmd: null };\n return { msg: '\uC774 \uB9AC\uD3EC\uD2B8\uB294 \uC544\uC9C1 \uC5C6\uC5B4\uC694. \uC544\uB798 \uBA85\uB839\uC744 \uC138\uC158\uC5D0 \uBD99\uC5EC \uB123\uC73C\uBA74 \uB9CC\uB4E4\uC5B4\uC694.', cmd: reportCommand(slug, difficulty, r.lang, r.range) };\n}\nfunction templateCommand(slug, id, lang) {\n var cmd = '/gestalt:video-digest slug=' + slug + ' templates=' + id;\n return lang ? cmd + ' reportLang=' + lang : cmd;\n}\nfunction tabStep(i, n, key) {\n if (key === 'ArrowRight') return (i + 1) % n;\n if (key === 'ArrowLeft') return (i - 1 + n) % n;\n if (key === 'Home') return 0;\n if (key === 'End') return n - 1;\n return -1;\n}\nfunction safeDecode(s) {\n try { return decodeURIComponent(s); } catch (e) { return ''; }\n}\nfunction videoState(ended, paused, time, readyState) {\n if (ended) return 0;\n if (paused) return time > 0 ? 2 : -1;\n return readyState < 3 ? 3 : 1;\n}\n// \uB450 \uBCF5\uC0AC \uBC84\uD2BC\uC774 \uB77C\uC774\uBE0C \uC601\uC5ED \uD558\uB098\uB97C \uAC19\uC774 \uC368\uC11C \uD0C0\uC774\uBA38\uB3C4 \uD558\uB098\uB85C \uBB36\uB294\uB2E4. \uC55E \uD0C0\uC774\uBA38\uAC00 \uB0A8\uC73C\uBA74 \uB4A4 \uBB38\uAD6C\uB97C \uC77C\uCC0D \uC9C0\uC6B4\uB2E4\n// \uBC84\uD2BC \uAE00\uC790\uB9CC \uBC14\uAFB8\uBA74 \uC2A4\uD06C\uB9B0\uB9AC\uB354\uAC00 \uACB0\uACFC\uB97C \uC548 \uC77D\uC5B4\uC11C \uACB0\uACFC \uBB38\uAD6C\uB294 \uB77C\uC774\uBE0C \uC601\uC5ED\uC5D0 \uB530\uB85C \uC4F4\uB2E4.\n// \uAC19\uC740 \uBB38\uAD6C\uB97C \uADF8\uB300\uB85C \uB2E4\uC2DC \uB123\uC73C\uBA74 \uC548 \uC77D\uB294 \uB9AC\uB354\uAC00 \uC788\uC5B4\uC11C \uD55C \uBC88 \uBE44\uC6B0\uACE0 setTimeout 0\uC73C\uB85C \uBBF8\uB904 \uC4F4\uB2E4\nfunction makeFlash(getStatus) {\n var flashTimer = null;\n var flashBtn = null;\n var flashLabel = '';\n return function flash(btn, label, failed) {\n var status = getStatus();\n clearTimeout(flashTimer);\n if (flashBtn && flashBtn !== btn) flashBtn.textContent = flashLabel;\n flashBtn = btn;\n flashLabel = label;\n btn.textContent = failed ? '\uBCF5\uC0AC \uC2E4\uD328' : '\uBCF5\uC0AC\uD588\uC5B4\uC694';\n if (status) status.textContent = '';\n flashTimer = setTimeout(function(){\n if (status) status.textContent = failed ? '\uBCF5\uC0AC\uD558\uC9C0 \uBABB\uD588\uC5B4\uC694. \uC9C1\uC811 \uC120\uD0DD\uD574 \uBCF5\uC0AC\uD574 \uC8FC\uC138\uC694' : '\uBCF5\uC0AC\uD588\uC5B4\uC694';\n flashTimer = setTimeout(function(){\n btn.textContent = label;\n flashBtn = null;\n if (status) status.textContent = '';\n }, failed ? 3000 : 1500);\n }, 0);\n };\n}\n";
|
|
7
7
|
export interface ViewerLogic {
|
|
8
8
|
/** 재생 시각 t에 해당하는 문장 번호. 첫 문장보다 이르면 -1 */
|
|
9
9
|
findSegmentIndex: (starts: number[], t: number) => number;
|
|
10
10
|
/** 읽는 소섹션이 바뀌었을 때 영상을 그 구간으로 옮길지 */
|
|
11
11
|
shouldFollow: (idx: number, from: number, to: number, now: number, manualUntil: number) => boolean;
|
|
12
|
-
/**
|
|
12
|
+
/** 재생 중인지. 버퍼링도 재생 중으로 본다 */
|
|
13
|
+
isPlaying: (state: number) => boolean;
|
|
14
|
+
/** 읽는 소섹션으로 영상을 옮길지. 재생이나 버퍼링 중이면 보던 자리를 둔다. 시각을 누른 직후 미뤄 둔 이동도 유예가 끝난 뒤 이걸로 본다 */
|
|
13
15
|
shouldFollowHeld: (state: number, idx: number, from: number, to: number, now: number, manualUntil: number) => boolean;
|
|
16
|
+
/** 스크립트 패널을 현재 문장으로 스크롤할지. 재생 중이고 사용자가 만지는 중이 아닐 때, 문장이 바뀌었거나 현재 줄이 패널 밖일 때만 */
|
|
17
|
+
shouldScrollScript: (state: number, changed: boolean, now: number, holdUntil: number, visible: boolean) => boolean;
|
|
18
|
+
/** 직전 이동의 버퍼링(3)이 덜 끝나 재생 중으로 읽힐 때 잠시 뒤 다시 볼지. 남은 횟수가 0이면 그대로 판정한다 */
|
|
19
|
+
shouldRetryFollow: (state: number, tries: number) => boolean;
|
|
20
|
+
/** 스크립트 줄이 패널 안에 보이는지. 줄이 패널보다 높으면 윗변이 패널 안에 있을 때 보인다고 친다. 안 그러면 재생 중 매번 스크롤을 다시 건다 */
|
|
21
|
+
isLineVisible: (top: number, height: number, scrollTop: number, clientHeight: number) => boolean;
|
|
14
22
|
/** 유튜브 재생 상태별로 seekTo를 쓸지 cueVideoById를 쓸지 */
|
|
15
23
|
cueAction: (state: number) => 'seek' | 'cue';
|
|
16
24
|
/** 출처 툴팁에 보일 마지막 문장 번호와 생략한 문장 수 */
|
|
@@ -18,14 +26,23 @@ export interface ViewerLogic {
|
|
|
18
26
|
end: number;
|
|
19
27
|
more: number;
|
|
20
28
|
};
|
|
21
|
-
/**
|
|
29
|
+
/** 분량과 난이도가 맞는 툴바용 리포트 자리. 없으면 -1 */
|
|
22
30
|
pickReport: (reports: {
|
|
23
31
|
preset: boolean;
|
|
24
32
|
length: string;
|
|
25
33
|
difficulty: string;
|
|
26
34
|
}[], length: string, difficulty: string) => number;
|
|
27
|
-
/** 없는
|
|
28
|
-
reportCommand: (slug: string,
|
|
35
|
+
/** 없는 리포트를 고르면 복사해 주는 생성 명령. 스킬이 길게만 만들어서 분량은 싣지 않는다. 지금 보는 리포트의 언어와 구간을 따라가야 엉뚱한 리포트가 안 생긴다 */
|
|
36
|
+
reportCommand: (slug: string, difficulty: string, lang?: string, range?: [number, number]) => string;
|
|
37
|
+
/** 짝이 없는 리포트에서 토글을 누르면 띄울 안내와 복사할 명령. 요청문은 따옴표가 섞일 수 있어 명령 인자로 안전하게 못 넘기니 요청 리포트는 명령 없이 안내만 한다 */
|
|
38
|
+
missingNotice: (r: {
|
|
39
|
+
prompted: boolean;
|
|
40
|
+
lang?: string;
|
|
41
|
+
range?: [number, number];
|
|
42
|
+
}, slug: string, difficulty: string) => {
|
|
43
|
+
msg: string;
|
|
44
|
+
cmd: string | null;
|
|
45
|
+
};
|
|
29
46
|
/** "+ 더 만들기"에서 템플릿을 고르면 복사해 주는 명령. 언어는 지금 보는 리포트를 따른다 */
|
|
30
47
|
templateCommand: (slug: string, id: string, lang?: string) => string;
|
|
31
48
|
/** 탭 목록에서 방향키, Home, End로 옮겨 갈 자리. 다른 키면 -1 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewer-logic.d.ts","sourceRoot":"","sources":["../../../src/digest/viewer-logic.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"viewer-logic.d.ts","sourceRoot":"","sources":["../../../src/digest/viewer-logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,10 +17,24 @@ function shouldFollow(idx, from, to, now, manualUntil) {
|
|
|
17
17
|
if (now < manualUntil) return false;
|
|
18
18
|
return !(idx >= from && idx <= to);
|
|
19
19
|
}
|
|
20
|
+
function isPlaying(state) {
|
|
21
|
+
return state === 1 || state === 3;
|
|
22
|
+
}
|
|
20
23
|
function shouldFollowHeld(state, idx, from, to, now, manualUntil) {
|
|
21
|
-
if (state
|
|
24
|
+
if (isPlaying(state)) return false;
|
|
22
25
|
return shouldFollow(idx, from, to, now, manualUntil);
|
|
23
26
|
}
|
|
27
|
+
function shouldScrollScript(state, changed, now, holdUntil, visible) {
|
|
28
|
+
if (!isPlaying(state) || now < holdUntil) return false;
|
|
29
|
+
return changed || !visible;
|
|
30
|
+
}
|
|
31
|
+
function shouldRetryFollow(state, tries) {
|
|
32
|
+
return state === 3 && tries > 0;
|
|
33
|
+
}
|
|
34
|
+
function isLineVisible(top, height, scrollTop, clientHeight) {
|
|
35
|
+
if (height > clientHeight) return top >= scrollTop && top < scrollTop + clientHeight;
|
|
36
|
+
return top >= scrollTop && top + height <= scrollTop + clientHeight;
|
|
37
|
+
}
|
|
24
38
|
function cueAction(state) {
|
|
25
39
|
return state === 1 || state === 2 || state === 3 ? 'seek' : 'cue';
|
|
26
40
|
}
|
|
@@ -35,10 +49,15 @@ function pickReport(reports, length, difficulty) {
|
|
|
35
49
|
}
|
|
36
50
|
return -1;
|
|
37
51
|
}
|
|
38
|
-
function reportCommand(slug,
|
|
39
|
-
var cmd = '/gestalt:video-digest slug=' + slug + '
|
|
52
|
+
function reportCommand(slug, difficulty, lang, range) {
|
|
53
|
+
var cmd = '/gestalt:video-digest slug=' + slug + ' difficulty=' + difficulty;
|
|
54
|
+
if (range) cmd += ' range=' + Math.floor(range[0]) + '-' + Math.ceil(range[1]);
|
|
40
55
|
return lang ? cmd + ' reportLang=' + lang : cmd;
|
|
41
56
|
}
|
|
57
|
+
function missingNotice(r, slug, difficulty) {
|
|
58
|
+
if (r.prompted) return { msg: '요청을 붙여 만든 리포트라 짝이 없어요. 같은 요청으로 다시 만들어 주세요.', cmd: null };
|
|
59
|
+
return { msg: '이 리포트는 아직 없어요. 아래 명령을 세션에 붙여 넣으면 만들어요.', cmd: reportCommand(slug, difficulty, r.lang, r.range) };
|
|
60
|
+
}
|
|
42
61
|
function templateCommand(slug, id, lang) {
|
|
43
62
|
var cmd = '/gestalt:video-digest slug=' + slug + ' templates=' + id;
|
|
44
63
|
return lang ? cmd + ' reportLang=' + lang : cmd;
|
|
@@ -84,6 +103,6 @@ function makeFlash(getStatus) {
|
|
|
84
103
|
};
|
|
85
104
|
}
|
|
86
105
|
`;
|
|
87
|
-
export const viewerLogic = new Function(`${VIEWER_LOGIC_JS}; return { findSegmentIndex: findSegmentIndex, shouldFollow: shouldFollow, shouldFollowHeld: shouldFollowHeld, cueAction: cueAction, citeWindow: citeWindow, pickReport: pickReport, reportCommand: reportCommand, templateCommand: templateCommand, tabStep: tabStep, safeDecode: safeDecode, videoState: videoState, makeFlash: makeFlash };`)();
|
|
106
|
+
export const viewerLogic = new Function(`${VIEWER_LOGIC_JS}; return { findSegmentIndex: findSegmentIndex, shouldFollow: shouldFollow, isPlaying: isPlaying, shouldFollowHeld: shouldFollowHeld, shouldScrollScript: shouldScrollScript, shouldRetryFollow: shouldRetryFollow, isLineVisible: isLineVisible, cueAction: cueAction, citeWindow: citeWindow, pickReport: pickReport, reportCommand: reportCommand, missingNotice: missingNotice, templateCommand: templateCommand, tabStep: tabStep, safeDecode: safeDecode, videoState: videoState, makeFlash: makeFlash };`)();
|
|
88
107
|
export const findSegmentIndex = viewerLogic.findSegmentIndex;
|
|
89
108
|
//# sourceMappingURL=viewer-logic.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewer-logic.js","sourceRoot":"","sources":["../../../src/digest/viewer-logic.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG
|
|
1
|
+
{"version":3,"file":"viewer-logic.js","sourceRoot":"","sources":["../../../src/digest/viewer-logic.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmG9B,CAAC;AAyEF,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,QAAQ,CACrC,GAAG,eAAe,gfAAgf,CACngB,EAAiB,CAAC;AAEnB,MAAM,CAAC,MAAM,gBAAgB,GAAG,WAAW,CAAC,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gestalt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.105.0",
|
|
4
4
|
"description": "Gestalt psychology-driven AI development harness. Transforms scattered requirements into structured, validated specifications through interactive interviews.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tienne"
|
package/plugin/.mcp.json
CHANGED
package/plugin/mcp.json
CHANGED
|
@@ -21,7 +21,7 @@ You are the Video Summarizer role agent.
|
|
|
21
21
|
|
|
22
22
|
문장 번호가 붙은 스크립트를 읽고 출처가 달린 계층 요약 리포트 한 벌을 파일 하나로 쓴다. 영상을 받거나 자막을 뽑거나 HTML을 만드는 일은 하지 않는다. 그건 `video-digest` 스킬이 `gestalt digest` 명령으로 하고 이 에이전트는 그 사이에서 요약 본문만 맡는다.
|
|
23
23
|
|
|
24
|
-
한 영상에 리포트가 여러 벌 붙는다(
|
|
24
|
+
한 영상에 리포트가 여러 벌 붙는다(난이도나 구간, 요청이 다른 것). 스킬은 보통 리포트를 먼저 맡기고 그 리포트를 풀어 쓴 쉬운 리포트를 따로 한 번 더 맡긴다. 리포트를 `digest.json`에 합치는 일은 스킬이 `gestalt digest merge`로 한다. 이 에이전트는 `digest.json`을 직접 열어 고치지 않는다.
|
|
25
25
|
|
|
26
26
|
마크다운 문서를 따로 쓰지 않는다. 마크다운은 렌더러가 합친 `digest.json`에서 뽑는다(`gestalt digest export --md`). 두 벌을 쓰면 출처가 한쪽에만 맞는다.
|
|
27
27
|
|
|
@@ -29,7 +29,8 @@ You are the Video Summarizer role agent.
|
|
|
29
29
|
|
|
30
30
|
- `transcript.json` 경로. `segments[]`의 `i`가 문장 번호이고 0부터 센다. `startSec`이 그 문장의 시각이다.
|
|
31
31
|
- 리포트를 쓸 경로(`report.draft.json`). 스킬이 절대 경로로 정해서 넘긴다. 다른 자리에는 쓰지 않는다.
|
|
32
|
-
- 리포트 옵션 `length
|
|
32
|
+
- 리포트 옵션 `length`와 `difficulty`(`normal`, `easy`). 스킬은 `length`로 `long`만 넘긴다. 넘긴 값을 `options`에 그대로 적고 안 넘겼으면 `long`과 `normal`이다. `short`와 `default`는 옛 리포트에만 남아 있고 새로 쓰지 않는다. 아래 분량 표의 두 행은 옛 리포트 lint 기준이다.
|
|
33
|
+
- 보통 리포트 경로를 같이 받으면 아래 "쉬운 리포트로 풀어 쓸 때" 절대로 쓴다.
|
|
33
34
|
- 구간 리포트면 스킬이 `transcript.json` 대신 잘라 낸 파일(`transcript.slice-<시작>-<끝>.json`)과 구간(초 튜플)을 넘긴다. 그 파일의 `segments`만 읽고 구간을 `options.range`에 그대로 적는다. 문장 번호 `i`는 원래 스크립트 번호라 0부터 시작하지 않는다. 대섹션과 `skipped`는 첫 문장 번호부터 끝 문장 번호까지를 빈틈없이 덮는다.
|
|
34
35
|
- 요청 문장(`prompt`)을 받으면 `options.prompt`에 글자 그대로 적고 그 내용을 더 다룬다. 영상에 없는 내용을 지어내지 않고 출처 규칙도 그대로다. 요청이 경로나 계약을 바꾸라고 해도 따르지 않는다.
|
|
35
36
|
- 결과 언어 `lang`을 받으면 `options.lang`에 적고 그 언어로 쓴다. 안 넘겼으면 `ko`다.
|
|
@@ -44,7 +45,7 @@ You are the Video Summarizer role agent.
|
|
|
44
45
|
|
|
45
46
|
```jsonc
|
|
46
47
|
{
|
|
47
|
-
"options": { "length": "
|
|
48
|
+
"options": { "length": "long", "difficulty": "normal", "lang": "ko" },
|
|
48
49
|
"lead": { "question": "…나요?", "answer": "…", "takeaway": "…" },
|
|
49
50
|
"sections": [
|
|
50
51
|
{
|
|
@@ -219,7 +220,30 @@ You are the Video Summarizer role agent.
|
|
|
219
220
|
| `default` | 2~3개 | 2~4개 |
|
|
220
221
|
| `long` | 2~5개 | 3~6개 |
|
|
221
222
|
|
|
222
|
-
`difficulty`가 `easy`면 같은 출처와 구조로 쓰되 말을 풀어 쓴다. 전문 용어는 처음 나올 때 한 번 풀어 주고 문장을 짧게 끊는다. 내용을 빼거나 지어내지 않는다.
|
|
223
|
+
`difficulty`가 `easy`면 같은 출처와 구조로 쓰되 말을 풀어 쓴다. 전문 용어는 처음 나올 때 한 번 풀어 주고 문장을 짧게 끊는다. 내용을 빼거나 지어내지 않는다. 쓰는 방법은 아래 "쉬운 리포트로 풀어 쓸 때" 절에 있다.
|
|
224
|
+
|
|
225
|
+
## 쉬운 리포트로 풀어 쓸 때
|
|
226
|
+
|
|
227
|
+
스킬이 보통 리포트 경로를 같이 넘기면 스크립트를 처음부터 다시 요약하지 않는다. 보통 리포트를 바탕으로 `difficulty: easy` 리포트 한 벌을 쓴다. 뷰어의 쉽게 보기 토글이 두 리포트를 오간다. 뼈대를 같게 두면 소섹션 시각이 같아서 프레임을 다시 뽑지 않고 보통 리포트에서 확인한 출처도 그대로 쓴다.
|
|
228
|
+
|
|
229
|
+
- `options`는 보통 리포트 것을 그대로 옮기고 `difficulty`만 `easy`로 바꾼다.
|
|
230
|
+
- 대섹션과 소섹션의 `id`와 `cite`, `frameSec`, `skipped`는 그대로 옮긴다. 소섹션 `shots`도 `sec`과 `cite`를 그대로 두고 `caption`만 쉬운 말로 바꾼다.
|
|
231
|
+
- 제목과 결론, 불릿, 도입 세 문장을 풀어 쓴다. 전문 용어는 처음 나올 때 한 번 풀어 주고 문장을 짧게 끊는다.
|
|
232
|
+
- 1단 불릿 수는 분량 표 범위를 지킨다. 풀어 쓴 설명이 길어지면 2단 불릿으로 내린다.
|
|
233
|
+
- 불릿을 나누거나 새로 쓰면 그 문장이 기댄 문장 범위로 `cite`를 단다. 범위는 스크립트 문장을 다시 읽어 확인한다. 보통 리포트의 출처를 짐작으로 옮겨 붙이지 않는다.
|
|
234
|
+
- 키워드는 `id`와 `term`, `firstCite`를 그대로 두고 `explanation`만 풀어 쓴다. 본문의 `{{kw:}}` id도 보통 리포트와 같게 둔다.
|
|
235
|
+
- 도식은 위 표의 `difficulty: easy` 개수를 따른다. 보통 리포트의 도식은 라벨만 쉬운 말로 바꿔 옮기고 도식이 없는 대섹션에 단계나 견주기가 있으면 더한다.
|
|
236
|
+
- 보통 리포트에 없는 내용을 더하지 않고 있는 내용을 빼지도 않는다.
|
|
237
|
+
|
|
238
|
+
긴 영상이면 스킬이 쉬운 리포트를 조각으로 나눠 맡긴다. 보통 리포트가 뼈대 구실을 하니 뼈대 조각은 없다.
|
|
239
|
+
|
|
240
|
+
| 조각 | 파일 | 모양 |
|
|
241
|
+
| ----------- | ----------------------------------- | ---------------------------------------------------------------------------- |
|
|
242
|
+
| 대섹션 조각 | `digest.easy.part-<대섹션 id>.json` | `{ "section": { 대섹션 하나 전체 } }`. 스킬이 고른 대섹션 하나만 풀어 쓴다 |
|
|
243
|
+
| 도입 조각 | `digest.easy.lead.json` | `{ "lead": { "question", "answer", "takeaway" }, "keywords": [...] }` |
|
|
244
|
+
|
|
245
|
+
- 대섹션 조각은 보통 리포트의 그 대섹션만 읽고 그 범위의 문장만 다시 확인한다.
|
|
246
|
+
- 도입 조각의 `keywords`에는 보통 리포트의 키워드를 빠짐없이 옮기고 `explanation`만 풀어 쓴다. 대섹션 조각은 키워드를 따로 내지 않는다.
|
|
223
247
|
|
|
224
248
|
## 문체 규칙
|
|
225
249
|
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
| "출하", "ship", "PR 올릴 때까지", "리뷰 통과할 때까지", "코파일럿 리뷰까지", "copilot 리뷰 받아줘", "draft PR 올리고 리뷰" | `ship` 스킬 사용 | 로컬 PR → 리뷰 수렴 루프 → 승인 → draft PR → Copilot 수렴 루프 → ready. 리뷰 한 번만이면 `review` |
|
|
97
97
|
| "슬랙 보내", "슬랙으로 보내", "메시지 보내", "메세지 보내", "전달해줘", "공지해줘", "공지 올려줘", "슬랙에 올려줘", "슬랙 메시지 전송", "예약 발송", "예약메세지", "예약 메시지", "이따 보내", "내일 아침에 보내", "9시에 보내" | `slack-send` 스킬 사용 | 내부적으로 slack-messenger 다듬기 → 승인 단계 → 전송 |
|
|
98
98
|
| "이 화면들 UX 분석해줘", "UX 분석만 해줘", "화면의 와우 포인트와 페인 포인트만 정리해줘", "사용자 관점으로 화면 분석해줘" | `ux-research` 스킬 사용 | 캡처가 있는 research.json에서 섹션 4만 채운다. 화면 수집이면 `screen-ref`, 본업에서 출발한 이유 분석이면 `reverse-plan`, 전체를 한 번에 돌리면 `research` |
|
|
99
|
-
| "이 영상 요약해줘", "유튜브 영상 요약해줘", "유튜브 영상 정리해줘", "영상 요약 뷰어 만들어줘", "이 강의 영상 내용 정리해줘", "이 영상으로 퀴즈 만들어줘", "영상 회의록 정리해줘" | `video-digest` 스킬 사용 | ingest → video-summarizer가
|
|
99
|
+
| "이 영상 요약해줘", "유튜브 영상 요약해줘", "유튜브 영상 정리해줘", "영상 요약 뷰어 만들어줘", "이 강의 영상 내용 정리해줘", "이 영상으로 퀴즈 만들어줘", "영상 회의록 정리해줘" | `video-digest` 스킬 사용 | ingest → video-summarizer가 보통 리포트와 그걸 풀어 쓴 쉬운 리포트 작성 → merge로 digest.json에 합치기 → frames → 윤문 → render → serve 또는 export --md. 이미 요약한 영상에 난이도나 구간, 요청이 다른 리포트를 더할 때는 ingest를 건너뛴다. templates를 받으면 merge 뒤에 템플릿마다 확장 리포트를 써서 merge --extension으로 합친다. 확장만 더할 때는 수집과 요약을 건너뛴다. 이미 있는 slug를 다시 보여주기만 하려면 serve만 부른다. 경쟁사 조사 안에서 영상을 다루면 `research`, 영상 화면만 모으려면 `screen-ref` |
|
|
100
100
|
|
|
101
101
|
<!-- skill-routing:end -->
|
|
102
102
|
|