Update project files
This commit is contained in:
@@ -408,8 +408,31 @@ function move(role,x,y){
|
||||
ch.style.left=x+'px';
|
||||
ch.style.top=y+'px';
|
||||
}
|
||||
setInterval(()=>{Object.keys(chars).forEach(k=>{const a=anim[k];if(a.mode==='walk'){a.frame=(a.frame+1)%5;setSprite(k,'walk',a.frame,a.dir)}else if(a.mode==='work'){a.frame=(a.frame+1)%4;setSprite(k,'work',a.frame)} });},286)
|
||||
setInterval(()=>{Object.keys(chars).forEach(k=>{const a=anim[k];if(a.mode==='sit'){a.frame=(a.frame+1)%2;setSprite(k,'sit',a.frame)} });},700)
|
||||
// ── Managed intervals (pause while the office view is hidden) ──
|
||||
// The pixel-office runs several animation/roam/banter intervals. While the
|
||||
// webview tab is not visible they do invisible work and keep timers hot —
|
||||
// wasteful. _managedInterval registers each one; a visibilitychange handler
|
||||
// pauses them all when the document is hidden and resumes them when shown.
|
||||
// Behavior while visible is unchanged (same callbacks, same periods).
|
||||
const _managedIntervals=[];
|
||||
function _managedInterval(fn,ms){
|
||||
const rec={fn:fn,ms:ms,id:null};
|
||||
rec.id=setInterval(fn,ms);
|
||||
_managedIntervals.push(rec);
|
||||
return rec;
|
||||
}
|
||||
function _pauseManagedIntervals(){
|
||||
for(const rec of _managedIntervals){ if(rec.id!==null){ clearInterval(rec.id); rec.id=null; } }
|
||||
}
|
||||
function _resumeManagedIntervals(){
|
||||
for(const rec of _managedIntervals){ if(rec.id===null){ rec.id=setInterval(rec.fn,rec.ms); } }
|
||||
}
|
||||
document.addEventListener('visibilitychange',()=>{
|
||||
if(document.hidden) _pauseManagedIntervals();
|
||||
else _resumeManagedIntervals();
|
||||
});
|
||||
_managedInterval(()=>{Object.keys(chars).forEach(k=>{const a=anim[k];if(a.mode==='walk'){a.frame=(a.frame+1)%5;setSprite(k,'walk',a.frame,a.dir)}else if(a.mode==='work'){a.frame=(a.frame+1)%4;setSprite(k,'work',a.frame)} });},286)
|
||||
_managedInterval(()=>{Object.keys(chars).forEach(k=>{const a=anim[k];if(a.mode==='sit'){a.frame=(a.frame+1)%2;setSprite(k,'sit',a.frame)} });},700)
|
||||
// ── 책상 회피 path planner ──
|
||||
// walkPath의 각 leg를 직선이 아닌 *책상을 우회하는* L자 또는 corridor 경로로
|
||||
// 펴서 캐릭터가 책상을 가로지르지 않게. 책상이 회전됐을 때를 대비해 padding
|
||||
@@ -498,7 +521,7 @@ function sendHome(role,mode='sit'){
|
||||
if(Math.abs(cx-hx)<1&&Math.abs(cy-hy)<1){setSprite(role,mode);return;}
|
||||
walkPath(role,[st.dock,[hx,hy]],()=>setSprite(role,mode));
|
||||
}
|
||||
setInterval(()=>{
|
||||
_managedInterval(()=>{
|
||||
if(!['idle','done'].includes(_prevStatus || 'idle')) return;
|
||||
const free=Object.keys(chars).filter(k=>anim[k]?.mode==='sit'&&!chars[k].classList.contains('active'));
|
||||
if(!free.length)return;
|
||||
@@ -725,7 +748,7 @@ function _innerThoughtTick(){
|
||||
const text = _innerThoughtFor(st.agentKey, anim[role].mode);
|
||||
if(text) _bubbleFromLog(role, text);
|
||||
}
|
||||
setInterval(_innerThoughtTick, 7500);
|
||||
_managedInterval(_innerThoughtTick, 7500);
|
||||
|
||||
// ── Webtoon-style 티키타카 banter (refactor: pipeline-aware) ──
|
||||
// 각 phase 에 *시퀀스화된 대화 script* 가 있어 phase 진입 시 한 줄씩 시간 차로 emit.
|
||||
|
||||
@@ -584,7 +584,35 @@ interface FileScan {
|
||||
documentProject: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* mtime-keyed scan cache. The previous implementation re-read (and re-classified)
|
||||
* every brain file from disk on every chat message. We now reuse a parsed
|
||||
* `FileScan` while the file's mtime is unchanged — re-reading only when the file
|
||||
* actually changes. This mirrors the mtime-keyed caching style of
|
||||
* `retrieval/brainIndex.ts` (whose `getBrainTokenIndex` caches tokens the same
|
||||
* way) while keeping the scan output byte-identical, so scoring is unaffected.
|
||||
*/
|
||||
interface ScanCacheEntry {
|
||||
mtimeMs: number;
|
||||
size: number;
|
||||
scan: FileScan;
|
||||
}
|
||||
const _scanCache = new Map<string, ScanCacheEntry>();
|
||||
|
||||
function scanFile(file: string, brainRoot: string): FileScan {
|
||||
let mtimeMs = 0;
|
||||
let size = 0;
|
||||
try {
|
||||
const stat = fs.statSync(file);
|
||||
mtimeMs = stat.mtimeMs;
|
||||
size = stat.size;
|
||||
const cached = _scanCache.get(file);
|
||||
if (cached && cached.mtimeMs === mtimeMs && cached.size === size) {
|
||||
return cached.scan;
|
||||
}
|
||||
} catch {
|
||||
// stat failed — fall through and attempt a fresh read (which will also fail safely)
|
||||
}
|
||||
const relative = path.relative(brainRoot, file);
|
||||
const title = path.basename(file, path.extname(file));
|
||||
let content = '';
|
||||
@@ -598,7 +626,11 @@ function scanFile(file: string, brainRoot: string): FileScan {
|
||||
const lower = content.toLowerCase();
|
||||
const documentProject = inferDocumentProject(relative, lower);
|
||||
const titleWithPath = `${relative.replace(/[\\/]/g, ' ')} ${title}`;
|
||||
return { file, relative, title, titleWithPath, content, lower, sourceType, knowledgeRole, documentProject };
|
||||
const scan: FileScan = { file, relative, title, titleWithPath, content, lower, sourceType, knowledgeRole, documentProject };
|
||||
if (mtimeMs > 0) {
|
||||
_scanCache.set(file, { mtimeMs, size, scan });
|
||||
}
|
||||
return scan;
|
||||
}
|
||||
|
||||
function scoreScan(scan: FileScan, terms: string[], intent: SecondBrainQueryIntent, targetProject?: string): SecondBrainTraceDocument {
|
||||
|
||||
Reference in New Issue
Block a user