x-ipe 1.0.23__py3-none-any.whl → 1.0.25__py3-none-any.whl
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.
- x_ipe/app.py +32 -1
- x_ipe/handlers/terminal_handlers.py +6 -0
- x_ipe/handlers/voice_handlers.py +5 -0
- x_ipe/resources/copilot-instructions.md +19 -6
- x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
- x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
- x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
- x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
- x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
- x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
- x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
- x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
- x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
- x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
- x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
- x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
- x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
- x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
- x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
- x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
- x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
- x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
- x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
- x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
- x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
- x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
- x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
- x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
- x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
- x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
- x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
- x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
- x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
- x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
- x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
- x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
- x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
- x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
- x_ipe/routes/__init__.py +2 -0
- x_ipe/routes/ideas_routes.py +289 -0
- x_ipe/routes/kb_routes.py +80 -0
- x_ipe/routes/main_routes.py +18 -0
- x_ipe/routes/project_routes.py +7 -0
- x_ipe/routes/proxy_routes.py +10 -2
- x_ipe/routes/quality_evaluation_routes.py +193 -0
- x_ipe/routes/settings_routes.py +6 -0
- x_ipe/routes/tools_routes.py +6 -0
- x_ipe/routes/tracing_routes.py +232 -0
- x_ipe/routes/uiux_feedback_routes.py +50 -0
- x_ipe/services/__init__.py +5 -0
- x_ipe/services/config_service.py +6 -0
- x_ipe/services/file_service.py +20 -0
- x_ipe/services/homepage_service.py +160 -0
- x_ipe/services/ideas_service.py +535 -2
- x_ipe/services/kb_service.py +378 -0
- x_ipe/services/proxy_service.py +37 -7
- x_ipe/services/settings_service.py +13 -0
- x_ipe/services/skills_service.py +4 -0
- x_ipe/services/terminal_service.py +24 -0
- x_ipe/services/themes_service.py +4 -0
- x_ipe/services/tools_config_service.py +4 -0
- x_ipe/services/tracing_service.py +333 -0
- x_ipe/services/uiux_feedback_service.py +148 -1
- x_ipe/services/voice_input_service_v2.py +11 -0
- x_ipe/static/css/base.css +7 -0
- x_ipe/static/css/homepage-infinity.css +330 -0
- x_ipe/static/css/kb-core.css +301 -0
- x_ipe/static/css/quality-evaluation.css +345 -0
- x_ipe/static/css/sidebar.css +14 -4
- x_ipe/static/css/terminal.css +23 -0
- x_ipe/static/css/tracing-dashboard.css +796 -0
- x_ipe/static/css/uiux-feedback.css +7 -1
- x_ipe/static/css/workplace.css +636 -0
- x_ipe/static/img/homepage-infinity-loop.png +0 -0
- x_ipe/static/js/features/confirm-dialog.js +169 -0
- x_ipe/static/js/features/folder-view.js +742 -0
- x_ipe/static/js/features/homepage-infinity.js +314 -0
- x_ipe/static/js/features/kb-core.js +371 -0
- x_ipe/static/js/features/quality-evaluation.js +387 -0
- x_ipe/static/js/features/sidebar.js +255 -12
- x_ipe/static/js/features/tracing-dashboard.js +855 -0
- x_ipe/static/js/features/tracing-graph.js +1031 -0
- x_ipe/static/js/features/tree-drag.js +227 -0
- x_ipe/static/js/features/tree-search.js +228 -0
- x_ipe/static/js/features/workplace.js +661 -33
- x_ipe/static/js/init.js +76 -0
- x_ipe/static/js/terminal-v2.js +45 -14
- x_ipe/static/js/terminal.js +50 -49
- x_ipe/static/js/uiux-feedback.js +75 -16
- x_ipe/templates/base.html +24 -0
- x_ipe/templates/index.html +10 -1
- x_ipe/templates/knowledge-base.html +110 -0
- x_ipe/templates/workplace.html +4 -0
- x_ipe/tracing/__init__.py +37 -0
- x_ipe/tracing/buffer.py +135 -0
- x_ipe/tracing/context.py +125 -0
- x_ipe/tracing/decorator.py +288 -0
- x_ipe/tracing/middleware.py +197 -0
- x_ipe/tracing/parser.py +235 -0
- x_ipe/tracing/redactor.py +111 -0
- x_ipe/tracing/writer.py +122 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/RECORD +138 -65
- x_ipe/app.py.bak +0 -1333
- x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
- x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
- x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
- x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
- x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
- x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
- x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
- {x_ipe-1.0.23.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Homepage Infinity Loop - FEATURE-026
|
|
3
|
+
*
|
|
4
|
+
* Interactive visualization of the X-IPE development lifecycle.
|
|
5
|
+
* Displays 8 stages on an infinity loop (∞) with clickable navigation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class HomepageInfinity {
|
|
9
|
+
static sidebar = null;
|
|
10
|
+
static tooltipTimeout = null;
|
|
11
|
+
|
|
12
|
+
static STAGE_MAPPING = {
|
|
13
|
+
ideation: {
|
|
14
|
+
icon: '📋',
|
|
15
|
+
label: 'REQUIREMENT',
|
|
16
|
+
theme: 'control',
|
|
17
|
+
status: 'ready',
|
|
18
|
+
section: 'requirements',
|
|
19
|
+
selector: '[data-section-id="requirements"]',
|
|
20
|
+
position: { left: '7.33%', top: '68.37%' }
|
|
21
|
+
},
|
|
22
|
+
requirement: {
|
|
23
|
+
icon: '💡',
|
|
24
|
+
label: 'IDEATION',
|
|
25
|
+
theme: 'control',
|
|
26
|
+
status: 'ready',
|
|
27
|
+
section: 'ideation',
|
|
28
|
+
selector: '[data-section-id="ideation"]',
|
|
29
|
+
position: { left: '15%', top: '17.6%' }
|
|
30
|
+
},
|
|
31
|
+
implementation: {
|
|
32
|
+
icon: '⚙️',
|
|
33
|
+
label: 'IMPLEMENT',
|
|
34
|
+
theme: 'control',
|
|
35
|
+
status: 'ready',
|
|
36
|
+
section: 'code',
|
|
37
|
+
selector: '[data-section-id="code"]',
|
|
38
|
+
position: { left: '34.44%', top: '82%' }
|
|
39
|
+
},
|
|
40
|
+
deployment: {
|
|
41
|
+
icon: '🚀',
|
|
42
|
+
label: 'DEPLOY',
|
|
43
|
+
theme: 'control',
|
|
44
|
+
status: 'tbd',
|
|
45
|
+
section: 'management',
|
|
46
|
+
selector: null,
|
|
47
|
+
position: { left: '60.33%', top: '26.69%' }
|
|
48
|
+
},
|
|
49
|
+
validation: {
|
|
50
|
+
icon: '✅',
|
|
51
|
+
label: 'VALIDATION',
|
|
52
|
+
theme: 'transparency',
|
|
53
|
+
status: 'ready',
|
|
54
|
+
section: 'quality-evaluation',
|
|
55
|
+
selector: '[data-section-id="quality-evaluation"]',
|
|
56
|
+
position: { left: '87.11%', top: '19.78%' }
|
|
57
|
+
},
|
|
58
|
+
monitoring: {
|
|
59
|
+
icon: '📊',
|
|
60
|
+
label: 'MONITORING',
|
|
61
|
+
theme: 'transparency',
|
|
62
|
+
status: 'ready',
|
|
63
|
+
section: 'tracing',
|
|
64
|
+
selector: '[data-section-id="tracing"]',
|
|
65
|
+
position: { left: '90.67%', top: '73.9%' }
|
|
66
|
+
},
|
|
67
|
+
feedback: {
|
|
68
|
+
icon: '💬',
|
|
69
|
+
label: 'FEEDBACK',
|
|
70
|
+
theme: 'transparency',
|
|
71
|
+
status: 'ready',
|
|
72
|
+
section: 'uiux-feedbacks',
|
|
73
|
+
selector: '[data-section-id="uiux-feedbacks"]',
|
|
74
|
+
position: { left: '63.33%', top: '79.04%' }
|
|
75
|
+
},
|
|
76
|
+
planning: {
|
|
77
|
+
icon: '📅',
|
|
78
|
+
label: 'PLANNING',
|
|
79
|
+
theme: 'transparency',
|
|
80
|
+
status: 'ready',
|
|
81
|
+
section: 'planning',
|
|
82
|
+
selector: '[data-section-id="planning"]',
|
|
83
|
+
position: { left: '41.56%', top: '30.25%' }
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get HTML template for homepage
|
|
89
|
+
* @returns {string} HTML template
|
|
90
|
+
*/
|
|
91
|
+
static getTemplate() {
|
|
92
|
+
const stageButtons = this._renderStageButtons();
|
|
93
|
+
|
|
94
|
+
return `
|
|
95
|
+
<div class="homepage-infinity-container">
|
|
96
|
+
<header class="homepage-header">
|
|
97
|
+
<h1>X-IPE</h1>
|
|
98
|
+
<p>An AI native integrated project environment for end to end business value delivery</p>
|
|
99
|
+
</header>
|
|
100
|
+
|
|
101
|
+
<div class="infinity-loop-container">
|
|
102
|
+
<div class="infinity-loop">
|
|
103
|
+
<img src="/static/img/homepage-infinity-loop.png"
|
|
104
|
+
alt="Development Lifecycle"
|
|
105
|
+
class="infinity-bg"
|
|
106
|
+
onerror="this.style.display='none'">
|
|
107
|
+
|
|
108
|
+
${stageButtons}
|
|
109
|
+
|
|
110
|
+
<span class="loop-label control">CONTROL</span>
|
|
111
|
+
<span class="loop-label transparency">TRANSPARENCY</span>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<footer class="homepage-legend">
|
|
116
|
+
<span class="legend-control">● Control (What AI Does)</span>
|
|
117
|
+
<span class="legend-transparency">● Transparency (What We See)</span>
|
|
118
|
+
</footer>
|
|
119
|
+
|
|
120
|
+
<div class="homepage-tooltip" id="homepage-tooltip" style="display: none;">
|
|
121
|
+
<span class="tooltip-text"></span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Render stage buttons HTML
|
|
129
|
+
* @returns {string} Stage buttons HTML
|
|
130
|
+
*/
|
|
131
|
+
static _renderStageButtons() {
|
|
132
|
+
const buttons = [];
|
|
133
|
+
|
|
134
|
+
for (const [stageId, config] of Object.entries(this.STAGE_MAPPING)) {
|
|
135
|
+
const tbdClass = config.status === 'tbd' ? ' tbd' : '';
|
|
136
|
+
const tbdAttr = config.status === 'tbd' ? ' data-tbd="true"' : '';
|
|
137
|
+
const tbdBadge = config.status === 'tbd' ? '<span class="tbd-badge">TBD</span>' : '';
|
|
138
|
+
|
|
139
|
+
buttons.push(`
|
|
140
|
+
<button class="stage-btn ${config.theme}${tbdClass}"
|
|
141
|
+
data-stage="${stageId}"
|
|
142
|
+
data-section="${config.section}"
|
|
143
|
+
data-selector="${config.selector || ''}"
|
|
144
|
+
style="left: ${config.position.left}; top: ${config.position.top};"
|
|
145
|
+
${tbdAttr}>
|
|
146
|
+
<span class="stage-icon">${config.icon}</span>
|
|
147
|
+
<span class="stage-label">${config.label}</span>
|
|
148
|
+
${tbdBadge}
|
|
149
|
+
</button>
|
|
150
|
+
`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return buttons.join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Initialize homepage with sidebar reference
|
|
158
|
+
* @param {ProjectSidebar} sidebar - Sidebar instance for navigation
|
|
159
|
+
*/
|
|
160
|
+
static init(sidebar) {
|
|
161
|
+
this.sidebar = sidebar;
|
|
162
|
+
this._bindStageClicks();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Bind click handlers to stage buttons
|
|
167
|
+
*/
|
|
168
|
+
static _bindStageClicks() {
|
|
169
|
+
const buttons = document.querySelectorAll('.stage-btn');
|
|
170
|
+
|
|
171
|
+
buttons.forEach(btn => {
|
|
172
|
+
btn.addEventListener('click', (e) => {
|
|
173
|
+
const stage = e.currentTarget.dataset.stage;
|
|
174
|
+
this.navigateToStage(stage);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Navigate to a stage's corresponding sidebar item
|
|
181
|
+
* @param {string} stage - Stage name (e.g., 'ideation')
|
|
182
|
+
*/
|
|
183
|
+
static navigateToStage(stage) {
|
|
184
|
+
const config = this.STAGE_MAPPING[stage];
|
|
185
|
+
|
|
186
|
+
if (!config) {
|
|
187
|
+
console.warn(`Unknown stage: ${stage}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle TBD stages
|
|
192
|
+
if (config.status === 'tbd') {
|
|
193
|
+
this._showTooltip(stage, 'Coming Soon');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Navigate to sidebar item
|
|
198
|
+
if (config.selector && this.sidebar) {
|
|
199
|
+
// Expand the section first
|
|
200
|
+
this._expandSection(config.section);
|
|
201
|
+
|
|
202
|
+
// Then highlight and scroll to item
|
|
203
|
+
this._highlightItem(config.selector);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Expand a sidebar section
|
|
209
|
+
* @param {string} sectionId - Section ID to expand
|
|
210
|
+
*/
|
|
211
|
+
static _expandSection(sectionId) {
|
|
212
|
+
// Main app uses data-section-id, workplace uses data-section
|
|
213
|
+
const sectionHeader = document.querySelector(`[data-section-id="${sectionId}"] .nav-section-header`) ||
|
|
214
|
+
document.querySelector(`[data-section="${sectionId}"] .section-header`);
|
|
215
|
+
if (sectionHeader) {
|
|
216
|
+
const section = sectionHeader.closest('.nav-section, .sidebar-section');
|
|
217
|
+
// Check if section is collapsed (submenu hidden)
|
|
218
|
+
const submenu = section?.querySelector('.sidebar-submenu');
|
|
219
|
+
if (submenu && submenu.style.display === 'none') {
|
|
220
|
+
sectionHeader.click();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Also try using sidebar API if available
|
|
225
|
+
if (this.sidebar && typeof this.sidebar.expandSection === 'function') {
|
|
226
|
+
this.sidebar.expandSection(sectionId);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Highlight a sidebar item
|
|
232
|
+
* @param {string} selector - CSS selector for target item
|
|
233
|
+
*/
|
|
234
|
+
static _highlightItem(selector) {
|
|
235
|
+
// Try multiple selector patterns
|
|
236
|
+
let item = document.querySelector(selector);
|
|
237
|
+
|
|
238
|
+
// If not found, try finding by text content in sidebar
|
|
239
|
+
if (!item && selector.includes('ideas')) {
|
|
240
|
+
item = document.querySelector('[data-section-id="workplace"] .nav-section-header');
|
|
241
|
+
} else if (!item && selector.includes('requirements')) {
|
|
242
|
+
item = document.querySelector('[data-section-id="project"] .nav-section-header');
|
|
243
|
+
} else if (!item && selector.includes('planning')) {
|
|
244
|
+
item = document.querySelector('[data-section-id="management"] .nav-section-header');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!item) {
|
|
248
|
+
console.warn(`Sidebar item not found: ${selector}`);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Remove any existing highlights
|
|
253
|
+
document.querySelectorAll('.homepage-highlight').forEach(el => {
|
|
254
|
+
el.classList.remove('homepage-highlight');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Add highlight class
|
|
258
|
+
item.classList.add('homepage-highlight');
|
|
259
|
+
|
|
260
|
+
// Scroll into view
|
|
261
|
+
item.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
262
|
+
|
|
263
|
+
// Remove highlight after delay (3 seconds)
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
item.classList.remove('homepage-highlight');
|
|
266
|
+
}, 3000);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Show tooltip near a stage button
|
|
271
|
+
* @param {string} stage - Stage name
|
|
272
|
+
* @param {string} message - Tooltip message
|
|
273
|
+
*/
|
|
274
|
+
static _showTooltip(stage, message) {
|
|
275
|
+
const tooltip = document.getElementById('homepage-tooltip');
|
|
276
|
+
const button = document.querySelector(`[data-stage="${stage}"]`);
|
|
277
|
+
|
|
278
|
+
if (!tooltip || !button) return;
|
|
279
|
+
|
|
280
|
+
// Set message
|
|
281
|
+
tooltip.querySelector('.tooltip-text').textContent = message;
|
|
282
|
+
|
|
283
|
+
// Position near button
|
|
284
|
+
const rect = button.getBoundingClientRect();
|
|
285
|
+
tooltip.style.left = `${rect.left + rect.width / 2}px`;
|
|
286
|
+
tooltip.style.top = `${rect.bottom + 8}px`;
|
|
287
|
+
tooltip.style.display = 'block';
|
|
288
|
+
|
|
289
|
+
// Clear existing timeout
|
|
290
|
+
if (this.tooltipTimeout) {
|
|
291
|
+
clearTimeout(this.tooltipTimeout);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Hide after 2 seconds
|
|
295
|
+
this.tooltipTimeout = setTimeout(() => {
|
|
296
|
+
this._hideTooltip();
|
|
297
|
+
}, 2000);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Hide tooltip
|
|
302
|
+
*/
|
|
303
|
+
static _hideTooltip() {
|
|
304
|
+
const tooltip = document.getElementById('homepage-tooltip');
|
|
305
|
+
if (tooltip) {
|
|
306
|
+
tooltip.style.display = 'none';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Export for use in workplace.js
|
|
312
|
+
if (typeof window !== 'undefined') {
|
|
313
|
+
window.HomepageInfinity = HomepageInfinity;
|
|
314
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FEATURE-025-A: KB Core Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* Frontend JavaScript for Knowledge Base page.
|
|
5
|
+
* Uses two-column layout (like Ideation view) with tree on left of content area.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const kbCore = {
|
|
9
|
+
index: null,
|
|
10
|
+
topics: [],
|
|
11
|
+
searchTerm: '',
|
|
12
|
+
container: null,
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Render the Knowledge Base view in a container (like WorkplaceManager)
|
|
16
|
+
*/
|
|
17
|
+
render(container) {
|
|
18
|
+
this.container = container;
|
|
19
|
+
container.innerHTML = `
|
|
20
|
+
<div class="kb-container">
|
|
21
|
+
<div class="kb-sidebar pinned" id="kb-sidebar">
|
|
22
|
+
<div class="kb-sidebar-header">
|
|
23
|
+
<span class="kb-sidebar-title">Knowledge Base</span>
|
|
24
|
+
<button class="kb-refresh-btn" id="btn-refresh-index" title="Refresh index">
|
|
25
|
+
<i class="bi bi-arrow-clockwise"></i>
|
|
26
|
+
</button>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="kb-search-box">
|
|
29
|
+
<input type="text" class="kb-search-input" id="kb-search" placeholder="Search files...">
|
|
30
|
+
</div>
|
|
31
|
+
<div class="kb-tree" id="kb-tree">
|
|
32
|
+
<div class="kb-loading"><i class="bi bi-hourglass-split"></i> Loading...</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="kb-content" id="kb-content">
|
|
36
|
+
<div class="kb-placeholder">
|
|
37
|
+
<i class="bi bi-archive"></i>
|
|
38
|
+
<h5>Knowledge Base</h5>
|
|
39
|
+
<p class="text-muted">Select a file from the tree to view</p>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
`;
|
|
44
|
+
this.init();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the Knowledge Base view.
|
|
49
|
+
* Fetches index and renders tree + content.
|
|
50
|
+
*/
|
|
51
|
+
async init() {
|
|
52
|
+
console.log('[KB] Initializing Knowledge Base view');
|
|
53
|
+
|
|
54
|
+
// Bind event handlers
|
|
55
|
+
this.bindEvents();
|
|
56
|
+
|
|
57
|
+
// Load initial data
|
|
58
|
+
await this.loadIndex();
|
|
59
|
+
|
|
60
|
+
// Render UI
|
|
61
|
+
this.renderTree();
|
|
62
|
+
this.renderWelcome();
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Bind DOM event handlers.
|
|
67
|
+
*/
|
|
68
|
+
bindEvents() {
|
|
69
|
+
// Refresh button
|
|
70
|
+
const refreshBtn = document.getElementById('btn-refresh-index');
|
|
71
|
+
if (refreshBtn) {
|
|
72
|
+
refreshBtn.addEventListener('click', () => this.refreshIndex());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Search input
|
|
76
|
+
const searchInput = document.getElementById('kb-search');
|
|
77
|
+
if (searchInput) {
|
|
78
|
+
searchInput.addEventListener('input', (e) => {
|
|
79
|
+
this.searchTerm = e.target.value.toLowerCase();
|
|
80
|
+
this.renderTree();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Load file index from API.
|
|
87
|
+
*/
|
|
88
|
+
async loadIndex() {
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch('/api/kb/index');
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
this.index = await response.json();
|
|
93
|
+
console.log('[KB] Index loaded:', this.index.files?.length || 0, 'files');
|
|
94
|
+
} else {
|
|
95
|
+
console.error('[KB] Failed to load index:', response.status);
|
|
96
|
+
this.index = { version: '1.0', last_updated: null, files: [] };
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('[KB] Error loading index:', error);
|
|
100
|
+
this.index = { version: '1.0', last_updated: null, files: [] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Also load topics
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch('/api/kb/topics');
|
|
106
|
+
if (response.ok) {
|
|
107
|
+
const data = await response.json();
|
|
108
|
+
this.topics = data.topics || [];
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('[KB] Error loading topics:', error);
|
|
112
|
+
this.topics = [];
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Refresh index from file system.
|
|
118
|
+
*/
|
|
119
|
+
async refreshIndex() {
|
|
120
|
+
const refreshBtn = document.getElementById('btn-refresh-index');
|
|
121
|
+
if (refreshBtn) {
|
|
122
|
+
refreshBtn.disabled = true;
|
|
123
|
+
refreshBtn.innerHTML = '<i class="bi bi-hourglass-split"></i>';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch('/api/kb/index/refresh', { method: 'POST' });
|
|
128
|
+
if (response.ok) {
|
|
129
|
+
this.index = await response.json();
|
|
130
|
+
console.log('[KB] Index refreshed:', this.index.files?.length || 0, 'files');
|
|
131
|
+
|
|
132
|
+
// Reload topics
|
|
133
|
+
await this.loadIndex();
|
|
134
|
+
|
|
135
|
+
// Re-render
|
|
136
|
+
this.renderTree();
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('[KB] Error refreshing index:', error);
|
|
140
|
+
} finally {
|
|
141
|
+
if (refreshBtn) {
|
|
142
|
+
refreshBtn.disabled = false;
|
|
143
|
+
refreshBtn.innerHTML = '<i class="bi bi-arrow-clockwise"></i>';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Render the tree in kb-sidebar (within content area).
|
|
150
|
+
*/
|
|
151
|
+
renderTree() {
|
|
152
|
+
const container = document.getElementById('kb-tree');
|
|
153
|
+
if (!container) return;
|
|
154
|
+
|
|
155
|
+
const files = this.index?.files || [];
|
|
156
|
+
|
|
157
|
+
// Filter by search term
|
|
158
|
+
const filteredFiles = this.searchTerm
|
|
159
|
+
? files.filter(f =>
|
|
160
|
+
f.name.toLowerCase().includes(this.searchTerm) ||
|
|
161
|
+
f.topic?.toLowerCase().includes(this.searchTerm) ||
|
|
162
|
+
f.keywords?.some(k => k.includes(this.searchTerm))
|
|
163
|
+
)
|
|
164
|
+
: files;
|
|
165
|
+
|
|
166
|
+
// Group by folder structure
|
|
167
|
+
const landing = filteredFiles.filter(f => f.path.startsWith('landing/'));
|
|
168
|
+
const byTopic = {};
|
|
169
|
+
|
|
170
|
+
filteredFiles.forEach(f => {
|
|
171
|
+
if (f.topic && !f.path.startsWith('landing/')) {
|
|
172
|
+
if (!byTopic[f.topic]) byTopic[f.topic] = [];
|
|
173
|
+
byTopic[f.topic].push(f);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Build HTML
|
|
178
|
+
let html = '';
|
|
179
|
+
|
|
180
|
+
// Landing folder
|
|
181
|
+
html += `
|
|
182
|
+
<div class="kb-folder">
|
|
183
|
+
<div class="kb-folder-header" data-folder="landing">
|
|
184
|
+
<i class="bi bi-chevron-down"></i>
|
|
185
|
+
<i class="bi bi-inbox text-warning"></i>
|
|
186
|
+
<span>Landing (${landing.length})</span>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="kb-folder-files">
|
|
189
|
+
${landing.map(f => this.renderFileItem(f)).join('')}
|
|
190
|
+
${landing.length === 0 ? '<div class="kb-empty">No files</div>' : ''}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
// Topic folders
|
|
196
|
+
const sortedTopics = Object.keys(byTopic).sort();
|
|
197
|
+
sortedTopics.forEach(topic => {
|
|
198
|
+
const topicFiles = byTopic[topic];
|
|
199
|
+
html += `
|
|
200
|
+
<div class="kb-folder">
|
|
201
|
+
<div class="kb-folder-header" data-folder="${topic}">
|
|
202
|
+
<i class="bi bi-chevron-down"></i>
|
|
203
|
+
<i class="bi bi-folder text-info"></i>
|
|
204
|
+
<span>${topic} (${topicFiles.length})</span>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="kb-folder-files">
|
|
207
|
+
${topicFiles.map(f => this.renderFileItem(f)).join('')}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
`;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Empty topics placeholder
|
|
214
|
+
if (sortedTopics.length === 0 && landing.length === 0) {
|
|
215
|
+
html += '<div class="kb-empty-state"><i class="bi bi-archive"></i><p>No files in Knowledge Base</p></div>';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
container.innerHTML = html;
|
|
219
|
+
|
|
220
|
+
// Bind folder toggle events
|
|
221
|
+
container.querySelectorAll('.kb-folder-header').forEach(header => {
|
|
222
|
+
header.addEventListener('click', (e) => {
|
|
223
|
+
const folder = e.currentTarget.closest('.kb-folder');
|
|
224
|
+
folder.classList.toggle('collapsed');
|
|
225
|
+
const icon = e.currentTarget.querySelector('i:first-child');
|
|
226
|
+
icon.classList.toggle('bi-chevron-down');
|
|
227
|
+
icon.classList.toggle('bi-chevron-right');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Bind file click events
|
|
232
|
+
container.querySelectorAll('.kb-file-item').forEach(item => {
|
|
233
|
+
item.addEventListener('click', () => {
|
|
234
|
+
// Remove active from others
|
|
235
|
+
container.querySelectorAll('.kb-file-item').forEach(i => i.classList.remove('active'));
|
|
236
|
+
item.classList.add('active');
|
|
237
|
+
|
|
238
|
+
const path = item.dataset.path;
|
|
239
|
+
this.showFilePreview(path);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Render a single file item in the sidebar.
|
|
246
|
+
*/
|
|
247
|
+
renderFileItem(file) {
|
|
248
|
+
const icon = this.getFileIcon(file.type);
|
|
249
|
+
return `
|
|
250
|
+
<div class="kb-file-item" data-path="${file.path}" title="${file.name}">
|
|
251
|
+
<i class="bi ${icon}"></i>
|
|
252
|
+
<span class="kb-file-name">${file.name}</span>
|
|
253
|
+
</div>
|
|
254
|
+
`;
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get Bootstrap icon class for file type.
|
|
259
|
+
*/
|
|
260
|
+
getFileIcon(type) {
|
|
261
|
+
const icons = {
|
|
262
|
+
'pdf': 'bi-file-earmark-pdf text-danger',
|
|
263
|
+
'markdown': 'bi-markdown text-info',
|
|
264
|
+
'text': 'bi-file-text',
|
|
265
|
+
'docx': 'bi-file-earmark-word text-primary',
|
|
266
|
+
'xlsx': 'bi-file-earmark-excel text-success',
|
|
267
|
+
'python': 'bi-filetype-py text-warning',
|
|
268
|
+
'javascript': 'bi-filetype-js text-warning',
|
|
269
|
+
'typescript': 'bi-filetype-tsx text-primary',
|
|
270
|
+
'java': 'bi-filetype-java text-danger',
|
|
271
|
+
'json': 'bi-filetype-json text-warning',
|
|
272
|
+
'html': 'bi-filetype-html text-danger',
|
|
273
|
+
'css': 'bi-filetype-css text-info',
|
|
274
|
+
'image': 'bi-file-image text-success',
|
|
275
|
+
'unknown': 'bi-file-earmark'
|
|
276
|
+
};
|
|
277
|
+
return icons[type] || icons['unknown'];
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Render welcome/placeholder in content area.
|
|
282
|
+
*/
|
|
283
|
+
renderWelcome() {
|
|
284
|
+
const content = document.getElementById('kb-content');
|
|
285
|
+
if (!content) return;
|
|
286
|
+
|
|
287
|
+
const files = this.index?.files || [];
|
|
288
|
+
const landing = files.filter(f => f.path.startsWith('landing/'));
|
|
289
|
+
|
|
290
|
+
content.innerHTML = `
|
|
291
|
+
<div class="kb-welcome">
|
|
292
|
+
<div class="kb-stats mb-4">
|
|
293
|
+
<span class="badge bg-secondary me-2">${files.length} files</span>
|
|
294
|
+
<span class="badge bg-info">${this.topics.length} topics</span>
|
|
295
|
+
</div>
|
|
296
|
+
<div class="kb-section">
|
|
297
|
+
<h6><i class="bi bi-inbox text-warning me-2"></i>Landing (Unprocessed)</h6>
|
|
298
|
+
<div class="kb-landing-list">
|
|
299
|
+
${landing.length > 0 ? landing.map(f => `
|
|
300
|
+
<div class="kb-landing-file">
|
|
301
|
+
<i class="bi ${this.getFileIcon(f.type)}"></i>
|
|
302
|
+
<span>${f.name}</span>
|
|
303
|
+
<span class="text-muted small ms-auto">${this.formatSize(f.size)}</span>
|
|
304
|
+
</div>
|
|
305
|
+
`).join('') : '<p class="text-muted small">No files in landing folder</p>'}
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="kb-section mt-4">
|
|
309
|
+
<h6><i class="bi bi-folder text-info me-2"></i>Topics</h6>
|
|
310
|
+
<div class="kb-topics-grid">
|
|
311
|
+
${this.topics.length > 0 ? this.topics.map(topic => {
|
|
312
|
+
const topicFiles = files.filter(f => f.topic === topic);
|
|
313
|
+
return `
|
|
314
|
+
<div class="kb-topic-card">
|
|
315
|
+
<i class="bi bi-folder me-2"></i>${topic}
|
|
316
|
+
<span class="badge bg-secondary ms-auto">${topicFiles.length}</span>
|
|
317
|
+
</div>
|
|
318
|
+
`;
|
|
319
|
+
}).join('') : '<p class="text-muted small">No topics created yet</p>'}
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
`;
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Show file preview in content area.
|
|
328
|
+
*/
|
|
329
|
+
async showFilePreview(path) {
|
|
330
|
+
const content = document.getElementById('kb-content');
|
|
331
|
+
if (!content) return;
|
|
332
|
+
|
|
333
|
+
const file = this.index?.files?.find(f => f.path === path);
|
|
334
|
+
if (!file) {
|
|
335
|
+
content.innerHTML = '<div class="kb-error">File not found</div>';
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
content.innerHTML = `
|
|
340
|
+
<div class="kb-file-preview">
|
|
341
|
+
<div class="kb-file-header">
|
|
342
|
+
<i class="bi ${this.getFileIcon(file.type)} fs-4"></i>
|
|
343
|
+
<div class="kb-file-info">
|
|
344
|
+
<h6 class="mb-0">${file.name}</h6>
|
|
345
|
+
<small class="text-muted">${file.path} · ${this.formatSize(file.size)}</small>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="kb-file-meta mt-3">
|
|
349
|
+
<span class="badge bg-secondary me-1">${file.type}</span>
|
|
350
|
+
${file.topic ? `<span class="badge bg-info me-1">${file.topic}</span>` : ''}
|
|
351
|
+
${(file.keywords || []).map(k => `<span class="badge bg-outline-secondary me-1">${k}</span>`).join('')}
|
|
352
|
+
</div>
|
|
353
|
+
<div class="kb-file-content mt-3">
|
|
354
|
+
<p class="text-muted small">File preview coming in a future release.</p>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
`;
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Format file size for display.
|
|
362
|
+
*/
|
|
363
|
+
formatSize(bytes) {
|
|
364
|
+
if (bytes < 1024) return bytes + ' B';
|
|
365
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
366
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Export for use in template
|
|
371
|
+
window.kbCore = kbCore;
|