heyiam 0.2.28 → 0.3.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/README.md +45 -0
- package/dist/config.js +10 -1
- package/dist/db.js +1 -2
- package/dist/export.js +40 -25
- package/dist/format-utils.js +5 -0
- package/dist/index.js +168 -0
- package/dist/mount.js +300 -102
- package/dist/parsers/claude.js +2 -28
- package/dist/parsers/codex.js +2 -26
- package/dist/parsers/cursor.js +2 -26
- package/dist/parsers/duration.js +35 -0
- package/dist/parsers/gemini.js +2 -20
- package/dist/parsers/types.js +0 -1
- package/dist/public/assets/index-BZ65TU_Y.js +40 -0
- package/dist/public/assets/index-CqCaW2cb.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/redact.js +4 -104
- package/dist/render/build-render-data.js +9 -2
- package/dist/render/index.js +32 -5
- package/dist/render/liquid.js +147 -7
- package/dist/render/mock-data.js +303 -0
- package/dist/render/templates/aurora/portfolio.liquid +204 -0
- package/dist/render/templates/aurora/project.liquid +260 -0
- package/dist/render/templates/aurora/session.liquid +223 -0
- package/dist/render/templates/aurora/styles.css +1178 -0
- package/dist/render/templates/bauhaus/portfolio.liquid +179 -0
- package/dist/render/templates/bauhaus/project.liquid +300 -0
- package/dist/render/templates/bauhaus/session.liquid +333 -0
- package/dist/render/templates/bauhaus/styles.css +1641 -0
- package/dist/render/templates/blueprint/portfolio.liquid +167 -0
- package/dist/render/templates/blueprint/project.liquid +286 -0
- package/dist/render/templates/blueprint/session.liquid +248 -0
- package/dist/render/templates/blueprint/styles.css +1285 -0
- package/dist/render/templates/canvas/portfolio.liquid +215 -0
- package/dist/render/templates/canvas/project.liquid +235 -0
- package/dist/render/templates/canvas/session.liquid +223 -0
- package/dist/render/templates/canvas/styles.css +1436 -0
- package/dist/render/templates/carbon/portfolio.liquid +170 -0
- package/dist/render/templates/carbon/project.liquid +249 -0
- package/dist/render/templates/carbon/session.liquid +190 -0
- package/dist/render/templates/carbon/styles.css +1091 -0
- package/dist/render/templates/chalk/portfolio.liquid +199 -0
- package/dist/render/templates/chalk/project.liquid +245 -0
- package/dist/render/templates/chalk/session.liquid +215 -0
- package/dist/render/templates/chalk/styles.css +1157 -0
- package/dist/render/templates/circuit/portfolio.liquid +162 -0
- package/dist/render/templates/circuit/project.liquid +247 -0
- package/dist/render/templates/circuit/session.liquid +205 -0
- package/dist/render/templates/circuit/styles.css +1403 -0
- package/dist/render/templates/cosmos/portfolio.liquid +232 -0
- package/dist/render/templates/cosmos/project.liquid +327 -0
- package/dist/render/templates/cosmos/session.liquid +239 -0
- package/dist/render/templates/cosmos/styles.css +1151 -0
- package/dist/render/templates/daylight/portfolio.liquid +217 -0
- package/dist/render/templates/daylight/project.liquid +229 -0
- package/dist/render/templates/daylight/session.liquid +219 -0
- package/dist/render/templates/daylight/styles.css +1311 -0
- package/dist/render/templates/editorial/portfolio.liquid +126 -0
- package/dist/render/templates/editorial/project.liquid +202 -0
- package/dist/render/templates/editorial/session.liquid +171 -0
- package/dist/render/templates/editorial/styles.css +822 -0
- package/dist/render/templates/ember/portfolio.liquid +318 -0
- package/dist/render/templates/ember/project.liquid +232 -0
- package/dist/render/templates/ember/session.liquid +202 -0
- package/dist/render/templates/ember/styles.css +1283 -0
- package/dist/render/templates/glacier/portfolio.liquid +271 -0
- package/dist/render/templates/glacier/project.liquid +288 -0
- package/dist/render/templates/glacier/session.liquid +217 -0
- package/dist/render/templates/glacier/styles.css +1200 -0
- package/dist/render/templates/grid/portfolio.liquid +265 -0
- package/dist/render/templates/grid/project.liquid +306 -0
- package/dist/render/templates/grid/session.liquid +260 -0
- package/dist/render/templates/grid/styles.css +1441 -0
- package/dist/render/templates/kinetic/portfolio.liquid +170 -0
- package/dist/render/templates/kinetic/project.liquid +242 -0
- package/dist/render/templates/kinetic/session.liquid +228 -0
- package/dist/render/templates/kinetic/styles.css +944 -0
- package/dist/render/templates/meridian/portfolio.liquid +255 -0
- package/dist/render/templates/meridian/project.liquid +376 -0
- package/dist/render/templates/meridian/session.liquid +298 -0
- package/dist/render/templates/meridian/styles.css +1369 -0
- package/dist/render/templates/minimal/portfolio.liquid +71 -0
- package/dist/render/templates/minimal/project.liquid +154 -0
- package/dist/render/templates/minimal/session.liquid +140 -0
- package/dist/render/templates/minimal/styles.css +525 -0
- package/dist/render/templates/mono/portfolio.liquid +291 -0
- package/dist/render/templates/mono/project.liquid +275 -0
- package/dist/render/templates/mono/session.liquid +276 -0
- package/dist/render/templates/mono/styles.css +1016 -0
- package/dist/render/templates/neon/portfolio.liquid +217 -0
- package/dist/render/templates/neon/project.liquid +225 -0
- package/dist/render/templates/neon/session.liquid +195 -0
- package/dist/render/templates/neon/styles.css +1265 -0
- package/dist/render/templates/noir/portfolio.liquid +137 -0
- package/dist/render/templates/noir/project.liquid +220 -0
- package/dist/render/templates/noir/session.liquid +241 -0
- package/dist/render/templates/noir/styles.css +1223 -0
- package/dist/render/templates/obsidian/portfolio.liquid +257 -0
- package/dist/render/templates/obsidian/project.liquid +280 -0
- package/dist/render/templates/obsidian/session.liquid +241 -0
- package/dist/render/templates/obsidian/styles.css +1401 -0
- package/dist/render/templates/paper/portfolio.liquid +267 -0
- package/dist/render/templates/paper/project.liquid +235 -0
- package/dist/render/templates/paper/session.liquid +271 -0
- package/dist/render/templates/paper/styles.css +1509 -0
- package/dist/render/templates/parallax/portfolio.liquid +305 -0
- package/dist/render/templates/parallax/project.liquid +275 -0
- package/dist/render/templates/parallax/session.liquid +295 -0
- package/dist/render/templates/parallax/styles.css +1874 -0
- package/dist/render/templates/parchment/portfolio.liquid +290 -0
- package/dist/render/templates/parchment/project.liquid +289 -0
- package/dist/render/templates/parchment/session.liquid +346 -0
- package/dist/render/templates/parchment/styles.css +1397 -0
- package/dist/render/templates/partials/_beats.liquid +16 -0
- package/dist/render/templates/partials/_breadcrumb.liquid +9 -0
- package/dist/render/templates/partials/_footer.liquid +7 -0
- package/dist/render/templates/partials/_growth-chart.liquid +7 -0
- package/dist/render/templates/partials/_key-decisions.liquid +20 -0
- package/dist/render/templates/partials/_links.liquid +16 -0
- package/dist/render/templates/partials/_narrative.liquid +8 -0
- package/dist/render/templates/partials/_phases.liquid +20 -0
- package/dist/render/templates/partials/_portfolio-header.liquid +20 -0
- package/dist/render/templates/partials/_portfolio-projects.liquid +16 -0
- package/dist/render/templates/partials/_portfolio-stats.liquid +19 -0
- package/dist/render/templates/partials/_qa.liquid +13 -0
- package/dist/render/templates/partials/_screenshot.liquid +15 -0
- package/dist/render/templates/partials/_session-cards.liquid +30 -0
- package/dist/render/templates/partials/_session-header.liquid +39 -0
- package/dist/render/templates/partials/_session-sidebar.liquid +30 -0
- package/dist/render/templates/partials/_skills.liquid +12 -0
- package/dist/render/templates/partials/_source-breakdown.liquid +22 -0
- package/dist/render/templates/partials/_stats.liquid +38 -0
- package/dist/render/templates/partials/_work-timeline.liquid +7 -0
- package/dist/render/templates/project.liquid +7 -4
- package/dist/render/templates/radar/portfolio.liquid +233 -0
- package/dist/render/templates/radar/project.liquid +278 -0
- package/dist/render/templates/radar/session.liquid +300 -0
- package/dist/render/templates/radar/styles.css +1049 -0
- package/dist/render/templates/showcase/portfolio.liquid +231 -0
- package/dist/render/templates/showcase/project.liquid +237 -0
- package/dist/render/templates/showcase/session.liquid +210 -0
- package/dist/render/templates/showcase/styles.css +1279 -0
- package/dist/render/templates/signal/portfolio.liquid +227 -0
- package/dist/render/templates/signal/project.liquid +278 -0
- package/dist/render/templates/signal/session.liquid +282 -0
- package/dist/render/templates/signal/styles.css +1395 -0
- package/dist/render/templates/strata/portfolio.liquid +192 -0
- package/dist/render/templates/strata/project.liquid +282 -0
- package/dist/render/templates/strata/session.liquid +261 -0
- package/dist/render/templates/strata/styles.css +1350 -0
- package/dist/render/templates/styles.css +1190 -0
- package/dist/render/templates/terminal/portfolio.liquid +118 -0
- package/dist/render/templates/terminal/project.liquid +161 -0
- package/dist/render/templates/terminal/session.liquid +145 -0
- package/dist/render/templates/terminal/styles.css +492 -0
- package/dist/render/templates/verdant/portfolio.liquid +333 -0
- package/dist/render/templates/verdant/project.liquid +309 -0
- package/dist/render/templates/verdant/session.liquid +237 -0
- package/dist/render/templates/verdant/styles.css +1257 -0
- package/dist/render/templates/zen/portfolio.liquid +136 -0
- package/dist/render/templates/zen/project.liquid +187 -0
- package/dist/render/templates/zen/session.liquid +203 -0
- package/dist/render/templates/zen/styles.css +1207 -0
- package/dist/render/templates.js +90 -0
- package/dist/routes/context.js +15 -10
- package/dist/routes/enhance.js +17 -40
- package/dist/routes/export.js +14 -4
- package/dist/routes/preview.js +480 -108
- package/dist/routes/projects.js +11 -19
- package/dist/routes/publish.js +15 -17
- package/dist/routes/settings.js +94 -1
- package/dist/routes/sse.js +9 -0
- package/dist/server.js +8 -2
- package/dist/settings.js +17 -9
- package/package.json +2 -4
- package/dist/public/assets/index-B_d6DlEI.js +0 -21
- package/dist/public/assets/index-Dalqz2mC.css +0 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template registry for heyi.am project/session rendering.
|
|
3
|
+
*
|
|
4
|
+
* Built-in templates ship with the CLI. Custom user templates
|
|
5
|
+
* can live in ~/.config/heyiam/templates/{name}/ (Phase 2).
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { resolve, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const TEMPLATES_DIR = resolve(__dirname, 'templates');
|
|
12
|
+
export const BUILT_IN_TEMPLATES = [
|
|
13
|
+
// Original 5
|
|
14
|
+
{ name: 'editorial', label: 'Editorial', description: 'Classic light theme with card-based layout', accent: '#084471', mode: 'light', tags: [] },
|
|
15
|
+
{ name: 'kinetic', label: 'Kinetic', description: 'Matches the heyi.am landing page — orange accent, section tags, narrative cards', accent: '#f97316', mode: 'dark', tags: ['animated'] },
|
|
16
|
+
{ name: 'terminal', label: 'Terminal', description: 'Green-on-black terminal aesthetic with ASCII elements', accent: '#4ade80', mode: 'dark', tags: ['minimal'] },
|
|
17
|
+
{ name: 'minimal', label: 'Typography', description: 'Ultra-clean light mode with serif typography', accent: '#1c1917', mode: 'light', tags: ['minimal'] },
|
|
18
|
+
{ name: 'showcase', label: 'Showcase', description: 'Cinematic scroll animations with animated charts and stat counters', accent: '#818cf8', mode: 'dark', tags: ['animated'] },
|
|
19
|
+
// New templates
|
|
20
|
+
{ name: 'parallax', label: 'Parallax', description: 'Fixed floating headshot with full-page parallax — content scrolls around the photo', accent: '#60a5fa', mode: 'dark', tags: ['animated'] },
|
|
21
|
+
{ name: 'blueprint', label: 'Blueprint', description: 'Engineering schematic with SVG connector lines, grid background, and dimension annotations', accent: '#64748b', mode: 'light', tags: ['animated'] },
|
|
22
|
+
{ name: 'radar', label: 'Radar', description: 'HUD cockpit with radar navigation widget and cyan-tinted luminous elements', accent: '#22d3ee', mode: 'dark', tags: ['animated', 'data-dense'] },
|
|
23
|
+
{ name: 'strata', label: 'Strata', description: 'Depth-based parallax with overlapping card layers and warm amber palette', accent: '#d97706', mode: 'light', tags: ['animated'] },
|
|
24
|
+
{ name: 'noir', label: 'Noir', description: 'Pure monochrome — black, white, gray only with bold typography and film noir drama', accent: '#e5e5e5', mode: 'dark', tags: ['minimal'] },
|
|
25
|
+
{ name: 'verdant', label: 'Verdant', description: 'Nature-inspired with warm earthy palette, leaf motifs, and organic rounded shapes', accent: '#15803d', mode: 'light', tags: [] },
|
|
26
|
+
{ name: 'neon', label: 'Neon', description: 'Synthwave aesthetic with pink and cyan dual accent and tasteful neon glow effects', accent: '#f472b6', mode: 'dark', tags: ['animated'] },
|
|
27
|
+
{ name: 'paper', label: 'Paper', description: 'Newspaper print aesthetic with multi-column layout, drop caps, and serif typography', accent: '#1a1a1a', mode: 'light', tags: ['minimal'] },
|
|
28
|
+
{ name: 'cosmos', label: 'Cosmos', description: 'Starfield background with gold accent and constellation SVG lines connecting elements', accent: '#fbbf24', mode: 'dark', tags: ['animated'] },
|
|
29
|
+
{ name: 'bauhaus', label: 'Bauhaus', description: 'De Stijl geometric shapes in red, blue, yellow with thick borders and asymmetric grids', accent: '#dc2626', mode: 'light', tags: ['animated'] },
|
|
30
|
+
{ name: 'mono', label: 'Mono', description: '100% monospace terminal — green on black, ASCII bar charts, git-log phases, typing animation', accent: '#4ade80', mode: 'dark', tags: ['minimal', 'data-dense'] },
|
|
31
|
+
{ name: 'glacier', label: 'Glacier', description: 'Frosted glassmorphism with backdrop blur cards, cool blue palette, and soft shadows', accent: '#38bdf8', mode: 'light', tags: ['animated'] },
|
|
32
|
+
{ name: 'ember', label: 'Ember', description: 'Warm dark theme with orange-to-red fire gradient accents and ember glow on stats', accent: '#f97316', mode: 'dark', tags: ['animated'] },
|
|
33
|
+
{ name: 'zen', label: 'Zen', description: 'Japanese minimalism — maximum whitespace, no cards, thin rules, serif display, 640px column', accent: '#78716c', mode: 'light', tags: ['minimal'] },
|
|
34
|
+
{ name: 'circuit', label: 'Circuit', description: 'PCB aesthetic with circuit trace patterns, component pads, and lime green accent', accent: '#a3e635', mode: 'dark', tags: ['animated'] },
|
|
35
|
+
{ name: 'parchment', label: 'Parchment', description: 'Old book aesthetic with all-serif typography, sepia palette, drop caps, and colophon footer', accent: '#92400e', mode: 'light', tags: ['minimal'] },
|
|
36
|
+
{ name: 'aurora', label: 'Aurora', description: 'Northern lights gradient header that slowly shifts — restrained dark with teal magic', accent: '#2dd4bf', mode: 'dark', tags: ['animated'] },
|
|
37
|
+
{ name: 'grid', label: 'Grid', description: 'Bento dashboard layout with mixed-size CSS grid cells like iOS widgets', accent: '#6366f1', mode: 'light', tags: ['data-dense'] },
|
|
38
|
+
{ name: 'obsidian', label: 'Obsidian', description: 'Deep black with purple gem accent and hover shimmer like light catching a gemstone', accent: '#a855f7', mode: 'dark', tags: ['animated'] },
|
|
39
|
+
{ name: 'chalk', label: 'Chalk', description: 'Whiteboard aesthetic with handwritten display font, sketch-style borders, and annotation arrows', accent: '#334155', mode: 'light', tags: [] },
|
|
40
|
+
{ name: 'signal', label: 'Signal', description: 'Mission control dashboard — dense data tables, status badges, and fast-updating metrics', accent: '#ef4444', mode: 'dark', tags: ['data-dense'] },
|
|
41
|
+
{ name: 'canvas', label: 'Canvas', description: 'Art gallery with extreme whitespace, full-bleed images, and large airy typography', accent: '#fb7185', mode: 'light', tags: ['minimal'] },
|
|
42
|
+
{ name: 'meridian', label: 'Meridian', description: 'Topographic map aesthetic with contour line patterns and elevation-style charts', accent: '#34d399', mode: 'dark', tags: ['animated'] },
|
|
43
|
+
{ name: 'carbon', label: 'Carbon', description: 'Brushed metal industrial — diagonal stripe texture, silver chrome palette, no color', accent: '#94a3b8', mode: 'dark', tags: ['minimal'] },
|
|
44
|
+
{ name: 'daylight', label: 'Daylight', description: 'Bright and airy with soft blue shadows, sky blue accent, and friendly rounded shapes', accent: '#0ea5e9', mode: 'light', tags: ['animated'] },
|
|
45
|
+
];
|
|
46
|
+
const BUILT_IN_NAMES = new Set(BUILT_IN_TEMPLATES.map((t) => t.name));
|
|
47
|
+
export const DEFAULT_TEMPLATE = 'editorial';
|
|
48
|
+
export function isValidTemplate(name) {
|
|
49
|
+
return BUILT_IN_NAMES.has(name);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve which template to use.
|
|
53
|
+
* Priority: project override → user default → 'editorial'
|
|
54
|
+
*/
|
|
55
|
+
export function resolveTemplate(projectTemplate, userDefault) {
|
|
56
|
+
if (projectTemplate && isValidTemplate(projectTemplate))
|
|
57
|
+
return projectTemplate;
|
|
58
|
+
if (userDefault && isValidTemplate(userDefault))
|
|
59
|
+
return userDefault;
|
|
60
|
+
return DEFAULT_TEMPLATE;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load concatenated CSS for a template (base + template-specific).
|
|
64
|
+
* Used by export.ts for standalone HTML and by preview.
|
|
65
|
+
*/
|
|
66
|
+
const cssCache = new Map();
|
|
67
|
+
export function getTemplateCss(templateName) {
|
|
68
|
+
const name = isValidTemplate(templateName) ? templateName : DEFAULT_TEMPLATE;
|
|
69
|
+
const cached = cssCache.get(name);
|
|
70
|
+
if (cached !== undefined)
|
|
71
|
+
return cached;
|
|
72
|
+
let css = '';
|
|
73
|
+
try {
|
|
74
|
+
css = readFileSync(resolve(TEMPLATES_DIR, 'styles.css'), 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
catch { /* empty */ }
|
|
77
|
+
try {
|
|
78
|
+
const templateCss = readFileSync(resolve(TEMPLATES_DIR, name, 'styles.css'), 'utf-8');
|
|
79
|
+
css += '\n\n/* === ' + name + ' template styles === */\n' + templateCss;
|
|
80
|
+
}
|
|
81
|
+
catch { /* no template-specific CSS — fine */ }
|
|
82
|
+
cssCache.set(name, css);
|
|
83
|
+
return css;
|
|
84
|
+
}
|
|
85
|
+
export function getTemplateNames() {
|
|
86
|
+
return BUILT_IN_TEMPLATES.map((t) => t.name);
|
|
87
|
+
}
|
|
88
|
+
export function getTemplateInfo(name) {
|
|
89
|
+
return BUILT_IN_TEMPLATES.find((t) => t.name === name);
|
|
90
|
+
}
|
package/dist/routes/context.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* Created during the server.ts refactor to avoid circular dependencies.
|
|
4
4
|
*/
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { readFileSync } from 'node:fs';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
8
7
|
import { listSessions, parseSession } from '../parsers/index.js';
|
|
9
8
|
import { bridgeToAnalyzer, mergeActiveIntervals, sumIntervalMs } from '../bridge.js';
|
|
10
9
|
import { analyzeSession } from '../analyzer.js';
|
|
11
10
|
import { loadEnhancedData, loadProjectEnhanceResult, getUploadedState, } from '../settings.js';
|
|
11
|
+
import { getTemplateCss } from '../render/templates.js';
|
|
12
12
|
import { archiveSessionFiles } from '../archive.js';
|
|
13
13
|
import { getDatabase, openDatabase, getSessionStats as dbGetSessionStats, getSessionCount, getAllSessionMetas, getAllProjectStats, getSessionsByProject, getProjectUuid, } from '../db.js';
|
|
14
14
|
import { ensureSessionIndexed, displayNameFromDir } from '../sync.js';
|
|
@@ -39,7 +39,16 @@ function computeMergedDurationFromDb(db, projectDir, naiveSumMinutes) {
|
|
|
39
39
|
return mergedMinutes > 0 ? mergedMinutes : naiveSumMinutes;
|
|
40
40
|
}
|
|
41
41
|
import { escapeHtml } from '../format-utils.js';
|
|
42
|
-
|
|
42
|
+
/** Look up a project by name or dirName, sending 404 if not found. Returns null on miss. */
|
|
43
|
+
export async function requireProject(ctx, projectParam, res) {
|
|
44
|
+
const projects = await ctx.getProjects();
|
|
45
|
+
const proj = projects.find((p) => p.name === projectParam || p.dirName === projectParam);
|
|
46
|
+
if (!proj) {
|
|
47
|
+
res.status(404).json({ error: { code: 'PROJECT_NOT_FOUND', message: 'Project not found' } });
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return proj;
|
|
51
|
+
}
|
|
43
52
|
/**
|
|
44
53
|
* Build an agent summary from child session metas. Returns null when
|
|
45
54
|
* there are no children (or none produce valid stats).
|
|
@@ -466,13 +475,9 @@ export function createRouteContext(sessionsBasePath, dbPath) {
|
|
|
466
475
|
};
|
|
467
476
|
}
|
|
468
477
|
// ── buildPreviewPage ─────────────────────────────────────
|
|
469
|
-
function buildPreviewPage(title, bodyHtml, banner) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
try {
|
|
473
|
-
inlineCss = readFileSync(renderCssPath, 'utf-8');
|
|
474
|
-
}
|
|
475
|
-
catch { /* */ }
|
|
478
|
+
function buildPreviewPage(title, bodyHtml, banner, templateName) {
|
|
479
|
+
// Load full template CSS (base + template-specific) via the same path as the React embed
|
|
480
|
+
const inlineCss = getTemplateCss(templateName || 'editorial');
|
|
476
481
|
const cssTag = `<style>${inlineCss}\n/* Preview override */\nbody { overflow: auto !important; min-height: auto !important; }\n#root { min-height: auto !important; }</style>`;
|
|
477
482
|
const bannerHtml = banner
|
|
478
483
|
? `<div style="background: var(--primary, #084471); color: white; text-align: center; padding: 0.5rem; font-family: 'Inter', sans-serif; font-size: 0.75rem; letter-spacing: 0.05em;">${escapeHtml(banner)}</div>`
|
|
@@ -486,7 +491,7 @@ export function createRouteContext(sessionsBasePath, dbPath) {
|
|
|
486
491
|
<title>${escapeHtml(title)} — Preview</title>
|
|
487
492
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
488
493
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
489
|
-
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
494
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Newsreader:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet" />
|
|
490
495
|
${cssTag}
|
|
491
496
|
</head>
|
|
492
497
|
<body>
|
package/dist/routes/enhance.js
CHANGED
|
@@ -3,6 +3,8 @@ import { getProvider } from '../llm/index.js';
|
|
|
3
3
|
import { triageSessions } from '../llm/triage.js';
|
|
4
4
|
import { enhanceProject, refineNarrative } from '../llm/project-enhance.js';
|
|
5
5
|
import { getAnthropicApiKey, saveEnhancedData, loadEnhancedData, deleteEnhancedData, loadFreshProjectEnhanceResult, saveProjectEnhanceResult, loadProjectEnhanceResult, buildProjectFingerprint, getUploadedState, } from '../settings.js';
|
|
6
|
+
import { requireProject } from './context.js';
|
|
7
|
+
import { startSSE } from './sse.js';
|
|
6
8
|
export function createEnhanceRouter(ctx) {
|
|
7
9
|
const router = Router();
|
|
8
10
|
// Triage endpoint -- AI selects which sessions are worth showcasing (SSE stream)
|
|
@@ -11,21 +13,11 @@ export function createEnhanceRouter(ctx) {
|
|
|
11
13
|
res.status(400).json({ error: { code: 'NO_API_KEY', message: 'No Anthropic API key configured. Add one in Settings or set ANTHROPIC_API_KEY.' } });
|
|
12
14
|
return;
|
|
13
15
|
}
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
if (!proj) {
|
|
18
|
-
res.status(404).json({ error: { code: 'PROJECT_NOT_FOUND', message: 'Project not found' } });
|
|
16
|
+
const project = String(req.params.project);
|
|
17
|
+
const proj = await requireProject(ctx, project, res);
|
|
18
|
+
if (!proj)
|
|
19
19
|
return;
|
|
20
|
-
|
|
21
|
-
res.writeHead(200, {
|
|
22
|
-
'Content-Type': 'text/event-stream',
|
|
23
|
-
'Cache-Control': 'no-cache',
|
|
24
|
-
Connection: 'keep-alive',
|
|
25
|
-
});
|
|
26
|
-
const send = (event) => {
|
|
27
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
28
|
-
};
|
|
20
|
+
const send = startSSE(res);
|
|
29
21
|
try {
|
|
30
22
|
const total = proj.sessions.length;
|
|
31
23
|
const sessionsWithStats = [];
|
|
@@ -62,13 +54,11 @@ export function createEnhanceRouter(ctx) {
|
|
|
62
54
|
// Enhance a single session
|
|
63
55
|
router.post('/api/projects/:project/sessions/:id/enhance', async (req, res) => {
|
|
64
56
|
try {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const proj =
|
|
68
|
-
if (!proj)
|
|
69
|
-
res.status(404).json({ error: { code: 'PROJECT_NOT_FOUND', message: 'Project not found' } });
|
|
57
|
+
const project = String(req.params.project);
|
|
58
|
+
const id = String(req.params.id);
|
|
59
|
+
const proj = await requireProject(ctx, project, res);
|
|
60
|
+
if (!proj)
|
|
70
61
|
return;
|
|
71
|
-
}
|
|
72
62
|
const meta = proj.sessions.find((s) => s.sessionId === id);
|
|
73
63
|
if (!meta) {
|
|
74
64
|
res.status(404).json({ error: { code: 'SESSION_NOT_FOUND', message: 'Session not found' } });
|
|
@@ -124,14 +114,7 @@ export function createEnhanceRouter(ctx) {
|
|
|
124
114
|
res.status(400).json({ error: { code: 'NO_API_KEY', message: 'No Anthropic API key configured. Add one in Settings or set ANTHROPIC_API_KEY.' } });
|
|
125
115
|
return;
|
|
126
116
|
}
|
|
127
|
-
res
|
|
128
|
-
'Content-Type': 'text/event-stream',
|
|
129
|
-
'Cache-Control': 'no-cache',
|
|
130
|
-
Connection: 'keep-alive',
|
|
131
|
-
});
|
|
132
|
-
const send = (data) => {
|
|
133
|
-
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
134
|
-
};
|
|
117
|
+
const send = startSSE(res);
|
|
135
118
|
try {
|
|
136
119
|
const projects = await ctx.getProjects();
|
|
137
120
|
const proj = projects.find((p) => p.name === project || p.dirName === project);
|
|
@@ -237,7 +220,7 @@ export function createEnhanceRouter(ctx) {
|
|
|
237
220
|
});
|
|
238
221
|
// Save project enhance result explicitly
|
|
239
222
|
router.post('/api/projects/:project/enhance-save', async (req, res) => {
|
|
240
|
-
const
|
|
223
|
+
const project = String(req.params.project);
|
|
241
224
|
const { selectedSessionIds, result, title, repoUrl, projectUrl, screenshotBase64 } = req.body;
|
|
242
225
|
if (!Array.isArray(selectedSessionIds) || !result?.narrative) {
|
|
243
226
|
res.status(400).json({ error: { code: 'INVALID_INPUT', message: 'selectedSessionIds and result are required' } });
|
|
@@ -248,12 +231,9 @@ export function createEnhanceRouter(ctx) {
|
|
|
248
231
|
return;
|
|
249
232
|
}
|
|
250
233
|
try {
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
if (!proj) {
|
|
254
|
-
res.status(404).json({ error: { code: 'PROJECT_NOT_FOUND', message: 'Project not found' } });
|
|
234
|
+
const proj = await requireProject(ctx, project, res);
|
|
235
|
+
if (!proj)
|
|
255
236
|
return;
|
|
256
|
-
}
|
|
257
237
|
saveProjectEnhanceResult(proj.dirName, selectedSessionIds, result, undefined, { title, repoUrl, projectUrl, screenshotBase64 });
|
|
258
238
|
res.json({ saved: true, enhancedAt: new Date().toISOString() });
|
|
259
239
|
}
|
|
@@ -263,14 +243,11 @@ export function createEnhanceRouter(ctx) {
|
|
|
263
243
|
});
|
|
264
244
|
// Get cached project enhance result
|
|
265
245
|
router.get('/api/projects/:project/enhance-cache', async (req, res) => {
|
|
266
|
-
const
|
|
246
|
+
const project = String(req.params.project);
|
|
267
247
|
try {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
if (!proj) {
|
|
271
|
-
res.status(404).json({ error: { code: 'PROJECT_NOT_FOUND', message: 'Project not found' } });
|
|
248
|
+
const proj = await requireProject(ctx, project, res);
|
|
249
|
+
if (!proj)
|
|
272
250
|
return;
|
|
273
|
-
}
|
|
274
251
|
const cached = loadProjectEnhanceResult(proj.dirName);
|
|
275
252
|
if (!cached) {
|
|
276
253
|
res.status(404).json({ error: { code: 'NO_CACHE', message: 'No cached enhance result' } });
|
package/dist/routes/export.js
CHANGED
|
@@ -111,9 +111,14 @@ export function createExportRouter(ctx) {
|
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
const cache = data.enhanceCache ?? buildFallbackCache(data.sessions);
|
|
114
|
-
const
|
|
114
|
+
const proj = data.project;
|
|
115
115
|
const outDir = safeExportPath(outputPath, dirName, 'html');
|
|
116
|
-
const result = await exportHtml(dirName, cache, data.sessions, outDir, 'local', {
|
|
116
|
+
const result = await exportHtml(dirName, cache, data.sessions, outDir, 'local', {
|
|
117
|
+
totalFilesChanged: proj.totalFiles,
|
|
118
|
+
totalAgentDurationMinutes: proj.totalAgentDuration,
|
|
119
|
+
totalInputTokens: proj.totalInputTokens,
|
|
120
|
+
totalOutputTokens: proj.totalOutputTokens,
|
|
121
|
+
});
|
|
117
122
|
res.json(result);
|
|
118
123
|
}
|
|
119
124
|
catch (err) {
|
|
@@ -131,8 +136,13 @@ export function createExportRouter(ctx) {
|
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
138
|
const cache = data.enhanceCache ?? buildFallbackCache(data.sessions);
|
|
134
|
-
const
|
|
135
|
-
const htmlFiles = generateHtmlFiles(dirName, cache, data.sessions, 'local', {
|
|
139
|
+
const proj = data.project;
|
|
140
|
+
const htmlFiles = generateHtmlFiles(dirName, cache, data.sessions, 'local', {
|
|
141
|
+
totalFilesChanged: proj.totalFiles,
|
|
142
|
+
totalAgentDurationMinutes: proj.totalAgentDuration,
|
|
143
|
+
totalInputTokens: proj.totalInputTokens,
|
|
144
|
+
totalOutputTokens: proj.totalOutputTokens,
|
|
145
|
+
});
|
|
136
146
|
const zipBuffer = createZipBuffer(htmlFiles);
|
|
137
147
|
const filename = `${dirName.replace(/[^a-zA-Z0-9_-]/g, '_')}.zip`;
|
|
138
148
|
res.setHeader('Content-Type', 'application/zip');
|