export const MODEL_INFO_CHANNEL = "dashboard:model-info"; export const GIT_INFO_CHANNEL = "dashboard:git-info"; export const REFRESH_CHANNEL = "dashboard:refresh"; export interface ModelInfoState { provider: string; modelId: string; modelName: string; thinking: string; contextTokens: number | null; contextWindow: number; contextPercent: number | null; cost: number; latestCacheHitRate: number | null; tokensPerSecond: number | null; generating: boolean; } export interface PullRequestInfo { number: number; url: string; isDraft: boolean; } export interface GitInfoState { isRepository: boolean; branch: string | null; changedFiles: number; pullRequest: PullRequestInfo | null; worktreeName: string | null; hasConflicts: boolean; ahead: number | null; behind: number | null; insertions: number; deletions: number; lastCommitTs: number | null; } export function emptyModelInfoState(): ModelInfoState { return { provider: "", modelId: "no-model", modelName: "No model", thinking: "off", contextTokens: null, contextWindow: 0, contextPercent: null, cost: 0, latestCacheHitRate: null, tokensPerSecond: null, generating: false, }; } export function emptyGitInfoState(): GitInfoState { return { isRepository: false, branch: null, changedFiles: 0, pullRequest: null, worktreeName: null, hasConflicts: false, ahead: null, behind: null, insertions: 0, deletions: 0, lastCommitTs: null, }; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isNullableNumber(value: unknown) { return value === null || typeof value === "number"; } export function isModelInfoState(value: unknown): value is ModelInfoState { if (!isRecord(value)) return false; return ( typeof value.provider === "string" && typeof value.modelId === "string" && typeof value.modelName === "string" && typeof value.thinking === "string" && isNullableNumber(value.contextTokens) && typeof value.contextWindow === "number" && isNullableNumber(value.contextPercent) && typeof value.cost === "number" && isNullableNumber(value.latestCacheHitRate) && isNullableNumber(value.tokensPerSecond) && typeof value.generating === "boolean" ); } function isPullRequestInfo(value: unknown): value is PullRequestInfo { if (!isRecord(value)) return false; return ( typeof value.number === "number" && typeof value.url === "string" && typeof value.isDraft === "boolean" ); } export function isGitInfoState(value: unknown): value is GitInfoState { if (!isRecord(value)) return false; return ( typeof value.isRepository === "boolean" && (value.branch === null || typeof value.branch === "string") && typeof value.changedFiles === "number" && (value.pullRequest === null || isPullRequestInfo(value.pullRequest)) && (value.worktreeName === null || typeof value.worktreeName === "string") && typeof value.hasConflicts === "boolean" && isNullableNumber(value.ahead) && isNullableNumber(value.behind) && typeof value.insertions === "number" && typeof value.deletions === "number" && isNullableNumber(value.lastCommitTs) ); }