mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-10-14 16:17:01 +00:00
Fixes for block timer lookback
This commit is contained in:
1 parent
9c97b0b6c0
commit
27632788a5
6 files changed
+103
-67
No files matched your search
+7
-3
@@ -2,6 +2,10 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { runTUI } from './tui';
|
||||
import type {
|
||||
BlockMetrics,
|
||||
TokenMetrics
|
||||
} from './types';
|
||||
import { StatusJSONSchema } from './types/StatusJSON';
|
||||
import { updateColorMap } from './utils/colors';
|
||||
import {
|
||||
@@ -68,15 +72,15 @@ async function renderMultipleLines(data: StatusJSON) {
|
||||
// Check if block timer is needed
|
||||
const hasBlockTimer = lines.some(line => line.some(item => item.type === 'block-timer'));
|
||||
|
||||
let tokenMetrics = null;
|
||||
let tokenMetrics: TokenMetrics | null = null;
|
||||
if (hasTokenItems && data.transcript_path)
|
||||
tokenMetrics = await getTokenMetrics(data.transcript_path);
|
||||
|
||||
let sessionDuration = null;
|
||||
let sessionDuration: string | null = null;
|
||||
if (hasSessionClock && data.transcript_path)
|
||||
sessionDuration = await getSessionDuration(data.transcript_path);
|
||||
|
||||
let blockMetrics = null;
|
||||
let blockMetrics: BlockMetrics | null = null;
|
||||
if (hasBlockTimer && data.transcript_path)
|
||||
blockMetrics = getBlockMetrics(data.transcript_path);
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface BlockMetrics {
|
||||
startTime: Date;
|
||||
lastActivity: Date;
|
||||
isActive: boolean;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BlockInfo } from '../utils/jsonl';
|
||||
import type { BlockMetrics } from '../types';
|
||||
|
||||
import type { StatusJSON } from './StatusJSON';
|
||||
import type { TokenMetrics } from './TokenMetrics';
|
||||
@@ -7,7 +7,7 @@ export interface RenderContext {
|
||||
data?: StatusJSON;
|
||||
tokenMetrics?: TokenMetrics | null;
|
||||
sessionDuration?: string | null;
|
||||
blockMetrics?: BlockInfo | null;
|
||||
blockMetrics?: BlockMetrics | null;
|
||||
terminalWidth?: number | null;
|
||||
isPreview?: boolean;
|
||||
lineIndex?: number; // Index of the current line being rendered (for theme cycling)
|
||||
|
||||
+2
-1
@@ -16,4 +16,5 @@ export type { TokenMetrics, TokenUsage, TranscriptLine } from './TokenMetrics';
|
||||
export type { RenderContext } from './RenderContext';
|
||||
export type { PowerlineFontStatus } from './PowerlineFontStatus';
|
||||
export type { ClaudeSettings } from './ClaudeSettings';
|
||||
export type { ColorEntry } from './ColorEntry';
|
||||
export type { ColorEntry } from './ColorEntry';
|
||||
export type { BlockMetrics } from './BlockMetrics';
|
||||
+76
-57
@@ -4,6 +4,7 @@ import { globSync } from 'tinyglobby';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import type {
|
||||
BlockMetrics,
|
||||
TokenMetrics,
|
||||
TranscriptLine
|
||||
} from '../types';
|
||||
@@ -13,12 +14,6 @@ const readFile = promisify(fs.readFile);
|
||||
const readFileSync = fs.readFileSync;
|
||||
const statSync = fs.statSync;
|
||||
|
||||
export interface BlockInfo {
|
||||
startTime: Date;
|
||||
lastActivity: Date;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export async function getSessionDuration(transcriptPath: string): Promise<string | null> {
|
||||
try {
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
@@ -151,7 +146,7 @@ export async function getTokenMetrics(transcriptPath: string): Promise<TokenMetr
|
||||
/**
|
||||
* Gets block metrics for the current 5-hour block from JSONL files
|
||||
*/
|
||||
export function getBlockMetrics(transcriptPath: string | undefined): BlockInfo | null {
|
||||
export function getBlockMetrics(transcriptPath: string | undefined): BlockMetrics | null {
|
||||
if (!transcriptPath || typeof transcriptPath !== 'string') {
|
||||
return null;
|
||||
}
|
||||
@@ -169,9 +164,8 @@ export function getBlockMetrics(transcriptPath: string | undefined): BlockInfo |
|
||||
currentPath = path.dirname(currentPath);
|
||||
}
|
||||
|
||||
if (!claudePath) {
|
||||
if (!claudePath)
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return findMostRecentBlockStartTime(claudePath);
|
||||
@@ -187,7 +181,7 @@ export function getBlockMetrics(transcriptPath: string | undefined): BlockInfo |
|
||||
function findMostRecentBlockStartTime(
|
||||
rootDir: string,
|
||||
sessionDurationHours = 5
|
||||
): BlockInfo | null {
|
||||
): BlockMetrics | null {
|
||||
const sessionDurationMs = sessionDurationHours * 60 * 60 * 1000;
|
||||
const now = new Date();
|
||||
|
||||
@@ -195,9 +189,8 @@ function findMostRecentBlockStartTime(
|
||||
const pattern = path.join(rootDir, 'projects', '**', '*.jsonl').replace(/\\/g, '/');
|
||||
const files = globSync([pattern]);
|
||||
|
||||
if (files.length === 0) {
|
||||
if (files.length === 0)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Step 2: Get file stats and sort by modification time (most recent first)
|
||||
const filesWithStats = files.map((file) => {
|
||||
@@ -207,58 +200,84 @@ function findMostRecentBlockStartTime(
|
||||
|
||||
filesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
||||
|
||||
// Step 3: Collect recent timestamps to properly identify blocks
|
||||
// We need to look back far enough to find the start of the current/recent block
|
||||
const timestamps: Date[] = [];
|
||||
// Look back 20 hours to find the current session
|
||||
const cutoffTime = new Date(now.getTime() - 20 * 60 * 60 * 1000);
|
||||
// Step 3: Progressive lookback - start small and expand if needed
|
||||
// Start with 2x session duration (10 hours), expand to 48 hours if needed
|
||||
const lookbackChunks = [
|
||||
10, // 2x session duration - catches most cases
|
||||
20, // 4x session duration - catches longer sessions
|
||||
48 // Maximum lookback for marathon sessions
|
||||
];
|
||||
|
||||
for (const { file, mtime } of filesWithStats) {
|
||||
// Skip files that are too old (optimization)
|
||||
if (mtime.getTime() < cutoffTime.getTime()) {
|
||||
let timestamps: Date[] = [];
|
||||
let mostRecentTimestamp: Date | null = null;
|
||||
let continuousWorkStart: Date | null = null;
|
||||
let foundSessionGap = false;
|
||||
|
||||
for (const lookbackHours of lookbackChunks) {
|
||||
const cutoffTime = new Date(now.getTime() - lookbackHours * 60 * 60 * 1000);
|
||||
timestamps = [];
|
||||
|
||||
// Collect timestamps for this lookback period
|
||||
for (const { file, mtime } of filesWithStats) {
|
||||
if (mtime.getTime() < cutoffTime.getTime()) {
|
||||
break;
|
||||
}
|
||||
const fileTimestamps = getAllTimestampsFromFile(file);
|
||||
timestamps.push(...fileTimestamps);
|
||||
}
|
||||
|
||||
if (timestamps.length === 0) {
|
||||
continue; // Try next chunk
|
||||
}
|
||||
|
||||
// Sort timestamps (most recent first)
|
||||
timestamps.sort((a, b) => b.getTime() - a.getTime());
|
||||
|
||||
// Get most recent timestamp (only set once)
|
||||
if (!mostRecentTimestamp && timestamps[0]) {
|
||||
mostRecentTimestamp = timestamps[0];
|
||||
|
||||
// Check if the most recent activity is within the current session period
|
||||
const timeSinceLastActivity = now.getTime() - mostRecentTimestamp.getTime();
|
||||
if (timeSinceLastActivity > sessionDurationMs) {
|
||||
// No activity within the current session period
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a session gap in this chunk
|
||||
continuousWorkStart = mostRecentTimestamp;
|
||||
for (let i = 1; i < timestamps.length; i++) {
|
||||
const currentTimestamp = timestamps[i];
|
||||
const previousTimestamp = timestamps[i - 1];
|
||||
|
||||
if (!currentTimestamp || !previousTimestamp)
|
||||
continue;
|
||||
|
||||
const gap = previousTimestamp.getTime() - currentTimestamp.getTime();
|
||||
|
||||
if (gap >= sessionDurationMs) {
|
||||
// Found a true session boundary
|
||||
foundSessionGap = true;
|
||||
break;
|
||||
}
|
||||
|
||||
continuousWorkStart = currentTimestamp;
|
||||
}
|
||||
|
||||
// If we found a gap, we're done
|
||||
if (foundSessionGap) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Get all timestamps from this file (not just the latest)
|
||||
const fileTimestamps = getAllTimestampsFromFile(file);
|
||||
timestamps.push(...fileTimestamps);
|
||||
}
|
||||
|
||||
if (timestamps.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Step 4: Sort timestamps and identify the most recent block
|
||||
timestamps.sort((a, b) => b.getTime() - a.getTime()); // Most recent first
|
||||
|
||||
// Find the most recent timestamp
|
||||
const mostRecentTimestamp = timestamps[0];
|
||||
if (!mostRecentTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now work backwards to find when continuous work started
|
||||
let continuousWorkStart: Date = mostRecentTimestamp;
|
||||
|
||||
for (let i = 1; i < timestamps.length; i++) {
|
||||
const currentTimestamp = timestamps[i];
|
||||
const previousTimestamp = timestamps[i - 1];
|
||||
|
||||
if (!currentTimestamp || !previousTimestamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if there's a gap larger than 1 hour (consider it a break)
|
||||
const gap = previousTimestamp.getTime() - currentTimestamp.getTime();
|
||||
const oneHourMs = 60 * 60 * 1000;
|
||||
|
||||
if (gap > oneHourMs) {
|
||||
// We found a gap - continuous work starts with previousTimestamp
|
||||
// If this was our last chunk, use what we have
|
||||
if (lookbackHours === lookbackChunks[lookbackChunks.length - 1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This timestamp is still part of continuous work
|
||||
continuousWorkStart = currentTimestamp;
|
||||
if (!mostRecentTimestamp || !continuousWorkStart) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Floor the continuous work start to the hour
|
||||
|
||||
@@ -68,15 +68,22 @@ export class BlockTimerWidget implements Widget {
|
||||
}
|
||||
|
||||
// Check if we have block metrics in context
|
||||
const blockInfo = context.blockMetrics;
|
||||
if (!blockInfo) {
|
||||
return null;
|
||||
const blockMetrics = context.blockMetrics;
|
||||
if (!blockMetrics) {
|
||||
// No active session - show empty progress bar or 0hr 0m
|
||||
if (displayMode === 'progress' || displayMode === 'progress-short') {
|
||||
const barWidth = displayMode === 'progress' ? 32 : 16;
|
||||
const emptyBar = '░'.repeat(barWidth);
|
||||
return item.rawValue ? `[${emptyBar}] 0%` : `Block [${emptyBar}] 0%`;
|
||||
} else {
|
||||
return item.rawValue ? '0hr 0m' : 'Block: 0hr 0m';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Calculate elapsed time and progress
|
||||
const now = new Date();
|
||||
const elapsedMs = now.getTime() - blockInfo.startTime.getTime();
|
||||
const elapsedMs = now.getTime() - blockMetrics.startTime.getTime();
|
||||
const sessionDurationMs = 5 * 60 * 60 * 1000; // 5 hours
|
||||
const progress = Math.min(elapsedMs / sessionDurationMs, 1.0);
|
||||
const percentage = (progress * 100).toFixed(1);
|
||||
|
||||
Reference in new issue
Block a user