@lovelybunch/api 1.0.75-alpha.8 → 1.0.75-alpha.9
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/dist/lib/terminal/terminal-manager.d.ts +1 -3
- package/dist/lib/terminal/terminal-manager.js +1 -52
- package/dist/routes/api/v1/ai/route.js +323 -2
- package/dist/routes/api/v1/chats/[id]/index.js +2 -1
- package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
- package/dist/routes/api/v1/chats/[id]/route.js +30 -1
- package/dist/routes/api/v1/context/index.js +0 -2
- package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
- package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/index.js +1 -0
- package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/route.js +176 -0
- package/dist/routes/api/v1/mcp/index.js +78 -3
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
- package/dist/server-with-static.js +2 -0
- package/dist/server.js +2 -0
- package/package.json +4 -4
- package/static/assets/{ActivityPage-DSSML9J-.js → ActivityPage-OxRci_V2.js} +1 -1
- package/static/assets/{ApiKeysSettingsPage-Chw9PNL5.js → ApiKeysSettingsPage-C0evI19e.js} +2 -2
- package/static/assets/{ArchitectureEditPage-CIjqkpMz.js → ArchitectureEditPage-D7xcH6dY.js} +4 -4
- package/static/assets/{ArchitecturePage-Db__w054.js → ArchitecturePage-pvnlX-NW.js} +1 -1
- package/static/assets/{AuthSettingsPage-Bpooi8Z0.js → AuthSettingsPage-Bu0CZ1rY.js} +2 -2
- package/static/assets/{CallbackPage-BGLKeyjv.js → CallbackPage-D0lkjxCT.js} +1 -1
- package/static/assets/CodePage-BJ4PC5nb.js +2 -0
- package/static/assets/{CollapsibleSection-B6RO5o5R.js → CollapsibleSection-Bt_ZLnJc.js} +1 -1
- package/static/assets/DashboardPage-BiffPdmj.js +41 -0
- package/static/assets/{GitPage-DxjLaRWe.js → GitPage-TrTxZ27J.js} +2 -2
- package/static/assets/{GitSettingsPage-tKBXYAFm.js → GitSettingsPage-D7q5xQd_.js} +2 -2
- package/static/assets/{IdentityPage-D2yBjeYn.js → IdentityPage-CY0Ak2j0.js} +3 -3
- package/static/assets/{ImplementationStepsEditor-DWjDyZzP.js → ImplementationStepsEditor-Ctx0CvbU.js} +2 -2
- package/static/assets/IntegrationsSettingsPage-C2wJVdM7.js +1 -0
- package/static/assets/{JobDetailPage-BQmTHled.js → JobDetailPage-Phx_IlKX.js} +1 -1
- package/static/assets/KnowledgeDetailPage-BdTUfWqj.js +1 -0
- package/static/assets/KnowledgeEditPage-D8XK4IUf.js +1 -0
- package/static/assets/KnowledgePage-Ci9G7Br-.js +8 -0
- package/static/assets/{LoginPage-DptfKsWo.js → LoginPage-Dqxd7cTa.js} +1 -1
- package/static/assets/McpSettingsPage-10n35zXi.js +1 -0
- package/static/assets/NewKnowledgePage-BlJzzuh7.js +9 -0
- package/static/assets/{NewProposalPage-B_sDMBTK.js → NewProposalPage-BP7Ttoxk.js} +2 -2
- package/static/assets/{NewSkillPage-Cwy2MSr9.js → NewSkillPage-ByqN--mH.js} +1 -1
- package/static/assets/ProjectEditPage-DKJTY2uc.js +11 -0
- package/static/assets/{ProjectPage-DgUr4bVU.js → ProjectPage-2VblKCWz.js} +1 -1
- package/static/assets/PromptsSettingsPage-B4mOhXuo.js +1 -0
- package/static/assets/ProposalDetailPage-m3ysyzpj.js +1 -0
- package/static/assets/ProposalEditPage-3XVg_paW.js +1 -0
- package/static/assets/{ProposalsPage-C7n4-G05.js → ProposalsPage-B3u0aFFz.js} +3 -3
- package/static/assets/ResourceDetailPage-somBLUpC.js +1 -0
- package/static/assets/ResourcesPage-2BbjIWfF.js +41 -0
- package/static/assets/{RoleEditPage-Cu7ZB3yj.js → RoleEditPage-CLzX7Xhi.js} +1 -1
- package/static/assets/{RolePage-Sc-GFiL2.js → RolePage-qXWXZ2FZ.js} +1 -1
- package/static/assets/{RulesSettingsPage-DdMCzh9j.js → RulesSettingsPage-BtM7p8F6.js} +3 -3
- package/static/assets/SchedulePage-4tFcIBSs.js +4 -0
- package/static/assets/{SkillDetailPage-5sDxf3Of.js → SkillDetailPage-CroSdaju.js} +1 -1
- package/static/assets/{SkillEditPage-BDd2CtAS.js → SkillEditPage-Czlo8WWT.js} +1 -1
- package/static/assets/{SkillsPage-D2G7EfK8.js → SkillsPage-CgULbcI-.js} +2 -2
- package/static/assets/{SkillsSettingsPage-1N0JQOYc.js → SkillsSettingsPage-DKtpy7qk.js} +1 -1
- package/static/assets/SourceInput-BITn1Y15.js +1 -0
- package/static/assets/{TagInput-D_SdcypZ.js → TagInput-BK91_M1N.js} +1 -1
- package/static/assets/TerminalPage-8fwvnOo2.js +1 -0
- package/static/assets/TerminalSessionPage-BhO5U48p.js +13 -0
- package/static/assets/UserPreferencesPage-DrgYEcxO.js +1 -0
- package/static/assets/UserSettingsPage-Dj6lKLi8.js +1 -0
- package/static/assets/UtilitiesPage-Djr4qT5L.js +1 -0
- package/static/assets/{alert-BD5jo3SI.js → alert-CsMvyYoX.js} +1 -1
- package/static/assets/{arrow-down-BxcoVp6S.js → arrow-down-BZnfbld8.js} +1 -1
- package/static/assets/{arrow-left-CdM_IPng.js → arrow-left-WGBYWq3h.js} +1 -1
- package/static/assets/{arrow-up-BOJ6ob9X.js → arrow-up-BByVUPE7.js} +1 -1
- package/static/assets/{badge-DEiQk9C9.js → badge-AwLOflf5.js} +1 -1
- package/static/assets/{browser-modal-Dp1eMxt6.js → browser-modal-BzGNFfTG.js} +2 -2
- package/static/assets/{card-BCFxXzRk.js → card-SN5gKnu7.js} +1 -1
- package/static/assets/{chevron-left-C25izNzZ.js → chevron-left-C7uNq9l_.js} +1 -1
- package/static/assets/{plus-iamYJu5V.js → chevron-up-CHdIiLxL.js} +2 -2
- package/static/assets/{chevrons-up-DqbWMOjv.js → chevrons-up-TXwQuoUN.js} +1 -1
- package/static/assets/{circle-alert-CMRMPnbY.js → circle-alert-37E5gU9K.js} +1 -1
- package/static/assets/{circle-check-D5wZZPvj.js → circle-check-D02pWDME.js} +1 -1
- package/static/assets/{circle-check-big-NI18oHuP.js → circle-check-big-nY4PntB5.js} +1 -1
- package/static/assets/{circle-play-BhVU869u.js → circle-play-7EXFLo4F.js} +1 -1
- package/static/assets/{circle-x-BXDB-G_q.js → circle-x-By4JoTHB.js} +1 -1
- package/static/assets/{clipboard-DC2xmNVx.js → clipboard-BdymjxLO.js} +1 -1
- package/static/assets/{clock-CeCp7Pz1.js → clock-HDu44KTo.js} +1 -1
- package/static/assets/{download-ZF_XbTIA.js → download-Cv2G2Eg9.js} +1 -1
- package/static/assets/{droid-DqWsM2dp.js → droid-CPteN3f9.js} +1 -1
- package/static/assets/{external-link-CYBz87-P.js → external-link-DwMXcCCj.js} +1 -1
- package/static/assets/{eye-BT8MAvKY.js → eye-DYnjJzdb.js} +1 -1
- package/static/assets/{folder-git-2-BE9AIPnj.js → folder-git-2-COeWFPHS.js} +1 -1
- package/static/assets/index-9Tv-j_Ga.js +472 -0
- package/static/assets/index-GFQ5RqVh.css +2 -0
- package/static/assets/{info-DunFSp-x.js → info-BmtuPMhv.js} +1 -1
- package/static/assets/{label-vYhfrPMD.js → label-TGqbNfMO.js} +1 -1
- package/static/assets/{markdown-editor-BzZ8tCto.js → markdown-editor-ls1JPK_e.js} +1 -1
- package/static/assets/{pause-BHonpdnw.js → pause-CAWbvTiL.js} +1 -1
- package/static/assets/{play-CCo7tau2.js → play-DF_Qeu0H.js} +1 -1
- package/static/assets/{radio-group-Db-pBuyW.js → radio-group-DYTbywtK.js} +1 -1
- package/static/assets/{refresh-cw-Bg7vQzOw.js → refresh-cw-BFZxHqbC.js} +1 -1
- package/static/assets/{search-CH2zaibZ.js → search-Dr90tbch.js} +1 -1
- package/static/assets/select-Cs5qtMYV.js +1 -0
- package/static/assets/{switch-CH-VOgPo.js → switch-4TDb6YiQ.js} +1 -1
- package/static/assets/{tabs-XeRAjZYR.js → tabs-BrbEvF4V.js} +1 -1
- package/static/assets/{tag-CRP5nL5-.js → tag-DrQkepeD.js} +1 -1
- package/static/assets/{terminal-preview-DMJMuORo.js → terminal-preview-uuKF9_x4.js} +1 -1
- package/static/assets/use-terminal-BG5UXuVE.js +1 -0
- package/static/assets/video-DYA2WfbA.js +36 -0
- package/static/assets/{zap-BlzMp7dY.js → zap-h9QOsasv.js} +1 -1
- package/static/index.html +2 -2
- package/static/assets/CodePage-CrokcH-S.js +0 -2
- package/static/assets/DashboardPage-BaSQQ8Nv.js +0 -41
- package/static/assets/IntegrationsSettingsPage-Bx8-0Ig4.js +0 -1
- package/static/assets/KnowledgeDetailPage-QMU2bC3L.js +0 -1
- package/static/assets/KnowledgeEditPage-DbMJVcLc.js +0 -1
- package/static/assets/KnowledgePage-aU1GxZSZ.js +0 -8
- package/static/assets/McpSettingsPage-C3WxFwRB.js +0 -1
- package/static/assets/NewKnowledgePage-Cbiswrw_.js +0 -9
- package/static/assets/ProjectEditPage-BznWiBBc.js +0 -11
- package/static/assets/PromptsSettingsPage-CY0-870a.js +0 -1
- package/static/assets/ProposalDetailPage-K4iMXHEg.js +0 -1
- package/static/assets/ProposalEditPage-jZOtCMqP.js +0 -1
- package/static/assets/ResourcesPage-vB5-XkUv.js +0 -71
- package/static/assets/SchedulePage-Bkkf2wA0.js +0 -4
- package/static/assets/SourceInput-DlC0zwva.js +0 -1
- package/static/assets/TerminalPage--KZ7azvP.js +0 -1
- package/static/assets/TerminalSessionPage-ClvxK9ia.js +0 -13
- package/static/assets/UserPreferencesPage-CheOH7jZ.js +0 -1
- package/static/assets/UserSettingsPage-C8STDvfQ.js +0 -1
- package/static/assets/UtilitiesPage-9rTxR2md.js +0 -1
- package/static/assets/calendar-Cx5r9R7A.js +0 -6
- package/static/assets/index-DVTgTsDa.css +0 -2
- package/static/assets/index-hqVgTgRB.js +0 -462
- package/static/assets/use-terminal-DuGZuvd2.js +0 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import { getLogger, KnowledgeKinds } from '@lovelybunch/core/logging';
|
|
6
|
+
import { requireAuth } from '../../../../../middleware/auth.js';
|
|
7
|
+
// Helper function to generate a simple summary from content
|
|
8
|
+
function generateSummary(content, maxLines = 3, maxChars = 200) {
|
|
9
|
+
// Remove markdown formatting for cleaner summary
|
|
10
|
+
const cleanContent = content
|
|
11
|
+
.replace(/^#+\s+/gm, '') // Remove headings
|
|
12
|
+
.replace(/[*_`]/g, '') // Remove bold, italic, code
|
|
13
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
|
|
14
|
+
.trim();
|
|
15
|
+
// Get first few lines
|
|
16
|
+
const lines = cleanContent.split('\n').filter(line => line.trim().length > 0);
|
|
17
|
+
const summary = lines.slice(0, maxLines).join(' ');
|
|
18
|
+
// Truncate if too long
|
|
19
|
+
return summary.length > maxChars
|
|
20
|
+
? summary.substring(0, maxChars) + '...'
|
|
21
|
+
: summary;
|
|
22
|
+
}
|
|
23
|
+
const app = new Hono();
|
|
24
|
+
function getKnowledgePath() {
|
|
25
|
+
let basePath;
|
|
26
|
+
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
27
|
+
// Dev mode: use project root .nut directory
|
|
28
|
+
basePath = process.env.GAIT_DEV_ROOT;
|
|
29
|
+
}
|
|
30
|
+
else if (process.env.GAIT_DATA_PATH) {
|
|
31
|
+
// Production mode: use GAIT_DATA_PATH (set by CLI)
|
|
32
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Fallback: use current directory .nut
|
|
36
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
37
|
+
}
|
|
38
|
+
return path.join(basePath, 'knowledge');
|
|
39
|
+
}
|
|
40
|
+
function generateFilename(title) {
|
|
41
|
+
// Convert title to filename-safe format
|
|
42
|
+
return title
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters
|
|
45
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
46
|
+
.replace(/--+/g, '-') // Replace multiple hyphens with single
|
|
47
|
+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
|
|
48
|
+
+ '.md';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* GET /api/v1/knowledge/:filename
|
|
52
|
+
* Load a specific knowledge document
|
|
53
|
+
*/
|
|
54
|
+
app.get('/:filename', async (c) => {
|
|
55
|
+
try {
|
|
56
|
+
const filename = c.req.param('filename');
|
|
57
|
+
const knowledgePath = getKnowledgePath();
|
|
58
|
+
const actualFilename = filename.endsWith('.md') ? filename : `${filename}.md`;
|
|
59
|
+
const filePath = path.join(knowledgePath, actualFilename);
|
|
60
|
+
const [fileContent, stats] = await Promise.all([
|
|
61
|
+
fs.readFile(filePath, 'utf-8'),
|
|
62
|
+
fs.stat(filePath)
|
|
63
|
+
]);
|
|
64
|
+
const { data, content } = matter(fileContent);
|
|
65
|
+
// Extract title from metadata, first heading, or use filename
|
|
66
|
+
const title = data.title ||
|
|
67
|
+
content.match(/^#\s+(.+)$/m)?.[1] ||
|
|
68
|
+
actualFilename.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
|
69
|
+
const document = {
|
|
70
|
+
filename: actualFilename,
|
|
71
|
+
metadata: {
|
|
72
|
+
...data,
|
|
73
|
+
title, // Include title in metadata
|
|
74
|
+
updated: stats.mtime.toISOString(),
|
|
75
|
+
tags: data.tags || [],
|
|
76
|
+
sources: data.sources || []
|
|
77
|
+
},
|
|
78
|
+
content,
|
|
79
|
+
title
|
|
80
|
+
};
|
|
81
|
+
return c.json({
|
|
82
|
+
success: true,
|
|
83
|
+
document
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error.code === 'ENOENT') {
|
|
88
|
+
return c.json({ success: false, error: 'Knowledge document not found' }, 404);
|
|
89
|
+
}
|
|
90
|
+
console.error('Error loading knowledge document:', error);
|
|
91
|
+
return c.json({ success: false, error: 'Failed to load knowledge document' }, 500);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* PUT /api/v1/knowledge/:filename
|
|
96
|
+
* Update a specific knowledge document
|
|
97
|
+
*/
|
|
98
|
+
app.put('/:filename', async (c) => {
|
|
99
|
+
try {
|
|
100
|
+
const filename = c.req.param('filename');
|
|
101
|
+
const body = await c.req.json();
|
|
102
|
+
const knowledgePath = getKnowledgePath();
|
|
103
|
+
const actualFilename = filename.endsWith('.md') ? filename : `${filename}.md`;
|
|
104
|
+
const filePath = path.join(knowledgePath, actualFilename);
|
|
105
|
+
// Check if file exists
|
|
106
|
+
try {
|
|
107
|
+
await fs.access(filePath);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return c.json({ success: false, error: 'Knowledge document not found' }, 404);
|
|
111
|
+
}
|
|
112
|
+
// Read current content
|
|
113
|
+
const currentContent = await fs.readFile(filePath, 'utf-8');
|
|
114
|
+
const { data: currentData, content: currentMarkdown } = matter(currentContent);
|
|
115
|
+
// Extract current title from markdown content or metadata
|
|
116
|
+
const currentTitle = currentMarkdown.match(/^#\s+(.+)$/m)?.[1] ||
|
|
117
|
+
currentData.title ||
|
|
118
|
+
actualFilename.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
|
119
|
+
// Prepare updated content
|
|
120
|
+
const updatedContent = body.content !== undefined ? body.content : currentMarkdown;
|
|
121
|
+
// Determine if title has changed
|
|
122
|
+
const newTitle = body.title !== undefined ? body.title : currentTitle;
|
|
123
|
+
const titleChanged = body.title !== undefined && body.title !== currentTitle;
|
|
124
|
+
const updatedMetadata = {
|
|
125
|
+
...currentData,
|
|
126
|
+
...body.metadata,
|
|
127
|
+
title: newTitle, // Store title in metadata
|
|
128
|
+
updated: new Date().toISOString(),
|
|
129
|
+
// Ensure these are arrays
|
|
130
|
+
tags: body.metadata?.tags !== undefined ? body.metadata.tags : (currentData.tags || []),
|
|
131
|
+
sources: body.metadata?.sources !== undefined ? body.metadata.sources : (currentData.sources || [])
|
|
132
|
+
};
|
|
133
|
+
// Handle title change - might need to rename file
|
|
134
|
+
let newFilename = actualFilename;
|
|
135
|
+
let newFilePath = filePath;
|
|
136
|
+
// Only rename file if title explicitly changed
|
|
137
|
+
if (titleChanged) {
|
|
138
|
+
newFilename = generateFilename(newTitle);
|
|
139
|
+
newFilePath = path.join(knowledgePath, newFilename);
|
|
140
|
+
// Check if new filename conflicts with existing file (unless it's the same file)
|
|
141
|
+
if (newFilename !== actualFilename) {
|
|
142
|
+
try {
|
|
143
|
+
await fs.access(newFilePath);
|
|
144
|
+
return c.json({ success: false, error: 'A document with this title already exists' }, 409);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// File doesn't exist, which is what we want
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Create the updated markdown content with frontmatter
|
|
152
|
+
const fileContent = matter.stringify(updatedContent, updatedMetadata);
|
|
153
|
+
// Write to new location (or same location if filename unchanged)
|
|
154
|
+
await fs.writeFile(newFilePath, fileContent, 'utf-8');
|
|
155
|
+
// If filename changed, delete old file
|
|
156
|
+
if (newFilename !== actualFilename) {
|
|
157
|
+
await fs.unlink(filePath);
|
|
158
|
+
}
|
|
159
|
+
// Log knowledge update event
|
|
160
|
+
try {
|
|
161
|
+
const session = await requireAuth(c);
|
|
162
|
+
const actor = session ? `human:${session.email}` : "human:unknown";
|
|
163
|
+
const logger = getLogger();
|
|
164
|
+
logger.log({
|
|
165
|
+
kind: KnowledgeKinds.UPDATE,
|
|
166
|
+
actor,
|
|
167
|
+
subject: `knowledge:${newFilename}`,
|
|
168
|
+
tags: ["knowledge", ...(updatedMetadata.tags || [])],
|
|
169
|
+
payload: {
|
|
170
|
+
filename: newFilename,
|
|
171
|
+
oldFilename: actualFilename !== newFilename ? actualFilename : undefined,
|
|
172
|
+
title: newTitle,
|
|
173
|
+
category: updatedMetadata.category,
|
|
174
|
+
summary: generateSummary(updatedContent),
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (logError) {
|
|
179
|
+
console.error('Error logging knowledge update:', logError);
|
|
180
|
+
}
|
|
181
|
+
return c.json({
|
|
182
|
+
success: true,
|
|
183
|
+
document: {
|
|
184
|
+
filename: newFilename,
|
|
185
|
+
title: newTitle,
|
|
186
|
+
metadata: updatedMetadata,
|
|
187
|
+
content: updatedContent
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.error('Error updating knowledge document:', error);
|
|
193
|
+
return c.json({ success: false, error: 'Failed to update knowledge document' }, 500);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
/**
|
|
197
|
+
* DELETE /api/v1/knowledge/:filename
|
|
198
|
+
* Delete a specific knowledge document
|
|
199
|
+
*/
|
|
200
|
+
app.delete('/:filename', async (c) => {
|
|
201
|
+
try {
|
|
202
|
+
const filename = c.req.param('filename');
|
|
203
|
+
const knowledgePath = getKnowledgePath();
|
|
204
|
+
const actualFilename = filename.endsWith('.md') ? filename : `${filename}.md`;
|
|
205
|
+
const filePath = path.join(knowledgePath, actualFilename);
|
|
206
|
+
// Check if file exists
|
|
207
|
+
try {
|
|
208
|
+
await fs.access(filePath);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return c.json({ success: false, error: 'Knowledge document not found' }, 404);
|
|
212
|
+
}
|
|
213
|
+
// Read file before deletion for logging
|
|
214
|
+
let title;
|
|
215
|
+
try {
|
|
216
|
+
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
217
|
+
const { content } = matter(fileContent);
|
|
218
|
+
title = content.match(/^#\s+(.+)$/m)?.[1];
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Ignore if we can't read the file
|
|
222
|
+
}
|
|
223
|
+
// Delete the file
|
|
224
|
+
await fs.unlink(filePath);
|
|
225
|
+
// Log knowledge deletion event
|
|
226
|
+
try {
|
|
227
|
+
const session = await requireAuth(c);
|
|
228
|
+
const actor = session ? `human:${session.email}` : "human:unknown";
|
|
229
|
+
const logger = getLogger();
|
|
230
|
+
logger.log({
|
|
231
|
+
kind: KnowledgeKinds.DELETE,
|
|
232
|
+
actor,
|
|
233
|
+
subject: `knowledge:${actualFilename}`,
|
|
234
|
+
tags: ["knowledge"],
|
|
235
|
+
payload: {
|
|
236
|
+
filename: actualFilename,
|
|
237
|
+
title,
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (logError) {
|
|
242
|
+
console.error('Error logging knowledge deletion:', logError);
|
|
243
|
+
}
|
|
244
|
+
return c.json({
|
|
245
|
+
success: true,
|
|
246
|
+
message: 'Knowledge document deleted successfully'
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
console.error('Error deleting knowledge document:', error);
|
|
251
|
+
return c.json({ success: false, error: 'Failed to delete knowledge document' }, 500);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
export default app;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './route.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './route.js';
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import filenameRoute from './[filename]/index.js';
|
|
6
|
+
import { getLogger, KnowledgeKinds } from '@lovelybunch/core/logging';
|
|
7
|
+
import { requireAuth } from '../../../../middleware/auth.js';
|
|
8
|
+
// Helper function to generate a simple summary from content
|
|
9
|
+
function generateSummary(content, maxLines = 3, maxChars = 200) {
|
|
10
|
+
// Remove markdown formatting for cleaner summary
|
|
11
|
+
const cleanContent = content
|
|
12
|
+
.replace(/^#+\s+/gm, '') // Remove headings
|
|
13
|
+
.replace(/[*_`]/g, '') // Remove bold, italic, code
|
|
14
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
|
|
15
|
+
.trim();
|
|
16
|
+
// Get first few lines
|
|
17
|
+
const lines = cleanContent.split('\n').filter(line => line.trim().length > 0);
|
|
18
|
+
const summary = lines.slice(0, maxLines).join(' ');
|
|
19
|
+
// Truncate if too long
|
|
20
|
+
return summary.length > maxChars
|
|
21
|
+
? summary.substring(0, maxChars) + '...'
|
|
22
|
+
: summary;
|
|
23
|
+
}
|
|
24
|
+
const app = new Hono();
|
|
25
|
+
function getKnowledgePath() {
|
|
26
|
+
let basePath;
|
|
27
|
+
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
28
|
+
// Dev mode: use project root .nut directory
|
|
29
|
+
basePath = process.env.GAIT_DEV_ROOT;
|
|
30
|
+
}
|
|
31
|
+
else if (process.env.GAIT_DATA_PATH) {
|
|
32
|
+
// Production mode: use GAIT_DATA_PATH (set by CLI)
|
|
33
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Fallback: use current directory .nut
|
|
37
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
38
|
+
}
|
|
39
|
+
return path.join(basePath, 'knowledge');
|
|
40
|
+
}
|
|
41
|
+
function generateFilename(title) {
|
|
42
|
+
// Convert title to filename-safe format
|
|
43
|
+
return title
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters
|
|
46
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
47
|
+
.replace(/--+/g, '-') // Replace multiple hyphens with single
|
|
48
|
+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
|
|
49
|
+
+ '.md';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* GET /api/v1/knowledge
|
|
53
|
+
* Load all knowledge documents
|
|
54
|
+
*/
|
|
55
|
+
app.get('/', async (c) => {
|
|
56
|
+
try {
|
|
57
|
+
const knowledgePath = getKnowledgePath();
|
|
58
|
+
// Ensure directory exists
|
|
59
|
+
await fs.mkdir(knowledgePath, { recursive: true });
|
|
60
|
+
const files = await fs.readdir(knowledgePath);
|
|
61
|
+
const documents = await Promise.all(files
|
|
62
|
+
.filter(file => file.endsWith('.md'))
|
|
63
|
+
.map(async (file) => {
|
|
64
|
+
const filePath = path.join(knowledgePath, file);
|
|
65
|
+
const [fileContent, stats] = await Promise.all([
|
|
66
|
+
fs.readFile(filePath, 'utf-8'),
|
|
67
|
+
fs.stat(filePath)
|
|
68
|
+
]);
|
|
69
|
+
const { data, content } = matter(fileContent);
|
|
70
|
+
// Extract title from metadata, first heading, or use filename
|
|
71
|
+
const title = data.title ||
|
|
72
|
+
content.match(/^#\s+(.+)$/m)?.[1] ||
|
|
73
|
+
file.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
|
|
74
|
+
return {
|
|
75
|
+
filename: file,
|
|
76
|
+
metadata: {
|
|
77
|
+
...data,
|
|
78
|
+
title, // Include title in metadata
|
|
79
|
+
updated: stats.mtime.toISOString(),
|
|
80
|
+
tags: data.tags || [],
|
|
81
|
+
sources: data.sources || []
|
|
82
|
+
},
|
|
83
|
+
content,
|
|
84
|
+
title
|
|
85
|
+
};
|
|
86
|
+
}));
|
|
87
|
+
return c.json({
|
|
88
|
+
success: true,
|
|
89
|
+
documents: documents.sort((a, b) => a.filename.localeCompare(b.filename))
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error('Error loading knowledge documents:', error);
|
|
94
|
+
return c.json({
|
|
95
|
+
success: false,
|
|
96
|
+
error: 'Failed to load knowledge documents',
|
|
97
|
+
documents: []
|
|
98
|
+
}, 500);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* POST /api/v1/knowledge
|
|
103
|
+
* Create a new knowledge document
|
|
104
|
+
*/
|
|
105
|
+
app.post('/', async (c) => {
|
|
106
|
+
try {
|
|
107
|
+
const body = await c.req.json();
|
|
108
|
+
if (!body.title || !body.content) {
|
|
109
|
+
return c.json({ success: false, error: 'Title and content are required' }, 400);
|
|
110
|
+
}
|
|
111
|
+
const knowledgePath = getKnowledgePath();
|
|
112
|
+
await fs.mkdir(knowledgePath, { recursive: true });
|
|
113
|
+
const filename = generateFilename(body.title);
|
|
114
|
+
const filePath = path.join(knowledgePath, filename);
|
|
115
|
+
// Check if file already exists
|
|
116
|
+
try {
|
|
117
|
+
await fs.access(filePath);
|
|
118
|
+
return c.json({ success: false, error: 'A document with this title already exists' }, 409);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// File doesn't exist, which is what we want
|
|
122
|
+
}
|
|
123
|
+
// Prepare frontmatter
|
|
124
|
+
const now = new Date();
|
|
125
|
+
const frontmatter = {
|
|
126
|
+
version: '1.0',
|
|
127
|
+
title: body.title, // Store title in metadata
|
|
128
|
+
updated: now.toISOString(),
|
|
129
|
+
type: 'knowledge',
|
|
130
|
+
category: body.metadata?.category || 'general',
|
|
131
|
+
tags: body.metadata?.tags || [],
|
|
132
|
+
sources: body.metadata?.sources || [],
|
|
133
|
+
...body.metadata
|
|
134
|
+
};
|
|
135
|
+
// Create the markdown content with frontmatter
|
|
136
|
+
const fileContent = matter.stringify(body.content, frontmatter);
|
|
137
|
+
await fs.writeFile(filePath, fileContent, 'utf-8');
|
|
138
|
+
// Log knowledge creation event
|
|
139
|
+
try {
|
|
140
|
+
const session = await requireAuth(c);
|
|
141
|
+
const actor = session ? `human:${session.email}` : "human:unknown";
|
|
142
|
+
const logger = getLogger();
|
|
143
|
+
logger.log({
|
|
144
|
+
kind: KnowledgeKinds.CREATE,
|
|
145
|
+
actor,
|
|
146
|
+
subject: `knowledge:${filename}`,
|
|
147
|
+
tags: ["knowledge", ...(frontmatter.tags || [])],
|
|
148
|
+
payload: {
|
|
149
|
+
filename,
|
|
150
|
+
title: body.title,
|
|
151
|
+
category: frontmatter.category,
|
|
152
|
+
summary: generateSummary(body.content),
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (logError) {
|
|
157
|
+
console.error('Error logging knowledge creation:', logError);
|
|
158
|
+
}
|
|
159
|
+
return c.json({
|
|
160
|
+
success: true,
|
|
161
|
+
document: {
|
|
162
|
+
filename,
|
|
163
|
+
title: body.title,
|
|
164
|
+
metadata: frontmatter,
|
|
165
|
+
content: body.content
|
|
166
|
+
}
|
|
167
|
+
}, 201);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('Error creating knowledge document:', error);
|
|
171
|
+
return c.json({ success: false, error: 'Failed to create knowledge document' }, 500);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
// Register filename route for individual documents
|
|
175
|
+
app.route('/', filenameRoute);
|
|
176
|
+
export default app;
|
|
@@ -3,7 +3,7 @@ import { promises as fs } from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { ZodError } from 'zod';
|
|
5
5
|
import { listProposals, getProposal, createProposal, updateProposal, deleteProposal, getContext, updateContext, appendContext, replaceContextSection, listKnowledge, getKnowledge, createKnowledge, updateKnowledge, listEvents, } from '@lovelybunch/core';
|
|
6
|
-
import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
|
|
6
|
+
import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool, resourcesTool } from '@lovelybunch/mcp';
|
|
7
7
|
import { FileStorageAdapter } from '../../../../lib/storage/file-storage.js';
|
|
8
8
|
const app = new Hono();
|
|
9
9
|
const storage = new FileStorageAdapter();
|
|
@@ -85,7 +85,8 @@ app.get('/', async (c) => {
|
|
|
85
85
|
activity_events: eventsTool,
|
|
86
86
|
project_context: projectContextTool,
|
|
87
87
|
architecture_context: architectureContextTool,
|
|
88
|
-
role_context: roleContextTool
|
|
88
|
+
role_context: roleContextTool,
|
|
89
|
+
resources: resourcesTool
|
|
89
90
|
};
|
|
90
91
|
return c.json({
|
|
91
92
|
success: true,
|
|
@@ -113,7 +114,8 @@ app.get('/schema', async (c) => {
|
|
|
113
114
|
activity_events: eventsTool,
|
|
114
115
|
project_context: projectContextTool,
|
|
115
116
|
architecture_context: architectureContextTool,
|
|
116
|
-
role_context: roleContextTool
|
|
117
|
+
role_context: roleContextTool,
|
|
118
|
+
resources: resourcesTool
|
|
117
119
|
}
|
|
118
120
|
};
|
|
119
121
|
return c.json(schema);
|
|
@@ -151,6 +153,9 @@ app.post('/execute', async (c) => {
|
|
|
151
153
|
if (tool === 'role_context') {
|
|
152
154
|
return await executeRoleContextTool(c, args);
|
|
153
155
|
}
|
|
156
|
+
if (tool === 'resources') {
|
|
157
|
+
return await executeResourcesTool(c, args);
|
|
158
|
+
}
|
|
154
159
|
return c.json({ success: false, error: 'Unknown tool' }, 400);
|
|
155
160
|
}
|
|
156
161
|
catch (error) {
|
|
@@ -449,6 +454,76 @@ async function executeArchitectureContextTool(c, args) {
|
|
|
449
454
|
async function executeRoleContextTool(c, args) {
|
|
450
455
|
return executeContextTool(c, 'role', args);
|
|
451
456
|
}
|
|
457
|
+
// Resources tool executor — proxies to the resource API endpoints on the same server
|
|
458
|
+
async function executeResourcesTool(c, args) {
|
|
459
|
+
const { operation, query, type_filter, resource_id, prompt, model, aspect_ratio, text, voice, duration, resolution, url, tags, description } = args;
|
|
460
|
+
const port = process.env.PORT ? parseInt(process.env.PORT) : 3001;
|
|
461
|
+
const apiBase = `http://127.0.0.1:${port}`;
|
|
462
|
+
try {
|
|
463
|
+
switch (operation) {
|
|
464
|
+
case 'list': {
|
|
465
|
+
const response = await fetch(`${apiBase}/api/v1/resources`);
|
|
466
|
+
const result = await response.json();
|
|
467
|
+
if (!result.success || !result.data) {
|
|
468
|
+
return c.json({ success: false, error: 'Failed to list resources' }, 500);
|
|
469
|
+
}
|
|
470
|
+
let resources = result.data;
|
|
471
|
+
if (type_filter) {
|
|
472
|
+
const prefix = type_filter.toLowerCase();
|
|
473
|
+
resources = resources.filter((r) => r.type?.toLowerCase().startsWith(prefix));
|
|
474
|
+
}
|
|
475
|
+
if (query) {
|
|
476
|
+
const q = query.toLowerCase();
|
|
477
|
+
resources = resources.filter((r) => r.name?.toLowerCase().includes(q) ||
|
|
478
|
+
r.metadata?.description?.toLowerCase().includes(q) ||
|
|
479
|
+
r.metadata?.tags?.some((tag) => tag.toLowerCase().includes(q)));
|
|
480
|
+
}
|
|
481
|
+
return c.json({
|
|
482
|
+
success: true,
|
|
483
|
+
data: resources.map((r) => ({
|
|
484
|
+
id: r.id, name: r.name, type: r.type, size: r.size,
|
|
485
|
+
tags: r.metadata?.tags, description: r.metadata?.description
|
|
486
|
+
})),
|
|
487
|
+
count: resources.length,
|
|
488
|
+
message: query ? `Found ${resources.length} resources matching "${query}"` : `Found ${resources.length} resources`
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
case 'get': {
|
|
492
|
+
if (!resource_id) {
|
|
493
|
+
return c.json({ success: false, error: 'resource_id is required for get operation' }, 400);
|
|
494
|
+
}
|
|
495
|
+
const response = await fetch(`${apiBase}/api/v1/resources`);
|
|
496
|
+
const result = await response.json();
|
|
497
|
+
if (!result.success || !result.data) {
|
|
498
|
+
return c.json({ success: false, error: 'Failed to fetch resources' }, 500);
|
|
499
|
+
}
|
|
500
|
+
const resource = result.data.find((r) => r.id === resource_id);
|
|
501
|
+
if (!resource) {
|
|
502
|
+
return c.json({ success: false, error: `Resource '${resource_id}' not found` }, 404);
|
|
503
|
+
}
|
|
504
|
+
return c.json({ success: true, data: resource, message: `Retrieved resource ${resource.name}` });
|
|
505
|
+
}
|
|
506
|
+
// For write operations, return a not-implemented message since these require
|
|
507
|
+
// long-running Replicate calls that are better handled via the AI route's
|
|
508
|
+
// tool execution (which runs within streamText's tool loop).
|
|
509
|
+
case 'generate_image':
|
|
510
|
+
case 'generate_audio':
|
|
511
|
+
case 'generate_video':
|
|
512
|
+
case 'add_from_url': {
|
|
513
|
+
return c.json({
|
|
514
|
+
success: false,
|
|
515
|
+
error: `The '${operation}' operation is only available through the AI assistant chat. Use the assistant sidebar to generate or add resources.`
|
|
516
|
+
}, 400);
|
|
517
|
+
}
|
|
518
|
+
default:
|
|
519
|
+
return c.json({ success: false, error: `Unknown operation: ${operation}` }, 400);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
console.error('Error executing resources tool:', error);
|
|
524
|
+
return c.json({ success: false, error: error.message || 'Resources tool execution failed' }, 500);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
452
527
|
/**
|
|
453
528
|
* GET /api/v1/mcp/raw-config
|
|
454
529
|
* Returns the raw MCP configuration for editing in settings UI
|
|
@@ -4,7 +4,7 @@ export async function POST(c) {
|
|
|
4
4
|
try {
|
|
5
5
|
const proposalId = c.req.param('proposalId');
|
|
6
6
|
const body = await c.req.json().catch(() => ({}));
|
|
7
|
-
const {
|
|
7
|
+
const { startupCommand } = body;
|
|
8
8
|
if (!proposalId) {
|
|
9
9
|
return c.json({ error: 'Proposal ID is required' }, 400);
|
|
10
10
|
}
|
|
@@ -13,7 +13,7 @@ export async function POST(c) {
|
|
|
13
13
|
const shellPreference = (userSettings.preferences.terminalShell || 'bash');
|
|
14
14
|
// Create a new terminal session for this proposal
|
|
15
15
|
const terminalManager = getGlobalTerminalManager();
|
|
16
|
-
const session = await terminalManager.createSession(proposalId,
|
|
16
|
+
const session = await terminalManager.createSession(proposalId, shellPreference, startupCommand);
|
|
17
17
|
return c.json({
|
|
18
18
|
sessionId: session.id,
|
|
19
19
|
proposalId: session.proposalId,
|
|
@@ -169,6 +169,7 @@ import resources from './routes/api/v1/resources/index.js';
|
|
|
169
169
|
import resourcesById from './routes/api/v1/resources/[id]/index.js';
|
|
170
170
|
import resourcesThumbnail from './routes/api/v1/resources/[id]/thumbnail/index.js';
|
|
171
171
|
import context from './routes/api/v1/context/index.js';
|
|
172
|
+
import knowledge from './routes/api/v1/knowledge/index.js';
|
|
172
173
|
import config from './routes/api/v1/config/index.js';
|
|
173
174
|
import user from './routes/api/v1/user/index.js';
|
|
174
175
|
import skills from './routes/api/v1/skills/index.js';
|
|
@@ -197,6 +198,7 @@ app.route('/api/v1/resources', resources);
|
|
|
197
198
|
app.route('/api/v1/resources/:id', resourcesById);
|
|
198
199
|
app.route('/api/v1/resources/:id/thumbnail', resourcesThumbnail);
|
|
199
200
|
app.route('/api/v1/context', context);
|
|
201
|
+
app.route('/api/v1/knowledge', knowledge);
|
|
200
202
|
app.route('/api/v1/config', config);
|
|
201
203
|
app.route('/api/v1/user', user);
|
|
202
204
|
app.route('/api/v1/skills', skills);
|
package/dist/server.js
CHANGED
|
@@ -168,6 +168,7 @@ import resources from './routes/api/v1/resources/index.js';
|
|
|
168
168
|
import resourcesById from './routes/api/v1/resources/[id]/index.js';
|
|
169
169
|
import resourcesThumbnail from './routes/api/v1/resources/[id]/thumbnail/index.js';
|
|
170
170
|
import context from './routes/api/v1/context/index.js';
|
|
171
|
+
import knowledge from './routes/api/v1/knowledge/index.js';
|
|
171
172
|
import config from './routes/api/v1/config/index.js';
|
|
172
173
|
import user from './routes/api/v1/user/index.js';
|
|
173
174
|
import skills from './routes/api/v1/skills/index.js';
|
|
@@ -195,6 +196,7 @@ app.route('/api/v1/resources', resources);
|
|
|
195
196
|
app.route('/api/v1/resources/:id', resourcesById);
|
|
196
197
|
app.route('/api/v1/resources/:id/thumbnail', resourcesThumbnail);
|
|
197
198
|
app.route('/api/v1/context', context);
|
|
199
|
+
app.route('/api/v1/knowledge', knowledge);
|
|
198
200
|
app.route('/api/v1/config', config);
|
|
199
201
|
app.route('/api/v1/user', user);
|
|
200
202
|
app.route('/api/v1/skills', skills);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovelybunch/api",
|
|
3
|
-
"version": "1.0.75-alpha.
|
|
3
|
+
"version": "1.0.75-alpha.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/server-with-static.js",
|
|
6
6
|
"exports": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"@ai-sdk/anthropic": "^3.0.41",
|
|
38
38
|
"@hono/node-server": "^1.13.7",
|
|
39
39
|
"@hono/node-ws": "^1.0.6",
|
|
40
|
-
"@lovelybunch/core": "^1.0.75-alpha.
|
|
41
|
-
"@lovelybunch/mcp": "^1.0.75-alpha.
|
|
42
|
-
"@lovelybunch/types": "^1.0.75-alpha.
|
|
40
|
+
"@lovelybunch/core": "^1.0.75-alpha.9",
|
|
41
|
+
"@lovelybunch/mcp": "^1.0.75-alpha.9",
|
|
42
|
+
"@lovelybunch/types": "^1.0.75-alpha.9",
|
|
43
43
|
"adm-zip": "^0.5.16",
|
|
44
44
|
"ai": "^6.0.79",
|
|
45
45
|
"arctic": "^1.9.2",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,A as p,j as e,
|
|
1
|
+
import{r as a,A as p,j as e,x as y,bt as v,B as N,p as w,C as b}from"./index-9Tv-j_Ga.js";import{C as c,a as d,b as k,c as C}from"./card-SN5gKnu7.js";import{B as m}from"./badge-AwLOflf5.js";import{R as E}from"./refresh-cw-BFZxHqbC.js";const A=5e3;function $(){const[n,o]=a.useState([]),[i,x]=a.useState(!0),[h,u]=a.useState(new Set),l=a.useCallback(async()=>{x(!0);try{const s=await fetch(`${p}/api/v1/events?limit=${A}`);if(!s.ok)throw new Error("Failed to load events");const t=await s.json();o(Array.isArray(t.items)?[...t.items].reverse():[])}catch(s){console.error("Failed to load events:",s),o([])}finally{x(!1)}},[]),g=a.useCallback(s=>{u(t=>{const r=new Set(t);return r.has(s)?r.delete(s):r.add(s),r})},[]);a.useEffect(()=>{l()},[l]);const f=s=>{if(!s)return"Unknown time";try{return new Date(s).toLocaleString()}catch{return s}},j=s=>{switch(s?.toLowerCase()){case"error":return"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200";case"warn":case"warning":return"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200";case"info":return"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200";case"debug":return"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200";default:return"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"}};return i?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsx("p",{className:"text-muted-foreground",children:"View system activity and events"})]}),e.jsx(c,{children:e.jsx(d,{className:"pt-6",children:e.jsxs("div",{className:"flex items-center justify-center",children:[e.jsx(y,{className:"h-8 w-8 animate-spin text-muted-foreground"}),e.jsx("span",{className:"ml-2 text-muted-foreground",children:"Loading events..."})]})})})]}):n.length===0?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsx("p",{className:"text-muted-foreground",children:"View system activity and events"})]}),e.jsx(c,{children:e.jsx(d,{className:"pt-6",children:e.jsxs("div",{className:"text-center",children:[e.jsx(v,{className:"mx-auto h-12 w-12 text-muted-foreground"}),e.jsx("h3",{className:"mt-4 text-lg font-semibold",children:"No Events Found"}),e.jsx("p",{className:"mt-2 text-sm text-muted-foreground",children:"Activity events will appear here as they occur."})]})})})]}):e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsxs("p",{className:"text-muted-foreground",children:["View system activity and events (",n.length," events)"]})]}),e.jsxs(N,{variant:"outline",size:"sm",onClick:()=>void l(),disabled:i,children:[e.jsx(E,{className:`h-4 w-4 mr-2 ${i?"animate-spin":""}`}),"Refresh"]})]}),e.jsx("div",{className:"space-y-3",children:n.map(s=>{const t=h.has(s.seq);return e.jsxs(c,{className:"transition-colors",children:[e.jsx(k,{className:"py-3 cursor-pointer hover:bg-muted/30",onClick:()=>g(s.seq),children:e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"flex items-start gap-2 flex-1 min-w-0",children:[t?e.jsx(w,{className:"h-4 w-4 mt-0.5 text-muted-foreground shrink-0"}):e.jsx(b,{className:"h-4 w-4 mt-0.5 text-muted-foreground shrink-0"}),e.jsxs("div",{className:"space-y-1 flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs(C,{className:"text-sm font-medium",children:["#",s.seq]}),s.kind&&e.jsx(m,{variant:"outline",className:"text-xs",children:s.kind}),s.level&&e.jsx(m,{className:`text-xs ${j(s.level)}`,children:s.level})]}),s.message&&e.jsx("p",{className:`text-sm text-muted-foreground ${t?"":"truncate"}`,children:s.message})]})]}),e.jsx("span",{className:"text-xs text-muted-foreground whitespace-nowrap",children:f(s.ts)})]})}),t&&e.jsx(d,{className:"pt-0 pb-4",children:e.jsx("pre",{className:"text-xs bg-muted p-3 rounded-md overflow-x-auto",children:JSON.stringify(s,null,2)})})]},s.seq)})})]})}export{$ as default};
|