@lincoln504/pi-research 0.1.13 → 1.0.1
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/LICENSE +1 -1
- package/README.md +55 -138
- package/dist/cli.mjs +23375 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +130 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +211 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +122 -0
- package/docs/THIRD-PARTY-NOTICES.md +66 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1195 -0
- package/src/config.ts +1032 -155
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +258 -11
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -212
- package/src/infrastructure/searxng-lifecycle.ts +0 -644
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research Panel Color Utilities
|
|
3
|
+
*
|
|
4
|
+
* Color parsing, conversion, and gradient generation for wave animation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse ANSI foreground color escape code to extract color index or RGB values.
|
|
9
|
+
*
|
|
10
|
+
* Returns:
|
|
11
|
+
* - { type: '256', index: N } for \x1b[38;5;Nm
|
|
12
|
+
* - { type: 'rgb', r, g, b } for \x1b[38;2;r;g;bm
|
|
13
|
+
* - { type: 'basic', index: N } for \x1b[30-37m
|
|
14
|
+
* - null if parse fails
|
|
15
|
+
*/
|
|
16
|
+
export function parseAnsiFgColor(
|
|
17
|
+
ansiCode: string
|
|
18
|
+
): { type: '256' | 'rgb' | 'basic'; index?: number; r?: number; g?: number; b?: number } | null {
|
|
19
|
+
// Match \x1b[38;5;N... (256-color)
|
|
20
|
+
// eslint-disable-next-line no-control-regex
|
|
21
|
+
const match256 = ansiCode.match(/\x1b\[38;5;(\d+)/);
|
|
22
|
+
if (match256 && match256[1] !== undefined) {
|
|
23
|
+
return { type: '256', index: parseInt(match256[1], 10) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Match \x1b[38;2;r;g;b... (truecolor)
|
|
27
|
+
// eslint-disable-next-line no-control-regex
|
|
28
|
+
const matchRgb = ansiCode.match(/\x1b\[38;2;(\d+);(\d+);(\d+)/);
|
|
29
|
+
if (matchRgb && matchRgb[1] !== undefined && matchRgb[2] !== undefined && matchRgb[3] !== undefined) {
|
|
30
|
+
return {
|
|
31
|
+
type: 'rgb',
|
|
32
|
+
r: parseInt(matchRgb[1], 10),
|
|
33
|
+
g: parseInt(matchRgb[2], 10),
|
|
34
|
+
b: parseInt(matchRgb[3], 10),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Match basic colors \x1b[30-37...
|
|
39
|
+
// eslint-disable-next-line no-control-regex
|
|
40
|
+
const matchBasic = ansiCode.match(/\x1b\[3([0-7])/);
|
|
41
|
+
if (matchBasic && matchBasic[1] !== undefined) {
|
|
42
|
+
return { type: 'basic', index: parseInt(matchBasic[1], 10) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert RGB to nearest 256-color cube or grayscale index.
|
|
50
|
+
*
|
|
51
|
+
* Algorithm:
|
|
52
|
+
* 1. Convert RGB to 6x6x6 cube coordinates (0-5 each)
|
|
53
|
+
* 2. Find closest grayscale (232-255)
|
|
54
|
+
* 3. Return whichever is closer (with preference for cube if saturation > threshold)
|
|
55
|
+
*/
|
|
56
|
+
export function rgbTo256(r: number, g: number, b: number): number {
|
|
57
|
+
// 6x6x6 cube values
|
|
58
|
+
const CUBE_VALUES = [0, 95, 135, 175, 215, 255];
|
|
59
|
+
|
|
60
|
+
// Find closest cube index
|
|
61
|
+
const findClosest = (value: number) => {
|
|
62
|
+
let minDist = Infinity;
|
|
63
|
+
let minIdx = 0;
|
|
64
|
+
for (let i = 0; i < CUBE_VALUES.length; i++) {
|
|
65
|
+
const cubeValue = CUBE_VALUES[i];
|
|
66
|
+
if (cubeValue === undefined) continue;
|
|
67
|
+
const dist = Math.abs(value - cubeValue);
|
|
68
|
+
if (dist < minDist) {
|
|
69
|
+
minDist = dist;
|
|
70
|
+
minIdx = i;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return minIdx;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const rIdx = findClosest(r);
|
|
77
|
+
const gIdx = findClosest(g);
|
|
78
|
+
const bIdx = findClosest(b);
|
|
79
|
+
|
|
80
|
+
// Cube color
|
|
81
|
+
const cubeIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;
|
|
82
|
+
const cubeR = CUBE_VALUES[rIdx] ?? 0;
|
|
83
|
+
const cubeG = CUBE_VALUES[gIdx] ?? 0;
|
|
84
|
+
const cubeB = CUBE_VALUES[bIdx] ?? 0;
|
|
85
|
+
|
|
86
|
+
// Calculate distances
|
|
87
|
+
const colorDistance = (r1: number, g1: number, b1: number, r2: number, g2: number, b2: number) => {
|
|
88
|
+
const dr = r1 - r2;
|
|
89
|
+
const dg = g1 - g2;
|
|
90
|
+
const db = b1 - b2;
|
|
91
|
+
// Weighted Euclidean distance (human eye more sensitive to green)
|
|
92
|
+
return dr * dr * 0.299 + dg * dg * 0.587 + db * db * 0.114;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const cubeDist = colorDistance(r, g, b, cubeR, cubeG, cubeB);
|
|
96
|
+
|
|
97
|
+
// Find closest grayscale (232-255)
|
|
98
|
+
const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
|
99
|
+
const GRAY_VALUES = Array.from({ length: 24 }, (_, i) => 8 + i * 10);
|
|
100
|
+
let minGrayDist = Infinity;
|
|
101
|
+
let minGrayIdx = 0;
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < GRAY_VALUES.length; i++) {
|
|
104
|
+
const dist = Math.abs(gray - (GRAY_VALUES[i] ?? 0));
|
|
105
|
+
if (dist < minGrayDist) {
|
|
106
|
+
minGrayDist = dist;
|
|
107
|
+
minGrayIdx = i;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const grayIndex = 232 + minGrayIdx;
|
|
112
|
+
const grayR = GRAY_VALUES[minGrayIdx] ?? 0;
|
|
113
|
+
const grayG = GRAY_VALUES[minGrayIdx] ?? 0;
|
|
114
|
+
const grayB = GRAY_VALUES[minGrayIdx] ?? 0;
|
|
115
|
+
const grayDist = colorDistance(r, g, b, grayR, grayG, grayB);
|
|
116
|
+
|
|
117
|
+
// Prefer cube if not essentially grayscale (low saturation)
|
|
118
|
+
const maxDist = Math.max(cubeDist, grayDist);
|
|
119
|
+
if (maxDist === 0) return cubeIndex;
|
|
120
|
+
const saturation = cubeDist / maxDist;
|
|
121
|
+
|
|
122
|
+
// If very low saturation, prefer grayscale; otherwise prefer cube
|
|
123
|
+
return saturation < 0.15 ? grayIndex : cubeIndex;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Convert 256-color index to RGB values.
|
|
128
|
+
*
|
|
129
|
+
* @param index - 256-color index (0-255)
|
|
130
|
+
* @returns RGB values or null if index is invalid
|
|
131
|
+
*/
|
|
132
|
+
export function indexToRgb(index: number): { r: number; g: number; b: number } | null {
|
|
133
|
+
if (index < 0 || index > 255) return null;
|
|
134
|
+
|
|
135
|
+
// Basic colors (0-7)
|
|
136
|
+
if (index < 8) {
|
|
137
|
+
const BASIC_COLORS: [number, number, number][] = [
|
|
138
|
+
[0, 0, 0], // 0: black
|
|
139
|
+
[128, 0, 0], // 1: red
|
|
140
|
+
[0, 128, 0], // 2: green
|
|
141
|
+
[128, 128, 0], // 3: yellow
|
|
142
|
+
[0, 0, 128], // 4: blue
|
|
143
|
+
[128, 0, 128], // 5: magenta
|
|
144
|
+
[0, 128, 128], // 6: cyan
|
|
145
|
+
[192, 192, 192], // 7: white
|
|
146
|
+
];
|
|
147
|
+
const color = BASIC_COLORS[index];
|
|
148
|
+
return color ? { r: color[0], g: color[1], b: color[2] } : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// High-intensity colors (8-15)
|
|
152
|
+
if (index < 16) {
|
|
153
|
+
const HIGH_COLORS: [number, number, number][] = [
|
|
154
|
+
[128, 128, 128], // 8: bright black (gray)
|
|
155
|
+
[255, 0, 0], // 9: bright red
|
|
156
|
+
[0, 255, 0], // 10: bright green
|
|
157
|
+
[255, 255, 0], // 11: bright yellow
|
|
158
|
+
[0, 0, 255], // 12: bright blue
|
|
159
|
+
[255, 0, 255], // 13: bright magenta
|
|
160
|
+
[0, 255, 255], // 14: bright cyan
|
|
161
|
+
[255, 255, 255], // 15: bright white
|
|
162
|
+
];
|
|
163
|
+
const highIndex = index - 8;
|
|
164
|
+
const color = HIGH_COLORS[highIndex];
|
|
165
|
+
return color ? { r: color[0], g: color[1], b: color[2] } : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 6x6x6 color cube (16-231)
|
|
169
|
+
if (index < 232) {
|
|
170
|
+
const cubeIndex = index - 16;
|
|
171
|
+
const rIdx = Math.floor(cubeIndex / 36) % 6;
|
|
172
|
+
const gIdx = Math.floor(cubeIndex / 6) % 6;
|
|
173
|
+
const bIdx = cubeIndex % 6;
|
|
174
|
+
|
|
175
|
+
const CUBE_VALUES = [0, 95, 135, 175, 215, 255];
|
|
176
|
+
return {
|
|
177
|
+
r: CUBE_VALUES[rIdx] ?? 0,
|
|
178
|
+
g: CUBE_VALUES[gIdx] ?? 0,
|
|
179
|
+
b: CUBE_VALUES[bIdx] ?? 0,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Grayscale ramp (232-255)
|
|
184
|
+
const grayIndex = index - 232;
|
|
185
|
+
const grayValue = 8 + grayIndex * 10;
|
|
186
|
+
return { r: grayValue, g: grayValue, b: grayValue };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Convert RGB to HSL
|
|
191
|
+
*/
|
|
192
|
+
export function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
|
|
193
|
+
r /= 255;
|
|
194
|
+
g /= 255;
|
|
195
|
+
b /= 255;
|
|
196
|
+
|
|
197
|
+
const max = Math.max(r, g, b);
|
|
198
|
+
const min = Math.min(r, g, b);
|
|
199
|
+
let h = 0;
|
|
200
|
+
let s = 0;
|
|
201
|
+
const l = (max + min) / 2;
|
|
202
|
+
|
|
203
|
+
if (max !== min) {
|
|
204
|
+
const d = max - min;
|
|
205
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
206
|
+
|
|
207
|
+
switch (max) {
|
|
208
|
+
case r:
|
|
209
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
210
|
+
break;
|
|
211
|
+
case g:
|
|
212
|
+
h = ((b - r) / d + 2) / 6;
|
|
213
|
+
break;
|
|
214
|
+
case b:
|
|
215
|
+
h = ((r - g) / d + 4) / 6;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { h: h * 360, s, l };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Convert HSL to RGB
|
|
225
|
+
*/
|
|
226
|
+
export function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
|
|
227
|
+
h /= 360;
|
|
228
|
+
|
|
229
|
+
let r: number;
|
|
230
|
+
let g: number;
|
|
231
|
+
let b: number;
|
|
232
|
+
|
|
233
|
+
if (s === 0) {
|
|
234
|
+
r = g = b = l;
|
|
235
|
+
} else {
|
|
236
|
+
const hue2rgb = (p: number, q: number, t: number) => {
|
|
237
|
+
if (t < 0) t += 1;
|
|
238
|
+
if (t > 1) t -= 1;
|
|
239
|
+
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
240
|
+
if (t < 1/2) return q;
|
|
241
|
+
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
242
|
+
return p;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
246
|
+
const p = 2 * l - q;
|
|
247
|
+
r = hue2rgb(p, q, h + 1/3);
|
|
248
|
+
g = hue2rgb(p, q, h);
|
|
249
|
+
b = hue2rgb(p, q, h - 1/3);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
r: Math.round(r * 255),
|
|
254
|
+
g: Math.round(g * 255),
|
|
255
|
+
b: Math.round(b * 255),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Cycle HSL saturation and lightness for gradient generation.
|
|
261
|
+
*
|
|
262
|
+
* Sawtooth wave: stepIndex=0 → full brightness/saturation → dark, desaturated end.
|
|
263
|
+
* An easeIn curve (power 2.2) spreads frames perceptually: bright drops fast,
|
|
264
|
+
* mid-dark range gets more dwell time.
|
|
265
|
+
*/
|
|
266
|
+
export function cycleHslSaturationLightness(
|
|
267
|
+
baseR: number,
|
|
268
|
+
baseG: number,
|
|
269
|
+
baseB: number,
|
|
270
|
+
stepIndex: number,
|
|
271
|
+
totalSteps: number
|
|
272
|
+
): { r: number; g: number; b: number } {
|
|
273
|
+
const hsl = rgbToHsl(baseR, baseG, baseB);
|
|
274
|
+
|
|
275
|
+
// Sawtooth: progress 0→1 over the cycle, then snaps back
|
|
276
|
+
const progress = (stepIndex % totalSteps) / totalSteps;
|
|
277
|
+
const linearFactor = 1 - progress; // 1.0 (bright) → 0.0 (dark)
|
|
278
|
+
|
|
279
|
+
// easeIn (2.2): fast initial brightness drop, slow approach to dark end —
|
|
280
|
+
// perceptually even spacing across the brightness range
|
|
281
|
+
const curvedFactor = Math.pow(linearFactor, 2.2);
|
|
282
|
+
|
|
283
|
+
// Lightness: 100% of base (bright) → 45% of base (dark)
|
|
284
|
+
const newL = hsl.l * (0.45 + 0.55 * curvedFactor);
|
|
285
|
+
// Saturation: 100% → 30% of base (strongly desaturated at dark end)
|
|
286
|
+
const newS = hsl.s * (0.30 + 0.70 * curvedFactor);
|
|
287
|
+
|
|
288
|
+
return hslToRgb(hsl.h, newS, newL);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Derive a 256-color gradient from a base color index.
|
|
293
|
+
*
|
|
294
|
+
* This is the main entry point for generating wave animation colors.
|
|
295
|
+
*/
|
|
296
|
+
export function derive256Gradient(baseIndex: number, steps: number): string[] {
|
|
297
|
+
const colors: string[] = [];
|
|
298
|
+
|
|
299
|
+
// Parse base color to RGB
|
|
300
|
+
const baseRgb = indexToRgb(baseIndex);
|
|
301
|
+
if (!baseRgb) {
|
|
302
|
+
// Fallback to a simple gradient if base color is invalid
|
|
303
|
+
for (let i = 0; i < steps; i++) {
|
|
304
|
+
colors.push(`\x1b[38;5;${Math.floor((i / steps) * 255)}m`);
|
|
305
|
+
}
|
|
306
|
+
return colors;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Generate gradient by cycling through HSL variations
|
|
310
|
+
for (let i = 0; i < steps; i++) {
|
|
311
|
+
const rgb = cycleHslSaturationLightness(baseRgb.r, baseRgb.g, baseRgb.b, i, steps);
|
|
312
|
+
const index256 = rgbTo256(rgb.r, rgb.g, rgb.b);
|
|
313
|
+
colors.push(`\x1b[38;5;${index256}m`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return colors;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Derive an RGB color gradient from base RGB values.
|
|
321
|
+
*/
|
|
322
|
+
export function deriveRgbGradient(r: number, g: number, b: number, steps: number): string[] {
|
|
323
|
+
const colors: string[] = [];
|
|
324
|
+
|
|
325
|
+
for (let i = 0; i < steps; i++) {
|
|
326
|
+
const rgb = cycleHslSaturationLightness(r, g, b, i, steps);
|
|
327
|
+
colors.push(`\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return colors;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Export internal functions for testing (with _ prefix for internal use)
|
|
334
|
+
export const _rgbTo256 = rgbTo256;
|
|
335
|
+
export const _indexToRgb = indexToRgb;
|
|
336
|
+
export const _rgbToHsl = rgbToHsl;
|
|
337
|
+
export const _hslToRgb = hslToRgb;
|
|
338
|
+
export const _parseAnsiFgColor = parseAnsiFgColor;
|
|
339
|
+
export const _derive256Gradient = derive256Gradient;
|
|
340
|
+
export const _deriveRgbGradient = deriveRgbGradient;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research Panel Formatting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Functions for formatting tokens, costs, and progress.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResearchProgress } from '../types/research-panel-types.ts';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format tokens for display
|
|
11
|
+
*/
|
|
12
|
+
export function formatTokens(tokens: number): string {
|
|
13
|
+
if (tokens < 1000) return tokens.toString();
|
|
14
|
+
if (tokens < 10000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
15
|
+
if (tokens < 1_000_000) return `${Math.round(tokens / 1000)}k`;
|
|
16
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render a percentage string for the header.
|
|
21
|
+
* Returns empty string when progress data is absent or not yet initialised.
|
|
22
|
+
* Rounds to nearest 10% increment (10, 20, 30, ..., 90, 100).
|
|
23
|
+
*/
|
|
24
|
+
export function renderProgressPct(progress: ResearchProgress | undefined): string {
|
|
25
|
+
if (!progress || progress.expected <= 0) return '';
|
|
26
|
+
const pct = Math.min(1, progress.made / progress.expected) * 100;
|
|
27
|
+
const rounded = Math.round(pct / 10) * 10;
|
|
28
|
+
return `${Math.min(100, rounded)}%`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format cost for display
|
|
33
|
+
*/
|
|
34
|
+
export function formatCost(cost: number): string {
|
|
35
|
+
if (cost === 0) return '$0.00';
|
|
36
|
+
if (cost < 0.0001) return '<$0.0001';
|
|
37
|
+
if (cost < 0.01) return `$${cost.toFixed(4)}`;
|
|
38
|
+
if (cost < 1) return `$${cost.toFixed(3)}`;
|
|
39
|
+
if (cost < 100) return `$${cost.toFixed(2)}`;
|
|
40
|
+
return `$${Math.round(cost)}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Export internal functions for testing
|
|
44
|
+
export function _formatTokens(tokens: number): string {
|
|
45
|
+
return formatTokens(tokens);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function _renderProgressPct(progress: ResearchProgress | undefined): string {
|
|
49
|
+
return renderProgressPct(progress);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function _formatCost(cost: number): string {
|
|
53
|
+
return formatCost(cost);
|
|
54
|
+
}
|