feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
- Sidebar: dynamic brand-accent colors, brainstorm section restyled - AI chat general: popup panel with expand/collapse, hides when contextual AI open - AI chat contextual: tabs reordered (Actions first), X close button, height fix - Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.) - Global color cleanup: emerald/orange hardcoded → brand-accent dynamic - Brainstorm page: orange → brand-accent throughout - PageEntry animation component added to key pages - Floating AI button: bg-brand-accent instead of hardcoded black - i18n: all 15 locales updated with new AI/billing keys - Billing: freemium quota tracking, BYOK, stripe subscription scaffolding - Admin: integrated into new design - AGENTS.md + CLAUDE.md project rules added
This commit is contained in:
76
architectural-grid_landing/src/services/temporalService.ts
Normal file
76
architectural-grid_landing/src/services/temporalService.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Note, NoteAccessLog, NotePrediction } from '../types';
|
||||
|
||||
/**
|
||||
* Simulates finding the dominant frequency in access logs for a specific note
|
||||
* returning the period in days.
|
||||
*/
|
||||
export function detectAccessCycle(logs: NoteAccessLog[]): number | null {
|
||||
if (logs.length < 5) return null;
|
||||
|
||||
const accessDays = logs
|
||||
.map(log => new Date(log.accessedAt).getTime())
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const intervals: number[] = [];
|
||||
for (let i = 1; i < accessDays.length; i++) {
|
||||
intervals.push((accessDays[i] - accessDays[i - 1]) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
// Simple heuristic: if intervals are consistently around a value, that's our cycle
|
||||
// We'll calculate the median interval
|
||||
const sortedIntervals = [...intervals].sort((a, b) => a - b);
|
||||
const median = sortedIntervals[Math.floor(sortedIntervals.length / 2)];
|
||||
|
||||
// Check if enough intervals are close to median
|
||||
const withinThreshold = intervals.filter(v => Math.abs(v - median) < Math.max(2, median * 0.2));
|
||||
|
||||
if (withinThreshold.length >= intervals.length * 0.6) {
|
||||
return median;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function predictNextAccess(note: Note, logs: NoteAccessLog[]): NotePrediction | null {
|
||||
const cycleDays = detectAccessCycle(logs);
|
||||
if (!cycleDays) return null;
|
||||
|
||||
const lastAccess = new Date(logs[logs.length - 1].accessedAt);
|
||||
const nextAccessDate = new Date(lastAccess.getTime() + cycleDays * 24 * 60 * 60 * 1000);
|
||||
|
||||
const now = new Date();
|
||||
const daysUntilNext = (nextAccessDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
|
||||
|
||||
// Only predict if it's coming up in the next 2 weeks
|
||||
if (daysUntilNext > 0 && daysUntilNext < 14) {
|
||||
return {
|
||||
noteId: note.id,
|
||||
predictedRelevanceDate: nextAccessDate.toISOString(),
|
||||
confidence: 0.7,
|
||||
reason: `Historical access pattern suggests a ${Math.round(cycleDays)}-day cycle.`,
|
||||
generatedAt: now.toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCoaccessedNotes(baseNoteId: string, logs: NoteAccessLog[], allNotes: Note[]): Note[] {
|
||||
const WINDOW_MS = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
const baseNoteLogs = logs.filter(l => l.noteId === baseNoteId);
|
||||
const coaccessedIds = new Set<string>();
|
||||
|
||||
baseNoteLogs.forEach(baseLog => {
|
||||
const baseTime = new Date(baseLog.accessedAt).getTime();
|
||||
logs.forEach(otherLog => {
|
||||
if (otherLog.noteId === baseNoteId) return;
|
||||
const otherTime = new Date(otherLog.accessedAt).getTime();
|
||||
if (Math.abs(baseTime - otherTime) < WINDOW_MS) {
|
||||
coaccessedIds.add(otherLog.noteId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return allNotes.filter(n => coaccessedIds.has(n.id));
|
||||
}
|
||||
Reference in New Issue
Block a user